/* Engineering Preparation Platform
 *
 * Direction: a calibrated instrument, not a dashboard. This product measures
 * something and shows its workings, so the page is built like a datasheet —
 * hairlines, tick marks, tabular figures, and two colours that each mean one
 * specific thing:
 *
 *   teal  = measured. What the ledger actually contains.
 *   brass = reference. The target profile of the goal you chose.
 *
 * The gap between them is the product's entire question, so no third accent is
 * allowed to compete with it.
 *
 * Server-rendered throughout; no charting library, no JavaScript (SRS A-17).
 */

@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;700&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500&display=swap');

:root {
  --paper:    #E7EAEE;
  --panel:    #FBFCFD;
  --ink:      #12161B;
  --ink-2:    #5B6573;
  --ink-3:    #8B95A3;
  --rule:     #CBD2DA;
  --rule-2:   #DDE2E8;

  --have:     #0F4C4A;   /* measured */
  --have-dim: #0F4C4A26;
  --target:   #8A6A22;   /* reference */
  --target-dim: #8A6A2218;
  --warn:     #8C2F26;

  --display: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  --body:    'IBM Plex Sans', ui-sans-serif, system-ui, sans-serif;
  --mono:    'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace;

  --gut: clamp(1rem, 4vw, 2.5rem);
}

@media (prefers-color-scheme: dark) {
  :root {
    --paper:  #15181C;
    --panel:  #1B1F24;
    --ink:    #E7EAEE;
    --ink-2:  #98A2B0;
    --ink-3:  #6B7684;
    --rule:   #2C323A;
    --rule-2: #232930;

    --have:     #4FBFB2;
    --have-dim: #4FBFB222;
    --target:   #C9A24A;
    --target-dim: #C9A24A1A;
    --warn:     #E08C82;
  }
}

*, *::before, *::after { box-sizing: border-box; }

/* `hidden` is a UA-stylesheet rule, so ANY authored `display` declaration
   outranks it — `.pick { display: flex }` silently made the attribute inert and
   the topic filter looked like it did nothing at all. The markup uses `hidden`
   as "this is not here", never as a styling hook, so nothing should be able to
   overrule it; !important is the normalize.css remedy for exactly this, and it
   ends the whole class of bug rather than patching each selector that trips it.
   Three separate controls have been hidden this way (.pick, .pickgroup, the
   design authoring section) and only one of them had the problem. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 15px/1.6 var(--body);
  -webkit-font-smoothing: antialiased;
  /* A short page — the group list, an empty project list, a search with two
     hits — otherwise left the footer stranded in the middle of the viewport
     with a third of the screen blank beneath it. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
main { flex: 1 0 auto; }
.foot { flex-shrink: 0; }

a { color: var(--ink); }

.skip-link { position: absolute; left: -9999px; }
.skip-link:focus {
  left: 1rem; top: 1rem; z-index: 20;
  background: var(--ink); color: var(--panel);
  padding: .5rem .75rem; font: 500 12px var(--mono);
}

:focus-visible { outline: 2px solid var(--have); outline-offset: 2px; }

/* Visually hidden but still announced — `hidden` would drop it from the
   accessibility tree, which defeats the aria-labelledby that points at it. */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}

.wrap { max-width: 1180px; margin: 0 auto; padding: 0 var(--gut); }

/* ─────────────────────────────────────────────────────── instrument bar
   Two tiers on purpose. The top strip is utility — who you are, what you are
   reaching for. The row beneath it is where you can go. One strip carrying both
   is what broke: ten destinations plus an account cannot share 46 pixels, and
   letting them wrap produces two rows that look accidental instead of two that
   look meant. */

.bar { border-bottom: 1px solid var(--rule-2); background: var(--panel); }
.bar .wrap { display: flex; align-items: center; gap: 1.25rem; min-height: 44px; }

.bar .mark {
  font: 700 13px/1 var(--display);
  letter-spacing: .16em; text-transform: uppercase;
  text-decoration: none; color: var(--ink);
}
.bar .readout {
  font: 400 11px/1 var(--mono);
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3);
  display: flex; gap: 1.25rem; margin: 0;
  /* The divider belongs to the readout, not to the brand: only pages that have
     figures should show a rule after the name. Hung off .mark it left a
     dangling separator on every page that had none. */
  padding-left: 1.25rem; border-left: 1px solid var(--rule);
}
.bar .readout:empty { display: none; }
.bar .readout b { font-weight: 600; color: var(--ink-2); }
.bar .spacer { flex: 1 1 auto; }

.utility { display: flex; align-items: center; gap: 1.1rem; }
.utility a, .linkish {
  font: 400 11px/1 var(--mono);
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3); text-decoration: none; white-space: nowrap;
}
.utility a:hover, .linkish:hover { color: var(--ink); }
.utility a[aria-current="page"] { color: var(--ink); }
.utility form { margin: 0; display: flex; }
.utility .who {
  font: 500 11px/1 var(--mono); letter-spacing: .06em; color: var(--ink-2);
  padding-left: 1.1rem; border-left: 1px solid var(--rule);
}
.linkish { background: none; border: 0; padding: 0; cursor: pointer; }

/* Search belongs in the chrome, not in the nav: it is a thing you reach for
   rather than a place you go, and putting it here removes a destination. */
