/* ==========================================================================
   MyBrain HUD — stylesheet
   See README.md in this directory for the rules a change must not break.

   Sections
     1  Tokens                 7  Roster
     2  Reset + base           8  Project detail
     3  Primitives             9  Composers + toasts
     4  Login                 10  Billing strip
     5  App chrome            11  Reports
     6  Needs-you             12  Responsive + motion + contrast
   ========================================================================== */

/* == 1. Tokens ============================================================ */

:root {
  color-scheme: light dark;

  /* ---- type ------------------------------------------------------------ */
  --f-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
            "Segoe UI", Roboto, "Inter", sans-serif;
  --f-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  --t-micro: 0.6875rem;   /* 11 — column labels, eyebrows. Always tracked. */
  --t-small: 0.8125rem;   /* 13 — chips, metadata */
  --t-meta:  0.875rem;    /* 14 — next action, secondary prose */
  --t-body:  0.9375rem;   /* 15 — task titles, body copy */
  --t-lead:  1.0625rem;   /* 17 — emphasised body */
  --t-title: 1.25rem;     /* 20 — project name in the roster */
  --t-head:  1.5rem;      /* 24 — needs-you project name */
  --t-hero:  2rem;        /* 32 — page title, detail header */
  --t-mega:  2.75rem;     /* 44 — login digits, "nothing is stuck" */

  --w-reg: 400;
  --w-med: 500;
  --w-semi: 600;
  --w-bold: 700;

  --lh-tight: 1.15;
  --lh-snug: 1.3;
  --lh-body: 1.45;

  --track-caps: 0.07em;   /* uppercase micro labels only */
  --track-tight: -0.011em;

  /* ---- space (4pt) ----------------------------------------------------- */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px; --s5: 20px;
  --s6: 24px; --s7: 32px; --s8: 40px; --s9: 56px; --s10: 72px;

  --r-xs: 4px; --r-sm: 6px; --r-md: 10px; --r-lg: 14px; --r-xl: 22px;
  --tap: 44px;                 /* minimum touch target. Never smaller. */
  --measure: 68ch;             /* prose never wider than this */
  --shell: 1080px;

  /* ---- surfaces (light) ------------------------------------------------ */
  --bg:          #f6f6f7;
  --surface:     #ffffff;
  --surface-2:   #f0f0f2;
  --surface-3:   #e7e7ea;
  --line:        rgba(0, 0, 0, .085);
  --line-strong: rgba(0, 0, 0, .17);

  --ink:   #16171a;
  --ink-2: #55585f;
  --ink-3: #6a6e79;   /* 4.7:1 on --bg. Do not lighten: it carries body text. */

  /* ---- meaning-bearing colour (light) ---------------------------------- */
  /* Each of these is ONLY legal alongside a shape or a word. See README. */
  --accent:    #0a5fd0;
  --accent-2:  #e8f0fd;
  --ok:        #0d7350;      /* teal-lean green: separates from amber for deuteranopia */
  --ok-bg:     #e2f3ec;
  --warn:      #8f5300;
  --warn-bg:   #fbeedb;
  --risk:      #bd2440;      /* magenta-lean red: keeps a blue channel */
  --risk-bg:   #fbe6ea;
  --unknown:   #6c707a;      /* never green, never red — "?" has no valence */

  --focus: var(--accent);
  --shadow-pop: 0 8px 28px rgba(0, 0, 0, .13), 0 1px 3px rgba(0, 0, 0, .09);

  --dur-fast: 120ms;
  --dur: 170ms;
  --ease: cubic-bezier(.32, .72, .26, 1);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg:          #0b0b0d;
    --surface:     #141518;
    --surface-2:   #1b1c21;
    --surface-3:   #24262c;
    --line:        rgba(255, 255, 255, .095);
    --line-strong: rgba(255, 255, 255, .21);

    --ink:   #f1f2f4;
    --ink-2: #a8acb6;
    --ink-3: #878c98;

    --accent:   #6da6ff;
    --accent-2: #16233a;
    --ok:       #3ecf95;
    --ok-bg:    #0e2b23;
    --warn:     #e6a744;
    --warn-bg:  #2e2210;
    --risk:     #ff7286;
    --risk-bg:  #341620;
    --unknown:  #8c919c;

    --shadow-pop: 0 10px 34px rgba(0, 0, 0, .6), 0 1px 3px rgba(0, 0, 0, .5);
  }
}

/* == 2. Reset + base ====================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  /* Horizontal containment lives HERE and only here. Setting overflow-x on
     `body` as well computes its overflow-y to `auto`, which quietly makes
     body its own scroll container -- one whose height equals its content, so
     it never scrolls -- and `overscroll-behavior-y: none` then stops the
     wheel chaining up to this element, which is the one that does. Either
     rule alone is harmless; together they eat every wheel event. Keyboard
     scrolling survived, which is what made it look like a rendering fault
     rather than a CSS one. */
  overflow-x: hidden;
  overscroll-behavior-y: none;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: var(--w-reg) var(--t-body)/var(--lh-body) var(--f-sans);
  letter-spacing: var(--track-tight);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-variant-numeric: tabular-nums;
  min-height: 100dvh;
}

/* h4-h6 were missing here, and the moment the reports view used an h4 for a
   release-note headline the browser's own 1.33em margins came back and put 22px
   of air between every headline and its own paragraph. A reset that covers
   only the headings you happen to use today is not a reset. */
h1, h2, h3, h4, h5, h6, p, figure, ul, ol { margin: 0; padding: 0; }
h4, h5, h6 { font-size: inherit; font-weight: inherit; }
ul, ol { list-style: none; }

button, input, textarea, select {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
}

button { background: none; border: 0; margin: 0; padding: 0; cursor: pointer; }

/* Structural links (rows, cards) are surfaces, not prose — no underline, no
   blue. Only a link inside running text gets link styling, via .link. */
a { color: inherit; text-decoration: none; }
a.link {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}

:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

::selection { background: var(--accent-2); color: var(--ink); }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip-link {
  position: fixed; top: var(--s3); left: var(--s3); z-index: 90;
  padding: var(--s2) var(--s4);
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--r-md);
  box-shadow: var(--shadow-pop);
  transform: translateY(-200%);
}
.skip-link:focus-visible { transform: none; }