.barsearch input {
  width: 11rem; padding: .3rem .5rem;
  background: var(--paper); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 0;
  font: 400 12px var(--mono);
}
.barsearch input:focus {
  outline: none; border-color: var(--have); width: 15rem;
}
.barsearch input::placeholder { color: var(--ink-3); }

.navbar { border-bottom: 1px solid var(--rule); background: var(--panel); }
.navbar nav {
  display: flex; gap: 1.6rem; align-items: stretch;
  /* Scrolls rather than wraps on a narrow screen. A nav that reflows into two
     rows changes the height of every page it is on. */
  overflow-x: auto; scrollbar-width: none;
}
.navbar nav::-webkit-scrollbar { display: none; }
.navbar a {
  font: 500 11px/1 var(--mono);
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); text-decoration: none; white-space: nowrap;
  padding: .85rem 0 .8rem; border-bottom: 2px solid transparent;
}
.navbar a:hover { color: var(--ink); }
.navbar a[aria-current="page"] { color: var(--ink); border-bottom-color: var(--have); }

@media (max-width: 640px) {
  .bar .readout { display: none; }
  .barsearch input { width: 7rem; }
}

/* ──────────────────────────────────────────────────────────── masthead */

.masthead { padding: clamp(2.5rem, 7vw, 4.5rem) 0 clamp(1.5rem, 4vw, 2.5rem); }

.eyebrow {
  font: 500 11px/1 var(--mono);
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--ink-3);
  display: flex; align-items: center; gap: .6rem;
}
.eyebrow::after {
  content: ''; flex: 1 1 auto; height: 1px; background: var(--rule);
}
.eyebrow .goal { color: var(--target); font-weight: 600; }

.masthead h1 {
  font: 500 clamp(2rem, 5.5vw, 3.25rem)/1.08 var(--display);
  letter-spacing: -.025em;
  margin: 1.1rem 0 0;
  max-width: 18ch;
}
.masthead p {
  color: var(--ink-2); max-width: 56ch; margin: 1rem 0 0;
}

/* ────────────────────────────────────────────────────────── the dial
   The signature. Two shapes on one set of axes: what the ledger holds, and
   what the chosen goal asks for. Everything else on the page stays quiet so
   this reads first. */

.instrument {
  display: grid;
  grid-template-columns: minmax(0, 420px) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: start;
  background: var(--panel);
  border: 1px solid var(--rule);
  padding: clamp(1.25rem, 3vw, 2rem);
}
@media (max-width: 780px) { .instrument { grid-template-columns: 1fr; } }

.dial { display: block; width: 100%; height: auto; }
.dial-ring   { fill: none; stroke: var(--rule-2); stroke-width: 1; }
.dial-ring--outer { stroke: var(--rule); }
.dial-spoke  { stroke: var(--rule-2); stroke-width: 1; }
.dial-tick   { fill: var(--ink-3); font: 400 9px var(--mono); }

/* Reference outline: dashed, unfilled, brass. It is a target, so it must read
   as a line you have not reached rather than a second measurement. */
.dial-target {
  fill: var(--target-dim); stroke: var(--target);
  stroke-width: 1.5; stroke-dasharray: 4 3;
}
.dial-actual { fill: var(--have-dim); stroke: var(--have); stroke-width: 2; }
.dial-node   { fill: var(--have); stroke: var(--panel); stroke-width: 1.5; }
.dial-node:focus-visible { outline: none; stroke: var(--ink); stroke-width: 2; }
.dial-label {
  fill: var(--ink-2); font: 500 10px var(--mono);
  letter-spacing: .04em; text-transform: uppercase;
}

.legend {
  display: flex; gap: 1.5rem; flex-wrap: wrap;
  margin-top: .5rem; padding-top: .9rem; border-top: 1px solid var(--rule-2);
  font: 400 11px/1 var(--mono); color: var(--ink-2);
}
.legend span { display: flex; align-items: center; gap: .45rem; }
.legend i { width: 16px; height: 0; border-top-width: 2px; border-top-style: solid; }
.legend .k-have   { border-color: var(--have); }
.legend .k-target { border-color: var(--target); border-top-style: dashed; }

/* ─────────────────────────────────────────────── gap readout (§38 rule)
   Ordered by largest shortfall, because that ordering IS the recommendation
   rule: gap = target − current, largest first. The structure is the algorithm,
   not a decoration on top of it. */

/* Owns its own top spacing. It used to inherit the gap entirely from whatever
   preceded it, which held only while that neighbour had a bottom margin — a
   standalone .hint (margin-bottom 0) before one left the heading sitting on the
   paragraph's baseline. Adjacent margins collapse, so this restores the gap
   where it was missing without widening it anywhere it was already right. */
.readout-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 1rem; margin-top: 2.25rem;
  padding-bottom: .6rem; border-bottom: 1px solid var(--rule);
}
/* First in its section, the container already supplies the space. */
.wrap > .readout-head:first-child { margin-top: 0; }
.readout-head h2 {
  font: 500 12px/1 var(--mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-2); margin: 0;
}
.readout-head .note { font: 400 11px var(--mono); color: var(--ink-3); }

.gaps { list-style: none; margin: 0; padding: 0; }
.gaps li {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .35rem 1rem;
  padding: .7rem 0;
  border-bottom: 1px solid var(--rule-2);
}
.gaps .name {
  font: 500 12px/1 var(--mono); letter-spacing: .06em; text-transform: uppercase;
}
.gaps .figures {
  font: 500 12px/1 var(--mono); color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.gaps .figures b { color: var(--have); font-weight: 600; }
.gaps .figures em { color: var(--target); font-style: normal; }

/* The track is the same two-colour statement as the dial, laid flat. */
.track {
  grid-column: 1 / -1;
  height: 6px; background: var(--rule-2); position: relative; overflow: hidden;
}
.track .to-target { position: absolute; inset: 0 auto 0 0; background: var(--target-dim);
                    border-right: 1px dashed var(--target); }
.track .achieved  { position: absolute; inset: 0 auto 0 0; background: var(--have); }

.gaps li.met .figures b { color: var(--have); }
.gaps li.out-of-scope { opacity: .45; }

/* ───────────────────────────────────────────────────────── empty state
   An empty screen is an invitation, so it says what to do and what will
   happen, not "no data". */

.empty {
  margin-top: 1rem; padding: 1rem 1.15rem;
  border-left: 2px solid var(--have); background: var(--have-dim);
  font-size: 14px; color: var(--ink-2);
}
.empty strong { color: var(--ink); font-weight: 500; }

/* ─────────────────────────────────────────────────────────── datasheet */

.sheet { margin: clamp(2.5rem, 6vw, 4rem) 0 0; }
.sheet > p { color: var(--ink-2); max-width: 64ch; margin: .9rem 0 0; font-size: 14px; }

.grid {
  width: 100%; border-collapse: collapse; margin-top: 1.25rem;
  font: 400 13px var(--mono); font-variant-numeric: tabular-nums;
}
.grid caption {
  text-align: left; padding-bottom: .5rem;
  font: 500 11px var(--mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3);
}
.grid th {
  text-align: left; font-weight: 500; color: var(--ink-3);
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
  padding: 0 .8rem .5rem 0; border-bottom: 1px solid var(--rule);
}
.grid td { padding: .5rem .8rem .5rem 0; border-bottom: 1px solid var(--rule-2); }
.grid .num { text-align: right; padding-right: 0; }
.grid tbody tr:hover { background: var(--rule-2); }

.paths { list-style: none; padding: 0; margin: 1.25rem 0 0; display: grid; gap: 1px;
         background: var(--rule-2); border: 1px solid var(--rule-2); }
.paths li { background: var(--panel); padding: .9rem 1rem; }
.paths .pname { font: 500 13px var(--mono); letter-spacing: .06em; }
.paths .pdesc { color: var(--ink-2); font-size: 13px; margin-top: .2rem; }
.paths .pprofile {
  color: var(--ink-3); font: 400 11px var(--mono); margin-top: .5rem;
  letter-spacing: .04em;
}

/* ─────────────────────────────────────────────────────────────── forms */

.form-page { max-width: 27rem; margin: clamp(2rem, 6vw, 4rem) auto; }
.form-page h1 {
  font: 500 1.9rem/1.1 var(--display); letter-spacing: -.02em; margin: .8rem 0 0;
}
.form-page > p.sub { color: var(--ink-2); margin: .6rem 0 0; font-size: 14px; }
.form-page form { margin-top: 2rem; }

.form-page label {
  display: block; margin: 1.25rem 0 .35rem;
  font: 500 11px var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-2);
}
.form-page input, .form-page select {
  width: 100%; padding: .6rem .7rem;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 0;
  font: 400 15px var(--body);
}
.form-page input:focus, .form-page select:focus {
  outline: none; border-color: var(--have); box-shadow: inset 0 0 0 1px var(--have);
}
.form-page button {
  margin-top: 2rem; width: 100%; padding: .75rem;
  background: var(--have); color: var(--panel);
  border: 0; border-radius: 0; cursor: pointer;
  font: 600 12px var(--mono); letter-spacing: .14em; text-transform: uppercase;
}
.form-page button:hover { filter: brightness(1.15); }

.hint { color: var(--ink-3); font-size: 12.5px; margin: .4rem 0 0; line-height: 1.5; }
.alt { color: var(--ink-2); font-size: 13px; margin-top: 2rem;
       padding-top: 1rem; border-top: 1px solid var(--rule); }

/* Errors sit against the field that caused them. A form error the reader has
   to hunt for is a form they abandon. */
.field-error {
  color: var(--warn); font: 500 12px var(--mono); margin: .4rem 0 0;
}
.field-error::before { content: '▲ '; }

/* ───────────────────────────────────────────────────────────── footer */

.foot {
  margin-top: clamp(3rem, 8vw, 5rem);
  border-top: 1px solid var(--rule); padding: 1.25rem 0 2.5rem;
}
.foot p {
  margin: 0; color: var(--ink-3);
  font: 400 11px var(--mono); letter-spacing: .05em;
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ─────────────────────────────────────────────────────── catalog page */

.masthead--tight { padding-bottom: 1.5rem; }

.flash {
  margin: 0 0 1.5rem; padding: .8rem 1rem;
  border-left: 2px solid var(--have); background: var(--have-dim);
  font: 500 13px var(--mono); color: var(--ink);
}

.filters {
  display: flex; flex-wrap: wrap; gap: 1rem; align-items: flex-end;
  padding: 1rem 1.15rem; margin-bottom: 2rem;
  background: var(--panel); border: 1px solid var(--rule);
}
.filter { display: flex; flex-direction: column; gap: .3rem; min-width: 9rem; flex: 1 1 9rem; }
.filter label {
  font: 500 10px var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3);
}
.filters input, .filters select {
  padding: .45rem .55rem; background: var(--paper); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 0; font: 400 13px var(--body);
}
.filters input:focus, .filters select:focus {
  outline: none; border-color: var(--have); box-shadow: inset 0 0 0 1px var(--have);
}
.filters button {
  padding: .5rem 1.1rem; background: var(--have); color: var(--panel);
  border: 0; cursor: pointer;
  font: 600 11px var(--mono); letter-spacing: .12em; text-transform: uppercase;
}
.filters .clear {
  font: 400 11px var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3); text-decoration: none; padding-bottom: .55rem;
}
.filters .clear:hover { color: var(--ink); }