.root { min-height: 100dvh; }

/* == 3. Primitives ======================================================== */

/* -- eyebrow: the only uppercase in the design ---------------------------- */
.eyebrow {
  font-size: var(--t-micro);
  font-weight: var(--w-semi);
  letter-spacing: var(--track-caps);
  text-transform: uppercase;
  color: var(--ink-3);
}

.num { font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1; }

.mono {
  font-family: var(--f-mono);
  font-size: var(--t-small);
  font-variant-ligatures: none;
}

.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

.wrap-any { overflow-wrap: anywhere; word-break: break-word; }

/* -- chip: severity is carried by GLYPH first, colour second -------------- */
.chip {
  display: inline-flex; align-items: center; gap: var(--s1);
  padding: 3px var(--s2) 3px 5px;
  border-radius: var(--r-sm);
  font-size: var(--t-small);
  font-weight: var(--w-med);
  line-height: 1.25;
  white-space: nowrap;
  background: var(--surface-2);
  color: var(--ink-2);
}
.chip svg { flex: none; }
/* Risk carries FOUR channels, because a daltonized theme flattens red and
   amber to the same yellow: triangle geometry, a filled surface, a hairline
   border, and heavier weight. Hue is the one nobody has to rely on. */
.chip--risk {
  background: var(--risk-bg);
  color: var(--risk);
  font-weight: var(--w-semi);
  border: 1px solid color-mix(in srgb, var(--risk) 34%, transparent);
  padding-block: 2px;
}
.chip--warn { background: var(--warn-bg); color: var(--warn); }
.chip--info { background: var(--surface-2); color: var(--ink-2); }
.chip--ok   { background: var(--ok-bg);   color: var(--ok); }
.chip--plain { background: none; padding-left: 0; padding-right: 0; }

/* In the triage list only RISK gets a filled surface. Warn and info are
   glyph + word on the page background, so the red actually means something. */
.stuck__why .chip--warn,
.stuck__why .chip--info,
.stuck__why .chip--ok {
  background: none;
  padding-left: 0; padding-right: 0;
}
.stuck__why .chip--warn { color: var(--warn); }
.stuck__why .chip--info { color: var(--ink-3); }

/* -- unknown: the "?" pill. Dashed border so it reads as absent, not zero -- */
.unknown {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 1px var(--s2);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  color: var(--unknown);
  font-size: var(--t-small);
  font-weight: var(--w-med);
  background: none;
  cursor: help;
}
.unknown b { font-weight: var(--w-bold); font-size: var(--t-body); line-height: 1; }

/* -- priority pill: number carries it, colour only reinforces ------------- */
.pri {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; height: 20px; padding: 0 5px;
  border-radius: var(--r-xs);
  font-size: var(--t-micro); font-weight: var(--w-bold);
  letter-spacing: .02em;
  background: var(--surface-2); color: var(--ink-3);
  border: 1px solid transparent;
}
.pri--1 { background: var(--risk-bg); color: var(--risk); border-color: color-mix(in srgb, var(--risk) 26%, transparent); }
.pri--2 { background: var(--surface-2); color: var(--ink-2); border-color: var(--line); }
.pri--3 { background: none; color: var(--ink-3); border-color: var(--line); }
/* A board that ranks in words gets its own word. Lower case, because the
   only uppercase in this design is .eyebrow, and "NOW" is not the rank —
   "now" is. */
.pri--word {
  min-width: 0; max-width: 100%; padding: 0 6px; letter-spacing: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; line-height: 20px;
}

/* -- buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  min-height: 36px; padding: 0 var(--s4);
  border-radius: var(--r-md);
  font-size: var(--t-meta); font-weight: var(--w-med);
  color: var(--ink-2);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.btn:hover { background: var(--surface-2); color: var(--ink); }
.btn:active { transform: translateY(.5px); }
.btn[disabled] { opacity: .45; pointer-events: none; }
.btn--primary {
  background: var(--accent); border-color: transparent; color: #fff;
  font-weight: var(--w-semi);
}
@media (prefers-color-scheme: dark) { .btn--primary { color: #0a1220; } }
.btn--primary:hover { background: color-mix(in srgb, var(--accent) 88%, var(--ink)); color: #fff; }
@media (prefers-color-scheme: dark) { .btn--primary:hover { color: #0a1220; } }
.btn--quiet { background: none; border-color: transparent; color: var(--ink-3); }
.btn--quiet:hover { background: var(--surface-2); color: var(--ink); }
.btn--lg { min-height: var(--tap); padding: 0 var(--s6); font-size: var(--t-body); }

/* -- state glyph: four distinct SHAPES, colour is redundant --------------- */
.glyph { display: inline-flex; flex: none; align-items: center; justify-content: center; }
.glyph--working { color: var(--ok); }
.glyph--idle    { color: var(--warn); }
.glyph--cold    { color: var(--ink-3); }
.glyph--unseen  { color: var(--ink-3); opacity: .75; }

/* -- rule / section head -------------------------------------------------- */
.section-head {
  display: flex; align-items: baseline; gap: var(--s3);
  padding: 0 0 var(--s3);
}
.section-head .count { font-size: var(--t-small); color: var(--ink-3); }

/* == 4. Login ============================================================= */

.lock {
  min-height: 100dvh;
  display: grid; place-items: center;
  padding: var(--s8) var(--s5) calc(var(--s8) + env(safe-area-inset-bottom));
  text-align: center;
}
.lock__inner { width: min(420px, 100%); }

.lock__mark {
  width: 56px; height: 56px; margin: 0 auto var(--s6);
  color: var(--ink-3);
}

.lock__title {
  font-size: var(--t-hero); font-weight: var(--w-semi);
  letter-spacing: -0.02em; line-height: var(--lh-tight);
}
.lock__sub {
  margin-top: var(--s2); color: var(--ink-3); font-size: var(--t-meta);
}