.grid--problems td { vertical-align: middle; height: 42px; }
.grid--problems .p-record { text-align: right; white-space: nowrap; }
.grid--problems .p-record form { display: inline; margin-left: .4rem; }
.grid--problems .p-title a { color: var(--ink); text-decoration: none; }
.grid--problems .p-title a:hover { text-decoration: underline; }
.grid--problems .p-topic { color: var(--ink-2); font-size: 12px; }
.grid--problems .p-topic .muted { color: var(--ink-3); }
/* A solved row recedes rather than disappearing — the catalog is a record of
   what you have done as much as a list of what is left. */
.grid--problems tr.is-solved .p-title a { color: var(--ink-2); }

.tag {
  font: 500 10px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  padding: .15rem .4rem; border: 1px solid var(--rule);
}
.tag--easy   { color: var(--have); border-color: var(--have); }
.tag--medium { color: var(--target); border-color: var(--target); }
.tag--hard   { color: var(--warn); border-color: var(--warn); }

.state {
  font: 500 10px var(--mono); letter-spacing: .08em; text-transform: uppercase;
}
.state--verified   { color: var(--have); }
.state--pending    { color: var(--ink-3); }
.state--unverified { color: var(--target); }
.state--rejected   { color: var(--warn); }

.mini {
  padding: .3rem .6rem; background: var(--have); color: var(--panel);
  border: 0; cursor: pointer;
  font: 600 10px var(--mono); letter-spacing: .1em; text-transform: uppercase;
}
.mini:hover { filter: brightness(1.15); }
.mini--ghost {
  background: none; color: var(--ink-3); border: 1px solid var(--rule);
}
.mini--ghost:hover { color: var(--ink); border-color: var(--ink-3); filter: none; }

/* ─────────────────────────────────────────────────────────── settings */

.handles { list-style: none; margin: 0 0 2rem; padding: 0; display: grid; gap: 1px;
           background: var(--rule-2); border: 1px solid var(--rule-2); }
.handle { background: var(--panel); padding: 1rem 1.15rem; }
.handle-head { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
.hplat {
  font: 500 10px var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); border: 1px solid var(--rule); padding: .15rem .4rem;
}
.hname { font: 500 14px var(--mono); }
.hrating {
  font: 500 10px var(--mono); letter-spacing: .08em; text-transform: uppercase;
  color: var(--target);
}
.handle-how { color: var(--ink-2); font-size: 13px; margin: .7rem 0 0; max-width: 62ch; }
.handle form { display: inline; margin: .8rem .4rem 0 0; }

/* The code is the one thing on the page the reader must copy exactly, so it is
   the only element allowed to shout. */
.code {
  display: inline-block; margin: .7rem 0 0; padding: .5rem .8rem;
  background: var(--paper); border: 1px dashed var(--target);
  font: 600 15px var(--mono); letter-spacing: .06em; color: var(--target);
  user-select: all;
}

.filters--add { align-items: flex-end; }
/* A form immediately after a bordered list merged into it and read as another
   row of the list. */
.tasks + .filters--add, .grouplist + .filters--add { margin-top: 1.5rem; }

/* ─────────────────────────────────────────── personal summary (§41) */

.summary {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1px; margin-bottom: 1.5rem;
  background: var(--rule-2); border: 1px solid var(--rule);
}
.stat { background: var(--panel); padding: 1rem 1.15rem; display: flex; flex-direction: column; }
.stat .k {
  font: 500 10px var(--mono); letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-3);
}
.stat .v {
  font: 500 2rem/1.1 var(--display); letter-spacing: -.02em; margin-top: .35rem;
  display: flex; align-items: baseline; gap: .5rem;
}
.stat .v em {
  font: 500 11px var(--mono); font-style: normal; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3);
}
.stat .sub { font: 400 11px var(--mono); color: var(--ink-3); margin-top: .35rem; }
.levelbar { height: 3px; background: var(--rule-2); margin-top: .6rem; }
.levelbar i { display: block; height: 100%; background: var(--have); }

/* ─────────────────────────────────────────────── contribution heatmap
   A grid filled column-first, so each column is one week and the rows are
   weekdays — the shape people already know how to read. */

.heat {
  display: grid; grid-template-rows: repeat(7, 11px); grid-auto-flow: column;
  grid-auto-columns: 11px; gap: 3px; margin-top: 1.25rem;
  overflow-x: auto; padding-bottom: .5rem;
}
.heat-cell { position: relative; width: 11px; height: 11px; background: var(--rule-2); }
.heat-1 { background: color-mix(in srgb, var(--have) 22%, var(--rule-2)); }
.heat-2 { background: color-mix(in srgb, var(--have) 45%, var(--rule-2)); }
.heat-3 { background: color-mix(in srgb, var(--have) 70%, var(--rule-2)); }
.heat-4 { background: var(--have); }

/* The label is real markup rather than a title attribute, so it appears
   instantly on hover and is reachable by keyboard — a tooltip you have to wait
   a second for is a tooltip nobody reads. */
.heat-tip {
  position: absolute; bottom: calc(100% + 6px); left: 50%; transform: translateX(-50%);
  white-space: nowrap; padding: .3rem .5rem; z-index: 5;
  background: var(--ink); color: var(--paper);
  font: 400 11px var(--mono); opacity: 0; pointer-events: none;
}
.heat-cell:hover .heat-tip, .heat-cell:focus-visible .heat-tip { opacity: 1; }

/* ─────────────────────────────────────────────────────────── groups */

.grouplist { list-style: none; padding: 0; margin: 0 0 2rem; display: grid; gap: 1px;
             background: var(--rule-2); border: 1px solid var(--rule-2); }
.grouplist li { background: var(--panel); padding: 1rem 1.15rem; }
.gname { font: 500 15px var(--mono); letter-spacing: .04em; color: var(--ink);
         text-decoration: none; }
.gname:hover { text-decoration: underline; }
.gmeta { font: 400 11px var(--mono); color: var(--ink-3); margin-top: .3rem; }
.gmeta b { color: var(--target); font-weight: 600; letter-spacing: .08em; }
.gdesc { color: var(--ink-2); font-size: 13px; margin-top: .4rem; }

.two-up { display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1.5rem; }

.tabs { display: flex; gap: 1.5rem; margin: 1.25rem 0 0; }
.tab {
  font: 500 11px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3); text-decoration: none; padding-bottom: .4rem;
  border-bottom: 2px solid transparent;
}
.tab:hover { color: var(--ink); }
.tab--on { color: var(--ink); border-bottom-color: var(--have); }

.grid--board .rank { color: var(--ink-3); width: 3rem; padding-right: .8rem; }
/* `.num` strips right padding so figures align on their last digit; a numeric
   cell followed by a text one therefore has nothing between them, and "1" and
   "Ashutosh" rendered as "1Ashutosh". */
.grid .num + td:not(.num) { padding-left: .8rem; }
.grid th.num + th:not(.num) { padding-left: .8rem; }
/* Your own row, marked without shouting: on a board among friends you should be
   able to find yourself instantly. */
.grid--board tr.me { background: var(--have-dim); }
.grid--board tr.me td { font-weight: 600; }

.feed { list-style: none; padding: 0; margin: 1.25rem 0 0; display: grid; gap: 1px;
        background: var(--rule-2); border: 1px solid var(--rule-2); }
.feed li { background: var(--panel); padding: .7rem 1rem; font-size: 13.5px;
           display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; }
.feed .who { font: 500 12px var(--mono); letter-spacing: .04em; }
.feed a { color: var(--ink); }

/* A member who has left still ranks in a window they were present for (A-12).
   Saying so is better than hiding them, which would let a finished month be
   rewritten by removing whoever won it. */
.gone {
  margin-left: .5rem; padding: .1rem .35rem;
  font: 500 9px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3); border: 1px solid var(--rule);
}

.tabs--sub { margin-top: .75rem; }
.tabs--sub .tab { font-size: 10px; }

.cmeta { font: 400 12px var(--mono); color: var(--ink-3); margin-top: .6rem; }
.cmeta b { color: var(--target); font-weight: 600; }
.grid--board .track { position: relative; display: block; height: 6px; width: 8rem;
                      background: var(--rule-2); }

/* ──────────────────────────────────────────────────────────────── labs */

.scenario { max-width: 62ch; }

/* ────────────────────────────────────────────────── authored prose
   Briefs and written submissions come through the Markdown renderer, so this
   has to style whatever GitHub-flavoured Markdown can produce rather than the
   two or three tags a hand-written template would emit. Kept to 68ch: a brief
   is read, and a full-width line of body text is not.

   Everything is scoped under .prose so it cannot leak into the chrome — the
   page's own <h2> is a mono section label, and a brief's <h2> is a heading in
   someone's writing. They are different things that share a tag. */

.prose { max-width: 68ch; }
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }

.prose p { margin: 0 0 .9rem; line-height: 1.65; }

.prose h1, .prose h2, .prose h3, .prose h4 {
  font-family: var(--display); font-weight: 600; line-height: 1.25;
  color: var(--ink); margin: 1.6rem 0 .6rem; letter-spacing: -0.01em;
}
.prose h1 { font-size: 22px; }
.prose h2 { font-size: 19px; }
.prose h3 { font-size: 16px; }
.prose h4 { font-size: 14px; }

.prose ul, .prose ol { margin: 0 0 .9rem; padding-left: 1.35rem; }
.prose li { margin: .25rem 0; line-height: 1.6; }
.prose li > p { margin: 0; }
/* A GFM task list draws its own box, so the bullet beside it is a second
   marker for one item. */
.prose li:has(> input[type="checkbox"]) { list-style: none; margin-left: -1.35rem; }
.prose li > input[type="checkbox"] { margin-right: .45rem; accent-color: var(--have); }

.prose code {
  font: 400 13px var(--mono); background: var(--paper);
  padding: .1rem .3rem; border: 1px solid var(--rule-2);
}
.prose pre {
  background: var(--paper); border: 1px solid var(--rule-2);
  padding: .8rem .9rem; margin: 0 0 .9rem;
  overflow-x: auto; line-height: 1.5;
}
/* The border and background belong to the <pre>; doubling them on the <code>
   inside draws a box within a box. */