.digits {
  position: relative;
  display: flex; gap: var(--s2); justify-content: center;
  margin: var(--s8) 0 var(--s5);
}
.digits__field {
  position: absolute; inset: 0; width: 100%; height: 100%;
  opacity: 0; border: 0; background: none;
  font-size: 16px;           /* stops iOS zoom-on-focus */
  letter-spacing: 3em;
  caret-color: transparent;
  cursor: pointer;
}
.digit {
  width: 48px; height: 62px;
  display: grid; place-items: center;
  border-radius: var(--r-lg);
  background: var(--surface);
  border: 1.5px solid var(--line);
  font-size: var(--t-head); font-weight: var(--w-med);
  color: var(--ink);
  transition: border-color var(--dur-fast) var(--ease),
              background var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease);
}
.digit--filled { border-color: var(--line-strong); }
.digit--active {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
}
.digits--busy .digit { opacity: .55; }
.digits--bad .digit { border-color: var(--risk); }

.lock__msg {
  min-height: 22px;
  font-size: var(--t-meta);
  color: var(--ink-3);
}
.lock__msg--bad {
  color: var(--risk); font-weight: var(--w-med);
  display: flex; align-items: center; justify-content: center; gap: var(--s2);
}
.lock__hint {
  margin: var(--s9) auto 0; max-width: 30ch;
  font-size: var(--t-small); color: var(--ink-3);
}
.lock__mark { opacity: .75; }

@keyframes shake {
  0%,100% { transform: translateX(0); }
  20% { transform: translateX(-6px); }
  40% { transform: translateX(5px); }
  60% { transform: translateX(-3px); }
  80% { transform: translateX(2px); }
}
.digits--bad { animation: shake 340ms var(--ease); }

@keyframes unlock-out {
  to { opacity: 0; transform: scale(1.035); }
}
.lock--open { animation: unlock-out 260ms var(--ease) forwards; }

/* == 5. App chrome ======================================================== */

.shell {
  max-width: var(--shell);
  margin: 0 auto;
  padding: 0 var(--s6) calc(var(--s10) + env(safe-area-inset-bottom));
}

.masthead {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--s3) var(--s5);
  padding: var(--s8) 0 var(--s7);
}
.masthead__title {
  font-size: var(--t-hero); font-weight: var(--w-bold);
  letter-spacing: -0.026em; line-height: 1;
  margin-right: auto;
}
.masthead__meta {
  display: flex; align-items: center; gap: var(--s2) var(--s4);
  flex-wrap: wrap;
  font-size: var(--t-meta); color: var(--ink-3);
}
.masthead__actions { display: flex; align-items: center; gap: var(--s1); flex: none; }
.pulse { display: inline-flex; align-items: center; gap: var(--s2); }
.pulse b { font-weight: var(--w-semi); color: var(--ink-2); }

.dot-sep { color: var(--line-strong); }

/* == 6. Needs-you ========================================================= */

.triage { margin-bottom: var(--s9); }

.triage__head {
  display: flex; align-items: center; gap: var(--s3);
  margin-bottom: var(--s5);
}
.triage__head .eyebrow {
  font-size: 0.75rem; color: var(--ink-2);
}
.triage__rule { flex: 1; height: 1px; background: var(--line); }

/* A list, not a stack of cards. Hairlines and whitespace do the separating;
   the severity geometry sits in its own gutter so the shapes form a column
   you can read down. Boxes would only add chrome. */
.stuck { display: block; }

.stuck__item {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr);
  gap: var(--s2) var(--s3);
  align-items: start;
  padding: var(--s5) var(--s3) var(--s5) 0;
  border-bottom: 1px solid var(--line);
  border-radius: var(--r-md);
  transition: background var(--dur-fast) var(--ease);
}
.stuck__item:last-child { border-bottom: 0; }
.stuck__item:hover { background: var(--surface); }
.stuck__mark { padding-top: 7px; }

.stuck__head {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: var(--s2) var(--s5);
  min-width: 0;
}
.stuck__name {
  font-size: 1.625rem;              /* 26 — the across-the-room size */
  font-weight: var(--w-semi);
  letter-spacing: -0.024em;
  line-height: 1.1;
  color: var(--ink);
  white-space: nowrap;
}
.stuck__client {
  font-size: var(--t-small); font-weight: var(--w-med); color: var(--ink-3);
  letter-spacing: 0; margin-left: calc(var(--s5) * -1 + var(--s2));
}
.stuck__why {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--s1) var(--s4);
  font-size: var(--t-meta);
}
.stuck__why .chip { font-size: var(--t-meta); }
.stuck__next {
  grid-column: 2;
  font-size: var(--t-meta); color: var(--ink-3);
  line-height: var(--lh-snug);
  display: flex; gap: var(--s2); min-width: 0;
}
.stuck__next .arrow { color: var(--line-strong); flex: none; margin-top: 3px; }
.stuck__next span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* the quieter second tier */
/* Second tier. Same page background — a slab here would compete with the
   thing above it, which is the only thing that should be loud. */
.watching {
  margin-top: var(--s5);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s2) var(--s5);
  padding: var(--s3) 0 0 33px;
  border-top: 1px solid var(--line);
}
.watching__label { font-size: var(--t-micro); flex: none; }
.watching__item {
  display: inline-flex; align-items: baseline; gap: var(--s2);
  font-size: var(--t-meta); color: var(--ink-3);
  min-height: var(--tap); align-items: center;
  border-radius: var(--r-sm);
}
.watching__item:hover b { color: var(--accent); }
.watching__item b { font-weight: var(--w-semi); color: var(--ink); }

/* the reward state */
.allclear {
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s7) var(--s5);
}
.allclear__text {
  font-size: var(--t-head); font-weight: var(--w-semi);
  letter-spacing: -0.02em; color: var(--ink);
}
.allclear__sub { font-size: var(--t-meta); color: var(--ink-3); margin-top: 2px; }
.allclear .glyph { color: var(--ok); }

/* == 7. Roster ============================================================ */

/* --cols lives on .board so the one label row and every data row below it
   share the same track definition. Change it in one place or not at all. */
.board { --cols: minmax(150px, 300px) 84px 100px 54px 116px minmax(60px, 1fr) 18px; }
.board + .board { margin-top: var(--s8); }

.board__head {
  display: flex; align-items: baseline; gap: var(--s3);
  padding-bottom: var(--s3);
}