.prose pre code { background: none; border: 0; padding: 0; font-size: 12.5px; }

.prose blockquote {
  margin: 0 0 .9rem; padding: .1rem 0 .1rem .9rem;
  border-left: 2px solid var(--rule); color: var(--ink-2);
}
.prose blockquote p:last-child { margin-bottom: 0; }

.prose a { color: var(--have); text-decoration: underline; text-underline-offset: 2px; }
.prose a:hover { color: var(--ink); }

.prose hr { border: 0; border-top: 1px solid var(--rule); margin: 1.5rem 0; }
.prose img { max-width: 100%; height: auto; }

/* A table can be wider than the column; it scrolls inside itself rather than
   pushing the page sideways. */
.prose table {
  border-collapse: collapse; margin: 0 0 .9rem; font-size: 13.5px;
  display: block; overflow-x: auto; max-width: 100%;
}
.prose th, .prose td {
  border: 1px solid var(--rule-2); padding: .4rem .6rem; text-align: left;
}
.prose th {
  background: var(--paper); font: 500 11px var(--mono);
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-2);
}

/* A submission sits inside the page rather than being the page, so it is
   inset — the same reading the old <pre class="result"> gave it. */
.prose--sunk {
  background: var(--panel); border: 1px solid var(--rule);
  padding: 1rem; max-width: none;
}

/* ─────────────────────────────────────────────────────────── diagrams
   Before mermaid runs — and permanently, without JavaScript — this is the
   diagram's source. It is styled as a code block because that is what it is,
   and labelled so a reader knows it is meant to be a picture. Once drawn, the
   code styling would fight the SVG, so .mermaid--drawn takes it back off. */
.prose pre.mermaid {
  font: 400 12.5px/1.5 var(--mono); color: var(--ink-2);
  white-space: pre-wrap; word-break: break-word;
  position: relative; padding-top: 1.7rem;
}
.prose pre.mermaid::before {
  content: "diagram source";
  position: absolute; top: .5rem; left: .9rem;
  font: 500 9.5px var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3);
}
.prose pre.mermaid.mermaid--drawn {
  background: none; border: 0; padding: .5rem 0 1rem;
  text-align: center; white-space: normal;
}
.prose pre.mermaid.mermaid--drawn::before { content: none; }
.prose pre.mermaid svg { max-width: 100%; height: auto; }

/* Editing the brief. Collapsed it is one quiet line; the summary carries the
   marker itself so the disclosure triangle does not sit in the mono type at a
   size the browser picks. */
.editbrief { margin-top: 1.25rem; max-width: 68ch; }
.editbrief > summary {
  display: inline-block; cursor: pointer; list-style: none;
  font: 500 11px var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); border-bottom: 1px dashed var(--rule);
  padding-bottom: .15rem;
}
.editbrief > summary::-webkit-details-marker { display: none; }
.editbrief > summary::before { content: "+ "; }
.editbrief[open] > summary::before { content: "− "; }
.editbrief > summary:hover { color: var(--have); border-bottom-color: var(--have); }
.editbrief > summary:focus-visible { outline: 2px solid var(--have); outline-offset: 3px; }
/* The form inside already carries .form-block's own top margin. */
.editbrief .form-block { margin-top: .75rem; }

/* The one-line "you can use Markdown here" note under a brief's textarea. */
.mdnote { display: block; margin-top: .3rem; color: var(--ink-3); }
/* The same one-line note, when what it says is a consequence rather than a
   capability. Brass is the reference colour: this is a fact about the rules,
   not an error the reader has made. */
.mdnote--warn { color: var(--target); }
.mdnote code {
  font: 400 11.5px var(--mono); background: var(--paper);
  padding: .05rem .25rem; border: 1px solid var(--rule-2);
}

.form-block { max-width: 62ch; margin-top: 2rem; }
.form-block label {
  display: block; margin: 1.5rem 0 .3rem;
  font: 500 11px var(--mono); letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-2);
}
/* Everything text-like, by exclusion rather than by enumeration. Listing
   [type=url] and [type=file] meant a plain <input> — the commonest kind —
   picked up no styling at all, and every new field type would repeat the
   mistake. */
.form-block textarea,
.form-block input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]):not([type="submit"]):not([type="file"]) {
  width: 100%; padding: .6rem .7rem;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 0; font: 400 14px var(--body);
}
/* A select is a control, not a text box, but it should sit on the same grid. */
.form-block select {
  width: 100%; padding: .6rem .7rem;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 0; font: 400 14px var(--body);
}
.form-block select:focus {
  outline: none; border-color: var(--have); box-shadow: inset 0 0 0 1px var(--have);
}
.form-block textarea { font-family: var(--mono); font-size: 13px; resize: vertical; }
.form-block textarea:focus, .form-block input:focus {
  outline: none; border-color: var(--have); box-shadow: inset 0 0 0 1px var(--have);
}
.form-block button {
  margin-top: 2rem; padding: .7rem 1.5rem;
  background: var(--have); color: var(--panel); border: 0; cursor: pointer;
  font: 600 12px var(--mono); letter-spacing: .14em; text-transform: uppercase;
}

.artifact { margin-top: 1rem; }
.grouplist li.is-done .gname { color: var(--ink-2); }

/* ────────────────────────────────────────────────────────── projects */

.tasks { list-style: none; padding: 0; margin: 1.25rem 0 0; display: grid; gap: 1px;
         background: var(--rule-2); border: 1px solid var(--rule-2); }
.task { background: var(--panel); padding: .7rem 1rem; display: flex; align-items: center;
        gap: .75rem; flex-wrap: wrap; }
.tbox { color: var(--ink-3); font-size: 15px; }
.task--done .tbox { color: var(--have); }
.task--done .ttitle { color: var(--ink-2); }
.ttitle { flex: 1 1 12rem; font-size: 14px; }
.tdom { font: 500 10px var(--mono); letter-spacing: .08em; text-transform: uppercase;
        color: var(--ink-3); border: 1px solid var(--rule); padding: .1rem .35rem; }
.task form { display: inline-flex; gap: .3rem; margin: 0; }
.inline-url { padding: .25rem .4rem; background: var(--paper); color: var(--ink);
              border: 1px solid var(--rule); font: 400 12px var(--body); width: 14rem; }

.reviews li { display: block; }
.rev-part { margin-top: .4rem; font-size: 13.5px; color: var(--ink-2); }
.rev-part b {
  font: 600 10px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3); margin-right: .4rem;
}
/* The rating dropdowns are 1-10 and should be small; scoped by class so the
   width does not also squash the topic picker beside them. */
.form-block select.score {
  width: 5rem; padding: .4rem .5rem; font: 400 13px var(--mono);
}

/* ────────────────────────────────────────────────── search and alerts */

.searchbar { display: flex; gap: .6rem; margin-top: 1.2rem; max-width: 40rem; }
.searchbar input {
  flex: 1; padding: .7rem .8rem; background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); font: 400 16px var(--body);
}
.searchbar input:focus { outline: none; border-color: var(--have);
                         box-shadow: inset 0 0 0 1px var(--have); }
.searchbar button {
  padding: .7rem 1.4rem; background: var(--have); color: var(--panel); border: 0;
  cursor: pointer; font: 600 12px var(--mono); letter-spacing: .12em; text-transform: uppercase;
}

.results { list-style: none; padding: 0; margin: 1.25rem 0 0; display: grid; gap: 1px;
           background: var(--rule-2); border: 1px solid var(--rule-2); }
.results li { background: var(--panel); }
.results a { display: flex; align-items: baseline; gap: .9rem; padding: .7rem 1rem;
             text-decoration: none; color: var(--ink); flex-wrap: wrap; }
.results a:hover { background: var(--paper); }
.skind {
  font: 500 10px var(--mono); letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-3); border: 1px solid var(--rule); padding: .1rem .4rem;
  min-width: 5.5rem; text-align: center;
}
.stitle { flex: 1 1 14rem; font-size: 14px; }
.ssub { color: var(--ink-3); font: 400 11px var(--mono); }

.alerts { list-style: none; padding: 0; margin: 1.25rem 0 1.5rem; display: grid; gap: 1px;
          background: var(--rule-2); border: 1px solid var(--rule-2); }
.alerts li { background: var(--panel); padding: .8rem 1rem; display: flex;
             justify-content: space-between; gap: 1rem; font-size: 14px; flex-wrap: wrap; }
/* Unread is marked with a rule down the side rather than a bolder weight: the
   list should read the same, with one extra fact about each row. */
.alerts li.unread { border-left: 2px solid var(--have); }
.alerts a { color: var(--ink); }
.alerts .when { color: var(--ink-3); font: 400 11px var(--mono); white-space: nowrap; }

.stat--alert { border-left: 2px solid var(--target); }
.stat--alert .v { color: var(--target); }
.stat--alert a { color: var(--target); }

/* A challenge's problem list reuses the task row: same shape, same reading. */
.cp { background: var(--panel); padding: .7rem 1rem; display: flex; align-items: center;
      gap: .75rem; flex-wrap: wrap; }
.cp--done .ttitle { color: var(--ink-2); }
.cp--done .tbox { color: var(--have); }
.cp .ttitle { color: var(--ink); text-decoration: none; }
.cp .ttitle:hover { text-decoration: underline; }
.cp form { display: inline; margin: 0 0 0 auto; }

/* A multi-select needs height to be usable at all; the default single-row form
   makes choosing several things a scroll-and-guess. */
.form-block select[multiple] {
  height: auto; padding: .4rem; font: 400 13px var(--body);
}
.form-block select[multiple] option { padding: .2rem .3rem; }

.filters select[multiple] { height: auto; padding: .3rem; }
.filters select[multiple] option { padding: .15rem .3rem; }

/* ─────────────────────────────────────────────────── file inputs
   NOTE the :not([type="file"]) on the general input rule above. Each :not()
   argument contributes its own specificity, so four of them make that selector
   (0,5,1) — which outranks .form-block input[type="file"] at (0,2,1) and won
   silently. Excluding file there is the fix; raising specificity here would
   have started an arms race between two rules that should not compete.
   The button inside a file input is drawn by the browser and inherits none of
   the surrounding form, so it arrives as a grey OS-styled control sitting in the
   middle of a page that has none. ::file-selector-button is the only handle on
   it; the -webkit- prefix covers Safari and older Chrome, which do not yet
   accept the standard name and would otherwise leave the default in place.

   Written as two rule blocks rather than one selector list on purpose: a browser
   that does not recognise one pseudo-element drops the ENTIRE rule it appears
   in, so grouping them would mean neither applies anywhere. */