/* one label row per board — labels are paid for once, not per row */
.roster__labels {
  display: grid;
  grid-template-columns: var(--cols);
  gap: var(--s3);
  padding: 0 var(--s3) var(--s2) calc(var(--s3) + 22px);
  border-bottom: 1px solid var(--line);
}

.row {
  display: block; width: 100%; text-align: left;
  padding: var(--s4) var(--s3);
  border-bottom: 1px solid var(--line);
  border-radius: var(--r-md);
  transition: background var(--dur-fast) var(--ease);
}
.row:hover { background: var(--surface); }
.row:last-child { border-bottom: 0; }

.row__main {
  display: grid;
  grid-template-columns: 22px var(--cols);
  gap: var(--s3);
  align-items: baseline;
}

.row__name {
  display: flex; align-items: baseline; gap: var(--s2);
  min-width: 0;
}
.row__name b {
  font-size: var(--t-title); font-weight: var(--w-semi);
  letter-spacing: -0.017em; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.row__tag {
  font-size: var(--t-micro); font-weight: var(--w-med);
  color: var(--ink-3);
  border: 1px solid var(--line); border-radius: var(--r-xs);
  padding: 1px 4px; flex: none;
}

.cell { font-size: var(--t-meta); color: var(--ink-2); min-width: 0; }
.cell--ok { color: var(--ok); }
.cell--warn { color: var(--warn); }
.cell--risk { color: var(--risk); font-weight: var(--w-med); }
.cell--dim { color: var(--ink-3); }
.cell--branch { font-family: var(--f-mono); font-size: var(--t-small); color: var(--ink-3); }

.gitcell { display: flex; align-items: center; gap: var(--s3); flex-wrap: nowrap; }
.gitbit { display: inline-flex; align-items: center; gap: 3px; }

.row__main > .glyph { align-self: center; }

/* Terminates the row and says "this opens". Apple's list-disclosure, and it
   stops the metric group drifting into an unresolved right edge. */
.row__go { align-self: center; color: var(--line-strong); justify-self: end; }
.row:hover .row__go { color: var(--ink-3); }

.row__next {
  display: flex; gap: var(--s2);
  margin: var(--s2) 0 0;
  padding-left: calc(22px + var(--s3));
  font-size: var(--t-meta);
  color: var(--ink-3);
  line-height: var(--lh-snug);
}
.row__next .arrow { flex: none; color: var(--line-strong); }
.row__next span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.row__mobile { display: none; }

/* the honesty footnote */
.footnote {
  margin-top: var(--s8);
  padding-top: var(--s5);
  border-top: 1px solid var(--line);
  font-size: var(--t-meta); color: var(--ink-3);
  max-width: var(--measure);
}

/* == 8. Project detail ==================================================== */

.detail__bar {
  position: sticky; top: 0; z-index: 20;
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) 0;
  padding-top: max(var(--s3), env(safe-area-inset-top));
  background: color-mix(in srgb, var(--bg) 86%, transparent);
  backdrop-filter: saturate(180%) blur(16px);
  -webkit-backdrop-filter: saturate(180%) blur(16px);
  border-bottom: 1px solid transparent;
}
.detail__bar--stuck { border-bottom-color: var(--line); }
.detail__bar b { font-size: var(--t-meta); font-weight: var(--w-semi); }
.back {
  display: inline-flex; align-items: center; gap: var(--s1);
  min-height: var(--tap); padding-right: var(--s2);
  color: var(--accent); font-size: var(--t-meta); font-weight: var(--w-med);
  margin-left: calc(var(--s2) * -1); padding-left: var(--s2);
  border-radius: var(--r-md);
}

.detail__head { padding: var(--s6) 0 var(--s6); }
.detail__title {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s3);
  font-size: var(--t-hero); font-weight: var(--w-bold);
  letter-spacing: -0.026em; line-height: var(--lh-tight);
}
.detail__purpose {
  margin-top: var(--s2); color: var(--ink-2); font-size: var(--t-lead);
  max-width: var(--measure);
}
.detail__facts {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3);
  margin-top: var(--s5);
}

.notice {
  display: flex; gap: var(--s3);
  padding: var(--s4) var(--s5);
  margin-bottom: var(--s6);
  border-radius: var(--r-lg);
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-size: var(--t-meta); color: var(--ink-2);
  line-height: var(--lh-snug);
  width: fit-content;
  max-width: var(--measure);
}
.notice svg { flex: none; color: var(--ink-3); margin-top: 1px; }
.notice b { color: var(--ink); font-weight: var(--w-semi); }
.notice--risk { background: var(--risk-bg); border-color: color-mix(in srgb, var(--risk) 22%, transparent); }
.notice--risk svg, .notice--risk b { color: var(--risk); }

.filters {
  display: flex; gap: var(--s2); flex-wrap: wrap;
  margin-bottom: var(--s5);
  overflow-x: auto;
  scrollbar-width: none;
  padding-bottom: 2px;
}
.filters::-webkit-scrollbar { display: none; }
.filter {
  min-height: 34px; padding: 0 var(--s4);
  border-radius: 999px;
  border: 1px solid var(--line);
  font-size: var(--t-meta); font-weight: var(--w-med);
  color: var(--ink-2); background: var(--surface);
  white-space: nowrap;
}
.filter[aria-pressed="true"] {
  background: var(--ink); color: var(--bg); border-color: var(--ink);
}

.group { margin-bottom: var(--s7); }
.group__head {
  display: flex; align-items: baseline; gap: var(--s3);
  padding: 0 0 var(--s2) var(--s2);
}

/* [state] [priority] [title + meta] [tools]
   Priority is NEVER hover-revealed — it is one of the four facts about a
   task and it forms a scannable rail down the left. Only the edit
   affordances hide until hover, and never on touch. */
.task {
  display: grid;
  grid-template-columns: 26px 30px minmax(0, 1fr) auto;
  gap: var(--s2) var(--s3);
  align-items: start;
  padding: var(--s3) var(--s2);
  border-radius: var(--r-md);
  border-bottom: 1px solid var(--line);
}
.task:last-child { border-bottom: 0; }