/* Framed like the fields either side of it. A file input is a button plus a
   caption rather than a text box, but leaving it unboxed made one row of a
   three-row form look like it belonged to a different page. */
.form-block input[type="file"] {
  width: 100%;
  padding: .35rem;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: 0;
  font: 400 13px var(--mono);
  color: var(--ink-2);
  cursor: pointer;
}

.form-block input[type="file"]::file-selector-button {
  margin-right: .8rem;
  padding: .4rem .9rem;
  background: none;
  color: var(--ink-2);
  border: 1px solid var(--rule);
  border-radius: 0;
  font: 600 10px var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  cursor: pointer;
}
.form-block input[type="file"]::-webkit-file-upload-button {
  margin-right: .8rem;
  padding: .4rem .9rem;
  background: none;
  color: var(--ink-2);
  border: 1px solid var(--rule);
  border-radius: 0;
  font: 600 10px var(--mono);
  letter-spacing: .1em;
  text-transform: uppercase;
  cursor: pointer;
}

.form-block input[type="file"]:hover::file-selector-button {
  color: var(--ink);
  border-color: var(--ink-3);
}
.form-block input[type="file"]:hover::-webkit-file-upload-button {
  color: var(--ink);
  border-color: var(--ink-3);
}

/* The focus ring belongs on the whole control, not on the button alone —
   keyboard focus lands on the input. */
.form-block input[type="file"]:focus-visible {
  outline: none;
  border-color: var(--have);
  box-shadow: inset 0 0 0 1px var(--have);
}

/* ────────────────────────────────────────────────────── topic picker
   A scrolling panel of grouped checkboxes. It replaces a <select multiple>,
   which required a modifier key nobody is told about and would silently discard
   every prior selection on a plain click. */

.form-block--wide { max-width: 46rem; }

.picker {
  max-height: 20rem; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--rule);
  padding: .5rem .75rem;
}
.pickgroup { margin-bottom: .9rem; }
.pickgroup:last-child { margin-bottom: 0; }
.pickgroup h4 {
  margin: .4rem 0 .5rem;
  font: 500 10px var(--mono); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3);
  position: sticky; top: -.5rem;
  background: var(--panel); padding: .3rem 0;
}
/* Two columns where there is room, so fifty topics is a panel rather than a
   scroll. */
.pickgroup { column-count: 2; column-gap: 1.5rem; }
.pickgroup h4 { column-span: all; }

/* Scoped as `.form-block .pick`, not `.pick`. These ARE <label> elements, and
   `.form-block label` — the uppercase mono field caption — scores (0,1,1) and
   beats a bare `.pick` at (0,1,0). Every topic rendered as a shouting caption
   with 1.5rem of margin above it. Same trap as the file input: the general rule
   was more specific than the specific one. */
.form-block .pick {
  display: flex; align-items: center; gap: .5rem;
  margin: 0 0 .1rem; padding: .2rem .3rem;
  font: 400 13px/1.35 var(--body);
  letter-spacing: normal; text-transform: none;
  color: var(--ink-2);
  cursor: pointer; break-inside: avoid;
}
.form-block .pick:hover { color: var(--ink); background: var(--paper); }
.form-block .pick input {
  width: auto; margin: 0; flex: none;
  accent-color: var(--have); cursor: pointer;
}
.form-block .pick input:checked + span { color: var(--ink); font-weight: 500; }

/* ────────────────────────────────────── topic picker: enhanced state
   These are inserted by static/js/prep.js and therefore never appear without
   it — which is the point. A filter box that cannot filter is worse than no
   filter box. */

.picker-filter {
  width: 100%; margin-bottom: .5rem; padding: .5rem .7rem;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--rule); border-radius: 0;
  font: 400 13px var(--body);
}
.picker-filter:focus {
  outline: none; border-color: var(--have); box-shadow: inset 0 0 0 1px var(--have);
}

.picked {
  display: flex; flex-wrap: wrap; align-items: center; gap: .4rem;
  margin-top: .6rem; padding: .6rem .7rem;
  background: var(--panel); border: 1px solid var(--rule-2);
}
.picked--empty {
  color: var(--ink-3); font: 400 12px var(--mono);
}

/* Scoped as `.form-block .chip`: a chip IS a <button>, and `.form-block button`
   — the full-width uppercase mono submit — scores (0,1,1) against a bare
   `.chip` at (0,1,0). Third rule in this file to lose that way; the pattern is
   always a general element rule inside a container beating a class meant for
   one thing. */
.form-block .chip {
  display: inline-flex; align-items: center; gap: .4rem;
  width: auto; margin: 0; padding: .25rem .55rem;
  background: none; color: var(--ink-2);
  border: 1px solid var(--rule); border-radius: 0;
  font: 400 12.5px var(--body);
  letter-spacing: normal; text-transform: none;
  cursor: pointer;
}
.form-block .chip::after {
  content: "×"; color: var(--ink-3); font-size: 15px; line-height: 1;
}
.form-block .chip:hover { color: var(--ink); border-color: var(--ink-3); filter: none; }
.form-block .chip:hover::after { color: var(--warn); }
/* The first chip decides the scoring domain, so it is the one marked — the rule
   is otherwise invisible until after you submit. */
.form-block .chip--primary {
  color: var(--have); border-color: var(--have); font-weight: 500;
}

.picked-note {
  margin-left: auto; color: var(--ink-3);
  font: 400 11px var(--mono); letter-spacing: .04em;
}