/* Three lists, three vocabularies, and the rail speaks each one.
   RockyTops ranks in WORDS (now / soon / normal), so its rail is wider — but
   still a fixed width, because `auto` would size per row and a rail that
   changes width every row has stopped being a rail.
   NeedConnect ranks NOTHING, so there is no column at all. Not an empty one:
   an empty cell is still a claim that a rank exists and is missing. */
.tasks--words .task  { grid-template-columns: 26px 62px minmax(0, 1fr) auto; }
.tasks--norail .task { grid-template-columns: 26px minmax(0, 1fr) auto; }
.task:hover { background: var(--surface); }
.task--done .task__title { color: var(--ink-3); text-decoration: line-through; text-decoration-thickness: 1px; }
.task--busy { opacity: .5; }

.check {
  width: 26px; height: 26px; margin-top: -1px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  color: var(--ink-3);
  flex: none;
}
button.check { min-width: var(--tap); min-height: var(--tap); margin: -9px 0 -9px -9px; }
button.check:hover { color: var(--accent); }
.check--done { color: var(--ok); }
.check--ro { cursor: default; color: var(--ink-2); }
.check--ro.check--done { color: var(--ok); }

/* priority rail */
.task__pri { padding-top: 2px; }
button.task__pri {
  min-width: var(--tap); min-height: var(--tap);
  display: flex; align-items: flex-start; justify-content: flex-start;
  margin: -10px 0 -10px -10px; padding: 12px 0 0 10px;
  border-radius: var(--r-md);
}
button.task__pri:hover .pri { border-color: var(--line-strong); }

.task__body { min-width: 0; }
.task__title {
  font-size: var(--t-body); color: var(--ink);
  line-height: var(--lh-snug);
  overflow-wrap: anywhere;
  /* Wide enough that most titles stay on one line (scanning beats reading
     here), narrow enough that a 150-character one does not become a river. */
  max-width: 92ch;
}
.task__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px var(--s2);
  margin-top: 3px;
  font-size: var(--t-small); color: var(--ink-3);
  line-height: 1.4;
}
.task__meta > span { display: inline-flex; align-items: center; gap: var(--s1); }
.task__meta > .dot-sep { gap: 0; }
/* Tags repeat down the page and carry the least information of anything
   here, so they are the quietest thing in the row. */
.task__meta .tag { color: var(--ink-3); font-size: var(--t-micro); opacity: .85; }
.task__meta .who { color: var(--ink-2); font-weight: var(--w-med); }
.task__meta .due--soon { color: var(--warn); font-weight: var(--w-med); }
.task__meta .due--over { color: var(--risk); font-weight: var(--w-semi); }
.task__meta .blocked {
  display: inline-flex; align-items: center; gap: var(--s1);
  color: var(--warn); font-weight: var(--w-med);
}
/* An unread note on an item still open is the failure that board exists to
   prevent, so it is the loudest thing in the row and it leads. Warn colour,
   but the speech-bubble glyph and the two words carry it on their own. */
.task__meta .unread { color: var(--warn); font-weight: var(--w-semi); }
/* The owning board's own status word, verbatim. Quiet: it is context, and it
   repeats down the page. */
.task__meta .statusword { color: var(--ink-2); font-weight: var(--w-med); }

.task__tools {
  display: flex; align-items: center; gap: var(--s1);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
.task:hover .task__tools,
.task:focus-within .task__tools { opacity: 1; }
@media (hover: none) { .task__tools { opacity: 1; } }

.tool {
  min-width: var(--tap); min-height: var(--tap);
  display: grid; place-items: center;
  border-radius: var(--r-md);
  color: var(--ink-3);
}
.tool:hover { background: var(--surface-2); color: var(--ink); }

.pri-menu {
  position: absolute; z-index: 60;
  display: flex; flex-direction: column; gap: 2px;
  padding: var(--s1);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-pop);
}
.pri-menu button {
  display: flex; align-items: center; gap: var(--s2);
  min-height: 38px; padding: 0 var(--s3);
  border-radius: var(--r-sm);
  font-size: var(--t-meta); color: var(--ink-2);
  white-space: nowrap;
}
.pri-menu button:hover { background: var(--surface-2); color: var(--ink); }

.empty {
  padding: var(--s8) var(--s4);
  text-align: center; color: var(--ink-3); font-size: var(--t-meta);
}

/* == 9. Composers + toasts ================================================ */

.composer {
  display: flex; align-items: flex-end; gap: var(--s2);
  padding: var(--s2);
  border-radius: var(--r-lg);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  transition: border-color var(--dur-fast) var(--ease);
}
.composer:focus-within { border-color: var(--accent); }
.composer textarea {
  flex: 1; min-width: 0;
  border: 0; background: none; resize: none;
  padding: var(--s2) var(--s3);
  font-size: 16px;               /* stops iOS zoom-on-focus */
  line-height: var(--lh-snug);
  max-height: 40vh;
}
.composer textarea::placeholder { color: var(--ink-3); }
.composer .btn { flex: none; min-height: 36px; }

.composer-block { margin-top: var(--s7); }
.composer-block > .eyebrow { display: block; margin-bottom: var(--s3); }
.composer-hint { margin-top: var(--s2); font-size: var(--t-small); color: var(--ink-3); }

.addrow { display: flex; gap: var(--s2); margin-top: var(--s3); flex-wrap: wrap; }
.addrow select {
  min-height: 36px; padding: 0 var(--s3);
  border-radius: var(--r-md); border: 1px solid var(--line-strong);
  background: var(--surface); color: var(--ink-2); font-size: var(--t-meta);
}

.toasts {
  position: fixed; z-index: 100;
  left: 50%; transform: translateX(-50%);
  bottom: calc(var(--s5) + env(safe-area-inset-bottom));
  display: flex; flex-direction: column; gap: var(--s2);
  width: min(520px, calc(100vw - var(--s8)));
  pointer-events: none;
}
.toast {
  display: flex; align-items: flex-start; gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  box-shadow: var(--shadow-pop);
  font-size: var(--t-meta); color: var(--ink);
  line-height: var(--lh-snug);
  pointer-events: auto;
  animation: toast-in var(--dur) var(--ease);
}
.toast svg { flex: none; margin-top: 2px; }
.toast--risk { border-color: color-mix(in srgb, var(--risk) 45%, transparent); }
.toast--risk svg { color: var(--risk); }
.toast--ok svg { color: var(--ok); }
.toast--info svg { color: var(--ink-3); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } }

.skeleton {
  display: grid; place-items: center; min-height: 60dvh;
  color: var(--ink-3); font-size: var(--t-meta);
}

/* == 10. Billing strip ==================================================== */

/* Client projects only, between NEEDS YOU and the roster. It answers a
   different question from the block above it -- "what has gone unbilled?"
   rather than "what has stopped moving?" -- so it gets its own band and never
   competes for the fold.

   There is no colour in here at all, and that is deliberate. Severity comes
   down the wire in this product; no sensor has yet judged "63 days against a
   monthly cadence", so the cadence is printed beside the number and the
   reader makes the comparison. The day a sensor publishes `money.level`,
   render it the way every other level is rendered -- geometry first. */

.bill { margin-bottom: var(--s9); }

.bill__head {
  display: flex; align-items: center; gap: var(--s3);
  margin-bottom: var(--s4);
}
.bill__glyph { color: var(--ink-3); }

.bill__body {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
  gap: var(--s5) var(--s7);
}

/* Whitespace separates these, not boxes -- the client name in semibold is the
   anchor your eye lands on and the big number hangs off it. */
.bill__cell {
  display: flex; flex-direction: column; gap: 2px;
  min-width: 0; min-height: var(--tap);
  padding: var(--s2) var(--s3) var(--s2) 0;
  border-radius: var(--r-md);
  transition: background var(--dur-fast) var(--ease);
}
.bill__cell:hover { background: var(--surface); }

.bill__who { display: flex; align-items: baseline; gap: var(--s2); min-width: 0; }
.bill__who b {
  font-size: var(--t-meta); font-weight: var(--w-semi); color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bill__proj { font-size: var(--t-small); color: var(--ink-3); }

.bill__lead { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--s1) var(--s2); margin-top: 2px; }
.bill__days {
  font-size: var(--t-head); font-weight: var(--w-semi);
  letter-spacing: -0.022em; line-height: 1.1; color: var(--ink);
}
.bill__unit { font-size: var(--t-small); color: var(--ink-3); }
.bill__sub { font-size: var(--t-small); color: var(--ink-3); }
.bill__note { font-size: var(--t-small); color: var(--ink-2); margin-top: var(--s1); }

/* The "not set" / "never" lead. Same dashed geometry that means ABSENT
   everywhere else in this design, at the size the number would have been --
   so the slot is visibly occupied by an answer, and there is no zero and no
   bare dash that could be read as "nothing owed". */
.unknown--lead {
  font-size: var(--t-lead);
  font-weight: var(--w-semi);
  padding: 2px var(--s3);
  letter-spacing: -0.01em;
}

/* The rate sits in the context line, shown plainly. There is one viewer and
   he is the owner; a reveal control here would be a click charged thirty
   times a day against a threat that does not exist -- and would be the first
   plank of a "same screen, fewer fields" architecture that the future client
   portal must NOT be built on. See money.js note 3. */
.bill__sub b { font-weight: var(--w-semi); color: var(--ink-2); }

/* Every client unset is ONE fact, not N. See money.js note 2.
   Deliberately a flowing paragraph and not a flex row: as a flex row the pill
   took a line of its own and the sentence underneath began "for all 3
   clients", which reads as a fragment. The pill is the first two words of the
   sentence, so it has to sit in the sentence. */
.bill__none {
  max-width: var(--measure);
  font-size: var(--t-meta); color: var(--ink-3); line-height: 1.75;
}
.bill__none .unknown--lead { margin-right: var(--s1); vertical-align: baseline; }
.bill__none code { color: var(--ink-2); }

/* == 11. Reports ========================================================== */

.detail__barname { margin-right: auto; }
.report__baractions { display: flex; gap: var(--s2); flex: none; }

.report__controls {
  display: grid; gap: var(--s3);
  /* A GRID item has min-width:auto too, and a grid track sized to its widest
     content is the other half of the same bug the flex rule below fixes. Both
     lines are needed: without this one, a scroll rail inside a row still
     widened the row, and the row widened the page. */
  grid-template-columns: minmax(0, 1fr);
  padding-bottom: var(--s6);
  margin-bottom: var(--s6);
  border-bottom: 1px solid var(--line);
}
.ctrl-row { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s3) var(--s5); }
/* A flex child defaults to min-width:auto and will not shrink below its own
   content -- which is how a `flex-wrap: nowrap` filter rail became 1120px wide
   inside a 358px column and took the whole page with it. The rail is supposed
   to scroll inside itself; this is the line that lets it. */
.ctrl-row > .filters { min-width: 0; flex: 1 1 auto; margin-bottom: 0; }
.ctrl-row > .fields { flex: 0 0 auto; }
.ctrl-row__glyph { color: var(--ink-3); flex: none; }
.ctrl-sep { width: 1px; align-self: stretch; min-height: 22px; background: var(--line-strong); flex: none; }
.filter--personal { color: var(--ink-3); }

.fields { display: flex; flex-wrap: wrap; gap: var(--s3); }
.field { display: flex; align-items: center; gap: var(--s2); min-width: 0; flex: 0 1 auto; }
.field__input {
  min-width: 0; min-height: 34px; padding: 0 var(--s3);
  border-radius: var(--r-md); border: 1px solid var(--line-strong);
  background: var(--surface); color: var(--ink);
  font-size: var(--t-meta);
}

.report__controls > * { min-width: 0; }

.report__results { display: block; }

/* One pack per project. A rule and whitespace separate them; a card would put
   a box around something that is already a page. */
.pack { padding-top: var(--s4); }
.pack + .pack {
  margin-top: var(--s10);
  padding-top: var(--s8);
  border-top: 1px solid var(--line-strong);
}

.pack__head { display: flex; align-items: center; flex-wrap: wrap; gap: var(--s3) var(--s4); }
.pack__id { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--s3); min-width: 0; }
.pack__name {
  font-size: var(--t-hero); font-weight: var(--w-bold);
  letter-spacing: -0.026em; line-height: var(--lh-tight);
}
.pack__client { font-size: var(--t-meta); color: var(--ink-3); }
.pack__marks { display: flex; align-items: center; gap: var(--s2); margin-right: auto; }
.pack__copy { flex: none; }

.pack__eng {
  display: flex; align-items: center; flex-wrap: wrap;
  gap: var(--s3) var(--s6);
  margin-top: var(--s5);
}
.pack__eng > .glyph { color: var(--ink-3); }
.pack__engbit { display: flex; flex-direction: column; gap: 1px; }
.pack__engval { font-size: var(--t-meta); font-weight: var(--w-med); color: var(--ink); }
/* Same rule as the strip: an absence is a word, and it wears the dashed mark
   so it cannot be skimmed as a value. */
.pack__engval--off {
  color: var(--unknown); font-weight: var(--w-reg);
  border-bottom: 1px dashed var(--line-strong);
  width: fit-content;
}

.notice--notes { margin-top: var(--s5); margin-bottom: 0; }
.notes__list { margin-top: var(--s2); }
.notes__list li { position: relative; padding-left: var(--s4); }
.notes__list li + li { margin-top: var(--s2); }
.notes__list li::before {
  content: ""; position: absolute; left: 3px; top: .62em;
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--line-strong);
}

.sec { margin-top: var(--s8); }
.sec__head { margin-bottom: var(--s5); }
.sec__title {
  font-size: var(--t-title); font-weight: var(--w-semi);
  letter-spacing: -0.017em; color: var(--ink);
}
.sec__why {
  margin-top: 2px; max-width: var(--measure);
  font-size: var(--t-meta); color: var(--ink-3); line-height: var(--lh-snug);
}
.sec__foot { margin-top: var(--s3); font-size: var(--t-small); color: var(--ink-3); }

/* -- released: the lead, at reading size ---------------------------------- */

.rel__day + .rel__day { margin-top: var(--s7); }
.rel__dayhead {
  display: flex; align-items: baseline; gap: var(--s3);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--s4);
}
.rel__item { max-width: var(--measure); }
.rel__item + .rel__item { margin-top: var(--s6); }
/* The audience sits BESIDE the headline, not above it. Above it, each note
   cost three stacked blocks and twelve of them turned the section into a
   ladder; beside it, the headline still starts every item and "who was this
   written for" is answered without spending a line on it. */
.rel__top { display: flex; align-items: baseline; flex-wrap: wrap; gap: var(--s1) var(--s3); }
.rel__aud {
  flex: none;
  font-size: var(--t-micro); font-weight: var(--w-med);
  color: var(--ink-3);
  border: 1px solid var(--line); border-radius: var(--r-xs);
  padding: 1px 5px;
}
.rel__headline {
  font-size: var(--t-lead); font-weight: var(--w-semi);
  letter-spacing: -0.012em; line-height: var(--lh-snug); color: var(--ink);
}
.rel__detail {
  margin-top: var(--s2);
  font-size: var(--t-body); color: var(--ink-2); line-height: var(--lh-body);
}
.more { margin-top: var(--s5); }
.more .caret, .disclose .caret {
  display: inline-flex; color: var(--ink-3);
  transition: transform var(--dur-fast) var(--ease);
}
.more[aria-expanded="true"] .caret,
.disclose[aria-expanded="true"] .caret { transform: rotate(90deg); }

/* -- the gap where a report should be ------------------------------------- */

/* A project with no release-note feed does NOT get its commit list quietly
   promoted into the lead position. The section stays, empty, at a size you
   cannot miss -- the frame left on the wall. Feeling the gap is what gets it
   filled; hiding it would say these projects are in the same shape as the one
   with 46 hand-written notes, and they are not. */
.sec--gap .gap {
  max-width: var(--measure);
  padding: var(--s8) var(--s6);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
}
.gap__lead {
  font-size: var(--t-head); font-weight: var(--w-semi);
  letter-spacing: -0.02em; color: var(--unknown); line-height: var(--lh-tight);
}
.gap__body {
  margin-top: var(--s3);
  font-size: var(--t-meta); color: var(--ink-3); line-height: var(--lh-body);
}

/* -- task state ----------------------------------------------------------- */

.threads {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--s6);
}
.thread__head {
  display: flex; align-items: center; gap: var(--s2);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--s3);
}
.thread__head .glyph { color: var(--ink-3); }
.thread--none .thread__head { opacity: .75; }
.thread__list li { font-size: var(--t-meta); color: var(--ink-2); line-height: var(--lh-snug); }
.thread__list li + li { margin-top: var(--s3); }
.thread__empty { font-size: var(--t-small); color: var(--ink-3); line-height: var(--lh-snug); }

/* -- engineering detail: demoted, folded, and labelled -------------------- */

.sec__h3 { font: inherit; }
.disclose {
  display: flex; align-items: center; gap: var(--s2);
  width: 100%; min-height: var(--tap); text-align: left;
  border-radius: var(--r-md); color: var(--ink);
}
.disclose .glyph { color: var(--ink-3); }
.disclose__title {
  font-size: var(--t-title); font-weight: var(--w-semi); letter-spacing: -0.017em;
}
.disclose:hover .disclose__title { color: var(--accent); }
.sec__why--eng { padding-left: calc(14px + 14px + var(--s2) * 2); }

.commits__list { margin-top: var(--s5); }
.commits__day + .commits__day { margin-top: var(--s5); }
.commits__day > .eyebrow {
  display: block; padding-bottom: var(--s2);
  border-bottom: 1px solid var(--line); margin-bottom: var(--s2);
}
.commit {
  display: grid; grid-template-columns: 74px minmax(0, 1fr) auto;
  gap: var(--s3); align-items: baseline;
  padding: 5px 0;
}
.commit__sha { color: var(--ink-3); }
.commit__subject { font-size: var(--t-meta); color: var(--ink-2); overflow-wrap: anywhere; }
.commit__files { font-size: var(--t-small); color: var(--ink-3); white-space: nowrap; }

.areas { margin-top: var(--s7); }
.areas__row { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s3); }
.area {
  display: inline-flex; align-items: baseline; gap: var(--s2);
  max-width: 100%;
  padding: 3px var(--s3);
  border-radius: var(--r-sm);
  background: var(--surface-2);
}
.area b { font-size: var(--t-small); font-weight: var(--w-semi); color: var(--ink-2); }

/* -- the clipboard fallback ----------------------------------------------- */

/* Plain http from a phone means `navigator.clipboard` does not exist. This is
   the third rung: show the text, select it, say why. Slower, still a success.
   See copy.js. */
.copyfall {
  margin: var(--s5) 0;
  padding: var(--s4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--surface);
}
.copyfall__why {
  max-width: var(--measure);
  font-size: var(--t-meta); color: var(--ink-2); line-height: var(--lh-snug);
}
.copyfall__text {
  display: block; width: 100%; min-height: 240px; max-height: 50vh;
  margin: var(--s3) 0;
  padding: var(--s3);
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface-2); color: var(--ink);
  font-family: var(--f-mono);
  font-size: 16px;              /* stops iOS zoom-on-focus */
  line-height: 1.45;
  resize: vertical;
}

/* == 12. Responsive ======================================================= */

@media (max-width: 900px) {
  .board { --cols: minmax(130px, 1.4fr) 80px 96px 52px 110px 18px; }
  .roster__labels span:nth-child(6),
  .row__main > *:nth-child(7) { display: none; }   /* branch */
}

@media (max-width: 720px) {
  :root { --t-hero: 1.75rem; --t-head: 1.3125rem; --t-mega: 2.25rem; }

  .shell { padding: 0 var(--s4) calc(var(--s9) + env(safe-area-inset-bottom)); }
  .masthead { padding: var(--s6) 0 var(--s5); gap: var(--s2) var(--s4); }
  .masthead__title { margin-right: auto; }
  .masthead__actions { order: 1; margin-right: -12px; }
  .masthead__meta { order: 2; width: 100%; }

  .triage { margin-bottom: var(--s7); }
  .stuck__item { padding: var(--s4) var(--s2) var(--s4) 0; }
  .stuck__name { font-size: 1.375rem; white-space: normal; }
  .stuck__client { margin-left: 0; }
  .stuck__head { gap: var(--s1) var(--s3); }
  .stuck__why { width: 100%; gap: var(--s1) var(--s3); }
  .stuck__next span {
    white-space: normal; display: -webkit-box;
    -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  }

  .roster__labels { display: none; }

  .row { padding: var(--s4) var(--s2); border-radius: 0; }
  .row__main { grid-template-columns: 22px minmax(0, 1fr) 18px; gap: var(--s3); align-items: center; }
  .row__main > *:nth-child(n+3):nth-child(-n+7) { display: none; }
  .row__mobile {
    display: flex; flex-wrap: wrap; gap: var(--s1) var(--s3);
    padding-left: calc(22px + var(--s3));
    margin-top: var(--s2);
  }
  .row__next { padding-left: calc(22px + var(--s3)); }
  .row__next span { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }

  .detail__head { padding: var(--s5) 0; }
  .task { grid-template-columns: 26px 30px minmax(0, 1fr); }
  .tasks--words .task  { grid-template-columns: 26px 58px minmax(0, 1fr); }
  .tasks--norail .task { grid-template-columns: 26px minmax(0, 1fr); }
  .task__tools { grid-column: 3; margin-top: var(--s1); margin-left: -8px; }
  .tasks--norail .task__tools { grid-column: 2; }

  .digit { width: 44px; height: 58px; }
  .digits { gap: 6px; }

  .watching { padding: var(--s3) 0 0; }

  /* Every control reaches the 44px minimum on touch. Non-negotiable. */
  /* Filters become a scroll rail rather than wrapping to a second row —
     the wide thing scrolls inside its own container, never the page. */
  .filters { flex-wrap: nowrap; margin-inline: calc(var(--s4) * -1); padding-inline: var(--s4); }

  .btn, .filter, .addrow select, .composer .btn { min-height: var(--tap); }
  .composer textarea { min-height: var(--tap); padding-block: 12px; }
  .skip-link { min-height: var(--tap); display: flex; align-items: center; }
  .composer { padding: var(--s1); }

  /* -- billing ------------------------------------------------------------ */
  /* One column. Side-by-side cells at 390 would put a 24px number in a 100px
     box and the cadence would wrap to three lines under it. */
  .bill { margin-bottom: var(--s7); }
  .bill__body { grid-template-columns: 1fr; gap: var(--s4); }
  .bill__cell { padding: var(--s2) 0; }
  
  /* -- reports ------------------------------------------------------------ */
  .pack + .pack { margin-top: var(--s8); padding-top: var(--s7); }
  .pack__copy { width: 100%; }
  .pack__marks { margin-right: 0; }
  .pack__eng { gap: var(--s3) var(--s5); }

  /* Each control row takes the full width and wraps to its own line; the
     preset rail scrolls inside itself rather than widening the page. */
  .ctrl-row { gap: var(--s3); }
  .ctrl-row > .filters { flex: 1 1 100%; }
  .ctrl-row > .fields { flex: 1 1 100%; width: 100%; }
  .field { flex: 1 1 148px; }
  .field__input { flex: 1 1 auto; min-width: 0; min-height: var(--tap); }

  .ctrl-sep { display: none; }

  .threads { grid-template-columns: 1fr; gap: var(--s5); }
  .sec--gap .gap { padding: var(--s6) var(--s5); }
  .sec__why--eng { padding-left: 0; }

  /* sha and subject keep a line each; the file count tucks under the subject
     rather than squeezing a third column into 358px. */
  .commit { grid-template-columns: 66px minmax(0, 1fr); gap: var(--s1) var(--s3); }
  .commit__files { grid-column: 2; }

  .copyfall__text { min-height: 200px; }
}

@media (max-width: 400px) {
  .digit { width: 40px; height: 54px; font-size: var(--t-title); }
}

/* == Motion ============================================================== */

@keyframes view-in { from { opacity: 0; transform: translateY(4px); } }
.view { animation: view-in var(--dur) var(--ease); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

@media (prefers-contrast: more) {
  :root { --line: rgba(0,0,0,.3); --line-strong: rgba(0,0,0,.55); --ink-3: #4a4d55; }
  @media (prefers-color-scheme: dark) {
    :root { --line: rgba(255,255,255,.34); --line-strong: rgba(255,255,255,.62); --ink-3: #b3b8c2; }
  }
}
