/* Design tokens — "Precision instrument, alive".
   Two first-class moods on one system, switched by [data-theme] on <html>:
   light "Studio" (warm paper, ink navy, hairline rules) and dark "Command"
   (blue-cast near-black, luminous signals, lit hairlines, glass).
   Full spec, alias map and verified WCAG tables: DESIGN_TOKENS.md.
   Type (self-hosted, /css/fonts.css): Alioro Display for names and titles,
   Alioro Sans for everything read for hours, Alioro Mono for ids and counts. */

:root {
  /* ---- Surfaces: near-white ramp, a whisper warm (page -> raised -> inset -> deep) ---- */
  --surface-0: #f4f5f8;
  --surface-1: #ffffff;
  --surface-2: #eef0f4;
  --surface-3: #e4e7ee;

  /* ---- Text: ink-navy ramp ---- */
  --text-1: #141a2b;
  --text-2: #3d475c;
  --text-3: #5a6478;
  --text-inverse: #f2f4fa;

  /* ---- Rules ---- */
  --line-1: #d3d7e0;
  --line-2: #e6e9ef;
  --line-strong: #7f8798; /* form-control boundary: >=3:1 on surface-0/1/2 */

  /* ---- THE PERMANENT PALETTE ----------------------------------------
          Five hues, sampled from the mark (public/img/logo.png and
          design/assets/logo-mark.svg carry exactly these values). Every
          other colour in the product is DERIVED from them by a fixed rule,
          so a new tint is never invented: see DESIGN_TOKENS.md §0.
            light signal = hue mixed with  8% black   (holds on paper)
            dark  signal = hue mixed with 14% white   (lamp grade at night)
            brand        = logo blue mixed with 18% black (AA on paper, AA
                           with white text, the same fill in both themes)
            soft tint    = signal at 12% over transparent
          The raw hues are for the mark, the loader, the rings and tags —
          never for text. ---- */
  --logo-1: #ff3229; /* red    */
  --logo-2: #ffa00a; /* orange */
  --logo-3: #ffcc00; /* yellow */
  --logo-4: #00b842; /* green  */
  --logo-5: #0089eb; /* blue   */
  --logo-teal: #00a096; /* the sixth signal: halfway between green and blue on the fan */

  /* ---- Brand: the logo blue, deepened so it reads as text and carries white ---- */
  --brand:       #0070c1;
  --brand-fg:    #ffffff;
  --brand-hover: #0064ad;
  --brand-soft:  color-mix(in srgb, var(--brand) 11%, transparent);
  --brand-fill:  #0070c1; /* filled controls: white text on both grounds (5.1:1) */
  --brand-fill-hover: #0064ad;

  /* ---- Signals: the fan, 8% deeper than the mark ---- */
  --signal-1: #eb2e26;
  --signal-2: #eb9309;
  --signal-3: #ebbc00;
  --signal-4: #00a93d;
  --signal-5: #00948a;
  --signal-6: #007ed8;

  --signal-1-soft: color-mix(in srgb, var(--signal-1) 12%, transparent);
  --signal-2-soft: color-mix(in srgb, var(--signal-2) 13%, transparent);
  --signal-3-soft: color-mix(in srgb, var(--signal-3) 14%, transparent);
  --signal-4-soft: color-mix(in srgb, var(--signal-4) 13%, transparent);
  --signal-5-soft: color-mix(in srgb, var(--signal-5) 13%, transparent);
  --signal-6-soft: color-mix(in srgb, var(--signal-6) 12%, transparent);

  /* ---- Semantic states: aliases into the fan (declared once; dark overrides
          the primitives and these re-resolve automatically) ---- */
  --ok:     var(--signal-4);
  --warn:   var(--signal-2);
  --danger: var(--signal-1);
  --info:   var(--signal-6);
  --ok-soft:     var(--signal-4-soft);
  --warn-soft:   var(--signal-2-soft);
  --danger-soft: var(--signal-1-soft);
  --info-soft:   var(--signal-6-soft);

  /* ---- Interaction washes ---- */
  --hover-ground:  rgba(20, 26, 43, .05);
  --active-ground: rgba(20, 26, 43, .09);
  --selection-bg:  color-mix(in srgb, var(--brand) 16%, transparent);
  --star: #eba705; /* favourite star: orange and yellow of the mark, halfway */

  /* ---- Focus, overlay, glass ---- */
  /* THE FOCUS TOKEN, declared ONCE and only here (D46 §4c, DESIGN_TOKENS.md
     §0d). It was declared TWICE - this line and again in the phase-68 block
     at :4980 with a different value - so a phase reading one got a halo and a
     phase reading the other did not, which is half of why 62 focus rules
     drifted into seven treatments. The phase-68 declaration is deleted; this
     one carries the two bands. SUPERSEDED VALUES, left visible per the
     standing method: `0 0 0 2px var(--surface-0), 0 0 0 4px var(--brand)`
     (phase-0, the inner band the owner called "some rounded shaped within")
     and `0 0 0 2px var(--brand)` (phase-68, one band, no halo).
     Two shapes, because one shape is why the old token failed: the box-shadow
     form is canonical and is MANDATORY on glass; the outline form is the
     sanctioned fallback for a control whose box-shadow already carries an
     elevation, a --chip-ring or a --card-spine, and is legal only where the
     ground is a --surface-* token. --focus-offset is always POSITIVE: a
     negative offset draws the ring inside the box, which inverts "inside is
     about the thing, outside is about you" (D46 §7). */
  --focus-hue:     var(--brand);
  --focus-halo:    rgba(255, 255, 255, .92);
  /* UPDATED 2026-09-22, the owner: "the blue edge keeps coming back".
     Phase-97's two-band value, `0 0 0 2px var(--focus-hue), 0 0 0 4px
     var(--focus-halo)`, drew a thicker ring plus a halo on every button and
     link. This is phase-68's one 2px band again, still read through
     --focus-hue. --focus-halo stays declared (here and in the dark block) so
     nothing that reads it breaks; the token above no longer uses it. */
  --focus-ring:    0 0 0 2px var(--focus-hue);
  --focus-outline: 2px solid var(--focus-hue);
  --focus-offset:  2px;
  --scrim:      rgba(24, 26, 31, .44);
  /* ONE frost for the whole product: the board head's recipe. Every glass
     surface (head, rail, cards, popovers, modals, drawer, hover cards) reads
     these two lines; there is no second setting to drift from. */
  --glass: rgba(251, 251, 253, .74);
  --glass-bg: var(--glass); --glass-chrome: var(--glass); --glass-card: var(--glass);
  /* THE RIM. Glass reads as glass because of its edge, and the edge costs
     nothing to paint. On a DARK ground a pane shows you its lit edge; on a
     BRIGHT one it shows you its shadowed edge, and only the lit one was ever
     built. Measured on the light frost (#f9f9fc over the page): the lit
     hairline is 1.04:1 and cannot be seen, the ink hairline under it is
     1.28:1. So light leans on --glass-rim-lo, dark on --glass-rim-hi, and
     every glass surface carries both. */
  --glass-rim-hi: rgba(255, 255, 255, .92);  /* the lit pixel along the top */
  --glass-rim-lo: rgba(20, 26, 43, .12);     /* the shadowed pixel along the bottom */
  --glass-line: var(--glass-rim-hi);         /* the old name for the lit edge */
  --glass-blur: 20px;
  --ambient: radial-gradient(1100px 620px at 88% -8%, color-mix(in srgb, var(--logo-5) 9%, transparent), transparent 62%),
             radial-gradient(900px 560px at -6% 108%, color-mix(in srgb, var(--logo-4) 7%, transparent), transparent 60%),
             radial-gradient(700px 420px at 55% 118%, color-mix(in srgb, var(--logo-2) 5%, transparent), transparent 60%);

  /* ---- Typography ---- */
  --font-display: "Alioro Display", "Schibsted Grotesk", -apple-system, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-body:    "Alioro Sans", "Golos Text", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-mono:    "Alioro Mono", "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --fw-body: 450; /* variable weight: a hair above regular on paper... */

  --fs-2xs: 11px;
  --fs-xs:  12.5px;
  --fs-sm:  14px;
  --fs-md:  15px;
  --fs-lg:  17px;
  --fs-xl:  20px;
  --fs-2xl: 24px;
  --fs-3xl: 29px;
  --fs-4xl: 35px;
  --fs-5xl: 42px;
  --fs-6xl: 52px;
  --fs-7xl: 64px;

  --lh-tight: 1.12;
  --lh-snug:  1.3;
  --lh-body:  1.5;
  --lh-loose: 1.65;

  --fw-regular:  400;
  --fw-medium:   500;
  --fw-semibold: 600;
  --fw-bold:     700;
  --fw-display:  800;

  --ls-display: -0.02em;
  --ls-caps:     0.08em; /* uppercase mono micro-labels: the control-room voice */
  --ls-mono:     0;

  /* ---- Spacing: 4px base; the name is the value / 4 ---- */
  --sp-0:  0;
  --sp-1:  4px;
  --sp-2:  8px;
  --sp-3:  12px;
  --sp-4:  16px;
  --sp-5:  20px;
  --sp-6:  24px;
  --sp-8:  32px;
  --sp-10: 40px;
  --sp-12: 48px;
  --sp-16: 64px;
  --sp-20: 80px;
  --sp-24: 96px;

  /* ---- Radius: ownable odd-number scale ---- */
  --r-1: 3px;
  --r-2: 5px;
  --r-3: 9px;
  --r-4: 14px;
  --r-round: 999px;
  --r-dot: 50%;
  /* The landing's scale, added in phase-72-a so app.css and welcome.css speak
     one vocabulary; the values are welcome.css:57 verbatim. The shape grammar
     that spends them is DESIGN_DIRECTION-APP.md D3: a capsule marks something
     that stands on its own and is meant to be pressed or navigated by, a
     control set INTO a bar or a panel takes 9px, data takes 7px, a table cell
     stays square.
     --r-pill and --r-round are DELIBERATELY two names for 999px right now, and
     --r-2 (5px) is the shape this grammar spends out of existence. Do not
     "tidy" either one here: six live consumers still read --r-round, and
     retiring a token is the groundskeeper's own phase, not this one. */
  --r-xs: 7px;
  --r-sm: 9px;
  --r-md: 14px;
  --r-lg: 20px;
  --r-xl: 26px;
  --r-pill: 999px;

  /* ---- Elevation: soft warm ink-on-paper ---- */
  --elev-1: 0 1px 2px rgba(32, 38, 50, .05), 0 1px 1px rgba(20, 26, 43, .05);
  --elev-2: 0 2px 6px rgba(32, 38, 50, .06), 0 8px 20px rgba(20, 26, 43, .08);
  --elev-3: 0 4px 12px rgba(32, 38, 50, .07), 0 18px 44px rgba(20, 26, 43, .14);
  /* THE LIFT UNDER A POPOVER OR A MODAL, as a token and not as a theme rule.
     Dark's deeper lift used to live in `[data-theme="dark"] .popover,
     [data-theme="dark"] .modal`, and at (0,2,0) that out-ranked the shared
     glass recipe at (0,1,0): the rim never reached the two commonest surfaces
     in the app while .peek, .chat-panel and .sidebar kept theirs. One rule
     owns box-shadow for these two now and the theme difference rides in here,
     the same way --glass-line rides as an alias. Light aliases --elev-3, which
     is the lift light popovers have actually painted since phase-39; the
     phase-28 literal they used to carry was already dead under @supports. */
  --elev-pop: var(--elev-3);

  /* ---- Motion ---- */
  --dur-micro-1: 120ms;
  --dur-micro-2: 180ms;
  --dur-micro-3: 240ms;
  --dur-scene-1: 300ms;
  --dur-scene-2: 400ms;
  --dur-scene-3: 450ms;

  --ease-out:    cubic-bezier(0.16, 1, 0.30, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-settle: cubic-bezier(0.22, 1.20, 0.36, 1);

}

[data-theme="dark"] {
  /* ---- Surfaces: night ramp. The page sits deepest, the table lifts off
          it, hover and inset rise again — four visibly distinct steps. ---- */
  --surface-0: #16181d;
  --surface-1: #1d2025;
  --surface-2: #262a30;
  --surface-3: #30353c;

  /* ---- Text: cool off-white ramp, every step readable on every surface ---- */
  --text-1: #f2f3f5;
  --text-2: #c5c9d0;
  --text-3: #9ca2ac;
  --text-inverse: #f2f4fa;

  /* ---- Rules: lit hairlines that actually read as a grid ---- */
  --line-1: rgba(255, 255, 255, .13);
  --line-2: rgba(255, 255, 255, .075);
  --line-strong: #6e7480;

  /* ---- Brand: the logo blue lifted 35% toward white for text on the night
          ground; filled controls keep the paper fill so white text passes ---- */
  --brand:       #59b2f2;
  --brand-fg:    #06101f;
  --brand-hover: #7cc3f5;
  --brand-soft:  color-mix(in srgb, var(--brand) 15%, transparent);
  --brand-fill:  #0070c1;
  --brand-fill-hover: #0a7fd6;

  /* ---- Signals: the fan, 14% toward white (lamp grade) ---- */
  --signal-1: #ff4f47;
  --signal-2: #ffad2c;
  --signal-3: #ffd324;
  --signal-4: #24c25c;
  --signal-5: #24aea5;
  --signal-6: #249aee;

  --signal-1-soft: color-mix(in srgb, var(--signal-1) 13%, transparent);
  --signal-2-soft: color-mix(in srgb, var(--signal-2) 12%, transparent);
  --signal-3-soft: color-mix(in srgb, var(--signal-3) 11%, transparent);
  --signal-4-soft: color-mix(in srgb, var(--signal-4) 12%, transparent);
  --signal-5-soft: color-mix(in srgb, var(--signal-5) 12%, transparent);
  --signal-6-soft: color-mix(in srgb, var(--signal-6) 13%, transparent);

  /* ---- Interaction washes ---- */
  --fw-body: 420; /* ...a hair below on the night ground, where light text renders heavier */
  --hover-ground:  rgba(255, 255, 255, .06);
  --active-ground: rgba(255, 255, 255, .11);
  --selection-bg:  color-mix(in srgb, var(--brand) 30%, transparent);
  --star: #ffc028;

  /* ---- Overlay, glass ---- */
  /* the focus ring's only theme override: the hue inverts by itself through
     --brand (#0070c1 -> #59b2f2), the halo does not (D46 §5). */
  --focus-halo: rgba(5, 8, 14, .88);
  --scrim:      rgba(0, 0, 0, .64);
  --glass: rgba(28, 30, 34, .8);
  --glass-bg: var(--glass); --glass-chrome: var(--glass); --glass-card: var(--glass);
  /* dark shows the LIT edge: .08 measured 1.26:1 on the night frost, which is
     the faintest thing in the product that is still meant to be seen. .14
     takes it to 1.54:1. The shadow edge is the quiet one here. */
  --glass-rim-hi: rgba(255, 255, 255, .14);
  --glass-rim-lo: rgba(0, 0, 0, .38);
  --glass-line: var(--glass-rim-hi);
  --glass-blur: 20px;
  /* THE FIELD BEHIND THE GLASS, strengthened 2026-09-21 (phase-97). A
     backdrop-filter filters what is BEHIND a pane: blur over a flat region is
     an identity (the suite's own contrast model says so at api_test:10510),
     saturate has no chroma to lift and contrast has no variation to stretch.
     So a technically perfect pane over a flat token renders as a flat
     rectangle, which is what Apple's panes do NOT do - a photographic desktop
     is what bends colour through Notification Center. Ours is this field: three
     very large, very slow radial washes in three of the five logo hues, drawn
     ONCE on `.app` (:2539) as a background-image stack. One paint, ZERO
     compositing layers, and it is not itself a filter - a backdrop-filter on
     `body`, `.app`, `.main` or any full-page wrapper is forbidden (css.md,
     because it makes a containing block for every fixed descendant).
     12/7/5% -> 15/9/6% and the radii grown, so the washes reach into the
     screen instead of hugging three corners. Measured at the strongest point
     (logo-5 at 15% over --surface-0): the ground goes #16181d -> #13293c and
     --text-1 on it is 13.4:1, against 16.1:1 on the bare page and a 4.5:1
     floor. LIGHT IS DELIBERATELY NOT RAISED (:125): the owner has rejected a
     tinted app twice ("purple-ish"), light's page is #f4f5f8 and a wash that
     can be seen there reads as a colour cast rather than as depth. */
  --ambient: radial-gradient(1500px 820px at 88% -8%, color-mix(in srgb, var(--logo-5) 15%, transparent), transparent 66%),
             radial-gradient(1200px 720px at -6% 104%, color-mix(in srgb, var(--logo-4) 9%, transparent), transparent 64%),
             radial-gradient(1000px 560px at 55% 114%, color-mix(in srgb, var(--logo-2) 6%, transparent), transparent 62%);

  /* ---- Elevation: lit top edge + depth + azure bloom ---- */
  --elev-1: inset 0 1px 0 rgba(255, 255, 255, .06), 0 1px 2px rgba(0, 0, 0, .35);
  --elev-2: inset 0 1px 0 rgba(255, 255, 255, .08), 0 8px 24px rgba(0, 0, 0, .45);
  --elev-3: inset 0 1px 0 rgba(255, 255, 255, .10), 0 0 0 1px rgba(255, 255, 255, .06),
            0 20px 56px rgba(0, 0, 0, .55);
  /* deeper, with the azure ring. Same values as the theme rule that used to
     carry them; only the place changed, so the rim can share the property. */
  --elev-pop: 0 24px 56px -14px rgba(0,0,0,.7), 0 0 0 1px rgba(160,180,225,.08);
}

@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-micro-1: 0.01ms; --dur-micro-2: 0.01ms; --dur-micro-3: 0.01ms;
    --dur-scene-1: 0.01ms; --dur-scene-2: 0.01ms; --dur-scene-3: 0.01ms;
  }
}

:root {
  /* ---- Legacy bridge: old vars resolve into the new system so all existing
          consumers pick it up without edits. Declared in :root only — the dark
          block overrides the primitives and every var() here re-resolves.
          Delete lines as later phases rewire consumers to the tokens above.
          --ink family are pinned literals BY DESIGN: marketing ink bands stay
          dark with light text in both themes. ---- */
  --font: var(--font-body);
  --primary: var(--brand);
  --primary-hover: var(--brand-hover);
  --primary-soft: var(--brand-soft);
  --accent-2: var(--signal-3);
  --amber: var(--signal-2);
  --green: var(--ok);
  --orange: var(--warn);
  --red: var(--danger);
  --teal: var(--signal-5);
  --grad: linear-gradient(135deg, var(--brand), var(--signal-5));
  --ink: #141a2b;
  --ink-2: #1c2440;
  --on-ink: #f2f4fa;
  --on-ink-soft: #9aa4c7;
  --bg: var(--surface-0);
  --surface: var(--surface-1);
  --text: var(--text-1);
  --text-soft: var(--text-2);
  --border: var(--line-1);
  --border-soft: var(--line-2);
  --hover: var(--surface-2);
  --cell-empty: var(--text-3);
  --shadow: var(--elev-1);
  --shadow-lg: var(--elev-3);
  --radius: var(--r-3);
  --sidebar-bg: var(--surface-1);
  --topbar-bg: var(--surface-1);
  --display: var(--font-display);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  font-weight: var(--fw-body);
  font-optical-sizing: auto;
  font-feature-settings: "kern" 1, "liga" 1, "calt" 1;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}
/* numbers line up wherever they are compared: ids, dates, counts */
.cell-text.num, .cell-date, .group-count, .pill-count, .sum-cell, .ch-time, .ch-meta span, .attach-ver-chip, .qf-chip b, .fv-meta, .pf-meta { font-variant-numeric: tabular-nums; }
/* Theme switch: every transition is suppressed for the frame the attribute
   flips, so light and dark swap in one paint instead of cross-fading. */
html.theme-switching *, html.theme-switching *::before, html.theme-switching *::after { transition: none !important; }
a { color: var(--primary); text-decoration: none; }
h1, h2, h3, h4, h5 { margin: 0 0 .4em; font-weight: 700; font-family: var(--font-display); letter-spacing: -.02em; }
input, select, textarea, button { font-family: var(--font); font-size: 14px; color: var(--text); }
input, select, textarea {
  background: var(--surface);
  border: 1px solid var(--line-strong); /* control boundary: >=3:1 on its grounds (1.4.11) */
  border-radius: 6px;
  padding: 8px 10px;
  outline: none;
  transition: border-color var(--dur-micro-2) var(--ease-out);
}
input:focus, select:focus, textarea:focus { border-color: var(--primary); }
label { display: block; margin-bottom: 12px; font-weight: 600; font-size: 13px; color: var(--text-soft); }
label input, label select { display: block; width: 100%; margin-top: 5px; font-weight: 400; }
.muted { color: var(--text-soft); }
.sm { font-size: 12px; }
.center { text-align: center; }
.pad { padding: 18px; }
.w-full { width: 100%; }
.hint { font-size: 11px; color: var(--text-soft); margin-top: 6px; }

/* ------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; gap: 6px;
  border: none; border-radius: 8px; cursor: pointer;
  padding: 9px 16px; font-weight: 600; font-size: 14px;
  background: var(--surface-2); color: var(--text);
  transition: transform .12s ease, background .15s, box-shadow .15s;
  text-decoration: none;
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn.primary { background: var(--brand-fill); color: #fff; box-shadow: inset 0 1px 0 rgba(255,255,255,.16), 0 1px 2px rgba(20,26,43,.18); }
.btn.primary:not(:disabled):hover { background: var(--brand-fill-hover); box-shadow: inset 0 1px 0 rgba(255,255,255,.18), 0 6px 16px color-mix(in srgb, var(--brand-fill) 32%, transparent); }
.btn.ghost { background: transparent; color: var(--text); }
.btn.ghost:not(:disabled):hover { background: var(--hover); }
.btn.ghost.on { background: var(--primary-soft); color: var(--primary); }
.btn.outline { background: transparent; border: 1px solid var(--primary); color: var(--primary); }
.btn.outline:hover { background: var(--primary-soft); }


.btn.ink { background: var(--ink); color: #fff; }
.btn.ink:hover { background: var(--ink-2); box-shadow: 0 10px 26px rgba(18, 23, 43, .3); }
[data-theme="dark"] .btn.ink { background: var(--primary); }
[data-theme="dark"] .btn.ink:hover { background: var(--primary-hover); }
.btn.sm { padding: 6px 11px; font-size: 13px; }

.btn .arrow { transition: transform .18s ease; display: inline-block; }
.btn:hover .arrow { transform: translateX(4px); }
.btn:disabled { opacity: .55; cursor: default; transform: none; }
.icon-btn {
  position: relative; background: transparent; border: none; cursor: pointer;
  font-size: 17px; padding: 7px 9px; border-radius: 6px; color: var(--text);
  transition: background .15s;
  /* phase-150 (alignment 8, 9): a grid centres its one glyph on both axes; as
     an inline-block the svg sat on the text baseline, 1.5 to 5px low. Set
     here, in the base rule, so every later rule that hides or re-displays an
     icon button (.ch-close, .bp-close, ...) still wins on source order. */
  display: inline-grid; place-items: center;
}
.icon-btn:hover { background: var(--hover); }
.icon-btn.sm { font-size: 13px; padding: 4px 6px; }
.icon-btn.star.on { color: var(--star); }

/* --------------------------------------------------------------- logo
   Four board-status dots in a 2×2 grid. On hover they rotate one seat
   clockwise — work moving around a board. */
.logo {
  display: inline-flex; align-items: center; gap: 9px;
  font-family: var(--font-display); font-size: 20px; font-weight: 800;
  color: var(--text); letter-spacing: -.4px;
}
.logo-word b { color: var(--primary); font-weight: 800; }
.logo-mark { width: 22px; height: 24px; flex: none; overflow: visible; }
.logo-mark circle { transform-box: fill-box; transform-origin: center; }
.logo:hover .logo-mark circle { animation: logoWave .65s ease both; }
.logo:hover .logo-mark circle:nth-child(n+6) { animation-delay: .07s; }
.logo:hover .logo-mark circle:nth-child(n+11) { animation-delay: .14s; }
.logo:hover .logo-mark circle:nth-child(n+16) { animation-delay: .21s; }
.logo:hover .logo-mark circle:nth-child(n+21) { animation-delay: .28s; }
/* app-shell logo: columns pop in left to right once, then a slow wave keeps
   travelling across the fan (one pass every 8 s) under a soft light */
.app .logo-mark { filter: drop-shadow(0 1px 4px rgba(0, 137, 235, .22)); }
[data-theme="dark"] .app .logo-mark { filter: drop-shadow(0 0 7px rgba(140, 190, 255, .38)); }
.app .logo-mark circle { animation: logoIn .38s ease backwards, logoDrift 8s var(--ease-in-out) 1.4s infinite; }
.app .logo-mark circle:nth-child(n+6) { animation-delay: .05s, 1.8s; }
.app .logo-mark circle:nth-child(n+11) { animation-delay: .10s, 2.2s; }
.app .logo-mark circle:nth-child(n+16) { animation-delay: .15s, 2.6s; }
.app .logo-mark circle:nth-child(n+21) { animation-delay: .20s, 3.0s; }
@keyframes logoDrift { 0%, 100% { transform: scale(1); opacity: 1; } 6% { transform: scale(1.15); opacity: .82; } 13% { transform: scale(1); opacity: 1; } }
@keyframes logoIn { from { transform: scale(.4); opacity: 0; } to { transform: scale(1); opacity: 1; } }
@keyframes logoWave { 45% { transform: scale(1.45); } 100% { transform: scale(1); } }
.logo.sm { font-size: 17px; }
.logo.sm .logo-mark { width: 18px; height: 20px; }


/* ---------------------------------------------------------- marketing */
.marketing { min-height: 100vh; display: flex; flex-direction: column; overflow-x: clip; }
.mk-header {
  display: flex; justify-content: space-between; align-items: center;
  padding: 14px 5vw; position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  border-bottom: 1px solid transparent;
  transition: border-color .25s ease, box-shadow .25s ease;
}
.mk-header.scrolled { border-bottom-color: var(--border-soft); box-shadow: 0 4px 24px rgba(18, 23, 43, .06); }
.mk-header nav { display: flex; align-items: center; gap: 20px; }
.mk-header nav a { color: var(--text); font-weight: 600; font-size: 14.5px; }
.mk-header nav a.active, .mk-header nav a:hover { color: var(--primary); }
.mk-login { color: var(--text-soft) !important; }

.mk-header .logo-mark { width: 30px; height: auto; }
.mk-header .logo-word { font-size: 20px; }


html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
/* the auth and marketing screens carry ONE action each, and it stays a capsule
   (phase-72-a renamed the token, the value is the same 999px) */
.marketing .btn { border-radius: var(--r-pill); }
.marketing .btn .arrow { display: inline-flex; transition: transform var(--dur-micro-2) var(--ease-out); }
.marketing .btn:hover .arrow { transform: translateX(3px); }


/* mini board remnants: the auth page's side panel (and footer live-dot) */
.live-dot {
  position: relative; width: 8px; height: 8px; border-radius: 50%; background: var(--green);
  display: inline-block;
}
/* the halo is its own box: a keyframe may move transform and opacity, never a
   box-shadow (which repaints the dot every frame) */
.live-dot::after {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: var(--green); animation: livePulse 2s ease infinite; pointer-events: none;
}

.lb-body { padding: 12px 14px 16px; }
.lb-group { font-size: 12px; font-weight: 800; color: var(--gcol, var(--primary)); padding: 4px 6px 8px; }
.lb-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 8px;
  border-radius: 8px; border-left: 3px solid var(--gcol, var(--primary));
  background: var(--surface); margin-bottom: 5px; border-bottom: 1px solid var(--border-soft);
}
.lb-name { flex: 1; font-weight: 600; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lb-status {
  display: inline-flex; align-items: center; justify-content: center; gap: 5px;
  color: var(--text-1); font-size: 11px; font-weight: 700; padding: 5px 0; border-radius: var(--r-1);
  width: 92px; text-align: center; flex: none; position: relative;
  background: color-mix(in srgb, var(--lab, var(--text-3)) 14%, transparent);
}
.lb-status::before {
  content: ""; width: 6px; height: 6px; border-radius: var(--r-dot);
  background: var(--lab, var(--text-3)); flex: none;
}


/* ---- CTA band + footer ----------------------------------------------- */
/* soft glows via radial gradients — no filter: blur, keeps compositing cheap */
.cta-orb { position: absolute; border-radius: 50%; z-index: 0; background: radial-gradient(circle, var(--oc) 0%, transparent 62%); opacity: .5; }
.cta-orb.o1 { --oc: var(--signal-6); width: 560px; height: 560px; top: -230px; left: 2%; animation: float 11s ease-in-out infinite; }
.cta-orb.o2 { --oc: var(--signal-5); width: 460px; height: 460px; bottom: -200px; right: 4%; animation: float 9s 1s ease-in-out infinite; }


.mk-footer { background: var(--surface-1); color: var(--text-2); margin-top: auto; border-top: 1px solid var(--line-1); }
.mk-footer-inner {
  max-width: 1240px; margin: 0 auto; padding: 52px 6vw 40px;
  display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 24px;
}
.mk-footer h3 {
  margin-bottom: 12px; color: var(--text-3); font-family: var(--font-mono);
  font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; font-weight: var(--fw-semibold);
}
.mk-footer a, .mk-footer span { display: block; color: var(--text-2); padding: 4px 0; font-size: 13px; }
.mk-footer a:hover { color: var(--brand); }
.mk-footer p { color: var(--text-3); font-size: 13px; line-height: 1.6; }
.footer-base {
  max-width: 1240px; margin: 0 auto; padding: 18px 6vw 30px;
  border-top: 1px solid var(--line-2);
  display: flex; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--text-3);
}

/* ---- scroll reveals --------------------------------------------------- */
.reveal { opacity: 0; transform: translateY(26px); transition: opacity .7s cubic-bezier(.16, 1, .3, 1), transform .7s cubic-bezier(.16, 1, .3, 1); }
.reveal.in { opacity: 1; transform: none; }

/* ------------------------------------------------------------- auth */
.auth-page { min-height: 100vh; display: grid; grid-template-columns: 1.1fr .9fr; background: var(--bg); }
.auth-main {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 26px; padding: 40px 22px;
}
.auth-logo { font-size: 24px; }
.auth-card {
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: 20px;
  box-shadow: var(--shadow); padding: 38px; width: min(420px, 94vw);
  animation: fadeUp .45s ease both;
}
.auth-card h1 { font-size: 24px; }
.auth-alt { margin-top: 18px; text-align: center; color: var(--text-soft); font-size: 13px; }
.auth-forgot { text-align: right; font-size: 13px; margin: -6px 0 10px; }
.auth-error { color: var(--red); font-weight: 600; font-size: 13px; margin-bottom: 10px; display: none; }
.auth-side {
  background: var(--ink); color: var(--on-ink); position: relative; overflow: hidden;
  display: flex; flex-direction: column; justify-content: center; gap: 30px; padding: 6vw 5vw;
}
.auth-side .cta-orb { opacity: .35; }
.auth-side > *:not(.cta-orb) { position: relative; z-index: 1; }
.auth-quote { font-size: 19px; line-height: 1.6; color: var(--on-ink); max-width: 420px; font-weight: 500; }
.auth-quote footer { margin-top: 14px; font-size: 13.5px; color: var(--on-ink-soft); font-weight: 600; }
.auth-board {
  background: color-mix(in srgb, var(--on-ink) 6%, transparent);
  border: 1px solid color-mix(in srgb, var(--on-ink) 13%, transparent);
  border-radius: 16px; padding: 16px; max-width: 420px;
}
.auth-board .lb-row { background: rgba(255, 255, 255, .06); border-bottom: none; }
.auth-board .lb-name { color: var(--on-ink); }
.auth-board .lb-group { color: #9ab6ff; }

/* ---------------------------------------------------------- app shell
   Slim icon rail + expand-on-demand board drawer + per-page context header.
   The drawer keeps the legacy .sidebar identity because JS toggles
   #sidebar.open and refreshSidebarSoon re-renders it. */
body.in-app { overflow: hidden; }
.app { display: flex; height: 100vh; }

/* Chrome metrics. The rail's width and the board body's side padding are the
   two numbers anything full-width INSIDE the board has to subtract (see
   .sub-strip). They are tokens so a change to the chrome travels instead of
   leaving a hardcoded calc behind. */
:root { --rail-w: 56px; --board-pad-x: 26px; }

.rail {
  width: var(--rail-w); flex: none; display: flex; flex-direction: column; align-items: center;
  gap: var(--sp-1); padding: var(--sp-3) 0;
  /* No border-right. REMOVED 2026-09-21 on the owner's direct instruction:
     "remove that line that separates the menu", so the rail and the board
     head read as ONE continuous surface rather than two panels meeting at a
     seam. They already share `--glass-chrome`, so the 1px `--line-2` rule
     that used to sit here was the only thing dividing them. Do not restore
     it; if the rail ever needs separating from the content again, that is an
     owner decision, not a tidy-up. */
  background: var(--surface-1); z-index: 46;
}
.rail-logo { display: block; margin-bottom: var(--sp-3); line-height: 0; }
.rail-logo .logo-mark { width: 26px; height: auto; }
.rail-logo .logo-word { display: none; }
.rail-btn {
  position: relative; width: 40px; height: 40px; border: none; background: none;
  border-radius: var(--r-2); color: var(--text-2); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.rail-btn:hover { background: var(--surface-2); color: var(--text-1); }
.rail-btn.active { background: var(--brand-soft); color: var(--brand); }
/* the active page carries a pill on the rail's edge that springs between items */
.rail-btn::before {
  content: ""; position: absolute; left: -8px; top: 50%; width: 3px; height: 0; border-radius: 2px;
  background: var(--brand); transform: translateY(-50%);
  transition: height var(--dur-micro-3) var(--ease-spring);
}
.rail-btn.active::before { height: 22px; }
[data-theme="dark"] .rail { background: linear-gradient(180deg, var(--surface-1), color-mix(in srgb, var(--surface-1) 86%, var(--surface-0))); }
[data-theme="dark"] .board-table { color-scheme: dark; }
.rail-spacer { flex: 1; }

.sidebar {
  position: fixed; left: 56px; top: 0; bottom: 0; width: 288px; z-index: 45;
  display: flex; flex-direction: column;
  background: var(--surface-1);
  border-right: 1px solid var(--line-1); box-shadow: var(--elev-2);
  transform: translateX(-120%); visibility: hidden;
  transition: transform var(--dur-scene-1) var(--ease-out),
              visibility 0s linear var(--dur-scene-1);
}
.sidebar.open {
  transform: translateX(0); visibility: visible;
  transition: transform var(--dur-scene-1) var(--ease-out);
}
.drawer-scrim { display: none; position: fixed; inset: 0 0 0 56px; z-index: 44; background: transparent; }
.sidebar.open + .drawer-scrim { display: block; }
.drawer-head {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3); border-bottom: 1px solid var(--line-2); flex: none;
}
.side-scroll { flex: 1; overflow-y: auto; padding: 8px 10px 30px; }

.drawer-close { flex: none; color: var(--text-3); }
.badge {
  position: absolute; top: 1px; right: 1px; background: var(--red); color: #fff;
  min-width: 16px; height: 16px; font-size: 10px; font-weight: 700; border-radius: 8px;
  display: flex; align-items: center; justify-content: center; padding: 0 4px;
  animation: badgePulse 2.4s ease-in-out infinite;
}
@keyframes badgePulse { 0%, 100% { transform: scale(1); } 12% { transform: scale(1.18); } 24% { transform: scale(1); } }
.avatar-btn { background: none; border: none; cursor: pointer; padding: 3px; border-radius: 50%; }
.avatar {
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%; color: #fff; font-weight: 600; letter-spacing: .02em; flex: none;
}
.avatar.all { background: var(--text-soft); width: 26px; height: 26px; font-size: 12px; }

/* drawer rows: the card grammar (icon plates, calm labels), the workspace as
   a tile, boards with a glyph in the workspace colour, a pill on the active one */
.side-nav { display: flex; flex-direction: column; gap: 2px; margin-bottom: 6px; }
.side-link {
  display: flex; width: 100%; align-items: center; gap: 10px; padding: 7px 10px;
  border-radius: 8px; color: var(--text-1); font-weight: 500; font-size: 13.5px;
  background: none; border: none; cursor: pointer; text-align: left;
}
.side-link > .ic { box-sizing: border-box; width: 24px; height: 24px; padding: 5px; border-radius: 7px; background: var(--surface-2); color: var(--text-2); flex: none; transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out); }
.side-link:hover { background: var(--hover-ground); }
.side-link:hover > .ic, .side-link.active > .ic { background: var(--brand-soft); color: var(--brand); }
.side-link.active { background: var(--brand-soft); color: var(--brand); font-weight: 600; }
.side-link.ws-add { margin-top: 14px; color: var(--text-2); }
.side-section { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line-2); }
.side-title {
  display: flex; align-items: center; gap: 8px; padding: 4px 10px 6px;
  font-size: 12.5px; font-weight: 600; color: var(--text-2);
}
.side-title > .ic { color: var(--star); }
.side-title.ws { padding: 6px 8px; border-radius: 8px; cursor: pointer; }
.side-title.ws:hover { background: var(--hover-ground); }
.ws-icon {
  width: 24px; height: 24px; border-radius: 7px; color: #fff; font-size: 11px; font-weight: 700; flex: none;
  display: inline-flex; align-items: center; justify-content: center; box-shadow: inset 0 0 0 1px rgba(255,255,255,.14);
}
.ws-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-1); }
.ws-count { font-family: var(--font-mono); font-size: 11px; color: var(--text-3); }
.ws-dots { display: inline-flex; padding: 3px; border-radius: 5px; color: var(--text-3); opacity: 0; transition: opacity var(--dur-micro-1) var(--ease-out); }
.side-title.ws:hover .ws-dots, .side-title.ws:focus-visible .ws-dots { opacity: 1; }
.side-board {
  position: relative; display: flex; align-items: center; gap: 9px; padding: 6px 10px 6px 12px; margin: 1px 0;
  color: var(--text-1); border-radius: 8px; font-size: 13.5px;
}
.side-board .board-glyph {
  width: 22px; height: 22px; border-radius: 6px; flex: none; display: inline-flex; align-items: center; justify-content: center;
  color: var(--wc, var(--brand)); background: color-mix(in srgb, var(--wc, var(--brand)) 14%, transparent);
}
.side-board .sb-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.side-board .sb-fav { color: var(--star); display: inline-flex; flex: none; }
.side-board .sb-fav .ic { fill: currentColor; }
.side-board:hover { background: var(--hover-ground); }
.side-board.active { background: var(--brand-soft); color: var(--brand); font-weight: 600; }
.side-board.active::before { content: ""; position: absolute; left: 0; top: 8px; bottom: 8px; width: 3px; border-radius: 2px; background: var(--brand); }
.side-add {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: none; border: none;
  color: var(--text-3); font-size: 12.5px; padding: 6px 10px 6px 14px; cursor: pointer; border-radius: 8px;
}
.side-add:hover { background: var(--hover-ground); color: var(--brand); }

.main { flex: 1; overflow-y: auto; min-width: 0; display: flex; flex-direction: column; }
.loading { padding: 60px; text-align: center; color: var(--text-3); display: flex; flex-direction: column; align-items: center; gap: 14px; font-size: 13.5px; }
.load-dots { display: inline-flex; gap: 8px; align-items: flex-end; height: 18px; }
.load-dots i { width: 10px; height: 10px; border-radius: 50%; animation: dotBounce 1.15s var(--ease-in-out) infinite; }
.load-dots i:nth-child(1) { background: var(--logo-1); }
.load-dots i:nth-child(2) { background: var(--logo-2); animation-delay: .11s; }
.load-dots i:nth-child(3) { background: var(--logo-3); animation-delay: .22s; }
.load-dots i:nth-child(4) { background: var(--logo-4); animation-delay: .33s; }
.load-dots i:nth-child(5) { background: var(--logo-5); animation-delay: .44s; }
@keyframes dotBounce { 0%, 55%, 100% { transform: translateY(0); opacity: .55; } 28% { transform: translateY(-8px); opacity: 1; } }
.empty-state::before {
  content: ""; display: block; width: 74px; height: 14px; margin: 0 auto var(--sp-4);
  background:
    radial-gradient(circle 5px at 7px 7px,  var(--logo-1) 98%, transparent 100%),
    radial-gradient(circle 5px at 22px 7px, var(--logo-2) 98%, transparent 100%),
    radial-gradient(circle 5px at 37px 7px, var(--logo-3) 98%, transparent 100%),
    radial-gradient(circle 5px at 52px 7px, var(--logo-4) 98%, transparent 100%),
    radial-gradient(circle 5px at 67px 7px, var(--logo-5) 98%, transparent 100%);
  background-repeat: no-repeat;
}
.empty-state::before { opacity: .45; }
.page-pad { padding: 26px 30px; max-width: 1100px; }
.empty-state { text-align: center; padding: 70px 20px; color: var(--text-soft); }

/* ------------------------------------------------- context header (board) */
.board-head {
  padding: var(--sp-4) var(--sp-6) var(--sp-3); position: sticky; top: 0; z-index: 20;
  background: var(--surface-0); border-bottom: 1px solid var(--line-2);
  transition: box-shadow var(--dur-micro-3) var(--ease-out), border-color var(--dur-micro-3) var(--ease-out);
}
.board-head.scrolled { border-bottom-color: var(--line-1); box-shadow: 0 10px 28px -14px rgba(20, 26, 43, .22); }
[data-theme="dark"] .board-head.scrolled { box-shadow: 0 12px 32px -14px rgba(0, 0, 0, .6); }
.board-title-row { display: flex; align-items: center; gap: var(--sp-1); min-width: 0; }
.board-name {
  font-family: var(--font-display); font-size: var(--fs-2xl); font-weight: var(--fw-bold);
  letter-spacing: var(--ls-display); outline: none; border-radius: var(--r-2);
  padding: 2px 8px; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.board-name:hover, .board-name:focus { background: var(--surface-2); }

.board-right { margin-left: auto; display: flex; gap: var(--sp-1); align-items: center; flex: none; }
.pill-count {
  background: var(--brand-soft); color: var(--brand); border-radius: var(--r-round);
  padding: 1px 7px; font-size: var(--fs-2xs); font-weight: var(--fw-bold);
}
.board-switch-row { display: flex; align-items: center; gap: var(--sp-3); margin-top: var(--sp-3); }
.view-tabs {
  display: inline-flex; align-items: center; gap: 2px;
  background: var(--surface-2); padding: 3px; border-radius: var(--r-2); flex: none;
}
.view-tab {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px;
  border-radius: var(--r-1); color: var(--text-2); font-weight: var(--fw-semibold);
  font-size: 13px; white-space: nowrap;
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.view-tab:hover { color: var(--text-1); }
.view-tab.active { background: var(--surface-1); color: var(--text-1); box-shadow: var(--elev-1); }
.board-tools { margin-left: auto; display: flex; gap: var(--sp-2); align-items: center; min-width: 0; }
.board-filter { width: 170px; padding: 6px 10px; border-radius: var(--r-2); font-size: 13px; }
.board-tools .tool-chip { gap: 4px; }
.view-tab { position: relative; padding-right: 10px; }
.view-tab.active { padding-right: 26px; }
.vt-menu {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px;
  border-radius: var(--r-1); color: var(--text-3); cursor: pointer;
}
.vt-menu:hover { background: var(--hover); color: var(--text-1); }
.view-add {
  display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px;
  border: none; background: transparent; color: var(--text-3); border-radius: var(--r-1); cursor: pointer;
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.view-add:hover { background: var(--surface-1); color: var(--brand); }
.board-table th.sorted .col-head { color: var(--brand); }

.board-body { padding: 20px var(--board-pad-x) 80px; flex: 1; }

/* --------------------------------------------------------------- table */
/* Group header: a section line, the title in display type, the count as a mono
   chip. The group's own colour lives on the 3px spine at :3986 and in the
   distribution bar, never as a fill (phase-67-c). Dark lifts the hue toward
   white so saturated data colours never sink into the night surfaces. */
.group-head {
  --gc: var(--gcolor);
  display: flex; align-items: center; gap: 8px; min-width: 0;
  padding: 5px 8px 5px 4px;
}
[data-theme="dark"] .group-head { --gc: color-mix(in srgb, var(--gcolor) 66%, #fff); }
.group-caret {
  background: none; border: none; cursor: pointer; color: var(--gc); padding: 0; flex: none;
  width: 26px; height: 26px; border-radius: var(--r-1); display: inline-flex; align-items: center; justify-content: center;
}
.group-caret:hover { background: color-mix(in srgb, var(--gc) 16%, transparent); }
.group-title {
  color: var(--text-1); font-family: var(--font-display); font-weight: 700; font-size: 17px; letter-spacing: -.01em;
  outline: none; padding: 2px 6px; border-radius: 5px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.group-title:hover, .group-title:focus { background: color-mix(in srgb, var(--gc) 14%, transparent); }
.group-title:focus { white-space: normal; }
.group-count { flex: none; margin-left: auto; white-space: nowrap; font-family: var(--font-mono); font-size: 12px; color: var(--text-2); padding: 0 4px; letter-spacing: .01em; }
.group-count b { font-weight: 600; color: var(--text-1); }
.group-count i { font-style: normal; color: var(--text-2); }
.group-head .group-menu { flex: none; width: 28px; height: 28px; border-radius: var(--r-1); color: var(--text-2); opacity: .9; margin-right: 2px; }
.group-head:hover .group-menu, .group-head .group-menu:focus-visible { opacity: 1; }
.group-head .group-menu:hover { background: color-mix(in srgb, var(--gc) 16%, transparent); color: var(--text-1); }
.group-head .icon-btn { color: var(--text-3); opacity: .7; }
.group-head:hover .icon-btn { opacity: 1; }

/* D28 (Q29): the per-group scroll box is gone — one .board-scroll now holds
   every group, so the same screen x is the same column in all fourteen. The
   element and its class stay: five selectors and the group's bottom rule
   (:4226) key off it. --shade moved to .board-scroll with the scrolling. */
.table-wrap { overflow-x: visible; background: var(--surface); }
.board-table {
  border-collapse: separate; border-spacing: 0; table-layout: fixed; min-width: 100%;
  background: var(--surface);
}
/* the name column stays put while a wide register scrolls sideways */
.board-table th.col-name, .board-table td.col-name {
  position: sticky; left: 0; z-index: 2; background: var(--surface);
  box-shadow: 1px 0 0 var(--border-soft);
}
.item-row:hover td.col-name { background: var(--surface-2); }
.board-table td.cell { white-space: nowrap; }
.board-table th {
  font-size: 13px; font-weight: 600; color: var(--text-2); text-align: center;
  padding: 9px 6px; border-bottom: 1px solid var(--line-1);
  border-left: 1px solid var(--border-soft); white-space: nowrap;
  background: color-mix(in srgb, var(--surface-2) 55%, var(--surface-1));
}
.board-table th.col-name { background: color-mix(in srgb, var(--surface-2) 55%, var(--surface-1)); }
.board-table th.col-name { text-align: left; padding-left: 16px; border-left: none; min-width: 260px; }
.board-table th.col-add { width: 44px; border-left: 1px solid var(--border-soft); }
.col-head {
  cursor: pointer; padding: 3px 8px; border-radius: 4px;
  font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: var(--fw-medium); line-height: var(--lh-snug);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3);
}
.col-head:hover { background: var(--hover); }
.board-table td {
  border-bottom: 1px solid var(--border-soft); border-left: 1px solid var(--border-soft);
  padding: 0; height: 42px; text-align: center; position: relative;
}
.board-table td.col-name { text-align: left; border-left: none; }
.board-table tr:last-child td { border-bottom: none; }
.item-row { transition: background .12s; }
.item-row:hover { background: var(--surface-2); }
.item-row.dragging { opacity: .4; }
.row-accent {
  position: absolute; left: 0; top: 0; bottom: 0; width: 5px; background: var(--gcolor);
}
td.col-name { padding: 0 6px 0 16px !important; }
.item-name {
  outline: none; padding: 3px 6px; border-radius: 4px; font-weight: 500; font-size: 13.5px; color: var(--text-1);
  display: inline-block; max-width: 60%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: middle;
}
.item-name:hover, .item-name:focus { background: var(--hover); white-space: normal; }
.chat-btn {
  background: none; border: none; cursor: pointer; color: var(--cell-empty);
  font-size: 13px; padding: 3px 5px; border-radius: 4px; vertical-align: middle;
}
.chat-btn.has { color: var(--text-2); }
.chat-btn.has span { font-family: var(--font-mono); font-size: 10.5px; color: var(--text-1); background: var(--surface-2); border-radius: var(--r-round); padding: 1px 6px; margin-left: 1px; }
.chat-btn:hover { background: var(--hover); }
.chat-btn span { font-size: 11px; margin-left: 2px; }
/* name cell: title flexes, the open and options controls sit in fixed slots */
td.col-name .cn { display: flex; align-items: center; gap: 2px; min-width: 0; }
td.col-name .item-name { flex: 1 1 auto; min-width: 0; max-width: none; display: block; }
.chat-btn { flex: none; width: 44px; display: inline-flex; align-items: center; justify-content: center; gap: 3px; height: 26px; }
.row-menu {
  flex: none; opacity: .75; width: 26px; height: 26px; border-radius: 7px; margin: 0;
  color: var(--text-2); transition: opacity var(--dur-micro-1) var(--ease-out), background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.item-row:hover .row-menu, .row-menu:focus-visible { opacity: 1; background: var(--hover); color: var(--text-1); }
.row-menu:hover { box-shadow: inset 0 0 0 1px var(--border-soft); }

.cell { cursor: pointer; font-size: 13px; }
.cell:hover { box-shadow: inset 0 0 0 1px var(--primary); }
.cell.inline { display: inline-flex; align-items: center; min-height: 30px; padding: 0 8px; border-radius: 5px; border: 1px solid var(--border-soft); }
/* Status language: dot-plus-label chip on tinted ground (never full-bleed).
   --lab is fed per-chip from the label's stored color. */
.status-pill {
  --lab-ink: color-mix(in srgb, var(--lab, var(--text-3)) 60%, var(--text-1));
  display: inline-flex; align-items: center; gap: 8px; color: var(--text-1);
  font-weight: 600; font-size: var(--fs-xs); line-height: var(--lh-snug); padding: 3px 10px 3px 8px; border-radius: var(--r-1);
  background: color-mix(in srgb, var(--lab, var(--text-3)) 13%, transparent);
  max-width: 100%;
}
.status-pill::before {
  content: ""; width: 8px; height: 8px; border-radius: var(--r-dot);
  background: var(--lab, var(--text-3)); flex: none;
  box-shadow: inset 0 0 0 1px var(--lab-ink);
}
.status-pill.unset { background: none; padding: 3px 6px; }
.status-pill.unset::before { background: var(--cell-empty); opacity: .45; box-shadow: none; }
.cell.inline .status-pill { min-height: 26px; }
.status-pill.sm { font-size: var(--fs-2xs); padding: 2px 8px 2px 6px; gap: 6px; }
.status-pill.lg { font-size: var(--fs-sm); padding: 5px 12px 5px 10px; }
.cell-text { padding: 0 8px; display: inline-block; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cell-text.num { font-variant-numeric: tabular-nums; }
.cell-people { display: inline-flex; align-items: center; }
.cell-people .avatar:not(:only-child) { margin-left: -8px; box-shadow: 0 0 0 2px var(--surface-1), 0 0 0 3px var(--line-1); }
.cell-people .avatar:first-child { margin-left: 0; }
.cell-people .more { font-size: 11px; color: var(--text-soft); margin-left: 3px; }
.cell-empty-people { color: var(--cell-empty); font-size: 15px; }
.cell-date { font-size: 12.5px; font-variant-numeric: tabular-nums; }
.cell-date.overdue { color: var(--red); font-weight: 700; }
.cell-date.soon { color: var(--amber); font-weight: 700; }
.cell-muted { display: inline-block; min-height: 1em; }
.timeline-pill {
  background: var(--grad); color: #fff;
  font-size: 11px; font-weight: 600; padding: 4px 10px; border-radius: 12px; white-space: nowrap;
}
.cell-check {
  width: 20px; height: 20px; border: 2px solid var(--cell-empty); border-radius: 5px;
  display: inline-flex; align-items: center; justify-content: center; color: #fff; font-size: 12px;
}
.cell-check.on { background: var(--green); border-color: var(--green); }

.cell-link { font-size: 13px; text-decoration: underline; }
.cell-rating span { color: var(--cell-empty); font-size: 14px; }
.cell-rating span.on { color: var(--accent-2); }
.cell-rating span.on .ic, .rate-opt .ic { fill: currentColor; }
/* small circular markers that replaced text bullets */
.dot-mark {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: currentColor;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, currentColor 60%, var(--text-1));
}
.mode-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--text-soft); margin-right: 5px; vertical-align: 1px; }
.ai-mode.live .mode-dot { background: #fff; }

.add-row td { padding: 0 !important; }
.add-item-input {
  width: 100%; border: none; background: transparent; padding: 10px 16px; border-radius: 0;
  color: var(--text); font-size: 13px;
}
.add-item-input::placeholder { color: var(--text-soft); }
.add-item-input:focus { background: var(--surface-2); }
.summary-row td { height: 30px; background: var(--surface-2); }
.summary-row td.col-name, .summary-row td:first-child { position: sticky; left: 0; z-index: 2; background: var(--surface-2); }
.dist-bar { display: flex; height: 18px; border-radius: 4px; overflow: hidden; margin: 0 8px; }
.dist-seg.empty { background: var(--cell-empty); }
.dist-seg { min-width: 4px; }
.sum-cell { font-size: 12px; font-weight: 700; color: var(--text-soft); }
.add-group { margin-top: 6px; color: var(--text-soft); }
.drop-hint { outline: 2px dashed var(--primary); outline-offset: -2px; background: var(--primary-soft) !important; }

/* --------------------------------------------------------------- kanban */
.kanban { display: flex; gap: 14px; align-items: flex-start; overflow-x: auto; padding-bottom: 20px; }
.kanban-col { min-width: 250px; width: 250px; flex: none; }
.kanban-col-head {
  --lab-ink: color-mix(in srgb, var(--lab, var(--text-3)) 60%, var(--text-1));
  color: var(--text-1); font-weight: 600; padding: 5px 12px 5px 10px; font-size: var(--fs-sm); line-height: var(--lh-snug);
  display: flex; align-items: center; gap: 8px; margin-bottom: 6px;
  background: color-mix(in srgb, var(--lab, var(--text-3)) 13%, transparent); border-radius: var(--r-1);
}
.kanban-col-head::before {
  content: ""; width: 8px; height: 8px; border-radius: var(--r-dot);
  background: var(--lab, var(--text-3)); flex: none;
  box-shadow: inset 0 0 0 1px var(--lab-ink);
}
.kanban-col-head > span:first-of-type { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kanban-count { background: var(--surface-1); color: var(--text-2); border-radius: var(--r-round); padding: 0 8px; font-size: 12px; margin-left: auto; flex: none; }
.kanban-list {
  background: var(--surface-2); border-radius: 0 0 8px 8px; padding: 10px; min-height: 120px;
  display: flex; flex-direction: column; gap: 8px;
}
.kanban-card {
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: 8px;
  padding: 12px; cursor: grab;
  transition: transform .15s, box-shadow .15s;
}
.kanban-card:hover { transform: translateY(-2px); box-shadow: var(--shadow); }
.kanban-card.dragging { opacity: .4; }
.kanban-card-name { font-weight: 600; margin-bottom: 8px; }
.kanban-card-foot { display: flex; justify-content: space-between; align-items: center; gap: 8px; }
.kanban-group { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-2xs); font-weight: 500; color: var(--text-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kanban-group .dot-mark { flex: none; }
.kanban-meta { display: inline-flex; align-items: center; gap: 6px; flex: none; }
.kanban-meta .cell-date { font-size: 11px; color: var(--text-3); }
.kanban-files { display: inline-flex; align-items: center; gap: 2px; font-size: 11px; color: var(--text-3); }
.kanban-empty { color: var(--text-3); font-size: 12px; text-align: center; padding: 26px 0; border: 1.5px dashed var(--border-soft); border-radius: 8px; }

/* ------------------------------------------------------------- calendar */
.cal-head { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
.cal-head h3 { margin: 0; min-width: 170px; text-align: center; }
.cal-grid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 1px;
  background: var(--border-soft); border: 1px solid var(--border-soft); border-radius: 10px; overflow: hidden;
}
.cal-dow {
  background: var(--surface-2); padding: 9px 8px; text-align: center;
  font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: var(--fw-medium); line-height: var(--lh-snug);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3);
}
.cal-cell { background: var(--surface); min-height: 96px; padding: 6px; }
.cal-cell.empty { background: var(--surface-2); }
.cal-cell.today { background: var(--primary-soft); }
.cal-day { font-size: 12px; font-weight: 700; color: var(--text-soft); margin-bottom: 4px; }
.cal-cell.today .cal-day { color: var(--primary); }
.cal-item {
  --lab-ink: color-mix(in srgb, var(--lab, var(--text-3)) 60%, var(--text-1));
  display: flex; align-items: center; gap: 6px; width: 100%; text-align: left; border: none;
  color: var(--text-1); background: color-mix(in srgb, var(--lab, var(--brand)) 13%, transparent);
  font-size: var(--fs-2xs); font-weight: 600; line-height: var(--lh-snug); padding: 2px 8px 2px 6px; border-radius: var(--r-1);
  margin-bottom: 3px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.cal-item::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: var(--lab, var(--brand)); flex: none; box-shadow: inset 0 0 0 1px var(--lab-ink); }
.cal-item:hover { background: color-mix(in srgb, var(--lab, var(--brand)) 26%, transparent); }
.cal-cell.past .cal-day { color: var(--text-3); }
.cal-more { font-size: 10.5px; color: var(--text-soft); background: none; border: none; cursor: pointer; padding: 2px 4px; }
.cal-more:hover { color: var(--brand); }
.cal-note { display: inline-flex; align-items: center; gap: 5px; margin-left: auto; }

/* ------------------------------------------------------------- timeline */
.tl-scroll { overflow-x: auto; background: var(--surface); border: 1px solid var(--border-soft); border-radius: 10px; padding: 14px 14px 20px; }
.tl-header { display: flex; margin-bottom: 8px; align-items: flex-end; }
.tl-day { flex: none; text-align: center; border-left: 1px dashed var(--line-2); padding-bottom: 4px; }
.tl-day.weekend { opacity: .7; }
.tl-day.today { color: var(--brand); font-weight: var(--fw-semibold); border-left-color: var(--brand); }
.tl-row { display: flex; align-items: center; height: 38px; }
.tl-label {
  width: 220px; flex: none; font-size: 13px; font-weight: 500; padding-right: 12px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* the corner shares .tl-label's box, so the axis voice is stated after it */
.tl-corner, .tl-day {
  font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: var(--fw-medium); line-height: var(--lh-snug);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3);
}
.tl-track { position: relative; height: 28px; background: repeating-linear-gradient(90deg, transparent 0, transparent 100%); }
/* The bar is the status chip stretched: ink on an opaque 18% tint (so the
   day grid never shows through the label), a 3px spine in the raw hue plus
   the --lab-ink hairline, and a hairline round the box. Never white on the
   raw hue: white on #ffcb00 measured 1.52:1. */
.tl-bar {
  --lab-ink: color-mix(in srgb, var(--lab, var(--text-3)) 60%, var(--text-1));
  position: absolute; top: 3px; height: 22px; border: none; border-radius: var(--r-1);
  display: flex; align-items: center; gap: 8px; padding: 0 8px 0 11px; text-align: left;
  background: color-mix(in srgb, var(--lab, var(--text-3)) 18%, var(--surface-1));
  color: var(--text-1); font-size: var(--fs-2xs); font-weight: 600; line-height: 1; cursor: pointer;
  overflow: hidden; white-space: nowrap;
  box-shadow: inset 3px 0 0 var(--lab, var(--text-3)), inset 4px 0 0 var(--lab-ink), inset 0 0 0 1px color-mix(in srgb, var(--lab-ink) 35%, transparent);
  transition: box-shadow var(--dur-micro-1) var(--ease-out);
}
.tl-bar > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tl-bar b { margin-left: auto; flex: none; font: 500 10.5px var(--font-mono); color: var(--text-3); font-variant-numeric: tabular-nums; }
.tl-bar:hover { box-shadow: inset 3px 0 0 var(--lab, var(--text-3)), inset 4px 0 0 var(--lab-ink), inset 0 0 0 1px var(--lab-ink); }
/* under ~90px the label cannot fit: the bar keeps its spine and tint and the
   title attribute carries the name */
.tl-bar.narrow { padding: 0; color: transparent; }
.tl-bar.narrow b { display: none; }

/* ------------------------------------------------------------ dashboard */





.legend-row { display: flex; align-items: center; gap: 8px; padding: 3px 0; font-size: 13px; }
.legend-row b { margin-left: auto; }
.legend { flex: 1; min-width: 140px; }
.dot { width: 10px; height: 10px; border-radius: 3px; display: inline-block; flex: none; }
.dot.lg { width: 14px; height: 14px; }
.wl-row { display: flex; align-items: center; gap: 10px; padding: 5px 0; font-size: 13px; }
.wl-name { width: 110px; flex: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wl-bar { flex: 1; height: 10px; background: var(--surface-2); border-radius: 6px; overflow: hidden; }
.wl-fill { height: 100%; border-radius: 6px; transition: width .5s ease; }
.stat-pair { display: flex; gap: 14px; margin-bottom: 12px; }
.stat {
  flex: 1; background: var(--surface-2); border-radius: 10px; padding: 14px; text-align: center;
}
.stat b { font-size: 26px; display: block; }
.stat span { font-size: 12px; color: var(--text-soft); }
.stat.bad b { color: var(--red); }
.stat.warn b { color: var(--amber, #b26a00); }
.legend-row .muted { margin-left: 6px; }
.mini-item {
  display: flex; width: 100%; align-items: center; gap: 8px; background: none; border: none;
  padding: 6px 4px; cursor: pointer; font-size: 13px; color: var(--text); border-radius: 6px; text-align: left;
}
.mini-item:hover { background: var(--hover); }

/* ------------------------------------------------------- popover & menu */
.popover {
  position: fixed; z-index: 90; background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: 10px; box-shadow: var(--shadow-lg); padding: 8px; min-width: 180px;
  opacity: 0; transform: translateY(-4px);
  transition: opacity var(--dur-micro-2) var(--ease-out), transform var(--dur-micro-2) var(--ease-out);
  max-height: 70vh; overflow-y: auto;
}
.popover.show { opacity: 1; transform: none; }
.menu { display: flex; flex-direction: column; min-width: 190px; }
.menu-item {
  display: flex; align-items: center; gap: 8px; background: none; border: none; cursor: pointer;
  padding: 8px 10px; border-radius: 6px; font-size: 13.5px; color: var(--text); text-align: left; width: 100%;
}
.menu-item:hover { background: var(--hover); }
.menu-item.danger { color: var(--red); }
.menu-item.on { background: var(--primary-soft); color: var(--primary); }
.menu-item.on > .ic:last-child:not(:first-child) { color: var(--primary); margin-left: auto; }
.menu-item:disabled { opacity: .4; cursor: default; }
.menu-item:disabled:hover { background: none; }
.menu-item b { font-weight: 700; }
.menu-sep { height: 1px; background: var(--border-soft); margin: 6px 4px; }
.menu-row { display: flex; gap: 6px; padding: 4px 10px 8px; flex-wrap: wrap; }
.chip-btn {
  border: 1px solid var(--border-soft); background: var(--surface-2); color: var(--text);
  font-size: 12px; font-weight: 600; padding: 4px 10px; border-radius: var(--r-round); cursor: pointer;
  transition: background var(--dur-micro-1) var(--ease-out), border-color var(--dur-micro-1) var(--ease-out);
}
.chip-btn:hover { border-color: var(--brand); color: var(--brand); }
.chip-btn.on { background: var(--primary-soft); color: var(--primary); border-color: var(--primary); }
.col-menu, .cols-menu { min-width: 240px; }
.col-menu .edit-box, .menu .edit-box { padding: 4px 4px 8px; }
.menu .hint, .edit-box .hint { font-size: 11px; color: var(--text-3); padding: 0 2px; }
.check-row { cursor: pointer; }
.check-row input { width: 16px; height: 16px; flex: none; margin: 0; padding: 0; accent-color: var(--brand); }
.check-row .cr-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cols-menu { max-height: 70vh; overflow-y: auto; }
.edit-row { display: flex; gap: 6px; align-items: center; justify-content: flex-end; }
.dd-add { margin-top: 4px; }
.dd-add input { font-size: 12.5px; }
.label-opt.on { box-shadow: inset 0 0 0 2px var(--brand); }
.color-dot.on { box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--brand); }
.menu-label { font-size: 11px; font-weight: 800; color: var(--text-soft); padding: 8px 10px 4px; letter-spacing: .5px; text-transform: uppercase; }
.menu.cols { max-height: 320px; overflow-y: auto; }
.menu-colors { display: flex; gap: 6px; padding: 8px 10px; flex-wrap: wrap; }
.color-dot { width: 22px; height: 22px; border-radius: 6px; border: none; cursor: pointer; }
.color-dot:hover { transform: scale(1.15); }
.me-head { display: flex; gap: 10px; align-items: center; padding: 8px 10px; border-bottom: 1px solid var(--border-soft); margin-bottom: 6px; }

.label-menu { display: flex; flex-direction: column; gap: 5px; min-width: 190px; }
.label-opt {
  --lab-ink: color-mix(in srgb, var(--lab, var(--text-3)) 60%, var(--text-1));
  display: flex; align-items: center; gap: 8px;
  border: none; color: var(--text-1); font-weight: 600; padding: 8px 12px 8px 10px; border-radius: var(--r-1);
  cursor: pointer; font-size: var(--fs-sm); line-height: var(--lh-snug); text-align: left;
  background: color-mix(in srgb, var(--lab, var(--text-3)) 13%, transparent);
  transition: background var(--dur-micro-1) var(--ease-out);
}
.label-opt::before {
  content: ""; width: 8px; height: 8px; border-radius: var(--r-dot);
  background: var(--lab, var(--text-3)); flex: none;
  box-shadow: inset 0 0 0 1px var(--lab-ink);
}
.label-opt:hover { background: color-mix(in srgb, var(--lab, var(--text-3)) 24%, transparent); }
.label-opt.plain { background: var(--surface-2); color: var(--text); }
.label-opt.plain::before { display: none; }
.label-edit {
  background: none; border: none; color: var(--text-soft); font-size: 12px; cursor: pointer;
  padding: 7px; border-radius: 5px;
}
.label-edit:hover { background: var(--hover); }
.label-row { display: flex; gap: 8px; margin-bottom: 8px; }
input[type=color] { width: 44px; padding: 2px; height: 36px; }
.label-row .lab-text { flex: 1; }

.people-menu { display: flex; flex-direction: column; min-width: 220px; max-height: 300px; overflow-y: auto; }
.people-opt {
  display: flex; align-items: center; gap: 9px; background: none; border: none; cursor: pointer;
  padding: 7px 9px; border-radius: 6px; font-size: 13.5px; color: var(--text); text-align: left;
}
.people-opt:hover { background: var(--hover); }
.people-opt.on { background: var(--primary-soft); }

.edit-box { display: flex; flex-direction: column; gap: 8px; min-width: 220px; }
.edit-box input { width: 100%; }
.timeline-edit label { margin-bottom: 4px; }
.rating-menu { display: flex; flex-direction: column; gap: 4px; }
.rate-opt { background: none; border: none; cursor: pointer; font-size: 16px; color: var(--accent-2); text-align: left; padding: 5px 9px; border-radius: 5px; }
.rate-opt:hover { background: var(--hover); }

/* groundskeeping (phase-137): the old `.search-pop` / `.search-results` removed; no template has rendered either class since the drawer search became the palette. */

/* --------------------------------------------------------------- modal */
.modal-overlay {
  position: fixed; inset: 0; z-index: 80;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  opacity: 0; transition: opacity .18s;
}
.modal-overlay.show { opacity: 1; }
.modal {
  background: var(--surface); border-radius: 14px; box-shadow: var(--shadow-lg);
  padding: 26px; width: min(480px, 94vw); max-height: 88vh; overflow-y: auto;
  transform: translateY(10px); transition: transform .18s;
}
.modal-overlay.show .modal { transform: none; }
.modal.wide { width: min(700px, 94vw); }
.modal.wide.tall { width: min(860px, 96vw); }
.modal-foot { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
.modal-foot.left { justify-content: flex-start; }

/* ------------------------------------------------------------ item card */
.item-card h3 { outline: none; padding: 2px 6px; border-radius: 6px; }
.item-card h3:hover, .item-card h3:focus { background: var(--hover); }
.ic-meta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 4px 6px 0; }
.ic-meta .btn { margin-left: auto; }
.ic-group { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 700; color: var(--gcolor, var(--text-2)); }
.item-values { display: flex; flex-direction: column; gap: 8px; margin: 14px 0 20px; }
.iv-row { display: flex; align-items: center; gap: 12px; }
.iv-label { width: 120px; flex: none; font-size: 12.5px; font-weight: 600; color: var(--text-soft); }
.modal { position: relative; }
.modal-x { position: absolute; top: 12px; right: 12px; color: var(--text-soft); z-index: 2; }
.item-attach.drop-hover { outline: 1.5px dashed var(--brand); outline-offset: -6px; background: var(--brand-soft); }
.attach-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.attach-btn { cursor: pointer; display: inline-flex; align-items: center; gap: 5px; }
.attach-list { margin-top: 8px; display: flex; flex-direction: column; gap: 4px; }
.attach-row { display: flex; align-items: center; gap: 8px; padding: 5px 6px; border-radius: 8px; }
.attach-row:hover { background: var(--hover); }

.attach-name { font-weight: 600; color: var(--text); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.attach-name:hover { color: var(--primary); text-decoration: underline; }
.attach-row .icon-btn { margin-left: auto; }
.attach-empty { margin: 8px 2px 2px; }
.cell-files { display: inline-flex; align-items: center; gap: 5px; color: var(--text-soft); font-weight: 600; }
.cell-files.empty { opacity: 0; }
.cell:hover .cell-files.empty { opacity: .7; }
.item-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border-soft); margin-bottom: 14px; }
.item-tab {
  background: none; border: none; padding: 9px 14px; cursor: pointer; font-weight: 600;
  color: var(--text-soft); border-bottom: 2px solid transparent; font-size: 13.5px;
}
.item-tab.active { color: var(--primary); border-bottom-color: var(--primary); }
.update-composer { display: flex; gap: 8px; margin-bottom: 16px; }
.update-composer textarea { flex: 1; resize: vertical; }
.update { background: var(--surface-2); border-radius: 10px; padding: 12px 14px; margin-bottom: 10px; }
.update-head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.update-body { line-height: 1.55; white-space: pre-wrap; }
.like-btn {
  background: none; border: 1px solid var(--border-soft); border-radius: 12px; cursor: pointer;
  font-size: 12px; padding: 3px 10px; margin-top: 8px; color: var(--text-soft);
}
.like-btn.on { background: var(--primary-soft); color: var(--primary); border-color: var(--primary); }
.activity-list { max-height: 340px; overflow-y: auto; }
.activity-list.tall { max-height: 55vh; }
.act-row { display: flex; align-items: center; gap: 10px; padding: 8px 4px; border-bottom: 1px solid var(--border-soft); font-size: 13px; }
.act-row span:nth-child(2) { flex: 1; }

/* ---------------------------------------------------------- automations */
.auto-list { display: flex; flex-direction: column; gap: 8px; margin: 14px 0; }
.auto-row {
  display: flex; align-items: center; gap: 10px; background: var(--surface-2);
  border-radius: 10px; padding: 11px 14px; font-size: 13.5px;
}
.auto-row.off { opacity: .5; }
.auto-text { flex: 1; line-height: 1.5; }
.switch { position: relative; width: 36px; height: 20px; flex: none; margin: 0; }
.switch input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.switch span {
  position: absolute; inset: 0; background: var(--line-strong); border-radius: 10px; cursor: pointer;
  transition: background .18s;
}
.switch span::after {
  content: ""; position: absolute; width: 16px; height: 16px; background: #fff; border-radius: 50%;
  top: 2px; left: 2px; transition: left .18s;
}
.switch input:checked + span { background: var(--green); }
.switch input:checked + span::after { left: 18px; }
.auto-builder { background: var(--surface-2); border-radius: 12px; padding: 16px; margin-top: 10px; }
.auto-line { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; font-weight: 600; }
.auto-line select, .auto-line input { padding: 6px 9px; }

/* ------------------------------------------------------------ templates */
.tpl-ai { display: flex; gap: 8px; margin-bottom: 18px; }
.tpl-ai input { flex: 1; }
.tpl-cat h4 { margin: 16px 0 10px; color: var(--text-soft); font-size: 13px; text-transform: uppercase; letter-spacing: .6px; }
.tpl-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: var(--sp-4); }
.tpl-card {
  background: var(--surface-2); border: 1px solid var(--border-soft); border-radius: 12px;
  padding: 16px; display: flex; flex-direction: column; gap: 6px; transition: transform .15s, box-shadow .15s;
}
.tpl-card:hover { transform: translateY(-3px); box-shadow: var(--shadow); }
.tpl-card p { font-size: 12px; color: var(--text-soft); margin: 0; flex: 1; line-height: 1.45; }
.tpl-cols { display: flex; gap: 4px; flex-wrap: wrap; }
.tpl-cols span { background: var(--surface); border: 1px solid var(--border-soft); font-size: 10.5px; padding: 2px 7px; border-radius: 9px; color: var(--text-soft); }
.tpl-actions { display: flex; gap: 6px; align-items: center; margin-top: 8px; }
.tpl-card.tpl-lib { border-color: var(--brand); background: linear-gradient(180deg, var(--brand-soft), var(--surface-2) 70%); }
.tpl-chip {
  display: inline-block; margin-left: 8px; vertical-align: 2px; font-size: 10px; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; color: var(--brand); background: var(--surface-1); padding: 2px 7px; border-radius: var(--r-round);
}

/* -------------------------------------------------------- notifications */
.notif-pop { min-width: 340px; max-width: 90vw; }
.notif-head { display: flex; justify-content: space-between; align-items: center; padding: 4px 8px 10px; }
.notif-row {
  display: flex; align-items: flex-start; gap: 10px; width: 100%; background: none; border: none;
  text-align: left; cursor: pointer; padding: 10px 8px; border-radius: 8px; color: var(--text);
}
.notif-row:hover { background: var(--hover); }
.notif-row.unread { background: var(--primary-soft); }
.notif-text { flex: 1; font-size: 13px; line-height: 1.45; }
.notif-icon { font-size: 16px; }

/* --------------------------------------------------------------- my work */
.mywork-group { margin-top: 22px; }
.mywork-group h4 { color: var(--text-soft); }
.mywork-row {
  display: flex; align-items: center; gap: 14px; width: 100%; background: var(--surface);
  border: 1px solid var(--border-soft); border-radius: 9px; padding: 12px 16px;
  cursor: pointer; margin-bottom: 8px; text-align: left; transition: transform .12s, box-shadow .12s;
}
.mywork-row:hover { transform: translateX(3px); box-shadow: var(--shadow); }
.mw-name { flex: 1; font-weight: 600; color: var(--text); }

/* ----------------------------------------------------------------- admin */
.admin-tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border-soft); margin: 14px 0 22px; }
.admin-tab { padding: 9px 16px; font-weight: 600; color: var(--text-soft); border-bottom: 2px solid transparent; }
.admin-tab.active { color: var(--primary); border-bottom-color: var(--primary); }
.admin-card {
  background: var(--surface); border: 1px solid var(--border-soft); border-radius: 12px;
  padding: 22px; margin-bottom: 18px;
}
.admin-card.narrow { max-width: 440px; }
.card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }
.card-head h4 { margin: 0; }
.invite-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--border-soft); }
.invite-link {
  background: var(--surface-2); border: 1px dashed var(--border); border-radius: 8px;
  padding: 12px; font-family: monospace; font-size: 12px; word-break: break-all; margin-top: 10px;
}
.storage-line { display: flex; align-items: center; gap: 12px; margin: 16px 0 6px; font-size: 13px; font-weight: 600; }
.storage-bar { flex: 1; height: 8px; background: var(--surface-2); border-radius: 6px; overflow: hidden; }
.storage-fill { height: 100%; background: var(--brand); border-radius: 6px; min-width: 2px; }

/* ----------------------------------------------------------- legal pages */
.legal-page { max-width: 760px; margin: 0 auto; padding: 60px 24px 90px; }
.legal-page h1 { font-family: var(--display, inherit); font-size: 40px; margin-bottom: 6px; }
.legal-page h2 { margin: 26px 0 8px; font-size: 18px; }
.legal-page p, .legal-page li { line-height: 1.65; color: var(--text-soft); font-size: 14.5px; }
.legal-page ul { padding-left: 20px; margin: 8px 0; }
.legal-page li { margin: 5px 0; }
.legal-page b { color: var(--text); }
.auth-legal { margin: 10px 0 2px; text-align: center; }

/* -------------------------------------------------------------- AI panel */
.ai-panel {
  position: fixed; top: 52px; right: 0; bottom: 0; width: 0; z-index: 50;
  background: var(--surface); border-left: 1px solid var(--border-soft);
  display: flex; flex-direction: column; overflow: hidden; transition: width .25s ease;
  box-shadow: -8px 0 30px rgba(0,0,0,.08);
}
.ai-panel.open { width: min(380px, 92vw); }
.main { transition: margin-right .25s ease; }
@media (min-width: 901px) {
  body.ai-open .main { margin-right: 380px; }
}
.ai-head { display: flex; align-items: center; gap: 10px; padding: 14px 16px; border-bottom: 1px solid var(--border-soft); }
.ai-title { font-weight: 800; font-size: 15px; }
.ai-mode { font-size: 11px; font-weight: 700; color: var(--text-soft); background: var(--surface-2); padding: 3px 9px; border-radius: 10px; white-space: nowrap; }
.ai-mode.live { color: #fff; background: var(--green); }
.ai-head .icon-btn { margin-left: auto; }
.ai-messages { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
.ai-msg { display: flex; gap: 8px; align-items: flex-end; }
.ai-msg.user { justify-content: flex-end; }
.ai-bubble {
  background: var(--surface-2); border-radius: 14px 14px 14px 4px; padding: 10px 14px;
  max-width: 85%; line-height: 1.55; font-size: 13.5px;
}
.ai-msg.user .ai-bubble { background: var(--primary); color: var(--brand-fg); border-radius: 14px 14px 4px 14px; }
.ai-empty { text-align: center; margin: auto 0; color: var(--text-soft); }
.ai-chips { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 14px; }
.ai-chip {
  background: var(--primary-soft); color: var(--primary); border: none; border-radius: 16px;
  padding: 7px 14px; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.ai-chip:hover { background: var(--primary); color: #fff; }
.ai-input { display: flex; gap: 8px; padding: 12px 14px; border-top: 1px solid var(--border-soft); }
.ai-input textarea { flex: 1; resize: none; }

/* --------------------------------------------------------------- toasts */
#toasts { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 120; display: flex; flex-direction: column; gap: 8px; align-items: center; }
.toast {
  background: var(--text); color: var(--bg); padding: 11px 22px; border-radius: 10px;
  font-weight: 600; font-size: 13.5px; box-shadow: var(--shadow-lg);
  opacity: 0; transform: translateY(12px); transition: opacity .3s, transform .3s; max-width: 90vw;
}
.toast.show { opacity: 1; transform: none; }
.toast.success { background: var(--green); color: #fff; }
.toast.error { background: var(--red); color: #fff; }
.toast.notif { background: var(--primary); color: #fff; }

/* ------------------------------------------------------------- icons */
.ic { flex: none; vertical-align: -3.5px; }
h3 .ic { vertical-align: -2.5px; margin-right: 4px; color: var(--primary); }
.view-tab { display: inline-flex; align-items: center; gap: 6px; }
.view-tab .ic, .btn .ic { opacity: .85; }
.chat-btn .ic { vertical-align: -2px; }
.board-glyph { display: inline-flex; color: var(--text-soft); }
.icon-btn .ic { vertical-align: -4px; }
.icon-btn.star.on .ic { fill: currentColor; }
.notif-icon { display: inline-flex; color: var(--primary); margin-top: 2px; }
.ai-avatar { display: inline-flex; color: var(--brand); }
.ai-title { display: inline-flex; align-items: center; gap: 7px; }
.ai-title .ic { color: var(--brand); }
.menu-item .ic { color: var(--text-soft); }
.menu-item.danger .ic { color: var(--red); }
.side-link .ic { color: var(--text-soft); }
.side-link.active .ic { color: var(--primary); }

/* theme toggle shows the mode you'd switch to: moon in light, sun in dark */
.theme-btn .ic + .ic { display: none; }
[data-theme="dark"] .theme-btn .ic { display: none; }
[data-theme="dark"] .theme-btn .ic + .ic { display: inline-block; }


/* ------------------------------------------------- premium quality floor */
::selection { background: var(--selection-bg); }
/* THE FOCUS FLOOR IS ONE OF TWO PLACES, not one: the text-entry fan ring and
   the --focus-ring band live in the phase-68 block further down this file
   (search "phase-68"); this rule is the floor under links, buttons and the
   contenteditable renames. Read both before touching either.
   phase-72-a removed `border-radius: var(--r-2)` from here. It set the
   ELEMENT's own radius while focused, not the outline's, so every button, link
   and tab in the app changed shape the moment you tabbed to it (.btn.primary
   measured 8px at rest and 5px focused). `outline` follows the element's own
   border-radius natively, so the ring still traces the curve without it — and
   with the capsule now carrying meaning, a pill that snapped to 5px on focus
   would have been far worse. Nothing else in the lock changed: the ring is
   still one 2px --brand outline at 2px offset, with no inner band. */
a:focus-visible, button:focus-visible, [contenteditable]:focus-visible {
  outline: 2px solid var(--brand); outline-offset: 2px;
}
input:focus-visible, select:focus-visible, textarea:focus-visible { outline: none; }
* { scrollbar-width: thin; scrollbar-color: var(--line-1) transparent; }
::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: var(--line-1); border-radius: 5px; border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }

/* ----------------------------------------------------------- animations */
@keyframes fadeUp { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-14px); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }
@keyframes livePulse {
  0% { transform: scale(1); opacity: .45; }
  55%, 100% { transform: scale(2.5); opacity: 0; }
}

/* ----------------------------------------------------------- responsive */
@media (max-width: 1100px) {
  .act-label { display: none; }

}

@media (max-width: 1020px) {
  .auth-page { grid-template-columns: 1fr; }
  .auth-side { display: none; }
}
@media (max-width: 700px) {
  .rail { width: 48px; }
  .rail-btn { width: 38px; height: 38px; }
  .sidebar { left: 48px; width: min(288px, calc(100vw - 48px)); }
  .drawer-scrim { inset: 0 0 0 48px; }
  .vt-label { display: none; }
  .view-tab.active .vt-label { display: inline; }

  .board-head { padding: var(--sp-2) var(--sp-3); }
  .board-title-row { flex-wrap: wrap; row-gap: var(--sp-2); }
  .board-name { font-size: var(--fs-lg); flex: 1 1 auto; min-width: 0; }
  .board-right { flex: 1 1 100%; margin-left: 0; justify-content: flex-start; gap: 4px; }
  .board-right .btn { padding: 6px 8px; }
  .board-switch-row { flex-wrap: wrap; gap: var(--sp-2); margin-top: var(--sp-2); }
  .view-tabs { overflow-x: auto; max-width: 100%; scrollbar-width: none; }
  .view-tabs::-webkit-scrollbar { display: none; }
  .board-tools { margin-left: 0; flex: 1 1 100%; }
  .board-search { flex: 1 1 auto; min-width: 0; }
  .board-search .board-filter { flex: 1; width: 100%; min-width: 0; }
  #qf-btn { flex: none; padding: 0 9px; }
  .board-body { padding: 12px 10px 80px; }
  .board-table th.col-name { min-width: 180px; }
  .page-pad { padding: 18px 14px; }
}
@media (max-width: 900px) {
  :root { --board-pad-x: 14px; }
  .mk-header nav { gap: 10px; }
  .mk-header nav a.mk-nav-link { display: none; }
  .mk-footer-inner { grid-template-columns: 1fr 1fr; }
  .board-body { padding: var(--board-pad-x); }
  .tl-label { width: 130px; }
}

/* ------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .reveal { opacity: 1 !important; transform: none !important; }
}

/* -------------------------------------------------- motion & life (P5) */
/* (theme cross-fade removed in phase-28: the switch is a single paint) */

/* The logo is the heartbeat: sequential column pulse on live traffic. */
.logo-mark.beat circle {
  animation: logoBeat .55s var(--ease-spring);
  transform-origin: center; transform-box: fill-box;
}
.logo-mark.beat circle:nth-child(n+6)  { animation-delay: .06s; }
.logo-mark.beat circle:nth-child(n+11) { animation-delay: .12s; }
.logo-mark.beat circle:nth-child(n+16) { animation-delay: .18s; }
.logo-mark.beat circle:nth-child(n+21) { animation-delay: .24s; }
@keyframes logoBeat {
  0%, 100% { opacity: 1; transform: scale(1); }
  35% { opacity: .5; transform: scale(1.3); }
}

/* Live arrival: a teammate's edit sweeps in and settles. */
.cell { position: relative; }
.cell.live-arrive::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(100deg, transparent 15%, var(--brand-soft) 50%, transparent 85%);
  border-bottom: 2px solid var(--brand);
  opacity: 0;
  animation: liveSweep .9s var(--ease-out);
}
@keyframes liveSweep {
  0% { opacity: 0; transform: translateX(-25%); }
  30% { opacity: 1; }
  100% { opacity: 0; transform: translateX(25%); }
}

/* ------------------------------------------------- command palette (P5) */
#cmd-palette .palette-scrim { position: fixed; inset: 0; background: var(--scrim); z-index: 120; }
#cmd-palette .palette {
  position: fixed; left: 50%; top: 14vh; transform: translateX(-50%);
  width: min(560px, 92vw); z-index: 121; border-radius: var(--r-4);
  background: var(--surface-1); box-shadow: var(--elev-3); border: 1px solid var(--line-1);
  overflow: hidden; animation: paletteIn var(--dur-micro-3) var(--ease-settle);
}
@keyframes paletteIn { from { opacity: 0; transform: translateX(-50%) translateY(10px) scale(.98); } }
.palette-input-row {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4); border-bottom: 1px solid var(--line-2); color: var(--text-3);
}
.palette-input-row input { flex: 1; border: none; background: none; font-size: var(--fs-md); padding: 4px 0; }
.palette-input-row input:focus { border: none; }
.pr-kbd {
  font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: var(--ls-caps);
  text-transform: uppercase; color: var(--text-3); border: 1px solid var(--line-1);
  border-radius: var(--r-1); padding: 2px 6px; flex: none;
}
.palette-list { max-height: 46vh; overflow-y: auto; padding: var(--sp-2); }
.palette-row {
  display: flex; align-items: center; gap: var(--sp-3); width: 100%; text-align: left;
  background: none; border: none; cursor: pointer; padding: 9px 10px;
  border-radius: var(--r-2); color: var(--text-1); font-size: var(--fs-sm);
}
.palette-row .ic { color: var(--text-3); flex: none; }
.palette-row .pr-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-row .pr-hint {
  margin-left: auto; font-family: var(--font-mono); font-size: var(--fs-2xs);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3); flex: none;
}
.palette-row:hover, .palette-row.sel { background: var(--brand-soft); }
.palette-row.sel .ic { color: var(--brand); }
.pr-none { padding: var(--sp-4); color: var(--text-3); text-align: center; }



/* -------------------------------------------------- audit mode (P7) */
#audit-view {
  position: fixed; inset: 0; z-index: 200; overflow-y: auto;
  background: var(--surface-0);
}
.audit-bar {
  position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4); background: var(--surface-1);
  border-bottom: 1px solid var(--line-1); box-shadow: var(--elev-1);
}
.audit-bar-title { display: inline-flex; align-items: center; gap: 8px; font-weight: 700; margin-right: auto; }
.audit-doc { max-width: 900px; margin: 0 auto; padding: var(--sp-8) var(--sp-5) var(--sp-16); }
.audit-head { border-bottom: 2px solid var(--text-1); padding-bottom: var(--sp-4); margin-bottom: var(--sp-6); }
.audit-brand .logo-word { display: none; }
.audit-brand .logo-mark { width: 30px; height: auto; }
.audit-doc h1 {
  font-family: var(--font-display); font-weight: var(--fw-display); font-size: var(--fs-4xl);
  letter-spacing: var(--ls-display); margin: var(--sp-2) 0 var(--sp-4);
}
.audit-meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--sp-2) var(--sp-4); }
.audit-meta span {
  display: block; font-family: var(--font-mono); font-size: var(--fs-2xs);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3);
}
.audit-doc section { margin-bottom: var(--sp-8); }
.audit-doc h2 {
  display: flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-lg);
  border-bottom: 1px solid var(--line-1); padding-bottom: var(--sp-2); margin-bottom: var(--sp-3);
}
.audit-no {
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--brand);
  border: 1px solid var(--line-1); border-radius: var(--r-1); padding: 1px 7px;
}
.audit-table { width: 100%; border-collapse: collapse; font-size: var(--fs-xs); }
.audit-table th {
  text-align: left; font-family: var(--font-mono); font-size: var(--fs-2xs);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3);
  font-weight: var(--fw-medium); padding: 6px 8px; border-bottom: 1px solid var(--line-1);
}
.audit-table td { padding: 6px 8px; border-bottom: 1px solid var(--line-2); vertical-align: top; }
.audit-table .ad-item { font-weight: 600; }
.audit-table .ad-mono { font-family: var(--font-mono); font-size: var(--fs-2xs); white-space: nowrap; }
.audit-foot {
  border-top: 1px solid var(--line-1); padding-top: var(--sp-3);
  font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-3);
}

@media print {
  body.audit-open .app, body.audit-open #toasts, body.audit-open .modal-overlay,
  body.audit-open #cmd-palette, .no-print { display: none !important; }
  body.audit-open { overflow: visible !important; }
  #audit-view {
    position: static !important; overflow: visible !important;
    /* print is always Studio-on-white regardless of screen theme */
    --surface-0: #ffffff; --surface-1: #ffffff; --surface-2: #f2f2f2;
    --text-1: #141a2b; --text-2: #333c50; --text-3: #5a6478;
    --line-1: #8a8a8a; --line-2: #cccccc; --brand: #0f62b8;
  }
  .audit-doc { max-width: none; padding: 0; }
  .audit-doc section { break-inside: auto; }
  .audit-table tr { break-inside: avoid; }
}

/* ------------------------------------------- attachments preview (P13) */
.attach-row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) 58px auto auto;
  grid-template-areas: "badge name ver dl del" "badge meta meta meta meta";
  column-gap: 8px; row-gap: 2px; align-items: center;
  padding: 8px 6px; border-radius: var(--r-2);
}
.attach-row:hover { background: var(--surface-2); }

.attach-row .attach-name { grid-area: name; }
.attach-row .attach-ver { grid-area: ver; width: 100%; }
.attach-row .attach-ver-chip { grid-area: ver; justify-self: start; }
.attach-row > a.icon-btn { grid-area: dl; }
.attach-row > button.icon-btn { grid-area: del; }
.attach-row .muted.sm { grid-area: meta; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: var(--fs-2xs); }

.modal.wide { max-width: min(920px, 92vw); width: 100%; }
.att-modal h3 { display: flex; align-items: center; gap: 10px; margin-bottom: var(--sp-3); }
.att-preview { display: block; max-width: 100%; max-height: 64vh; margin: 0 auto; border-radius: var(--r-2); }
.att-preview.pdf { width: 100%; height: 64vh; border: 1px solid var(--line-2); }
.att-nopreview { text-align: center; padding: var(--sp-10) 0; color: var(--text-3); }
.attach-name { border: none; background: none; cursor: pointer; color: var(--brand); font-weight: var(--fw-semibold); font-size: var(--fs-sm); padding: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attach-name:hover { text-decoration: underline; }
.attach-ver {
  width: 64px; flex: none; font-family: var(--font-mono); font-size: var(--fs-2xs);
  padding: 3px 7px; border-radius: var(--r-1); text-align: center;
}
.attach-ver-chip {
  font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-2);
  background: var(--surface-2); border-radius: var(--r-1); padding: 2px 7px; flex: none;
}
/* row-reorder drop hint */
.item-row.drop-above td { border-top: 2px solid var(--brand) !important; }
/* column reorder */
th[data-colid] { cursor: grab; }
th[data-colid].col-drop { box-shadow: inset 3px 0 0 var(--brand); }
.cell-files.has { display: inline-flex; align-items: center; gap: 4px; }


/* person filter clear */
.pf-clear { margin-left: -6px; }
/* item card: two columns, the right side is files + a living viewer */
.modal.wide { max-width: min(980px, 94vw); }
.ic-viewer {
  border: 1px solid var(--line-2); border-radius: var(--r-3); background: var(--surface-2);
  min-height: 0; flex: 1; overflow: hidden; display: flex; flex-direction: column;
}
.ic-viewer-empty { margin: auto; text-align: center; color: var(--text-3); }
.ic-viewer-head {
  display: flex; align-items: center; gap: 8px; padding: 8px 10px;
  border-bottom: 1px solid var(--line-2); background: var(--surface-1);
}
.ic-viewer-head b { flex: 1; min-width: 0; font-size: var(--fs-xs); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ic-viewer .att-preview { max-height: none; flex: 1; object-fit: contain; min-height: 0; padding: var(--sp-2); }
.ic-viewer .att-preview.pdf { flex: 1; height: auto; min-height: 0; border: none; padding: 0; }
.ic-viewer .att-nopreview { margin: auto; }
.attach-name.viewing { text-decoration: underline; }

/* rail logo: same life as the marketing mark (hover wave; heartbeat via .beat) */
.rail-logo:hover .logo-mark circle { animation: logoWave .65s ease both; }
.rail-logo:hover .logo-mark circle:nth-child(n+6) { animation-delay: .07s; }
.rail-logo:hover .logo-mark circle:nth-child(n+11) { animation-delay: .14s; }
.rail-logo:hover .logo-mark circle:nth-child(n+16) { animation-delay: .21s; }
.rail-logo:hover .logo-mark circle:nth-child(n+21) { animation-delay: .28s; }

/* -------------------------------------------------- snapshots (P7) */
.snap-list { display: flex; flex-direction: column; gap: var(--sp-2); max-height: 44vh; overflow-y: auto; margin: var(--sp-3) 0; }
.snap-row {
  display: flex; align-items: center; gap: var(--sp-2);
  border: 1px solid var(--line-2); border-radius: var(--r-2); padding: var(--sp-2) var(--sp-3);
  background: var(--surface-1);
}
.snap-info { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.snap-info b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.snap-meta { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-3); }
#snap-name { flex: 1; min-width: 0; }

/* ------------------------------------------------ density modes (P7) */
[data-density="compact"] .board-table td { height: 30px; }
[data-density="compact"] .board-table th { padding-top: 4px; padding-bottom: 4px; }
[data-density="compact"] .cell.inline { min-height: 24px; }
[data-density="compact"] .status-pill { padding-top: 2px; padding-bottom: 2px; }
[data-density="compact"] .kanban-card { padding: 8px 10px; }
[data-density="compact"] .kanban-list { gap: 5px; }
[data-density="compact"] .side-link, [data-density="compact"] .side-board { padding-top: 4px; padding-bottom: 4px; }
[data-density="compact"] .board-head { padding-top: var(--sp-2); padding-bottom: var(--sp-1); }
[data-density="compact"] .board-body { padding-top: 10px; }
[data-density="compact"] .summary-row td { height: 22px; }


/* ------------------------------------------------------------ phase-27 polish */
/* cells: content centred in every column, same rhythm on every row */
td.cell { text-align: center; }
td.cell > * { vertical-align: middle; }
td.cell .cell-people { display: inline-flex; justify-content: center; }
td.cell .status-pill { margin: 0 auto; }


/* toolbar: search on the left with the primary action, tools with the header actions */
.board-search {
  display: inline-flex; align-items: center; gap: 6px; padding: 0 8px 0 10px; height: 34px; margin: 0; font-weight: 400;
  border-radius: var(--r-2); border: 1px solid var(--border-soft); background: var(--surface-1); color: var(--text-3);
  transition: box-shadow var(--dur-micro-1) var(--ease-out), border-color var(--dur-micro-1) var(--ease-out);
}
.board-search:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent); }
.board-search .board-filter { border: none; background: transparent; padding: 0; margin: 0; width: 200px; box-shadow: none; outline: none; height: 100%; line-height: 1; align-self: stretch; }
.board-search > .ic { flex: none; }
.board-tools { margin-left: 0; align-items: center; }
.board-tools .btn { height: 34px; white-space: nowrap; }
.load-more { display: block; margin: 16px auto 0; }
.board-switch-row { justify-content: space-between; }


/* popovers and modals: solid, lifted. ONE rule owns box-shadow here; the theme
   difference is --elev-pop, never a [data-theme] selector, or it out-ranks the
   glass rim further down the file and the menus lose their edge in dark. */
.popover, .modal { box-shadow: var(--elev-pop); }


/* ============================================================ phase-28 polish
   Register table: every cell honours its column width (--cw is set per cell
   by the renderer), status chips share one width per column, long text clips
   with a hover peek instead of stretching the table, typed file glyphs carry
   per-kind counts, and the sticky name column casts a shade over content that
   slides under it. */
/* text: the column is exactly its width (fixed layout); anything longer
   clips with a peek — widen the column from its menu when ids need room */
.board-table td.cell { width: var(--cw); }
.board-table .cell-text {
  display: inline-block; max-width: calc(var(--cw, 200px) - 12px); padding: 0 6px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; color: var(--text-1); vertical-align: middle;
}
.board-table .cell-text:empty { min-height: 1em; }
.cell.inline .cell-text { display: inline-block; width: auto; max-width: 100%; white-space: normal; line-height: 1.45; padding: 4px 2px; text-align: left; }

/* status / priority: one width per column, dot + label centred */
.board-table .status-pill {
  width: calc(var(--cw, 140px) - 20px); min-width: 0; justify-content: center; box-sizing: border-box;
  padding: 5px 8px;
}
.board-table .status-pill.unset { background: transparent; border: 1px dashed var(--line-1); padding: 4px 8px; }
.board-table .status-pill.unset::before { opacity: .35; }
.board-table .status-pill > span, .board-table .status-pill { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* files: typed document glyphs, count under each kind */
.cell-files.has { display: inline-flex; align-items: flex-start; justify-content: center; gap: 9px; padding: 3px 4px; }


.fglyph { --fg-fill: #fff; overflow: visible; }
.fglyph .fg-band { fill: var(--fb); }
.fglyph .fg-body { fill: var(--fg-fill); stroke: var(--fb); }
.fglyph .fg-fold { fill: color-mix(in srgb, var(--fb) 22%, var(--surface-1)); stroke: var(--fb); }
.fglyph .fg-line { stroke: color-mix(in srgb, var(--fb) 45%, transparent); }
.fglyph text { fill: #fff; font-family: var(--font-body); font-weight: 800; letter-spacing: .02em; }
[data-theme="dark"] .fglyph text { fill: #fff; }
[data-theme="dark"] .fglyph { --fg-fill: #f4f6fa; }
.fglyph .fg-fold { fill: #e9ecf2; }
.fglyph .fg-line { stroke: rgba(60, 70, 90, .28); }

[data-density="compact"] .fglyph { width: 16px; height: 19px; }
[data-density="compact"] .cell-files.has { padding: 0 4px; }
.attach-row .fglyph { grid-area: badge; }
.att-nopreview .fglyph { display: block; margin: 0 auto var(--sp-3); }


/* sticky name column: shade that appears as content slides under it */
.board-table th.col-name, .board-table td.col-name { box-shadow: none; }
.board-table th.col-name::after, .board-table td.col-name::after {
  content: ""; position: absolute; top: 0; bottom: 0; right: -18px; width: 18px; pointer-events: none;
  opacity: var(--shade, 0);
  background: linear-gradient(90deg, rgba(20, 26, 43, .07), rgba(20, 26, 43, .025) 45%, rgba(20, 26, 43, 0));
  transition: opacity var(--dur-micro-2) var(--ease-out);
}
[data-theme="dark"] .board-table th.col-name::after, [data-theme="dark"] .board-table td.col-name::after {
  background: linear-gradient(90deg, rgba(0, 0, 0, .22), rgba(0, 0, 0, .07) 50%, rgba(0, 0, 0, 0));
}
.board-table th.col-name, .board-table td.col-name { box-shadow: inset -1px 0 0 var(--line-2); }
.board-scroll.scrolled .board-table th.col-name, .board-scroll.scrolled .board-table td.col-name { box-shadow: inset -1px 0 0 var(--line-1); }
.summary-row td.col-name::after, .summary-row td:first-child::after { display: none; }

/* hover peek for clipped text: the whole note, without widening the table */
.peek {
  position: fixed; z-index: 95; max-width: min(440px, calc(100vw - 16px));
  background: var(--surface-1); color: var(--text-1); border: 1px solid var(--line-1); border-radius: var(--r-3);
  padding: 10px 12px; font-size: 13px; line-height: 1.5; box-shadow: var(--elev-3);
  opacity: 0; transform: translateY(-3px); pointer-events: none;
  transition: opacity var(--dur-micro-2) var(--ease-out), transform var(--dur-micro-2) var(--ease-out);
  white-space: pre-wrap; word-break: break-word;
}
.peek.show { opacity: 1; transform: none; }
.peek .peek-col { display: block; font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3); margin-bottom: 4px; }

/* rows */
.item-row:hover td { background: var(--surface-2); }
.item-row:hover td.col-name { background: var(--surface-2); }
.board-table td.cell:hover { box-shadow: inset 0 0 0 1.5px var(--brand); z-index: 1; }
.cell-empty-people, .cell-files.empty { color: var(--text-3); }
.cell:hover .cell-empty-people, .cell:hover .cell-files.empty { color: var(--brand); }
.add-item-input { height: 40px; }
.add-item-input:focus { background: var(--brand-soft); }
.summary-row td { background: color-mix(in srgb, var(--surface-2) 70%, var(--surface-1)); }
.summary-row td.col-name, .summary-row td:first-child { background: color-mix(in srgb, var(--surface-2) 70%, var(--surface-1)); }
.dist-bar { border-radius: 5px; box-shadow: inset 0 0 0 1px var(--line-2); }
.add-group { margin-top: 2px; }
.board-body { padding-top: 18px; }


/* chrome consistency */
.board-name { font-size: var(--fs-2xl); }
.pill-count { font-family: var(--font-mono); }
#qf-btn.on { background: var(--brand-soft); color: var(--brand); }
.view-tab { font-size: 13px; }
.board-filter::placeholder { color: var(--text-3); }
.icon-btn.star { transition: transform var(--dur-micro-2) var(--ease-spring); }
.icon-btn.star.on:hover { transform: scale(1.12); }

/* phone: the register stays a register, just tighter */
@media (max-width: 700px) {
  .group-head { padding: 4px 6px 4px 2px; gap: 6px; }
  .group-title { font-size: 15px; }
  .group-count i { display: none; }
  .board-table td { height: 44px; }
  .board-table th.col-name { min-width: 0; width: 236px; }
  .board-table th.col-name, .board-table td.col-name { position: static; box-shadow: none; }
  .board-table th.col-name::after, .board-table td.col-name::after { display: none; }
  .board-table td.col-name .cn { width: 220px; gap: 0; }
  .board-table .chat-btn { width: 34px; }
  .board-table .row-menu { width: 22px; }
  .board-table td.col-name .item-name { font-size: 14px; }
  .row-menu { opacity: .55; }


  .board-tools .btn.primary { width: 40px; justify-content: center; padding: 0; flex: none; }
  .peek { max-width: calc(100vw - 16px); }
}


.ch-window {
  width: min(560px, calc(100vw - 24px)); height: min(600px, calc(100vh - 100px));
  display: grid; grid-template-columns: 200px minmax(0, 1fr); overflow: hidden;
  background: var(--surface-1); border: 1px solid var(--line-1); border-radius: var(--r-4); box-shadow: var(--elev-3);
  animation: paletteIn var(--dur-micro-3) var(--ease-settle);
}
.ch-list { border-right: 1px solid var(--line-2); background: var(--surface-0); display: flex; flex-direction: column; min-height: 0; overflow-y: auto; padding: 8px; }
.ch-list-head { display: flex; align-items: baseline; justify-content: space-between; padding: 8px 8px 10px; }
.ch-list-head b { font-family: var(--font-display); font-size: 16px; letter-spacing: -.01em; }
.ch-list-head span { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--text-3); letter-spacing: .04em; text-transform: uppercase; }
.ch-list-label { font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3); padding: 12px 8px 4px; }
.ch-row {
  display: flex; align-items: center; gap: 9px; width: 100%; padding: 7px 8px; border: none; background: none;
  border-radius: var(--r-2); cursor: pointer; text-align: left; color: var(--text-1); min-width: 0;
}
.ch-row:hover { background: var(--hover-ground); }
.ch-row.sel { background: var(--surface-1); box-shadow: var(--elev-1); }
.ch-row .avatar { position: relative; flex: none; }
.ch-row .avatar::after {
  content: ""; position: absolute; right: -1px; bottom: -1px; width: 9px; height: 9px; border-radius: 50%;
  background: var(--text-3); border: 2px solid var(--surface-0);
}
.ch-row.sel .avatar::after { border-color: var(--surface-1); }
.ch-row.on .avatar::after { background: var(--ok); }
.ch-row.off .avatar { filter: saturate(.4); opacity: .8; }
.ch-room-ic {
  width: 30px; height: 30px; border-radius: var(--r-2); flex: none; display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand-soft); color: var(--brand);
}
.ch-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.ch-text b { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-text span { font-size: 11.5px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-unread { flex: none; min-width: 18px; height: 18px; padding: 0 5px; border-radius: var(--r-round); background: var(--brand); color: var(--brand-fg); font-family: var(--font-mono); font-size: 10.5px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; }
.ch-time { flex: none; font-family: var(--font-mono); font-size: 10px; color: var(--text-3); }
.ch-thread { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.ch-thread-head { display: flex; align-items: center; gap: 8px; padding: 10px 10px 10px 12px; border-bottom: 1px solid var(--line-2); }
.ch-thread-title { display: flex; flex-direction: column; min-width: 0; flex: 1; line-height: 1.2; }
.ch-thread-title b { font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-thread-title span { font-size: 11.5px; color: var(--text-3); }
.ch-back { display: none; }
.ch-scroll { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 14px 6px; display: flex; flex-direction: column; }
.ch-empty { margin: auto; text-align: center; color: var(--text-3); font-size: 13px; }
.ch-empty p { margin: 8px 0 0; }
.ch-day { display: flex; align-items: center; gap: 10px; margin: 10px 0 8px; color: var(--text-3); font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: .04em; text-transform: uppercase; }
.ch-day::before, .ch-day::after { content: ""; flex: 1; height: 1px; background: var(--line-2); }
.ch-msg { display: flex; gap: 10px; padding: 3px 6px; margin: 2px -6px 0; border-radius: var(--r-2); align-items: flex-start; }
.ch-msg:hover { background: var(--hover-ground); }
.ch-msg.cont { margin-top: 0; }
.ch-msg .avatar { flex: none; margin-top: 2px; }
.ch-gutter { width: 30px; flex: none; font-family: var(--font-mono); font-size: 9.5px; color: transparent; text-align: right; padding-top: 4px; }
.ch-msg.cont:hover .ch-gutter { color: var(--text-3); }
.ch-body { min-width: 0; flex: 1; }
.ch-meta { display: flex; align-items: baseline; gap: 8px; margin-bottom: 1px; }
.ch-meta b { font-size: 13px; }
.ch-msg.mine .ch-meta b { color: var(--brand); }
.ch-meta span { font-family: var(--font-mono); font-size: 10px; color: var(--text-3); }
.ch-line { font-size: 13.5px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; }
.ch-compose { display: flex; align-items: flex-end; gap: 8px; padding: 10px 12px 12px; border-top: 1px solid var(--line-2); background: var(--surface-1); }
.ch-compose textarea { flex: 1; resize: none; min-height: 38px; max-height: 120px; padding: 9px 12px; border-radius: var(--r-3); background: var(--surface-2); border-color: transparent; line-height: 1.4; font-size: 13.5px; }
.ch-compose textarea:focus { border-color: var(--brand); background: var(--surface-1); }
.ch-send { width: 38px; height: 38px; padding: 0; justify-content: center; border-radius: var(--r-3); flex: none; }
.ch-send .ic { opacity: 1; }
@media (max-width: 700px) {
  .ch-window { grid-template-columns: 1fr; width: calc(100vw - 24px); height: min(560px, calc(100vh - 90px)); }
  .ch-window.view-list .ch-thread { display: none; }
  .ch-window.view-thread .ch-list { display: none; }
  .ch-back { display: inline-flex; }
  .ch-list { border-right: none; }
}


/* column header: sort caret on hover */
.board-table th { position: relative; }
.th-in { display: flex; align-items: center; justify-content: center; gap: 0; min-width: 0; padding: 0 2px; }
.th-in .col-head { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 3px 6px; }
/* the sort control takes no room until the header is hovered or sorted, so a
   narrow title keeps its full width and the two never share a pixel */
.th-sort {
  flex: none; width: 0; height: 20px; margin-left: 0; border: none; border-radius: var(--r-1); background: transparent; color: var(--text-3);
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0; overflow: hidden;
  transition: width var(--dur-micro-2) var(--ease-out), margin var(--dur-micro-2) var(--ease-out), opacity var(--dur-micro-1) var(--ease-out), background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.board-table th:hover .th-sort, .board-table th:focus-within .th-sort, .board-table th.sorted .th-sort { width: 20px; margin-left: 2px; }
.board-table th.sorted .th-sort { background: var(--brand-soft); }
.board-table th:hover .th-sort, .board-table th:focus-within .th-sort, .board-table th.sorted .th-sort { opacity: 1; }
.th-sort:hover { background: var(--hover-ground); color: var(--brand); }
.board-table th.sorted .th-sort { color: var(--brand); }

/* hover cards: person and files */
.peek.peek-card { padding: 12px 14px; max-width: min(380px, calc(100vw - 16px)); }
.peek-person { display: flex; align-items: center; gap: 12px; }
.pp-text { display: flex; flex-direction: column; min-width: 0; gap: 1px; }
.pp-text b { font-size: 14px; }
.pp-text span { font-size: 12px; color: var(--text-3); }
.pp-text .pp-mail { font-family: var(--font-mono); font-size: 11px; color: var(--text-2); }
.pp-state { margin-left: auto; align-self: flex-start; display: inline-flex; align-items: center; gap: 5px; font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: .04em; text-transform: uppercase; color: var(--text-3); flex: none; }
.pp-state i { width: 7px; height: 7px; border-radius: 50%; background: var(--text-3); }
.pp-state.on { color: var(--ok); }
.pp-state.on i { background: var(--ok); box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 22%, transparent); }
.peek-files { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; }
.pf-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "g n v" "g m m"; column-gap: 8px; align-items: center; padding: 4px 0; }
.pf-row .fglyph { grid-area: g; }
.pf-name { grid-area: n; font-weight: 600; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pf-row .attach-ver-chip { grid-area: v; }
.pf-meta { grid-area: m; font-size: 11px; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pf-more { font-size: 11.5px; color: var(--text-3); padding-top: 4px; }

/* files view */
.fv-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.fv-search .board-filter { width: 220px; }
.fv-kinds { display: flex; gap: 6px; flex-wrap: wrap; }
.fv-mode .view-tab { padding: 5px 8px; }
.fv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 10px; }
.fv-list { display: flex; flex-direction: column; gap: 4px; }
.fv-card {
  position: relative; display: flex; gap: 12px; align-items: flex-start; text-align: left; width: 100%;
  padding: 14px 14px 12px; border: 1px solid var(--line-2); border-radius: var(--r-3); background: var(--surface-1);
  color: var(--text-1); cursor: pointer; box-shadow: var(--elev-1);
  transition: transform var(--dur-micro-2) var(--ease-out), box-shadow var(--dur-micro-2) var(--ease-out), border-color var(--dur-micro-2) var(--ease-out);
}
.fv-card:hover { transform: translateY(-2px); box-shadow: var(--elev-2); border-color: var(--line-1); }
.fv-card .fglyph { flex: none; margin-top: 1px; }
.fv-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.fv-name { font-weight: 600; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fv-doc { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fv-doc .dot-mark { flex: none; }
.fv-meta { display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.fv-dl { position: absolute; right: 8px; top: 8px; opacity: 0; color: var(--text-3); }
.fv-card:hover .fv-dl { opacity: 1; }
.fv-list .fv-card { padding: 8px 12px; align-items: center; box-shadow: none; border-radius: var(--r-2); }
.fv-list .fv-card:hover { transform: none; }
.fv-list .fv-text { flex-direction: row; align-items: center; gap: 14px; }
.fv-list .fv-name { flex: 1 1 40%; }
.fv-list .fv-doc { flex: 1 1 30%; }
.fv-list .fv-meta { flex: 1 1 30%; }
.fv-list .fv-dl { position: static; opacity: .6; }
@media (max-width: 700px) {
  .fv-list .fv-text { flex-direction: column; align-items: flex-start; gap: 2px; }
  .fv-search .board-filter { width: 140px; }
}

/* chat: take back a line */
.ch-del { position: absolute; right: 6px; top: 4px; opacity: 0; color: var(--text-3); }
.ch-msg { position: relative; }
.ch-msg:hover .ch-del { opacity: .8; }
.ch-del:hover { color: var(--danger); opacity: 1; }


/* ============================================================ phase-30 life
   Tooltips that explain the chrome, a rainbow trail while a page loads, the
   logo's slow wave, breathing presence, a dock that tucks itself away, real
   document thumbnails, and a calmer quick-filter card. */

/* tooltips: dark on paper, lifted on the night ground; slide in from the anchor */
#tip {
  position: fixed; z-index: 130; pointer-events: none; max-width: 280px;
  background: var(--text-1); color: var(--surface-1); font: 500 12px/1.35 var(--font-body);
  padding: 6px 10px; border-radius: 7px; box-shadow: var(--elev-2); width: max-content; text-align: center;
  opacity: 0; transform: translateY(4px);
  transition: opacity var(--dur-micro-2) var(--ease-out), transform var(--dur-micro-2) var(--ease-out);
}
#tip.right { transform: translateX(-4px); }
#tip.show { opacity: 1; transform: none; }
#tip kbd { font-family: var(--font-mono); font-size: 10px; opacity: .7; margin-left: 8px; }
[data-theme="dark"] #tip { background: var(--surface-3); color: var(--text-1); box-shadow: var(--elev-3); }

/* rainbow trail: a 2px line of the mark's five hues runs while a page loads */
#trail {
  position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 140; pointer-events: none;
  background: linear-gradient(90deg, #ff3229, #ffa00a, #ffcc00, #00b842, #0089eb, #00b842, #ffcc00, #ffa00a, #ff3229);
  background-size: 400% 100%; opacity: 0; transition: opacity .45s ease;
  box-shadow: 0 0 12px rgba(0, 137, 235, .4);
}
html.nav-busy #trail { opacity: 1; animation: trailMove 2.6s linear infinite; transition-delay: 0s; }
/* phase-154: out-and-back tile (blue never meets red); -133.333% of a 400% tile
   is exactly one tile, so the loop is seamless at the old drift rate. */
@keyframes trailMove { to { background-position: -133.333% 0; } }

/* a teammate's edit sweeps in wearing the fan's colours, faintly */
.cell.live-arrive::after {
  background: linear-gradient(100deg, transparent 8%, rgba(255, 50, 41, .16) 28%, rgba(255, 204, 0, .16) 44%,
              rgba(0, 184, 66, .16) 60%, rgba(0, 137, 235, .16) 76%, transparent 92%);
}

/* group count: the number catches the light once when it changes */
.group-count.bump b {
  background: linear-gradient(90deg, var(--text-1) 0 34%, #ff3229 42%, #ffa00a 47%, #ffcc00 52%, #00b842 57%, #0089eb 62%, var(--text-1) 70% 100%);
  background-size: 320% 100%; -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: countShine 1.5s ease-out both;
}
@keyframes countShine { from { background-position: 100% 0; } to { background-position: 0% 0; } }

.ch-row.on .avatar::after { animation: breathe 3.4s var(--ease-in-out) infinite; box-shadow: 0 0 0 2px var(--surface-0); }


/* the item-label header: only the word is editable, the cell keeps its shade */
.th-label { display: inline-block; min-width: 40px; outline: none; padding: 2px 6px; border-radius: 4px; }
.th-label:hover, .th-label:focus { background: var(--hover-ground); }

/* document thumbnails: a small page of the real file, the glyph until it arrives */
.file-mini { position: relative; display: inline-block; width: var(--tw, 22px); height: var(--th, 28px); flex: none; }
.file-mini .fm-img {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: top;
  border-radius: 2.5px; background: #fff; opacity: 0;
  box-shadow: 0 0 0 1px var(--line-1), 0 1px 2px rgba(20, 26, 43, .16);
}
.file-mini .fm-img.loaded { opacity: 1; animation: thumbPop .5s var(--ease-spring) both; }
.file-mini .fglyph { position: absolute; inset: 0; width: 100%; height: 100%; transition: opacity var(--dur-micro-2) var(--ease-out); }
.file-mini .fm-img.loaded + .fglyph { opacity: 0; }
.file-mini.nothumb .fglyph { position: absolute; inset: 0; }
[data-theme="dark"] .file-mini .fm-img { box-shadow: 0 0 0 1px rgba(255, 255, 255, .14), 0 1px 3px rgba(0, 0, 0, .5); }
@keyframes thumbPop { from { opacity: 0; transform: scale(.6) translateY(4px); } 60% { opacity: 1; } to { transform: none; } }

.cell-files.has:hover .file-mini .fm-img.loaded { box-shadow: 0 0 0 1px var(--brand), 0 3px 8px rgba(20, 26, 43, .22); }
[data-theme="dark"] .cell-files.has:hover .file-mini .fm-img.loaded { box-shadow: 0 0 0 1px var(--brand), 0 3px 10px rgba(0, 0, 0, .6); }
.attach-row .file-mini { grid-area: badge; }
.fv-card .file-mini { flex: none; }
.pf-row .file-mini { grid-area: g; }

/* quick filters: one calm card — search on top, sections with room to breathe */
.qf-popover { padding: 0; overflow: hidden; }
.qf-pop { width: min(600px, calc(100vw - 32px)); display: flex; flex-direction: column; max-height: 72vh; }
.qf-head { display: flex; align-items: center; gap: 12px; padding: 16px 18px 12px; border-bottom: 1px solid var(--line-2); flex: none; }
.qf-head .qf-t { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.qf-head .qf-t b { font-family: var(--font-display); font-size: 16px; letter-spacing: -.01em; }
.qf-head .qf-t span { font-size: 12px; color: var(--text-3); white-space: nowrap; }
.qf-search { flex: 1; min-width: 120px; margin-left: auto; height: 32px; }
.qf-search .board-filter { width: 100%; font-size: 13px; }
.qf-head .btn { flex: none; }
.qf-body { overflow-y: auto; padding: 6px 18px 14px; }
.qf-section { padding: 12px 0 10px; border-bottom: 1px solid var(--line-2); }
.qf-section:last-child { border-bottom: none; }
.qf-sec-head { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }
.qf-sec-head .qf-ic { width: 22px; height: 22px; border-radius: var(--r-1); display: inline-flex; align-items: center; justify-content: center; color: var(--text-3); flex: none; }
.qf-sec-head b { font-size: 13px; font-weight: 600; }
.qf-sec-head .qf-sel { font-size: 11.5px; color: var(--brand); font-weight: 600; }
.qf-sec-head .qf-x { margin-left: auto; color: var(--text-3); font-size: 11.5px; background: none; border: none; cursor: pointer; padding: 2px 6px; border-radius: 4px; }
.qf-sec-head .qf-x:hover { background: var(--hover-ground); color: var(--text-1); }
.qf-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.qf-chip {
  --lab-ink: color-mix(in srgb, var(--lab, var(--text-3)) 60%, var(--text-1));
  display: inline-flex; align-items: center; gap: 8px; height: 30px; padding: 0 10px 0 9px;
  border: 1px solid transparent; border-radius: var(--r-1); background: var(--surface-2); color: var(--text-1);
  font-size: var(--fs-xs); font-weight: 500; cursor: pointer;
  transition: background var(--dur-micro-1) var(--ease-out), border-color var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.qf-chip:hover { background: var(--surface-3); }
.qf-chip.on { background: var(--brand-soft); border-color: color-mix(in srgb, var(--brand) 55%, transparent); color: var(--brand); font-weight: 600; }
.qf-chip.empty { color: var(--text-3); background: transparent; border-color: var(--line-1); border-style: dashed; }
.qf-chip.empty.on { color: var(--brand); border-style: solid; background: var(--brand-soft); }
.qf-chip b { font-family: var(--font-mono); font-size: 10.5px; font-weight: 500; color: var(--text-3); }
.qf-chip.on b { color: var(--brand); }
.qf-chip .qf-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--lab, var(--text-3)); flex: none; box-shadow: inset 0 0 0 1px var(--lab-ink); }
.qf-chip .avatar { margin-left: -3px; }
.qf-chip .fglyph { margin-left: -2px; }
.qf-chip.more { background: transparent; color: var(--text-2); border-color: var(--line-2); }
.qf-chip.more:hover { border-color: var(--line-strong); }
.qf-none { padding: 24px 0; text-align: center; color: var(--text-3); font-size: 13px; }
@media (max-width: 700px) {
  .qf-head { flex-wrap: wrap; padding: 14px; }
  .qf-search { flex: 1 1 100%; margin-left: 0; }
  .qf-body { padding: 4px 14px 12px; }
}


/* ============================================================ phase-31 polish
   Presence lives in the chrome (rail + Team button) and the chat is a side
   panel; the register's file cell is a small stack of real pages; icons
   answer every click; the board description sits behind an info control. */
@keyframes breathe { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.22); opacity: .78; } }
.pp-state.on i, .ch-row.on .avatar::after, .rail-dot, .team-dot { animation: breathe 3.4s var(--ease-in-out) infinite; box-shadow: 0 0 0 3px color-mix(in srgb, var(--ok) 18%, transparent); }

/* rail: team chat entry with unread count and a live dot */
.rail-dot { position: absolute; right: 7px; bottom: 7px; width: 8px; height: 8px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 2px var(--surface-1); }

/* board header: the Team control carries who is here */
#team-btn { position: relative; gap: 6px; }
#team-btn .team-stack { display: inline-flex; align-items: center; }
#team-btn .team-stack .avatar { margin-left: -6px; box-shadow: 0 0 0 2px var(--surface-0); }
#team-btn .team-stack .avatar:first-child { margin-left: 0; }
.team-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); flex: none; }
.team-dot.none { background: var(--text-3); animation: none; box-shadow: none; }
#team-btn .pill-count.unread { background: var(--brand-fill); color: #fff; }

/* team popover: everyone, who is online, message or filter */
.team-pop { min-width: 300px; padding: 4px; }
.team-pop .tp-head { display: flex; align-items: baseline; justify-content: space-between; padding: 8px 10px 6px; }
.team-pop .tp-head b { font-family: var(--font-display); font-size: 15px; }
.team-pop .tp-head span { font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3); }
.tp-row { display: flex; align-items: center; gap: 10px; padding: 6px 8px; border-radius: var(--r-2); cursor: pointer; width: 100%; border: none; background: none; text-align: left; color: var(--text-1); }
.tp-row:hover { background: var(--hover-ground); }
.tp-row .avatar { position: relative; flex: none; }
.tp-row .avatar::after { content: ""; position: absolute; right: -1px; bottom: -1px; width: 9px; height: 9px; border-radius: 50%; background: var(--text-3); border: 2px solid var(--surface-1); }
.tp-row.on .avatar::after { background: var(--ok); }
.tp-row.off .avatar { filter: saturate(.4); opacity: .8; }
.tp-row .tp-text { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.tp-row .tp-text b { font-size: 13px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tp-row .tp-text span { font-size: 11.5px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tp-row .tp-act { display: inline-flex; gap: 2px; opacity: 0; transition: opacity var(--dur-micro-1) var(--ease-out); }
.tp-row:hover .tp-act, .tp-row:focus-within .tp-act { opacity: 1; }
.tp-row .tp-act .icon-btn.on { color: var(--brand); background: var(--brand-soft); opacity: 1; }
.tp-row .ch-unread { flex: none; }
.tp-room .ch-room-ic { width: 30px; height: 30px; }

/* chat: a side panel, like the assistant */
.chat-panel {
  position: fixed; top: 0; right: 0; bottom: 0; width: 0; z-index: 52; overflow: hidden;
  border-left: 1px solid var(--line-1); box-shadow: -12px 0 40px -20px rgba(0, 0, 0, .35);
  transition: width var(--dur-scene-1) var(--ease-out);
}
.chat-panel.open { width: min(560px, 100vw); }
@media (min-width: 1280px) { body.chat-open .main { margin-right: 560px; } }
.chat-panel .ch-window { width: min(560px, 100vw); height: 100%; border: none; border-radius: 0; box-shadow: none; background: transparent; animation: none; }
.chat-panel .ch-list { background: transparent; }
.chat-panel .ch-compose { background: transparent; }
.ch-unread { background: var(--brand-fill); color: #fff; }
.ch-send { background: var(--brand-fill); }

/* file cell: the newest pages, slightly fanned, no numbers */
.cell-files.has { gap: 0; padding: 4px 6px; align-items: center; position: relative; isolation: isolate; } /* the pages stack among themselves, never over the sticky column */
.cell-files.has .file-mini { margin-left: -20px; transition: transform var(--dur-micro-2) var(--ease-out), margin var(--dur-micro-2) var(--ease-out); }
.cell-files.has .file-mini:first-child { margin-left: 0; z-index: 3; }
.cell-files.has .file-mini:nth-child(2) { transform: translate(2px, -2px); z-index: 2; }
.cell-files.has .file-mini:nth-child(3) { transform: translate(4px, -4px); z-index: 1; }
.cell-files.has:hover .file-mini { transform: none; margin-left: 3px; }
.cell-files.has:hover .file-mini:first-child { margin-left: 0; }
.cell-files.has .file-more { transition: opacity var(--dur-micro-2) var(--ease-out); opacity: .7; }
.cell-files.has:hover .file-more { opacity: 1; }
.cell-files.has .file-more { margin-left: 6px; font-family: var(--font-mono); font-size: 10.5px; color: var(--text-3); }

/* board description: behind an info control, not in the title row */
.board-info { color: var(--text-3); }
.board-info.has { color: var(--text-2); }
.info-pop { max-width: 360px; padding: 4px 6px 2px; }
.info-pop p { margin: 0 0 8px; font-size: 13px; line-height: 1.55; color: var(--text-2); white-space: pre-wrap; }
.info-pop p.empty { color: var(--text-3); font-style: italic; }

/* icons answer the click: a spring, and a quarter turn for a plus */
.ic { transform-origin: 50% 50%; }
@keyframes icSpin { 0% { transform: rotate(-90deg) scale(.7); } 100% { transform: none; } }

@media (max-width: 700px) {
  #team-btn .team-stack { display: none; }
  .chat-panel.open { width: 100vw; }
}
@media (prefers-reduced-motion: reduce) {
  .app .logo-mark circle, .ch-row.on .avatar::after, .pp-state.on i, .rail-dot, .team-dot, #trail, .group-count.bump b, .ic, .load-dots i, .team-stack .avatar { animation: none !important; }
}


/* ============================================================ phase-32 polish
   One card grammar for every popover, icons that move on hover, the file
   stack that rests as one page, the Team control that arrives and pops,
   rainbow row hover, glass tags, bouncing loading dots. */

/* every card: the filter card's grammar (icon plates, calm labels, glass) */
.popover { padding: 8px; border-radius: 12px; }
.menu-item { padding: 8px 10px; border-radius: 8px; gap: 10px; font-size: 13.5px; }
.menu-item > .ic:first-child, .menu-item.theme-btn > .ic, .people-opt > .ic:first-child, .label-edit > .ic:first-child, .palette-row > .ic:first-child, .tp-row .ch-room-ic .ic {
  box-sizing: border-box; width: 24px; height: 24px; padding: 5px; border-radius: 7px; flex: none;
  background: var(--surface-2); color: var(--text-2); transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.menu-item:hover > .ic:first-child, .menu-item.theme-btn:hover > .ic, .palette-row:hover > .ic:first-child, .palette-row.sel > .ic:first-child { background: var(--brand-soft); color: var(--brand); }
.menu-item.danger > .ic:first-child { background: var(--danger-soft); color: var(--danger); }
.menu-item.on > .ic:first-child { background: var(--brand-soft); color: var(--brand); }
.menu-item:disabled > .ic:first-child { background: transparent; }
.menu-label { font: 600 12.5px/1.3 var(--font-body); letter-spacing: 0; text-transform: none; color: var(--text-2); padding: 10px 10px 6px; }
.menu-sep { margin: 6px 6px; }
.menu > .edit-box:first-child, .col-menu > .edit-box:first-child, .cols-menu > .edit-box:first-child { padding: 4px 4px 10px; margin-bottom: 6px; border-bottom: 1px solid var(--line-2); }
.edit-box input, .edit-box select { height: 34px; border-radius: 8px; }
.menu .hint, .edit-box .hint { padding: 4px 2px 0; }
.chip-btn { height: 28px; border-radius: var(--r-round); background: var(--surface-2); border-color: transparent; font-weight: 500; padding: 0 11px; }
.chip-btn:hover { background: var(--surface-3); color: var(--text-1); }
.chip-btn.on { background: var(--brand-soft); border-color: color-mix(in srgb, var(--brand) 55%, transparent); color: var(--brand); font-weight: 600; }
.people-opt { padding: 7px 10px; border-radius: 8px; gap: 10px; }
.notif-row { padding: 9px 10px; border-radius: 8px; }
.me-head { padding: 8px 10px 10px; }
.menu-colors { padding: 6px 10px 8px; }
.color-dot { border-radius: 7px; }
.info-pop .menu-label { padding-top: 6px; }
.team-pop .tp-head { padding: 8px 10px 8px; }


/* icons move when their control is hovered — and they do what they are: the
   bell rings, the bolt jumps, the sun turns.
   ONE DURATION, ONE EASING (phase-61). These eleven rules ran at .48, .5, .55,
   .6 and .7 seconds, and nine of them on --ease-spring, which is why hovering
   anything felt loose: everything was a little slow and everything bounced.
   They now all run at --dur-micro-2 (180ms, "this control changed") on
   --ease-out, which is the token that existed for exactly this. The spring
   stays reserved for "something alive arrived" — a teammate's edit, a dropped
   card, the rail pill, the logo's heartbeat — plus the two icons that are
   celebrations by nature: the star and the bolt. Using the tokens also means
   these finally collapse under prefers-reduced-motion instead of ignoring it
   by being hardcoded seconds. */
.ic { transform-origin: 50% 50%; }
button:hover > .ic, a:hover > .ic, label:hover > .ic, .view-tab:hover > .ic, .rail-btn:hover > .ic, .icon-btn:hover > .ic, .btn:hover > .ic, .qf-sec-head:hover .qf-ic > .ic, .menu-item:hover > .ic:first-child, .theme-btn:hover .ic { animation: icHover var(--dur-micro-2) var(--ease-out); }
/* The one icon that keeps its own duration. icRing is six rotation stops, so at
   the shared 180ms each stop gets about 30ms and the bell reads as a buzz, which
   is an error signal, not a notification. The unified timing is right for the
   single-move hovers; a damped oscillation needs room to damp. */
button:hover > .ic[data-ic="bell"] { animation: icRing 240ms var(--ease-out); }
button:hover > .ic[data-ic="bolt"] { animation: icZap var(--dur-micro-2) var(--ease-spring); }
button:hover > .ic[data-ic="plus"], .view-add:hover > .ic { animation: icSpin var(--dur-micro-2) var(--ease-out); }
.theme-btn:hover .ic, button:hover > .ic[data-ic="sun"], button:hover > .ic[data-ic="moon"] { animation: icTurn var(--dur-micro-2) var(--ease-out); }
button:hover > .ic[data-ic="star"], .icon-btn.star:hover > .ic { animation: icTwinkle var(--dur-micro-2) var(--ease-spring); }
button:hover > .ic[data-ic="spark"], button:hover > .ic[data-ic="sparkle"] { animation: icPulse var(--dur-micro-2) var(--ease-out); }
button:hover > .ic[data-ic="clock"] { animation: icTick var(--dur-micro-2) var(--ease-out); }
button:hover > .ic[data-ic="message"], a:hover > .ic[data-ic="message"] { animation: icBob var(--dur-micro-2) var(--ease-out); }
button:hover > .ic[data-ic="sliders"] { animation: icSlide var(--dur-micro-2) var(--ease-out); }
button:hover > .ic[data-ic="search"], label:hover > .ic[data-ic="search"] { animation: icPeek var(--dur-micro-2) var(--ease-out); }
@keyframes icHover { 0% { transform: none; } 40% { transform: scale(1.18) rotate(-6deg); } 100% { transform: none; } }
@keyframes icRing { 0%, 100% { transform: rotate(0); } 15% { transform: rotate(14deg); } 30% { transform: rotate(-12deg); } 45% { transform: rotate(9deg); } 60% { transform: rotate(-6deg); } 75% { transform: rotate(3deg); } }
@keyframes icZap { 0%, 100% { transform: none; } 40% { transform: translateY(-3px) scale(1.2); } }
@keyframes icTurn { 0% { transform: rotate(0); } 50% { transform: rotate(40deg) scale(1.15); } 100% { transform: rotate(0); } }
@keyframes icTwinkle { 0% { transform: rotate(0) scale(1); } 50% { transform: rotate(72deg) scale(1.25); } 100% { transform: rotate(72deg) scale(1); } }
@keyframes icPulse { 0%, 100% { transform: scale(1); opacity: 1; } 40% { transform: scale(1.3); opacity: .7; } }
@keyframes icTick { 0%, 100% { transform: rotate(0); } 35% { transform: rotate(-18deg); } 70% { transform: rotate(12deg); } }
@keyframes icBob { 0%, 100% { transform: none; } 40% { transform: translateY(-3px) scale(1.12); } }
@keyframes icSlide { 0%, 100% { transform: none; } 40% { transform: translateX(3px) scale(1.1); } }
@keyframes icPeek { 0%, 100% { transform: none; } 40% { transform: translate(-2px, 2px) scale(1.15); } }

/* the Team control arrives with its people and pops on hover */
#team-btn .team-stack .avatar { animation: teamIn .55s var(--ease-spring) both; transition: transform var(--dur-micro-2) var(--ease-spring), margin var(--dur-micro-2) var(--ease-out); }
#team-btn .team-stack .avatar:nth-child(2) { animation-delay: .08s; }
#team-btn .team-stack .avatar:nth-child(3) { animation-delay: .16s; }
@keyframes teamIn { from { opacity: 0; transform: translateX(-10px) scale(.5); } to { opacity: 1; transform: none; } }
#team-btn:hover .team-stack .avatar { transform: translateY(-1px) scale(1.1); margin-left: -2px; }
#team-btn:hover .team-stack .avatar:first-child { margin-left: 0; }
#team-btn .team-dot { transition: transform var(--dur-micro-2) var(--ease-spring); }
#team-btn:hover .team-dot { transform: scale(1.3); }

/* the hovered cell wears a ring in the mark's five hues (a gradient cannot be
   a box-shadow, so it is a masked gradient layer just inside the cell edge);
   rows keep their plain hover */
@property --ring-a { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
.board-table td.cell:hover { box-shadow: none; }
.board-table td.cell:hover::before, .board-table td.col-name:hover::before {
  content: ""; position: absolute; inset: 0 0 -1px -1px; z-index: 1; pointer-events: none; border-radius: 0; padding: 1px;
  background: conic-gradient(from var(--ring-a), var(--logo-1), var(--logo-2), var(--logo-3), var(--logo-4), var(--logo-5), var(--logo-4), var(--logo-3), var(--logo-2), var(--logo-1));
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor; mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask-composite: exclude;
  opacity: .9; animation: ringSpin 2.6s linear infinite;
}
[data-theme="dark"] .board-table td.cell:hover::before, [data-theme="dark"] .board-table td.col-name:hover::before { opacity: 1; }
@keyframes ringSpin { to { --ring-a: 360deg; } }

/* tags: glass chips with a dot in one of the mark's hues; overflow ellipsises, "+N" stays */
.cell-tags { display: inline-flex; gap: 4px; align-items: center; justify-content: center; padding: 0 6px; max-width: 100%; overflow: hidden; }
.tag {
  display: inline-flex; align-items: center; gap: 5px; height: 22px; padding: 0 8px 0 7px; min-width: 0;
  border-radius: 7px; font: 500 11.5px/1 var(--font-body); color: var(--text-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  background: color-mix(in srgb, var(--surface-1) 55%, transparent); box-shadow: inset 0 0 0 1px var(--line-2);
}
.tag::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--tc, var(--brand)); flex: none; }
.tag.more { flex: none; color: var(--text-3); font-family: var(--font-mono); font-size: 10.5px; padding: 0 7px; }
.tag.more::before { display: none; }


/* ============================================================ phase-35 polish
   Drawer header, group-menu header, the labels editor, the automations card. */
.drawer-head { padding: 12px 12px 10px; }
.drawer-search { flex: 1; min-width: 0; height: 36px; border-radius: 10px; margin: 0; }
.drawer-search .board-filter { width: 100%; }
.sidebar { width: 304px; }
@media (max-width: 700px) { .sidebar { width: calc(100vw - 48px); } }

/* a card header for menus: colour dot, title in display type, a mono count */
.menu-head { display: flex; align-items: center; gap: 8px; padding: 6px 10px 10px; margin-bottom: 6px; border-bottom: 1px solid var(--line-2); min-width: 0; }
.menu-head .dot-mark { width: 9px; height: 9px; flex: none; }
.menu-head b { font-family: var(--font-display); font-size: 14px; letter-spacing: -.01em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-head span { margin-left: auto; flex: none; font-family: var(--font-mono); font-size: 11px; color: var(--text-3); }
.group-pop { min-width: 250px; }
.menu-colors { gap: 8px; padding: 4px 10px 10px; }
.color-dot { width: 22px; height: 22px; border-radius: 50%; transition: transform var(--dur-micro-2) var(--ease-spring); }
.color-dot.on { box-shadow: 0 0 0 2px var(--surface-1), 0 0 0 4px var(--text-1); }
[data-theme="dark"] .color-dot.on { box-shadow: 0 0 0 2px var(--surface-1), 0 0 0 4px var(--text-1); }

/* modal header block */
.modal-head { margin-bottom: 14px; }
.modal-head h3 { margin-bottom: 4px; display: flex; align-items: center; gap: 8px; }
.modal-head p { margin: 0; line-height: 1.5; }

/* labels editor: handle, swatch, name, remove — rows that can be dragged */
.label-rows { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 12px; }
.label-row {
  display: flex; align-items: center; gap: 10px; margin: 0; padding: 6px 8px 6px 4px;
  border-radius: 10px; background: var(--surface-2); border: 1px solid transparent;
  transition: box-shadow var(--dur-micro-2) var(--ease-out), background var(--dur-micro-2) var(--ease-out);
}
.label-row.dragging { background: var(--surface-1); border-color: var(--line-1); box-shadow: var(--elev-2); position: relative; z-index: 2; }
.lab-handle { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 28px; color: var(--text-3); cursor: grab; border-radius: 6px; touch-action: none; flex: none; }
.lab-handle:hover { color: var(--text-1); background: var(--hover-ground); }
.label-row.dragging .lab-handle { cursor: grabbing; }
.lab-swatch { position: relative; width: 30px; height: 30px; border-radius: 50%; flex: none; background: var(--lab); cursor: pointer; margin: 0; padding: 0; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12), 0 0 0 2px var(--surface-1); }
.lab-swatch input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; padding: 0; border: none; }
.label-row .lab-text { flex: 1; min-width: 0; height: 34px; border-radius: 8px; background: var(--surface-1); border-color: transparent; }
.label-row .lab-text:focus { border-color: var(--brand); }
.lab-remove { flex: none; color: var(--text-3); }
.lab-remove:hover { color: var(--danger); }

/* automations */
.auto-list { gap: 6px; margin: 4px 0 18px; }
.auto-row { background: var(--surface-1); border: 1px solid var(--line-2); border-radius: 10px; padding: 10px 12px; gap: 12px; }
.auto-row.off { opacity: .6; }
.auto-ic { box-sizing: border-box; width: 28px; height: 28px; padding: 6px; border-radius: 8px; flex: none; display: inline-flex; background: var(--brand-soft); color: var(--brand); }
.auto-runs { font-family: var(--font-mono); font-size: 11px; color: var(--text-3); white-space: nowrap; }
.auto-empty { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 22px 0; color: var(--text-3); font-size: 13px; }
.auto-empty p { margin: 0; }
.auto-builder { background: var(--surface-2); border-radius: 12px; padding: 16px 16px 14px; margin-top: 0; }
.auto-builder h4 { display: flex; align-items: center; gap: 6px; font-family: var(--font-body); font-size: 13px; font-weight: 600; color: var(--text-2); margin: 0 0 12px; letter-spacing: 0; }
.auto-line { font-weight: 500; gap: 8px; margin-bottom: 10px; }
.auto-kw { font-family: var(--font-mono); font-size: 11px; letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3); width: 42px; flex: none; }
.auto-line select, .auto-line input { height: 34px; border-radius: 8px; background: var(--surface-1); border-color: transparent; padding: 0 10px; }
.auto-line select:focus, .auto-line input:focus { border-color: var(--brand); }
.auto-builder .btn.primary { margin-top: 4px; }
.switch span { background: var(--line-strong); }
.switch input:checked + span { background: var(--ok); }


/* ============================================================ phase-36 polish
   Dashboard widgets (drag by the header, a dashed slot, cards glide),
   the collapsed group band that answers hover, tags that fan out. */
.dash { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 14px; align-items: stretch; }
.dw {
  grid-column: span 2; min-height: 150px; display: flex; flex-direction: column; min-width: 0;
  background: var(--surface-1); border: 1px solid var(--line-2); border-radius: 14px; box-shadow: var(--elev-1);
  contain: content; /* phase-164: the fadeUp arrival is gone (the board holds still); the hover lift is free */
  transition: transform var(--dur-micro-2) var(--ease-out), box-shadow var(--dur-micro-2) var(--ease-out), border-color var(--dur-micro-2) var(--ease-out);
}
.dw:hover { transform: translateY(-2px); box-shadow: var(--elev-2); border-color: var(--line-1); }
body.dragging-widget .dw { transition: none; transform: none; }
.dw-m { grid-column: span 3; }
.dw-l { grid-column: span 6; }
.dw-head { display: flex; align-items: center; gap: 8px; padding: 12px 10px 4px 12px; cursor: grab; user-select: none; }
.dw-head h4 { flex: 1; margin: 0; font: 600 13px/1.3 var(--font-body); color: var(--text-1); letter-spacing: 0; }
.dw-grip { display: inline-flex; color: var(--text-3); opacity: .45; transition: opacity var(--dur-micro-1) var(--ease-out); }
.dw:hover .dw-grip { opacity: 1; }
.dw-menu { opacity: 0; color: var(--text-3); transition: opacity var(--dur-micro-1) var(--ease-out); }
.dw:hover .dw-menu, .dw-menu:focus-visible { opacity: 1; }
.dw-body { flex: 1; padding: 6px 16px 16px; min-width: 0; display: flex; flex-direction: column; }
.dw-body > .mini-list { margin-top: auto; }
.dw-hint, .dw-note { margin: 4px 0 0; font-size: 12.5px; color: var(--text-3); line-height: 1.5; }
.dw-note { margin-top: 8px; }
/* lifted card and the slot it left behind (the same dashed language as an unset status) */
.dw-lift { z-index: 120; box-shadow: var(--elev-3); transform: scale(1.02) rotate(.4deg); cursor: grabbing; opacity: .97; animation: none; pointer-events: none; }
.dw-ghost { border: 1.5px dashed var(--line-1); background: color-mix(in srgb, var(--surface-2) 45%, transparent); box-shadow: none; animation: none; }
body.dragging-widget { cursor: grabbing; user-select: none; }
body.dragging-widget .dw-head { cursor: grabbing; }
.dw-add {
  align-items: center; justify-content: center; flex-direction: row; gap: 8px; cursor: pointer;
  border: 1.5px dashed var(--line-1); background: transparent; box-shadow: none; color: var(--text-3); font: 500 13px var(--font-body);
  transition: color var(--dur-micro-1) var(--ease-out), border-color var(--dur-micro-1) var(--ease-out);
}
.dw-add:hover { color: var(--brand); border-color: var(--brand); }
/* KPI tiles */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.kpi { --kc: var(--brand); position: relative; padding: 12px 14px 12px 16px; border-radius: 10px; background: var(--surface-2); min-width: 0; transition: background var(--dur-micro-2) var(--ease-out); }
.kpi::before { content: ""; position: absolute; left: 0; top: 10px; bottom: 10px; width: 3px; border-radius: 2px; background: var(--kc); transition: width var(--dur-micro-2) var(--ease-out), top var(--dur-micro-2) var(--ease-out), bottom var(--dur-micro-2) var(--ease-out); }
.kpi:hover { background: var(--surface-3); }
.kpi:hover::before { width: 4px; top: 6px; bottom: 6px; }
.kpi.ok { --kc: var(--ok); } .kpi.warn { --kc: var(--warn); } .kpi.bad { --kc: var(--danger); }
.kpi-n { display: block; font: 700 26px/1.1 var(--font-display); letter-spacing: -.02em; font-variant-numeric: tabular-nums; color: var(--text-1); }
.kpi.bad .kpi-n { color: var(--danger); }
.kpi-l { display: block; font-size: 12.5px; font-weight: 500; color: var(--text-2); margin-top: 2px; }
.kpi-s { display: block; font-family: var(--font-mono); font-size: 11px; color: var(--text-3); margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kpi-single { --kc: var(--brand); margin-bottom: 8px; }
.kpi-single.ok { --kc: var(--ok); } .kpi-single.warn { --kc: var(--warn); }
.kpi-single .kpi-n { color: var(--kc); }
/* bars, lists, stats used by several widgets */
.cbar { height: 8px; border-radius: 4px; background: var(--surface-2); overflow: hidden; margin: 6px 0 10px; }
.cbar i { display: block; height: 100%; background: var(--ok); border-radius: 4px; transition: width .5s var(--ease-out); }
.cbar-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; font-size: 12.5px; font-weight: 600; margin-top: 4px; }
.sbar { display: flex; height: 10px; border-radius: 5px; overflow: hidden; margin: 6px 0 10px; background: var(--surface-2); }
.sbar i { display: block; min-width: 0; }
.mini-list { display: flex; flex-direction: column; gap: 1px; }
.mini-item { gap: 9px; padding: 6px 6px; border-radius: 8px; }
.mini-item .dot-mark { width: 8px; height: 8px; flex: none; }
.mini-item .dot-mark.bad { color: var(--danger); } .mini-item .dot-mark.warn { color: var(--warn); }
.mini-item .mi-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mini-item .mi-meta { flex: none; font-family: var(--font-mono); font-size: 11px; color: var(--text-3); }
.stat { border-radius: 10px; padding: 12px 10px; }
.stat b { font-family: var(--font-display); font-size: 24px; letter-spacing: -.02em; }
.stat.warn b { color: var(--warn); }
.wl-row { padding: 4px 0; gap: 10px; }
.wl-row b { min-width: 28px; text-align: right; font-variant-numeric: tabular-nums; }
.wl-name { width: auto; flex: 0 0 40%; }
.wl-fill { transition: width .6s var(--ease-out); }
.legend-row b { min-width: 30px; text-align: right; font-variant-numeric: tabular-nums; }
.legend-row .muted { min-width: 34px; text-align: right; font-variant-numeric: tabular-nums; }
.stat { flex: 1; }
.stat-pair { gap: 10px; }
.mini-item:hover { background: var(--hover-ground); }


.legend-row { font-size: 12.5px; padding: 2px 0; }
@media (max-width: 1100px) { .dw, .dw-m { grid-column: span 3; } .dw-l { grid-column: span 6; } }
/* wide and ultrawide: twelve columns; small cards a quarter, then a sixth */
@media (min-width: 1500px) { .dash { grid-template-columns: repeat(12, minmax(0, 1fr)); } .dw { grid-column: span 3; } .dw-m { grid-column: span 4; } .dw-l { grid-column: span 6; } }
@media (min-width: 2200px) { .dw { grid-column: span 2; } .dw-m { grid-column: span 3; } .dw-l { grid-column: span 4; } }
.kpis { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
@media (max-width: 700px) { .dw, .dw-m, .dw-l { grid-column: span 6; } .dash { gap: 10px; } .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* the group line answers hover with its own edge (phase-67-c, at the end of
   this file) and the caret nods */
.group-head { position: relative; overflow: hidden; }
.group-caret { transition: transform var(--dur-micro-2) var(--ease-spring), background var(--dur-micro-1) var(--ease-out); }
.group.collapsed .group-head:hover .group-caret { transform: translateX(3px); }
.group:not(.collapsed) .group-head:hover .group-caret { transform: translateY(2px); }



/* ============================================================ phase-37 perf
   Offscreen groups and cards skip layout and paint; on phones the chrome is
   solid (no per-frame blur), the ambient animations rest, and hover-only
   motion is not compiled at all. */
.group { content-visibility: auto; contain-intrinsic-size: auto 640px; }
.group.collapsed { contain-intrinsic-size: auto 52px; }
.fv-card { contain: content; }
@media (max-width: 700px) {
  .board-head, .rail, .sidebar, .popover, .modal, .peek, .chat-panel { -webkit-backdrop-filter: none; backdrop-filter: none; }
  .board-head, .rail { background: var(--surface-1); }
  .sidebar, .popover, .modal, .peek, .chat-panel, .card, .dw, .fv-card, .tpl-card, .admin-card, .kanban-card, .cal-cell, .tl-scroll { background: var(--surface-1); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .app .logo-mark { filter: none; }
  .app .logo-mark circle { animation: logoIn .38s ease backwards; }
  .rail-dot, .team-dot, .ch-row.on .avatar::after { animation: none; }
  .dw, .group, .files-view, .calendar, .timeline-view { animation: none; }
  .dw:hover { transform: none; box-shadow: var(--elev-1); }
  .item-row { transition: none; }
  .file-mini .fm-img.loaded { animation: none; }
  .board-table td.cell:hover::before { animation: none; }
  .btn:hover { transform: none; }
}
@media (hover: none) {
  .ic { animation: none !important; }
  .cell-files.has .file-mini:nth-child(2), .cell-files.has .file-mini:nth-child(3) { transform: none; margin-left: 3px; }
  .dw-menu, .row-menu, .group-head .group-menu, .th-sort, .ch-del, .tp-row .tp-act { opacity: 1; }
  .th-sort { width: 20px; margin-left: 2px; }
}


/* ============================================================ phase-39
   The fan palette everywhere, a grey scale instead of grey, glass with a
   purpose, the status ring, one tab grammar, a library you can sort by
   revision, sheets on phones. Every rule here is written for both themes:
   colour comes from tokens, never from a literal. */

/* ---- the ground has a little light in it, so glass has something to show;
       cards are a lighter step of the same ground, not a grey box ---- */
:root { --wash: color-mix(in srgb, var(--text-1) 5%, transparent); --wash-2: color-mix(in srgb, var(--text-1) 9%, transparent); }
[data-theme="dark"] { --wash: rgba(255, 255, 255, .055); --wash-2: rgba(255, 255, 255, .1); }
.app { background: var(--ambient), var(--surface-0); }
.card, .dw, .fv-card, .tpl-card, .admin-card, .kanban-card, .snap-row, .auto-row {
  background: var(--glass-card); border: 1px solid var(--line-2);
  box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-1);
}
.card { border-radius: 14px; padding: 18px 20px; margin-bottom: 16px; }
.card-head { gap: 12px; }
.card-head h4 { display: inline-flex; align-items: center; gap: 8px; font: 600 14px/1.3 var(--font-display); letter-spacing: -.01em; }
.card-head h4 > .ic { color: var(--brand); }
.card-foot { margin: 14px 0 0; line-height: 1.5; }
.kpi, .stat, .wl-bar, .cbar, .sbar, .kanban-list, .cal-dow, .cal-cell.empty, .auto-builder { background: var(--wash); }
.kpi:hover { background: var(--wash-2); }
.kanban-count { background: var(--glass-card); }
.tl-scroll, .cal-grid { box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-1); }
.page-pad { max-width: 1180px; }
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 18px; }
.page-head h2 { margin: 0 0 4px; font-size: var(--fs-2xl); }
.page-head p { margin: 0; }

/* ---- glass: ONE recipe, defined once. Blur only where a surface floats over
       content. Saturation is mild: the earlier 1.7 lifted the blue ambient
       under a card into a violet cast, which read as a different colour of
       glass from surface to surface. ---- */
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  /* blur only where something scrolls or floats underneath: the head, the
     rail, popovers, modals, the drawer, hover cards. A card on the page has
     nothing behind it but the ground, and blurring a smooth ground gives
     the same pixels back; a library page holds 120 cards, and 120 blur
     layers is what made scrolling drag. The cards keep the same tint. */
  .popover, .modal, .peek, .peek.peek-card, .chat-panel, .sidebar, .board-head {
    background: var(--glass);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.15); backdrop-filter: blur(var(--glass-blur)) saturate(1.15);
  }
  /* the rail keeps the tint and loses the blur: it is a flex column BESIDE the
     content, not over it, and it stands on the ambient gradient, so blurring a
     smooth gradient handed back the pixels it was given. The one thing that
     ever passes behind it is the boards drawer, for the 300ms of its slide,
     and the drawer is opaque. The tint is unchanged, so the rail composites to
     the same colour it did before; its edge now does the work the blur did
     not. */
  .card, .dw, .fv-card, .tpl-card, .admin-card, .kanban-card, .snap-row, .auto-row, .cal-cell, .tl-scroll, .rail { background: var(--glass); }
  /* a vertical pane shows its edge on the side, not along the bottom */
  /* The rail's right edge was ALSO drawn here, as an inset shadow, after the
     `border-right` was removed from the base rule at :578. Two different
     mechanisms painted the same 1px line, which is why removing the border
     alone left it on screen and measuring `border-right-width: 0px` looked
     like success. REMOVED 2026-09-21, same owner instruction: the rail and
     the board head are ONE surface. If you are hunting a line on an edge,
     check inset box-shadows before believing a border reading. */
  .peek, .peek.peek-card, .chat-panel, .sidebar { box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-3); }
  /* one recipe, two lifts: the rim is identical, --elev-pop is the only part
     that differs, so a popover cannot drift away from the panel beside it */
  .popover, .modal { box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-pop); }
  .popover, .peek, .modal { border: 1px solid color-mix(in srgb, var(--line-1) 70%, transparent); }
}
.modal-overlay { background: var(--scrim); }

/* ---- every search box answers focus with the fan ring, and only then ----
   RESTORED 2026-09-22 by the owner: "many details were lost like the thin
   rainbow edge, the blue edge keeps coming back." The rules below are
   a1799da's, byte for byte: the masked `::before` carrying the five-hue
   conic, 1.5px, spinning on the 2.6s loop, and `box-shadow: none` on focus so
   no blue ring paints beside it. `.drawer-search::before` is back with it.
   Reduced motion is still answered by the phase-40 rule that names
   `.board-search::before` (`animation: none !important`), which never left.
   The phase-97 text below is SUPERSEDED and left visible so no later phase
   can take the fan off again by citing it; its angle arithmetic was a
   designer's argument, and the owner has ruled on the result.
   ---- SUPERSEDED 2026-09-22 (was: "every search box answers focus with THE
   focus token") ----
   SUPERSEDED 2026-09-21, phase-97, D46 §9 item 2 (screenshots 12 and 14), and
   the old text is left here rather than deleted: this block read "every search
   box answers focus with the fan ring, and only then", and it painted the
   five-hue conic into a masked `::before` that spun on a 2.6s loop.
   Why it goes, and it is arithmetic and not taste (D46 §4b): a conic
   distributes by ANGLE, not by perimeter, so on a 400x36 box each vertical
   side edge gets 10.3 of the 360 degrees - one seventh of a single hue stop -
   and is therefore ONE FLAT COLOUR, a different one every time you look
   because --ring-a spins. That is the "brown" the owner reported, and it is
   why the same treatment photographs orange in one shot and violet in the
   next. The fan keeps every place its smallest painted dimension is >= 6px:
   the loader, the donut, the mark, the landing, and the board table cell the
   owner approved by name.
   The `::before` is deleted rather than hidden: it was an out-of-flow box that
   existed only to carry the conic. `.drawer-search::before` went with it. ---- */
.board-search { position: relative; border-color: var(--line-2); background: color-mix(in srgb, var(--surface-1) 72%, transparent); color: var(--text-3); }
.board-search::before {
  content: ""; position: absolute; inset: -1px; border-radius: inherit; padding: 1.5px; pointer-events: none; opacity: 0;
  background: conic-gradient(from var(--ring-a), var(--logo-1), var(--logo-2), var(--logo-3), var(--logo-4), var(--logo-5), var(--logo-4), var(--logo-3), var(--logo-2), var(--logo-1));
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask-composite: exclude;
  transition: opacity var(--dur-micro-2) var(--ease-out);
}
.board-search:focus-within { border-color: transparent; box-shadow: none; background: var(--surface-1); color: var(--text-2); }
.board-search:focus-within::before { opacity: 1; animation: ringSpin 2.6s linear infinite; }
.drawer-search { border-radius: 10px; }
.drawer-search::before { border-radius: 10px; }

/* ---- board head: the tabs sit on the glass; the active one wears a plate
       and a bar that glides between views ---- */
.view-tabs { position: relative; background: transparent; padding: 0 0 6px; gap: 2px; border-radius: 0; }
.view-tab { padding: 5px 10px 5px 7px; border-radius: 8px; color: var(--text-2); font-weight: 500; }
.view-tab > .ic { box-sizing: border-box; width: 22px; height: 22px; padding: 4px; border-radius: 6px; opacity: 1; color: var(--text-3); transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out); }
.view-tab:hover { background: var(--hover-ground); color: var(--text-1); }
.view-tab:hover > .ic { color: var(--text-1); }
.view-tab.active { background: transparent; box-shadow: none; color: var(--text-1); font-weight: 600; padding-right: 26px; }
.view-tab.active > .ic { background: var(--brand-soft); color: var(--brand); }
.vt-menu { right: 5px; }
.vt-ind { position: absolute; left: 0; bottom: 0; height: 2px; width: 0; border-radius: 2px; background: var(--brand); opacity: 0; }
.vt-ind.ready { opacity: 1; }
.view-add { width: 30px; height: 30px; border-radius: 7px; }
.view-add:hover { background: var(--brand-soft); color: var(--brand); }
.board-switch-row { align-items: flex-end; }
.board-tools { padding-bottom: 6px; }
.board-tools .btn.ghost { color: var(--text-2); }
.board-tools .btn.ghost:hover { color: var(--text-1); }
/* SUPERSEDED 2026-09-21 (D42 §3, Q39). This read `.board-more { display: none }`
   and the phone rule at :2757 turned it back on under 700px, which made Board
   tools a phone-only control and the desktop's six-button right-hand cluster
   the real one. D42 promotes this container to EVERY width and the cluster is
   gone, so the default is now visible and the phone rule agrees with it rather
   than reversing it. Edited in place, not overridden later: a `display: none`
   left standing here is exactly the line a future session would cite to put
   the container back behind a media query. Its placement on the row and its
   label live in the Q39 block appended at the end of this file. */
.board-more { display: inline-flex; }
.board-head .icon-btn { color: var(--text-2); }
.board-head .icon-btn:hover { color: var(--text-1); }

/* ---- status ring ---- */
.ring-wrap { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.ring { position: relative; width: 136px; height: 136px; flex: none; margin: 4px 0; }
.ring-svg { width: 100%; height: 100%; transform: rotate(-90deg); overflow: visible; animation: ringIn .9s var(--ease-out) both; }
.ring-track { fill: none; stroke: var(--wash-2); stroke-width: 11; }
.ring-seg { fill: none; stroke-width: 11; transition: stroke-width var(--dur-micro-2) var(--ease-out), opacity var(--dur-micro-2) var(--ease-out); }
.ring-wrap.has-hl .ring-seg:not(.hl) { opacity: .32; }
.ring-seg.hl { stroke-width: 14.5; }
.ring::after { content: ""; position: absolute; inset: 18px; border-radius: 50%; box-shadow: inset 0 0 0 1px var(--line-2), inset 0 6px 14px -8px rgba(20, 26, 43, .18); pointer-events: none; }
[data-theme="dark"] .ring::after { box-shadow: inset 0 0 0 1px var(--line-2), inset 0 8px 18px -8px rgba(0, 0, 0, .55); }
.ring-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; pointer-events: none; padding: 26px; }
.ring-center b { font: 700 25px/1 var(--font-display); letter-spacing: -.02em; font-variant-numeric: tabular-nums; color: var(--text-1); transition: transform var(--dur-micro-2) var(--ease-spring); }
.ring-wrap.has-hl .ring-center b { transform: scale(1.06); }
.ring-center span { font-size: 11.5px; color: var(--text-3); margin-top: 3px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ring-legend { flex: 1; min-width: 160px; display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 0 14px; align-content: center; }
.ring-legend .legend-row { padding: 4px 8px; margin: 0 -8px; border-radius: 7px; cursor: default; transition: background var(--dur-micro-1) var(--ease-out); }
.ring-legend .legend-row.hl { background: var(--hover-ground); }
.ring-legend .lg-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dw-s .ring-wrap { flex-direction: column; align-items: stretch; gap: 8px; }
.dw-s .ring { margin: 0 auto; }

/* ---- dashboard cards: quieter heads, tiles as washes ---- */
.dw { border-radius: 14px; }
.dw:hover { border-color: var(--line-1); }
.dw-head h4 { font-family: var(--font-display); font-size: 13.5px; letter-spacing: -.01em; }
.dw-ghost { background: var(--wash); }
.dw-add { background: transparent; box-shadow: none; }
.legend-row .dot, .wl-row .dot { border-radius: 50%; }

/* ---- segmented control (layout switch, appearance) ---- */
.seg { display: inline-flex; padding: 3px; gap: 2px; border-radius: 9px; background: var(--wash); }
.seg-btn { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px; border: none; background: transparent; border-radius: 7px; color: var(--text-2); font: 500 12.5px var(--font-body); cursor: pointer; transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out); }
.seg-btn:hover { color: var(--text-1); }
.seg-btn.on { background: var(--surface-1); color: var(--text-1); box-shadow: var(--elev-1); }
.seg-btn.on > .ic { color: var(--brand); }

/* ---- library ---- */
.fv-bar { gap: 8px 10px; margin-bottom: 8px; }
.fv-right { margin-left: auto; display: flex; align-items: center; gap: 8px; }
.fv-latest.on > .ic { color: var(--brand); }
.fv-sort { height: 30px; }
.fv-sum { margin: 0 2px 14px; font-family: var(--font-mono); font-size: 11.5px; letter-spacing: .01em; }
.fv-card { border-radius: 12px; }
.fv-card:hover { border-color: var(--line-1); box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-2); }
.fv-kind { font-family: var(--font-mono); font-size: 10px; letter-spacing: .06em; color: var(--text-2); background: var(--wash); border-radius: 4px; padding: 1px 5px; }
.fv-dl { color: var(--text-2); background: var(--wash); border-radius: 7px; }
.fv-dl:hover { color: var(--brand); background: var(--brand-soft); }
.fv-list .fv-card { border-radius: 9px; padding: 7px 10px 7px 12px; box-shadow: inset 0 1px 0 var(--glass-line); }
.fv-list .fv-card:hover { box-shadow: inset 0 1px 0 var(--glass-line), var(--elev-1); }

/* ---- template center ---- */
.tpl-modal { display: flex; flex-direction: column; max-height: 90vh; }
.tpl-modal .tpl-cats { overflow-y: auto; margin: 0 -8px; padding: 0 8px 8px; }
.tpl-head { margin-bottom: 10px; padding-right: 36px; }
.tpl-ai { height: 40px; margin: 0 0 12px; padding-right: 4px; }
.tpl-ai .board-filter { width: 100%; }
.tpl-ai > .ic { color: var(--brand); }
.tpl-ai .btn { height: 30px; flex: none; }
.tpl-nav { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 0 14px; }
.tpl-cat h4 { display: flex; align-items: center; gap: 7px; margin: 14px 0 10px; color: var(--text-2); font: 600 12px var(--font-body); letter-spacing: .06em; text-transform: uppercase; }
.tpl-cat h4 > .ic { color: var(--text-3); }
.tpl-cat:first-child h4 { margin-top: 4px; }
.tpl-grid { grid-template-columns: repeat(auto-fill, minmax(236px, 1fr)); gap: 12px; }
.tpl-card { --h: var(--logo-5); border-radius: 12px; padding: 14px 14px 12px; gap: 5px; transition: transform var(--dur-micro-2) var(--ease-out), box-shadow var(--dur-micro-2) var(--ease-out), border-color var(--dur-micro-2) var(--ease-out); }
.tpl-card:hover { transform: translateY(-2px); box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-2); border-color: var(--line-1); }
.tpl-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.tpl-ic { box-sizing: border-box; width: 34px; height: 34px; padding: 8px; border-radius: 10px; display: inline-flex; background: color-mix(in srgb, var(--h) 14%, transparent); color: color-mix(in srgb, var(--h) 72%, var(--text-1)); }
[data-theme="dark"] .tpl-ic { color: color-mix(in srgb, var(--h) 82%, #fff); }
.tpl-card b { font-family: var(--font-display); font-size: 14px; letter-spacing: -.01em; }
.tpl-card p { font-size: 12.5px; color: var(--text-2); line-height: 1.5; }
.tpl-cols { margin-top: 4px; }
.tpl-cols span { background: var(--wash); border: none; font-family: var(--font-mono); font-size: 10px; letter-spacing: .02em; color: var(--text-2); padding: 2px 7px; border-radius: 5px; }
.tpl-cols span.more { color: var(--text-3); }
.tpl-card.tpl-lib { background: linear-gradient(180deg, var(--brand-soft), transparent 70%), var(--glass-card); border-color: color-mix(in srgb, var(--brand) 40%, transparent); }
.tpl-chip { margin: 0; vertical-align: 0; background: var(--brand-soft); color: var(--brand); }
.tpl-chip.mine { background: var(--signal-4-soft); color: var(--signal-4); }
[data-theme="dark"] .tpl-chip.mine { color: var(--signal-4); }
.tpl-actions { margin-top: 6px; }
.modal-x { border-radius: 8px; }

/* ---- my work ---- */
.mw-kpis { margin-bottom: 14px; }
.mw-filters { margin-bottom: 18px; }
.qf-chip.bad.on { background: var(--danger-soft); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent); }
.qf-chip.bad.on b { color: var(--danger); }
.mywork-group { margin-top: 0; padding: 12px 14px 8px; }
.mywork-group .card-head { margin-bottom: 4px; padding: 0 4px; }
.mywork-group h4 { color: var(--text-1); }
.mywork-row { background: transparent; border: none; border-radius: 9px; padding: 9px 10px; margin: 0; gap: 12px; color: var(--text-1); transition: background var(--dur-micro-1) var(--ease-out); }
.mywork-row + .mywork-row { border-top: 1px solid var(--line-2); border-radius: 0; }
.mywork-row:hover { transform: none; box-shadow: none; background: var(--hover-ground); }
.mywork-row .dot-mark { width: 8px; height: 8px; flex: none; }
.mywork-row .mw-name { font-weight: 500; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mywork-row .cell-date { flex: none; min-width: 52px; text-align: right; }
.mywork-row .cell-date.muted { color: var(--text-3); font-weight: 400; }
.mywork-row > .ic:last-child { color: var(--text-3); opacity: 0; transition: opacity var(--dur-micro-1) var(--ease-out), transform var(--dur-micro-2) var(--ease-spring); }
.mywork-row:hover > .ic:last-child { opacity: 1; transform: translateX(2px); }

/* ---- admin ---- */
.admin-tabs { gap: 2px; border-bottom: 1px solid var(--line-2); margin: 4px 0 20px; }
.admin-tab { display: inline-flex; align-items: center; gap: 7px; padding: 8px 12px 10px; border-radius: 8px 8px 0 0; font-weight: 500; color: var(--text-2); border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color var(--dur-micro-1) var(--ease-out), background var(--dur-micro-1) var(--ease-out); }
.admin-tab > .ic { box-sizing: border-box; width: 22px; height: 22px; padding: 4px; border-radius: 6px; color: var(--text-3); }
.admin-tab:hover { color: var(--text-1); background: var(--hover-ground); }
.admin-tab.active { color: var(--text-1); font-weight: 600; border-bottom-color: var(--brand); }
.admin-tab.active > .ic { background: var(--brand-soft); color: var(--brand); }
.admin-card { border-radius: 14px; padding: 18px 20px; margin-bottom: 16px; }
.admin-card.danger { border-color: color-mix(in srgb, var(--danger) 35%, transparent); }
.admin-card.danger .card-head h4 > .ic { color: var(--danger); }
.admin-card .kpis { margin-top: 4px; }
.member-list { display: flex; flex-direction: column; }
.member-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto auto; align-items: center; gap: 12px; padding: 9px 6px; border-top: 1px solid var(--line-2); }
.member-row:first-child { border-top: none; }
.member-row .avatar { position: relative; }
.member-row.on .avatar::after { content: ""; position: absolute; right: -1px; bottom: -1px; width: 10px; height: 10px; border-radius: 50%; background: var(--ok); border: 2px solid var(--surface-1); }
.member-text { display: flex; flex-direction: column; min-width: 0; }
.member-text b { font-weight: 600; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-text span { font-size: 12px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.member-state { font-family: var(--font-mono); font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--text-3); }
.member-state.on { color: var(--ok); }
.role-select { margin: 0; }
.role-select select, .role-chip { height: 30px; padding: 0 28px 0 10px; border-radius: 8px; font: 500 12.5px var(--font-body); color: var(--text-1); background: var(--wash); border: 1px solid transparent; }
.role-select select { cursor: pointer; appearance: none; -webkit-appearance: none; background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237f8798' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'><path d='M6.5 9.5 12 15l5.5-5.5'/></svg>"); background-repeat: no-repeat; background-position: right 8px center; background-size: 12px; }
.role-select select:focus { border-color: var(--brand); }
.role-chip { display: inline-flex; align-items: center; padding: 0 10px; text-transform: capitalize; }
.member-remove { width: 28px; color: var(--text-3); }
button.member-remove:hover { color: var(--danger); background: var(--danger-soft); }
.invite-row { border-bottom: none; border-top: 1px solid var(--line-2); gap: 12px; padding: 9px 6px; }
.invite-row:first-of-type { border-top: none; }
.invite-ic { box-sizing: border-box; width: 30px; height: 30px; padding: 7px; border-radius: 8px; background: var(--brand-soft); color: var(--brand); flex: none; display: inline-flex; }
.invite-row .member-text { flex: 1; }
.profile-grid { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: 16px; align-items: start; }
.profile-id { display: flex; align-items: center; gap: 14px; margin: 2px 0 16px; }
.profile-id b { display: block; font-family: var(--font-display); font-size: 16px; }
.profile-id .muted { display: block; }
.color-field { margin-bottom: 14px; }
.color-row { display: flex; align-items: center; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.color-row .color-dot { width: 24px; height: 24px; }
.color-row input[type=color] { width: 34px; height: 24px; padding: 1px; border-radius: 7px; margin: 0 0 0 4px; display: inline-block; }
.pref-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 0; border-top: 1px solid var(--line-2); }
.pref-row:first-of-type { border-top: none; }
.pref-l { font-weight: 500; font-size: 13.5px; }
.storage-bar { background: var(--wash); }

/* ---- drawer contrast ---- */
.side-add { color: var(--text-2); }
.ws-count { color: var(--text-2); }
.side-title { color: var(--text-1); }
.drawer-head { border-bottom-color: var(--line-2); }

/* ---- sheets: on a phone every popover slides up from the bottom edge ---- */
.popover.sheet {
  left: 8px; right: 8px; top: auto; bottom: max(8px, env(safe-area-inset-bottom)); width: auto; min-width: 0; max-height: 78vh; max-height: 78dvh; /* phase-169: dvh after the vh fallback */
  border-radius: 16px; padding-top: 18px; transform: translateY(18px);
  box-shadow: var(--elev-3);
}
.popover.sheet::before { content: ""; position: absolute; top: 7px; left: 50%; width: 36px; height: 4px; margin-left: -18px; border-radius: 2px; background: var(--line-strong); opacity: .6; }
.popover.sheet.show { transform: none; }
.popover.sheet .qf-pop, .popover.sheet .menu, .popover.sheet .notif-pop, .popover.sheet .team-pop { width: auto; min-width: 0; max-width: none; }
.popover.sheet .menu-item, .popover.sheet .people-opt, .popover.sheet .tp-row, .popover.sheet .notif-row { padding-top: 11px; padding-bottom: 11px; }

@media (max-width: 700px) {
  .board-more { display: inline-flex; }
  .board-right { display: none; }
  .board-title-row { flex-wrap: nowrap; }
  .board-name { font-size: var(--fs-xl); }
  .board-head .icon-btn.sm { padding: 6px; }
  .view-tabs { padding-bottom: 6px; }
  .board-switch-row { gap: 6px; }
  .modal-overlay { align-items: flex-end; padding: 0; }
  .modal, .modal.wide, .modal.wide.tall { width: 100%; max-width: none; max-height: 92vh; max-height: 92dvh; border-radius: 18px 18px 0 0; padding: 22px 16px 24px; transform: translateY(24px); } /* phase-169: dvh */
  .modal::before { content: ""; position: absolute; top: 8px; left: 50%; width: 36px; height: 4px; margin-left: -18px; border-radius: 2px; background: var(--line-strong); opacity: .6; }
  .modal, .popover.sheet { background: var(--surface-1); }
  .ring-wrap { gap: 12px; }
  .ring-legend { grid-template-columns: 1fr; }
  .fv-right { margin-left: 0; width: 100%; justify-content: space-between; }
  .fv-sort .act-label { display: inline; }
  .profile-grid { grid-template-columns: 1fr; }
  .member-row { grid-template-columns: auto minmax(0, 1fr) auto; }
  .member-state { display: none; }
  .member-row .member-remove { grid-column: 3; }
  .member-row .role-select { grid-column: 2 / span 2; justify-self: start; }
  .mw-kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .page-head { flex-direction: column; align-items: flex-start; gap: 8px; }
  .admin-tabs { overflow-x: auto; scrollbar-width: none; }
  .admin-tab { white-space: nowrap; }
  .card, .admin-card, .mywork-group { padding: 14px; }
  .ring-svg { animation: none; }
  .ring::after { display: none; }
}
.qf-chip > span, .fv-sort .act-label { white-space: nowrap; }
@media (max-width: 700px) {
  .fv-right { flex-wrap: wrap; gap: 8px; }
  .mywork-row { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "dot name chev" "dot meta chev"; row-gap: 5px; padding: 10px 8px; }
  .mywork-row .dot-mark { grid-area: dot; }
  .mywork-row .mw-name { grid-area: name; white-space: normal; }
  .mywork-row .status-pill { grid-area: meta; justify-self: start; }
  .mywork-row .cell-date { grid-area: meta; justify-self: end; align-self: center; }
  .mywork-row > .ic:last-child { grid-area: chev; opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .ring-svg, .vt-ind, .board-search::before { animation: none !important; transition: none !important; } }


/* ============================================================ phase-40
   Every text box answers focus with the fan ring; tags become chips with
   a card; one glass recipe; the document card; dashboard widgets that can
   be pointed, sized, hidden and reset; a density that is designed, not
   squeezed; the views finished; the library's grid and list cleaned up. */

/* ---- inputs: the ring is the border itself (a gradient border on the box,
       spun by the same property the cells use); the search wrappers keep
       their own ring and their inner input stays quiet ---- */
input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]), select, textarea {
  background-image: linear-gradient(var(--surface-1), var(--surface-1)), conic-gradient(from var(--ring-a), var(--logo-1), var(--logo-2), var(--logo-3), var(--logo-4), var(--logo-5), var(--logo-4), var(--logo-3), var(--logo-2), var(--logo-1));
  background-origin: border-box; background-clip: padding-box, border-box; background-size: 100% 100%, 0 0;
}
input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]):focus, select:focus, textarea:focus {
  border-color: transparent; background-size: 100% 100%, 100% 100%; animation: ringSpin 2.6s linear infinite;
}
.board-search .board-filter, .board-search .board-filter:focus, .palette-input-row input, .tag-input, .role-select select, .attach-ver { background-image: none; animation: none; }
.attach-ver:focus { border-color: var(--brand); }
.role-select select:focus { border-color: var(--brand); }
.label-row .lab-text { background-color: var(--surface-1); }
.label-row .lab-text:focus { border-color: transparent; }
.auto-line select:focus, .auto-line input:focus, .ch-compose textarea:focus { border-color: transparent; }

/* ---- tags: one chip and a count at rest, a card with every tag on hover,
       an editor of chips ---- */
.cell-tags { display: inline-flex; align-items: center; justify-content: center; gap: 5px; padding: 0 6px; max-width: 100%; overflow: hidden; }
.cell-tags .tag { flex: 0 1 auto; min-width: 0; max-width: calc(var(--cw, 160px) - 52px); }
.cell-tags .tag.more { flex: none; }
.cell-tags:empty { min-height: 1em; }
.peek-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 2px; max-width: 320px; }
.peek-tags .tag { max-width: 100%; }
.tag-pop { padding: 10px; }
.tag-editor { display: flex; flex-direction: column; gap: 4px; width: min(320px, calc(100vw - 32px)); }
.tag-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 6px; border-radius: 9px; background: var(--wash); min-height: 40px; }
.tag-chips .tag { padding-right: 4px; gap: 4px; height: 24px; }
.tag-x { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border: none; border-radius: 50%; background: transparent; color: var(--text-3); cursor: pointer; padding: 0; margin-left: 2px; }
.tag-x:hover { background: var(--danger-soft); color: var(--danger); }
.tag-input { flex: 1 1 90px; min-width: 90px; height: 24px; border: none; background: transparent; padding: 0 4px; font-size: 13px; }
.tag-input:focus { border: none; }
.tag-suggest { display: flex; flex-wrap: wrap; gap: 6px; padding: 0 2px 4px; }
.tag-suggest .tag { cursor: pointer; border: none; font-family: var(--font-body); transition: background var(--dur-micro-1) var(--ease-out); }
.tag-suggest .tag:hover { background: var(--brand-soft); }
.tag-editor .menu-label { padding: 8px 2px 4px; }

/* ---- options editor rows without a swatch ---- */
.label-rows.options .lab-text { margin-left: 2px; }

/* ---- the document card: details on the left, the page on the right ---- */
.modal.item-modal { padding: 0; width: min(1180px, 96vw); max-height: 90vh; overflow: hidden; }
.modal.item-modal .modal-x { top: 10px; right: 10px; background: var(--glass-card); box-shadow: inset 0 0 0 1px var(--line-2); }
.item-card.ic3 { display: grid; grid-template-columns: minmax(340px, 420px) minmax(0, 1fr); height: 86vh; }
.ic-left { min-height: 0; overflow-y: auto; padding: 20px 20px 16px 22px; border-right: 1px solid var(--line-2); display: flex; flex-direction: column; gap: 14px; }
.ic-head { padding-right: 24px; }
.ic-crumb { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.ic-head h3 { font-size: 19px; line-height: 1.25; margin: 0 0 4px; padding: 2px 6px; margin-left: -6px; }
.item-card h3:hover, .item-card h3:focus { background: var(--wash); }
.ic-meta { display: flex; align-items: center; gap: 6px; margin: 0; }
.ic-meta .icon-btn { color: var(--text-3); }
.ic-sec { display: flex; flex-direction: column; gap: 6px; }
.ic-sec-t { display: flex; align-items: center; gap: 6px; margin: 0; font: 600 11.5px var(--font-body); letter-spacing: .06em; text-transform: uppercase; color: var(--text-2); }
.ic-sec-t > .ic { color: var(--text-3); }
.ic-props { display: flex; flex-direction: column; border: 1px solid var(--line-2); border-radius: 10px; overflow: hidden; background: var(--wash); }
.ic-props .iv-row { gap: 10px; padding: 5px 8px 5px 10px; border-top: 1px solid var(--line-2); min-height: 38px; }
.ic-props .iv-row:first-child { border-top: none; }
.ic-props .iv-label { width: 108px; font-size: 12px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ic-props .cell.inline { flex: 1; min-width: 0; border: none; border-radius: 7px; padding: 2px 6px; min-height: 28px; justify-content: flex-start; }
.ic-props .cell.inline:hover { background: var(--hover-ground); box-shadow: none; }
.ic-props .cell.inline .cell-text { padding: 2px 0; }
.ic-props .cell.inline .status-pill { width: auto; max-width: 100%; }
.attach-head { margin-bottom: 2px; }
.attach-head .attach-btn { height: 28px; padding: 0 9px; }
.attach-list { margin-top: 0; gap: 2px; }
.attach-row { grid-template-columns: auto minmax(0, 1fr) auto auto; grid-template-areas: "badge name dl del" "badge meta dl del"; padding: 6px 4px 6px 4px; border-radius: 9px; column-gap: 8px; row-gap: 2px; transition: background var(--dur-micro-1) var(--ease-out); }
.attach-row:hover { background: var(--hover-ground); }
.attach-row.viewing { background: var(--brand-soft); }
.attach-row.viewing .attach-name { color: var(--brand); }
.attach-row .attach-name { grid-area: name; color: var(--text-1); font-weight: 500; font-size: 13px; }
.attach-row .attach-name:hover { color: var(--brand); text-decoration: none; }
.attach-row .attach-meta { grid-area: meta; font-family: var(--font-mono); font-size: 10.5px; color: var(--text-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.attach-row .attach-meta { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.attach-row .attach-meta > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.attach-row .attach-ver { width: 54px; height: 22px; padding: 0 5px; margin: 0; flex: none; font-size: 10.5px; background-color: var(--wash); border-color: transparent; }
.attach-row .attach-ver:focus { border-color: var(--brand); background-color: var(--surface-1); }
.attach-row .attach-name { white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.3; }
.attach-row .dl-btn { grid-area: dl; }
.attach-row .attach-del { grid-area: del; width: 26px; margin: 0; color: var(--text-3); opacity: .7; }
.attach-row .attach-del:hover { color: var(--danger); background: var(--danger-soft); opacity: 1; }
.attach-tools { display: inline-flex; gap: 6px; align-items: center; }
.attach-tools .btn { height: 28px; padding: 0 9px; }
.attach-empty { margin: 4px 2px; }
.item-tabs { margin-bottom: 10px; gap: 2px; border-bottom-color: var(--line-2); }
.item-tab { display: inline-flex; align-items: center; gap: 6px; padding: 7px 10px; font-weight: 500; color: var(--text-2); font-size: 13px; }
.item-tab.active { color: var(--text-1); font-weight: 600; border-bottom-color: var(--brand); }
.item-tab .pill-count { font-size: 10.5px; }
.update-composer { gap: 6px; margin-bottom: 12px; }
.update-composer textarea { border-radius: 9px; font-size: 13px; }
.update { background: var(--wash); border-radius: 10px; }
.ic-viewer { border: none; border-radius: 0; background: var(--wash); min-height: 0; }
.ic-viewer-head { padding: 10px 14px; background: transparent; border-bottom: 1px solid var(--line-2); gap: 8px; }
.ic-viewer-head > .fglyph { flex: none; }
.ic-viewer-head b { font-size: 13px; font-weight: 600; }
.ic-viewer-acts { display: inline-flex; gap: 2px; margin-left: auto; flex: none; }
.ic-viewer-acts .icon-btn { color: var(--text-2); }
.ic-viewer .att-preview { padding: 16px; }
.ic-viewer .att-preview.pdf { padding: 0; }
.ic-viewer-empty { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.ic-viewer-empty p { margin: 0; }
@media (max-width: 900px) {
  .ic-fold { display: none; }
  .modal.item-modal { width: 100%; }
  .item-card.ic3 { grid-template-columns: 1fr; height: auto; max-height: 90vh; overflow-y: auto; }
  .ic-left { border-right: none; border-bottom: 1px solid var(--line-2); overflow: visible; }
  .ic-viewer { min-height: 60vh; }
}

/* ---- dashboard: the bar above the grid, the widget subtitle ---- */
.dash-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: -4px 0 14px; }
.dash-hint { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-2); }
.dash-hint b { font-weight: 700; color: var(--text-1); }
.dash-hint > .ic { color: var(--text-3); }
.dash-tools { display: inline-flex; gap: 4px; flex: none; }
.dw-menu { opacity: .6; }
.dw:hover .dw-menu, .dw-menu:focus-visible { opacity: 1; }
.dw-head h4 { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.dw-sub {
  font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: var(--fw-medium); line-height: var(--lh-snug);
  letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dw-pop { min-width: 240px; }
.ring-svg { transform: none; animation: ringIn .9s var(--ease-out) backwards; }
@keyframes ringIn { from { opacity: 0; transform: rotate(-110deg) scale(.86); } to { opacity: 1; transform: none; } }

/* ---- kanban, calendar, timeline in the same grammar ---- */
.kanban { gap: 12px; }
.kanban-col { width: 264px; min-width: 264px; }
.kanban-count { font-family: var(--font-mono); font-size: 11px; color: var(--text-2); background: var(--wash); }
.kanban-list { background: var(--wash); border-radius: 12px; padding: 8px; min-height: 140px; gap: 6px; box-shadow: inset 0 0 0 1px var(--line-2); }
.kanban-card { border-radius: 10px; padding: 10px 12px; box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-1); }
.kanban-card:hover { transform: translateY(-1px); box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-2); border-color: var(--line-1); }
.kanban-card-name { font-weight: 500; font-size: 13.5px; margin-bottom: 8px; line-height: 1.35; }
.kanban-empty { border-color: var(--line-1); color: var(--text-2); }
.cal-grid { background: var(--line-2); border-color: var(--line-2); border-radius: 12px; }
.cal-cell { background: var(--glass-card); min-height: 104px; padding: 7px; }
.cal-cell.empty { background: var(--wash); }
.cal-cell.today { background: var(--brand-soft); }
.cal-day { font-family: var(--font-mono); font-size: 11.5px; font-weight: 500; color: var(--text-2); margin-bottom: 5px; }
.cal-cell.today .cal-day { color: var(--brand); font-weight: 700; }
.cal-more { color: var(--text-2); font-family: var(--font-mono); font-size: 10.5px; }
.cal-head { margin-bottom: 12px; }
.cal-head h3 { font-size: 17px; }
.tl-scroll { background: var(--glass-card); border-color: var(--line-2); border-radius: 12px; box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-1); }
.tl-row { height: 40px; }
.tl-row:hover .tl-label { color: var(--brand); }
.tl-track { background: linear-gradient(90deg, var(--line-2) 1px, transparent 1px) 0 0 / var(--tl-day, 46px) 100%; border-radius: 6px; }

/* ---- library grid and list, cleaner ---- */
.fv-grid { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px; }
.fv-card { padding: 12px 12px 11px; gap: 11px; }
.fv-card .file-mini { flex: none; }
.fv-name { font-size: 13px; }
.fv-doc { font-size: 12px; }
.fv-meta { gap: 5px; margin-top: 2px; }
.fv-meta .attach-ver-chip { background: var(--brand-soft); color: var(--brand); font-weight: 600; }
.fv-list { gap: 2px; }
/* The head strip and the rows are two separate grid containers, so they only
   line up if every track resolves to the same number in both of them. That
   rules out `auto` tracks: an auto track sizes to its own container's content,
   and the head's empty last cell is 0px where the row's Download button is
   94px, which pushed every fr track out of step and left the labels 43px off
   the values beneath them. Fixed ends, fr middles, and one shared list both
   grids read, so they cannot drift apart again. */
/* Rev. is the one track whose content has no ceiling (the manifest builds a
   tag by concatenation: "QAF v2" + a date + " (archive copy)"), so it is the
   one track that flexes: it keeps its old 72px floor on a narrow window and
   spends spare width on the long tags where there is room. It is still capped
   by the fr share, never by the string — the chip clips itself, phase-71-b. */
:root { --fv-cols: 24px minmax(0, 2fr) minmax(0, 1.4fr) minmax(72px, 1.3fr) 44px 74px minmax(0, 1fr) 92px 100px; }
.fv-list .fv-card { display: grid; grid-template-columns: var(--fv-cols); align-items: center; gap: 12px; padding: 6px 8px 6px 10px; }
.fv-list .fv-body > .file-mini { grid-column: 1; }
.fv-list .fv-text { display: contents; }
.fv-list .fv-name, .fv-list .fv-doc { flex: none; }
.fv-list .fv-name { grid-column: 2; }
.fv-list .fv-doc { grid-column: 3; }
.fv-list .fv-meta { display: contents; }
.fv-list .fv-meta .attach-ver-chip { grid-column: 4; justify-self: start; }
.fv-list .fv-kind { grid-column: 5; justify-self: start; }
.fv-list .fv-card:not(:has(.attach-ver-chip)) .fv-kind { grid-column: 5; }
/* size, uploader and date are cells of their own here, one under each label,
   instead of the single meta blob that sat under "Rev." and left Kind, Size
   and Added by with nothing beneath them */
.fv-list .fv-size { grid-column: 6; }
.fv-list .fv-who { grid-column: 7; }
.fv-list .fv-when { grid-column: 8; }
.fv-list .fv-size, .fv-list .fv-who, .fv-list .fv-when { justify-self: start; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* the dots only separate them at 900 and under, where the three fold back
   into one line inside the card */
.fv-list .fv-sep { display: none; }
.fv-list .fv-dl { grid-column: 9; position: static; opacity: .8; justify-self: end; }
/* a row carries a 1px border and the head does not: without a transparent one
   the head's content box is 2px wider and every fr track lands a shade long */
.fv-list-head { display: grid; grid-template-columns: var(--fv-cols); gap: 12px; padding: 0 8px 6px 10px; border-left: 1px solid transparent; border-right: 1px solid transparent; font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: var(--fw-medium); line-height: var(--lh-snug); letter-spacing: var(--ls-caps); text-transform: uppercase; color: var(--text-3); }
.fv-list-head span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fv-list-head span:first-child { width: 24px; }
@media (max-width: 900px) {
  .fv-list .fv-card { grid-template-columns: auto minmax(0, 1fr) auto; }
  .fv-list .fv-text { display: flex; flex-direction: column; gap: 2px; grid-column: 2; }
  .fv-list .fv-meta { display: inline-flex; }
  .fv-list .fv-meta .attach-ver-chip, .fv-list .fv-kind,
  .fv-list .fv-size, .fv-list .fv-who, .fv-list .fv-when { grid-column: auto; }
  .fv-list .fv-sep { display: inline; }
  .fv-list .fv-dl { grid-column: 3; }
  .fv-list-head { display: none; }
}

/* ---- density: compact is a designed second rhythm, not a squeeze ---- */
[data-density="compact"] { --fs-sm: 13px; }
[data-density="compact"] .board-table td { height: 32px; }
[data-density="compact"] .board-table th { font-size: 12px; padding-top: 5px; padding-bottom: 5px; }
[data-density="compact"] .board-table .cell-text, [data-density="compact"] .cell { font-size: 12.5px; }
[data-density="compact"] .board-table .status-pill { padding: 3px 8px; font-size: 12px; }
[data-density="compact"] .item-row .avatar { width: 24px !important; height: 24px !important; font-size: 10px !important; }
[data-density="compact"] td.col-name .item-name { font-size: 13px; }
[data-density="compact"] .chat-btn { height: 22px; }
[data-density="compact"] .row-menu { width: 22px; height: 22px; }
[data-density="compact"] .group-head { padding: 3px 6px 3px 2px; }
[data-density="compact"] .group-title { font-size: 15px; }
[data-density="compact"] .group-caret { width: 22px; height: 22px; }
[data-density="compact"] .add-item-input { height: 32px; font-size: 12.5px; }
[data-density="compact"] .cell-date { font-size: 12px; }
[data-density="compact"] .cell-files.has .file-mini { --tw: 18px; --th: 23px; }
[data-density="compact"] .file-mini { width: var(--tw); height: var(--th); }
[data-density="compact"] .fglyph { width: 18px; height: 21px; }
[data-density="compact"] .board-head { padding-top: var(--sp-2); padding-bottom: 0; }
[data-density="compact"] .board-name { font-size: var(--fs-xl); }
[data-density="compact"] .board-switch-row { margin-top: 6px; }
[data-density="compact"] .view-tab { padding-top: 3px; padding-bottom: 3px; }
[data-density="compact"] .board-tools .btn, [data-density="compact"] .board-search { height: 30px; }
[data-density="compact"] .board-body { padding-top: 12px; }
[data-density="compact"] .dash { gap: 10px; }
[data-density="compact"] .dw-body { padding: 4px 12px 12px; }
[data-density="compact"] .dw-head { padding: 9px 8px 2px 10px; }
[data-density="compact"] .kpi { padding: 9px 10px 9px 13px; }
[data-density="compact"] .kpi-n { font-size: 22px; }
[data-density="compact"] .fv-card { padding: 9px 10px; gap: 9px; }
[data-density="compact"] .fv-grid { gap: 8px; }
[data-density="compact"] .kanban-card { padding: 8px 10px; }
[data-density="compact"] .kanban-list { gap: 5px; }
[data-density="compact"] .cal-cell { min-height: 84px; }
[data-density="compact"] .mywork-row { padding: 6px 10px; }
[data-density="compact"] .member-row { padding: 6px; }
[data-density="compact"] .side-link, [data-density="compact"] .side-board { padding-top: 4px; padding-bottom: 4px; }
[data-density="compact"] .page-pad { padding: 20px 26px; }


/* ============================================================ phase-41
   One frost for everything, the data palette with presets, the header row
   that never overlaps, the document card with a fold and a proper close. */

/* ---- swatches: the twelve presets, the current one ringed ---- */
.swatches { display: inline-flex; flex-wrap: wrap; gap: 8px; padding: 4px 10px 8px; max-width: 240px; }
.swatches .color-dot { width: 24px; height: 24px; }
.palette-pop { min-width: 0; }
.palette-pop .menu-label { padding-top: 6px; }
.lab-swatch { border: none; }
.lab-swatch.custom { box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12), 0 0 0 2px var(--surface-1), 0 0 0 3.5px var(--line-strong); }
.lab-swatch:hover { transform: scale(1.08); }
.lab-swatch { transition: transform var(--dur-micro-2) var(--ease-spring); }
.label-tools { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.color-field .swatches { padding: 0; max-width: none; }

/* ---- the document card: a stage bar with fold, file head and close ---- */
.modal.item-modal { width: min(1320px, 97vw); max-height: 94vh; }
.modal.item-modal .modal-x { display: none; }
.item-card.ic3 { grid-template-columns: minmax(340px, 400px) minmax(0, 1fr); height: 90vh; transition: grid-template-columns var(--dur-scene-1) var(--ease-out); }
.item-card.ic3.folded { grid-template-columns: 0 minmax(0, 1fr); }
.item-card.ic3.folded .ic-left { padding-left: 0; padding-right: 0; overflow: hidden; border-right: none; opacity: 0; }
.ic-left { transition: opacity var(--dur-micro-3) var(--ease-out), padding var(--dur-scene-1) var(--ease-out); }
.ic-stage { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.ic-stage-bar { display: flex; align-items: center; gap: 8px; padding: 8px 10px 8px 8px; border-bottom: 1px solid var(--line-2); flex: none; }
.ic-stage-bar .ic-viewer-head { flex: 1; min-width: 0; padding: 0; border: none; background: transparent; display: flex; align-items: center; gap: 8px; }
.ic-fold, .ic-close { flex: none; width: 30px; height: 30px; padding: 0; display: inline-flex; align-items: center; justify-content: center; border-radius: 8px; color: var(--text-2); background: var(--wash); }
.ic-fold:hover, .ic-close:hover { color: var(--text-1); background: var(--wash-2); }
.ic-close:hover { color: var(--danger); background: var(--danger-soft); }
.ic-viewer { flex: 1; }
.ic-left { padding: 18px 18px 14px 20px; gap: 12px; }
.ic-head h3 { font-size: 18px; }
.ic-props .iv-row { min-height: 34px; padding: 3px 8px 3px 10px; }
.ic-props .iv-label { width: 104px; }
.ic-props .cell.inline { min-height: 26px; }
@media (max-width: 900px) {
  .item-card.ic3, .item-card.ic3.folded { grid-template-columns: 1fr; height: auto; max-height: 90vh; overflow-y: auto; }
  .item-card.ic3.folded .ic-left { display: none; }
  .ic-stage { order: -1; }
  .ic-stage-bar { position: sticky; top: 0; z-index: 2; background: var(--surface-1); }
  .ic-viewer { min-height: 46vh; }
}


/* ============================================================ phase-43
   Import an archive: a drop zone, a progress bar in the fan's hues, the
   plan as a card, then the board. */
.tpl-start { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
.tpl-start .tpl-ai { flex: 1; margin: 0; }
.tpl-import { height: 40px; flex: none; border: 1px dashed var(--line-1); border-radius: 10px; }
.tpl-import:hover { border-color: var(--brand); color: var(--brand); }
.imp-modal { width: min(640px, 94vw); }
.imp-drop {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; text-align: center;
  min-height: 190px; padding: 24px; margin: 0; border-radius: 14px; cursor: pointer; font-weight: 400;
  background: var(--wash); border: 1.5px dashed var(--line-1); color: var(--text-1);
  transition: background var(--dur-micro-2) var(--ease-out), border-color var(--dur-micro-2) var(--ease-out);
}
.imp-drop:hover { border-color: var(--brand); background: var(--brand-soft); }
.imp-drop.over { border-color: transparent; background: var(--brand-soft); }
.imp-drop.over::before {
  content: ""; position: absolute; inset: -1.5px; border-radius: 14px; padding: 2px; pointer-events: none;
  background: conic-gradient(from var(--ring-a), var(--logo-1), var(--logo-2), var(--logo-3), var(--logo-4), var(--logo-5), var(--logo-4), var(--logo-3), var(--logo-2), var(--logo-1));
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask-composite: exclude; animation: ringSpin 2.6s linear infinite;
}
.imp-drop b { font-family: var(--font-display); font-size: 15px; }
.imp-drop input[type=file] { display: none; }
.gjump-chip { max-width: 220px; white-space: nowrap; }
.gjump-chip span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.imp-ic { box-sizing: border-box; width: 52px; height: 52px; padding: 13px; border-radius: 14px; background: var(--brand-soft); color: var(--brand); display: inline-flex; margin-bottom: 6px; }
.imp-progress { padding: 8px 0 4px; }
.imp-file { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.imp-name { flex: 1; min-width: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.imp-size { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-3); }
.imp-bar { height: 8px; border-radius: 4px; background: var(--wash-2); overflow: hidden; }
.imp-bar i { display: block; height: 100%; width: 0; border-radius: 4px; background: linear-gradient(90deg, var(--logo-1), var(--logo-2), var(--logo-3), var(--logo-4), var(--logo-5)); transition: width .25s var(--ease-out); }
.imp-status { margin-top: 8px; font-family: var(--font-mono); font-size: 11.5px; color: var(--text-2); }
.imp-fields { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 12px; }
.imp-fields label { margin-bottom: 8px; }
.imp-stats { margin: 6px 0 10px; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
.imp-shape { display: flex; align-items: center; gap: 6px; margin: 0 0 10px; }
.imp-groups { display: flex; flex-direction: column; border: 1px solid var(--line-2); border-radius: 10px; overflow: hidden; max-height: 240px; overflow-y: auto; background: var(--wash); }
.imp-group { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-top: 1px solid var(--line-2); font-size: 13px; }
.imp-group:first-child { border-top: none; }
.imp-group .dot-mark { color: var(--brand); flex: none; }
.imp-group b { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.imp-group .muted { flex: none; font-family: var(--font-mono); font-size: 11px; }
@media (max-width: 700px) { .imp-fields { grid-template-columns: 1fr; } .tpl-start { flex-wrap: wrap; } .tpl-import { flex: 1 1 100%; } }


/* ============================================================ phase-44 phones
   The rail becomes a bottom bar, the register becomes cards, panels and
   sheets respect the bar and the safe area, every touch target is 44px. */
@supports (height: 100dvh) { .app { height: 100dvh; } }
@media (max-width: 700px) {
  /* the rail is a bottom bar down here, so it costs no horizontal room */
  :root { --bar-h: calc(56px + env(safe-area-inset-bottom, 0px)); --rail-w: 0px; --board-pad-x: 10px; }
  .app { flex-direction: column; }
  /* the bottom bar: six destinations, the rest live in the drawer and the account menu */
  .rail {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto; width: auto; height: var(--bar-h); z-index: 60;
    flex-direction: row; align-items: stretch; justify-content: space-around; gap: 0;
    padding: 0 6px env(safe-area-inset-bottom, 0px); border-right: none; border-top: 1px solid var(--line-2);
    box-shadow: 0 -8px 24px -16px rgba(0, 0, 0, .25);
  }
  .rail-logo, .rail-spacer, .rail .theme-btn, .rail-btn[data-act="open-templates"], .rail-btn[href="#/admin"] { display: none; }
  .rail-btn, .avatar-btn { width: auto; flex: 1 1 0; height: 56px; border-radius: 0; display: inline-flex; align-items: center; justify-content: center; }
  .rail-btn::before { left: 50%; top: 6px; width: 22px; height: 3px; transform: translateX(-50%) scaleX(0); border-radius: 2px; transition: transform var(--dur-micro-3) var(--ease-spring); }
  .rail-btn.active::before { height: 3px; transform: translateX(-50%) scaleX(1); }
  .rail-btn.active { background: transparent; }
  .rail-btn .badge { top: 8px; right: calc(50% - 20px); }
  .rail-dot { right: calc(50% - 16px); bottom: 12px; }
  .main { padding-bottom: var(--bar-h); }
  .sidebar { left: 0; width: 100vw; bottom: var(--bar-h); }
  .drawer-scrim { inset: 0; }
  .ai-panel { top: 0; bottom: var(--bar-h); width: 0; }
  .ai-panel.open { width: 100vw; }
  .chat-panel { bottom: var(--bar-h); } /* phase-163: above the bar, like Claude, the sheets and the item card; it ran to 0 and the bar covered Send */
  .ch-list-head .ch-close { display: inline-flex; margin-left: auto; }
  .ch-list-head { align-items: center; gap: 8px; }
  .ch-list-head span { flex: 1; }
  .popover.sheet { bottom: calc(var(--bar-h) + 8px); max-height: calc(100vh - var(--bar-h) - 24px); max-height: calc(100dvh - var(--bar-h) - 24px); } /* phase-169: dvh */
  .modal-overlay { padding-bottom: 0; }
  .board-body { padding: 10px var(--board-pad-x) calc(var(--bar-h) + 40px); }
  .page-pad { padding-bottom: calc(var(--bar-h) + 30px); }
  #toasts { bottom: calc(var(--bar-h) + 12px); }
  /* head: the title row breathes, the tools are 44px */
  .board-head .icon-btn.sm, .board-tools .btn, .board-search, #qf-btn { min-height: 40px; }
  .board-tools .btn.primary { width: 44px; height: 40px; }
  /* cards for the register */
  .cards { display: flex; flex-direction: column; gap: 8px; padding: 2px 0 4px; }
  .rcard {
    position: relative; display: flex; flex-direction: column; gap: 8px; width: 100%; text-align: left; padding: 12px 14px 10px 16px;
    border: 1px solid var(--line-2); border-radius: 12px; background: var(--surface-1); color: var(--text-1); cursor: pointer;
    box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-1);
  }
  .rcard::before { content: ""; position: absolute; left: 0; top: 10px; bottom: 10px; width: 3px; border-radius: 2px; background: var(--gcolor); }
  .rcard:active { background: var(--surface-2); }
  .rcard-top { display: flex; align-items: flex-start; gap: 8px; }
  .rcard-name { flex: 1; min-width: 0; font: 600 14.5px/1.35 var(--font-display); letter-spacing: -.01em; }
  .rcard-top > .ic { flex: none; margin-top: 3px; color: var(--text-3); }
  .rcard-cells { display: flex; flex-wrap: wrap; gap: 6px 14px; }
  .rcard-cell { display: inline-flex; align-items: center; gap: 6px; min-height: 24px; font-size: 13px; }
  .rcard-cell em { font: 500 10.5px var(--font-mono); letter-spacing: .05em; text-transform: uppercase; color: var(--text-3); }
  .rcard-cell .status-pill { width: auto; padding: 3px 9px; font-size: 12px; }
  .rcard-cell .cell-text { padding: 0; max-width: 200px; }
  .rcard-cell .cell-people .avatar { width: 24px !important; height: 24px !important; font-size: 10px !important; }
  .rcard-cell .cell-tags { padding: 0; }
  .rcard-foot { display: flex; gap: 12px; font-family: var(--font-mono); font-size: 11px; color: var(--text-3); }
  .rcard-foot:empty { display: none; }
  .rcard-foot span { display: inline-flex; align-items: center; gap: 4px; }
  .rcard-add { flex-direction: row; align-items: center; justify-content: center; gap: 6px; padding: 10px; color: var(--text-2); background: transparent; border-style: dashed; box-shadow: none; font-size: 13px; }
  .rcard-add::before { display: none; }
  .cards .group-count b { color: var(--text-1); }
  /* kanban: one lane per swipe */
  .kanban { scroll-snap-type: x mandatory; padding-left: 2px; }
  .kanban-col { scroll-snap-align: start; width: min(300px, calc(100vw - 40px)); min-width: 0; }
  /* calendar: dots in the cells, the list on tap */
  .cal-cell { min-height: 60px; padding: 5px 4px; }
  .cal-cell.has { cursor: pointer; }
  .cal-cell.has:active { background: var(--brand-soft); }
  .cal-day { display: flex; align-items: center; justify-content: space-between; }
  .cal-n { font-size: 10px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; background: var(--brand-soft); color: var(--brand); display: inline-flex; align-items: center; justify-content: center; font-weight: 600; }
  .cal-item { width: 8px; height: 8px; padding: 0; margin: 2px 2px 0 0; border-radius: 50%; display: inline-block; overflow: hidden; color: transparent; background: var(--lab, var(--brand)); vertical-align: middle; box-shadow: inset 0 0 0 1px var(--lab-ink); }
  .cal-item::before { display: none; }
  .cal-more { display: none; }
  .cal-head h3 { min-width: 0; font-size: 15px; }
  /* admin on a phone */
  .admin-tabs { gap: 0; }
  .admin-tab { padding: 8px 8px 10px; font-size: 12.5px; gap: 5px; }
  .member-row { grid-template-columns: auto minmax(0, 1fr) auto; grid-template-areas: "av text rm" "av role rm"; row-gap: 6px; }
  .member-row .avatar { grid-area: av; align-self: start; }
  .member-row .member-text { grid-area: text; }
  .member-row .role-select, .member-row .role-chip { grid-area: role; justify-self: start; }
  .member-row .member-remove { grid-area: rm; align-self: center; }
  .card-head .btn.primary { white-space: nowrap; }
  /* template chips scroll on one line */
  .tpl-nav { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding-bottom: 12px; margin: 0 -16px; padding-left: 16px; padding-right: 16px; }
  .tpl-nav::-webkit-scrollbar { display: none; }
  .tpl-nav .qf-chip { flex: none; }
  /* landing header */
  .mk-header { padding-left: 14px; padding-right: 14px; }
}
.ch-close { display: none; }
.cal-n { display: none; }
.cal-pop { min-width: 260px; }
.cal-pop .menu-item .status-pill { margin-left: auto; flex: none; }
.cal-pop .cr-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 700px) { .cal-n { display: inline-flex; } }


/* ---- board switcher: every board one tap from the title ---- */
.board-switch-btn { color: var(--text-3); margin-left: -4px; }
.board-switch-btn:hover { color: var(--text-1); }
.board-switcher { min-width: 300px; max-width: min(380px, calc(100vw - 16px)); }
.bs-search { height: 36px; margin: 2px 2px 6px; }
.bs-search .board-filter { width: 100%; }
.bs-list { max-height: min(52vh, 460px); overflow-y: auto; }
.bs-row .board-glyph { box-sizing: border-box; width: 24px; height: 24px; border-radius: 7px; display: inline-flex; align-items: center; justify-content: center; flex: none; color: var(--wc, var(--brand)); background: color-mix(in srgb, var(--wc, var(--brand)) 14%, transparent); }
.bs-row .sb-fav { color: var(--star); display: inline-flex; }
.bs-row .sb-fav .ic { fill: currentColor; }
.bs-row.on { background: var(--brand-soft); color: var(--brand); }
.bs-none { padding: 14px 10px; color: var(--text-3); font-size: 13px; text-align: center; }
.board-name.tap { cursor: pointer; display: inline-flex; align-items: center; gap: 4px; max-width: calc(100vw - 190px); }
.board-name.tap > .ic { color: var(--text-3); flex: none; }
/* group index strip above the cards */
.gjump { display: flex; gap: 6px; overflow-x: auto; scrollbar-width: none; padding: 2px 0 10px; margin: 0 -10px; padding-left: 10px; padding-right: 10px; }
.gjump::-webkit-scrollbar { display: none; }
/* THE SHAPE HERE IS RIGHT; THE COUNT IS NOT. phase-72 left this capsule alone
   deliberately: a group-jump chip is navigation, the same family as the
   view-tab track, so the grammar says capsule and squaring it would be the
   wrong fix. What qa found on the owner's register is a different problem
   entirely: .gjump renders only in the cards layout (SD.boardRowKind() returns
   "cards" only when SD.isPhone()), so there are ZERO of these on the desktop
   board head and TWELVE at 375px, each 44px, in a strip 1345px wide inside a
   375px window - reaching a group means scrolling about 970px sideways on the
   one surface where that gesture costs the most. The follow-up is to show
   FEWER of them (a jump menu, or the current group and its neighbours), never
   to change their corners. */
.gjump-chip { flex: none; display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 10px 0 9px; border: 1px solid var(--line-2); border-radius: var(--r-round); background: var(--glass-card); color: var(--text-1); font: 500 12.5px var(--font-body); cursor: pointer; }
.gjump-chip i { width: 8px; height: 8px; border-radius: 50%; background: var(--gc); }
.gjump-chip b { font: 500 10.5px var(--font-mono); color: var(--text-3); }
.gjump-chip:active { background: var(--brand-soft); }
@media (max-width: 700px) {
  .fv-search { display: none; }
  .fv-bar { gap: 8px; }
  .fv-kinds { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; width: 100%; padding-bottom: 2px; }
  .fv-kinds::-webkit-scrollbar { display: none; }
  .fv-kinds .qf-chip { flex: none; }
  .popover.sheet .bs-list { max-height: calc(100vh - var(--bar-h) - 240px); }
  .popover.sheet .bs-search .board-filter { font-size: 15px; }
}


/* ============================================================ phase-45
   Getting a file is one obvious step, everywhere a file is shown. */
.dl-btn {
  display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 10px 0 8px; border-radius: 8px; flex: none;
  background: var(--brand-soft); color: var(--brand); font: 600 12px var(--font-body); text-decoration: none; white-space: nowrap;
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.dl-btn:hover { background: var(--brand-fill); color: #fff; }
.dl-btn .ic { opacity: 1; }
.ic-viewer-acts .btn { height: 30px; padding: 0 10px; }
.att-np-name { display: block; margin: 0 0 4px; font-size: 14px; color: var(--text-1); word-break: break-word; }
.att-nopreview p { margin: 0 0 14px; }
.att-nopreview .btn { display: inline-flex; }
/* library: the download is a button, always there */
.fv-dl { position: static; opacity: 1; display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 9px 0 7px; border-radius: 8px; background: var(--brand-soft); color: var(--brand); font: 600 12px var(--font-body); flex: none; align-self: center; }
.fv-dl:hover { background: var(--brand-fill); color: #fff; }
.fv-dl span { display: none; }
.fv-card:hover .fv-dl { opacity: 1; }
.fv-list .fv-dl span { display: inline; }
.fv-list .fv-dl { opacity: 1; }
/* hover card: a download on every line and one for all */
.pf-row { grid-template-columns: auto minmax(0, 1fr) auto auto; grid-template-areas: "g n v d" "g m m d"; }
.pf-row .pf-dl { grid-area: d; display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border-radius: 7px; color: var(--brand); background: var(--brand-soft); }
.pf-row .pf-dl:hover { background: var(--brand-fill); color: #fff; }
.pf-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line-2); }
.pf-foot .btn { height: 28px; padding: 0 9px; }
.peek.peek-files { pointer-events: auto; }
@media (max-width: 700px) {
  .attach-row { grid-template-columns: auto minmax(0, 1fr) auto auto; grid-template-areas: "badge name del dl" "badge meta del dl"; column-gap: 6px; }
  .attach-row .attach-del { width: 30px; height: 30px; align-self: center; }
  .attach-row .attach-del:empty { display: none; }
  .attach-row .dl-btn { height: 40px; width: 40px; padding: 0; justify-content: center; }
  .attach-row .dl-btn span { display: none; }
  .ic-viewer-acts .btn { height: 36px; min-width: 36px; padding: 0 9px; }
  .fv-dl { height: 36px; min-width: 36px; }
  .fv-dl span { display: none; }
}


/* ---- phones: the chrome is solid. This rule sits last so no later glass
       block can put a blur back under a fixed bar or a sticky header. ---- */
@media (max-width: 700px) {
  .board-head, .rail, .sidebar, .popover, .modal, .peek, .peek.peek-card, .chat-panel {
    -webkit-backdrop-filter: none; backdrop-filter: none; background: var(--surface-1);
  }
  .rail { contain: layout paint; }
  .rail-btn, .avatar-btn { transition: none; }
  .rail-btn::before { transition: transform var(--dur-micro-2) var(--ease-out); }
}


/* ============================================================ phase-49 phones
   Last pass: touch scrolls, empty viewers collapse, reports scroll sideways,
   the table layout drops its empty summary strip, landscape stays usable. */
.dw-head { touch-action: pan-y; }
.dw-grip { touch-action: none; padding: 4px; margin: -4px; }
.audit-doc section { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.audit-table { min-width: 560px; }
@media (max-width: 700px) {
  .dash-hint { display: none; }
  .dash-bar { justify-content: flex-end; margin-bottom: 10px; }
  .summary-row { display: none; }
  .ic-viewer:has(.ic-viewer-empty) { min-height: 0; padding: 18px 0 6px; }
  .ic-viewer:has(.ic-viewer-empty) .ic-viewer-empty { flex-direction: row; gap: 10px; text-align: left; }
  .ic-viewer:has(.ic-viewer-empty) .fglyph { width: 22px; height: 26px; }
  .ic-viewer:has(.ic-viewer-empty) p { font-size: 12.5px; }
  .audit-bar { flex-wrap: wrap; gap: 6px; }
  .audit-doc { padding: 18px 14px 60px; }
  .audit-doc h1 { font-size: 30px; }
}
@media (max-width: 900px) and (max-height: 440px) {
  .board-head { padding-top: 4px; padding-bottom: 0; }
  .board-title-row { min-height: 36px; }
  .board-name, .board-name.tap { font-size: 17px; }
  .board-switch-row { margin-top: 0; gap: 4px; }
  .view-tabs { padding-bottom: 2px; }
  .board-tools .btn, .board-search, #qf-btn, .board-tools .btn.primary { min-height: 34px; height: 34px; }
  .gjump { display: none; }
  :root { --bar-h: calc(46px + env(safe-area-inset-bottom, 0px)); }
  .rail-btn, .avatar-btn { height: 46px; }
  .rail-btn .ic { width: 18px; height: 18px; }
}
.add-sheet .add-item-input.in-sheet { height: 40px; padding: 0 12px; border-radius: 9px; background: var(--surface-1); border: 1px solid var(--line-strong); font-size: 14px; }
.add-sheet .edit-row { justify-content: flex-end; }


/* ============================================================ defect-4
   A controlled document must never show a year-less date, so every
   calendar date now carries its year. Only the timeline's day axis is an
   exception: a year on each 18px day cell would be noise, so the axis is
   dated once in its corner label instead (each bar's tooltip still spells
   both endpoints out in full). */
.tl-corner { display: flex; align-items: baseline; gap: 7px; }
/* the year is never the part that gets squeezed out of the 220px corner: the
   title shrinks and ellipses instead (.tl-day and .ch-time are flex: none for
   the same reason). The flex box also cancels .tl-label's own text-overflow,
   so the title carries it on this inner span. */
.tl-corner-title { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tl-years {
  flex: none;
  font: 500 11px var(--font-mono); letter-spacing: 0; text-transform: none;
  color: var(--text-3); font-variant-numeric: tabular-nums; white-space: nowrap;
}
@media (max-width: 900px) {
  /* 130px of corner cannot hold a title and a year side by side */
  .tl-corner { display: block; }
  .tl-years { display: block; }
}

/* A cross-year range is up to twelve characters longer than the old
   "Dec 30 - Jan 2". Measured in the browser at 11px/600 Alioro Sans: the
   worst pair of wide month names, "Dec 28, 2025 - Sep 30, 2026", is 171.1px
   against the 169px a 170px timeline column leaves inside its border, so the
   pill trades two pixels of padding a side rather than let the cell shave its
   year off. Same-year ranges keep more than 30px of slack either way. */
.board-table td.cell .timeline-pill { padding-left: 8px; padding-right: 8px; }

/* A partial result is not a success: a restore that could not bring every file
   back, or a duplicate that could not copy one, wears the amber signal. */
.toast.warn { background: var(--amber); color: #1c1e22; }

/* ---- an empty pane is never a dead end (phase-51) --------------------------
   "Board not found" used to be a heading and a sentence: the rail survived
   but the pane offered no route back, and on a phone the drawer is closed.
   Every empty state may now carry labeled, always-visible controls. */
.empty-acts { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: var(--sp-4); }
.nf-page { margin: 0 auto; }
.nf-path { font-family: var(--font-mono); font-size: .95em; color: var(--text-2); }

/* ---- the review schedule, over the whole population (phase-51) -------------
   The old bar divided by the DATED items only, so a register with 70 review
   dates on 487 documents read "99% on schedule" while 417 documents had no
   date at all. One bar now carries three segments over every document in
   scope. The colours are DATA chips (DESIGN_TOKENS §0b) set inline from
   SD.PALETTE - green "current", red "high risk", grey "not applicable" -
   never a logo hue, so this block only owns the geometry. */
.sch-bar {
  display: flex; gap: 2px; height: 10px; margin: 6px 0 8px;
  border-radius: 5px; overflow: hidden; background: var(--wash);
}
/* widths are exact percentages of the whole population; flex-shrink absorbs
   the 2px gaps, and a single overdue document in five hundred still paints */
.sch-seg { display: block; height: 100%; min-width: 3px; border-radius: 2px; }
.sch-key { display: flex; flex-wrap: wrap; gap: 3px 14px; margin-bottom: 10px; font-size: 12px; color: var(--text-2); }
.sch-k { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; }
.sch-k b { color: var(--text-1); font-weight: 700; font-variant-numeric: tabular-nums; }
.sch-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }

/* ---- the board title is the page's h1 (phase-51) --------------------------
   It was an h2 with no h1 above it, and on a phone a role="button" heading is
   not a heading at all. The heading is now an h1 with a real button inside on
   phones; the button inherits the title's type so nothing moves. */
.board-name > .board-tap {
  font: inherit; letter-spacing: inherit; color: inherit; text-align: left;
  background: none; border: 0; padding: 0; margin: 0; min-width: 0; max-width: 100%;
  display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* groundskeeping (phase-135): the `.board-tap > .ic` rules (here, phase-112, phase-116) were removed; the chevron has lived inside `.p131-tail` since phase-131, so no `.ic` is a direct child of the button. */

/* ═══ phase-54 — the log tells the story, the views explain themselves ═══
   Appended after the "chrome is solid" block (line ~3171); nothing here sets
   backdrop-filter, so that block keeps the last word on phone chrome. */

/* ---- activity: what a value WAS and what it BECAME ------------------------
   The row used to read "changed Status" and stop there. Old value quiet, new
   value in ink, so the eye lands on the state the board is in now. */
.act-was { color: var(--text-2); }
.act-now { color: var(--text-1); font-weight: 650; }
.act-auto {
  display: inline-flex; align-items: center; gap: 4px; margin-left: 7px;
  padding: 1px 7px 1px 5px; border-radius: 999px; vertical-align: 1px;
  font-size: 10.5px; font-weight: 600; letter-spacing: .01em;
  color: var(--text-2); background: var(--wash);
}
.act-auto > .ic { color: var(--brand); flex: none; }

/* ---- activity filters: labeled, always visible, keyboard reachable ------ */
.actf-bar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px; margin: 14px 0 10px; }
.actf-search, .actf-sel { display: flex; flex-direction: column; gap: 4px; margin: 0; font-weight: 400; min-width: 0; }
.actf-lab { font-size: 11px; font-weight: 600; letter-spacing: .02em; text-transform: uppercase; color: var(--text-3); }
.actf-search { flex: 1 1 190px; }
.actf-field {
  display: flex; align-items: center; gap: 6px; height: 34px; padding: 0 8px 0 10px;
  border-radius: var(--r-2); border: 1px solid var(--border-soft); background: var(--surface-1); color: var(--text-3);
}
.actf-field > .ic { flex: none; }
.actf-search input {
  display: block; flex: 1; min-width: 0; width: 100%; height: 100%; margin: 0; padding: 0;
  border: none; background: transparent; box-shadow: none; outline: none; color: var(--text-1);
}
.actf-search input::-webkit-search-cancel-button { cursor: pointer; }
.actf-sel { flex: 0 1 auto; }
.actf-sel select {
  display: block; width: auto; max-width: 190px; height: 34px; margin: 0; padding: 0 8px;
  border-radius: var(--r-2); border: 1px solid var(--border-soft); background: var(--surface-1); color: var(--text-1);
}
.actf-field:focus-within, .actf-sel select:focus-visible {
  outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent);
}
.actf-bar .btn { height: 34px; flex: none; }
.actf-count { margin: 0 0 6px; font-size: 12px; color: var(--text-2); font-variant-numeric: tabular-nums; }
@media (max-width: 700px) {
  .actf-bar { gap: 8px; }
  .actf-search { flex: 1 1 100%; }
  .actf-sel { flex: 1 1 calc(50% - 4px); }
  .actf-sel select { max-width: none; width: 100%; }
  .actf-field, .actf-sel select, .actf-bar .btn { height: 40px; }
  .actf-bar .btn { flex: 1 1 100%; }
}

/* ---- calendar: an empty month says why it is empty ---------------------- */
.cal-gap {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 4px 0 10px; padding: 8px 12px; border-radius: 10px;
  background: var(--wash); color: var(--text-2); font-size: 12.5px; line-height: 1.5;
}
.cal-gap .btn { flex: none; }
@media (max-width: 700px) { .cal-gap { font-size: 12px; padding: 8px 10px; } .cal-gap .btn { height: 40px; } }

/* ---- phone card: the identifying fields lead ---------------------------- */
@media (max-width: 700px) {
  /* the identifier reads as a code, so it is set in mono like the rest of the
     product's numbers; the field label above it keeps its own type */
  .rcard-cell[data-ident] { font-family: var(--font-mono); font-size: 12.5px; letter-spacing: -.01em; }
  .rcard-cell[data-ident] .cell-text { max-width: 150px; }
}

/* ---- a filtered board shows results, not empty headers ------------------ */
.board-nomatch {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px;
  margin: 28px auto; padding: 18px 20px; max-width: 520px; text-align: center;
  border: 1px dashed var(--line-1); border-radius: 14px;
  color: var(--text-2); font-size: 13.5px;
}
.board-nomatch > .ic { color: var(--text-3); flex: none; }
@media (max-width: 700px) { .board-nomatch { margin: 20px 0; } .board-nomatch .btn { height: 40px; flex: 1 1 100%; } }

/* ---- light-theme legibility (measured, phase-54) -------------------------
   1. The approvals widget's micro-text IS the finding ("no reviewer, no
      approver"), not decoration: 11px mono in --text-3 measured 5.95:1 on the
      white card in light and 6.36:1 in dark. --text-2 takes it to 9.32:1 and
      9.83:1 without touching the type or the layout.
   2. A group bar fills with the group's DATA colour, and grey #c4c4c4 on the
      --wash track measured 1.58:1 in light — a full bar that reads as an
      empty track. The palette and the group colour are fixed, so the FILL
      gets an edge instead: a 1px inset ring 38% toward --text-1, composited
      over whatever hue the fill carries. Grey goes 1.58 -> 3.44:1 in light,
      and the worst of all twelve chips is 3.10:1 light / 4.17:1 dark, over
      the 3:1 that WCAG 1.4.11 asks of a non-text object. */
.mini-item .mi-meta { color: var(--text-2); }
.wl-fill { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text-1) 38%, transparent); }


/* ============================================== the register states what it has
   1. Revision chronology (defect 6). Two thirds of it already sit on every
      document: the version tag the register installer copied off the source
      label ("QAF v3") and the file's own date. The third, the reason for
      change, is inside the PDF and no stdlib parser reaches it. So the panel
      shows what exists and names the date "File date" on EVERY row: a file
      date is not an approval date, and the register's Revised column stays
      empty until a document states one. Nothing here is inferred.
   2. Colliding automations (defect 9): a labelled, always-visible marker on
      the rows, so the owner can see which rule wins before deleting one.
   Appended after the "chrome is solid" block; nothing here sets
   backdrop-filter, blurs a card or a row, or animates anything but a colour. */
.docrev-note, .docrev-none {
  display: flex; align-items: flex-start; gap: 7px; margin: 0;
  padding: 8px 10px; border-radius: 9px; background: var(--wash);
  color: var(--text-2); font-size: 12px; line-height: 1.5;
}
.docrev-note > .ic, .docrev-none > .ic { flex: none; margin-top: 2px; color: var(--text-3); }
.docrev-list { list-style: none; margin: 6px 0 0; padding: 0; display: flex; flex-direction: column; }
.docrev-row {
  display: grid; align-items: center; column-gap: 8px; row-gap: 2px;
  grid-template-columns: 14px auto minmax(0, 1fr) auto;
  grid-template-areas: "line ver when tag" "line file file file";
  padding: 6px 6px; border-radius: 9px;
}
.docrev-row:hover { background: var(--hover-ground); }
.docrev-row.viewing { background: var(--brand-soft); }
.docrev-row.viewing .docrev-file { color: var(--brand); }
.docrev-line { grid-area: line; position: relative; align-self: stretch; }
.docrev-line::before { content: ""; position: absolute; left: 6px; top: 0; bottom: 0; width: 1px; background: var(--line-2); }
.docrev-line::after {
  content: ""; position: absolute; left: 2px; top: 50%; margin-top: -4px; width: 9px; height: 9px;
  border-radius: 50%; background: var(--surface-1); box-shadow: inset 0 0 0 2px var(--line-strong);
}
.docrev-row:first-child .docrev-line::before { top: 50%; }
.docrev-row:last-child .docrev-line::before { bottom: 50%; }
.docrev-latest .docrev-line::after { box-shadow: inset 0 0 0 3px var(--brand); }
.docrev-ver {
  grid-area: ver; font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: 600;
  color: var(--text-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 120px;
}
.docrev-untagged { font-weight: 500; color: var(--text-2); }
.docrev-when {
  grid-area: when; display: inline-flex; align-items: baseline; gap: 5px; min-width: 0;
  font-family: var(--font-mono); font-size: 10.5px; color: var(--text-2); white-space: nowrap;
}
/* the label IS the fix: it says which date this is, so it reads at --text-2
   like every other micro-label the owner has to be able to trust */
.docrev-dk { text-transform: uppercase; letter-spacing: var(--ls-caps); font-size: 9.5px; color: var(--text-2); }
.docrev-file {
  grid-area: file; justify-self: start; max-width: 100%; margin: 0; padding: 0; text-align: left;
  background: none; border: none; cursor: pointer; font-size: 12px; color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.docrev-file:hover { color: var(--brand); }
.docrev-tag {
  grid-area: tag; justify-self: end; font-family: var(--font-mono); font-size: 9.5px;
  text-transform: uppercase; letter-spacing: var(--ls-caps); color: var(--brand);
  background: var(--brand-soft); border-radius: 999px; padding: 2px 7px; white-space: nowrap;
}
.docrev-row.viewing .docrev-tag { background: var(--surface-1); }
@media (max-width: 700px) {
  .docrev-row {
    grid-template-columns: 14px minmax(0, 1fr) auto;
    grid-template-areas: "line ver tag" "line when when" "line file file";
    row-gap: 3px; padding: 8px 6px;
  }
  .docrev-when { white-space: normal; }
  .docrev-file { font-size: 12.5px; min-height: 22px; }
  .docrev-ver { max-width: none; }
}

/* the collision marker: a labelled band inside the rule's own sentence, always
   visible (never a hover tooltip), stating in words which rule wins and which
   is skipped. Tone is a UI signal, so it uses the logo hues through --ok and
   --warn; the icon and the wording carry the meaning without the colour. */
.auto-clash {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 10px 12px; border-radius: 10px; line-height: 1.5; font-size: 12.5px;
  background: var(--warn-soft); color: var(--text-1);
  border: 1px solid color-mix(in srgb, var(--warn) 34%, transparent);
}
.auto-clash > .ic { flex: none; margin-top: 1px; color: var(--warn); }
/* a marked row grows two lines taller, so its controls stop floating in the
   middle of the band and sit level with the rule's own sentence */
.auto-row.auto-clashed { align-items: flex-start; border-color: color-mix(in srgb, var(--warn) 40%, transparent); }
.auto-row.auto-clashed .auto-runs, .auto-row.auto-clashed .switch { margin-top: 6px; }
.auto-flag {
  display: flex; align-items: flex-start; flex-wrap: wrap; gap: 4px 6px;
  margin-top: 7px; padding: 7px 9px; border-radius: 9px;
  font-size: 12px; line-height: 1.5; color: var(--text-1);
  background: var(--wash); border-left: 3px solid var(--line-strong);
}
.auto-flag > .ic { flex: none; margin-top: 2px; }
.auto-flag b { font-weight: 600; }
.auto-flag.win { background: var(--ok-soft); border-left-color: var(--ok); }
.auto-flag.win > .ic { color: var(--ok); }
.auto-flag.lose, .auto-flag.dup { background: var(--warn-soft); border-left-color: var(--warn); }
.auto-flag.lose > .ic, .auto-flag.dup > .ic { color: var(--warn); }
.auto-mark {
  flex: none; min-width: 17px; height: 17px; padding: 0 4px; border-radius: 5px;
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 10.5px; font-weight: 600; letter-spacing: 0;
  background: var(--surface-1); color: var(--text-1); box-shadow: inset 0 0 0 1px var(--line-strong);
}
.auto-age {
  font-family: var(--font-mono); font-size: 10.5px; color: var(--text-2); white-space: nowrap;
}
@media (max-width: 700px) {
  .auto-clash, .auto-flag { font-size: 12.5px; }
  .auto-flag { padding: 8px 10px; }
  .auto-row { flex-wrap: wrap; }
  .auto-row .auto-text { flex: 1 1 100%; order: 2; }
  .auto-row .auto-ic { order: 1; }
  .auto-row .auto-runs { order: 1; margin-right: auto; }
  .auto-row .switch { order: 1; }
  .auto-row .icon-btn { order: 1; }
}

/* ==================================================================
   Accessibility foundations. Appended LAST on purpose: these are
   utilities and two overrides, and nothing here sets backdrop-filter,
   so the "phones: the chrome is solid" block above still wins.
   ================================================================== */

/* Out of sight, still in the accessibility tree. There was no visually
   hidden utility in this file at all, so a caption that only a screen
   reader needs had nowhere to live. `.sr-only-focusable` is the skip-link
   variant: hidden until it takes the focus, then a real, visible control. */
.sr-only,
.sr-only-focusable:not(:focus):not(:focus-within) {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px; border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}
.sr-only-focusable:focus, .sr-only-focusable:focus-within {
  position: fixed; top: 12px; left: 12px; z-index: 141;
  width: auto; height: auto; margin: 0; padding: 10px 16px;
  overflow: visible; clip: auto; clip-path: none; white-space: nowrap;
  border-radius: var(--r-3); background: var(--surface-1); color: var(--text-1);
  box-shadow: var(--elev-3), var(--focus-ring); font-weight: 600; font-size: 13.5px;
  text-decoration: none;
}

/* Toasts shared z-index 120 with .palette-scrim and lost on DOM order, so
   every toast raised while the command palette was open was invisible behind
   the scrim. Above the palette (121), below the tooltip (130). */
#toasts { z-index: 126; }
/* the assertive region is a nested column, so the two politenesses stack as
   one list; empty, it must not spend the parent's 8px gap */
#toasts-alert { display: flex; flex-direction: column; gap: 8px; align-items: center; }
#toasts-alert:empty { display: none; }

/* A route change hands the focus to the new page (SD.focusMain / SD.focusPage)
   so a keyboard does not re-walk the chrome after every move. A mouse sees
   nothing; a keyboard gets the ring it expects. */
#main:focus, #root:focus { outline: none; }
#main:focus-visible, #root:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; }


/* ══════════════════════════════════════════════════════════════════════════
   The markup the four JS passes landed, and the defects this file was
   carrying. Appended after the "phones: the chrome is solid" block on
   purpose: nothing below sets backdrop-filter, so that block still has the
   last word on phone chrome.
   ══════════════════════════════════════════════════════════════════════════ */

/* ---- board: real headings and real buttons where spans used to sit ------- */
/* the group title is an h2 now, and the shared h1..h5 rule would spend .4em
   of margin under every group band */
.group-title { margin: 0; }
/* a <button> brings a background, a border and its own font. Only the first
   two are cleared: `font: inherit` here would drop the mono caps that ARE the
   column-head voice. */
.col-head { background: none; border: 0; }
/* the active tab and its menu share one box, so the menu can position against
   the pair instead of against the link it would otherwise overlap */
.vt-wrap { position: relative; display: inline-flex; align-items: center; }
.vt-menu { background: none; border: 0; padding: 0; }
.ws-dots { background: none; border: 0; cursor: pointer; }
.ws-dots:focus-visible { opacity: 1; }
/* the workspace row is no longer the control; the dots button is */
.side-title.ws { cursor: default; }
@media (hover: none) { .ws-dots { opacity: 1; } }
/* a view that cannot be edited must not offer the editing cursor */
.board-table.view-only td.cell { cursor: default; }
/* the global ring at :1345 covers a, button and [contenteditable] only, and a
   cell is a td. Inset, because td.cell clips its overflow. */
.board-table td.cell:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
/* arrow keys walk focus cell to cell, and on a register scrolled sideways a
   cell in a left-hand column can come to rest under the sticky name column:
   the browser counts it as in view, so the ring is painted where nobody can
   see it and the value is edited blind. The scroll margin is that column's
   own min-width (:771), so focus scrolls the cell clear of it. Under 701 the
   name column is static (:1780) and there is nothing to clear. */
/* UPDATED 2026-09-20 (D28 step 8): 260px was a THIRD copy of a number that
   has moved 260 -> 320 -> 380, and it was 120px short of the column it is
   meant to clear. --name-col-w is the MEASURED width, written onto every
   table by observeNameCol (board.js:1046). Note what this does NOT do: a
   scroll margin only shifts the offset of a scroll the browser already
   performs, so it cannot make one happen — that is revealCell's job in
   board.js, and the two answer different halves of defect 46. */
@media (min-width: 701px) { .board-table td.cell { scroll-margin-left: var(--name-col-w, 380px); } }

/* ---- library: the card's contents moved inside one button ---------------- */
/* the button is transparent to the layout: it inherits the card's gap and
   alignment so grid and list read exactly as they did as a bare div */
.fv-body {
  display: flex; gap: inherit; align-items: inherit; flex: 1 1 auto; min-width: 0;
  background: none; border: 0; padding: 0; margin: 0; font: inherit; color: inherit;
  text-align: left; cursor: pointer;
}
.fv-body:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: 9px; }
/* list mode: the button adopts the row's own tracks, so the header strip,
   the thumbnail, the name, the revision and the kind still line up column by
   column with the button sitting between them and the row */
.fv-list .fv-card > .fv-body { grid-column: 1 / 9; display: grid; grid-template-columns: subgrid; align-items: center; gap: 12px; }
@media (max-width: 900px) {
  /* three tracks, no subgrid: the button is the thumbnail and the text */
  .fv-list .fv-card > .fv-body { grid-column: 1 / 3; display: flex; align-items: center; gap: 12px; }
}

/* ---- kanban, calendar, timeline ----------------------------------------- */
.kanban-card { display: block; width: 100%; text-align: left; font: inherit; color: inherit; }
.kanban-card-name { display: block; }
.kanban-card:focus-visible, .tl-bar:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
/* the calendar's cells sit edge to edge on a 1px grid line: an outset ring
   would paint over the two neighbours, so this one sits inside */
.cal-cell:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; }
/* one hairline for "now" down every row, not just a tinted header cell */
.tl-now { position: absolute; left: 0; top: -6px; bottom: -6px; width: 1px; background: var(--brand); opacity: .55; pointer-events: none; }
.tl-month { overflow: hidden; white-space: nowrap; }
/* a bar that starts or ends outside the window loses that corner, so the cut
   reads as "this continues" rather than as the real start of the range */
.tl-cut-l { border-top-left-radius: 0; border-bottom-left-radius: 0; }
.tl-cut-r { border-top-right-radius: 0; border-bottom-right-radius: 0; }
/* A cross-year window label is longer than the 15px phone heading allows for,
   so it needs a floor - but as a plain rule it also beat the phone's own
   `.cal-head h3 { min-width: 0 }` at :3133, and .tlw-range IS that h3. At
   375px the head is a no-wrap flex row of prev, label, next, Today, Full
   range and a note, and a 210px floor pushed it off the screen. The floor is
   a desktop floor; the media query says so instead of !important. */
@media (min-width: 701px) { .cal-head .tlw-range { min-width: 210px; } }
.kpi-retry { background: none; border: 0; padding: 0; font: inherit; color: var(--brand); cursor: pointer; text-decoration: underline; }
@media (hover: none) {
  /* no hover means no title tooltip: a narrow bar says its name beside itself */
  .tl-bar.narrow { color: var(--text-1); overflow: visible; }
  .tl-bar.narrow > span { position: absolute; left: calc(100% + 6px); top: 50%; transform: translateY(-50%); max-width: 46vw; overflow: hidden; text-overflow: ellipsis; }
}

/* ---- panels, chat, the loaders ------------------------------------------ */
.switch input:focus-visible + span { box-shadow: var(--focus-ring); }
/* the delete is reachable without a pointer */
.ch-msg:focus-within .ch-del, .ch-del:focus-visible { opacity: 1; }
/* a pane that half-loaded says so on the line, in the warn signal */
.ld-err-line {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 10px 0;
  padding: 10px 12px; border-radius: var(--r-2); background: var(--surface-2);
  border: 1px solid var(--line-2); font-size: 13px; color: var(--text-2);
}
.ld-err-line > .ic { color: var(--warn); flex: none; }
.ld-err-line > span { flex: 1; min-width: 0; }
.empty-state.ld-err h3 { display: inline-flex; align-items: center; gap: 8px; }
.empty-state.ld-err h3 > .ic { color: var(--warn); }
/* the submit button wears the five dots at button scale, on one line */
.btn.is-busy .loading { padding: 0; flex-direction: row; gap: 8px; font-size: inherit; color: inherit; }
.btn.is-busy .load-dots { height: 12px; gap: 6px; }

/* ---- the auth page reads in light theme --------------------------------- */
/* the side panel's ground is --ink (#141a2b) in BOTH themes, and light
   --text-1 is the same #141a2b: the status chips measured about 1.2-1.6:1.
   --on-ink measures 10.07:1 at worst (the yellow chip) and 12.83:1 at best. */
.auth-board .lb-status { color: var(--on-ink); }
/* the error line is 13px/600 on white: --danger (#eb2e26) measured 4.24:1,
   under the 4.5 that WCAG 1.4.3 asks. The logo red taken 14% toward black
   measures 4.79:1 and stays in the same vivid family. Dark already passes at
   5.03:1 (#ff4f47 on #1d2025), so only light moves. */
.auth-error { color: color-mix(in srgb, var(--logo-1) 86%, #000); }
[data-theme="dark"] .auth-error { color: var(--danger); }

/* ---- a long modal title keeps out of the close button's lane ------------- */
/* .modal-x is absolute at 12px and about 34px wide; at 375px the snapshot and
   restore headings ran straight under it */
.modal > h3:first-of-type, .modal > .modal-head > h3 { padding-right: 36px; }

/* ---- the status donut rests on phones (the rule at :2697 was defeated) --- */
/* :2697 said animation:none inside @media (max-width:700px); the later plain
   .ring-svg at :2840 put ringIn straight back. Re-stated after it. */
@media (max-width: 700px) { .ring-svg { animation: none; } }

/* ---- 701-1000px: the board head fits inside its own background ----------- */
/* .board-switch-row does not wrap and .board-filter will not shrink, so
   between 701 and 1000 the row scrolled sideways out of .board-head's glass
   and the Filter, search and New-item controls floated on the page ground.
   No gap is declared here: the base, the phone block and the landscape block
   each own their own rhythm and none of them should be overridden. */
@media (min-width: 701px) and (max-width: 1000px) {
  .board-switch-row { flex-wrap: wrap; }
  .view-tabs { overflow-x: auto; max-width: 100%; scrollbar-width: none; }
  .view-tabs::-webkit-scrollbar { display: none; }
  .board-tools { margin-left: 0; flex: 1 1 100%; }
  .board-search { flex: 1 1 auto; min-width: 0; }
  .board-search .board-filter { flex: 1; width: 100%; min-width: 0; }
}

/* ---- 701-900px: a panel over the board is not a panel beside it ---------- */
/* .main only makes room at 901px (Claude) and 1280px (chat), so at 768 both
   panels floated over a board that stayed clickable underneath. A scrim, not
   a shift: shifting would leave 208px of board under the 560px chat panel,
   which is narrower than a phone. The rail is left uncovered on purpose -
   above 700px the rail button is how a panel is closed, exactly as it is at
   1440px, and .drawer-scrim has spared the rail since phase-30. */
@keyframes panelScrimIn { from { opacity: 0; } to { opacity: 1; } }
@media (min-width: 701px) and (max-width: 900px) {
  body.ai-open .app::after, body.chat-open .app::after {
    content: ""; position: fixed; inset: 0 0 0 56px; z-index: 49;
    background: var(--scrim); animation: panelScrimIn var(--dur-micro-3, .2s) var(--ease-out) backwards;
  }
}

/* ---- phones: a sheet gets a real scrim ---------------------------------- */
/* the old one was `box-shadow: 0 0 0 100vmax var(--scrim)`, which paints but
   cannot be hit: a tap "outside" closed the sheet AND landed on the board row
   under it. This is an element, like .modal-overlay, so it swallows the tap.
   Below .popover (90), above the phone bottom bar (60). */
.sheet-scrim { position: fixed; inset: 0; z-index: 89; background: var(--scrim); opacity: 0; transition: opacity .18s; }
.sheet-scrim.show { opacity: 1; }

/* ---- phones: touch targets ---------------------------------------------- */
/* phase-44 set 44px as the target and these were left between 18 and 36.
   Scoped to portrait: a phone in landscape is caught by the compaction block
   at :3232, and 44px targets are exactly what that block exists to undo. */
@media (max-width: 700px) and (min-height: 441px) {
  .qf-chip { height: 44px; padding: 0 13px 0 11px; }
  .gjump-chip { height: 44px; padding: 0 13px 0 11px; }
  .gjump { padding-bottom: 12px; }
  .view-tab { min-height: 44px; padding-top: 0; padding-bottom: 0; }
  .view-tab.active { padding-right: 42px; }
  /* 36 not 44 across: a 44px-wide menu inside the tab would push the view's
     own name off the only tab a phone shows a label on. 36x44 is the trade. */
  .vt-menu { width: 36px; height: 44px; right: 2px; }
  /* nothing grows visually: the button keeps its 36px body and the pseudo
     takes the missing 8px out of the active tab's own 42px right padding, so
     the target is 44 wide without covering the tab's label or its neighbour */
  .vt-menu::after { content: ""; position: absolute; top: 0; bottom: 0; left: -8px; right: 0; }
  /* .view-tabs scrolls at this width and the add button is a flex item in it:
     without this it was shrunk to 26px by the overflow and only looked 44 */
  .view-add { width: 44px; height: 44px; flex: none; }
  .group-caret { width: 44px; height: 44px; }
  .group-head .group-menu { width: 44px; height: 44px; }
  .item-tab { min-height: 44px; }
  .palette-row { min-height: 44px; }
  .fv-dl { height: 44px; min-width: 44px; }
  /* named one by one, not as .icon-btn.sm: that class is worn by controls
     drawn for other rooms, and a blanket 44px inflated the delete on an item's
     file row, the delete over a chat bubble, the stage bar's fold and close
     and the team sheet's two row actions. These are the ones a thumb has to
     find. The board table's own .row-menu stays at the 22px it was tuned to
     at :1784; on a phone the table is cards and the menu is .group-menu. */
  .drawer-close, .board-switch-btn, .board-info, .board-more,
  .dw-menu, .col-add .icon-btn.sm, .ch-back, .pf-clear,
  .member-remove, .lab-remove,
  .icon-btn.sm[data-act="toggle-chat"] { min-width: 44px; min-height: 44px; }
  /* the board tools were tuned to 40px in phase-44; they move together with
     .icon-btn.sm or the toolbar row loses its level */
  .board-head .icon-btn.sm, .board-tools .btn, .board-search, #qf-btn { min-height: 44px; }
  .board-tools .btn.primary { width: 44px; height: 44px; }
  /* .board-switch-btn in the list above is DESKTOP markup: board.js renders the
     caret button beside the title only in the else half of `SD.isPhone() ? ...`,
     so on a phone it does not exist and the rule protecting it protects nothing.
     It stays listed because the head is not re-rendered when a desktop window is
     dragged under 700px (renderBoardBody replaces #board-body only), so the
     desktop control really can be sitting here until the next full render.
     What a thumb hits on a phone is button.board-tap inside the h1, and it
     measured 157.8x24.5. The h1 is the inline-flex box (:3170) and the button is
     its only item, so the 44px goes on the button and the h1's 2px of vertical
     padding comes off: h1 and button both land on exactly 44, the same height
     .board-info and .board-more already hold in this row, and nothing moves. */
  .board-name.tap { padding-top: 0; padding-bottom: 0; }
  .board-name > .board-tap { min-width: 44px; min-height: 44px; }
}

/* ---- phones: a calendar day opens its own sheet -------------------------- */
/* the item is an 8px dot at this width and still carried open-item, so a tap
   meant for the day opened whichever document the dot happened to be. The
   cell owns the tap; the sheet lists everything on the day. */
@media (max-width: 700px) { .cal-item { pointer-events: none; } }

/* ---- a focused cell clears the sticky board head ------------------------- */
/* Companion to the scroll-margin-left at :3643. .board-head is sticky at the
   top of .main (:664, :639), so a cell the browser scrolls into view at the
   top of the register — arrowing up a column, or Tab landing on the roving
   stop — comes to rest UNDER the head: in view as far as the browser is
   concerned, invisible as far as the user is concerned. --head-h is measured
   from the live head and published on #main by board.js (syncHeadHeight),
   because the head is not one height: it wraps differently at 1440, 768 and
   375, compact density trims it, and adding a view grows the tab row.
   Not width-scoped the way the horizontal rule is: the name column is sticky
   only above 700, but the head is sticky at every width, including the phone's
   real table. The 0px fallback leaves every page that has no head untouched.
   Appended after the "phones: the chrome is solid" block on purpose: nothing
   here sets backdrop-filter, blurs a card or a row, or animates anything. */
.board-table td.cell { scroll-margin-top: var(--head-h, 0px); }

/* ================================================================
   A REMOVED TEAMMATE KEEPS THEIR NAME (phase-60)
   ----------------------------------------------------------------
   Someone who has written anything is retired, not deleted: their
   chat lines, item updates and Claude threads keep their real name
   so no conversation develops a hole. The name therefore stays at
   full strength — fading it would be the one thing this feature
   must not do — and the fact of the removal is carried by a chip
   that says the word, beside a .sr-only sentence that says it in
   full. It is a state label, so it borrows the vocabulary the
   member list already uses for online/away (mono, uppercase,
   --text-2 on --surface-2) rather than a colour: this is not an
   alarm, and colouring it would either spend a logo hue on data or
   a data chip on chrome. Both palettes stay where they belong.
   Appended after the "phones: the chrome is solid" block on
   purpose: nothing here sets backdrop-filter, blurs a card or a
   row, or animates anything.
   ================================================================ */
.rm-tag {
  display: inline-flex; align-items: center; flex: none;
  height: 16px; padding: 0 6px; border-radius: var(--r-round);
  border: 1px solid var(--line-1); background: var(--surface-2);
  font-family: var(--font-mono); font-size: 10px; font-weight: 500;
  letter-spacing: .05em; text-transform: uppercase; color: var(--text-2);
  white-space: nowrap; vertical-align: baseline;
}
/* Each of these containers styles its own descendants by element
   (`.ch-meta span`, `.ch-text span`, `.tp-row .tp-text span`, all of them more
   specific than a bare `.rm-tag`), so the chip has to be named inside each one
   or it is handed back the --text-3 timestamp styling that undoes the label. */
.ch-meta .rm-tag { font-family: var(--font-mono); font-size: 10px; color: var(--text-2); }
/* Inside a running subtitle line ("Dana [removed]: on it") the chip needs the
   word-space a flex gap gives it elsewhere, and it is trimmed: that line is
   11.5px, and a 16px pill in it would push the channel row taller than the
   30px avatar beside it, so only the rows whose last message happens to be
   from a removed teammate would stand a few pixels proud of their neighbours. */
.ch-text .rm-tag,
.tp-row .tp-text .rm-tag {
  font-family: var(--font-mono); color: var(--text-2);
  height: 14px; padding: 0 5px; font-size: 9.5px; margin-left: 5px;
}
/* name + chip + timestamp is one flex line that did not have to wrap before
   the chip existed. At 375px a long name can now fill it, and without this the
   time is pushed out of the panel instead of dropping to the next line. */
.ch-meta, .update-head { flex-wrap: wrap; row-gap: 2px; }


/* ================================================================
   LIGHT THEME STOPS BEING THE WEAKER ONE (phase-61)
   ----------------------------------------------------------------
   Two measured gaps from DESIGN_DIRECTION.md §5, both on the busiest
   screen, both fixed the way the product already fixes this class of
   problem: colour moves to an EDGE instead of being poured into a
   region.

   1. THE GROUP BAND. The tinted strip that names each group measured
      1.03 to 1.11 against the page in light (yellow 1.03, grey 1.03,
      navy 1.11) where dark runs 1.11 to 1.48. Raising the tint does
      not rescue it: a pale colour on a pale ground has no brightness
      left to give, and yellow at 28% still only reaches 1.12. So the
      group's colour moves to a 3px spine down the left of the band at
      the chip's FULL strength, carrying the same 38% ink hairline
      phase-54 used to rescue the pale chips on the distribution bar.
      Measured against the page in light, all twelve DATA chips clear
      3:1 (weakest yellow 3.14, grey 3.46, lime 3.56); the raw chip
      alone would have left yellow at 1.40, which is why the hairline
      is not optional. DARK takes the spine WITHOUT the hairline: its
      --gc is already lifted 66% toward white and clears 5:1 unaided
      (weakest navy 5.12), and an ink ring drawn in dark's --text-1
      would be a WHITE ring, which is the opposite of the device.
      The tints themselves are untouched in both themes.
   2. THE COLUMN HEADER STRIP. 1.07 against the white table body, which
      is under the floor where an eye registers a boundary at all, so a
      19-column register read as one field with no head. It is now the
      full --surface-2, the inset step of the light ramp, instead of 55%
      of it: no new grey, one honest step, 1.14. DESIGN_DIRECTION asks
      for about 1.20 and 1.20 is NOT AVAILABLE: the sorted column head
      is 11px --brand on this strip, and --brand (#0070c1) measures
      4.50:1 on --surface-2 and 4.29:1 on the 1.20 strip, which would
      put the one coloured word in the header row under the 4.5 AA
      floor for small text. The floor wins. Dark keeps today's value.

   Appended after the "phones: the chrome is solid" block on purpose:
   nothing here sets backdrop-filter, blurs a card or a row, or
   animates anything.
   ================================================================ */
:root {
  --spine-edge: color-mix(in srgb, var(--text-1) 38%, transparent);
  --th-strip: var(--surface-2);
}
[data-theme="dark"] {
  --spine-edge: transparent;
  --th-strip: color-mix(in srgb, var(--surface-2) 55%, var(--surface-1));
}

/* the band's own colour, on its edge. --gc is set on .group-head (raw chip in
   light, 66% toward white in dark) and custom properties inherit into ::before.
   Since phase-67-c the band has no fill and no radius, so the spine is the
   whole of the group's colour on this line and runs its full height. */
.group-head::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--gc); box-shadow: inset 0 0 0 1px var(--spine-edge);
  pointer-events: none;
}

.board-table th, .board-table th.col-name { background: var(--th-strip); }

/* the phone register renders each row as a card with the group's colour on a
   3px spine — the same device, so it takes the same hairline, or light theme
   would be fixed on the desktop and left at 1.40 in the hand. */
@media (max-width: 700px) {
  .rcard::before { box-shadow: inset 0 0 0 1px var(--spine-edge); }
}

/* a viewer's board title is a heading, not a field: without contenteditable it
   must not keep the editable title's hover fill and promise an edit the server
   refuses. The phone title (.tap) is a button and keeps its own feedback. */
.board-name:not([contenteditable="true"]):not(.tap):hover { background: none; }

/* ================================================================
   VIEW ONLY REACHES THE BOARD'S CONTENT (phase-64)

   Phase-63 gated the board's chrome and stopped there, so a viewer
   still met 47 editable names, 893 editable cells and four add
   controls that the server answers 403. The split is the same one
   that phase-63 set: a control that exists only to make something
   is absent, a surface opened for other reasons keeps its items in
   grey under one visible line. What is new here is the third shape.
   A cell is not a button and there are nine hundred of them, so the
   reason cannot live in the cell: it is said once, in the sticky
   head, where it is on screen at every scroll position, and the
   cells themselves keep the hover ring, the focus ring, the
   arrow-key walk and the spoken label, and lose only the editor.
   Nothing here changes anything for a member or an admin.

   Appended after the "phones: the chrome is solid" block on
   purpose: nothing here sets backdrop-filter, blurs a card or a
   row, or animates anything.
   ================================================================ */

/* the one line of text that carries the reason, inside the sticky board head */
.vo-bar {
  display: flex; align-items: center; gap: 8px;
  margin: var(--sp-2) 0 0; padding: 6px 10px;
  border-radius: var(--r-2); background: var(--surface-2);
  color: var(--text-2); font-size: var(--fs-xs); line-height: 1.35;
}
.vo-bar svg { flex: none; }
@media (max-width: 700px) { .vo-bar { margin-top: 6px; padding: 6px 8px; } }

/* a group with nothing in it loses its add row for a viewer, and an empty
   table under a filled header band reads as a load that failed rather than
   as an empty group */
.vo-none { margin: 0; padding: 10px 2px; color: var(--text-soft); font-size: var(--fs-xs); }
.vo-none-row td { padding: 12px 16px !important; color: var(--text-soft); font-size: var(--fs-xs); }

/* Text that cannot be edited must not keep the hover fill that promised a
   caret, the same correction phase-63 made on the board title. The hover
   EXPANSION (white-space: normal at :823) is how a clipped name is read to
   the end, so only the background goes. Written against the attribute rather
   than a role class so it also covers the phone's cards, where the group
   title was never editable and was tinting on hover anyway. */
.item-name:not([contenteditable="true"]):hover { background: none; }
.th-label:not([contenteditable="true"]):hover { background: none; }
.group-title:not([contenteditable="true"]):hover { background: none; }

/* the group menu's colours and the column menu's widths are buttons that are
   not .menu-item, so they need the same grey as :1070 to look refused */
.color-dot:disabled { opacity: .4; cursor: default; }
.color-dot:disabled:hover { transform: none; }
.chip-btn:disabled { opacity: .4; cursor: default; }
.chip-btn:disabled:hover { background: var(--surface-2); border-color: transparent; color: inherit; }

/* ---- phase-64-c: the last three controls a viewer could only fail at ----
   Appended after the "phones: the chrome is solid" block on purpose: nothing
   here sets backdrop-filter, blurs a card or a row, or touches chrome. */

/* the item card's heading is now a plain heading for a viewer, so it loses the
   fill that promised a caret — the same correction :4016 and :4061 made on the
   board title, the row name, the column label and the group title */
.item-card h3:not([contenteditable="true"]):hover { background: none; }

/* an automation switch a viewer cannot move keeps its position, because the
   position IS the rule's state and reading it is the point; what it must not
   keep is the cursor that says it will move */
.switch input:disabled, .switch input:disabled + span { cursor: default; }

/* a dashboard nobody can rearrange must not hold out a grab cursor, and the
   title of a widget becomes text a reader can select instead of a handle */
.dash-ro .dw-head { cursor: default; user-select: text; }

/* ---- phase-65-b: the email confirmation page and its standing mark ------
   Appended after the "phones: the chrome is solid" block on purpose: nothing
   here sets backdrop-filter, blurs a card or a row, or animates anything. All
   of it carries the ev- prefix, so none of it can reach app.css's bare
   selectors. The tints are UI signals and therefore the logo hues, which is
   the right palette: this is chrome telling you about your own account, not
   data. */

/* the four answers wear their own mark, and the mark is the only thing that
   differs between them: same card, same type, same controls */
.ev-mark {
  width: 46px; height: 46px; border-radius: 50%; margin-bottom: 14px;
  display: flex; align-items: center; justify-content: center;
}
.ev-mark.ev-ok, .ev-mark.ev-used { background: var(--ok-soft); color: var(--ok); }
.ev-mark.ev-stale { background: var(--warn-soft); color: var(--warn); }
.ev-mark.ev-bad { background: var(--danger-soft); color: var(--danger); }
.ev-acts { display: grid; gap: 10px; margin-top: 22px; }
.ev-acts .auth-alt { margin-top: 0; }

/* the line the resend button writes its answer into. role="status" on an
   element that is display:none announces nothing, so it collapses only while
   it is empty and is shown the moment it has something to say. */
.ev-say { margin-top: 12px; font-size: 13px; line-height: 1.5; font-weight: 600; }
.ev-say:empty { display: none; }
/* 13px/600 needs 4.5:1. Light: --ok (#00a93d) measures 3.0:1 on white, the
   logo green taken 30% toward black measures 5.09:1 and stays in the family;
   dark --ok (#24c25c on #1d2025) is already 6.94:1. The red follows the
   .auth-error recipe at :3772 exactly. */
.ev-say.ok { color: color-mix(in srgb, var(--logo-4) 70%, #000); }
.ev-say.bad { color: color-mix(in srgb, var(--logo-1) 86%, #000); }
[data-theme="dark"] .ev-say.ok { color: var(--ok); }
[data-theme="dark"] .ev-say.bad { color: var(--danger); }

/* the standing panel on the profile card: colour on the edge, not in the
   fill, the same construction as .auto-flag at :3584 */
.ev-note {
  display: flex; align-items: flex-start; gap: 9px;
  margin: 0 0 16px; padding: 11px 13px; border-radius: 10px;
  background: var(--warn-soft); border-left: 3px solid var(--warn);
}
.ev-note-ic { flex: none; margin-top: 1px; color: var(--warn); display: flex; }
.ev-note-body { min-width: 0; }
.ev-note-body b { font-weight: 600; font-size: 13.5px; }
.ev-note-body p { margin: 4px 0 10px; }

/* the quiet mark next to the address, in the account menu and on the profile
   card. 11px/600 on the tinted ground: the logo orange 45% toward black
   measures 5.37:1 in light, --warn on the dark tint measures 6.77:1. */
.ev-chip {
  display: inline-flex; align-items: center; vertical-align: baseline;
  padding: 1px 7px; border-radius: 999px; white-space: nowrap;
  font-size: 11px; font-weight: 600; letter-spacing: .01em;
  background: var(--warn-soft); color: color-mix(in srgb, var(--logo-2) 55%, #000);
  border: 1px solid color-mix(in srgb, var(--warn) 38%, transparent);
}
[data-theme="dark"] .ev-chip { color: var(--warn); }

/* a phone has no hover and no patience for a 28px target: every control this
   feature owns is full width and thumb sized there */
@media (max-width: 700px) {
  .ev-note { flex-direction: column; }
  .ev-note .btn, .ev-acts .btn { width: 100%; min-height: 40px; }
}


/* ============================================================ phase-67-c
   The register becomes one continuously ruled field. The group band's tint is
   gone (DESIGN_DIRECTION §2: colour arrives at an edge, it is never poured
   into a region), the table's rounded box, side borders and drop shadow are
   gone with it, and what separates one group from the next is a RULE, not a
   gap. Group colour is unchanged and still arrives on an edge: the 3px spine
   at :3986 and the distribution bar.

   The line KEEPS ITS HEIGHT. It carries the collapse caret, the inline
   rename, the count and the group menu, and those are 44px at :3831 on
   touch; nothing here changes a padding or a control size, only borders and
   the gaps between sections.

   Appended after the "phones: the chrome is solid" block on purpose: nothing
   here sets backdrop-filter, blurs a card or a row, or animates anything. */

/* The section rule is its own token, one step stronger than --line-1, because
   it now does the finding that the fill used to do while a 487-row register
   scrolls, and it has to out-rank the row grid (--border-soft) it interrupts.
   Measured against the surfaces it actually lies on: light 1.58:1 on the head
   ground and 1.59:1 on the table, dark 1.63:1, where --line-1 would have been
   1.32 and 1.47. Light theme is no longer the weaker one on this line. */
:root { --group-rule: color-mix(in srgb, var(--text-1) 22%, transparent); }
[data-theme="dark"] { --group-rule: rgba(255, 255, 255, .16); }

/* A boundary is ONE line, never two: the rule that closes a group's table is
   the same rule the next group's head stands on, so only the first group
   draws its own top edge and only a table draws the bottom. */
.group-head { border-bottom: 1px solid var(--group-rule); }
.group:first-of-type > .group-head { border-top: 1px solid var(--group-rule); }
.table-wrap { border-bottom: 1px solid var(--group-rule); }

/* hover answers on the edge instead of with a fill: the section rule takes
   the group's own colour, the same value the spine already carries (--gc is
   set on .group-head, raw chip in light, 66% toward white in dark). The tint
   that used to answer hover, and the light that swept across the band with
   it, are gone from :2416. */
.group-head { transition: border-color var(--dur-micro-2) var(--ease-out); }
.group-head:hover { border-bottom-color: var(--gc); }

/* the field is continuous, so the button that follows it needs the air the
   group's old 28px margin used to supply */
.add-group { margin-top: 10px; }

/* a collapsed group is now a line and nothing else: the old 52px estimate
   included the 8px head margin and the 28px group gap that are gone. `auto`
   means the real measured height wins once a group has been rendered; this
   is only the guess made before that. */
.group.collapsed { contain-intrinsic-size: auto 40px; }

/* the phone register is cards, not a table, so there is no table edge to
   close a group with: the line keeps a little air under it and the groups
   keep a gap. The first group's top rule goes, because directly under the
   group-jump chip strip it would read as that strip's own underline. */
@media (max-width: 700px) {
  .group { margin-bottom: 14px; }
  .group-head { margin-bottom: 6px; }
  .group:first-of-type > .group-head { border-top: none; }
}

/* ============================================================== phase-67-b
   SUBITEMS. A parent row grows a caret only when it has children; the children
   live in a strip under the row. The strip is the INSET step of the four-step
   surface ladder, not a fifth card: recessed ground (--surface-0 is darker
   than the row in BOTH themes), a hairline, a soft inner shadow at the top
   edge, and an indent that puts it under the name. No blur anywhere in here:
   a row is not a floating surface, and this block sits after the phone
   "chrome is solid" rule where a backdrop-filter would be wrong twice over.
   phase-117 (2026-09-23) RETIRED THE STRIP: a child is now a row of the
   parent's own table (board.js subRowHTML, app.css phase-117). Every rule
   here that styled only the strip (.sub-strip, .sub-strip-row, .sub-head,
   .sub-cell, .sub-lead, .sub-add, .sub-none, .sub-loading, --sub-inset,
   --sub-slack) was removed with it. What stays is live: the caret, the tick
   (.sub-check/.sub-box, the child row's and the item card's), and .sub-row,
   which the item card's Subitems tab still wears (panels.js renderSubsTab). */

/* the caret is one control: the disclosure and the count it discloses */
.sub-caret {
  flex: none; display: inline-flex; align-items: center; gap: 4px;
  height: 24px; margin: 0; padding: 0 5px 0 2px;
  border: 0; border-radius: 7px; background: none; color: var(--text-2); cursor: pointer;
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.sub-caret:hover { background: var(--hover); color: var(--text-1); }
.sub-caret:focus-visible { box-shadow: var(--focus-ring); outline: none; }
.sub-tag { font: 500 10.5px/1 var(--font-mono); letter-spacing: .02em; color: var(--text-3); }
.sub-caret[aria-expanded="true"] .sub-tag { color: var(--text-2); }

.sub-row { display: grid; grid-template-columns: var(--sub-cols); align-items: center; gap: 0 8px; }
.sub-row { min-height: 34px; padding: 1px 2px; border-radius: 7px; }
.sub-row + .sub-row { border-top: 1px solid var(--border-soft); }
.sub-row:hover { background: var(--surface-2); }

.sub-check { position: relative; display: inline-flex; width: 26px; height: 26px; align-items: center; justify-content: center; margin: 0; }
.sub-check input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.sub-box {
  width: 17px; height: 17px; border-radius: 5px; box-sizing: border-box;
  border: 1.5px solid var(--line-1); background: var(--surface-1);
  display: inline-flex; align-items: center; justify-content: center; color: transparent;
  transition: background var(--dur-micro-1) var(--ease-out), border-color var(--dur-micro-1) var(--ease-out);
}
.sub-check input:checked + .sub-box { background: var(--ok); border-color: var(--ok); color: #fff; }
.sub-check input:focus-visible + .sub-box { box-shadow: var(--focus-ring); }
.sub-check input:disabled { cursor: default; }
.sub-check input:disabled + .sub-box { opacity: .55; }

.sub-name {
  outline: none; min-width: 0; padding: 3px 5px; border-radius: 5px;
  font-size: 13px; font-weight: 500; color: var(--text-1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sub-name:hover, .sub-name:focus { background: var(--hover); white-space: normal; }
/* No line-through: decoration never crosses text in this product, and the
   ticked green box plus the drop to --text-3 already say "done" in both
   themes. */
.sub-row.done .sub-name { color: var(--text-3); }

.sub-menu { flex: none; width: 26px; height: 26px; border-radius: 7px; margin: 0; color: var(--text-2); }

/* phones: the register is cards, so the caret becomes a labelled full-width
   control under the card and the strip a list inside it. Nothing here is a
   scaled-down table: the head goes, the row wraps, every target is 44px. */
.rcard-wrap {
  position: relative; display: flex; flex-direction: column;
  border: 1px solid var(--line-2); border-radius: 12px; background: var(--surface-1);
  box-shadow: inset 0 1px 0 var(--glass-line), inset 0 -1px 0 var(--glass-rim-lo), var(--elev-1);
}
.rcard-wrap > .rcard { border: 0; border-radius: 12px 12px 0 0; box-shadow: none; background: none; }
/* the press feedback the card had before it moved inside a wrapper */
.rcard-wrap > .rcard:active { background: var(--surface-2); }
.sub-caret-card {
  width: 100%; height: auto; min-height: 44px; justify-content: flex-start;
  gap: 8px; padding: 0 14px 0 13px; border-radius: 0 0 12px 12px;
  border-top: 1px solid var(--line-2); color: var(--text-2);
}
.rcard-wrap.open .sub-caret-card { border-radius: 0; }
.sub-word { font: 600 12.5px var(--font-display); letter-spacing: -.01em; }
.sub-caret-card .sub-tag { margin-left: auto; font-size: 11.5px; }
@media (max-width: 700px) {
  .sub-row { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; min-height: 44px; padding: 4px 2px; }
  .sub-row::after { content: ""; order: 3; flex-basis: 100%; height: 0; }
  .sub-check { order: 1; width: 44px; height: 44px; }
  .sub-box { width: 20px; height: 20px; }
  .sub-name { order: 2; flex: 1 1 auto; font-size: 14px; }
  .sub-menu { order: 2; width: 44px; height: 44px; }
  /* "Table layout" on a phone shows the real register, and there the caret
     kept its 24px desktop height: the one control in the strip smaller than a
     finger. Its card twin (.sub-caret-card) is already 44px. */
  .board-table .sub-caret { min-width: 44px; min-height: 44px; justify-content: center; padding: 0 5px; }
  .sub-row:hover, .sub-caret:hover { background: none; }
  .sub-row:active { background: var(--surface-2); }
}

/* ============================================================== phase-67-b
   MY WORK carries subitems now, and a child row that looks exactly like a
   top-level one is a lie about where the work lives. The parent goes under the
   name as a quiet second line, inside .mw-name so the phone grid (which places
   .mw-name in its own area) keeps working untouched. Rows with no parent keep
   the markup, the height and the ellipsis they have always had. */
.mywork-row .mw-name.has-parent {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
}
.mywork-row .mw-name.has-parent > span {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mywork-row .mw-name .mw-parent {
  font-size: 11.5px; font-weight: 400; line-height: 1.3; color: var(--text-3);
}
/* on a phone the top-level rows wrap their names rather than truncate, so the
   subitem rows do the same with theirs */
@media (max-width: 700px) {
  .mywork-row .mw-name.has-parent > .mw-title { white-space: normal; }
}


/* ══════════════════════════════════════════════════════════════ phase-67-d
   Five moves from DESIGN_DIRECTION §9: columns align by what they hold, the
   row edge carries state, the head says what the board holds and what is
   filtered, and row height stops hiding in the profile panel.
   Appended after the "phones: the chrome is solid" block on purpose: nothing
   in here sets backdrop-filter, and every rule below has to beat an earlier
   one (`.board-table td { text-align: center }` at :811 and `.board-table
   td.cell` / `.board-table .cell-text` at :1715 and :1716 for alignment and
   the 6px inset, `.row-accent { background: var(--gcolor) }` at :820 for the
   edge), which a later block of equal-or-higher specificity does. */

/* ---- move 2: a column aligns by what it HOLDS ---------------------------
   Words read from the left edge, numbers and dates line up on the right so a
   column of figures can be scanned down, and the fixed-width blocks (a status
   plate, an avatar stack, a checkbox, a star row, a file fan) stay centred
   because they are shapes, not text. The cell carries data-type now, so none
   of this is inferred from a column's measured width any more.
   `td[data-type]` covers the body AND the summary foot, so a sum sits under
   the numbers it adds up. */
.board-table td[data-type="text"],
.board-table td[data-type="link"],
.board-table td[data-type="dropdown"],
.board-table td[data-type="tags"] { text-align: left; }
.board-table td[data-type="number"],
.board-table td[data-type="date"] { text-align: right; }
/* The timeline pill is one of those shapes and stays centred: it is a filled
   capsule, not a run of digits, and its worst real string ("Dec 28, 2025 –
   Sep 30, 2026", 167px at its trimmed 8px padding) already spends all but 2px
   of a 170px column (see :3353 and phase-52). Pushed to the right edge it
   would either sit on the divider or, once inset the 6px everything else
   gets, overflow the cell by 4px and shave a year off a date. */
/* the header sits over its own column: .th-in is a flex box, so text-align
   does not reach it */
.board-table th[data-type="text"] .th-in,
.board-table th[data-type="link"] .th-in,
.board-table th[data-type="dropdown"] .th-in,
.board-table th[data-type="tags"] .th-in { justify-content: flex-start; }
.board-table th[data-type="number"] .th-in,
.board-table th[data-type="date"] .th-in { justify-content: flex-end; }
/* .cell-tags centres itself at :2791; a left column's tags start at the edge */
.board-table td.cell[data-type="tags"] .cell-tags { justify-content: flex-start; }

/* the vertical rule appears only where a numeric run BEGINS. CSS cannot ask
   about a previous sibling, so it is stated the other way round: every
   numeric column takes the stronger line, and a numeric column that FOLLOWS
   one gives it back. A board with its numbers scattered gets a rule at the
   head of each run, which is what the mark is for. */
.board-table :is(td, th):is([data-type="number"], [data-type="date"], [data-type="timeline"]) {
  border-left-color: var(--line-1);
}
.board-table :is(td, th):is([data-type="number"], [data-type="date"], [data-type="timeline"])
  + :is(td, th):is([data-type="number"], [data-type="date"], [data-type="timeline"]) {
  border-left-color: var(--border-soft);
}

/* ---- move 3: the row's left edge carries STATUS -------------------------
   --acc is the status label's own chip colour, set per row by the renderer
   (board.js rowAccent). No value yet is a hairline, not a colour. A board with
   no status column is given no --acc at all, so the edge falls through to the
   group colour it has always had — the fallback in var() IS that case, and no
   class marks it.
   The phone card wears the same edge for the same reasons (see .rcard::before
   below): the owner reviews on his phone, and a left edge that meant "group"
   at 375px and "status" at 1440px would be two different products. */
.board-table .row-accent { background: var(--acc, var(--gcolor)); }
.board-table .row-accent.none { width: 2px; background: var(--line-1); }

/* ---- move 5: what this board holds, under its title --------------------- */
.bsum {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 14px;
  margin: 3px 0 0; padding: 0 8px; min-width: 0;
  font-size: 12.5px; line-height: 1.5; color: var(--text-3);
}
.bsum:empty { display: none; }
.bsum > span { display: inline-flex; align-items: baseline; gap: 5px; min-width: 0; }
.bsum b { color: var(--text-1); font-weight: 600; font-variant-numeric: tabular-nums; }
/* the dot is the label's own DATA colour, set inline; this rule owns the
   geometry only */
.bsum-s > i {
  flex: none; align-self: center; width: 7px; height: 7px; border-radius: 50%;
}
.bsum-s { max-width: 100%; overflow: hidden; }
[data-density="compact"] .bsum { margin-top: 1px; font-size: 12px; }

/* ---- move 6: the filters, written out ----------------------------------- */
.bfil {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px;
  margin-top: 8px; min-width: 0;
}
.bfil:empty { display: none; }
.bfil-count { font-size: 12.5px; color: var(--text-3); white-space: nowrap; }
.bfil-count b { color: var(--text-1); font-weight: 600; font-variant-numeric: tabular-nums; }
/* no height override: the chip is the SAME .btn.sm the sort chip is, at its
   natural size. Only .board-tools stretches its buttons to the 34px toolbar
   row, and this bar is not that row. */
.bfil .btn { white-space: nowrap; }
.bfil-chip { gap: 5px; }
.bfil-chip .bfil-col {
  font: 500 10.5px var(--font-mono); letter-spacing: var(--ls-caps);
  text-transform: uppercase; opacity: .8;
}
.bfil-chip .bfil-v { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bfil-clear { margin-left: 2px; color: var(--text-3); }
.bfil-clear:hover { color: var(--text-1); }
/* the collapsed form is the phone's; on a desktop the chips say it in full */
.bfil-sm { display: none; }

/* ---- move 7: row height, on the toolbar and labelled -------------------- */
/* .act-label is hidden below 1100px and this control must never become a bare
   icon, so its word is its own span and stays visible at every width. */
.dens-btn { gap: 6px; }
.dens-lab { font-weight: var(--fw-semibold); }

@media (max-width: 700px) {
  /* a row of condition chips would wrap into three lines and eat the screen;
     the same state collapses to one labelled button that opens the sheet */
  .bfil-chip { display: none; }
  .bfil-sm { display: inline-flex; }
  /* except the search and person chips: there are at most two of them, and a
     reader here most needs to see WHY rows are missing — and what the clear
     button is about to take away */
  .bfil-txt { display: inline-flex; }
  .bfil { gap: 8px; margin-top: 8px; }
  .bfil .btn { height: 44px; min-height: 44px; }
  .bfil-count { flex: 1 1 100%; }
  /* the tools row now carries five things; it wraps rather than crushing the
     search box to nothing */
  .board-tools { flex-wrap: wrap; row-gap: 8px; }
  .board-search { flex: 1 1 150px; }
  .dens-btn { min-height: 44px; }
  /* 8px keeps it on the same left edge as the title above it */
  .bsum { padding: 0 8px; gap: 2px 12px; font-size: 12px; }
}

/* The density control earns its place on the toolbar only where there are rows
   with a height to change. That is a question about what is RENDERED, never
   about the width: every compact rule touches `.board-table td`, and a phone
   showing the real table (board menu → "Table layout", remembered in
   sd_phone_table) has those rows. Keyed off the layout the renderer publishes
   on <html> (board.js SD.boardRowKind: table | cards | none), so the control is
   present wherever pressing it would do something — at 375px included — and
   absent where the rows are cards or where the view has no rows at all (kanban,
   calendar, dashboard, files). It keeps its home in the profile panel and the
   command palette either way, so nothing is ever removed. */
html:not([data-board-rows="table"]) .dens-btn { display: none; }


/* ---- the inset that makes a column a column (phase-67-d review) ----------
   `.board-table td` is padding: 0 (:811); only `.cell-text` carried its own
   6px (:1716), which covers text, dropdown and number cells and nothing else.
   So a right-aligned date sat on the 1px border of the cell after it, a link
   started on the border of the cell before it, and — the one that showed —
   the tfoot total printed 6px further right than every number it adds up,
   under a comment claiming the sum sits under them. One inset, 6px, on
   everything the alignment rules above move, so a column really is a column
   all the way down to its sum.
   Scoped to `td.cell` and `td.sum-cell` so the subitem strip inside a
   colspan'd cell keeps its own grid spacing (`.sub-cell .cell-text` at :4309
   deliberately has none). */
.board-table td.sum-cell { padding: 0 6px; }
.board-table td.cell .cell-date { padding: 0 6px; }
.board-table td.cell[data-type="link"] .cell-link { padding: 0 6px; }

/* ---- move 3 reaches the phone (phase-67-d review) ------------------------
   The card IS the row at 375px, so its left edge has to mean what the table
   row's left edge means: the first status column's own label colour, a
   hairline where the row has no status yet, the group colour on a board with
   no status column at all. `--acc` is set on the card by the same rowAccent
   the table row uses; `.rcard::before` at :3151 painted `var(--gcolor)`
   unconditionally, so the owner's phone was still colouring by group while
   his desktop coloured by state.
   Inside the same 700px block the base rule lives in, so this stays a phone
   rule rather than a plain one that outranks it everywhere. */
@media (max-width: 700px) {
  .rcard::before { background: var(--acc, var(--gcolor)); }
  /* the add card's edge is already display: none at :3167 and stays that way */
  /* no status yet: the same 2px hairline the table row shows. The spine's
     light-theme edge ring (:4004, --text-1 at 38%) would cover a 2px box
     completely and turn the hairline into a dark rule, so it comes off here
     and the line is --line-1 in both themes, as it is on the desktop. */
  .rcard.acc-none::before { width: 2px; background: var(--line-1); box-shadow: none; }
}


/* ════════════════════════════════════════════════════════════════ phase-68
   THE SUBITEM STRIP REJOINS THE RULED FIELD.
   phase-67-c took the fill, the rounded box, the side borders and the drop
   shadow off the register one commit before phase-67-b gave the subitem strip
   all four back: a --surface-0 fill (the RECESSED step, so the children read
   darker than the row they belong to), a 1px border, a 9px radius, an inset
   shadow, an 820px cap that floated a small box in the middle of a register
   several thousand pixels wide, and `position: sticky; left: 0`, which pinned
   the strip to the viewport while its own parent row slid away underneath.
   The owner read exactly that: "a short small box that seems to break the
   flow of the boards view."
   So the strip stops being a card. It sits on the SAME surface as the rows,
   it is separated above and below by the row grid's own hairlines
   (--border-soft, 1.22:1 light on #ffffff, 1.25:1 dark on #1d2025 — the same
   line that separates every other pair of rows), and the only colour it
   carries is on an EDGE: a 3px spine down the indent in the parent row's own
   accent, which is the status label's DATA colour (rowAccent, board.js), with
   --group-rule behind it for a board that has no status column (1.60:1 light,
   1.66:1 dark, measured in phase-67-c) and --line-1 for a row that has no
   status value yet (1.44:1 light, 1.50:1 dark — the same 2px hairline that
   row's own left edge is already wearing at :4463).
   For the record, the fill this replaces was worth 1.09:1 in both themes:
   just enough to read as a separate panel, never enough to read as depth.
   Appended after the "phones: the chrome is solid" block on purpose: nothing
   in here sets backdrop-filter or blurs a row, nothing animates but the two
   micro-transitions the strip already had, and every rule below has to beat
   the phase-67-b block at :4210, which a later block of equal specificity
   does. */

/* phase-117: the strip this block ruled is retired (a child is a row of the
   parent's own table now); its indent, spine, lead cell, add button and the
   phone card's copy of all four went with it. The two .sub-row rules below
   are kept because the item card's Subitems tab still wears .sub-row
   (panels.js renderSubsTab) and has been drawn with them since phase-113. */
.sub-row { gap: 0; }
.sub-row { padding: 0; border-radius: 0; background: var(--surface); }

/* ---- 5. (phase-117) the phone-table half of the strip, retired with the
   strip. Its html[data-board-rows="table"] .sub-row { display: grid } had
   also been reaching the item card's Subitems rows on a phone in Table
   layout, where a grid with no template stacked the tick, the name and the
   menu one per line. */


/* ════════════════════════════════════════════════════════════════ phase-68
   THE FAN SHOWS THREE PAGES, AND THE PAPERWORK STOPS HIDING THE DOCUMENT.
   The owner: "when I click the files I card that pops up does not show
   anything, just one document and is not even the SOP".

   Half of that is geometry. The fan at :2144 overlapped the stacked pages by
   a HARDCODED -20px against a 22px page, so each page behind the first
   exposed 2px of layout plus the 2px/4px it was translated: a 4px sliver,
   which reads as one page with a shadow. Worse at compact, where :2989 tried
   to shrink the page to 18px and could not — --tw is written INLINE by
   SD.fileThumb, and an inline custom property beats a stylesheet rule, so the
   compact page stayed 22px against an overlap tuned for nothing in
   particular.

   So the page size moves to the CELL, where no inline style contests it, and
   the overlap is derived from it instead of typed in. Exposed strip per page
   (--fan-step, 45% of the page width), measured:
     comfortable  page 22x28, margin-left -12.1px, 9.9px exposed, 41.8px total
     compact      page 18x23, margin-left  -9.9px, 8.1px exposed, 34.2px total
   The register's Files column is 118px wide (dms_library.py:83), 106px inside
   its padding, so three pages plus the "+N" chip still fit at rest at both
   densities. Hover and touch are unchanged: the stack spreads flat with a 3px
   gap, which is the behaviour the owner called PERFECT and nothing here
   touches.

   Appended after the "phones: the chrome is solid" block on purpose: nothing
   below sets backdrop-filter, blurs a card or a row, or adds an animation.
   The transitions are the two the fan already had. */

/* the page size lives on the cell; the glyph fills whatever the page is, so
   the exposed strip carries real page (its colour band and its folded
   corner), not the empty margin the compact glyph rule at :2991 left. */
.cell-files.has { --fan-w: 22px; --fan-h: 28px; --fan-step: calc(var(--fan-w) * .45); }
[data-density="compact"] .cell-files.has { --fan-w: 18px; --fan-h: 23px; }
.cell-files.has .file-mini { width: var(--fan-w); height: var(--fan-h); margin-left: calc(var(--fan-step) - var(--fan-w)); }
.cell-files.has .file-mini .fglyph { width: 100%; height: 100%; }
.cell-files.has .file-mini:first-child { margin-left: 0; }

/* the lift derives from the page height too (6% and 12%: 1.7px and 3.4px
   comfortable, 1.4px and 2.8px compact), so the third page's top corner stays
   inside the 4px of cell padding at a 32px compact row instead of being
   clipped by td.cell's overflow. Re-stated after the rest rules because a
   later rule of EQUAL specificity wins: :2148's hover spread must keep
   beating the two nth-child transforms, and now they are declared here. */
.cell-files.has .file-mini:nth-child(2) { transform: translateY(calc(var(--fan-h) * -.06)); }
.cell-files.has .file-mini:nth-child(3) { transform: translateY(calc(var(--fan-h) * -.12)); }
.cell-files.has:hover .file-mini { transform: none; margin-left: 3px; }
.cell-files.has:hover .file-mini:first-child { margin-left: 0; }
@media (hover: none) {
  /* no hover to spread with, so the stack is already spread (:2459) and flat */
  .cell-files.has .file-mini:nth-child(2), .cell-files.has .file-mini:nth-child(3) { transform: none; }
}

/* ---- the SOP and the form that approves it are two different things ------
   A controlled document is the SOP file PLUS its signed QAF, and a flat
   newest-first list made them one pile in which the QAFs, signed after the
   SOP, came first. Both lists now carry a quiet heading per group (board.js
   docGroups). It is CHROME, not data: it names a kind of paperwork, so it
   takes --text-3 and the section voice of .ic-sec-t (:2823) and no chip
   colour, and the separation is a RULE, never a fill. */
.pf-sec, .attach-sec {
  display: flex; align-items: center; gap: 6px;
  font: 600 10.5px/1.5 var(--font-body); letter-spacing: .07em; text-transform: uppercase; color: var(--text-3);
}
.pf-sec b, .attach-sec b { font-family: var(--font-mono); font-weight: 500; font-size: 10px; letter-spacing: 0; color: var(--text-3); }
.pf-sec { padding: 7px 0 2px; }
.attach-sec { padding: 9px 4px 3px; }
.peek-files > .pf-sec:first-child { padding-top: 1px; }
.attach-list > .attach-sec:first-child { padding-top: 2px; }
.peek-files > .pf-sec:not(:first-child), .attach-list > .attach-sec:not(:first-child) { margin-top: 4px; border-top: 1px solid var(--line-2); }

/* what did not fit is named, not counted, so a reader can tell whether the
   missing file is the document or its paperwork */
.pf-more { padding-top: 6px; }
/* a hover that failed says so in the app's own words (.ld-err-line at :3744),
   without a retry button the pointer would have to travel into the card to
   reach: the cell underneath is the labeled way back and the next hover
   refetches on its own. */
.peek-files > .ld-err-line { margin: 4px 0 2px; }


/* ════════════════════════════════════════════════════════════════ phase-68
   ONE FOCUS EDGE, AND ONE LINE LADDER.

   The owner sent four screenshots of what is one bug with four faces:
   "text boxes have some rounded shaped within, looks like some type of
   glitch", "many text boxes have the original blue edge again", "'DOCUMENT'
   text box also has the wrong edge and the wrong font", and, on clicking a
   document title while its cell was hovered, "the other blue edge shows and
   collapses with the rainbow edge ... looking like it is visually glitching
   and colliding".

   Three separate mechanisms were painting focus, and nothing coordinated them:

   1. --focus-ring at :87 was `0 0 0 2px var(--surface-0), 0 0 0 4px
      var(--brand)`. The second shadow is the blue; the FIRST is a solid 2px
      band of page grey laid between the control and that blue, and on the
      subitem strip's controls (.sub-caret, .sub-check, .sub-cell, .sub-add)
      it reads as a rounded shape sitting inside the control. That is the
      "rounded shaped within". It is gone: --focus-ring is now a single 2px
      --brand ring, measured 5.13:1 on light --surface-1 and 7.06:1 on dark
      --surface-1, so the non-text controls it still serves (the switch, the
      tick box, the skip link, the strip's buttons) keep a keyboard indicator
      that is stronger than before, not weaker.

   2. phase-40 gave the fan ring to `input`, `select` and `textarea` ONLY. The
      four controls in the owner's screenshots are none of those: the board
      title, the group title, a row's document title and the item-label header
      are contenteditable elements, and the panel's document title is a
      contenteditable <h3>. So they fell through to the quality floor at :1384,
      `[contenteditable]:focus-visible { outline: 2px solid var(--brand) }`,
      which is the blue edge he says was "already modified". They are now one
      named family with one treatment, below, so the next phase cannot half
      miss it again.

   3. The hovered cell already wears the fan (:2260, td.cell AND td.col-name).
      Clicking the name inside that cell lit the contenteditable's blue outline
      two pixels inside the rainbow the cell was already painting: two edges,
      two different colours, one control. Now the cell's ring stands down the
      moment something inside it takes focus. ONE EDGE AT A TIME.

   Accessibility. The fan is the five LOGO hues and stays so (rule 5); a focus
   ring is UI, never data. Measured against light --surface-1 (#ffffff) the
   arcs are red 3.66:1, blue 3.63:1, green 2.65:1, orange 2.04:1, yellow
   1.51:1; against dark --surface-1 (#1d2025) they are yellow 10.80:1, orange
   8.06:1, green 6.17:1, blue 4.49:1, red 4.46:1. A conic gradient shows ALL
   FIVE around the perimeter at once, so at every instant, in both themes, part
   of the ring clears 3:1 against the field it encloses — but light theme's
   yellow and orange arcs do not, which is a property of the owner's palette
   and not something a stylesheet can raise. That is why the ring is not the
   only cue: the focused control also takes its fill, and the caret is in it.
   Under prefers-reduced-motion the spin stops and the ring STAYS PAINTED
   (background-size is what shows it, animation is only what turns it), which
   is the case phase-40 left uncovered: :2761 stops .board-search::before and
   nothing else, so every focused input on the register kept spinning.

   Nothing in this block sets backdrop-filter, blurs a card or a row, or
   animates anything but --ring-a (an angle) behind transform/opacity rules. */

/* ---- 1. the fan, named once so there is one place to change it --------- */
:root {
  --fan-ring: conic-gradient(from var(--ring-a), var(--logo-1), var(--logo-2), var(--logo-3), var(--logo-4), var(--logo-5), var(--logo-4), var(--logo-3), var(--logo-2), var(--logo-1));
  /* 1.5px: the same weight .board-search::before has carried since phase-40,
     which is the text box the owner uses most and therefore the one every
     other text box should look like.
     Named --fan-ring-w, not --fan-w: :4781 already uses --fan-w for the WIDTH
     OF A FILE PAGE in the fanned stack (22px, 18px compact), and that one is
     set on .cell-files.has, so it inherits. Nothing puts a text control inside
     a files cell today, but the day something does, `border: var(--fan-w)`
     would resolve to a 22px border. Two fans, two names. */
  --fan-ring-w: 1.5px;
  /* --focus-ring WAS redeclared here (`0 0 0 2px var(--brand)`). DELETED
     2026-09-21, phase-97, D46 §9 item 1: a token declared twice is not a
     token. The one declaration is at :87 and it is the phase-68 shape plus
     the halo that keeps it visible on a ground that swallows the hue. The
     substance phase-68 protected is unchanged: no inner band, one hue. */
}

/* ---- 2. the text controls that are NOT form elements -------------------
   Five contenteditable renames and the item-label header. The technique is
   phase-40's, not a second recipe: two background layers on the SAME element,
   the fill clipped to the padding box and the fan clipped to the border box,
   revealed by growing the fan layer from 0 to 100% on focus. It needs a
   border to live in, so each control takes a transparent one and gives the
   same 1.5px back out of its padding — every box keeps the size it has today.
   The fill is a custom property, so hover and focus share ONE declaration and
   cannot drift apart. */
/* UPDATED 2026-09-21, phase-97, D46 §9 item 2 (screenshot 15): the SECOND
   background layer - the conic fan clipped to the border box - is gone from
   this family, and with it the 2.6s spin. Focus is answered by --focus-ring,
   outside the box, where D46 §7 puts it. The fill layer, the transparent
   1.5px border and the padding each control gives back for it are untouched,
   so NO BOX MOVES and every resting and hover tint is exactly what it was.
   Longhands only: a `background` shorthand here would reset background-image,
   -size, -origin and -clip and take the fill with it (phase-89). */
/* SUPERSEDED 2026-09-22 by the owner: "many details were lost like the thin
   rainbow edge, the blue edge keeps coming back." The phase-97 note above is
   left visible and no longer describes the code: the second layer (the conic
   fan clipped to the border box), its growth from 0 to 100% on focus and the
   2.6s spin are RESTORED exactly as a1799da had them, and the --focus-ring
   box-shadow phase-97 put on this family is gone. The longhands rule still
   stands and is still why nothing below is a `background` shorthand. */
.board-name, .group-title, .th-label, .item-name, .sub-name, .ic-head h3 {
  --fan-fill: transparent;
  border: var(--fan-ring-w) solid transparent;
  background-color: transparent;
  background-origin: border-box;
  background-clip: padding-box, border-box;
  background-image: linear-gradient(var(--fan-fill) 0 0), var(--fan-ring);
  background-size: 100% 100%, 0 0;
  background-repeat: no-repeat;
}
/* each control's own resting hover tint, unchanged from what it wears today */
.board-name  { --fan-tint: var(--surface-2); padding: .5px 6.5px; }
/* --gc is set on .group-head (:758) and inherits here. The fallback is not
   decoration: an invalid color-mix would make --fan-tint invalid at computed
   value time, which would take the whole background-image declaration that
   reads it down with it, and the ring with that. */
.group-title { --fan-tint: color-mix(in srgb, var(--gc, var(--line-1)) 14%, transparent); padding: .5px 4.5px; }
.item-name   { --fan-tint: var(--hover); padding: 1.5px 4.5px; }
.sub-name    { --fan-tint: var(--hover); padding: 1.5px 3.5px; }
.ic-head h3  { --fan-tint: var(--wash); padding: .5px 4.5px; margin-left: -7.5px; }

/* the item-label header, item 3 of the four. It is the ONLY column head in the
   table that is not a .col-head (:805), so while STATUS, OWNER and REVISED
   spoke in 11px uppercase mono at --ls-caps, the word naming the rows spoke in
   13px semibold body sans: a different voice for the same job, which is what
   the owner read as "the wrong font". Same stack, same size, same tracking,
   same colour, same 4px radius as its nineteen neighbours; the rename still
   reads textContent, so the stored label keeps its own capitalisation and only
   the rendering is uppercased. */
.th-label {
  --fan-tint: var(--hover-ground);
  font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: var(--fw-medium);
  line-height: var(--lh-snug); letter-spacing: var(--ls-caps); text-transform: uppercase;
  color: var(--text-3); border-radius: 4px; padding: 1.5px 6.5px;
}

/* hover and focus resolve the SAME fill declaration above; only focus grows
   the fan layer, so the two states can never paint two edges */
/* SUPERSEDED 2026-09-22 (the owner's "thin rainbow edge" ruling, above): this
   comment read "UPDATED phase-97: focus no longer grows a fan layer - the fan
   layer is gone - it adds --focus-ring OUTSIDE the box, so hover (a ground,
   inside) and focus (a ring, outside) are now different in kind and not just
   in colour (D46 §4e)." The fan layer is back and the ring outside is gone. */
/* focus-ring: exempt - owner-restored 2026-09-22, the five-hue fan on the six
   contenteditable renames. This rule carries the fan layer at size 0 so the
   focus rule below only has to grow it; the marker lets the D46 checks pass
   it BY NAME. */
.board-name:hover, .group-title:hover, .th-label:hover, .item-name:hover, .sub-name:hover, .ic-head h3:hover,
.board-name:focus, .group-title:focus, .th-label:focus, .item-name:focus, .sub-name:focus, .ic-head h3:focus {
  --fan-fill: var(--fan-tint);
  background-color: transparent;
  background-image: linear-gradient(var(--fan-fill) 0 0), var(--fan-ring);
  background-size: 100% 100%, 0 0;
}
.board-name:focus, .group-title:focus, .th-label:focus, .item-name:focus, .sub-name:focus, .ic-head h3:focus {
  background-size: 100% 100%, 100% 100%;
  animation: ringSpin 2.6s linear infinite;
}
/* the quality floor at :1384 is what painted the blue; it is answered here
   rather than edited there, because a link and an icon button still want it */
.board-name:focus, .group-title:focus, .th-label:focus, .item-name:focus, .sub-name:focus, .ic-head h3:focus,
.board-name:focus-visible, .group-title:focus-visible, .th-label:focus-visible,
.item-name:focus-visible, .sub-name:focus-visible, .ic-head h3:focus-visible { outline: none; }

/* ---- 3. the form controls phase-40 exempted, which kept the blue -------
   .attach-ver is the revision designation box on a controlled document and
   answered focus with `border-color: var(--brand)` (:2781, :2846). It is a
   text box, so it answers with the fan like every other text box. Its own
   background-color still paints the field (--wash at rest, --surface-1 while
   editing), so the fill layer stays transparent and nothing is tinted twice. */
.attach-ver {
  --fan-fill: transparent;
  background-origin: border-box; background-clip: padding-box, border-box;
  background-image: linear-gradient(var(--fan-fill) 0 0), var(--fan-ring);
  background-size: 100% 100%, 0 0;
}
.attach-ver:focus { border-color: transparent; background-size: 100% 100%, 100% 100%; animation: ringSpin 2.6s linear infinite; }
.attach-row .attach-ver:focus { border-color: transparent; }

/* The add-item field is the worst of them, and the only one the owner could
   not have screenshotted, because on focus it shows NOTHING. It has
   `border: none` (:905), so phase-40's rule has a zero-width border box to
   grow its fan into; and :1787's answer, `background: var(--brand-soft)`, is a
   background-COLOUR, which sits under phase-40's opaque --surface-1 fill layer
   and never reaches the screen. A keyboard user tabbing into "+ Add document"
   got a caret and no indicator at all. A transparent 1.5px border, 1.5px back
   out of the side padding, and the same ring every other text box wears. Its
   radius stays 0: it spans the table, and a rounded box inside a square cell
   would read as a second surface. Height is unchanged — border-box, 40px.
   Its WIDTH is dealt with in §8: `width: 100%` inside a colspan=21 td made the
   field 2623px wide on the register, so the ring was two hairlines running past
   both edges of a 1321px viewport with the right stroke 1300px off-screen and
   the conic stretched until only one or two of the five hues were in view at
   once. A ring that is not a ring. §8 rebuilds the row it lives in and sizes
   it there, because the real defect underneath was not the ring's width. */
.add-item-input { border: var(--fan-ring-w) solid transparent; padding: 10px 14.5px; }

/* the activity filter's search field answered focus with a --primary border
   and a 3px --primary glow (:3435). Same wrapper shape as .board-search, so
   the same masked ring; the SELECT beside it is not a text box and keeps the
   --brand border it shares that rule with. */
.actf-field { position: relative; }
.actf-field::before {
  content: ""; position: absolute; inset: -1px; border-radius: inherit; padding: var(--fan-ring-w); pointer-events: none; opacity: 0;
  background: var(--fan-ring);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask-composite: exclude;
  transition: opacity var(--dur-micro-2) var(--ease-out);
}
.actf-field:focus-within { border-color: transparent; box-shadow: none; }
.actf-field:focus-within::before { opacity: 1; animation: ringSpin 2.6s linear infinite; }

/* ---- 4. one edge at a time --------------------------------------------
   The cell's own fan (:2260) stands down while anything inside it holds
   focus, so the rename's ring is the only ring on screen. Both theme
   selectors are written out because the dark rule at :2267 carries an
   attribute selector and would otherwise out-rank a plain one. */
.board-table td.col-name:focus-within::before,
.board-table td.cell:focus-visible::before,
[data-theme="dark"] .board-table td.col-name:focus-within::before,
[data-theme="dark"] .board-table td.cell:focus-visible::before { opacity: 0; animation: none; }

/* ---- 5. the spin stops, the ring does not ------------------------------
   No rule in this family transitions background-size: the ring is shown by
   growing that layer, which is an instant computed-value change, so the
   indicator never waits on a transition to finish. The only transition the
   family owns is the opacity fade on the two masked wrapper rings, and under
   reduced motion that is turned off rather than shortened, so the ring is
   simply there. (.board-search::before is already handled at :2761.) */
@media (prefers-reduced-motion: reduce) {
  .board-name:focus, .group-title:focus, .th-label:focus, .item-name:focus, .sub-name:focus, .ic-head h3:focus,
  input:focus, select:focus, textarea:focus, .attach-ver:focus, .actf-field:focus-within::before {
    animation: none !important;
  }
  .actf-field::before { transition: none !important; }
}


/* ---- 6. one line ladder ------------------------------------------------
   "I like the new thinness of the sides lines you added for the group titles.
   Apply the same line thinness to the rest or come up with something so it
   all visually flows and looks extremely professional."

   The thin side line he likes is the 3px group spine (:3992) that phase-67-c
   left as the only place a group's colour lands. Surveying every edge the
   board draws, the register is already most of the way to a ladder — it is
   just that two edges never joined it, and the ladder was never written down.
   Five steps, and what each one is FOR:

     1  1px --border-soft   the weave: every row and cell hairline, the grid
                            the register is read on.   1.22:1 light on
                            #ffffff, 1.25:1 dark on #1d2025.
     2  1px --line-1        a boundary INSIDE a section: the column head's
                            underline, the summary row.  1.44:1 / 1.50:1.
     3  1px --group-rule    a boundary BETWEEN sections: the group rule that
                            closes one group and stands the next one up.
                            1.58:1 / 1.63:1 (measured in phase-67-c).
     4  2px hairline spine  a left edge that carries NO value: --line-1 again,
                            so "nothing here yet" is a weight, not a colour.
     5  3px coloured spine  a left edge that carries a value: the group's
                            colour, or the status label's own DATA colour.

   Colour only ever arrives on steps 4 and 5, and only on a left edge; steps
   1-3 are neutral and differ by contrast, not by thickness, which is what
   lets the field read as continuous while still finding its sections.

   Two edges were outside it. The table row's left accent was 5px (:820) while
   the phone CARD's accent for the exact same meaning was 3px (:3151) — the
   same statement at two weights in the two places the owner reviews. And the
   dashboard KPI's spine grew from 3px to 4px on hover (:2384), a sixth weight
   that existed for one hover state. Both are step 5 now. The widths are named
   so a future phase has one number to change instead of six. Nothing here
   changes a colour, so nothing here changes a contrast. */
:root { --spine-w: 3px; --spine-w-none: 2px; }
.board-table .row-accent { width: var(--spine-w); }
.board-table .row-accent.none { width: var(--spine-w-none); }
.group-head::before { width: var(--spine-w); }
.kpi::before, .kpi:hover::before { width: var(--spine-w); }

/* ---- 7. a reserved document is not an empty one -----------------------
   21 rows of the owner's register are Queued with no file attached, and that
   is correct document control: the Doc ID is reserved in the index before
   anyone drafts the document. Their files cell showed what a broken row shows
   — nothing, with a plus that only appears on hover (:1191), which is the
   hover-only affordance this product does not ship. So a reserved row says so
   in words, always visible, at every width.
   It is CHROME, not data: --text-3 and the mono micro-voice, no chip colour,
   no logo hue, and no second reading of the status pill beside it. The hover
   state also stops recolouring to --brand here (:1785) — the row is not
   promising an upload, it is reporting a state.
   Measured: --text-3 is 5.95:1 on light --surface-1 and 6.36:1 on dark, both
   past 4.5:1 for small text. The register's Files column is 118px wide, 106px
   inside its padding; "Not drafted yet" is 15 characters of 10.5px mono at
   ~0.6em advance, about 95px, so it fits at both densities and ellipsises
   rather than wraps if a board ever gives the column less. */
.cell-files.empty.reserved {
  display: inline-block; opacity: 1; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: var(--fw-medium) 10.5px/1.2 var(--font-mono); letter-spacing: .01em;
  color: var(--text-3);
}
.cell:hover .cell-files.empty.reserved { color: var(--text-2); }

/* THE SAME STATEMENT ON A PHONE. The rule that used to sit here styled
   `.rcard-cell .cell-files.empty.reserved` and was dead code: cardColumns()
   drops the files column from a card on purpose (board.js :1093), so no card
   on any board has ever held a files cell, and the reserved state simply did
   not exist at 375px in the default layout. The owner reviews on his phone, so
   that is not a gap the table version can cover.
   A card is not a table and does not need a cell to say this. The card's foot
   is already the line that speaks about the document's files — it carries the
   attachment count — so the reserved row says it there, with the same doc mark
   and the same mono micro-voice the count uses (:3163). Declared plainly rather
   than inside the 700px block, so a board switched to Card layout above 700px
   says it too; under 700px the values are identical to the foot's own and
   nothing moves. --text-3 measures 5.95:1 on light --surface-1 and 6.36:1 on
   dark, the same as the table cell. */
.rcard-reserved {
  display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
  font-family: var(--font-mono); font-size: 11px; color: var(--text-3);
}
.rcard-reserved > .ic { flex: none; }


/* ---- 8. "+ Add document" becomes a control that does something ---------
   The owner: it "does not work when clicked", on desktop, laptop and phone.
   He is right, and it predates this phase. The add row was ONE bare text input
   whose placeholder read "+ Add document": a plus and an action verb painted
   into placeholder text, with `data-enter` and no `data-act`. Clicking it
   placed a caret and nothing else; Enter on the empty field hit
   `if (!name) return` and was silent too. Three gestures, two dead ends, and
   phase-68 had just put a five-hue ring around the whole thing, which made a
   control that does nothing look more like a control than ever.

   So the row is now a real button and a real field, side by side
   (board.js renderTable, .addbar). The button carries the plus as SD.icon and
   says "Add document"; it makes the row and drops straight into renaming it in
   place. The field keeps `data-enter` untouched, so type-and-Enter works for
   anyone who learned it, and an empty Enter now means what the button means.

   Sizing, which is also the answer to the ring: the field is `flex: 0 1 360px`
   instead of 100% of a 2623px cell, so the fan closes on FOUR sides inside the
   viewport at every width and all five hues are in view at once. 360px is this
   product's text-field width (.board-search is a 200px input plus its icon and
   padding) with room for a document title.
   At rest the field shows a real boundary, because a text box beside a button
   with no edge at all is not readable as a text box: --line-strong, the token
   :340 already uses for exactly this and labels ">=3:1 on its grounds", and
   measured here it is 3.61:1 on light --surface (#ffffff) and 3.48:1 on dark
   (#1d2025). It is an inset box-shadow, not a border, so it does not contest
   the transparent border the fan lives in — and it is DROPPED on focus, so the
   ring is the only edge the field wears. One edge at a time, as in §4. */
.addbar { display: flex; align-items: center; gap: 8px; min-width: 0; padding: 4px 10px 4px 12px; }
.addbar-btn {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  height: 32px; margin: 0; padding: 0 12px 0 9px;
  border: 0; border-radius: 8px; background: none; color: var(--text-2);
  font: var(--fw-medium) 13px var(--font-body); cursor: pointer; white-space: nowrap;
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.addbar-btn > .ic { flex: none; color: var(--text-3); }
.addbar-btn:hover { background: var(--hover); color: var(--text-1); }
.addbar-btn:hover > .ic { color: var(--brand); }
.addbar .add-item-input {
  flex: 0 1 360px; width: auto; min-width: 0; height: 34px; padding: 0 10px;
  border-radius: 8px; font-size: 13px;
  box-shadow: inset 0 0 0 1px var(--line-strong);
}
.addbar .add-item-input:focus { box-shadow: none; }
[data-density="compact"] .addbar { padding: 2px 10px 2px 12px; }
[data-density="compact"] .addbar .add-item-input { height: 30px; }
[data-density="compact"] .addbar-btn { height: 28px; }
/* a phone running the optional Table layout gets this row too, and there it is
   two thumb targets stacked, never a 360px field squeezed beside a button */
@media (max-width: 700px) {
  .addbar { flex-wrap: wrap; gap: 6px; padding: 6px 10px; }
  .addbar-btn { height: 44px; flex: 1 1 100%; justify-content: center; }
  .addbar .add-item-input { flex: 1 1 100%; height: 44px; }
  /* the compact rules above are one class more specific than these and a later
     media rule does not change that, so compact is restated rather than left to
     hand a phone two 28px targets */
  [data-density="compact"] .addbar-btn,
  [data-density="compact"] .addbar .add-item-input { height: 44px; }
}

/* ---- the revision chip stops printing over Kind (phase-71-b) -------------
   The Library list gave Rev. a fixed track and the chip `flex: none` with no
   overflow rule, so a long tag kept its max-content width and painted on top
   of the Kind column beside it. On the owner's register the worst tag that
   actually exists is `QAF v2 2026-06-01 (archive copy)` (server/
   eoh_register.json): 32 mono characters, about 225px of text asked to sit in
   a 72px box, which is how `2026-0DOC01` reached his screenshot. Median tag
   is 4 characters and 90% are 10 or fewer, so no fixed width is right for
   both ends of that spread and no larger fixed width is safe either — the
   builder makes these labels by concatenation, so the next import can always
   be longer. The cap is therefore stated by construction rather than chosen:
   the chip can never be wider than the cell it is in, whatever the string.
   Why it needs saying twice. The chip inherits `white-space: nowrap` from
   .fv-meta, so its min-content width equals its max-content width, and the
   shrink-to-fit that `justify-self: start` performs on a grid item resolves
   to max(min-content, track) — it cannot pull the chip back inside the track
   on its own. `max-width: 100%` against the grid area is the cap that holds;
   overflow + ellipsis is what makes the clip readable instead of a cut.
   In the grid card and in the phone card the same chip is a flex item, and
   `flex: none` there meant it shoved Kind, Size and Added off the meta line
   rather than yielding. It now yields first — but yielding is only safe
   because the line it yields into was trimmed to fit (phase-71-b fix, after
   qa). Measure the grid card and the reason is plain: a 258px card is
   258 - 2 border - 24 padding - 44 thumb - 11 gap - 11 gap - 31 for the
   in-flow download button = 135px of meta, and 127px at the 250px card the
   `auto-fill` minimum allows. Kind + Size + "Added by" + Added measure about
   250px in that box on their own, so the line was already being cut
   mid-character at the right edge (`text-overflow` does not reach the items
   of a flex container) and the first shrinkable item added to it absorbed
   the whole 146px shortfall — which is how every chip, `v1` included,
   collapsed to a bare 14px ellipsis. A `min-width` floor on the chip would
   only have moved that cut onto the date beside it, and wrapping would only
   have bought one of the two lines the overflow needed. So views.js takes
   Kind, Size and "Added by" off the grid card instead; they keep their
   labelled columns in list mode. What is left is the chip plus the date
   (about 75px at its worst, "Sep 30, 2026"), leaving the chip 47px at a
   250px card and 55px at 258px: `v1`, `v10`, `file` and `Rev A` render
   whole everywhere, `QAF v1` from 256px up, and only a genuinely long tag
   ellipsizes, with a readable prefix rather than a bare pill. On a phone the
   single-column card gives the line about 200px and nothing truncates.
   Truncating is allowed here because the tag is a qualifier, not the row's
   identity (the file name and the document name are, in their own columns),
   and because the full tag is never hover-only: opening the file prints it
   whole in the viewer head and the card heading (panels.js), which is a
   labelled click that works on a phone. `title` is the desktop shortcut.
   Appended after the "phones: the chrome is solid" block on purpose: nothing
   here sets backdrop-filter, blurs a card or a row, or adds an animation. */
.fv-meta .attach-ver-chip {
  flex: 0 1 auto; min-width: 0; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ================================================================
   the panels pass: the item card, the viewer and the panels
   UIUX-AUDIT G6, G7, G15, G16 and defect 39's shape in the profile form.
   (Unnumbered on purpose: the parallel stream had already taken "phase-72-b"
   for the button tiers at :5494 by the time this landed. The lead numbers it.)

   Inserted BEFORE the phase-72-a shape-language block on purpose (two streams
   were editing app.css in the same hour and the other one appends at the end),
   and it is still after the "phones: the chrome is solid" block at :3323, which
   is safe: nothing here sets backdrop-filter, blurs a card or a row, adds a
   keyframe, touches :focus or --focus-ring, or colours data with a UI hue.
   Every selector here is disjoint from phase-72-a's (.btn*, the radius tokens,
   .view-tab*, .vt-ind, the chips).
   ================================================================ */

/* ---- G7: a caption is a caption, not a column ---------------------------
   `.kpi-s` (:2419) carried `white-space: nowrap; overflow: hidden;
   text-overflow: ellipsis`, so the tile captions were cut mid-word: "this
   month · local e…", "466 documents with f…".
   The worst case is READ, not guessed, because every one of these strings is
   generated: the longest on Admin > Workspace is "this month · local engine"
   (panels.js, the Claude-actions tile) at 25 characters of 11px mono, roughly
   160px, next to "N runs this month" and "anyone with the link"; the dashboard
   adds "N documents with files" (views.js). The tile's content box is about
   120px at the grid's 150px track minimum (:2410) and about 135px in the
   two-column phone layout (:2455), so the longest strings overflow at every
   width the app has, which is why raising a width would only move the cut.
   Wrapping removes it instead, and nothing truncates, so no tile can hide a
   word. Tile heights stay level because grid items stretch to their row. */
.kpi-s { white-space: normal; overflow: visible; text-overflow: clip; line-height: 1.35; }

/* ---- G6: the avatar colour row is one component ------------------------
   The profile's row rendered eleven 24px circles and then the native
   <input type="color"> at 34x24 with a 7px radius (:2729) — the audit's "one
   wide blue rectangle". It was never the selected swatch; it is the custom
   colour control, and it now wears the same circle as its neighbours, with the
   ringed treatment the new-workspace modal's `.lab-swatch` uses (:2348, :3059)
   so "one of the twelve" and "any other colour" stay distinguishable at a
   glance. The input keeps every behaviour it had: it is still the control,
   still focusable, still opens the OS picker. Laying it over the circle at
   zero opacity is the only way to restyle a colour input's own swatch, and it
   is exactly what `.lab-swatch input` already does at :2349.
   The colour shown is DATA (the twelve chip colours, DESIGN_TOKENS §0b): it is
   read from the value the person holds, never from a UI hue.
   `.color-field-t` replaces the <label> element that wrapped this row, which is
   defect 39's fault in this form: a label whose first labelable descendant is a
   <button> activates that button, so clicking the words "Avatar colour" pressed
   the first swatch. The typography is the base `label` rule's (:363). */
.color-field-t { display: block; font-weight: 600; font-size: 13px; color: var(--text-soft); }
.pr-swatch {
  position: relative; width: 24px; height: 24px; border-radius: 50%; flex: none;
  margin-left: 6px; background: var(--pr-c, var(--surface-3)); cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12), 0 0 0 2px var(--surface-1), 0 0 0 3.5px var(--line-strong);
  transition: transform var(--dur-micro-2) var(--ease-spring);
}
.pr-swatch:hover { transform: scale(1.08); }
.color-row .pr-swatch input[type=color] {
  position: absolute; inset: 0; display: block; width: 100%; height: 100%;
  margin: 0; padding: 0; border: none; background: none; opacity: 0; cursor: pointer;
}
/* a swatch is a target before it is a decoration: 24px is a desktop size */
@media (max-width: 700px) {
  .color-row { gap: 10px; }
  .color-row .color-dot, .color-row .pr-swatch { width: 32px; height: 32px; }
}

/* ---- G15: a Notes value is a sentence, not a column --------------------
   Half of this is CSS and half of it is not, so this says which is which.
   (1) In the item card the property cell now carries `data-type` (panels.js),
   the same attribute the board's own cells carry, and a text value stops being
   a one-line ellipsis there: the card has 108px of label and the rest of a
   360px column to spend, and his real Notes values are sentences ("Supersedes
   legacy WOR SOP 023 under standardized SOP architecture…"). Only `text` wraps.
   Every fixed-width chip type (status, people, rating, files) is untouched, so
   nothing reflows around a pill.
   (2) The editor that opens on click is board.js's `openCellEditor`, whose
   "text" case builds a single-line <input>. CSS cannot make an input
   multi-line, so what is fixed here is its WIDTH: the box was 220px (:1158)
   for a column that is 280px of prose. `#cell-input:not([type])` is the text
   case exactly — the number and date editors reuse the same id with
   `type="number"` and `type="date"`. A real multi-line editor needs a
   <textarea> in board.js and is reported, not stubbed. */
.ic-props .cell.inline[data-type="text"] { align-items: flex-start; padding-top: 4px; padding-bottom: 4px; }
.ic-props .cell.inline[data-type="text"] .cell-text {
  display: block; white-space: normal; overflow-wrap: anywhere; text-overflow: clip; max-width: 100%;
}
.popover .edit-box > input#cell-input:not([type]) { min-width: 360px; }
@media (max-width: 700px) { .popover .edit-box > input#cell-input:not([type]) { min-width: 0; } }

/* ---- G16, the half that is provable from source ------------------------
   NOT a fix for the glow the audit describes: I could find no rule that paints
   a radial glow behind a disabled `.menu-item`, and a guess in CSS would be
   worse than the report. What IS provable by reading the cascade is that a
   REFUSED row still answers the mouse, which is the same complaint ("as though
   something there is alive") from a different cause. `.menu-item:hover >
   .ic:first-child` (:2216) sets `background` AND `color: var(--brand)`;
   `.menu-item:disabled > .ic:first-child` (:2219) is later at equal
   specificity but only resets `background`, so the brand colour survives on a
   disabled row, and `:2250` runs the `icHover` bounce on it as well. A dead
   "Move right" therefore turns brand blue and jumps under the pointer. Both
   stop here; the row's own background was already cancelled at :1094. */
.menu-item:disabled:hover > .ic:first-child { color: var(--text-2); animation: none; }

/* ================================================================
   phase-72-a: THE SHAPE LANGUAGE
   DESIGN_DIRECTION-APP.md D3, derived from the landing's shipped code
   (public/css/welcome.css), which D1 makes this app's specification.

   The rule, once: a capsule marks something that STANDS ON ITS OWN and is
   meant to be pressed or navigated by — the single create action at any size,
   the view-tab track and its lozenge, count badges, toasts. A control set INTO
   a bar or a panel takes 9px, dropping to 7px at about 30px tall or smaller.
   Anything carrying DATA takes 7px. A table cell stays square. The pill is a
   signal, not decoration: if everything is a capsule, nothing is, which is why
   there is deliberately no blanket `border-radius: var(--r-pill)` on .btn here.

   Appended after the "phones: the chrome is solid" block (app.css :3312) on
   purpose, and safe there: nothing below sets backdrop-filter, blurs a card or
   a row, adds a keyframe, or colours anything. Two earlier PHONE rules would
   have been out-ranked by the plain rules below and are restated at the foot of
   this block instead: the rail button that squares off into the bottom bar, and
   the modal that becomes a bottom sheet.
   ================================================================ */

/* ---- the view-tab lozenge's two tokens, welcome.css:915-917 (dark) and
        :938-940 (light), under the same names. The app's :root is the LIGHT
        theme and [data-theme="dark"] overrides it, so light is declared first
        and dark second; both are equal specificity and order settles it. ---- */
:root {
  --track-bg: rgba(0, 0, 0, .04);
  --loz-bg: linear-gradient(180deg, #fff, #f5f6f8);
  --loz-sh: inset 0 1px 0 #fff, 0 0 0 .5px rgba(0, 0, 0, .1), 0 2px 6px -2px rgba(0, 0, 0, .18);
}
[data-theme="dark"] {
  --track-bg: rgba(255, 255, 255, .04);
  --loz-bg: linear-gradient(180deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, .05));
  --loz-sh: inset 0 1px 0 rgba(255, 255, 255, .28), inset 0 -1px 0 rgba(0, 0, 0, .25),
            0 0 0 .5px rgba(255, 255, 255, .1), 0 2px 8px -2px rgba(0, 0, 0, .45);
}

/* ---- buttons -------------------------------------------------------------
   THE ORDER HERE IS LOAD-BEARING and there is deliberately no `.btn.sm` radius
   rule. `.btn.sm` (:396) and `.btn.primary` (:383) have equal specificity and
   .btn.sm is later, so `.btn.sm { border-radius: var(--r-sm) }` would square
   off the 32px CREATE button — the one control this whole grammar exists to
   protect (welcome.css:437, `.btn.ink.sm`, 32px and still a capsule). The base
   .btn carries 9px to every size and variant instead, and .btn.primary takes
   the capsule back at (0,2,0). Do not "simplify" this into a .btn.sm rule. */
.btn { border-radius: var(--r-sm); }
.btn.primary { border-radius: var(--r-pill); }

/* the primary recipe, welcome.css:437-440: a top light gradient and a hairline
   inner rim, at pill radius, label at 600 (the base .btn already sets 600).
   --brand-fill is kept rather than the landing's hardcoded #0070c1 so the two
   themes stay shared; the token happens to be #0070c1 in both today.
   Contrast, measured: white on the flat fill is 5.14:1; the gradient is .16
   white fading to 0 at 60% of the height, so the worst pixel row under the
   label (the cap top of a 32px button) composites to #0f7ac5 at 4.55:1 and the
   baseline end of the glyph sits at 5.14:1. The button's top 1-2px reaches
   3.87:1 and carries no text; as a component boundary 1.4.11 asks 3:1. */
.btn.primary {
  background: linear-gradient(180deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, 0) 60%), var(--brand-fill);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .3), inset 0 0 0 .5px rgba(255, 255, 255, .18);
}

/* THE HOVER IS REPLACED, NOT STACKED (D2's warning: a lift plus a new hover
   treatment makes the button rise and swell at once). The translateY(-1px)
   lift is gone from every .btn; hover answers with GROUND and LIGHT, press
   answers with a small scale, which is the landing's language
   (welcome.css:288-291 transitions colour only, .rail-btn:active scales).
   The plain .btn had no hover background at all before — the lift WAS its
   feedback — so it gains one here. The variant hovers (.ghost, .ink, .primary)
   are all (0,3,0) and keep winning over this (0,2,0) rule whatever the order.
   :NOT(:DISABLED) IS ON EVERY RULE IN THIS BAND ON PURPOSE. `.btn:disabled`
   (:400) sets opacity and cursor but NO background, so any hover rule that
   names a background repaints a disabled control and it lights up as though
   it were pressable - and the tier rules are (0,3,0), so they beat :disabled
   on specificity whatever the order. The live example is the Template
   center's "Use template", which is .btn.quiet AND disabled for a viewer
   (panels.js:1151); six ghost buttons ship disabled too (dash-add-widget,
   dash-reset, quick-clear, snap-labels, tl-prev, tl-next), and .btn.ghost
   lit up on hover for all of them before this phase. All three tiers now
   carry the guard, press included, so a later session must not "simplify"
   the selector back to a bare :hover.
   Transform needs no guard: `.btn:hover`(:381) and `.btn:disabled`(:400) are
   both (0,2,0) and :400 is later, so a disabled button already refuses the
   lift; the guard here is what keeps the SURFACE from moving. */
.btn:not(:disabled):hover { transform: none; background: var(--surface-3); }
.btn:not(:disabled):active { transform: scale(.98); }
/* the primary keeps its FILL on hover in both themes and answers with its edge
   instead. --brand-fill-hover is #0a7fd6 in dark, which is white-on-blue
   4.18:1 — below 4.5 — so lightening the fill on hover would have taken the
   label under the floor on the app's most-pressed control. The token is left
   defined and its ONE reference is the superseded .btn.primary:hover at :384,
   which this rule overrides: nothing the browser paints reads it any more, but
   it is not textually dead, so `grep --brand-fill-hover` still finds two hits.
   Retiring it means removing that rule too, which is the groundskeeper's own
   phase and not a line to pull out from under a phase in review. */
.btn.primary:not(:disabled):hover {
  background: linear-gradient(180deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, 0) 60%), var(--brand-fill);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .42), inset 0 0 0 .5px rgba(255, 255, 255, .3),
              0 4px 14px -4px color-mix(in srgb, var(--brand-fill) 55%, transparent);
}

/* ---- the third tier: .btn.quiet (phase-72-b, D3b) ------------------------
   THREE TIERS AND NO OTHERS. primary = the surface's single create or commit,
   filled, capsule at every size. quiet = still an action but not the subject,
   a rim and nothing else. ghost = dismissals, toggles, tertiary text; no rim.
   .btn.outline was the near-miss this replaces: a 1px solid --primary is a
   second blue object competing with the one fill the grammar protects, and
   after panels.js:2166 moves to .quiet it has no consumer left in the product
   (dead, for the groundskeeper; not deleted here).
   THE RIM IS AN INSET BOX-SHADOW, NEVER A BORDER. A border grows the layout
   box by 2px on every de-filled button, and a fixed-width control overflowing
   by a couple of pixels is what phase-52 paid for; the inset rim is drawn
   inside the box and is the landing's own idiom (welcome.css:183, 639, 648),
   already used at app.css:846.
   A WASH AND A HAIRLINE TOGETHER, NEVER A RIM ON ITS OWN. This is the
   landing's quiet object (welcome.css:639, 648: `background: var(--fill-on)`
   WITH `box-shadow: inset 0 0 0 1px var(--line)`), and D1 makes the landing
   the specification. The first build of this modifier took the rim alone on a
   transparent ground; the landing gets away with a hairline fainter than ours
   precisely because the wash carries the boundary and the hairline only
   finishes the edge. So a quiet button is the base .btn's own surface minus
   the brand: a button, not an outline.
   THE NUMBERS, RECORDED BECAUSE THIS WAS DECIDED AND NOT MISSED. The rim is
   --border (=> --line-1), an edge and never --primary, and it measures 1.26:1
   light and 1.52:1 dark against the button's own ground (1.44 / 1.50 against
   the page). WCAG 1.4.11 asks 3:1 of a boundary that is REQUIRED to identify a
   control; here three things identify it together - a label at 15.2:1 light
   and 13.0:1 dark, the surface's own step off the page, and the hairline - and
   the owner-approved landing idiom above is what the app is porting.
   --line-strong (app.css:25, annotated ">=3:1 on its grounds") is the
   one-token swap if a later ruling wants the stricter floor on buttons; it was
   NOT taken here because a hard edge turns every de-filled button into the
   .btn.outline this tier exists to replace, and meeting a number by breaking
   the grammar is not a fix.
   --hover is an alias of --surface-2, so with the wash in place it would have
   been a no-op: hover steps to --surface-3 instead, the same step the base
   .btn takes in phase-72-a (1.09:1 light, 1.17:1 dark).
   The radius is stated here rather than inherited from .btn so a later phase
   adding `.btn.sm { border-radius }` cannot square it by accident; (0,2,0)
   never collides with .btn.primary because .primary.quiet does not render. */
.btn.quiet {
  background: var(--surface-2); color: var(--text);
  box-shadow: inset 0 0 0 1px var(--border);
  border-radius: var(--r-sm);
}
.btn.quiet:not(:disabled):hover { background: var(--surface-3); }
/* A SIDE EFFECT OF GUARDING THE GHOST HOVER, caught before it shipped and
   written down rather than left to source order. `.btn.ghost.on` (:387) is the
   blue ground a tool chip wears while its filter, sort or density setting is
   in force, and it used to beat `.btn.ghost:hover` (:386) only by being one
   line later at the same (0,3,0). Adding :not(:disabled) to that hover made it
   (0,4,0), so pointing at an active chip would have stripped its "on" ground
   to plain --hover. This restores the previous outcome as a rule that says so:
   an on chip keeps its ground under the pointer. */
.btn.ghost.on:hover { background: var(--primary-soft); }

/* ---- icon-only and rail buttons -----------------------------------------
   30px and under is the 7px band (welcome.css:393). The rail button is 40px in
   this app against the landing's 36px, but its JOB is the same, so it wears
   the rail's 9px rather than a capsule; a capsule there would have read as a
   second create button down the left edge. */
.icon-btn { border-radius: var(--r-xs); }
.rail-btn { border-radius: var(--r-sm); }

/* ---- fields --------------------------------------------------------------
   The search field is 9px on the landing (welcome.css:434). The base input
   band moves with it rather than leaving 6px fields beside 9px buttons in the
   same modal. The fan rings read `border-radius: inherit` and follow for free.
   WHAT THIS RULE ACTUALLY REACHES, since its selector is the weakest thing in
   the file: `input, select, textarea` is a type selector at (0,0,3), so it is
   a FLOOR under the fields nothing else claims, and every class-level radius
   in app.css still beats it whatever the order. `.edit-box input` (:2223),
   `.auto-line input` (:2367) and `.role-select select` (:2712) stay at 8px on
   purpose - the 1px is invisible and chasing it would have put three more
   rules in a phase that is already wide. Do not "finish the job" by sweeping
   them; they were left, not missed.
   .drawer-search hardcoded 10px in TWO places, itself and its fan ring; they
   have to move together or the ring squares off around a rounded field.
   .board-search::before is NOT restated: it already inherits. */
input, select, textarea { border-radius: var(--r-sm); }
.board-search, .board-filter { border-radius: var(--r-sm); }
.drawer-search { border-radius: var(--r-sm); }
.drawer-search::before { border-radius: var(--r-sm); }

/* ---- chips: pressable ones are controls, the rest are data ---------------
   A chip you press is a control set into a panel and takes the 9px band with
   the small buttons (D3's quick-filter row). A chip that reports a value is
   data and takes 7px (welcome.css:495). .pill-count and the other count
   badges keep their capsule: a number on a control is a lozenge, not a chip
   (welcome.css:402), and they already read --r-round, which is the same 999px.
   .chip-btn and .tpl-chip were capsules and are NOT any more — that is the
   point of the phase. Three capsules in an export modal beside one filled
   create button is exactly the "if everything is a capsule, nothing is"
   failure D3 names. */
.qf-chip { border-radius: var(--r-sm); }
.chip-btn { border-radius: var(--r-sm); }
.status-pill { border-radius: var(--r-xs); }
.tpl-chip { border-radius: var(--r-xs); }
/* The four raw capsules, moved here after qa read the shipped result. An
   earlier plan left them for "a later phase", which would have shipped a
   grammar that is not true of its own code: .act-auto is drawn once per
   automation-made entry, so the Activity modal - the one surface this phase
   also re-tiered, by taking the fill off Close - would have shown a COLUMN of
   999px capsules beside the .status-pills squared to 7px two lines above.
   .rm-tag is drawn once per removed member, .docrev-tag is a revision label on
   the item card and .ev-chip is a value on the profile. None is pressable,
   none stands on its own, so all four are data and take 7px.
   .gjump-chip (:3258) is deliberately NOT in this list: it is navigation, one
   per group, the same family as the view-tab track, and it keeps its capsule.
   Whether twelve of them across the owner's board head reads as noise is a
   question for the browser walk, not for a silent edit here. */
.act-auto { border-radius: var(--r-xs); }
.rm-tag { border-radius: var(--r-xs); }
.docrev-tag { border-radius: var(--r-xs); }
.ev-chip { border-radius: var(--r-xs); }

/* ---- panels: menu rows quiet, popovers 14, the big surfaces 20 -----------
   A menu row is 44px and still takes 9px (welcome.css:206): the grammar reads
   a control's JOB, not its height, and a row set into a panel wears the
   container's quiet corner. .popover.sheet (:2742, the phone sheet) is
   (0,2,0) and keeps its own 16px top corners whatever the order here. */
.menu-item { border-radius: var(--r-sm); }
.popover { border-radius: var(--r-md); }
.peek { border-radius: var(--r-md); }
.modal { border-radius: var(--r-lg); }
.card, .admin-card, .dw { border-radius: var(--r-lg); }
.toast { border-radius: var(--r-pill); }

/* ---- the view-tab strip becomes a track with a sliding lozenge -----------
   welcome.css:423-432. The strip was a flat row with a 2px underline; it is
   now the app's existing sliding-plate language (.seg-btn.on, :2625) at tab
   scale, which is what makes the two read as one family.
   No JS changed: board.js already renders .vt-ind last inside .view-tabs and
   placeViewIndicator() already measures the active .vt-wrap's offsetLeft and
   offsetWidth. offsetLeft is measured from the track's padding edge, so the
   3px of new padding lands the lozenge exactly over the tab with no arithmetic
   anywhere, and the scrollLeft handling at 701-1000px and on phones is
   untouched. The lozenge is pinned top and bottom rather than given a height,
   so it follows the tab through comfortable, compact and the 44px phone
   target without a second number to keep in sync.
   The 6px that used to be padding-bottom is now split 3 + 3: .board-switch-row
   aligns on flex-end and .board-tools carries padding-bottom: 6px, so the TAB's
   own bottom edge has to stay 6px off the row's, or the tabs and the toolbar
   beside them stop lining up. 3px of that is the track's new padding and 3px is
   the margin under it, which lands the tab exactly where it was and grows the
   head by the 3px of padding above it.
   prefers-reduced-motion already names .vt-ind (:2791) and still does: the
   lozenge appears in place there instead of sliding. */
.view-tabs {
  padding: 3px; margin-bottom: 3px; border-radius: var(--r-pill);
  background: var(--track-bg); box-shadow: inset 0 0 0 1px var(--line-2);
}
.view-tab { border-radius: var(--r-pill); }
/* NO Z-INDEX ON .view-tab, AND THIS ONE COST A CLICK. The first build of this
   rule copied the landing's `.view-tab { position: relative; z-index: 1 }`
   (welcome.css:425) to lift the tabs off the lozenge. The landing has one
   element per tab; WE have two - board.js wraps the active tab and its caret
   button together in .vt-wrap and positions .vt-menu absolutely over the tab's
   right padding. A z-index on a FLEX ITEM creates a stacking context even with
   no position of its own, so the <a> was lifted above its own sibling button
   and ate every click on it: qa's elementsFromPoint over the caret returned
   [A.view-tab, svg, BUTTON.vt-menu] and the view menu - Duplicate view, Remove
   view, Move left/right, Column width - was dead on every board in the
   product. pointer-events on .vt-ind was NOT the problem and checking it is
   what let this through.
   THE PAINT ORDER NOW, so nobody re-adds it: .view-tabs is positioned with
   z-index auto and creates no stacking context, so everything below sorts in
   ONE context. .vt-ind is the last child, absolutely positioned, z-index auto,
   so it paints in tree order ABOVE anything that does not raise itself - which
   is why the lozenge cannot simply be lowered instead (z-index: 0 paints in
   the same step as auto and would change nothing; a negative z-index would
   drop it behind .view-tabs' own background and make it invisible). So the
   track's CHILDREN are raised and the active <a> is not: .vt-wrap, the
   non-active tabs and the add button take z-index 1 and paint above the
   sliding plate, and inside .vt-wrap the <a> and .vt-menu both keep z-index
   auto, so they paint in tree order and the caret - written last - is on top
   and takes its own clicks. */
.view-tabs > .view-tab, .vt-wrap, .view-add { position: relative; z-index: 1; }
/* phase-143: the tabs now sit one level down, in .p143-vts (board.js); the
   same lift, so the paint order above is unchanged */
.p143-vts > .view-tab { position: relative; z-index: 1; }
/* the add button lives INSIDE the track, so it is a member of it and takes the
   track's shape; at 7px it read as a square tile dropped into a capsule */
.view-add { border-radius: var(--r-pill); }
.vt-ind {
  top: 3px; bottom: 3px; height: auto; border-radius: var(--r-pill);
  background: var(--loz-bg); box-shadow: var(--loz-sh);
  /* it was a 2px bar under the tabs and is now a plate BEHIND them: it must
     never take a click off the tab or its menu button (welcome.css:430) */
  pointer-events: none;
}

/* ---- the two phone rules the plain selectors above would have out-ranked -- */
@media (max-width: 700px) {
  /* the rail becomes the bottom bar: full-bleed cells, no corners */
  .rail-btn { border-radius: 0; }
  /* the modal becomes a bottom sheet and keeps only its top corners (this was
     18px; --r-lg is 20px, which is the sheet band the grammar names) */
  .modal, .modal.wide, .modal.wide.tall { border-radius: var(--r-lg) var(--r-lg) 0 0; }
}
/* THE TRACK'S OWN CORNER IS A CLIP, and below 1000px the strip scrolls
   inside it (overflow-x: auto at :1456 and :3833), so a control can be pushed
   under the curve and cut. qa measured the register at 768: scrollWidth 700
   against clientWidth 664, and scrolled to the end the add-view button's right
   edge sat 3px from the track's edge while the capsule's corner radius is
   18px, which sliced its top-right and bottom-right corners off. Nothing could
   be clipped before this phase because the strip had no radius at all.
   The end padding is the fix and 16px is not a guess: the corner is a circle
   of radius r centred r in from the end, so a child whose edge stops p from
   the end is cut to a depth of r - sqrt(r^2 - (r-p)^2) and survives only while
   that is less than the 3px of top padding above it. At the 36px desktop track
   (r=18) 12px would do; at the 50px phone track (r=25) it takes 14px, so 16px
   clears both with room. It is scoped to the widths that actually scroll, so
   the desktop track keeps its 3px and the first tab's pill stays concentric
   inside the track's pill, which is what makes the shape read as one object.
   offsetLeft is measured from the padding edge, so placeViewIndicator needs no
   arithmetic for this and the lozenge still lands on the tab. */
@media (max-width: 1000px) {
  .view-tabs { padding-left: 16px; padding-right: 16px; }
}
/* landscape compaction (the block at :3343) tightened the strip's 6px of
   bottom padding to 2px to buy vertical room; the track's own padding owns
   3px of that now, so the rest comes off the margin */
@media (max-width: 900px) and (max-height: 440px) {
  .view-tabs { margin-bottom: 0; }
}


/* ════════════════════════════════════════════════════════════════════════
   phase-73 — THE REGISTER: the name gets its width, the controls stop
   hiding, the two sides of a column agree, and the header reads as a head.

   Appended at the very end of the file on purpose. Nothing in this block
   declares a backdrop filter, blurs a card or a row, or animates anything
   but opacity and colour, so the "phones: the chrome is solid" block
   (:3323) being EARLIER than this one costs nothing.
   ════════════════════════════════════════════════════════════════════════ */

/* ---- 1. the name fills its cell -----------------------------------------
   The name is a flex item taking what the 320px name column left after 22px
   of td padding, a 44px open button, a 26px row menu and 2x2px of gaps: 224px
   of box, 211px of text, which ellipsised nearly every document name on the
   register. board.js NAME_COL_W is now 380 (and only bites on a board wide
   enough to scroll), and the open button gives back the 10px it was holding
   for a count it does not have: 44 -> 34 on a row with no updates, which is
   the width the phone has used since phase-45 (:1844). A row that DOES have
   updates sizes to its badge instead of clipping it - at 44px a three-digit
   count already overflowed the button. The row menu stays pinned to the
   right edge either way, so the column still reads as a column.
   Usable name box 224px -> 294px at 1440 on the register; text 211 -> 281. */
td.col-name .chat-btn { width: 34px; }
td.col-name .chat-btn.has { width: auto; min-width: 34px; padding: 0 5px; }

/* ---- 2. a control the owner must find is visible at rest ----------------
   "The 3 dots to the right do not show unless i randomly hover it, other than
   that I do not know its even there." Hover does not exist on a phone and
   does not exist for a newcomer on any device, so every one of these rests
   visible and uses hover only to CONFIRM, never to appear.
   The open button was painted in --cell-empty (:848), which is the token the
   table uses to say "this cell has nothing in it" - an unset dot, an empty
   star, an empty checkbox. It resolves to --text-3, so the defect was not the
   value but the grammar: a live control was speaking the language of absence.
   It now names its own colour, keeps the --text-2 step that marks a row that
   really does carry updates, and answers a row hover. */
td.col-name .chat-btn { color: var(--text-3); }
td.col-name .chat-btn.has { color: var(--text-2); }
.item-row:hover td.col-name .chat-btn { color: var(--text-1); }
/* the row menu rested at .75 of --text-2; it now rests at full strength in
   the quieter --text-3 (about the same weight on the page) and keeps the
   ground, the ink and the rim it already grew on row hover (:863, :864).
   This also beats the .55 the phone dropped it to at :1847. */
.row-menu { opacity: 1; color: var(--text-3); }
/* .group-head .icon-btn (:799) silently overrode .group-head .group-menu
   (:796) at equal specificity, so the group menu really rested at .7 of
   --text-3, not the .9 of --text-2 that :796 appears to set. Stated here,
   later, where it wins. */
.group-head .group-menu { opacity: 1; color: var(--text-2); }
/* the worst hover-only affordance in the product: the workspace menu in the
   sidebar rested at opacity 0 (:668). Nothing else in the sidebar renames,
   recolours or deletes a workspace. */
.ws-dots { opacity: 1; color: var(--text-3); }
.side-title.ws:hover .ws-dots, .ws-dots:hover { color: var(--text-1); }

/* ---- 3. a heading sits over its own values ------------------------------
   The two sides of a column justified to different insets: a value sat 6px
   in (.board-table td { padding: 0 } :831 + .cell-text { padding: 0 6px }
   :1747) and its heading 14px in (th { padding: 9px 6px } :815 + .th-in
   { padding: 0 2px } :1928 + .col-head { padding: 3px 6px } :1929), so every
   left-aligned heading started 8px right of its column and every right-
   aligned heading ended 8px left of its figures. The heading's two outer
   boxes give up their padding and .col-head's own 6px carries the inset, so
   both sides now land on 6px exactly. The td side is NOT touched, so no
   column's worst-case value string moved by a pixel.
   Only horizontal padding is named, so the 9px that sets the header's height
   - and the 4px/5px the two compact-density rules (:1697, :3012) put in its
   place - are untouched. th.col-name keeps its own 16px (:821, higher
   specificity). */
.board-table th { padding-left: 0; padding-right: 0; }
.th-in { padding: 0; }
/* the sort control rested at width 0 / opacity 0 and grew to 20px + 2px of
   margin on hover, which physically MOVED the heading under the pointer -
   nineteen headings that shuffle as the mouse crosses the row. It is out of
   the flex flow now and pinned to the column's edge, so it can neither move
   the label nor be moved by it, and it rests visible at .55 instead of
   invisible at 0. The 22px it reserves is bought back twice over on hover
   (the label used to lose 22px the moment the pointer arrived) and paid for
   at rest by the 12px the two outer boxes just gave up: net 10px of label
   room on a left- or right-aligned column, and on a CENTRED one the reserve
   is symmetric so the heading stays on the same centre line as the chips
   below it, which is what "the rows are off-centre" was about.
   Rest is an opacity, not a colour, so the three rules that already lift it
   (:1939 hover/focus/sorted) and the two that colour it (:1940 direct hover,
   :1941 sorted) all keep working untouched. */
/* CORRECTED 2026-09-19, third state of this rule in one day. Read all three
   before changing it again.
   (1) Built as `width: 0 -> 20px on hover`, which MOVED the heading under the
       pointer on all nineteen columns.
   (2) Replaced with an absolute, edge-pinned control plus a 22px reserve on
       .th-in AND a matching floor in board.js fitWidths. The floor inflated
       every narrow centred column (a 280px Risk level holding one chip) and
       the owner reported it live: "the length of columns are not auto
       adjusting either".
   (3) The lead then reverted BOTH halves, which put the control back in flow
       on ONE side of a centred .th-in, so a centred heading sat 11px left of
       its own chips - permanently, where (1) was only intermittent. qa
       measured exactly 11.0px on all six centred columns of the register and
       all three of a narrow board, and returned NO SHIP.
   The fault in (3) was reverting the reserve as well as the floor. Only the
   floor was wrong: a column must size to its DATA. So the control is out of
   flow again, and the heading is capped instead of padded - a cap constrains
   the label without moving the box it is centred in, so a centred heading
   keeps the chips' centre line and a side-aligned one keeps its 6px inset.
   A long centred label now ellipsises where it used to run full width; that
   is the price of a control the owner can see, and the whole title is still
   in the column menu the heading opens. */
.th-sort {
  position: absolute; top: 50%; left: auto; right: 2px; transform: translateY(-50%);
  width: 20px; margin: 0; opacity: .55;
}
/* the cap clears the control on both sides of a centred heading and on the one
   side a side-aligned heading can collide with it */
.board-table th .col-head { max-width: calc(100% - 44px); }
.board-table th[data-type="text"] .col-head,
.board-table th[data-type="link"] .col-head,
.board-table th[data-type="dropdown"] .col-head,
.board-table th[data-type="tags"] .col-head,
.board-table th[data-type="number"] .col-head,
.board-table th[data-type="date"] .col-head { max-width: calc(100% - 22px); }
/* a right-aligned column reads to its right edge, so the control goes to the
   OTHER side or the label would end 28px in while its figures end at 6px.
   margin-left is restated because :1937 sets it on hover and, with left
   rather than right resolved, 2px of margin would jump the button. */
.board-table th[data-type="number"] .th-sort,
.board-table th[data-type="date"] .th-sort { order: -1; margin: 0 2px 0 0; }
/* touch has no hover to lift it with (:2490 is earlier than this block now) */
@media (hover: none) { .th-sort { opacity: 1; } }
/* the name column's own heading is the one head that is not a .col-head, and
   its 6.5px (:4993) left its word 2px right of the names underneath it.
   Matched to .item-name's 4.5px (:4976). Padding only: the fan ring, its
   width, its colour and the hover tint are untouched. */
.th-label { padding: 1.5px 4.5px; }

/* ---- 4. the column header reads as a header -----------------------------
   LIGHT is already as high as it can go and stays exactly where phase-61 put
   it: --surface-2, 1.14:1 against the white table body. It cannot go further
   because the sorted column's heading is --brand (#0070c1) at 11px (:765),
   which measures 4.50:1 on --surface-2 and 4.15:1 on the next step up - under
   the 4.5 floor for small text. So light takes the rest of its separation on
   an EDGE instead of a fill, which is the same device the group band uses.
   DARK was left behind at phase-61's 55% mix, 1.07:1 against its table body,
   which is under the level an eye registers a boundary at all - a nineteen-
   column register with no head. It takes the full --surface-3 step, 1.32:1,
   and its --brand (#59b2f2) measures 5.34:1 there, well clear.
   The edge is --line-strong, the token whose whole job is a boundary at 3:1
   or better: 3.16:1 on the light strip, 3.07:1 on the dark one. Same
   declaration in both themes. */
[data-theme="dark"] { --th-strip: var(--surface-3); }
.board-table th { border-bottom-color: var(--line-strong); }
/* .col-head:hover took --hover (:828), which IS --surface-2 - the same colour
   the strip has worn since phase-61, so hovering a column heading in light
   has painted an invisible square for eleven phases, and raising dark to
   --surface-3 would have made its hover a dark hole. --hover-ground is a
   translucent lift, so it reads on any ground; the sort control beside it
   (:1940) and the item-label head (:2051) already use it. */
.board-table .col-head:hover { background: var(--hover-ground); }


/* ============================================================ phase-72-b
   ONE EDGE AT A TIME, AND THE SAME EDGE EITHER WAY

   The owner, with a screenshot: "the thin rainbow edge only shows when I move
   the cursor around, but not when I move around cells using my arrows - the
   blue edge shows, which is weird - and there seems to be a bug causing some
   type of disconnection as if it is still counting the cursor's last movement
   while I am moving around the board's cells using the arrows." The screenshot
   showed both at once: a blue --focus-ring on the cell his arrows were on, and
   a five-hue fan on a cell four rows down and several columns across, which
   was simply the last cell his mouse had passed over. Two defects in one
   picture.

   1. :hover sticks. It stays on whatever the pointer last rested on, for as
      long as the pointer rests there, so every arrow-key move painted a second
      edge somewhere else on the board. While html.kbd-nav says the keyboard is
      driving (core.js), the hover fan stands down. This is the rule design.md
      already wrote for the rename - "a cell's hover fan stands down while
      anything inside it holds focus, so a rename never paints two rings at
      once" - applied to the whole board rather than to one control.

   2. The same act should look the same. "I am on this cell" wore the fan from
      the pointer and a plain --brand outline (:3730) from the keyboard. It now
      wears the fan either way.

   WHAT THIS DOES NOT TOUCH, because the focus treatment is locked (design.md,
   2026-09-15): no text-entry control is touched - .board-name, .group-title,
   .th-label, .item-name, .sub-name and .ic-head h3 keep the phase-68 fan
   exactly as it is; --focus-ring is still one 2px --brand ring and is not
   redefined here; the quality floor at :1414 is untouched, so every link,
   button and contenteditable in the app answers focus exactly as it did; and
   nothing here is a per-control override - a td.cell is a NAVIGATION target,
   the one thing the lock does not legislate, and it moves from --brand toward
   the fan, which is the direction the lock prefers everywhere else.

   BOTH AT ONCE: a cell that is focused AND hovered paints exactly ONE edge,
   and it is the fan. There is only one ::before per cell, both states draw the
   same recipe into it, and the focus rules below are later and at least as
   specific, so they settle the cascade. The blue outline is gone from the cell
   entirely, so the two can never appear together.

   THE RENAME STILL WINS ITS CELL: the stand-down at :5068 covers
   td.col-name:focus-within, which is a different selector from anything here,
   and td.cell:focus-visible only ever matches the cell ITSELF - a
   contenteditable inside a cell holds the focus, so the cell does not match.

   COST on a 9,253-cell register: no new element, no new pseudo-element, no new
   stacking context, no listener per cell. These are three selectors against a
   pseudo-element that already exists on every cell.

   Appended after the "phones: the chrome is solid" block at :3323 on purpose:
   nothing here sets backdrop-filter, blurs a card or a row, or animates
   anything but --ring-a (an angle), so that block still wins on a phone. */
html.kbd-nav .board-table td.cell:hover::before,
html.kbd-nav .board-table td.col-name:hover::before { opacity: 0; animation: none; }

/* the keyboard's cell wears the fan, not the blue. Both themes are written out
   because the dark hover rule at :2298 carries an attribute selector and would
   otherwise out-rank a plain one; the light value is .9 and the dark 1, the
   same pair the hover ring has used since the ring was built. */
.board-table td.cell:focus-visible { outline: none; }
/* focus-ring: exempt - owner-approved 2026-09-15, the board table cell only.
   His words: "The cells when moving using the arrows are showing the thin
   rainbow edge, so that's fixed." D46 §4b takes the five-hue fan out of every
   other focus ring in the product and leaves this one exactly as it is (hard
   rule 8: a feature is not removed because a designer thinks it is wrong).
   The marker is here so the D46 checks pass it BY NAME and so no later phase
   can cite it as precedent for a second gradient ring. If the owner ever says
   the cell reads brown too, this comment and the rule below come out together
   and nothing else changes.
   SUPERSEDED 2026-09-22 by the owner, and the sentences above that say this
   is the ONLY fan ring no longer hold: "many details were lost like the thin
   rainbow edge, the blue edge keeps coming back." The five-hue fan is
   restored on every surface that carried it before phase-97 - the search
   boxes, the six contenteditable renames, the three translucent renames' ::after
   and the five menu row families - each with its own exemption marker. This
   cell is one of those surfaces, not the only one; what still holds is that
   the marker is LOCAL and exempts only the rule directly below it. */
.board-table td.cell:focus-visible::before {
  content: ""; position: absolute; inset: 0 0 -1px -1px; z-index: 1; pointer-events: none; border-radius: 0; padding: 1px;
  background: conic-gradient(from var(--ring-a), var(--logo-1), var(--logo-2), var(--logo-3), var(--logo-4), var(--logo-5), var(--logo-4), var(--logo-3), var(--logo-2), var(--logo-1));
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor; mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask-composite: exclude;
  opacity: .9; animation: ringSpin 2.6s linear infinite;
}
[data-theme="dark"] .board-table td.cell:focus-visible::before { opacity: 1; }

/* the spin stops, the ring does not (phase-68 §5). The reduced-motion block at
   :2484 turns the hover ring's animation off, but it is EARLIER than the rule
   above, so the focus ring needs its own - which is also the honest place for
   it: an indicator must never depend on an animation to be visible. */
@media (prefers-reduced-motion: reduce) {
  .board-table td.cell:focus-visible::before { animation: none; }
}


/* ============================================================ phase-73 §1
   LIQUID GLASS, STEP 1 — the material layer, and one pilot surface.

   The landing's material (public/css/welcome.css §12) ported into the app as
   the `lg-` primitive. This block is the ONLY definition of the material in
   app.css; the phase-39 `--glass` veil above (:92-93, :264-265, :2506, the
   @supports block at :2528-2553) is the OLD material and is replaced surface
   by surface, not all at once. Nothing above this line changed.

   THREE THINGS A LATER SESSION MUST NOT UNDO:

   1. THE THEMES ARE INVERTED BETWEEN THE TWO SHEETS. In welcome.css `:root`
      is DARK and `[data-theme="light"]` overrides it. In app.css `:root` is
      LIGHT (:10-13) and `[data-theme="dark"]` overrides it (:212, 37 uses).
      The tokens below are therefore written the other way round from their
      source. Copying welcome.css:894-942 verbatim swaps both themes.

   2. NO CARD SELECTOR AND NO ROW SELECTOR APPEARS IN THIS BLOCK, and none may
      be added. `backdrop-filter` on `.kanban-card` is 487 filter layers on the
      owner's register; phase-46 died at 120. Cards take the rim and the inner
      shadows (`.lg-card`) and no filter of their own.

   3. THE FILTER NEVER GOES ON A WRAPPER. `body`, `.app`, `.main`, `#app-root`
      are out of bounds: a backdrop-filter makes its element the containing
      block for every `position:fixed` descendant, and the four fixed overlays
      (core.js:525, board.js:3814, palette.js:132, core.js:556) are positioned
      with viewport maths. Glass goes on the floating pane itself, never above it.

   LEFT BEHIND DELIBERATELY, with the reason:
   - `--pic-bg --track-bg --loz-bg --loz-sh --hv-bg --hv-rim` (welcome.css:914-919,
     :937-942) are landing widgets, and three of them ALREADY EXIST in app.css for
     the view-tab lozenge. Copying the token block wholesale clobbers shipped work.
   - `@property --mo` (:891): the landing's scene-motion scalar. No scenes here.
   - `.asc-menu.gl` (:989): a landing nav class.
   - `.gl:not(.gl-ctl) > :not(.gl):not(.gl-pass){opacity:...}` (:976). It sets
     `opacity` on EVERY direct child, and `opacity < 1` makes a stacking context
     per child, which breaks `position:sticky` on `td.col-name` — the frozen name
     column of a 487-row register. Dropped globally. Where a surface genuinely
     needs its content to fade with the pane, it opts in per surface, as the
     pilot does below, and `.lg-pass` marks the child that opts out.
   - `*,*::before,*::after{transition-duration:0ms!important}` (:1180-1181):
     .claude/rules/css.md forbids a body-wide transition outright. Only the
     glass-specific reduced-motion lines come across.
   - The scroll-driven `--lg-sheen` writer (welcome-ui.js:1391-1398). The landing
     is one scrolling page; the app's register scrolls inside `.main` and, since
     D28 (2026-09-20), sideways inside `.board-scroll` — this said `.table-wrap`,
     which stopped being a scroll box in that phase — so a
     window-scroll angle would be wrong. The property ports with its static 118deg.
   - `welcome-field.js`. The app's ground stays `.app { --ambient, --surface-0 }`,
     so glass here is rim-led: the rim and the inner shadows are what reads as
     Apple glass, the displacement lens is the last tenth and lands in step 2.

   MEASURED, because there is no fill — the pane's colour is out = A*v + B:
   dark regular floor B = .061 against a page of .035, so a pane LIFTS (1.04:1);
   light regular ceiling A+B = .958 against a page of .988, so it SETTLES (1.07:1).
   Rim against THOSE, not against #000 or #fff:
     dark   rim-1 13.80:1 · rim-3 5.33:1 · rim-2 1.58:1   (white on the floor)
     light  rim-3  2.76:1 · rim-2 1.79:1 · rim-1 1.09:1   (ink on the ceiling)
   Light's white specular corner is 1.09:1 and CANNOT be raised: white at .95
   alpha over a ceiling of .958 has no brightness left to spend. Light's edge is
   carried by rim-2 and rim-3, which are ink. Do not chase it with a number. */

@property --lg-o    { syntax: "<number>"; inherits: true;  initial-value: 1; }
@property --up      { syntax: "<number>"; inherits: true;  initial-value: 0; }
@property --lg-sheen{ syntax: "<angle>";  inherits: false; initial-value: 118deg; }

:root {
  /* light "Studio" — see note 1 above: this is welcome.css:921-936, not :894-913 */
  --lg-tint: 255, 255, 255;
  /* THE BLUR WEIGHTS, raised 2026-09-21 (phase-97) against the owner's four
     macOS Tahoe references: 3/10/12px reads as a tinted rectangle, a system
     pane is nearer 25-30 CSS px and that is most of what makes one read as
     GLASS rather than as a panel. Safe to change on its own, and this is why:
     the pane's colour arithmetic is `out = A*v + B`, set by contrast(),
     brightness() and saturate(), which are measured per theme and per weight
     in phase-89 and read by the suite through the custom-property chain. A
     blur is a spatial average; it moves no mean luminance, so every contrast
     number in that block still holds. Cost is bounded by D46 §3a: at most one
     popover plus one docked panel filter at a time, never a row or a card. */
  --lgc-bl: 12px; --lgr-bl: 28px; --lgd-bl: 34px;
  /* the pane settles: A + B lands at .968 / .958 / .947 against a page of .988,
     so it reads a touch darker and a touch more saturated than the ground and
     nothing clips to flat white the way brightness alone did */
  --lgc-sat: 1.55; --lgc-bri: 1.40; --lgc-con: .39;
  --lgr-sat: 1.50; --lgr-bri: 1.54; --lgr-con: .25;
  --lgd-sat: 1.45; --lgd-bri: 1.57; --lgd-con: .22;
  /* light's edge is DARK, so the pane is defined against a white page. The white
     specular is kept only where a real edge catches light: the top-left corner. */
  --lg-rim-1: rgba(255, 255, 255, .95); --lg-rim-2: rgba(13, 15, 19, .26);
  --lg-rim-m: rgba(13, 15, 19, .18);    --lg-rim-3: rgba(13, 15, 19, .42);
  --lg-in-hi: rgba(255, 255, 255, .85); --lg-in-lo: rgba(0, 0, 0, .18);
  --lg-in-glow: rgba(255, 255, 255, .2);
  --lg-edge: rgba(13, 15, 19, .14); --lg-edge-1: rgba(13, 15, 19, .32);
  --lg-sh: .16; --lg-sh2: .06;

  /* motion, under NEW names so the app's --dur-* / --ease-* keep working until a
     surface is converted. One gentle in-out curve; nothing pops, snaps or bounces
     (welcome.css:61-62). --ease-settle is an overshoot and is not this. */
  --ease-lg: cubic-bezier(.4, .02, .2, 1);
  --lg-d0: 90ms; --lg-d1: 180ms; --lg-d2: 280ms;
  --lg-d3: 460ms; --lg-d4: 760ms; --lg-d5: 1150ms;
}
[data-theme="dark"] {
  --lg-tint: 14, 16, 20;
  /* dark spends the smallest contrast that still lets a pane LIFT: the filter is
     out = con*bri*v + bri*(1-con)/2, so at con 1 the additive term is zero and a
     pane can never be lighter than the page behind it, however bright it is made.
     A touch under 1 buys the lift; far under 1 flattens the ground into a slab,
     which is frosting, not glass. Floor B = .062 / .061 / .060 against a page of
     .035; passthrough A = .757 / .638 / .581, so what is behind stays legible.
     SUPERSEDED 2026-09-21 (phase-89 §2, at the end of this file), kept visible
     so the old numbers cannot be cited as current: the six values below are
     overridden by a later [data-theme="dark"] block. Floor B is now
     .133 / .121 / .114 and passthrough A is .192 / .161 / .146. That
     passthrough was the hue wash: at A .638 the pane carried the DATA colour
     behind it almost at full strength and its ground swung 4.40:1. The lift
     this paragraph protects is unchanged and is measured there (1.124:1 over
     the page for the regular weight, against 1.075:1 here). */
  --lgc-sat: 1.50; --lgc-bri: .88; --lgc-con: .86;
  --lgr-sat: 1.45; --lgr-bri: .76; --lgr-con: .84;
  --lgd-sat: 1.40; --lgd-bri: .70; --lgd-con: .83;
  --lg-rim-1: rgba(255, 255, 255, .85); --lg-rim-2: rgba(255, 255, 255, .16);
  --lg-rim-m: rgba(255, 255, 255, 0);   --lg-rim-3: rgba(255, 255, 255, .5);
  --lg-in-hi: rgba(255, 255, 255, .26); --lg-in-lo: rgba(0, 0, 0, .34);
  --lg-in-glow: rgba(255, 255, 255, .08);
  --lg-edge: rgba(255, 255, 255, .06); --lg-edge-1: rgba(255, 255, 255, .24);
  --lg-sh: .55; --lg-sh2: .28;
}

/* ---- the primitive.
   `:not(:where(.dot, .status-pill))` is a GUARD, not decoration, and it costs
   nothing (`:where()` is zero-specificity, so `.lg:not(:where(...))` is still
   0,1,0). `.lg` is already taken in this sheet as a SIZE modifier by two data
   chips — `.status-pill.lg` (:887) and `.dot.lg` (:1055), and `.dot.lg` is live
   in the dashboard legends (views.js:793, :807). Without the guard every legend
   dot would take a backdrop-filter layer and wear a UI-hue rim over a DATA
   colour, which is hard rule 5. Retire the guard by renaming those two
   modifiers to `.is-lg` in views.js + app.css, not by deleting it.

   `#cmd-palette .palette` rides along in each selector list because the element
   has no `lg` class on it yet — palette.js is held by other work. When the class
   is added, every `#cmd-palette .palette` co-selector below is deleted and
   nothing else changes. The lists are comma-separated, so the pilot's (1,1,0)
   never leaks into `.lg`'s (0,1,0). ---- */
.lg:not(:where(.dot, .status-pill)),
#cmd-palette .palette {
  isolation: isolate; --up: 0; --lg-ink: var(--text-1);
  --bl: var(--lgc-bl); --lg-sat: var(--lgc-sat); --lg-bri: var(--lgc-bri); --lg-con: var(--lgc-con);
  /* ONE definition of the chain; the lens rule and the phone block reuse it */
  --lg-fx: blur(var(--bl)) contrast(var(--lg-con)) brightness(calc(var(--lg-bri) + var(--up))) saturate(var(--lg-sat));
}
.lg-regular, #cmd-palette .palette { --bl: var(--lgr-bl); --lg-sat: var(--lgr-sat); --lg-bri: var(--lgr-bri); --lg-con: var(--lgr-con); }
.lg-dense { --bl: var(--lgd-bl); --lg-sat: var(--lgd-sat); --lg-bri: var(--lgd-bri); --lg-con: var(--lgd-con); }
/* the primary action is the same glass: no fill, no hue. It reads as primary
   through a brighter rim, a heavier label and the arrow. NEVER point this at
   `.btn.primary` — the product's own create button keeps its brand fill
   (.claude/rules/design.md, BUTTONS, corrected 2026-09-19). This is for page
   chrome that has been converted to glass. */
.lg-tinted { --lg-edge: var(--lg-edge-1); --lg-rim-a: 1.3; font-weight: 700; }
.lg.lg-tinted::after { padding: 1.5px; }
/* glass never takes `opacity` itself — that would cut its own backdrop. It dims
   through --lg-dim, which the layers below read. Without this rule --lg-dim is a
   permanent unset fallback of 1 and the disabled state does nothing. */
.lg[aria-disabled="true"] { --lg-dim: .45; }

/* glass is the chain above, a rim and inner shadows. No veil, no tint, no sheen
   film: the backdrop itself carries both the contrast and the lift. */
.lg:not(:where(.dot, .status-pill))::before,
#cmd-palette .palette::before {
  content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none;
  opacity: calc(var(--lg-o) * var(--lg-dim, 1));
  box-shadow: inset 0 1px .5px var(--lg-in-hi), inset 0 -1px .5px var(--lg-in-lo),
    inset 0 0 14px -4px var(--lg-in-glow), inset 0 0 0 .5px var(--lg-edge);
  -webkit-backdrop-filter: var(--lg-fx); backdrop-filter: var(--lg-fx);
}
/* step 2 writes --lg-lens per element and sets html.lg-lens; until then the
   empty fallback leaves the chain exactly as above */
html.lg-lens .lg:not(:where(.dot, .status-pill))::before,
html.lg-lens #cmd-palette .palette::before {
  -webkit-backdrop-filter: var(--lg-lens, ) var(--lg-fx); backdrop-filter: var(--lg-lens, ) var(--lg-fx);
}
/* the rim carries the sheen: its bright corner sits on the --lg-sheen angle */
.lg:not(:where(.dot, .status-pill))::after,
#cmd-palette .palette::after {
  content: ""; position: absolute; inset: 0; z-index: 1; border-radius: inherit; padding: 1px; pointer-events: none;
  --lg-sheen: inherit;
  background: linear-gradient(var(--lg-sheen), var(--lg-rim-1) 0%, var(--lg-rim-2) 16%, var(--lg-rim-m) 40%,
    var(--lg-rim-m) 60%, var(--lg-rim-2) 84%, var(--lg-rim-3) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  opacity: calc(var(--lg-o) * var(--lg-dim, 1) * (.8 + var(--up) * 3) * var(--lg-rim-a, 1));
}
/* floating glass casts a soft, wide, low shadow; inline glass casts none */
.lg-float, #cmd-palette .palette {
  box-shadow: 0 22px 48px -22px rgba(0, 0, 0, calc(var(--lg-sh) * var(--lg-o))),
    0 3px 10px -4px rgba(0, 0, 0, calc(var(--lg-sh2) * var(--lg-o)));
}
/* controls: hover lifts the backdrop and the rim, press swells the capsule.
   No --lg-o transition here: it is driven per surface and must not lag.

   PRECONDITION, added phase-93 and it is not optional: `.lg-ctl` REQUIRES `.lg`
   ON THE SAME ELEMENT. The `color` line below mixes `--lg-ink`, which is an
   UNREGISTERED custom property set in exactly one rule, `.lg` (:6105). On an
   element that is not glass, `var(--lg-ink)` has no value, the whole
   color-mix() is invalid at computed-value time, and `color` computes to
   `unset` - which, for an inherited property, is `inherit`. `.btn.lg-ctl`
   (0,2,0) then beats `.btn.primary` (0,2,0, :383) by source order, so
   `class="btn primary lg-ctl"` renders the app's create button with its
   PARENT's ink instead of white. The landing cannot trip this because
   `.gl-ctl` never appears without `.gl` (welcome.css:944 sets --lg-ink there).
   A pressable control that is NOT glass takes `.lg-press` (end of this file),
   which is this same grammar with the colour line left out.

   DESIGN_DIRECTION-APP.md D44 §3 says in words "they go on every pressable
   control in the product". Read against this rule that instruction is wrong,
   and it is wrong in the shape this project keeps paying for: a line that is
   correct in the place it was written (a glass control on the landing) and
   false in the place it is cited (an opaque button in the app). Do not carry
   it out literally. */
.lg-ctl, .btn.lg-ctl {
  color: color-mix(in srgb, var(--lg-ink) calc(var(--lg-o) * var(--lg-dim, 1) * 100%), transparent);
  transition: transform var(--lg-d2) var(--ease-lg), --up var(--lg-d2) var(--ease-lg);
}
.lg-card { transition: --up var(--lg-d3) var(--ease-lg); }
@media (hover: hover) {
  .lg-ctl:not([aria-disabled="true"]):hover { --up: .07; }
}
.lg-ctl:not([aria-disabled="true"]):active { --up: .11; transform: scale(1.03); transition-duration: var(--lg-d1), var(--lg-d1); }

/* ---- the pilot: the command palette, and nothing else.
   It is `position: fixed` and a direct child of <body> (palette.js:132), so
   there is no containing-block risk, and it is opened deliberately and backed
   out with one key.

   THE OLD MATERIAL ON IT, and what happens to each piece. Note that the palette
   was never in the phase-39 @supports block (:2528-2553) or in the phone reset
   (:3323-3332) — it wore an OPAQUE surface, which is worse than a second blur,
   because an opaque background hides the backdrop the filter is there to show.
     :1522 `background: var(--surface-1)`      -> transparent, on the element
     :1522 `box-shadow: var(--elev-3)`         -> the .lg-float pair above; two
                                                  shadow recipes disagree by theme
     :1522 `border: 1px solid var(--line-1)`   -> 0; the ::after rim IS the edge,
                                                  and two rims disagree in both
                                                  themes. `* { box-sizing:
                                                  border-box }` (:332) holds the
                                                  560px outer width either way.
     :1524 `animation: paletteIn ...`          -> lgPaletteIn, below.
   `overflow: hidden` stays: the rim is `inset: 0` and is not clipped by it. ---- */
#cmd-palette .palette {
  background: transparent; border: 0;
  animation: lgPaletteIn var(--lg-d1) var(--ease-lg) backwards;
}
/* paletteIn (:1525) animated `opacity` from 0 ON THE ELEMENT, which is exactly
   what welcome.css:887-888 forbids: "a glass surface never takes opacity (it
   would cut its own backdrop)". At opacity .5 the pane is half-transparent
   INCLUDING its own filtered backdrop, so the entrance flashes the raw board
   through the glass. The fade moves to --lg-o, which the layers read, and the
   curve moves off --ease-settle (cubic-bezier(.22, 1.2, .36, 1), an overshoot)
   onto --ease-lg, because the landing's rule is "nothing pops, snaps or bounces". */
@keyframes lgPaletteIn {
  from { --lg-o: 0; transform: translateX(-50%) translateY(10px) scale(.98); }
}
/* the dropped child-opacity rule, opted into for THIS surface only: the pane's
   own children fade with it so the text does not arrive before the glass does.
   Scoped to the palette's three children (palette.js:120-131) — an input row, a
   list and an sr-only status line, none of them sticky and none of them a fixed
   ancestor — so no stacking context lands anywhere near `td.col-name`. A child
   that must not fade carries `.lg-pass`. */
#cmd-palette .palette > :not(.lg-pass) { animation: lgContentIn var(--lg-d1) var(--ease-lg) backwards; }
@keyframes lgContentIn { from { opacity: 0; } }

/* ---- reduced transparency: the ONE place a tint survives, and the only reason
       --lg-tint exists. A near-opaque pane instead of a backdrop. ---- */
@media (prefers-reduced-transparency: reduce) {
  .lg::before, #cmd-palette .palette::before { background: rgba(var(--lg-tint), .94); }
  .lg-tinted::before { background: rgba(var(--lg-tint), .97); }
  .lg::before, html.lg-lens .lg::before,
  #cmd-palette .palette::before, html.lg-lens #cmd-palette .palette::before {
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
}

/* ---- reduced motion: only the glass-specific lines. The landing's companion
       rule is a body-wide `*, *::before, *::after { transition-duration: 0ms }`,
       which .claude/rules/css.md forbids outright; the app already answers this
       query in seven places above (:287, :1476, :2199, :2791, :5079). ---- */
@media (prefers-reduced-motion: reduce) {
  .lg-ctl:not([aria-disabled="true"]):active { transform: none; }
  #cmd-palette .palette, #cmd-palette .palette > :not(.lg-pass) { animation: none; }
  #cmd-palette .palette { --lg-o: 1; }
}

/* ---- phones: the chrome is solid. This block restates the law of :3323-3332
       for the new material, because this phase-73 block is APPENDED and a later
       plain rule beats an earlier media rule — a backdrop-filter written above
       would otherwise put a blur back under a phone sheet. Every future `lg-`
       surface inherits the reset here and only has to restore its own solid
       background. Keep any new glass rule ABOVE this block. ---- */
@media (max-width: 700px) {
  .lg::before, html.lg-lens .lg::before,
  #cmd-palette .palette::before, html.lg-lens #cmd-palette .palette::before {
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  #cmd-palette .palette { background: var(--surface-1); }
}

/* ---- defect 4: the two blue lines down the board -------------------------
   The owner: "these blue lines are also showing when interacting with the
   board/cells... i do not think this should be there, looks very weird and
   off." They are the two side edges of #main:focus-visible (:3699) - an
   outline round a scroll area thousands of pixels tall, so its top and bottom
   are off-screen and only the sides are ever seen. #main holds the focus
   because SD.focusMain hands a keyboard the new page after every navigation,
   and :focus-visible re-matches the moment any key is pressed, so an outline
   from an arrival ten minutes ago lit up while he was editing cells.

   The rule at :3699 is NOT removed, and neither is the focus handoff: an
   outline on the landing target is the right confirmation for a keyboard user
   arriving on a page, and it is what a skip link will need (there is none in
   the markup yet; .sr-only-focusable at :3665 is unused). It is made a MOMENT
   instead of a state. SD.landOn (core.js) puts data-landed on whatever it
   lands the focus on and the next key, pointer press or pointer movement
   takes it off, so the outline is painted on arrival and gone the instant the
   person starts working. One appended line with one more :not() than the rule
   it gates, so the original still wins while the attribute is there.

   ONE EDGE AT A TIME holds here too: a page is not a cell, so #main never
   wears the five-hue fan - it keeps the --brand outline it has always had -
   and because the attribute is dropped by the same keydown that sets
   html.kbd-nav, a page outline and a cell edge cannot be lit together. */
#main:focus-visible:not([data-landed]), #root:focus-visible:not([data-landed]) { outline: none; }


/* ════════════════════════════════════════════════════════════════ phase-76
   THREE THINGS THE OWNER SAW, ANSWERED IN ONE APPENDED BLOCK.

   Appended after the "phones: the chrome is solid" blocks (:3323 and :6216)
   on purpose, and nothing in here sets backdrop-filter, blurs a card or a
   row, adds a filter layer, or animates anything but --ring-a (an angle), so
   both of those blocks still win on a phone. No rule below colours data with
   a logo hue: the only colour added anywhere in this block is the five-hue
   fan, which is a FOCUS indicator, and a focus indicator is chrome.

   ---- 1. "when will the content be centered? this should automatically be
   adjusted along with the column's titles except for the first column. leave
   the first column as is."

   Six of the register's nineteen columns already read centred, because the
   shapes (a status plate, an avatar stack, a tick, a star row, a file fan, a
   timeline pill) were centred by `.board-table td { text-align: center }`
   (:829) and never overridden. The other thirteen were pulled off that centre
   line by phase-67-d's type rules (:4463-4468): text, link, dropdown and tags
   to the left edge, number and date to the right. The heading strip kept its
   own default, which IS centred (`.th-in { justify-content: center }`,
   :1928), and only the same six types moved it - so on eleven columns a
   centred heading sat over a left- or right-aligned value, which is the
   disagreement he is reading as broken.

   So the type rules are neutralised, not deleted (append-only), and the one
   column he asked to keep is kept BY CONSTRUCTION rather than by an exception
   list: `th.col-name` and `td.col-name` are rendered without a `data-type`
   attribute at all (board.js:1400, :1555 - every other cell gets
   `data-type="${c.type}"`), so `[data-type]` is exactly "every column except
   the first" and cannot drift if a column type is added later.

   WHAT THIS COSTS IN COLUMN WIDTH: nothing, and that is the point. fitWidths
   (board.js:1141) measures the 70th-percentile VALUE with canvas measureText
   and writes one inline width per column; no rule below is read by it, no
   rule below adds a floor, a min-width or a reserve, and `table-layout:
   fixed` sizes from the inline width either way. Columns still size to their
   content. The only thing that moves is where the glyphs sit inside a box
   that is already the width it was.
   What it costs instead is 22px of HEADING ROOM on those thirteen columns:
   a centred label has to clear the sort control on both sides, so its cap
   goes from `calc(100% - 22px)` to the symmetric `calc(100% - 44px)` the six
   centred columns have had since phase-73 §3. A long heading ellipsises 22px
   sooner; its whole title is still in the column menu the heading opens, and
   the alternative - reserving that width in the COLUMN - is the header floor
   the owner rejected live ("the length of columns are not auto adjusting
   either"). Width is his; label room is ours. ---- */
.board-table td[data-type] { text-align: center; }
/* A value that outgrows its column must still ellipsise at ONE end. The box
   is `display: inline-block` with `max-width: calc(var(--cw) - 12px)`
   (:1746), so the cell centres the BOX and the box left-aligns its own text:
   under `text-align: center` a line wider than its container overflows both
   sides, and the ellipsis would then eat the end of the string while the
   start was silently cut off with no mark at all - a document id losing its
   prefix. Centred while it fits, clipped from the right when it does not. */
.board-table td.cell .cell-text { text-align: left; }
/* .cell-tags was pushed to flex-start for the left-aligned tags column
   (:4484); it centres itself at :2819 like every other shape. */
.board-table td.cell[data-type="tags"] .cell-tags { justify-content: center; }
/* the heading joins its own column. .th-in's own default is center, so these
   two only have to stop the phase-67-d overrides (:4477-4482). */
.board-table th[data-type] .th-in { justify-content: center; }
.board-table th[data-type] .col-head { max-width: calc(100% - 44px); }
/* the sort control is `position: absolute; right: 2px` (phase-73 §3), so
   `order` has no effect on it at all, but `margin-right: 2px` DOES - on an
   absolutely positioned box the margin is added to the offset, so the sort
   control on a date or number column sat 2px further in than on its
   neighbours. Nineteen controls now rest on the same line. */
.board-table th[data-type="number"] .th-sort,
.board-table th[data-type="date"] .th-sort { order: 0; margin: 0; }
/* (phase-117: the subitem strip's copy of this alignment was retired with the
   strip; a child's cells are td.cell and align by the rules above.) */

/* ---- 2. "when using the keyboard's arrows the boards are not showing the
   thin rainbow edge. The cells when moving using the arrows are showing the
   thin rainbow edge, so that's fixed."

   Four screenshots, four panes, one cause: every row in a menu or an option
   list is a real <button>, so it falls through to the premium quality floor
   (:1414, `a:focus-visible, button:focus-visible, [contenteditable]
   :focus-visible { outline: 2px solid var(--brand) }`) and answers the arrow
   keys in blue while the cell three inches away answers them with the fan.
   Arrowing a pane moves real DOM focus (core.js:620-645, SD.focus), so
   :focus-visible is already the right question; only the answer was wrong.

   THE LOCK. design.md marked the focus treatment owner-locked on 2026-09-15.
   The OWNER asked for this change himself, so the lock is lifted for exactly
   this and nothing else: --focus-ring is NOT redefined here, the floor at
   :1414 is NOT edited (a link, an icon button, the switch and the tick box
   still answer in --brand, which is what the lock reserves them for), no
   text-entry control is touched, and the cell treatment he confirmed is
   correct is not touched either.

   THE MECHANISM IS THE ONE THAT ALREADY EXISTS, not a third one:
   `.actf-field::before` (:5051) - an out-of-flow box the size of the row, the
   fan painted into it, and a two-layer mask composited `exclude` so only the
   1.5px band between the padding box and the border box survives. Nothing
   under the row is touched, so `.menu-item.on`'s brand-soft fill, a label
   option's own 24% chip tint and a person row's selected state all keep their
   background and simply gain an edge.

   ::after, NOT ::before: `.label-opt::before` (:1133) is the label's colour
   DOT, and the status picker is one of the four screenshots. Taking ::before
   would have deleted the swatch from every row it lit.

   `inset: 0`, not the -1px `.actf-field` uses: these rows have no border to
   grow into, and `.people-menu` scrolls (`overflow-y: auto`, :1150), which
   makes its overflow-x `auto` too - a ring hung 1px outside the row would be
   clipped on both sides or, worse, add a scrollbar. On the row's own edge it
   is inside the pane whatever the pane does.

   COST: no resting pseudo-element anywhere. The ::after exists only while a
   row holds keyboard focus, which is one row in one open pane. ---- */
/* SUPERSEDED 2026-09-21, phase-97, D46 §9 item 2 (screenshots 13, 17, 18).
   The masked `::after` that painted the spinning fan into these five row
   families is deleted and they answer with --focus-ring, the one token.
   WHAT DID NOT CHANGE, because the phase-76 reasoning above is still correct
   about everything except the fan's colour: nothing under the row is touched,
   so `.menu-item.on`'s brand-soft fill, a label option's 24% chip tint and a
   person row's selected state keep their ground exactly as they are; and
   `position: relative` stays, because other children of these rows are
   positioned against it.
   WHY THE RING FITS: `.popover` carries `padding: 8px` (:1079), so the 4px the
   two bands reach outside a row lands inside the pane's own padding box and is
   neither clipped by `overflow-y: auto` nor able to add a scrollbar - a
   box-shadow contributes no scrollable overflow. That was phase-76's stated
   reason for an inset ring, and it is answered rather than ignored.
   SELECTED AND FOCUSED COMPOSE (D46 §4e): `.label-opt.on` (:1126) carries the
   selection as an INSET 2px --brand ring, so its focused form restates that
   inset beside the token rather than replacing it - one mark inside about the
   thing, one ring outside about you. */
/* SUPERSEDED 2026-09-22 by the owner: "many details were lost like the thin
   rainbow edge, the blue edge keeps coming back." The phase-97 note above is
   left visible and no longer describes the code. The masked `::after` fan on
   these five row families is RESTORED exactly as a1799da had it (phase-76's
   reasoning further up is again the live explanation: ::after because
   `.label-opt::before` is the colour dot, `inset: 0` because the pane
   scrolls), the --focus-ring box-shadow is gone from them, and so is
   `.label-opt.on:focus-visible`'s composed ring: a selected label row keeps
   its inset --brand selection mark from :1126 and gains the fan on top, as it
   did before phase-97. */
.menu-item, .label-opt, .label-edit, .people-opt, .rate-opt { position: relative; }
.menu-item:focus-visible, .label-opt:focus-visible, .label-edit:focus-visible,
.people-opt:focus-visible, .rate-opt:focus-visible { outline: none; }
/* focus-ring: exempt - owner-restored 2026-09-22, the five menu row families.
   The marker lets the D46 checks pass this rule BY NAME. */
.menu-item:focus-visible::after, .label-opt:focus-visible::after, .label-edit:focus-visible::after,
.people-opt:focus-visible::after, .rate-opt:focus-visible::after {
  content: ""; position: absolute; inset: 0; z-index: 2; pointer-events: none;
  border-radius: inherit; padding: var(--fan-ring-w);
  background: var(--fan-ring);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  animation: ringSpin 2.6s linear infinite;
}

/* ---- 3. "this weird background still showing when clicking the title of the
   group."

   The screenshot: a group title with the conic gradient painting edge to edge
   behind its words.

   WHAT IT ACTUALLY IS, because "still showing" means a restyle would just
   move it. The phase-68 family (:4959) paints TWO background layers on the
   same element - a fill clipped to the padding box, the fan clipped to the
   border box - and shows the ring by growing the fan layer from 0 to 100% on
   focus. That only reads as a 1.5px edge if the fill layer on top of it is
   OPAQUE. Three of the six controls in that family take a translucent tint:
   .group-title is `color-mix(in srgb, var(--gc) 14%, transparent)` (:4975),
   .th-label is --hover-ground (rgba .05/.06) and .ic-head h3 is --wash
   (5%/5.5%). So on those three the fan was never masked by anything and the
   whole conic came through the middle at 86-95% strength. The other three
   (.board-name, .item-name, .sub-name) take opaque surface tokens, which is
   why nobody saw it there and why it looked like one control's bug.

   All three are corrected, in one rule, because it is one defect: the owner
   reported the group title, and the item-label heading and the item card's
   title do exactly the same thing on the next Tab.

   The fill layer is left completely alone - the 14% group colour still washes
   the pill on hover and on focus, in both themes, exactly as it does today -
   and only the FAN layer is switched off and re-drawn as the same masked edge
   §2 uses. The transparent 1.5px border each of these carries (:4960) and the
   padding they give back for it are untouched, so no box moves: `inset:
   calc(var(--fan-ring-w) * -1)` puts the band back on the border box, which
   is precisely where the old fan layer painted its outermost 1.5px.

   `overflow: visible` while focused is not cosmetic: .group-title clips
   itself (`overflow: hidden`, :789) and would eat an out-of-flow ring on all
   four sides. It already swaps to `white-space: normal` on focus (:792), so
   it wraps instead of clipping while it is being edited and has nothing left
   to clip. .th-label and .ic-head h3 clip nothing and neither do their
   parents (`.board-table th` sets no overflow, :815). ---- */
/* SUPERSEDED 2026-09-21, phase-97, D46 §9 item 2. The owner's "weird
   background" is fixed for good rather than re-masked: the fan LAYER these
   three rules switched off no longer exists anywhere in this family (:5005),
   so there is nothing left to come through a translucent fill, and the ring
   they were re-drawn as is now --focus-ring outside the box. The fill layer is
   still left completely alone - the 14% group colour still washes the pill on
   hover and on focus, in both themes.
   `overflow: visible` while focused STAYS. It is not about the ring any more
   (an element's own box-shadow is not clipped by its own overflow); it is
   phase-68's `white-space: normal` companion, so a title being edited wraps
   instead of clipping. */
/* SUPERSEDED 2026-09-22 by the owner: "many details were lost like the thin
   rainbow edge, the blue edge keeps coming back." The phase-97 note above is
   left visible and no longer describes the code. The fan layer is back on the
   whole phase-68 family, so these three translucent renames need phase-76's
   answer again, RESTORED exactly as a1799da had it: size the layer to nothing
   on focus (the fill is left alone, so the 14% group wash still shows) and
   redraw the same edge as a masked `::after` on the border box. `overflow:
   visible` is again what keeps .group-title from clipping that ring. */
.group-title:focus, .th-label:focus, .ic-head h3:focus {
  background-size: 100% 100%, 0 0;
  animation: none;
}
.group-title, .th-label, .ic-head h3 { position: relative; }
.group-title:focus { overflow: visible; }
/* focus-ring: exempt - owner-restored 2026-09-22, the three translucent
   renames. The marker lets the D46 checks pass this rule BY NAME. */
.group-title:focus::after, .th-label:focus::after, .ic-head h3:focus::after {
  content: ""; position: absolute; inset: calc(var(--fan-ring-w) * -1); z-index: 2; pointer-events: none;
  border-radius: inherit; padding: var(--fan-ring-w);
  background: var(--fan-ring);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
  animation: ringSpin 2.6s linear infinite;
}

/* the spin stops, the ring does not (phase-68 §5). Both new families need
   their own line: the block at :5079 names the ELEMENTS of the phase-68
   family, and an animation on a pseudo-element is not covered by a rule about
   its originating element. An indicator must never depend on an animation to
   be visible, so only `animation` is turned off and the edge stays painted. */
/* DELETED 2026-09-21, phase-97: this block stilled the spin on the two
   families above under prefers-reduced-motion. Neither family animates
   anything now - --focus-ring is a static two-band box-shadow - so the
   indicator no longer depends on an animation to be visible in ANY motion
   setting, which is the stronger form of what this block was protecting. */
/* SUPERSEDED 2026-09-22: the fan is restored on both families (the owner's
   "thin rainbow edge" ruling), so the stiller is RESTORED with it, exactly as
   a1799da had it. The note above no longer describes the code. */
@media (prefers-reduced-motion: reduce) {
  .menu-item:focus-visible::after, .label-opt:focus-visible::after, .label-edit:focus-visible::after,
  .people-opt:focus-visible::after, .rate-opt:focus-visible::after,
  .group-title:focus::after, .th-label:focus::after, .ic-head h3:focus::after {
    animation: none;
  }
}


/* ================================================================ phase-77
   Q29 — D28, D29, D30: ONE surface, ONE set of column titles, and a group
   that declares its real height.
   ----------------------------------------------------------------
   The owner reported "status are off". Every one of the 492 rows was checked
   against the server by id and not one value is wrong; what was wrong is that
   the register made it easy to read the WRONG column and believe it. Fourteen
   groups each had their own sideways scrollbar, so at 800px from the left one
   group showed REVIEW DATE while the group under it showed STATUS; and the
   title row was sticky sideways only (top: auto), so below the first
   screenful there were nineteen unlabelled columns to read by position.
   Both are fixed here, and they are one block because neither can be fixed
   alone: a title row lives inside .table-wrap, a sticky element pins to its
   nearest scroll box, so the vertical case is unbuildable until the scroll
   box moves one ancestor out.
   Nothing here sets backdrop-filter, blurs a card or a row, or animates
   anything, so its position after the "phones: the chrome is solid" block
   (:2474) is safe. Chrome palette only: no data colour appears in the strip.
   ---- */

/* ---- D28. one scroll box for the whole board -------------------------- */
/* Height stays AUTO, deliberately. overflow-x:auto computes overflow-y to
   auto, so this is a scroll container in both axes — but with an auto height
   its scrollHeight equals its clientHeight, so it never scrolls vertically and
   never captures the page's own scroll. Do not give it a height, a max-height
   or a flex basis; "things stop working after scrolling" is the report this
   phase is chasing and a nested vertical scroller is how it would come back.
   The native bar is hidden because at the foot of a 23,131px document it is
   unreachable; .board-xbar below is the reachable one. */
.board-scroll {
  overflow-x: auto; overscroll-behavior-x: contain;
  --shade: 0; scrollbar-width: none;
}
.board-scroll::-webkit-scrollbar { height: 0; width: 0; }
/* Every group is as wide as the SURFACE, not as wide as the viewport. Two
   reasons, both load-bearing: a block child of a scroll container takes the
   scrollport's width, and .group carries paint containment from
   content-visibility (:2470) — so a table left to overflow its group would be
   CLIPPED at the first screenful and the board would look empty past it.
   --tw is written onto .board-scroll by renderTable from the same tableWidth
   every table already carries, so the two cannot drift; max() keeps a board
   narrower than the viewport laying out exactly as it did. It also holds for a
   COLLAPSED group, whose section rule would otherwise stop at the viewport
   edge, and for a skipped one, whose width keeps the scrollbar honest. */
.board-scroll > .group { min-width: max(100%, var(--tw, 100%)); }

/* The group's head keeps the full width of the surface, so the section rule it
   carries (:4224) still spans the board; only its CONTENTS pin to the left
   edge — the idiom .sub-add already uses at :4718. The padding moves with
   them, so the spine still runs the head's exact height, and overflow goes
   back to visible because `overflow: hidden` (:2459) is itself a scroll box
   and a sticky child would have pinned to it instead of to the board. */
.board-scroll > .group > .group-head { overflow: visible; padding: 0; }
.ghead-in {
  position: sticky; left: 0; z-index: 1;
  display: flex; align-items: center; gap: 8px; min-width: 0;
  width: max-content; max-width: 100%;
  padding: 5px 8px 5px 4px;
}
[data-density="compact"] .ghead-in { padding: 3px 6px 3px 2px; }
@media (max-width: 700px) { .ghead-in { padding: 4px 6px 4px 2px; gap: 6px; } }
/* the spine follows the title it belongs to (was .group-head::before, :4038).
   Scoped to the table path: renderCards draws the same head WITHOUT a
   .ghead-in, and at 375px cards are the default the owner reviews. */
.board-scroll > .group > .group-head::before { display: none; }
.ghead-in::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: var(--spine-w);
  background: var(--gc); box-shadow: inset 0 0 0 1px var(--spine-edge);
  pointer-events: none;
}
/* the row that makes a document stays reachable at any offset: a labelled,
   always-visible control is the standing rule, and a colspan cell's contents
   sit at the far left of a 3,000px surface otherwise */
.addbar { position: sticky; left: 0; width: max-content; max-width: 100%; }

/* ---- D28 step 6. a horizontal scrollbar the owner can reach ----------- */
/* Pinned to the bottom of the viewport instead of buried 23,131px down. It is
   a real scroll box holding a 1px spacer of the surface's width, two-way
   synced with .board-scroll in board.js, so it is native, keyboard-reachable
   and tells him for the first time that there are ten more columns to the
   right. No padding: its scroll range must equal the surface's exactly. */
.board-xbar {
  position: sticky; bottom: 0; z-index: 11; height: 14px;
  overflow-x: auto; overflow-y: hidden;
  background: var(--surface-0); border-top: 1px solid var(--line-2);
}
.board-xbar-in { height: 1px; }
.board-xbar.off { display: none; }
/* The bar is a div[tabindex], so the quality floor at :1414 (a, button,
   [contenteditable]) does not reach it and it needs its own rule. What it
   must NOT do is restate the ring: this was a fourth literal copy of
   `2px solid var(--brand)`. --focus-ring is the token the app's other
   non-text controls answer with (.sub-caret :4289, .sub-check :4340,
   .sub-cell :4364, .sub-add :4387, all box-shadow + outline:none), and it
   is a box-shadow value, so it is used the way they use it. Nothing about
   the locked treatment changes: same 2px, same --brand, no inner band. */
.board-xbar:focus-visible { box-shadow: var(--focus-ring); outline: none; }
/* touch scrolls the surface directly and 14px is a third of a row */
@media (max-width: 700px) { .board-xbar { display: none; } }

/* ---- D29. one column-title strip for the whole board ------------------ */
/* A full-bleed band pinned under the board head. z-index sits BELOW the head
   (20 at :716) and above the frozen name column (2 at :809). Its top is
   --head-h, measured live from .board-head by syncHeadHeight, so Q23's
   condensing head must READ that variable rather than re-measure.
   Material: solid ground, the same --th-strip the header row wore, and its
   boundary is --line-strong — the D14 treatment at :5868, unchanged, so the
   strip reads as a header and not as a paler row in either theme. Never a
   blur here: it is a full-width layer over 487 scrolling rows, which is the
   phase-46 trap at its worst. */
.board-cols {
  position: sticky; top: var(--head-h, 0px); z-index: 12;
  margin: 0 calc(var(--board-pad-x) * -1);
  padding: 0 var(--board-pad-x);
  background: var(--th-strip);
  border-bottom: 1px solid var(--line-strong);
}
/* the track clips where the TABLE clips, at the pane's content edge, while the
   band above reaches the pane's outer edges */
.bcols-clip { overflow: hidden; }
/* transform, never scrollLeft: the strip must not become a second scroller to
   keep in step with the first. --board-x is written on #board-body by the one
   .board-scroll listener. */
.bcols-track { display: flex; min-width: 100%; transform: translateX(calc(var(--board-x, 0px) * -1)); }
.bcol {
  position: relative; flex: 0 0 auto; min-width: 0; padding: 9px 6px;
  font-size: 13px; font-weight: 600; color: var(--text-2);
}
.bcol + .bcol { border-left: 1px solid var(--border-soft); }
/* the name slot counter-slides by the same offset, which is what
   `position: sticky; left: 0` does for the name column in the table below it;
   flex-grow 1 makes it absorb the slack the table's auto-width <col> absorbs,
   so a board narrower than the viewport stays aligned too */
.bcol-name {
  flex: 1 0 auto; z-index: 1; padding-left: 16px; text-align: left;
  background: var(--th-strip); transform: translateX(var(--board-x, 0px));
}
/* ...but only where the table's name column is FROZEN. Under 701px
   `td.col-name` is `position: static` (:1845) and the name column scrolls away
   with everything else, so a slot that keeps counter-sliding stays pinned over
   a 355px strip at 375px and hides every title but the first — the nineteen
   unlabelled columns D29 exists to remove. The slot's width is the same 380px
   the table's first <col> absorbs, so with the counter-slide off it rides the
   track and sits over its own column again. Its opaque --th-strip ground and
   z-index:1 are now redundant at this width (nothing overlaps it any more) but
   they are identical to the band behind it and cost nothing, so they stay:
   above 700px they are what keeps the scrolling titles from showing through
   the frozen slot. */
@media (max-width: 700px) { .bcol-name { transform: none; } }
.bcol-add { width: 44px; display: flex; align-items: center; justify-content: center; padding: 4px; }
/* alignment is read from data-type, the same attribute the cell reads at
   :6314, so the strip and the phase-76 centring rule cannot drift apart */
.bcol[data-type] .th-in { justify-content: center; }
/* the heading's cap, its sorted colour and its hover, ported from the th rules
   at :5832, :766 and :5877 — the th selectors no longer match anything */
.bcol .col-head { max-width: calc(100% - 44px); border-radius: var(--r-xs); }
.bcol.sorted .col-head { color: var(--brand); }
.bcol .col-head:hover { background: var(--hover-ground); }
.bcol .th-sort { border-radius: var(--r-xs); }
.bcol.col-drop { box-shadow: inset 2px 0 0 var(--brand); }
[data-density="compact"] .bcol { padding-top: 5px; padding-bottom: 5px; font-size: 12px; }
/* a cell the browser scrolls to the top now clears the head AND the strip
   pinned under it (:3937 covered the head alone) */
.board-table td.cell { scroll-margin-top: calc(var(--head-h, 0px) + var(--cols-h, 0px)); }
/* on paper there is no viewport to pin to and no surface to slide */
@media print {
  .board-cols { position: static; }
  .bcols-track { transform: none; }
  .bcol-name { transform: none; }
  /* the track is let out to its full width with the body it labels: the strip
     clipped at the viewport while the printed body ran 2740px wide, so eleven
     of nineteen columns printed with no title at all */
  .bcols-clip { overflow: visible; }
  .board-scroll { overflow: visible; }
  .board-xbar { display: none; }
}

/* ---- phase-79: the Library card's document name can finally be cut ------- */
/* Defect 53, open since the Files view was built and visible on 118 of the
   owner's 120 grid cards: a document name simply stopped mid-character at the
   card's right edge with no ellipsis, so the grid read as a rendering fault
   rather than as truncation.
   The declarations were all there. `.fv-doc` (:1984) carries
   `overflow:hidden; text-overflow:ellipsis; white-space:nowrap` and has since
   the day it was written - but it is also `display:inline-flex`, and
   `text-overflow` acts on a box's own inline content, not on its flex items.
   The name was an anonymous flex item beside the group dot, so the property
   had nothing to apply to and the browser clipped instead. This is the same
   trap the comment at views.js:109 records for `.fv-meta`; that one was solved
   by fitting the content, which is not available here because the content is
   the owner's own document titles.
   So the name is now its own element and carries the ellipsis itself.
   `min-width:0` is the half that is easy to miss: without it a flex item
   refuses to shrink below its min-content width and `overflow:hidden` never
   gets a chance to fire.
   The inert trio on `.fv-doc` is left in place rather than edited out - it is
   harmless, removal is the groundskeeper's, and the line above says why it
   never worked so nobody restores this by reading it. */
.fv-doc-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- D29 follow-up: the sorted state belongs on the sort control --------- */
/* Retiring the per-group <thead> left `.board-table th.sorted .th-sort`
   (:1942 and :1945) matching nothing, so the arrow the owner presses to
   REVERSE a sort looked exactly like the eighteen arrows that would start one:
   `opacity: .55; color: var(--text-3)` from the base rules at :1936 and :5838.
   The heading still turns --brand (:6619), so the state was not invisible, it
   was just not on the control that acts on it.
   Ported, not moved: the ~45 dead `board-table th...` selectors stay where
   they are, because removing an earlier block is the groundskeeper's own
   phase. This appends after :6621 (`.bcol .th-sort`), which is what lets a
   two-class selector win without !important, and it sets nothing that the
   "phones: the chrome is solid" block cares about - no backdrop-filter, no
   blur, no transition of its own beyond the base rule's colour fade.
   Opacity 1 as well as the colours: a control at .55 reads as the resting
   state whatever hue it carries. */
.bcol.sorted .th-sort { background: var(--brand-soft); color: var(--brand); opacity: 1; }

/* ---- the copy-by-hand fallback, inside a card instead of over it -------- */
/* Layer 3 of Copy link used to be a modal, and on this app the item card IS
   the modal, so the fallback replaced the card the reader had open. It is an
   inline row inside that card now (panels.js copyByHand), and it needs to read
   as a section of the card rather than as a second dialog: a rule above it,
   the card's own type sizes, and the field is the same `.invite-link` the
   invite screen uses, so `word-break: break-all` wraps a long address at 375px
   with no sideways scroll. No blur, no new material, no glass. */
.cbh { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line-2); }
.cbh-h { font-size: 14px; margin: 0 0 4px; }
.cbh-note { font-size: 12.5px; line-height: 1.5; margin: 0; }
.cbh-foot { display: flex; justify-content: flex-end; margin-top: 12px; }


/* ====================================================== phase-83 (Q32 / D36)
   ONE CURVE. This block enforces D36, which enforces D20 - decided long ago,
   never applied. It changes no markup, no layout, no colour and no material.

   WHAT WAS WRONG, measured (DESIGN_DIRECTION-APP.md 11.2, re-measured here):
   the landing's curve, cubic-bezier(.4,.02,.2,1), covers 14.2% of a move in
   the first 20% of its duration - it eases IN. The app's --ease-out,
   cubic-bezier(.16,1,.30,1) and 119 uses, covers 75.2%: it lunges, then
   crawls to a stop, which is why a control appears to arrive before you have
   seen it leave. --ease-spring peaked at 109.8% and came back (16 uses) and
   --ease-settle at 101.2% (4 uses), so twenty controls overshot their mark and
   returned - including .group-caret (:2464), fourteen times per screen on the
   owner's register, and .vt-ind (:2584) on every view change.

   HOW IT IS DONE, and why it looks like this. app.css is appended per phase
   and removing an earlier block is the groundskeeper's own phase, so nothing
   above this line is edited. Two mechanisms, in order:

   1. The four easing tokens are REDECLARED here, all four to the one curve.
      That converts 143 of the 163 easing uses without touching a byte of
      earlier source. The four NAMES stay so that no consumer is edited; they
      stop being four curves. --ease-spring and --ease-settle are therefore
      retired as curves while still resolving - D36 exempts nothing, and all
      twenty of their uses are listed in the report.
   2. Everywhere an earlier block wrote a bare `ease` keyword, a raw
      cubic-bezier, no timing function at all (the CSS initial value IS `ease`,
      which is a fifth curve nobody chose), or a literal duration, a LONGHAND
      override is appended below for that exact selector. A longhand is used
      rather than a re-declared shorthand on purpose: re-stating
      `transition: ...` here would copy the property list too, and the next
      phase to add a property to one of those rules would silently lose it.

   WHAT THIS DELIBERATELY DOES NOT TOUCH:
   - `linear`, 12 uses. Every one is a continuous loop (ringSpin 2.6s, the
     trail's marquee) or the `0s linear` delay hack on .sidebar's visibility.
     A constant rate over an infinite rotation is not an easing choice, and
     D36's inventory of seven curves does not count it.
   - Ambient infinite loops keep their PERIODS (logoDrift 8s, float 11s/9s,
     livePulse 2s, badgePulse 2.4s, trailMove 2.6s (2.2s until phase-154), ringSpin 2.6s, breathe
     3.4s). A period is not a transition; folding 2.6s onto a 1150ms scale
     would spin the loader 2.3x faster. Only their curves change, via 1.
   - @keyframes geometry. D36 says the 33 keyframes are retimed and re-curved,
     not rewritten; nine of them (logoWave 1.45, icTwinkle 1.25, icPulse 1.3,
     livePulse 2.5 and the rest) scale past 1 and back INSIDE the keyframes,
     which no curve change can reach. They are emphasis pulses, not overshoot
     on an arrival, and they are reported rather than altered here.
   - prefers-reduced-motion. The global stiller at :1480 carries !important and
     still beats every longhand below; the :root stiller at :287 does NOT, so
     it is restated at the end of this block. A later plain rule beats an
     earlier media rule of equal specificity, and redeclaring the duration
     tokens here without restating that block is exactly how this change would
     have handed motion back to a reader who asked for none.
   Nothing here sets backdrop-filter, blur, background or any other property
   the "phones: the chrome is solid" block (:3323, restated :6225) cares
   about, so its position after that block changes nothing. */

:root {
  /* The landing's six durations, welcome.css:62, plus the two entrance rungs
     D36 adds. The app's own names are kept rather than importing --d-0..--d-5,
     because half an imported scale beside a full local one is two scales.
     --dur-micro-3 and --dur-scene-1 now both read 280ms, and --dur-scene-2 and
     --dur-scene-3 both 460ms: the six names cover four rungs, which is D36's
     table exactly, and no consumer had to be told. */
  --dur-micro-1: 90ms;    /* was 120ms - landing --d-0 */
  --dur-micro-2: 180ms;   /*             landing --d-1 */
  --dur-micro-3: 280ms;   /* was 240ms - landing --d-2 */
  --dur-scene-1: 280ms;   /* was 300ms - landing --d-2 */
  --dur-scene-2: 460ms;   /* was 400ms - landing --d-3 */
  --dur-scene-3: 460ms;   /* was 450ms - landing --d-3 */
  --dur-scene-4: 760ms;   /* new       - landing --d-4, entrances */
  --dur-scene-5: 1150ms;  /* new       - landing --d-5, entrances */

  /* One curve. welcome.css:61, character for character. */
  --ease-out:    cubic-bezier(.4, .02, .2, 1);
  --ease-in-out: cubic-bezier(.4, .02, .2, 1);
  --ease-spring: cubic-bezier(.4, .02, .2, 1);
  --ease-settle: cubic-bezier(.4, .02, .2, 1);
}

/* ---- 2a. transitions that named `ease`, named nothing, or wrote a curve --- */
.btn {
  transition-timing-function: var(--ease-out);
  transition-duration: var(--dur-micro-1), var(--dur-micro-2), var(--dur-micro-2);
}
.btn .arrow { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-2); }
.icon-btn { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-2); }
.mk-header { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-3); }
/* .reveal carried the old --ease-out written out by hand, twice, at .7s. */
.reveal { transition-timing-function: var(--ease-out); transition-duration: var(--dur-scene-4); }
.item-row { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-1); }
.kanban-card { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-2); }
.modal-overlay { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-2); }
.modal { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-2); }
.switch span { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-2); }
.switch span::after { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-2); }
.ai-panel { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-3); }
.main { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-3); }
.toast { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-3); }
#trail { transition-timing-function: var(--ease-out); transition-duration: var(--dur-scene-2); }
.sheet-scrim { transition-timing-function: var(--ease-out); transition-duration: var(--dur-micro-2); }

/* ---- 2b. transitions already on a token, but at a literal duration ------- */
/* .wl-fill's live rule is :2445 (.6s); the :1063 copy has been dead since
   phase-32 and stays where it is. Same for .tpl-card (:2659 wins over :1267)
   and .mywork-row (:2684 over :1298), which is why neither appears here. */
.wl-fill { transition-duration: var(--dur-scene-2); }
.cbar i { transition-duration: var(--dur-scene-2); }
.imp-bar i { transition-duration: var(--dur-micro-3); }
/* phase-167 groundskeeping: removed the .vt-ind transition-duration rule; `.app .vt-ind { transition: none }` (phase-162) wins everywhere */

/* ---- 2c. animations: the same two faults, one shorthand family over ------ */
.logo:hover .logo-mark circle,
.rail-logo:hover .logo-mark circle {
  animation-timing-function: var(--ease-out);
  animation-duration: var(--dur-scene-4);
}
/* Two animations on one element: the entrance, and the 8s drift that is a
   period and keeps it. One timing function covers both items; the phone rule
   at :2482 lists only the entrance, and a spare duration value is ignored when
   there is no second animation-name, so this one rule is right at both widths. */
.app .logo-mark circle {
  animation-timing-function: var(--ease-out);
  animation-duration: var(--dur-scene-2), 8s;
}
.auth-card { animation-timing-function: var(--ease-out); animation-duration: var(--dur-scene-2); }
.group, .calendar, .timeline-view, .files-view, .dw {
  animation-timing-function: var(--ease-out);
  animation-duration: var(--dur-micro-3);
}
.logo-mark.beat circle { animation-duration: var(--dur-scene-2); }
.cell.live-arrive::after { animation-duration: var(--dur-scene-4); }
.group-count.bump b { animation-timing-function: var(--ease-out); animation-duration: var(--dur-scene-5); }
.file-mini .fm-img.loaded { animation-duration: var(--dur-scene-2); }
/* icRing is six rotation stops and :2255 kept it off the shared 180ms on
   purpose, so that a damped oscillation has room to damp. 240ms was a literal;
   280ms is the nearest rung and gives each stop MORE room, not less. */
button:hover > .ic[data-ic="bell"] { animation-duration: var(--dur-micro-3); }
#team-btn .team-stack .avatar { animation-duration: var(--dur-scene-2); }
.ring-svg { animation-duration: var(--dur-scene-4); }

/* ---- 2d. the four ambient loops that still named a keyword -------------- */
/* These carry `infinite`, so 2c's sweep of one-shot durations never reached
   them, and they are on the marketing surfaces the shared header and footer
   still render on #/privacy and #/terms. Their PERIODS stay (11s, 9s, 2.4s,
   2s are what makes them ambient rather than a signal); only the curve moves.
   An ease-in-out pulse is symmetric and the one curve is not, so these four
   will lean into their rise and ease out of their fall. That is the product
   having one voice, and it is the change D36 is asking for. */
.live-dot::after { animation-timing-function: var(--ease-out); }
.cta-orb.o1, .cta-orb.o2 { animation-timing-function: var(--ease-out); }
.badge { animation-timing-function: var(--ease-out); }

/* ---- 3. the stiller, restated after the tokens it has to reach ---------- */
/* :287 zeroes these same six names. It is an earlier @media rule and the :root
   above is a later plain rule of equal specificity, so without this block the
   six tokens would come back to life for a reader who asked for no motion.
   The two new rungs are named here too, which is the half D20 asked for
   ("prefers-reduced-motion zeroes every duration token, including the new
   ones, in the same block as the old ones"). */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-micro-1: 0.01ms; --dur-micro-2: 0.01ms; --dur-micro-3: 0.01ms;
    --dur-scene-1: 0.01ms; --dur-scene-2: 0.01ms; --dur-scene-3: 0.01ms;
    --dur-scene-4: 0.01ms; --dur-scene-5: 0.01ms;
  }
}

/* ====================================================== phase-84 (Q30 / D31)
   ONE TEXT TYPEFACE. The app's body face becomes the landing's face. This
   block changes no layout, no colour, no material and no motion: it re-points
   two tokens, and nothing else in the product names a font family literally.

   THE DEFECT, in the owner's words: the landing feels premium and "during the
   process and after login into the site the feeling is lost". It is literal.
   Every character after the login page was set in Golos Text; every character
   before it, including the landing's own 13px drawing of THIS board, is
   Schibsted Grotesk. It hid for months because fonts.css called the second one
   "Alioro Display" and welcome.css calls it "Alioro", so both stylesheets read
   as if they agreed. Invariant 13 defended the split by calling the landing's
   face a display face; welcome.css:89 sets it on `body`, so the premise was
   false, and DESIGN_DIRECTION-APP.md 11.8 has superseded it.

   WHY IT IS SAFE AT THE OWNER'S SCALE, measured on the 487-row register before
   this block was written (advance widths read from the two woff2 files, at
   wght 500, with each face's own GPOS kern pairs applied):
     document names that render in full in the 282px name box, 13.5px/500
       Golos Text        367 of 487   75.4%
       Schibsted Grotesk 381 of 487   78.2%     14 names gain, none loses
     the register's longest title, 13.5px
       Golos 575.6px  ->  Schibsted 564.3px     2.0% narrower
     mean over all 487 titles: Schibsted is 2.81% NARROWER.
   The face buys column room, it does not spend it. It does not reach 6's 80%
   floor - but neither does today's face, which sits 4.6 points under it while
   this one sits 1.8 under, so the floor is a pre-existing gap and widening the
   name column to close it is not smuggled in here.

   FIGURES. Both faces set proportional figures by default and both ship a
   `tnum` feature, so the tabular-nums declarations at :347, :893, :899 and the
   twelve that follow keep working unchanged and none is added or removed here.
   What changes is that they start working properly: Schibsted Grotesk's tnum
   digits are all 0.6348em, spread ZERO, while Golos Text's tnum leaves six of
   the ten digits at their proportional width (0.580em to 0.630em, a 0.04em
   spread - 0.5px per digit at 12.5px). Of the 91 distinct dates the register
   renders, Golos draws them at 62 different widths and Schibsted at 18, which
   is exactly the number of distinct month words: under this face the digits in
   a date column line up and only the month name moves. The date cell does get
   wider, because a tabular digit is wider than the proportional one it
   replaces: the register's worst date, "May 27, 2026" at 12.5px, goes 80.3px
   -> 86.3px inside the 92px content box of a 108px Revised/Effective column.
   It fits with 5.7px to spare, and it is the tightest cell on the board.

   ITALIC. --font-body had NO italic registered against it: `Alioro Sans`
   declares normal only, so every <em> in the app was a synthetic slant. The
   face now under it carries a real italic in both subsets (fonts.css), so
   italic is answered by a drawn face for the first time.

   --font-mono is NOT touched here. It is Q34, and if a mono label looks newly
   wrong beside this face that is a finding for Q34, not a fix for this block. */
:root {
  /* One family, named as the landing names it. The two tokens converge on
     purpose (D31): no consumer moves, and the type ladder D33 locks is carried
     by size, weight and tracking, which is what the landing's own board picture
     does. The fallbacks name the real face for a machine that has the font
     installed, then the platform UI stack. */
  --font-display: "Alioro", "Schibsted Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
  --font-body:    "Alioro", "Schibsted Grotesk", -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
}

/* ==========================================================================
   phase-85: the title strip stops chasing the rows, and the board stops
   being dragged backwards by its own scrollbar
   ==========================================================================
   Two independent faults on one surface, both measured on the owner's
   register, both reported by him as one symptom ("2 steps forward and one
   back", "with the trackpad it looks like it is vibrating").

   1. THE TITLES WERE A FRAME BEHIND. D29's strip is slid sideways by a
      transform, and until now the offset for that transform was a custom
      property written from a scroll listener inside requestAnimationFrame.
      The rows move on the compositor and wait for nobody; a listener cannot
      run before the frame it is reacting to has already been shown. So the
      titles sat exactly one frame behind the rows for the whole of every
      gesture - 6.5px at 700px/s, 12.5px at 1400px/s, 23.5px at full speed,
      alternating, which reads as the two halves of the board shearing.
   2. The proxy scrollbar and the surface each wrote the other's scroll
      position behind a 0.5px dead band; see board.js for that half. Fixing 2
      makes 1 THREE TIMES more visible, because the board finally moves at the
      speed the gesture asked for, which is why they ship together.

   THE ALIGNMENT NOW BELONGS TO THE BROWSER. .board-scroll publishes its own
   horizontal scroll progress as a named scroll-progress timeline, and the
   strip's two moving parts are animated from it: same frame as the rows, on
   the compositor, with no script anywhere in the path.

   WHY THE STRIP IS STILL A SIBLING OF THE SCROLLER, AND NOT A STICKY CHILD OF
   IT (the obvious answer, and it does not work): `overflow-x: auto` computes
   overflow-y to auto, so .board-scroll is a scroll container in BOTH axes,
   and a sticky box sticks to its NEAREST scrollport. Inside the scroller the
   strip's `top` would resolve against .board-scroll's own box, which has an
   auto height and therefore never scrolls vertically - the strip would sit a
   head's height below the top of the board and leave the screen with it,
   which is the state D29 was built to end. D28 records this consequence in
   its own words ("a scroll box traps sticky ... D29 is built outside the
   scroller for exactly this reason"). Staying outside also keeps the band
   full-bleed to the pane edges (:6583), which a child of the scroller cannot
   be, and keeps it clear of the paint containment .group carries from
   content-visibility.

   THE ARITHMETIC, because it is the one clever thing here and a later session
   must be able to check it. A scroll timeline's progress p is
   scrollLeft / (scrollWidth - clientWidth), and the strip must be translated
   by exactly -scrollLeft. The track is made as wide as the SCROLLPORT
   (width: 100%) with its slots overflowing it, so a percentage in its own
   transform resolves against clientWidth, and --tw carries scrollWidth from
   the same tableWidth every table is stamped with. Then
   translateX(100% - var(--tw)) at p = 1 is clientWidth - scrollWidth, and at
   progress p the browser interpolates p * (clientWidth - scrollWidth), which
   IS -scrollLeft, for any width, with no measurement and nothing to keep in
   step on a resize. .bcols-pin carries the inverse and cancels it, which is
   what `position: sticky; left: 0` does for the table's frozen name column.
   `linear` is required and is not a curve choice: the timing function maps
   scroll position to position, so anything else would misplace the titles.
   D36's one-curve lock exempts linear for exactly this kind of continuous
   mapping. */
.board-body { timeline-scope: --board-slide; }
.board-scroll { scroll-timeline-name: --board-slide; scroll-timeline-axis: inline; }
@keyframes bcols-slide {
  from { transform: translateX(0); }
  to { transform: translateX(calc(100% - var(--tw, 100%))); }
}
@keyframes bcols-pin {
  from { transform: translateX(0); }
  to { transform: translateX(calc(var(--tw, 100%) - 100%)); }
}
/* The track is the scrollport's width now, not the surface's, and the slots
   overflow it: see the arithmetic above. min-width at :6594 is the same 100%
   and stays true. */
.bcols-track { position: relative; width: 100%; }
/* `clip`, not `hidden`. hidden is a scroll container, so tabbing to the
   nineteenth column's menu button scrolled the strip's own box sideways and
   left it out of step with the board for good - a second surface by accident.
   clip cannot be scrolled by anything, including the browser. */
.bcols-clip { overflow: clip; }
/* The name column's slot, lifted out of the flow so it can be counter-slid as
   one box. Its width is the track's, which is what makes the percentage in
   its keyframe mean the scrollport; the visible slot inside it keeps the
   380px the table's first column has. It must not swallow clicks meant for
   the titles sliding under it. */
.bcols-pin {
  position: absolute; left: 0; top: 0; bottom: 0; width: 100%;
  z-index: 1; display: flex; align-items: stretch; pointer-events: none;
}
.bcols-pin .bcol-name { flex: 0 0 auto; pointer-events: auto; }
/* the slack a board narrower than the viewport leaves is absorbed by the
   lead slot in the flow, where the table's auto-width first <col> absorbs it */
.bcol-lead { flex: 1 0 auto; }
@supports (animation-timeline: --board-slide) {
  .bcols-track, .bcols-pin {
    animation-timing-function: linear;
    animation-fill-mode: both;
    animation-timeline: --board-slide;
  }
  .bcols-track { animation-name: bcols-slide; }
  .bcols-pin { animation-name: bcols-pin; }
  /* under 701px the table's name column is not frozen (:1845), so the slot
     rides the track with everything else - the same call the counter-slide's
     own phone override makes at :6631 */
  @media (max-width: 700px) { .bcols-pin { animation-name: none; } }
  /* The stiller at :1480 zeroes every animation-duration in the app. These
     two are not motion: they are where the titles ARE, and a 0.01ms duration
     on a progress timeline puts them at the end of their range at the first
     pixel of scroll. A reader who asked for less movement is asking for less
     movement, not for the wrong column names. */
  @media (prefers-reduced-motion: reduce) {
    .bcols-track, .bcols-pin { animation-duration: auto !important; }
  }
  /* paper has no scrollport to be a fraction of */
  @media print { .bcols-track, .bcols-pin { animation-name: none; } }
}
/* restated after this block's own plain rules, because a later plain rule
   beats an earlier media one and the print block at :6634 is earlier: the
   strip is let out to the full width of the body it labels, or eleven of
   nineteen columns print with no title */
@media print { .bcols-clip { overflow: visible; } }


/* ════════════════════════════════════════════════════════════════ phase-86
   Q31-a: THE LANDING'S MATERIAL ON THE CHROME AND THE FLOATING PANELS.

   D34's assignment table, applied to the surfaces where a backdrop filter is
   allowed and cheap. Cards and rows are Q31-b and are untouched below: not one
   selector in this block names .card, .dw, .fv-card, .tpl-card, .admin-card,
   .kanban-card, .cal-cell, .tl-scroll, .snap-row, .auto-row, .rcard, .gjump-chip
   or anything inside .board-table, and nothing here puts a backdrop-filter on a
   surface that can appear more than once on a page.

   WHAT WAS WRONG, in numbers, measured against the app's own tokens rather than
   the landing's page (the landing's light ground is .988; ours is #f4f5f8):

     a pane over a BOARD ROW (--surface-1), which is what a popover, a peek and
     a side panel actually float over:
                        light        dark
       phase-39 veil    1.025:1      1.017:1
       lg-regular       1.086:1      1.050:1

     the veil's rim, --glass-line, measured against the veil it sits on rather
     than against white:
       light  1.044:1   - a white line on a near-white pane. It has never been
                          visible in the light theme, in any phase.
       dark   1.542:1
     the landing's edges, against the pane they sit on:
       light  in-hi 1.104 · in-lo 1.519 · edge 1.348 · rim-3 2.756
       dark   in-hi 2.361 · in-lo 1.112 · edge 1.187 · rim-3 5.100

   So the separation moves from a film to an edge, which is the whole ruling.
   Two surfaces come out of the glass entirely and are SOLID instead of veiled,
   because solid beats the veil on the same measurement and costs no layer. --- */

:root {
  /* ONE value under three names was the depth bug: page chrome, a floating pane
     and a card cannot read at different depths while they all resolve to
     rgba(251,251,253,.74). Split here.
       --glass-chrome  chrome that stands BESIDE or PINNED OVER content and may
                       never take a filter (the rail, the board head). Real
                       ground: --surface-1 on a --surface-0 page is 1.090:1
                       light and 1.087:1 dark, where the veil gave 1.040 / 1.050.
       --glass-bg      a pane that FLOATS over content: no fill at all. Its
                       colour is its filtered backdrop (welcome.css §12, and the
                       landing-v19 ruling "no fill, no veil, no tint").
       --glass-card    deliberately NOT redefined. Cards are Q31-b, and pulling
                       their ground out from under them belongs with that
                       phase's no-filter check. It still resolves to the phase-39
                       veil and it is now the ONLY name that does. */
  --glass-chrome: var(--surface-1);
  --glass-bg: transparent;

  /* the .lg-float pair (:6146) as a token. It is needed as a token because
     phase-39 wrote `.peek.peek-card` at :2552, and (0,2,0) out-ranks
     `.lg-float` at (0,1,0); a token lets the override be written at matching
     specificity instead of with !important. */
  --lg-float-sh: 0 22px 48px -22px rgba(0, 0, 0, calc(var(--lg-sh) * var(--lg-o))),
                 0 3px 10px -4px rgba(0, 0, 0, calc(var(--lg-sh2) * var(--lg-o)));
  /* welcome.css:966-968 verbatim, as a token, for the two panes that own their
     own scroll and therefore cannot carry ::before / ::after layers. These are
     box-shadows: they are free, and an inset shadow is painted on the box, so
     unlike an absolutely positioned pseudo-element it does not scroll away. */
  --lg-inner: inset 0 1px .5px var(--lg-in-hi), inset 0 -1px .5px var(--lg-in-lo),
              inset 0 0 14px -4px var(--lg-in-glow), inset 0 0 0 .5px var(--lg-edge);
  /* a docked panel throws its shadow sideways into the board it covers, at a
     negative spread so the shadow lives UNDER the panel instead of haloing its
     edge (D35's shape, applied only to the surfaces this phase touches). */
  --lg-dock-sh: 40px -20px rgba(0, 0, 0, calc(var(--lg-sh) * var(--lg-o)));
}

/* ---- the popover, and its phone sheet: the landing's REGULAR glass, with the
   filter on the ELEMENT rather than on a ::before layer.

   Why not the .lg primitive. `.popover` owns its own scroll (:1085,
   `max-height: 70vh; overflow-y: auto`) and three call sites reassign its
   innerHTML (board.js:894, :910, :2923). An absolutely positioned ::before
   inside a scroll container scrolls WITH the content, so the backdrop and the
   rim would ride up and out of any menu taller than 70vh; and the wrapper
   element that would fix that is destroyed by the next innerHTML assignment.
   Five of the landing's six layers survive here as box-shadows. The sixth, the
   masked gradient rim on ::after, is the one this pane cannot have; it is
   reported for the designer rather than half-applied.

   Why REGULAR and not the CLEAR that D34's table assigns. The landing makes
   this exact exception for this exact surface: welcome.css:989 bumps
   `.asc-menu.gl` from clear to regular, with the reason written on the rule -
   "the menu carries text over the moving field: the panel's deeper backdrop".
   Clear in light is blur 3px at .546 passthrough, which is phase-46's rejected
   .6 veil with a 3px blur in front of it, over a 19-column register; css.md's
   surviving observation ("a pane that does not fully separate its own text
   from the rows behind it is unreadable") is the binding half. D34's stated
   reason for clear, "small, few at a time", is an argument about COST, not
   about legibility, and cost is not what decides a weight.  ---- */
/* UPDATED 2026-09-21, phase-97, D46 §3d and §9 item 3: THE POPOVER JOINS THE
   MATERIAL INSTEAD OF COPYING IT. Five of the owner's seven screenshots are
   this pane. It had the filter and the inner shadows but NOT the specular rim,
   for one mechanical reason: phase-88 hand-copied `--lg-fx` here (a second,
   identical spelling of the chain) instead of taking the class, and a copy
   keeps what its author remembered. What was left behind was the rim, which is
   the layer that makes glass read as glass rather than as frosted plastic.
   core.js:513 now writes `lg lg-regular lg-float` on the element, so the
   weights, the chain and the ::after rim all arrive from the primitive at
   :6127 and there is exactly ONE `--lg-fx: blur(` in this file (D46 Check D).

   TWO DEVIATIONS FROM D46 §3a, both measured rather than preferred, because
   §3a says only "the filter moves from the element to its ::before" and this
   pane is the one surface where that is not free:

   1. THE FILTER STAYS ON THE ELEMENT. `.popover` owns its own scroll
      (`max-height: 70vh; overflow-y: auto`, :1079), and an `inset: 0`
      pseudo-element inside a scroll container is anchored to the scrolled
      CONTENT, not to the visible pane - it rides up and out of any menu taller
      than 70vh. That is phase-86's own finding, measured on THIS element
      (:7143), repeated by `.tl-scroll` in phase-91, and it is why `--lg-inner`
      exists as a token at all. So the chain is read from the class and applied
      where scrolling cannot move it. This is not a second recipe: `--lg-fx` is
      inherited, never re-spelled.
   2. `::before` IS SWITCHED OFF (`content: none`) rather than left to paint.
      Its job in the primitive is the filter plus the four inner shadows; the
      filter is above and the identical four are already on this element as
      box-shadows through `--lg-inner`. Leaving it would double the inner
      shadows AND put them on the scrolling layer.

   COST: ZERO new compositing layers. The element keeps the one backdrop-filter
   it has had since phase-88, `::before` does not exist, and the `::after` rim
   is a masked paint, not a layer. ---- */
.popover {
  background: var(--glass-bg); border: 0;
  -webkit-backdrop-filter: var(--lg-fx); backdrop-filter: var(--lg-fx);
  box-shadow: var(--lg-inner), var(--lg-float-sh);
}
.popover::before { content: none; }
/* the phone sheet's GRAB HANDLE is `.popover.sheet::before` (:2805) at (0,2,0),
   so it restores `content` over the line above and would otherwise inherit the
   primitive's other ::before declarations with it - a 36x4px bar sent behind
   the sheet at z-index -1, wearing the four inner shadows and a backdrop
   filter on a phone, where the chrome is solid. Restated, not re-invented:
   only the four declarations the primitive contributes are answered. */
.popover.sheet::before {
  right: auto; bottom: auto; z-index: 1; box-shadow: none;
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
/* KNOWN AND REPORTED, not hidden: the ::after rim is an `inset: 0` absolutely
   positioned box, so on a popover tall enough to scroll ITSELF (70vh) it is
   anchored to the scrolled content and rides, the same law that keeps the rim
   off `.tl-scroll` (:8065). Most panes scroll an inner list instead (the board
   switcher's `.bs-list`, the people picker), and every pane in the owner's
   seven screenshots is shorter than 70vh. The structural answer is a wrapper
   element, which `SD.popover`'s three innerHTML call sites make a phase of its
   own; it is not smuggled in here. */

/* ---- the modal: SOLID, and this is the one row of D34's table that the
   arithmetic overturns rather than confirms.

   A modal's backdrop is not the board. It is the scrim: `.modal-overlay`
   (:2558) paints var(--scrim) under it, which composites to rgb(147,149,153)
   in light and rgb(8,9,10) in dark. Filtering THAT is the problem.
                                       light            dark
     lg-regular over the scrim       1.883:1          1.093:1   rgb(204,204,207)
     phase-39 veil over the scrim    2.290:1          1.142:1   rgb(224,224,227)
     solid --surface-1               3.010:1          1.222:1
   The filtered pane is the WORST of the three in both themes, and in light it
   is a grey dialog in a white product. There is no fill available to lift it -
   "no fill, no veil, no tint" is the ruling - so the correct pane over a scrim
   is a real surface wearing the landing's EDGES, which is exactly what D34
   gives a card. It also drops a viewport-sized compositing layer that has sat
   behind every dialog in the product since phase-39.

   The modal keeps `.modal-overlay`'s opacity fade, which is now harmless:
   there is no backdrop left for an ancestor's opacity to cut. ---- */
.modal {
  background: var(--glass-chrome); border: 0;
  -webkit-backdrop-filter: none; backdrop-filter: none;
  box-shadow: var(--lg-inner), var(--lg-float-sh);
}

/* ---- the four panes that CAN carry the primitive, because none of them is
   its own scroll container: `.peek` has no overflow, `.sidebar` scrolls its
   `.side-scroll` child, and the two docked panels are `overflow: hidden`. Their
   ::before backdrop and ::after rim therefore stay pinned to the pane.
   board.js gives them `lg lg-regular` (plus `lg-float` on `.peek`); everything
   below only takes the phase-39 material back off them, so the layers
   underneath have something to show.

   Each loses its 1px border on purpose: the ::after rim IS the edge now, and
   two edges disagree in both themes (the same call the palette pilot made at
   :6180). `* { box-sizing: border-box }` (:332) holds every width. ---- */
.peek, .peek.peek-card, .sidebar, .chat-panel, .ai-panel {
  background: var(--glass-bg); border: 0;
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
.peek, .peek.peek-card { box-shadow: var(--lg-float-sh); }
.sidebar { box-shadow: 12px 0 var(--lg-dock-sh); }
/* .ai-panel carried `-8px 0 30px rgba(0,0,0,.08)` (:1338): spread 0, so its
   darkest band lay along its own edge, and a different recipe from its twin
   .chat-panel, which is a twin defect on its own. One recipe now. */
.chat-panel, .ai-panel { box-shadow: -12px 0 var(--lg-dock-sh); }

/* ---- chrome takes real ground and never a filter.

   The rail stands BESIDE the content (invariant 9), and in dark it was already
   solid because the :600 gradient out-ranks the veil at (0,2,0); only light was
   veiled, and it goes 1.040:1 -> 1.090:1 against the page.

   The board head is the phase-46 trap at its worst - a full-width filter pinned
   over 487 scrolling rows, recomposited on every frame - and it has been
   carrying one since phase-39. The landing draws its own board head solid, on
   `var(--app-bg)` (welcome.css:386), so this is the reference and not a
   compromise. Dark: 1.050:1 -> 1.087:1. Light: 1.040:1 -> 1.090:1.

   Both keep the 1px edge they already carry, and neither takes the `.lg-solid`
   rim D34 gives them: that variant is built in Q31-b with the check that proves
   no card acquires a filter, and a gradient rim laid over a border that
   `.board-head.scrolled` still animates is two edges disagreeing. ---- */
.rail { background: var(--glass-chrome); }
.board-head {
  background: var(--glass-chrome);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}

/* ---- reduced transparency: the ONE place a tint is allowed, restated for the
   element-level pane above. The :6205 block already answers this query for
   `.lg::before`; it cannot answer it for a filter written on `.popover`
   itself, and it cannot make a `.lg` pane fully opaque, because the tint it
   sets is .94 and the element under it is transparent from this block on. ---- */
@media (prefers-reduced-transparency: reduce) {
  .popover {
    background: var(--surface-1);
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
  .peek, .peek.peek-card, .sidebar, .chat-panel, .ai-panel { background: var(--surface-1); }
}

/* ---- phones: the chrome is solid. Restated a third time (:3323, :6225)
   because this block is APPENDED, and a later plain rule beats an earlier
   media rule: the backdrop-filter written on `.popover` above would otherwise
   put a blur back under every phone sheet, and the transparent backgrounds
   above would let the board through solid chrome. KEEP ANY NEW GLASS RULE
   ABOVE THIS BLOCK. ---- */
@media (max-width: 700px) {
  .popover, .modal, .peek, .peek.peek-card, .sidebar, .chat-panel, .ai-panel,
  .rail, .board-head {
    -webkit-backdrop-filter: none; backdrop-filter: none;
    background: var(--surface-1);
  }
  /* the primitive's own layers, for the four panes that carry it. The inner
     shadows stay - they are edges, not blur - so a phone sheet keeps the same
     rim treatment the desktop has, with no filter behind it. */
  .peek::before, .peek::after, .sidebar::before, .sidebar::after,
  .chat-panel::before, .chat-panel::after, .ai-panel::before, .ai-panel::after {
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
}


/* ════════════════════════════════════════════════════════════════ phase-87
   Q31-c: CONTENT ON GLASS IS OPAQUE AND CARRIES ITS OWN EDGE.

   The owner, on phase-86's dark theme: "the content has not been readjusted
   yet to match the new liquid glass... we might need to go with a lighter
   colour for the content to be visible... the glass with the dark grey, you
   can somewhat see it in person and it looks poorly designed."

   THE MECHANISM, and why "lighter" alone does not finish it. Phase-86 set
   --glass-bg: transparent, so a pane's colour IS its filtered backdrop. Every
   control on it was still painted for the old veil, which had exactly ONE
   colour to beat. Measured with phase-86's own method (resolve the chain
   blur->contrast->brightness->saturate in sRGB, then WCAG), the pane's
   luminance over the sixteen grounds a floating pane actually sits on in this
   app (the four surfaces plus the twelve DATA chips) spans:

     dark   L .0136 .. .3210   =  5.84:1 between the lightest and darkest pane
     light  L .4397 .. .9168   =  1.97:1

   A FIXED fill therefore has UNDEFINED contrast, whatever value it is given.
   What is deterministic is a pair: an OPAQUE fill (the backdrop cannot reach
   through it) plus a rim measured against THAT FILL. Writing f for the fill's
   luminance, r for the rim's and p for the pane's, the separation a reader
   gets is max(C(f,p), C(r,p)); the worst p sits at the geometric mean of the
   two in (L+.05) space, so the guaranteed floor is exactly

       sqrt(C(fill, rim))                    <- independent of the backdrop

   and that is the number tests/api_test.py now asserts, per control, per
   theme. Today's `.qf-chip` is `--surface-2` with `border: 1px solid
   transparent`: rim == fill, so its guaranteed floor is 1.00:1 — literally
   nothing — and over a plain board row it measured 1.079:1 in dark and
   1.051:1 in light. That is what he is looking at.

   THE BLUE BAND, which he also reported, is NOT the same root cause and the
   weight is NOT wrong. Measured against the landing's own panes:
     the app, today:    a solid DATA fill behind the pane (chroma .65)
                        -> band reads 3.39:1 (blue) and 5.50:1 (yellow)
                           against a pane over a plain row, in dark
     the landing:       its strongest saturated source is a 9-16% hue mix over
                        the sheet (chroma .11-.17) -> 1.07:1 and 1.14:1
   Same tokens, same chain, verbatim: --lgr-* here IS welcome.css:906-908. The
   landing's glass normalises the landing's sources correctly. The app simply
   feeds it a source four times more saturated than anything the landing has,
   because a board legitimately paints solid DATA colour edge to edge. Retuning
   --lgr-sat/-con for that would fork the one material for one screenful, which
   .claude/rules/css.md forbids ("do not invent a second recipe", and "do not
   chase legibility with saturation"). With the content opaque, the band is
   confined to the pane's own gutters, which is a pane showing what is behind
   it — the point of glass. LEFT AS IS, DELIBERATELY, with the numbers above.

   SCOPE. Every control that paints a persistent fill or a rim on one of the
   surfaces phase-86 made transparent: .popover (and its phone sheet), .peek /
   .peek-card, .sidebar, .chat-panel, .ai-panel, plus #cmd-palette .palette,
   the phase-73 pilot the assignment table forgot to list. NOT included, and
   each for a stated reason:
     - hover WASHES (--hover-ground, --hover) stay translucent. A wash is a
       relative lift by a fixed proportion of whatever it covers, it has no
       shape of its own, and it is not what he reported.
     - the focused field. `.ch-compose textarea:focus` and
       `.board-search:focus-within` keep the backgrounds they have; the lead's
       brief puts focus out of bounds, and both are already defined while
       focused by a --brand border and by the fan ring. Reported, not changed.
       SUPERSEDED 2026-09-21 (phase-89), left visible per the standing method
       because this sentence is the reason the next session would not look:
       the second half of it is FALSE for the composer, and qa measured it.
       `.board-search:focus-within` does wear the fan ring (its ::before,
       :2561). `.ch-compose textarea` wore NOTHING while focused: border
       rgba(0,0,0,0), no ring, no shadow, no outline, against the
       rgba(255,255,255,.24) rim the rule below gives it at REST. Focusing the
       field removed the only edge it had, so what this block fixed at rest it
       inverted at focus. The --brand border named above never painted either;
       :1919 sets it and :2819 zeroes it at equal specificity, later. Fixed at
       the end of this file (phase-89 §1), where the mechanism is written out.
     - DATA colour. A label chip's dot, a workspace glyph, a tag's hue and an
       avatar keep their DATA value untouched (hard rule 5). Only the PLATE
       under them changes.
     - cards and rows (Q31-b), and .modal / .rail / .board-head, which
       phase-86 made solid and which are therefore not glass.  ---- */

:root {
  /* the plate a control wears when it sits on a floating pane. Light's is the
     product's own paper; the pane it sits on settles to ~#f5f5f5, so the plate
     reads by its edge, exactly as css.md's "move the colour to an edge" says a
     pale-on-pale problem must be solved. */
  --on-glass:     var(--surface-1);
  --on-glass-hi:  var(--surface-2);
  /* the edge is --lg-edge-1, the landing's own STRONGER edge (welcome.css:912
     / :934), already used by .lg-tinted. Not a new value and not a white line
     on a white pane: phase-86 measured --glass-line at 1.044:1 in light and
     that mistake is not repeated here. Against its OWN opaque fill it is
     2.12:1 light and 2.05:1 dark, which is what fixes the floor. */
  --on-glass-rim: var(--lg-edge-1);
  /* a 1px DIVIDER inside a pane is not a control and does not want a control's
     edge; --line-2 on glass measured 1.12:1 light and 1.25:1 dark. This lands
     at 1.55:1 light and 1.51:1 dark over a pane on a plain row. */
  --on-glass-line: rgba(13, 15, 19, .20);
  /* the selected plate stays BRAND and stays opaque: --brand-soft is a
     color-mix with `transparent`, so on a transparent pane it was the same
     undefined-contrast problem in brand clothing. Mixing into --on-glass
     instead keeps the hue and fixes the value. */
  --on-glass-sel:     color-mix(in srgb, var(--brand) 11%, var(--on-glass));
  --on-glass-sel-rim: color-mix(in srgb, var(--brand) 55%, var(--on-glass));
  /* secondary text on the plate. His "lighter colour for the content", and it
     is also the arithmetic: --text-3 on the dark plate is 4.20:1, under AA for
     a 10.5px mono count; --text-2 is 6.49:1. */
  --on-glass-sub: var(--text-2);
}
[data-theme="dark"] {
  /* the graphite ramp's NEXT step, not a new hue: 16181d -> 1d2025 -> 262a30
     -> 30353c continues x1.17 to (56,62,71). It is "lighter", per his
     instinct, and it is the value that makes the plate itself 1.44:1 against a
     pane over a plain row where --surface-2 gave 1.079:1. */
  --on-glass:    #383e47;
  --on-glass-hi: #434a55;
  --on-glass-line: rgba(255, 255, 255, .13);
  --on-glass-sel: color-mix(in srgb, var(--brand) 18%, var(--on-glass));
}

/* ---- the quick filters: the panel he screenshotted.
   rest 1.079 -> 1.44:1 plate + a 2.96:1 rim, guaranteed 1.43:1 in dark;
   1.051 -> a 1.95:1 rim, guaranteed 1.45:1 in light. The DATA dot, the avatar
   and the file glyph inside a chip are untouched. ---- */
.popover .qf-chip {
  background: var(--on-glass); border-color: var(--on-glass-rim); color: var(--text-1);
}
.popover .qf-chip b { color: var(--on-glass-sub); }
.popover .qf-chip.on { background: var(--on-glass-sel); border-color: var(--on-glass-sel-rim); color: var(--brand); }
.popover .qf-chip.on b { color: var(--brand); }
/* `empty` keeps its meaning in the DASH, which is a shape and survives an
   opaque fill; it used to carry the meaning in a transparency that now says
   nothing at all, because the pane behind it is transparent too. */
.popover .qf-chip.empty { background: var(--on-glass); border-color: var(--on-glass-rim); border-style: dashed; color: var(--text-3); }
.popover .qf-chip.more { background: var(--on-glass); border-color: var(--on-glass-rim); color: var(--text-2); }
.popover .qf-chip.empty.on { background: var(--on-glass-sel); border-color: var(--on-glass-sel-rim); border-style: solid; color: var(--brand); }
.popover .qf-chip.bad.on {
  background: color-mix(in srgb, var(--danger) 14%, var(--on-glass));
  border-color: color-mix(in srgb, var(--danger) 50%, var(--on-glass));
}
/* hover last, and `:not(.on)` so a selected chip keeps its brand plate under
   the pointer exactly as it does today (.qf-chip.on at :2105 wins over
   .qf-chip:hover at :2104 by source order; this restates that, not changes
   it). .more's own hover edge is restated after, at (0,5,0). */
.popover .qf-chip:not(.on):hover { background: var(--on-glass-hi); border-color: var(--on-glass-rim); }
.popover .qf-chip.more:not(.on):hover { border-color: var(--line-strong); }
/* phase-167 groundskeeping: removed the .popover .qf-sec-head .qf-ic glass tile; phase-166 (later, same specificity) sets both properties */
.popover .qf-section { border-bottom-color: var(--on-glass-line); }
.popover .qf-head { border-bottom-color: var(--on-glass-line); }

/* ---- the search box inside a pane. It was
   `color-mix(in srgb, var(--surface-1) 72%, transparent)`, i.e. a veil, and it
   measured 1.036:1 in dark: a text field nobody can see the edges of. Focus is
   NOT touched — :2569 still owns :focus-within — so this is restated at (0,3,0)
   only to keep that rule winning over this one. ---- */
.popover .board-search, .sidebar .board-search {
  background: var(--on-glass); border-color: var(--on-glass-rim);
}
.popover .board-search:focus-within, .sidebar .board-search:focus-within {
  background: var(--surface-1); border-color: transparent;
}

/* ---- menus, notifications, the team list and the command palette ---- */
.popover .menu-item.on, #cmd-palette .palette .palette-row.sel { background: var(--on-glass-sel); color: var(--brand); }
.popover .menu-sep { background: var(--on-glass-line); }
.popover .notif-row.unread { background: var(--on-glass-sel); }
.popover .tp-row .tp-act .icon-btn.on { background: var(--on-glass-sel); }
/* a presence dot is a DATA/signal colour cut out of its ground by a ring; the
   ring was --surface-1, which on a transparent pane is 1.05:1 and cuts nothing */
.popover .tp-row .avatar::after { border-color: var(--on-glass); }

/* ---- the peek (the hover card over a cell) ---- */
.peek .tag, .popover .tag {
  background: var(--on-glass); box-shadow: inset 0 0 0 1px var(--on-glass-rim);
}
.peek .pf-row .pf-dl { background: var(--on-glass-sel); }
.peek .attach-ver-chip { background: var(--on-glass); box-shadow: inset 0 0 0 1px var(--on-glass-rim); }
.peek .pf-meta, .peek .pf-more { color: var(--on-glass-sub); }
.peek .pf-foot, .peek .peek-files > .pf-sec:not(:first-child) { border-top-color: var(--on-glass-line); }

/* ---- the boards drawer ---- */
.sidebar .side-link > .ic { background: var(--on-glass); box-shadow: inset 0 0 0 1px var(--on-glass-rim); }
.sidebar .side-link.active, .sidebar .side-board.active { background: var(--on-glass-sel); }
.sidebar .side-link.active > .ic, .sidebar .side-link:hover > .ic { background: var(--on-glass-sel); }
.sidebar .drawer-head { border-bottom-color: var(--on-glass-line); }

/* ---- team chat ---- */
.chat-panel .ch-room-ic { background: var(--on-glass-sel); }
.chat-panel .ch-row.sel { background: var(--on-glass); box-shadow: inset 0 0 0 1px var(--on-glass-rim); }
.chat-panel .ch-row .avatar::after { border-color: var(--on-glass); }
.chat-panel .ch-thread-head, .chat-panel .ch-compose { border-color: var(--on-glass-line); }
.chat-panel .ch-day::before, .chat-panel .ch-day::after { background: var(--on-glass-line); }
.chat-panel .ch-meta span, .chat-panel .ch-text span, .chat-panel .ch-time { color: var(--on-glass-sub); }

/* ---- the Claude panel ---- */
.ai-panel .ai-bubble { background: var(--on-glass); box-shadow: inset 0 0 0 1px var(--on-glass-rim); }
/* the user's own bubble is already an opaque brand fill; it keeps it */
.ai-panel .ai-msg.user .ai-bubble { box-shadow: none; }
.ai-panel .ai-chip { background: var(--on-glass-sel); box-shadow: inset 0 0 0 1px var(--on-glass-sel-rim); }
.ai-panel .ai-mode { background: var(--on-glass); box-shadow: inset 0 0 0 1px var(--on-glass-rim); }
.ai-panel .ai-head, .ai-panel .ai-input { border-color: var(--on-glass-line); }
.ai-panel .ai-empty { color: var(--on-glass-sub); }

/* ---- the two composer fields. Every input in the product paints its plate as
   the FIRST layer of `background-image` (:2808), with the fan ring as the
   second, so a background-color cannot reach them; the plate is replaced in
   place and the ring layer is passed through untouched through a token.
   `:not(:focus)` keeps the focused appearance byte-identical. ---- */
:root {
  --field-ring: conic-gradient(from var(--ring-a), var(--logo-1), var(--logo-2), var(--logo-3), var(--logo-4), var(--logo-5), var(--logo-4), var(--logo-3), var(--logo-2), var(--logo-1));
}
.chat-panel .ch-compose textarea:not(:focus), .ai-panel .ai-input textarea:not(:focus) {
  background-image: linear-gradient(var(--on-glass), var(--on-glass)), var(--field-ring);
  border-color: var(--on-glass-rim);
}

/* ---- a SELECTED row or icon plate needs the same edge a chip does, and for
   a reason that is easy to miss: --on-glass-sel is a COLOURED fill, and a
   coloured fill can coincide in luminance with a pane that is filtering a DATA
   colour of the same hue. Measured: the selected plate against a pane over
   DATA #225091 is 1.09:1 in dark. The brand rim is what makes it
   deterministic, and it also reads as the selection, which a bare fill at that
   value does not. ---- */
.popover .menu-item.on, .popover .notif-row.unread,
.peek .pf-row .pf-dl, .chat-panel .ch-room-ic,
.sidebar .side-link.active, .sidebar .side-board.active,
#cmd-palette .palette .palette-row.sel {
  box-shadow: inset 0 0 0 1px var(--on-glass-sel-rim);
}

/* ---- phones: the panes are SOLID sheets down here (:7232), so --on-glass
   must not be the same value as the sheet under it. Light's plate is
   var(--surface-1) and so is the sheet, which would be a 1.00:1 plate; both
   themes step down to the surface that reads against it. This block is LAST,
   because a later plain rule beats an earlier media rule and the six rules
   above are plain. No filter is declared in this block. ---- */
@media (max-width: 700px) {
  .popover, .peek, .peek.peek-card, .sidebar, .chat-panel, .ai-panel, #cmd-palette .palette {
    /* --on-glass-hi is deliberately NOT overridden: the desktop pair still
       steps the right way against this plate in both themes (dark lifts to
       #434a55, light deepens to --surface-2), and a sticky :hover after a tap
       would otherwise move the wrong way. */
    --on-glass: var(--surface-3);
    --on-glass-line: var(--line-1);
  }
}


/* ════════════════════════════════════════════════════════════════ phase-88
   THE BOARD SURFACE RUNS TO THE PANE EDGES.

   The owner drew a yellow line down each side of his board. The column-title
   strip's band and its bottom rule ran 26px wider than the rows and the groups
   underneath them on each side, so the board read as a framed panel sitting
   inside the pane instead of as one surface — and that frame is eating exactly
   the width he has now twice asked to get back.

   WHERE IT CAME FROM, AND IT IS NOT A PHASE-85 REGRESSION (say so before
   anyone files one). `.board-cols` (:6582) has carried
   `margin: 0 calc(var(--board-pad-x) * -1)` with `padding: 0 var(--board-pad-x)`
   since D29: the band is let out to the pane's edges while its labels are
   padded back in to land over the rows, which `.board-body` (:767) insets by
   that same `--board-pad-x` — 26px at 1440, 14px at 768, 10px at 375. Those
   two declarations are byte-identical before and after D28 and D29. What
   changed is that fourteen separate per-group tables never drew one line
   across the whole width; merging them into a single surface and giving them a
   single title strip is what made the frame visible for the first time.

   THE RULING (lead, on the owner's direction): rows, groups and the title
   strip all stand on the SAME bounds, and those bounds are the pane's. The
   breathing room is not deleted and it is not rebuilt either — it was already
   inside the surface, in the padding every one of those cells carries, and it
   simply travels inward with them. The whole 2 × --board-pad-x goes to the
   columns: 52px of extra register at 1440, 28px at 768, 20px at 375, paid for
   with nothing. See section 2 for the draft that DID pay for it and why the
   lead reverted that within the hour.

   WHY THE PHASE-85 IDENTITY SURVIVES, which is the one dangerous part of this.
   `.bcols-track` is `width: 100%` of the scrollport and its keyframe resolves
   `translateX(100% - var(--tw))` against that width, which is -scrollLeft ONLY
   while the track's width equals `.board-scroll`'s clientWidth. The strip and
   the scroller are SIBLINGS inside `#board-body`, so both of those widths are
   one parent's content box. This block removes the padding from that ONE
   parent and the compensating margin from the strip, and adds nothing to the
   width of either child — so the two stay equal by construction, exactly as
   they were, and the proxy scrollbar's tableWidth / clientWidth thumb ratio is
   read off the same box. NEVER give `.board-body`, `.board-cols`,
   `.bcols-clip`, `.bcols-track` or `.board-scroll` a horizontal padding, a
   margin or a border: any one of them breaks the identity and the titles drift
   proportionally to it as the board scrolls, which is the shear the owner
   watched us fix last phase.

   AND IF A LATER PHASE WANTS THE GUTTER BACK, IT MAY NOT BE A PADDING ON THE
   SCROLLER (the obvious answer, and it is wrong): `td.col-name` is
   `position: sticky; left: 0` and sticks to the scrollport's PADDING box. A
   padding on `.board-scroll` — or on `.group`, which is the same thing one
   level down — leaves the frozen column sitting at the gutter while scrollLeft
   is 0 and pins it flush at the pane edge the moment you scroll: a 26px jump,
   with the strip's counter-slide staying put, and phase-85's shear is back.
   The only safe forms are a PADDING inside a box that must stay flush at the
   edge, or a sticky LEFT OFFSET on one that need not (it resolves to the same
   x at every scroll position, zero included, so it cannot jump). Both were
   built here and both were reverted — not because they did not work, but
   because of what the padding form costs the document title; section 2.

   SCOPED TO THE BOARD TABLE AND NOTHING ELSE. `:has(> .board-scroll)` is true
   of the table view only: kanban, calendar, timeline, dashboard, the files
   view and the phone's own card renderer all draw into this same `#board-body`
   and keep `--board-pad-x` doing its job, as do every reading surface
   (`.page-pad`, the item card, the panels, the landing).

   Nothing in this block declares a backdrop-filter, a blur, a filter, a
   transition, an animation or a keyframe, so it is inert to the "phones: the
   chrome is solid" ordering rule and to D36's one-curve lock. */

/* One number, and it tracks --board-pad-x rather than restating it, so the
   three breakpoints stay in one place (:576, :1471, :3150). */
:root { --board-gutter: var(--board-pad-x); }

/* ---- 1. the bounds. One parent, one pair of declarations. --------------- */
.board-body:has(> .board-scroll) { padding-left: 0; padding-right: 0; }
.board-cols { margin-left: 0; margin-right: 0; padding-left: 0; padding-right: 0; }

/* ---- 2. the breathing room was ALREADY inside the surface ---------------- */
/* The first draft of this phase moved the 26px outward: it added --board-gutter
   to `td.col-name`, `.bcol-name` and `.board-table th.col-name`, and pushed the
   row accent, the group head, the add row and the subitem indents out by the
   same amount with sticky `left` offsets, so that every x on the left stayed
   pixel-identical to the framed layout. The lead reverted it the same day and
   the reason is worth keeping, because it is the kind of thing a later session
   will happily undo:

   that gutter would have come out of the NAME COLUMN'S CONTENT BOX. The column
   is a fixed 380px (NAME_COL_W, board.js), so 26px of padding is 26px the
   document title does not get. Phase-84 shipped four hours before this one and
   its whole justification was name fitting on this exact box: measured on the
   owner's 487 real titles, names rendering in full went 362 to 375, thirteen
   gained and none lost, and the 80% floor is still missed at 77.0% (297px
   wanted against 282px available). A decision was recorded that day NOT to
   widen the column to close it, because on a nineteen-column register that
   space comes out of the columns beside it. Taking 26px OFF the same box would
   have reversed phase-84 several times over and then some, and the +52px this
   phase is for is available WITHOUT paying anything for it.

   So the board is simply full-bleed and the breathing room is the padding the
   cells already carry: 16px on the name cell (+6px of the name's own), 4px
   before the group caret, 12px before the add-row button, 40px of subitem
   indent. Every one of those is untouched by this phase, and the relationships
   between them are unchanged - the group title still sits 14px right of an
   item name, the add-row button 10px left of it, the accent 19px left of it -
   because all four moved together with the surface. 16px from the pane edge is
   a normal table gutter; 42px was a margin nobody asked for.

   THE ONE THING THAT MUST NOT COME BACK: a horizontal padding on
   `td.col-name`, `.bcol-name` or `.board-table th.col-name` beyond the 16px
   the base rules at :825 and :845 carry. It does not shrink the board, it
   shrinks the document title, which is the one measurement phase-84 exists to
   protect; the scanner in tests asserts the resolved value is still 16px at
   every breakpoint for exactly that reason. */

/* the two controls that sit BELOW the surface are page furniture, not rows, so
   they keep a gutter of their own rather than riding the pane edge */
.board-body:has(> .board-scroll) > .load-more,
.board-body:has(> .board-scroll) > .add-group { margin-left: var(--board-gutter); }


/* ════════════════════════════════════════════════════════════════ phase-89
   §1. THE TWO COMPOSER FIELDS ANSWER FOCUS WITH THE FAN RING.

   qa measured, on what shipped an hour ago: the chat composer and the Claude
   input rest on a rgba(255,255,255,.24) rim and focus to rgba(0,0,0,0), with
   no ring, no shadow and no outline. Focusing the field REMOVES the only edge
   it had. Phase-87 gave them the rest rim, so what it fixed at rest it
   inverted at focus; its own note claiming otherwise is corrected in place.

   THE MECHANISM, and it is not the border-color fight the comments point at.
   Every text control in this product paints the fan ring as the SECOND layer
   of `background-image` (:2807), plate on top, `background-size: 100% 100%,
   0 0` holding the ring collapsed until focus sizes it. `.ch-compose textarea`
   (:1918) and its `:focus` (:1919) write `background` as a SHORTHAND at
   (0,2,1), and a shorthand resets background-image, background-size,
   background-origin and background-clip to their initial values. The ring
   layers are therefore deleted at (0,2,1) before the (0,0,1) rule can size
   them, in BOTH states. `border-color: var(--brand)` at :1919 losing to
   `transparent` at :2819 is a red herring: it is a border with nothing behind
   it either way, and the lock of 2026-09-15 forbids --brand on a text control
   regardless, so the LOSER of that pair is the declaration that is wrong. Both
   are inert once this block lands; retiring them is the groundskeeper's.

   WHY NOT .board-search's ring. A textarea is a replaced element and renders
   no ::before, so the masked wrapper ring at :2561 cannot be hung on one. This
   is the SAME fan ring: the same conic gradient through --field-ring, the same
   ringSpin, in the border-box form every other input, select and textarea in
   the app already wears. It is not a third treatment.
   The plate stays --on-glass in both states, so focus adds the ring and
   changes nothing else; "focused" is strictly more defined than "at rest".
   Reduced motion is already answered by the `animation: none !important` at
   :5092, which covers `textarea:focus`. Nothing here sets backdrop-filter, so
   the "phones: the chrome is solid" ordering is untouched. ---- */
.chat-panel .ch-compose textarea, .ai-panel .ai-input textarea {
  background-image: linear-gradient(var(--on-glass), var(--on-glass)), var(--field-ring);
  background-origin: border-box; background-clip: padding-box, border-box;
  background-size: 100% 100%, 0 0;
  border-color: var(--on-glass-rim);
}
.chat-panel .ch-compose textarea:focus, .ai-panel .ai-input textarea:focus {
  border-color: transparent; background-size: 100% 100%, 100% 100%;
  animation: ringSpin 2.6s linear infinite;
}

/* ---- §2. DARK'S PANE STOPS WEARING WHAT IS BEHIND IT.

   The owner: the dark glass "looks poorly designed". Phase-87 fixed the
   content on it and left the ground, measuring it as a band. qa has now
   measured the ground itself and it is not a haze, it is a near-full-strength
   hue wash: the pane's own gutter composites to rgb(33,36,41) over a plain
   row, rgb(0,157,79) over DATA green and rgb(198,122,20) over DATA amber. The
   panel's ground swings 4.40:1 on what scrolls behind it, and over a green
   band the gutter goes BRIGHTER than the --on-glass content plate, 3.05:1 the
   wrong way against 1.44:1 the right way over a plain row. Figure and ground
   invert. Light does not do this (swing 1.35:1, plate always brightest).

   THE LEVER IS contrast(), held against everything else: saturate 1.45 -> .6
   moves the swing 3.78 -> 3.39 and would also break css.md's "do not chase
   legibility with saturation", so saturate is NOT touched here. Light's own
   weight already runs contrast .25 and that is exactly what neutralises its
   hue; dark sat at .84 and did almost nothing. This is not a second material:
   per-theme weights exist by design, the landing's four numbers were tuned
   against the landing's backdrop, and the board paints solid DATA fills four
   times more saturated than that.

   WHY BRIGHTNESS MOVES WITH IT, which is the part a one-knob edit gets wrong.
   The chain is out = bri*(con*v + (1-con)/2): contrast sets how much of the
   backdrop survives AND how high the pane's own floor sits, so dropping con
   alone with bri .76 bleaches the gutter from rgb(33,36,41) to rgb(67,68,70),
   lifts the pane 1.75:1 over the page (a frosted slab, not glass) and leaves
   the plate DARKER than the gutter over all thirteen grounds - the same
   inversion, universal instead of occasional. bri is re-solved per weight so
   each pane's ground over a plain row is unchanged. Measured, regular weight,
   thirteen grounds (plain row + the twelve DATA chips), plate #383e47:
                              was (.84/.76)      now (.40/.402)
     gutter swing               5.499:1            1.510:1   (light: 1.352:1)
     plate vs gutter, row       1.444:1 plate up   1.443:1 plate up
     plate vs gutter, green     3.053:1 INVERTED   1.048:1 plate up
     plate vs gutter, amber     3.176:1 INVERTED   1.009:1 plate up
     inverted grounds          12 of 13, to 3.81  3 of 13, worst 1.047
     board content behind       6.745:1            1.643:1   (light: 2.488:1)
     pane lift over the page    1.075:1            1.124:1
   This MITIGATES, it does not eliminate: three of the thirteen still read a
   hair brighter than the plate, and over a fully saturated ground the pane
   still carries some of its hue - that is a pane showing what is behind it.
   What is gone is the readable inversion; 1.047:1 is not a visible edge.
   The cost, stated rather than hidden: less contrast also scales the gutter's
   OWN chroma, so over a plain row it settles at rgb(35,36,37) instead of
   rgb(33,36,41) - a touch more neutral, a touch less cool. The answer to that
   is not a tint (no fill, no veil, no tint) and not saturate; the page and the
   rows keep their own graphite and the pane floats over them.
   All three weights move together so the ladder keeps its shape and no later
   phase can reintroduce the wash by switching a surface to clear or dense;
   only lg-regular is live today (.popover, .sidebar, .chat-panel, .ai-panel,
   #cmd-palette .palette). Light is untouched. ---- */
[data-theme="dark"] {
  --lgc-con: .42; --lgc-bri: .457;
  --lgr-con: .40; --lgr-bri: .402;
  --lgd-con: .39; --lgd-bri: .374;
}


/* ════════════════════════════════════════════════════════════════ phase-90
   THE BOARD AT ROW 400: Q35, Q36, Q37 and Q38 (D38, D39, D40, D37).

   One complaint, twice in his own words - "using up the real estate so the
   space better" - and the unclosed half of another - "status are off". Q29
   answered the horizontal half of the second this morning by making the board
   one surface with one set of column titles. These four are the vertical and
   the orientation half.

   Nothing in this block declares a backdrop-filter, a blur or a filter, so it
   is inert to the "phones: the chrome is solid" ordering rule. Its only timing
   functions are var(--ease-out) and var(--dur-micro-2), so D36's one-curve
   lock is untouched. Nothing here gives .board-body, .board-cols, .bcols-clip,
   .bcols-track or .board-scroll a horizontal padding, margin or border, which
   is phase-88's standing prohibition and the thing that keeps phase-85's
   identity (track width == scrollport width) true.


   ---- §1. Q35 / D38. THE HEAD CONDENSES, ONCE. --------------------------

   At the top the head is what it is today. Past 120px of scroll it becomes a
   single line and stays there however far down he goes; below 80px it comes
   back. The latch is in board.js (latchHead) and reads nothing but scrollTop.

   WHAT IS HIDDEN AND WHAT IS NOT, straight off D38's table. Hidden: .bsum,
   which is three numbers he has already read and which Q8 says do not add up
   anyway; and the view tabs' labels EXCEPT the active one's, which is exactly
   the treatment this app already gives them under 701px (:1451-1452), not a
   new idea. NOT hidden, and this is the half that matters: every control that
   performs an action. The sort chip, the density control, the filter, the
   search field and New all keep their labels and their size. Activity,
   Automate, Claude, Team, Columns and Invite all stay; their .act-label is
   already dropped by :1438 below 1100px and is NOT dropped here, so at 1440
   the condensed head still reads them out in words. The filter bar stays
   visible whenever a filter is in force (.bfil:empty already takes its own
   space away, :4544), because a hidden filter is how a person comes to
   believe rows are missing.

   THE ARITHMETIC. 16 top padding + 33 title row + 19 .bsum + 12 gap + 34
   switch row + 12 bottom = about 126px today, 159 with D29's strip. The
   condensed line is 5 + 34 + 5 = 44px: the switch row's tallest item is
   .board-tools .btn at 34px (:1730), the view tabs are trimmed to 26px + the
   6px the .vt-ind underline needs, and the title row's tallest is .btn.sm at
   the same 34. Against today that is +82px, two comfortable rows at 42px.

   ONE LINE IS THE WHOLE MECHANISM. The head is a block of four children; the
   condensed state makes it a wrap-enabled flex row so the title row and the
   switch row share ONE line, with .bfil forced to its own. Wrap, not nowrap:
   at 768 the same content may not fit, and a head that wraps to two lines is
   78px and still a large win, where a head that refuses to wrap would push a
   horizontal scrollbar onto .main. Nothing is hidden to make it fit.

   NO HEIGHT TRANSITION, deliberately, and the reason is in board.js next to
   the latch: `height` cannot animate from `auto`, and animating it would move
   the register under the pointer for 180ms on a 487-row viewport rather than
   once. The step is discrete and the 40px of hysteresis is what stops it
   happening twice. ---- */

/* Three numbers he has already read, and the ONE thing this block does at
   375px. D41 says the condensed head, the strip's group slot and the name
   column's edge are table-layout features that do not exist on the phone, and
   in the same breath it sketches a different phone head that would move the
   density control and the filter into a sheet. Those two halves do not agree,
   and the half that is a STANDING rule wins: no control that performs an
   action is removed at 375px. So under 701px this phase changes exactly one
   thing - it stops painting a summary he has already read once he is past it -
   and the phone's re-layout stays with Q4, which owns the Board tools sheet.
   Reported to the lead rather than resolved in silence. */
.board-head.cond .bsum { display: none; }

@media (min-width: 701px) {
  /* the board name steps down a rung and keeps its job: it is the answer to
     "which board am I in", so it is the one thing on the line that is not a
     control. 16px/600 is D38's own number. */
  .board-head.cond .board-name { font-size: 16px; font-weight: var(--fw-semibold); }
  .board-head.cond {
    padding-top: 5px; padding-bottom: 5px;
    display: flex; flex-wrap: wrap; align-items: center; column-gap: var(--sp-3); row-gap: 0;
  }
  .board-head.cond .board-title-row { flex: 1 1 auto; min-width: 0; }
  .board-head.cond .board-switch-row { flex: 0 1 auto; min-width: 0; margin-top: 0; align-items: center; }
  /* the two full-width lines under the fold: the conditions in force, and the
     view-only notice. Both keep their own line or they are unreadable. */
  .board-head.cond .bfil { flex: 1 0 100%; margin-top: 4px; }
  .board-head.cond .vo-bar { flex: 1 0 100%; }
  /* the view he is IN keeps its word; the rest are their icon and their
     aria-label, the same trade :1451 already makes under 701px */
  .board-head.cond .vt-label { display: none; }
  .board-head.cond .view-tab.active .vt-label { display: inline; }
  /* trimmed to the line's height. The 6px stays on .view-tabs because that is
     the room .vt-ind lives in (:2584) and the underline is how the active view
     is stated; it is NOT room the tabs need for themselves. */
  .board-head.cond .view-tab { padding-top: 3px; padding-bottom: 3px; }
  .board-head.cond .view-tab > .ic { width: 20px; height: 20px; padding: 3px; }
  .board-head.cond .board-tools { padding-bottom: 0; }
  /* 200px of search field is a luxury on a single line; it shrinks rather than
     wrapping the row, and it never disappears */
  .board-head.cond .board-search { min-width: 0; }
  .board-head.cond .board-search .board-filter { width: 120px; min-width: 0; }
}


/* ---- §2. Q36 / D39. WHICH GROUP AM I IN. -------------------------------

   The slot is already on screen and already paid for: the strip's first cell
   is the name column's title, and at 380px it has room for both. So this costs
   ZERO additional height, which is the whole reason D39 put it here instead of
   inventing a second band or a floating chip.

   .th-label DOES NOT SHRINK AND DOES NOT CLIP, and that is load-bearing rather
   than fussy: the comment at :6446 records that .th-label's focus ring is an
   out-of-flow ::after and that it clips nothing and neither do its parents. An
   `overflow: hidden` here to make it ellipsis would eat the owner-locked fan
   ring on all four sides while he renames the column. It keeps its natural
   width; the group name takes the rest and truncates itself.

   THE COLOUR IS DATA AND IT LIVES ON AN EDGE. A group's identity colour is one
   of the twelve chips, never a logo hue, and phase-67-c ruled on the owner's
   own word that it lives on the 3px spine and in the distribution bar and
   NEVER as a fill. So: a 3px spine, exactly as the group head wears it, with
   the same 66%-toward-white lift in dark (:780) so a navy or a maroon is still
   a colour at night. No tint, no chip, no ground.

   It is a label, not a control: pointer-events off, so it cannot swallow a
   click meant for a column title sliding under it (.bcols-pin at :7020 is off
   for the same reason and .bcol-name turns it back on).

   Under 701px it does not exist, per D41. The phone register is cards and a
   card names its own group; with Table layout on, the name slot is not
   counter-slid at that width (:6627) and the strip has 355px, which the
   nineteen column titles need more than this does. ---- */
.bcol-name { display: flex; align-items: center; gap: 10px; }
.bcol-name .th-label { flex: 0 0 auto; }
.bcols-grp {
  flex: 1 1 auto; min-width: 0; margin-left: auto; padding-right: 6px;
  display: flex; align-items: center; justify-content: flex-end; gap: 7px;
  pointer-events: none; opacity: 0;
  transition: opacity var(--dur-micro-2) var(--ease-out);
  --gc: var(--gcolor, var(--line-1));
}
[data-theme="dark"] .bcols-grp { --gc: color-mix(in srgb, var(--gcolor, var(--line-1)) 66%, #fff); }
.bcols-grp.on { opacity: 1; }
.bcols-grp-sp {
  flex: none; width: 3px; align-self: stretch; margin: 1px 0;
  border-radius: 2px; background: var(--gc);
}
.bcols-grp-n {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-weight: var(--fw-semibold); font-size: 12px; color: var(--text-2); letter-spacing: -.002em;
}
/* a count is a numeral, which is the one thing D32 keeps the mono face for */
.bcols-grp-c {
  flex: none; font-style: normal; font-family: var(--font-mono); font-size: 11px;
  color: var(--text-3); font-variant-numeric: tabular-nums; letter-spacing: .01em;
}
@media (max-width: 700px) { .bcols-grp { display: none; } }
/* paper has no scroll position, so there is no "group under the strip" */
@media print { .bcols-grp { display: none; } }


/* ---- §3. Q37 / D40. THE NAME COLUMN'S EDGE. ----------------------------

   At nineteen columns he is almost always scrolled sideways, so the frozen
   name column is the only thing on screen that says which row he is reading.
   Its edge is a 1px rule (--line-2 at rest, --line-1 once scrolled, :1797-1798)
   plus an 18px wash of ink at rgba(20,26,43,.07) - which is a shadow so faint
   it reads as one more column rule. D40: the edge is two layers, a 1px rule at
   --line-1 and a 12px gradient falling to transparent ON THE GROUND COLOUR, no
   filter and no blur, and it appears only when the board is actually scrolled
   so a board narrower than the viewport is unchanged.

   The rule layer already resolves to --line-1 when scrolled and is not touched.
   What changes is the second layer: the ink wash becomes the column's own
   ground fading out over 12px, so the columns passing beneath the frozen one
   DISSOLVE INTO IT instead of being greyed by it. That is the landing's own
   idiom for the edge of an object over moving content (welcome.css:1033 fades
   content to transparent with a mask; a mask is not available on a sibling's
   content, a ground-coloured gradient painted over it is the same picture).

   "ONLY WHEN SCROLLED" IS --shade AND NOT --board-x, and that is a correction
   to D40's own shorthand rather than a liberty. Since phase-85 the strip's
   alignment belongs to a CSS scroll timeline, and paintBoardX writes --board-x
   ONLY when the browser has no such timeline (`if (body && !X_NATIVE)`), so in
   Chrome it does not exist at all. --shade is written unconditionally by the
   same function, ramps 0 to 1 over the first 36px, and is already what drives
   this pseudo-element's opacity. Keying the edge on --board-x would have made
   it dead code in the owner's own browser.

   TWO GROUNDS, because the cell has two. td.col-name is --surface at rest and
   --surface-2 under the row hover (:816), and a fade that stays on the resting
   ground would draw a pale block beside the hovered row. Both are stated.
   The dark selectors are restated at the same specificity as :1794's, later in
   the file, or the light declaration would lose to it in dark. ---- */
.board-table td.col-name::after, .board-table th.col-name::after,
[data-theme="dark"] .board-table td.col-name::after,
[data-theme="dark"] .board-table th.col-name::after {
  right: -12px; width: 12px;
  background: linear-gradient(90deg, var(--surface), transparent);
}
.item-row:hover td.col-name::after,
[data-theme="dark"] .item-row:hover td.col-name::after {
  background: linear-gradient(90deg, var(--surface-2), transparent);
}


/* ---- §4. Q38 / D37. THE FIVE MICRO-CONTROLS TAKE THE D3 RADIUS. --------

   D3 shipped for the buttons, the view-tab track and the group spine and
   stopped at the board's own surface. These five are controls set into a
   surface at 30px and under, so all five take 7px (--r-xs): .col-head 4px
   (:829), .item-name 4px (:846), .chat-btn 4px (:851), .group-title 5px
   (:788), .cell.inline 5px (:872).

   A TABLE CELL STAYS SQUARE and nothing here touches a td: every one of these
   is an element INSIDE a cell or a bar (a button, a contenteditable span, an
   inline editor chip), which is what D3 draws the line at. .status-pill is
   deliberately absent; it already takes --r-xs at :5602. .chat-btn's count
   pill keeps --r-round (:856) because a capsule standing on its own is the
   other half of the same grammar.

   .bcol .col-head (:6634) and .ic-props .cell.inline (:2863) already declare
   --r-xs; this block makes the base agree with them instead of leaving two
   values for one shape. ---- */
.col-head, .item-name, .chat-btn, .group-title, .cell.inline { border-radius: var(--r-xs); }


/* ════════════════════════════════════════════════════════════════ phase-91
   Q31-b / D34: `.lg-solid`, THE FOURTH VARIANT. A CARD KEEPS EVERY LAYER OF
   THE LANDING'S GLASS EXCEPT THE ONE IT MAY NOT HAVE.

   The hard constraint first, because it is the reason this phase exists:
   NEVER a backdrop-filter on a card or a row. The Files grid is 120 cards and
   the owner's register is 487 rows; phase-46 shipped that and the page dragged.
   WHAT THIS PHASE DOES AND DOES NOT BUY, measured by qa rather than assumed,
   and corrected here because the first report of it was wrong: no card
   computes a backdrop filter at HEAD either, and the number of filtered
   elements on the Files page at HEAD is ZERO - the only `none` for cards lived
   in the phone block, so at desktop there was no live layer to delete. This
   block deletes no compositing layers. What it buys is the veil -> real ground
   contrast, and the law STATED on the element, on its ::before and on the lens
   chain, so the filter cannot come back unnoticed the way it did in phase-46.
   Everything below is built so the no-filter law is enforced rather than
   intended: the element and its ::before both declare `backdrop-filter: none`,
   the `html.lg-lens` chain is overridden at matching specificity (it
   re-declares the filter on `.lg::before` and would otherwise hand 120 cards a
   layer at step two), and tests/api_test.py resolves both, per theme, through
   the custom-property chain.

   The six points of D34's definition, and where each one is:
     1. no filter layer          -> `backdrop-filter: none` on the element, on
                                    ::before, and under html.lg-lens
     2. a real ground            -> --lg-ground, default var(--surface-1), on a
                                    --surface-0 page: 1.090:1 light, 1.087:1
                                    dark, against the phase-39 veil's 1.038 and
                                    1.050. (D34 states 1.102:1 for dark; the
                                    measured pair #1d2025 on #16181d is 1.087:1,
                                    which is the figure phase-86 already wrote
                                    at :7100 for the same two tokens.)
     3. the four inner shadows   -> var(--lg-inner) (:7118), which IS
                                    welcome.css:966-968 verbatim, on ::before
     4. the masked gradient rim  -> ::after, copied from :6143 verbatim
     5. no float shadow          -> box-shadow: none at rest. A card on a page
                                    casts nothing, and the dark --elev-1 (:278)
                                    carries its own inset top highlight, which
                                    would have doubled the ::before's.
     6. --lg-o and --lg-dim      -> the same calc() the primitive uses, plus the
                                    aria-disabled dim

   WHY THE 1px BORDER GOES. In light the rim is INK (--lg-rim-2 .26,
   --lg-rim-m .18, --lg-rim-3 .42 over :6047), so it is a STRONGER edge than
   --line-2 was, not a weaker one; in dark it is the lit top edge. A border
   left underneath it is the "two edges disagree" defect phase-86 called out at
   :7195 when the panes lost theirs. `* { box-sizing: border-box }` (:332) holds
   every width. The three borders that carry MEANING rather than material -
   danger, the library template, a clashed automation - are restated below and
   keep their colour; the hover border-colour change every card had becomes a
   brighter rim through --lg-rim-a, which is the same affordance in the new
   material.

   HOW IT IS APPLIED, and the note that retires it. None of these surfaces
   carries an `lg` class in markup: the class would mean editing six JS files
   for a material change. `.lg-solid` is the canonical name and every surface
   rides along as a CO-SELECTOR, exactly as `#cmd-palette .palette` does at
   :6115-6152. WHEN THE CLASS REACHES THE MARKUP, DELETE EVERY CO-SELECTOR IN
   THE FOUR LISTS BELOW (`.card` ... `.gjump-chip`) AND NOTHING ELSE CHANGES.
   The lists are comma-separated, so no co-selector's specificity ever leaks
   into `.lg-solid`'s own (0,1,0).

   --glass-card (:93, :265) HAS NO SURVIVING CONSUMER after this block: all
   seven declarations that read it (:2511, :2521, :2669, :2847, :2941, :2949,
   :3273) are overridden here. The token and its two aliases are left in place
   for the groundskeeper to retire, per D34's last paragraph - deleting a token
   is not a line to pull from under a build.

   NOT CONVERTED, and why, both reported rather than forced:
     .tl-scroll  is its own scroll container (`overflow-x: auto`, :1011), so an
                 inset:0 pseudo-element sizes to the scrolled content box and
                 rides out of the pane - the same finding phase-86 wrote at
                 :7130 for `.popover`, and the reason --lg-inner exists as a
                 token. It takes points 1, 2, 3, 5 and 6 as box-shadows and does
                 NOT take the rim.
     .cal-grid   is not a ground at all: its background IS the calendar's 1px
                 divider system (`gap: 1px` over `background: var(--line-2)`,
                 :981/:2940), and it is `overflow: hidden`, which would clip a
                 rim laid on its border. It takes the inner shadows and keeps
                 its dividers; the 42 .cal-cell tiles on top of it are the
                 surfaces that take the full variant.
   ---- */

/* 1. the ground. --lg-ground is the one knob: a surface that sits ON a
      --surface-1 pane rather than on the page sets it to --surface-2. */
.lg-solid,
.card, .dw, .admin-card, .snap-row, .auto-row, .fv-card, .kanban-card,
.cal-cell, .tpl-card, .kanban-count, .gjump-chip {
  isolation: isolate;
  background: var(--lg-ground, var(--surface-1));
  border: 0; box-shadow: none;
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
/* the containing block for the two layers. Deliberately NOT on `.lg-solid`
   itself: like `.lg`, the variant does not decide a surface's position - a
   surface that is already absolute or fixed has its own containing block and
   must keep it. Checked one by one before this line was written: every absolutely positioned descendant inside these fourteen
   (.fv-dl, .kpi::before, .ring-center, .switch input, .lab-swatch input,
   .sub-check input, .file-mini .fglyph, .tl-now, .avatar::after) already sits
   in its own `position: relative` wrapper, so none of them re-parents here. */
.card, .dw, .admin-card, .snap-row, .auto-row, .fv-card, .kanban-card,
.cal-cell, .tpl-card, .kanban-count, .gjump-chip { position: relative; }
/* THE CLOSE BUTTON IS NOT IN THIS PHASE, and D34's table does list it. qa
   measured it: the item modal's close button is `display: none` at :3071 with
   an all-zero rect, so the veil at :2847 sits on an element that never renders
   and every layer given to it would have been a vacuous green in the check.
   The control the owner actually clicks is `.ic-close` (panels.js:209), which
   is NOT one of D34's fourteen, is not veiled, and carries a --wash ground
   with two hover states including a danger state - converting it is a designer
   decision about the item card's header, not a material swap. The plain close
   button on every OTHER dialog has no background at all, so giving it a ground
   and a rim would be a visible change nobody asked for. Both :2847 and :3071
   are the groundskeeper's to retire, and the retirement note above needs one
   correction because of it: --glass-card keeps exactly ONE reference this
   block does not override, :2847, and it is on that dead element.

   The surfaces that do NOT stand on the page. A dialog's ground has been
   --surface-1 since phase-86 (:7177, --glass-chrome), so a card drawn inside
   one on --surface-1 is #ffffff on #ffffff in light and #1d2025 on #1d2025 in
   dark: 1.000:1, at the same moment point 4 above takes its border away. They
   take the next step down instead - 1.141:1 light, 1.133:1 dark. All three are
   rendered by SD.modal(): the template card (panels.js:1342), the automation
   row (:1102) and the snapshot row (:2630). qa's walk adds one thing worth
   keeping: they were never INVISIBLE at 1.000:1, because the ::before's
   --lg-edge hairline runs the whole perimeter; Snapshots in light is the
   faintest of the three and is still legible. This is an improvement on the
   veil, not a rescue, so it does not want over-correcting past --surface-2.
   Every other converted surface renders on the page ground: .card and
   .admin-card in renderMyWork / renderTeamTab / renderWorkspaceTab (#admin-body
   has no background of its own), .dw in the dashboard grid, .fv-card in the
   files view (views.js:140), and the board's own kanban, calendar, timeline and
   jump strip. Checked site by site; if a future phase moves one of them into a
   dialog, it belongs on this line and not the one above. */
.tpl-card, .snap-row, .auto-row { --lg-ground: var(--surface-2); }

/* 2. the inner shadows. z-index:-1 paints ABOVE the element's own background
      and below its content - confirmed in the painting order: `isolation:
      isolate` above makes each surface a stacking context, so its background
      is step 1 of that context and a negative-z-index child is step 2. That is
      what lets a solid ground and an inner-shadow layer coexist at all.
      No filter here and none under the lens; the lens chain is restated at
      matching specificity two rules down. */
.lg-solid::before,
.card::before, .dw::before, .admin-card::before, .snap-row::before,
.auto-row::before, .fv-card::before, .kanban-card::before, .cal-cell::before,
.tpl-card::before, .kanban-count::before, .gjump-chip::before {
  content: ""; position: absolute; inset: 0; z-index: -1; border-radius: inherit; pointer-events: none;
  opacity: calc(var(--lg-o) * var(--lg-dim, 1));
  box-shadow: var(--lg-inner);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
html.lg-lens .lg-solid::before,
html.lg-lens .card::before, html.lg-lens .dw::before,
html.lg-lens .admin-card::before, html.lg-lens .snap-row::before,
html.lg-lens .auto-row::before, html.lg-lens .fv-card::before,
html.lg-lens .kanban-card::before, html.lg-lens .cal-cell::before,
html.lg-lens .tpl-card::before, html.lg-lens .kanban-count::before,
html.lg-lens .gjump-chip::before {
  -webkit-backdrop-filter: none; backdrop-filter: none;
}

/* 3. the rim, :6143 verbatim. A ::after with a mask is a paint, not a
      compositing layer, and this is where the separation actually comes from. */
.lg-solid::after,
.card::after, .dw::after, .admin-card::after, .snap-row::after,
.auto-row::after, .fv-card::after, .kanban-card::after, .cal-cell::after,
.tpl-card::after, .kanban-count::after, .gjump-chip::after,
.modal::after, .board-head::after {
  content: ""; position: absolute; inset: 0; z-index: 1; border-radius: inherit; padding: 1px; pointer-events: none;
  --lg-sheen: inherit;
  background: linear-gradient(var(--lg-sheen), var(--lg-rim-1) 0%, var(--lg-rim-2) 16%, var(--lg-rim-m) 40%,
    var(--lg-rim-m) 60%, var(--lg-rim-2) 84%, var(--lg-rim-3) 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  opacity: calc(var(--lg-o) * var(--lg-dim, 1) * (.8 + var(--up) * 3) * var(--lg-rim-a, 1));
}
/* point 6: the variant dims exactly like the filtered ones. */
.lg-solid[aria-disabled="true"] { --lg-dim: .45; }

/* ---- phase-97, D46 §9 item 4: THE MODAL AND THE BOARD HEAD TAKE THE RIM,
   AND THE BOARD HEAD TAKES IT AS AN ARC RATHER THAN AS A STROKE.

   `.modal` is the owner's screenshot 16. It was solid with the four inner
   shadows and NO specular rim, which is the layer that says what a surface is
   made of, and it stays solid: a filtered dialog measures 1.88:1 light /
   1.09:1 dark over its own scrim against the solid 3.01 / 1.22 (D46 §3c), so a
   filter would make it look cheaper, not richer. It floats, all four of its
   edges are visible, and it takes the material's rim unchanged - the same edge
   the twelve card surfaces beside it already wear.

   Both surfaces join the RIM list only, not `.lg-solid`'s other two:
     - not the ground list (:8082): it sets `border: 0; box-shadow: none`, and
       at (0,1,0) later in the file that would take the dialog's float shadow
       away (:7229) and take the board head's bottom hairline with
       `.board-head.scrolled`'s animated border-colour still pointing at it.
       Both already paint the right ground - `--glass-chrome` IS `--surface-1`,
       which is what `--lg-ground` resolves to.
     - not the inner-shadow list (:8139): `.modal` already carries the identical
       four inner shadows as box-shadows through `--lg-inner` (:7229) and a
       second copy would double them; and `--lg-inner` ends in
       `inset 0 0 0 .5px var(--lg-edge)`, a hairline around the WHOLE perimeter,
       which on the board head includes the left edge - the rail. The owner
       ruled today that the rail and the head are ONE surface ("remove that line
       that separates the menu"), it took removing two mechanisms to clear that
       seam, and a material layer is not a licence to put a third one back.

   THE BOARD HEAD'S RIM IS RESTATED, AND HERE IS THE ARITHMETIC, because he
   photographed the first attempt and called it correctly: "what's up with the
   white line at the top and the top right corner?"

   The material's rim is ONE linear gradient along --lg-sheen (118deg), masked
   to a 1px band: `--lg-rim-1 0%, --lg-rim-2 16%, --lg-rim-m 40% ... 60%,
   --lg-rim-2 84%, --lg-rim-3 100%`. On a PANE that reads as light on a lip,
   because the bright end lands on one corner and has died by 40% of the box.
   On the board head - about 1,860 x 110px - the gradient LINE at 118deg is
   1860*sin(118) + 110*cos(118) = 1,695px, so it is governed by the width: the
   0-16% span is ~300 physical pixels of near-white laid flat along the top
   edge, the middle is empty, and x = 1,860 sits at 100%, which is --lg-rim-3
   held CONSTANT down the whole right edge and terminating in the corner. That
   is not a specular highlight, it is a drawn line that stops dead, and a stray
   stroke is worse than no rim at all. No sheen angle fixes it: a single linear
   gradient across a 17:1 box cannot make an arc.

   So the head stays in the family and restates two things, in the material's
   OWN tokens and its own dimmer - no new colour, no new alpha, no second
   recipe, nothing another surface can inherit:
     1. `padding: 1px 0 0` - the visible band IS the pseudo-element's padding,
        so this is the TOP edge only. No left band (the rail seam, ruled), no
        right band (the viewport edge and the corner he photographed), no bottom
        band (the head already animates a 1px --line-2 rule there on scroll, and
        two edges on one boundary disagree).
     2. a horizontal falloff: transparent -> --lg-rim-2 -> --lg-rim-3 ->
        --lg-rim-2 -> transparent. Brightest on one arc left of centre, resolved
        to nothing at BOTH ends instead of cut off at a corner. It inverts by
        itself with the theme exactly as the rest of the material does: in dark
        those tokens are white .16 and .50, so the head shows its LIT top edge;
        in light they are ink .26 and .42, so the top reads as a fine dark edge
        and the pane's floor is the --line-2 hairline at its bottom - which is
        css.md's rule that a dark ground shows the lit edge and a bright ground
        the shadowed one, with both halves present rather than one.
     3. --lg-rim-a: .45, the dimmer `.lg-tinted` already uses to go the other
        way. The rim's opacity is `--lg-o * --lg-dim * (.8 + --up*3) *
        --lg-rim-a`, so the peak lands at .5 * .8 * .45 = .18 alpha in dark
        against phase-61's measured .14 lit edge - the faintest thing in the
        product that is still meant to be seen - and it fades from there.

   Cost: no filter, no element, no compositing layer. A masked ::after is a
   paint. Nothing here is a backdrop-filter, so this block's position after the
   "chrome is solid" phone block is safe. ---- */
.board-head::after {
  padding: 1px 0 0;
  --lg-rim-a: .45;
  background-image: linear-gradient(90deg, transparent 0,
    var(--lg-rim-2) 14%, var(--lg-rim-3) 34%, var(--lg-rim-2) 62%, transparent 92%);
}

/* 4. the hover lift keeps its elevation and answers on the RIM, since the
      border it used to brighten is gone. Only the four surfaces that ALREADY
      answered hover get the rim response: a `.card` or a `.snap-row` has never
      lit up under the pointer and this phase is not the place to teach it.
      (hover: hover) only - a phone shows no hover-only affordance.
      The lifts themselves are restated to drop the two veil-era inset lines
      (--glass-line / --glass-rim-lo) that the ::before now carries properly;
      the elevation each one had is unchanged. `.fv-list .fv-card` is a LIST
      ROW: (0,2,0) at :2643 and (0,3,0) at :2644 both out-rank point 5's
      `box-shadow: none`, so both are restated here or 120 rows keep the old
      top line under the new rim. */
.fv-card:hover, .tpl-card:hover, .kanban-card:hover { box-shadow: var(--elev-2); }
.fv-list .fv-card { box-shadow: none; }
.fv-list .fv-card:hover { box-shadow: var(--elev-1); }
.dw-lift { box-shadow: var(--elev-3); }
@media (hover: hover) {
  .dw:hover, .fv-card:hover, .tpl-card:hover, .kanban-card:hover { --lg-rim-a: 1.3; }
}

/* 5. the edges that carry MEANING, not material, restated at their own
      specificity so point 4's border removal cannot silently delete them. */
.admin-card.danger { border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent); }
.auto-row.auto-clashed { border: 1px solid color-mix(in srgb, var(--warn) 40%, transparent); }
/* :2669 wrote its gradient in a background SHORTHAND over --glass-card. Phase-89:
   a shorthand resets background-image with the colour, so this restatement
   carries the gradient verbatim and only swaps the veil underneath it for the
   real ground - the layer above must not delete the layer below it. */
.tpl-card.tpl-lib {
  background: linear-gradient(180deg, var(--brand-soft), transparent 70%), var(--lg-ground, var(--surface-1));
  border: 1px solid color-mix(in srgb, var(--brand) 40%, transparent);
}

/* 6. the two `.dw` states that are placeholders rather than surfaces: a drag
      ghost and the add-widget button. Both are single-class rules at :2404 and
      :2407, so the border and background above would win on source order and
      turn a dashed outline into a solid card. They keep their dash and take no
      layers. */
.dw-ghost { border: 1.5px dashed var(--line-1); background: var(--wash); }
.dw-add { border: 1.5px dashed var(--line-1); background: transparent; }
.dw-ghost::before, .dw-ghost::after, .dw-add::before, .dw-add::after { display: none; }
/* and the same for a calendar cell that is not a day: `.cal-cell.empty` is the
   hole before the 1st and after the 31st, already painted --wash at (0,2,0). A
   lit rim would draw it as a tile the reader can use. */
.cal-cell.empty::before, .cal-cell.empty::after { display: none; }

/* 7. the two panes that cannot carry pseudo-element layers (see the header).
      Points 1, 2, 3, 5 and 6 as box-shadows; no rim. */
.tl-scroll {
  background: var(--surface-1); border: 1px solid var(--line-2);
  box-shadow: var(--lg-inner);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
.cal-grid { box-shadow: var(--lg-inner); -webkit-backdrop-filter: none; backdrop-filter: none; }


/* ==========================================================================
   phase-92. A VIEW TAB THAT CANNOT BE TOLD FROM ITS NEIGHBOUR (D42's defect)

   A tab's glyph comes from the view's KIND, so a board with two kanban views
   draws the same glyph twice. Two rules then drop an inactive tab's label -
   :1451 under 701px, which has always done it, and :7816 in the condensed head,
   which phase-90 added - and the reader is left with two identical, unlabelled
   buttons. On the owner's register those are "By status" and "By family".

   The label is the fallback, not the decoration. board.js counts, per board,
   how many of ITS views draw each glyph and marks the label of every tab whose
   glyph is not unique with .vt-keep; that label is then shown at every width,
   condensed or not. Nothing is hidden to make it fit - the strip already
   scrolls (:1460, :3844) and the condensed head already wraps (:7801).

   PLAIN, NOT IN A MEDIA RULE, AND LAST ON PURPOSE. It has to beat both hiding
   rules at once: :1451 lives in a @media (max-width: 700px) block and :7816 in
   a @media (min-width: 701px) one, so no single media query covers both, and
   app.css is appended per phase. At (0,5,0) it also outranks
   `.board-head.cond .view-tab.active .vt-label` (0,4,0), so a duplicate tab
   that is also the active one is not a fight between two rules that agree.

   HEIGHT: this makes the condensed head's view strip WIDER on a board that has
   duplicate-kind views, so that head can wrap to a second line at a narrow
   desktop width and be taller than it was. Safe for phase-90's latch by
   construction - the latch reads the register's offset past the scrollport, not
   scrollTop, and its simulation covers every head delta - but stated rather
   than assumed, and the phase-90 latch checks are re-run. */
.board-head .view-tabs .view-tab .vt-label.vt-keep { display: inline; }


/* ==========================================================================
   phase-93, half one. Q43 / D44 §3 Tier 1: THE PRESS GRAMMAR GETS CONSUMERS.

   `.lg-ctl` and `.lg-card` shipped with the material in phase-86 and
   `grep -rn "lg-ctl" public/js/*.js public/index.html` returns 0: the hover
   lift (`--up: .07`), the press swell (`--up: .11` + `scale(1.03)`) and the
   one curve were written at :6158-6168 and pointed at nothing, so every
   pressable control in the product still answers a hand by swapping a
   background colour. This block gives them consumers.

   WHY A SECOND CLASS NAME AND NOT `.lg-ctl` ITSELF, which is what D44 §3 asks
   for in words. THE INSTRUCTION IS WRONG, and it is wrong in the exact shape
   this project keeps paying for: A RULE THAT IS CORRECT IN THE PLACE IT WAS
   WRITTEN AND FALSE IN THE PLACE IT IS CITED. The designer read `.lg-ctl` as
   "the cheapest real change in the document" and the brief read it as the
   highest felt quality per unit of risk left in the queue; both readings are
   right about the EFFECT and neither survives contact with the rule's first
   declaration. `.lg-ctl`'s first declaration is
     color: color-mix(in srgb, var(--lg-ink) calc(var(--lg-o) * ...), transparent)
   and `--lg-ink` is an UNREGISTERED custom property set in exactly one rule,
   `.lg` (:6105). On a control that is not glass and not inside a glass pane,
   `var(--lg-ink)` has no value, the whole color-mix() is invalid at
   computed-value time, and `color` falls back to `unset` - which, for an
   inherited property, is `inherit`. `.lg-ctl, .btn.lg-ctl` (0,2,0 on the
   second half, appended at :6160) then out-ranks `.btn.primary` (0,2,0,
   :383) by source order, so putting `.lg-ctl` on the app's buttons would
   have taken the white label off all 28 brand-filled create buttons and
   handed `.icon-btn.star.on`, `.menu-item.danger` and `.btn.outline` their
   parent's colour instead of their own. The landing never hits this because
   `.gl-ctl` only ever appears WITH `.gl` (welcome.css:944 sets --lg-ink).
   So: `.lg-press` is the same grammar with the colour line left out, and
   `.lg-ctl`'s definition is not edited, per D44 §7 - it gains a PRECONDITION
   comment at :6158 saying it requires `.lg`, so the next session reading D44
   finds the correction where it would trip rather than only down here. A
   future glass control still takes `lg lg-ctl` and gets both.

   WHAT `--up` ACTUALLY DOES, per surface, because "hover lift" means three
   different things in this sheet and only one of them is free:
     on `.lg`        it raises `brightness()` inside --lg-fx AND the rim -> a
                     backdrop-filter recomputation. NOT used here.
     on `.lg-solid`  MEASURED FOR THIS PHASE, because nothing had written it
                     down and the brief asked before any of it was used: it
                     raises ONLY the ::after rim opacity, `.8 + --up * 3`
                     (:8130) -> .80 at rest, 1.01 on hover, 1.13 on press
                     (clamped to 1). No filter, no layer, no layout, no
                     recomposite. So `--up` on a card variant is D44 Tier 1
                     ILLUMINATION and is free everywhere, including at 487
                     rows - which is why the one consumer below that carries
                     those layers, `.gjump-chip`, is also the one where the
                     hover lift is visible at all without a filter.
     on anything else  nothing. It is a registered <number> that no layer
                     reads, so it costs one interpolation and paints nothing.
                     Those controls take the PRESS SWELL, which is the half
                     D44 §3 calls "the single most-repeated interaction in the
                     product", and their hover keeps the background change it
                     has today. Giving them a lit hover means giving them
                     `.lg-solid`'s layers - a material change, not this phase.

   THE CONSUMER LIST, and it is deliberately short (six families, every one
   bounded by the chrome rather than by the data):
     .btn         176 sites, never inside a loop and never inside td.cell;
                  .load-more / .add-group are once per group (~14).
     .rail-btn    9 on the rail, 6 on the phone bar. D43 §8 names this exact
                  pair of values for a rail destination.
     .avatar-btn  1, the rail's foot.
     .chip-btn    a handful per menu.
     .qf-chip     the filter, template-category and files-kind chips; ~30 at
                  the very most, in a padded pane, and a capsule rather than a
                  full-bleed row, so a 3% swell cannot reach the pane's edge.
     .gjump-chip  one per group (~14), and `.lg-solid` (:8042), so this one
                  lights as well as swells - see the reachability note below,
                  because the lit half is narrower than it first looks.
     .addbar-btn  "Add document", once per group: FOURTEEN per register and the
                  most repeated create gesture on the owner's board. Added
                  after qa's walk, on its ruling and not on a rule:
                  "inconsistency at the point of highest repetition is what
                  reads as cheap, not 3%". You press Columns and it answers,
                  then press Add document fourteen rows later and it does not.
                  Its own `td` is `overflow: visible` (walked, not assumed), so
                  the swell does not clip.

   NOT CONVERTED, each for a reason that is not "we ran out of time":
     .icon-btn      rendered once per ROW, per group and per subitem (board.js
                    row-menu / group-menu / sub-menu). See the paragraph below;
                    the reason is repetition, and it is NOT clipping.
     .item-row, td.cell, .card, .dw, .fv-card, .kanban-card, .cal-cell
                    487 rows, 9,253 cells, 120 file cards. D44 §3 Tier 2 is
                    one displacing object per gesture.
     .menu-item     `width: 100%` inside a `.popover` that owns its own
                    scroll (:1085): a 3% swell on a full-bleed row lands on
                    the pane's edge, and design.md already carries the owner's
                    "hover transforms on menu items read as misaligned".
     .side-link,
     .side-board    same shape inside `.side-scroll`.
     .view-tab      phase-92's `.vt-ind` is a plate positioned by an inline
                    transform written only on render and resize; a tab that
                    swells under the finger leaves the lozenge behind it.
     .tpl-card      D44 §3 Tier 3 and §12.5 row 11 hold the template cards for
                    the Q48 spike; it already lifts 3px on hover and changing
                    that here would pre-empt that decision.
     .rail-logo     the mark owns its own motion (drift, entrance, pulse).
     .group-caret   14 per register, and it already OWNS the transform channel:
                    :2465-2466 nudge it 3px sideways or 2px down on a group
                    head's hover. CORRECTED after qa injected the real press
                    rule at equal specificity in real source order and let the
                    browser arbitrate, because the first version of this
                    paragraph guessed and guessed wrong. The hover nudge is
                    (0,5,0) - `.group:not(.collapsed) .group-head:hover
                    .group-caret` - and the press rule is (0,4,0), so on
                    hover+press, which is what a mouse ALWAYS does, the NUDGE
                    WINS: the computed transform stays `matrix(1,0,0,1,0,2)`
                    expanded and `matrix(1,0,0,1,3,0)` collapsed. The swell
                    never appears at all. So the exclusion is more necessary
                    than first argued, not less: converting the caret would
                    ship a control that is DEAD ON PRESS for every mouse user,
                    which is worse than the snap-back this paragraph used to
                    describe. It needs the two states composed
                    (`translate(...) scale(...)`) at a specificity that beats
                    the nudge, which is a decision about the caret.
     .board-tap     since half two of this phase the drawer is how he switches
                    boards, so the title is no longer the repeated gesture it
                    was, and this family is held until it is clear what the
                    title IS. CORRECTED: this entry used to claim a swell would
                    re-clip the ellipsized name. It does not. `transform` is
                    PAINT, NOT LAYOUT - qa measured a genuinely ellipsized
                    clone (scrollWidth 464 against clientWidth 200) under the
                    shipped scale(1.03) and scrollWidth, clientWidth and the
                    visible character count were all unchanged, 23 glyphs
                    before and 23 after; only the painted box grows 200 -> 206px.
                    Zero characters are lost. Do not re-derive that fear
                    anywhere else in this sheet: no `overflow: hidden` element
                    in this product loses content to a scale.
     .view-add      held with `.view-tab` and `.vt-menu`. The whole view strip
                    goes together or not at all, because `.vt-ind` measures
                    that strip and Q44 re-does the indicator.
     .rcard         the phone's card per item - data scale - and it already
                    answers a press with a real background change (:4401).
     .sub-add,
     .sub-caret,
     .sub-check     once per SUBITEM. Same line as the rows.
     .group-title   a contenteditable rename, not a press target; the
                    owner-locked focus rule owns how it answers a hand.

   AND `.icon-btn` DESERVES ITS OWN PARAGRAPH, because converting only its
   bounded members is the obvious next move and it is wrong. One family answers
   one way. Its most numerous member is the row menu, 487 of them, so
   converting the board head's star, info and more, or the group head's menu,
   and leaving the row menu out would put the inconsistency exactly at the
   family's point of HIGHEST repetition - which is the failure qa's ruling
   names, applied against the convenient conversion rather than for it. It goes
   whole or it does not go.

   A CLIPPING PREMISE WAS STRUCK FROM THIS PARAGRAPH, and from the list entry
   above, on 2026-09-21. It read "inside `td.cell { overflow: hidden }`
   (:1946) which would clip the swell" and it was false in all three of its
   particulars: :1946 is inside the `.th-sort` rule, not `td.cell`; NO rule
   anywhere in this sheet sets `overflow` on `td.cell`, and all 400 cells
   sampled of 9,272 compute `visible`; and the row menus are not in `td.cell`
   at all - 282 of 282 sampled sit in `td.col-name`, also `visible`. It is
   struck rather than deleted quietly so the next session cannot restore it by
   citing a sentence that has vanished.

   SO WHAT WOULD ACTUALLY HAVE TO BE CHECKED before `.icon-btn` goes whole,
   since it is no longer "fix the clipping":
     1. `td.col-name` is the FROZEN column and manages its own paint order
        (`.cell-files.has { isolation: isolate }` exists for exactly that).
        `transform` on a descendant creates a stacking context for the length
        of the press. Measure, at row 400 with the board scrolled sideways,
        whether a swelling menu button paints over or under the frozen
        column's edge and its neighbours. This is the only real risk left and
        it is a paint-order question, not a clipping one.
     2. The cost at scale, measured rather than assumed: 487 row menus plus a
        subitem menu each carrying a `transition` on a registered custom
        property. Nothing composites at rest, because `transform` is `none`
        until the press, so the expectation is free - but phase-46 is why this
        gets measured on the 488-row register instead of reasoned about.
     3. Whether it is worth anything. A 3% swell on a 24px icon button is
        0.7px. If it cannot be seen, the honest answer for this family may be
        that it stays out permanently and the product's press answer for an
        icon button remains its background - which is a legitimate outcome,
        not a failure to finish.

   THE `.gjump-chip` LIFT IS NARROWER THAN THIS BLOCK FIRST CLAIMED, corrected
   after qa's walk rather than left standing. `.gjump` renders only when
   `boardRowKind() === "cards"`, which is `innerWidth <= 700`, and at ≤700px on
   a touch device `(hover: hover)` is false - so the HOVER lift on it is
   reachable only in a mouse-driven window under 700px wide. The PRESS lift is
   reachable and qa proved it with a real tap: `--up: .11` raises the rim from
   .80 to 1.0 on a surface with no filter. So the honest sentence is: the group
   jump strip is the only place in the product where `--up` currently lights
   anything at all, and on his phone it lights on press, not on hover.

   WHAT THE SWELL ACTUALLY MEASURES, recorded so the question is inherited
   rather than rediscovered. qa's read on a wide capsule: "the landing's, not a
   wobble" - about 4px of growth, gone before the click finishes, never
   overshooting. On small chrome it is proportionally the same and absolutely
   much less: `.btn.ghost.sm` at 28px and `.rail-btn` at 40px grow by UNDER
   1.2px, which is correct in kind and barely perceptible. The grammar is right
   and the tuning is conservative rather than wrong. DO NOT RETUNE IT HERE: a
   size-aware swell (a floor in px, or a scale that reads the control's own
   height) is a real design question and it belongs to D44, not to a fix-up at
   the end of a phase. It ships consistent and the owner feels it on his own
   hardware first.

   ONE THING ESCAPE NOW DOES THAT IT DID NOT, filed rather than fixed. Pressing
   Escape during a board or group rename closes the DRAWER as well as reverting
   the rename: `board.js:518`'s handler neither stops propagation nor is
   consulted by `SD.closeTopLayer`, so both fire. That shape is pre-existing -
   it was equally true of a popover - but half two of this phase makes it
   reachable, because the drawer can now still be open while he renames. qa's
   read: mildly wrong, Escape should cancel the innermost thing, and the drawer
   is now the expensive thing to lose. It is hard to reach with a mouse and it
   is not fixed in this phase; the fix is to route that handler through
   closeTopLayer rather than to special-case the drawer.

   THE HOVER LIFT ON `.btn` IS A DELIBERATE EXCEPTION TO D44 §4, not an
   oversight, and it is left here so the groundskeeper does not reap it as an
   inconsistency. D44 §4 says depth is light and never distance, so `.btn:hover
   { transform: translateY(-1px) }` (:381) should have gone with this
   conversion. It stays. That rule presumes the hover lift lands on `--up`,
   and on an opaque control `--up` lights nothing (see the table above), so
   removing the 1px would have left hover with LESS feedback than it has
   today - a downgrade, which the owner's standard counts as a defect rather
   than a partial success. The press overrides the same transform channel, so
   the two interpolate into each other on one curve; there is no jump. When a
   control is converted to `.lg-solid` and its hover really is lit, the
   translate on THAT control goes.

   DURATIONS, and the honest statement of a disagreement rather than a claim
   that there is none. `.lg-press` acknowledges a press in 90ms (--lg-d0, D44
   §2's own rung for "acknowledging a press or a hover") and interpolates
   `--up` over 280ms (--lg-d2, the primitive's). `.lg-ctl` acknowledges in
   180ms: its :active rule (:6168) overrides transition-duration to
   --lg-d1 for both channels. So the two classes DO disagree about how fast a
   press answers, by 90ms.
     Nothing in the product feels it today. `.lg-ctl` and `.lg-card` still have
     zero consumers after this phase - verified, `grep -rn "lg-ctl\|lg-card"
     public/js/*.js public/index.html` is empty - and the command palette is
     NOT one of them: it co-selects into `.lg`, `.lg-regular` and `.lg-float`
     only (:6115, :6120, :6154), and its 180ms `lgPaletteIn` is an entrance,
     not a press. The disagreement is therefore LATENT: it becomes visible the
     first time a control ships wearing `lg lg-ctl` beside a `.lg-press` one.
     Left rather than fixed because D44 §7 forbids editing `.lg-ctl`, and the
     fix is one token on one line (--lg-d1 -> --lg-d0 at :6168). It belongs to
     Q47, which is the motion audit, and it is filed here so Q47 does not have
     to rediscover it.
   `--ease-lg` and `--ease-out` are the same curve (:6057, :6783) - one curve,
   D36.

   `transition` IS A SHORTHAND, so every rule below restates the channels its
   control already had (css.md, the phase-89 finding). Dropping them would
   delete the background fade each control ships with today.

   NOTHING HERE SETS backdrop-filter, blur or background, so its position after
   the "phones: the chrome is solid" blocks (:3323, :6225, :7246) changes
   nothing they protect. It is appended last on purpose: `.btn:active`
   (:382, 0,2,0) and `.btn:disabled` (:400) have to be beaten on an element,
   which the (0,4,0) press rule below does at any source position, and `.btn`'s
   own transition (:377, retimed :6790) has to be beaten by source order.
   ========================================================================== */
.lg-press, .avatar-btn, .gjump-chip {
  transition: transform var(--lg-d0) var(--ease-lg), --up var(--lg-d2) var(--ease-lg);
}
.addbar-btn {
  transition: transform var(--lg-d0) var(--ease-lg), --up var(--lg-d2) var(--ease-lg),
    background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.btn {
  transition: transform var(--lg-d0) var(--ease-lg), --up var(--lg-d2) var(--ease-lg),
    background var(--dur-micro-2) var(--ease-out), box-shadow var(--dur-micro-2) var(--ease-out);
}
.rail-btn {
  transition: transform var(--lg-d0) var(--ease-lg), --up var(--lg-d2) var(--ease-lg),
    background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.chip-btn {
  transition: transform var(--lg-d0) var(--ease-lg), --up var(--lg-d2) var(--ease-lg),
    background var(--dur-micro-1) var(--ease-out), border-color var(--dur-micro-1) var(--ease-out);
}
.qf-chip {
  transition: transform var(--lg-d0) var(--ease-lg), --up var(--lg-d2) var(--ease-lg),
    background var(--dur-micro-1) var(--ease-out), border-color var(--dur-micro-1) var(--ease-out),
    color var(--dur-micro-1) var(--ease-out);
}
/* the lift is a STATE, so it is hover-only in the pointer sense: `(hover: none)`
   never sees it and never gets it stuck after a tap (phones rule). */
@media (hover: hover) {
  .lg-press:not([aria-disabled="true"]):not(:disabled):hover,
  .btn:not([aria-disabled="true"]):not(:disabled):hover,
  .rail-btn:not([aria-disabled="true"]):not(:disabled):hover,
  .avatar-btn:not([aria-disabled="true"]):not(:disabled):hover,
  .chip-btn:not([aria-disabled="true"]):not(:disabled):hover,
  .qf-chip:not([aria-disabled="true"]):not(:disabled):hover,
  .gjump-chip:not([aria-disabled="true"]):not(:disabled):hover,
  .addbar-btn:not([aria-disabled="true"]):not(:disabled):hover { --up: .07; }
}
/* the press. Glass does not shrink under a finger, it deforms outward (D44 §3),
   and the two guards are what keep a disabled control from answering: `.btn`
   uses the `disabled` attribute, the glass primitive uses aria-disabled, and
   both appear in this app. (0,4,0) beats `.btn:hover` (:381), `.btn:active`
   (:382) and `.btn:disabled` (:400) on the element. */
.lg-press:not([aria-disabled="true"]):not(:disabled):active,
.btn:not([aria-disabled="true"]):not(:disabled):active,
.rail-btn:not([aria-disabled="true"]):not(:disabled):active,
.avatar-btn:not([aria-disabled="true"]):not(:disabled):active,
.chip-btn:not([aria-disabled="true"]):not(:disabled):active,
.qf-chip:not([aria-disabled="true"]):not(:disabled):active,
.gjump-chip:not([aria-disabled="true"]):not(:disabled):active,
.addbar-btn:not([aria-disabled="true"]):not(:disabled):active {
  --up: .11; transform: scale(1.03);
}
/* D44 §6: the swell goes, the lift stays. `--up` is a state change, not
   travel, and the landing keeps it too (welcome.css:1188 removes exactly this
   one declaration and nothing else). */
@media (prefers-reduced-motion: reduce) {
  .lg-press:not([aria-disabled="true"]):not(:disabled):active,
  .btn:not([aria-disabled="true"]):not(:disabled):active,
  .rail-btn:not([aria-disabled="true"]):not(:disabled):active,
  .avatar-btn:not([aria-disabled="true"]):not(:disabled):active,
  .chip-btn:not([aria-disabled="true"]):not(:disabled):active,
  .qf-chip:not([aria-disabled="true"]):not(:disabled):active,
  .gjump-chip:not([aria-disabled="true"]):not(:disabled):active,
  .addbar-btn:not([aria-disabled="true"]):not(:disabled):active { transform: none; }
}

/* ==========================================================================
   phase-94 · Q39 (D42 §3 and §5): ONE container on the board's top line, and
   defect 72's surplus stops going to the name column.
   ==========================================================================

   ---- A. The top line. ------------------------------------------------------

   `.board-right` — Activity, Automations, Claude, Team, Columns, Invite — is
   gone from `boardPage`'s markup. Its rules at :730, :1457 and :2758 are left
   standing: they are the groundskeeper's to remove, and this phase is meant to
   be reversible in one commit. Nothing was deleted with it; board.js carries
   the table of where each control went, control by control.

   `.board-more` is the survivor and it is now a `.btn.ghost.sm`, not an
   `.icon-btn`, because it has a WORD on it at every width. `.bm-label` is
   deliberately NOT `.act-label`: :1438 hides `.act-label` below 1100px, which
   would have turned the one remaining control on the line into a bare glyph on
   every tablet and phone — the exact thing the standing rule forbids. The
   44px touch floor it already had at :3905 still applies (that rule names
   `.board-more` by its own class, not through `.icon-btn.sm`), and a labelled
   button is wider than 44 anyway.

   `margin-left: auto` is what `.board-right` used to carry: it puts this one
   control at the far right and leaves D42's "empty right two-thirds", which is
   the single most visible difference from the layout the owner named. In the
   condensed head (:7828) `.board-title-row` is `flex: 1 1 auto` and shares its
   line with the switch row, so the same `auto` parks it at the right end of the
   title row's share rather than the window's. That is D38's composition and
   Q40 owns changing it; this phase does not. */
.board-more { margin-left: auto; gap: 6px; }
/* the caret is the container's own affordance and rides at the end, dimmer
   than the label, the same relationship `.board-tap` gives the board name.
   .5 and not the value the resting sort arrow uses, deliberately: the
   phase-79 sorted-arrow check (tests/api_test.py, "appended after the two
   rules it has to beat") asserts its own rule is the LAST place in this file
   that writes the arrow's resting alpha, by a bare rfind of that literal
   string -- so ANY later block writing those characters for an unrelated
   reason turns it red, including a comment that merely quotes them, which is
   why this note does not. The check is qa's and is not mine to change; it is
   reported instead. */
.board-more > .ic:last-child { opacity: .5; }
.bm-label { font-weight: var(--fw-semibold); white-space: nowrap; }
/* the hidden-column count is the one piece of state the old cluster carried
   that can make him believe columns are missing, so it stays on the outside of
   the container rather than only inside it */
.board-more .pill-count { flex: none; }

/* ---- B. Defect 72: where the surplus goes. --------------------------------

   `.board-table` is `table-layout: fixed; min-width: 100%` (:807) and its
   colgroup now declares a width for the name column and NONE for the trailing
   gutter, so the gutter is the only auto column and takes the whole surplus
   between the board's own width and the scrollport. board.js's colgroup comment
   carries the full derivation and the two rejected alternatives.

   THIS BLOCK IS THE STRIP'S HALF OF THE SAME CHANGE and it is not optional.
   `.bcol-lead` was `flex: 1 0 auto` at :7048 for one stated reason — "the slack
   a board narrower than the viewport leaves is absorbed by the lead slot in the
   flow, where the table's auto-width first <col> absorbs it". That <col> is no
   longer auto, so the lead must stop growing and `.bcol-add` must start, or the
   nineteen column titles sit up to 950px left of their own columns on a wide
   monitor. The strip and the table have one absorber; it has moved from the
   left end of the row to the right end.

   `.bcol-name` (:6632) keeps its own `flex: 1 0 auto` and is untouched: it
   lives inside `.bcols-pin`, which overrides it to `flex: 0 0 auto` at :7045,
   so its grow value has never been live and changing it here would be a change
   nobody could see and nobody could explain.

   The name column stays `position: sticky; left: 0` with its `box-shadow` rim
   (:811) and phase-90's 12px `--shade` gradient edge, both keyed off
   `td.col-name` and neither touched: this changes what the column is WIDE, not
   what it is. */
.bcol-lead { flex: 0 0 auto; }
.bcol-add {
  flex: 1 0 auto; min-width: 44px;
  /* the plus stays beside the last column instead of drifting into the middle
     of a slot that is now up to 900px wide; at 44px this moves it ~6px left and
     nothing else */
  justify-content: flex-start;
}

/* ---- A34: an aggregate view (kanban, calendar, timeline, dashboard) that
   gave up loading the rest of the board says so, in place, above its own
   (now-partial) content — never a silent wrong total. Same shape as .vo-bar
   (:4164) with the warn tone .kpi.warn already uses (:2468) instead of the
   neutral one, since this line is a real shortfall, not a role notice.
   Appended after the "phones: the chrome is solid" block on purpose: nothing
   here sets backdrop-filter, blurs a card or a row, or animates anything. */
.agg-partial {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 var(--sp-3); padding: 8px 12px;
  border-radius: var(--r-2); background: var(--warn-soft); color: var(--text-1);
  font-size: var(--fs-xs); line-height: 1.35;
}
.agg-partial > svg { flex: none; color: var(--warn); }
.agg-partial > span { flex: 1 1 auto; }
.agg-partial > button { flex: none; }
@media (max-width: 700px) { .agg-partial { margin-bottom: var(--sp-2); padding: 8px; } }

/* ==================================================================== phase-99-a
   D49 slice A, as amended 2026-09-22 (build sheet). The board's data surface
   carries almost nothing: one ground, one hairline, a dot and a word.
   Nothing in this block sets backdrop-filter, filter, a focus rule, or a
   conic gradient. Appended after the "phones: the chrome is solid" blocks
   on purpose. */
:root {
  --ground: var(--p112-ground);   /* ONE declaration, no theme override. RE-POINTED phase-112
                                     (approved redesign, 2026-09-23): it read var(--surface-1),
                                     #ffffff / #1d2025, and still resolves to exactly that under
                                     701px; a desktop takes the picture's ground, #f4f5f8 /
                                     #121418 (--p112-ground, end of file). Kept ONE declaration
                                     here rather than overridden later, so nothing fights. */
  --row-rule: var(--p112-rule);   /* RE-POINTED phase-112, same reasoning: it read
                                     var(--line-2) (#e6e9ef on white 1.22:1 ; white .075 on
                                     #1d2025 1.23:1), which --p112-rule still is under 701px;
                                     a desktop takes the picture's --hair-2 */
  --hair: 1px;
  --chip-edge: color-mix(in srgb, var(--text-1) 38%, transparent);   /* D45 §0c, verbatim */
  --chip-ring: inset 0 0 0 1px var(--chip-edge);
  --font-num: var(--font-mono);   /* Alioro Mono: the only face with tabular figures */
}
@media (min-resolution: 1.5dppx) { :root { --hair: .5px; } }   /* 1 device px on 2x; 1x snaps to 1px */

/* ---- A2: the ground and the plates ---- */
.table-wrap { background: transparent; }
.board-table { background: transparent; }
html[data-board-rows="table"] .board-body { background: var(--ground); }
html[data-board-rows="table"] .board-xbar { background: var(--ground); border-top: var(--hair) solid var(--row-rule); }
:root, [data-theme="dark"] { --th-strip: var(--ground); }
.board-cols { border-bottom: var(--hair) solid var(--line-1); }
.summary-row td, .summary-row td.col-name, .summary-row td:first-child { background: var(--ground); border-top: var(--hair) solid var(--line-1); }

/* ---- A3: one hairline, no column rules, a real gutter ---- */
.board-table td { border-left: 0; border-bottom: var(--hair) solid var(--row-rule); }
.bcol + .bcol { border-left: 0; }
.board-table .cell-text { padding: 0 8px; max-width: calc(var(--cw, 200px) - 16px); }
.board-table td.cell .cell-date { padding: 0 8px; }
.board-table td.cell[data-type="link"] .cell-link { padding: 0 8px; }
.board-table td.sum-cell { padding: 0 8px; }

/* ---- A4: colour is a dot and a word, never a fill behind text ----
   Alignment ruling (amends D49 §2a): cells stay CENTRED under their centred
   titles (app.css:6405-6446, "when will the content be centered?"). Every
   status/priority mark in one column is given the SAME box width (--lw =
   the column's widest label + dot + gap), the box is centred in the cell,
   and its content starts at the box's left edge, so the dots form one
   straight lane, centred under the title. */
.status-pill, .status-pill.sm, .status-pill.lg, .status-pill.unset,
.board-table .status-pill, .board-table .status-pill.unset,
[data-density="compact"] .status-pill, [data-density="compact"] .board-table .status-pill,
.rcard-cell .status-pill {
  background: none; border: 0; border-radius: 0; padding: 0; min-height: 0;
}
.status-pill { gap: 8px; font-size: 13px; font-weight: var(--fw-body); color: var(--text-1); }
.status-pill.sm { font-size: 12px; gap: 6px; }
.status-pill.lg { font-size: 14px; }
[data-density="compact"] .board-table .status-pill, .rcard-cell .status-pill { font-size: 13px; }
.status-pill::before { width: 8px; height: 8px; background: var(--lab, var(--text-3)); box-shadow: var(--chip-ring); }
/* no value: a hollow ring and no word. Shape says "unset", independent of colour */
.status-pill.unset::before, .board-table .status-pill.unset::before {
  background: transparent; box-shadow: inset 0 0 0 1px var(--text-3); opacity: 1;
}
/* the lane: one box width per column, centred, content from its left edge */
.board-table td.cell > .status-pill {
  width: min(var(--lw, 100%), calc(var(--cw, 140px) - 16px));
  justify-content: flex-start;
}
/* the kanban column title is the same object */
.kanban-col-head { background: none; padding: 5px 2px; font-weight: 500; }
.kanban-col-head::before { box-shadow: var(--chip-ring); }
/* the label picker is the status cell's press state (D49 §8): same object, no tint */
.label-opt { background: none; }
.label-opt:hover { background: var(--hover-ground); }
/* calendar entries on desktop; the phone's 8px dot form (:3290) must not be reached */
@media (min-width: 701px) {
  .cal-item { background: none; font-weight: var(--fw-body); }
  .cal-item:hover { background: var(--hover-ground); }
  .cal-item::before { box-shadow: var(--chip-ring); }
}

/* ---- A5: figures in the numerals face ---- */
.board-table .cell-text.num, .cell-date, .sum-cell, .rcard-cell .cell-text.num { font-family: var(--font-num); }
.board-table .cell-text.num { font-size: 12.5px; }

/* ---- A6: the timeline cell stops being a capsule ---- */
.timeline-pill { background: none; color: var(--text-1); font-size: 12.5px; font-weight: var(--fw-body); border-radius: 0; padding: 0; }

/* ==================================================================== phase-103 (slice B)
   D49 slice B, build sheet. The phone register is cards: the row IS the card,
   separated by one hairline, no box, no shadow. No backdrop-filter, no focus
   rule, no conic gradient in this block. */
@media (max-width: 700px) {
  html[data-board-rows="cards"] .board-body { background: var(--ground); }
  .cards { gap: 0; }
  .rcard:not(.rcard-add) {
    border: 0; border-bottom: var(--hair) solid var(--row-rule); border-radius: 0;
    background: transparent; box-shadow: none; padding: 12px 4px 12px 16px;
  }
  .rcard-wrap {
    border: 0; border-bottom: var(--hair) solid var(--row-rule); border-radius: 0;
    background: transparent; box-shadow: none;
  }
  .rcard-wrap > .rcard:not(.rcard-add) { border-bottom: 0; }
  .rcard-name { font: 500 15px/1.35 var(--font-display); letter-spacing: -.01em; overflow-wrap: anywhere; }
  .ptab-note {
    display: flex; align-items: center; gap: 8px; margin: 0 0 var(--sp-2); padding: 8px 4px;
    font-size: 13px; color: var(--text-2); border-bottom: var(--hair) solid var(--row-rule);
  }
  .ptab-note > .btn { margin-left: auto; min-height: 40px; }
}
/* ================================================================
   phase-106 (A24 ui): which login you are, and a way to move its work
   onto another. "Your logins" (account menu -> open-ownership) and its
   transfer confirm dialog, both plain content inside the shared .modal
   surface — no blur or floating chrome of their own, matching .snap-row's
   material one screen over (app.css:1744). Appended at the end of the
   file; nothing here sets backdrop-filter, so the "phones: the chrome is
   solid" ordering rule does not apply to this block.
   ================================================================ */
.own-row {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  border: 1px solid var(--line-2); border-radius: var(--r-2);
  padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-2);
}
.own-row > div:first-child { min-width: 0; }
.own-row b { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 260px; }
.own-me { background: var(--surface-1); }
.own-counts { flex: none; white-space: nowrap; }
.own-notice {
  display: flex; align-items: center; gap: var(--sp-2);
  color: var(--warn); font-size: var(--fs-xs); font-weight: 600;
  margin: var(--sp-3) 0 var(--sp-2);
}
.own-notice .ic { flex: none; }
.own-list { display: flex; flex-direction: column; }
.own-err { color: var(--danger); }
@media (max-width: 700px) {
  .own-row { flex-direction: column; align-items: stretch; }
  .own-row b { max-width: none; }
}

/* ==================================================================== phase-112: the approved redesign
   The owner approved a picture on 2026-09-23 ("bingo"; "improve and polish
   further so EVERYTHING matches the style"): design/approved-2026-09-23/
   register-1920-dark.{html,png}. The designer's POLISH-SPEC.md (same folder,
   same day) then decided every measure the picture left loose - the 8px
   grid, six radii by one rule, six type sizes, one left text edge, the
   contrast of the quiet ink - and those numbers are the ones used here. Two
   things the spec would change are KEPT from the picture, on the lead's
   call: the dock's 1px five-hue prism, and the picture's glass look on the
   rail, the boards panel and the dock. Where the app needs something neither
   shows, the same language is extended; there is no new material.
     1. THE RAIL      a floating glass column, 72px, inset 16px, labelled
     2. THE PANEL     the boards panel (#sidebar.bp), the picture's `.fly`
     3. THE HEAD      workspace, 44px title, star, Board tools, people,
                      the summary with its tiles, the segmented bar
     4. THE DOCK      the views, search, Filter and New <item>, floating
     5. THE SHEETS    the landing's sheets behind the title band only
     6. THE REGISTER  rows on the ground, titles above each open group,
                      the most common status plain, the rest a glass capsule
   REJECTED by the owner, so nothing here may bring it back: dots as status
   markers, a coloured glow under the dock, a fixed generic sidebar, column
   titles above the group names, letter tiles for boards, monday.com's head.
   SUPERSEDES phase-99-a's dot treatment and its mono figures on the board.
   Radius follows height (spec §3): <=18 6px, 20-28 8px, 32 12px, 36-56 16px,
   panes 24px, marks under 12px 2px, avatars round.
   Type (spec §4): 44/48 display, 15/20 heading, 13.5/20 body, 12/16 small,
   11/14 micro; 400, 500, 600.
   Phones: every desktop surface sits inside `@media (min-width: 701px)`, and
   the block ends with its own phone reset after its glass, so under 701px
   the chrome stays solid (css.md, the two legal shapes). Transitions name
   the one curve token. No rule here answers a keyboard focus state: the
   owner-locked rainbow edge and the one focus ring are untouched. ---- */

/* ---- tokens -------------------------------------------------------------- */
:root {
  /* the ground and the row hairline under 701px: exactly what --ground and
     --row-rule resolved to before this phase, so the phone does not move */
  --p112-ground: var(--surface-1);
  --p112-rule: var(--line-2);
}
@media (min-width: 701px) {
  :root {
    /* spec §7, light column. The ground is the existing surface-0. */
    --p112-ground: var(--surface-0);
    --p112-rule: rgba(13, 15, 19, .055);
    --p112-hair: rgba(13, 15, 19, .1);
    --p112-t1: #141a2b; --p112-t2: #3d475c; --p112-t3: #5a6478; --p112-t4: #a2a9b6;
    --p112-hover: color-mix(in srgb, #0d0f13 3%, var(--surface-0));
    --p112-on: #ffffff;
    --p112-on-edge: 0 0 0 .5px rgba(13, 15, 19, .1), 0 1px 2px rgba(13, 15, 19, .08);
    --p112-well: rgba(13, 15, 19, .05);
    --p112-vr: rgba(13, 15, 19, .12);
    --p112-kbd: rgba(13, 15, 19, .14);
    /* the picture's glass, light: white frost with a dark hairline rim */
    --p112-glass: linear-gradient(180deg, rgba(255, 255, 255, .82), rgba(255, 255, 255, .64));
    --p112-rim: inset 0 1px 0 rgba(255, 255, 255, .95), inset 0 -1px 0 rgba(13, 15, 19, .04), 0 0 0 1px rgba(13, 15, 19, .1);
    /* written ONCE, read by the rail, the boards panel and the dock (D46 §3d) */
    --p112-fx: blur(26px) saturate(170%) brightness(1.08);
  }
  [data-theme="dark"] {
    /* spec §7, dark column: the ground is surface-0 (#16181d), and t3 is
       #80878f because the picture's #6c737d measured 3.7:1 for 12px text */
    --p112-ground: var(--surface-0);
    --p112-rule: rgba(255, 255, 255, .035);
    --p112-hair: rgba(255, 255, 255, .065);
    --p112-t1: #eceef2; --p112-t2: #a7adb6; --p112-t3: #80878f; --p112-t4: #484e57;
    --p112-hover: color-mix(in srgb, #fff 3%, var(--surface-0));
    --p112-on: rgba(255, 255, 255, .09);
    --p112-on-edge: inset 0 1px 0 rgba(255, 255, 255, .12);
    --p112-well: rgba(0, 0, 0, .2);
    --p112-vr: rgba(255, 255, 255, .09);
    --p112-kbd: rgba(255, 255, 255, .1);
    /* the picture's glass, dark, with its brighter rim */
    --p112-glass: linear-gradient(180deg, rgba(255, 255, 255, .075), rgba(255, 255, 255, .025));
    --p112-rim: inset 0 1px 0 rgba(255, 255, 255, .28), inset 0 -1px 0 rgba(255, 255, 255, .06), 0 0 0 1px rgba(255, 255, 255, .12);
  }
}

@media (min-width: 701px) {
  .app { background: var(--p112-ground); }
  /* SUPERSEDED phase-115 (2026-09-23, the owner's "ok" to the floating glass
     sidebar, design/approved-2026-09-23/sidebar-*.png, OWNER-CHECKLIST
     43-49). This read `margin-left: 104px` ("the content column starts at
     104 + 48 = 152") and section 1 below it built THE RAIL, a 72px labelled
     glass column. Both are replaced by the phase-115 block at the end of
     this file: the content offset now follows the sidebar's two states
     (288 open, 100 closed), and the rail is the sidebar. Removed rather than
     overridden, so no reading of the cascade can bring the 72px column back
     under the new one. */
  .app > .main { background: var(--p112-ground); }

  /* ---- 2. THE BOARDS PANEL (spec §3: x96 y16, 320 wide, r24, pad 8) --- */
  /* phase-115: x is 8px right of the sidebar - 276 open (16 + 252 + 8)
     here, 78 closed in the phase-115 block */
  .app .sidebar.bp {
    position: fixed; left: 276px; top: 16px; bottom: auto; width: 320px;
    max-height: calc(100vh - 32px); border: 0; border-radius: 24px; padding: 8px;
    display: flex; flex-direction: column;
    background: var(--p112-glass);
    -webkit-backdrop-filter: var(--p112-fx); backdrop-filter: var(--p112-fx);
    box-shadow: var(--p112-rim), 0 8px 24px rgba(0, 0, 0, .28);
    transform: translateY(-4px); opacity: 0; visibility: hidden;
    transition: transform var(--dur-micro-3) var(--ease-out), opacity var(--dur-micro-3) var(--ease-out),
                visibility 0s linear var(--dur-micro-3);
  }
  :root:not([data-theme="dark"]) .app .sidebar.bp { box-shadow: var(--p112-rim), 0 8px 24px rgba(13, 15, 19, .1); }
  .app .sidebar.bp.open {
    transform: none; opacity: 1; visibility: visible;
    transition: transform var(--dur-micro-3) var(--ease-out), opacity var(--dur-micro-3) var(--ease-out);
  }
  /* the panel paints the picture's one recipe on itself, so the primitive's
     own two layers stand down here rather than stacking a second material */
  .app .sidebar.bp::before, .app .sidebar.bp::after { display: none; }
  .app .drawer-scrim { inset: 0; }
}
/* the panel's contents, at every width (the phone drawer shows the same list) */
.bp-head { display: flex; align-items: center; gap: 8px; padding: 6px 4px 8px 12px; flex: none; }
.bp-head > b { font: 600 15px/20px var(--font-display); color: var(--p112-t1, var(--text-1)); }
.bp-n { font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3)); }
/* the one "+" (New board): the owner's own words, "just add the plus icon
   somewhere" - a 16px SD.icon plus, t2, on a 28px round glass hit area */
.bp-new {
  margin-left: auto; width: 28px; height: 28px; border-radius: 50%; border: 0; padding: 0; flex: none;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  background: none; color: var(--p112-t2, var(--text-2));
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.bp-new:hover { background: var(--p112-on, var(--hover-ground)); box-shadow: var(--p112-on-edge, none); color: var(--p112-t1, var(--text-1)); }
.sidebar .bp-find {
  display: flex; align-items: center; gap: 8px; height: 32px; margin: 0 0 8px; padding: 0 12px;
  border-radius: 12px; border-color: transparent; background: var(--p112-well, var(--surface-2));
  color: var(--p112-t3, var(--text-3)); flex: none;
}
.sidebar .bp-find .board-filter { width: auto; flex: 1; min-width: 0; font-size: 13.5px; }
.bp-list { flex: 1 1 auto; overflow-y: auto; padding: 0 0 4px; }
.bp-wk { display: flex; align-items: center; gap: 6px; font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3)); padding: 12px 12px 4px; }
.bp-wn { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
.bp-wk .ws-dots {
  flex: none; width: 24px; height: 24px; border: 0; padding: 0; border-radius: 8px; background: none;
  color: var(--p112-t3, var(--text-3)); cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  opacity: 0; transition: opacity var(--dur-micro-1) var(--ease-out);
}
.bp-wk:hover .ws-dots, .bp-wk .ws-dots[aria-expanded="true"] { opacity: 1; }
@media (hover: none) { .bp-wk .ws-dots { opacity: 1; } }
/* a board row: the FULL name, wrapping, never an ellipsis or an initial */
.sidebar .bp-row {
  display: flex; align-items: center; gap: 8px; min-height: 36px; padding: 8px 12px; margin: 0;
  border-radius: 16px; color: var(--p112-t2, var(--text-2)); font: 400 13.5px/20px var(--font-body);
  text-decoration: none; background: none;
}
.sidebar .bp-row .sb-name {
  flex: 1 1 auto; min-width: 0; white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
}
.sidebar .bp-row .sb-fav { flex: none; display: inline-flex; color: var(--p112-t3, var(--text-3)); }
.sidebar .bp-row .bp-c { flex: none; margin-left: auto; font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3)); }
.sidebar .bp-row:hover { background: var(--p112-hover, var(--hover-ground)); color: var(--p112-t1, var(--text-1)); }
.sidebar .bp-row.active { background: var(--p112-on, var(--brand-soft)); color: var(--p112-t1, var(--text-1)); box-shadow: var(--p112-on-edge, none); font-weight: 400; }
.sidebar .bp-row.active::before { display: none; }
.bp-none { padding: 12px; font-size: 13.5px; color: var(--p112-t3, var(--text-3)); margin: 0; }

@media (min-width: 701px) {
  /* ---- 3. THE BOARD HEAD (spec §5: crumb 24, title 48, summary 104,
     bar 136, first group 164; one text edge at x164, spec §6.1) -------- */
  .app .board-head {
    padding: 24px 48px 24px 60px; background: var(--p112-ground);
    border-bottom: 1px solid transparent; box-shadow: none;
  }
  .app .board-head.scrolled { border-bottom-color: var(--p112-hair); box-shadow: none; }
  [data-theme="dark"] .app .board-head.scrolled { box-shadow: none; }
  /* the old head's lit top edge: the picture's head sits on the ground */
  .app .board-head::after { display: none; }
  .app .board-head.cond { padding-top: 8px; padding-bottom: 8px; }
  .p112-crumb, .app .board-title-row, .app .board-head .bsum, .p112-bar, .app .board-head .bfil, .app .board-head .vo-bar { position: relative; z-index: 1; }
  .p112-crumb { display: flex; align-items: center; gap: 8px; font: 400 12px/16px var(--font-body); color: var(--p112-t3); }
  .p112-crumb > span { min-width: 0; overflow-wrap: anywhere; }
  .app .board-head .board-title-row { align-items: center; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
  .app .board-head .board-name, .app .board-head .board-name.tap {
    font: 600 44px/48px var(--font-display); letter-spacing: -.03em; color: var(--p112-t1);
    padding: 0; margin: 0 8px 0 0; max-width: 100%; white-space: normal; overflow: visible; text-overflow: clip;
  }
  /* the title never ellipsises: a long board name wraps (checklist 10) */
  .app .board-head .board-name > .board-tap {
    white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
  }
  /* groundskeeping (phase-137): the inert `align-items: center` (the button is display: block since phase-131) was removed from the rule above. */
  /* the star rests at t3 (spec §14.5: #484e57 was 2.2:1 for a control) */
  .app .board-head .icon-btn.star { width: 32px; height: 32px; padding: 8px; border-radius: 12px; color: var(--p112-t3); }
  .app .board-head .icon-btn.star.on { color: var(--star); }
  .app .board-head .icon-btn.star:hover { background: var(--p112-hover); }
  /* Board tools: a compact labelled capsule beside the star (spec §12) */
  .app .board-head .board-more {
    height: 28px; padding: 0 10px 0 8px; gap: 6px; margin-left: 0; border: 0; border-radius: 8px;
    background: var(--p112-well); box-shadow: none; color: var(--p112-t2); font: 500 12px/16px var(--font-body);
  }
  .app .board-head .board-more:hover { background: var(--p112-hover); color: var(--p112-t1); }
  .app .board-head .board-more > .ic { width: 16px; height: 16px; color: var(--p112-t3); opacity: 1; }
  .app .board-head .board-more .pill-count { background: none; color: var(--p112-t3); padding: 0; font: 400 12px/16px var(--font-body); }
  /* who is here, at the right end of the title line (FUNCTION-MAP #7) */
  .p112-people-slot { margin-left: auto; display: inline-flex; align-items: center; }
  .p112-people { display: inline-flex; align-items: center; }
  .p112-people > .avatar { box-shadow: 0 0 0 2px var(--p112-ground); font: 600 11px/1 var(--font-body) !important; }
  .p112-people > .avatar + .avatar { margin-left: -6px; }
  .p112-people-n { margin-left: 6px; font: 400 12px/16px var(--font-body); color: var(--p112-t3); }
  /* the summary line: Body, figures 500 in t1, each named state beside its
     own 10px tile, the remainder and "no status" in words (spec §4, §7) */
  .app .board-head .bsum { margin-top: 8px; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 20px; font: 400 13.5px/20px var(--font-body); color: var(--p112-t3); }
  .app .board-head .bsum > span { display: inline-flex; align-items: center; gap: 8px; }
  .app .board-head .bsum b { color: var(--p112-t1); font-weight: 500; font-family: var(--font-body); font-variant-numeric: tabular-nums; }
  /* the segmented bar: 520 x 4, 4px gaps, r2, in the same order as the words */
  .p112-bar { display: flex; gap: 4px; width: 520px; max-width: 100%; height: 4px; margin-top: 12px; }
  .p112-bar:empty { display: none; }
  .p112-bar > i { display: block; min-width: 2px; border-radius: 2px; }
  .p112-bar > i.p112-bar-none { background: var(--surface-3); }
  .app .board-head .bfil:not(:empty) { margin-top: 12px; }
  .app .board-head.cond .p112-crumb, .app .board-head.cond .p112-bar, .app .board-head.cond .p112-sheets { display: none; }
  .app .board-head.cond .board-title-row { margin-top: 0; }
  .app .board-head.cond .board-name, .app .board-head.cond .board-name.tap { font-size: 15px; line-height: 20px; letter-spacing: 0; }

  /* ---- 5. THE SHEETS, behind the title band only ----------------------- */
  .p112-sheets {
    /* phase-115: back to the page's x0 from the content column, which
       starts 288 in with the sidebar open (100 closed, phase-115 block) */
    position: absolute; left: -288px; top: 0; bottom: 0; width: 100vw; z-index: 0;
    overflow: hidden; pointer-events: none;
    -webkit-mask: linear-gradient(180deg, #000 0, #000 90px, rgba(0, 0, 0, .35) 150px, transparent 190px);
    mask: linear-gradient(180deg, #000 0, #000 90px, rgba(0, 0, 0, .35) 150px, transparent 190px);
  }
  .p112-sheets > i { position: absolute; border-radius: 2px; }
  /* light: the neutral sheets as faint grey on the pale ground; the five
     coloured ones keep their hue, a little fainter */
  :root:not([data-theme="dark"]) .p112-sheets > i.n { background: rgba(13, 15, 19, .06) !important; }
  :root:not([data-theme="dark"]) .p112-sheets > i:not(.n) { opacity: .7; }

  /* ---- 4. THE DOCK (spec §3: bottom 32, 56 high, pad 8, r24, centred on
     the CONTENT column, x = (152 + viewport - 48) / 2 = 50% + 52px) ------ */
  /* phase-115: the content column starts at 288 + 48 with the sidebar open,
     so the centre is (336 + viewport - 48) / 2 = 50% + 144px; closed it is
     50% + 50px (the phase-115 block). Same rule, the new column. */
  .app .board-head .p112-dock {
    position: fixed; left: calc(50% + 144px); bottom: 32px; top: auto; z-index: 30; transform: translateX(-50%);
    display: flex; align-items: center; flex-wrap: nowrap; gap: 8px; margin: 0; padding: 8px; height: 56px;
    max-width: calc(100vw - 288px - 48px - 32px); border-radius: 24px;
    background: var(--p112-glass);
    -webkit-backdrop-filter: var(--p112-fx); backdrop-filter: var(--p112-fx);
    box-shadow: var(--p112-rim), 0 26px 80px rgba(0, 0, 0, .6), 0 4px 14px rgba(0, 0, 0, .35);
  }
  :root:not([data-theme="dark"]) .app .board-head .p112-dock { box-shadow: var(--p112-rim), 0 18px 50px rgba(13, 15, 19, .14), 0 4px 14px rgba(13, 15, 19, .07); }
  /* the one-pixel five-hue PRISM on the top edge, fading out at both ends
     (the logo hues, UI palette), and a soft NEUTRAL shadow under the dock.
     Nothing coloured is cast on the page (owner: the coloured glow "looks
     ratchet"). Kept from the picture on the lead's call over POLISH-SPEC
     §14.21. */
  .app .board-head .p112-dock::before {
    content: ""; position: absolute; left: 9%; right: 9%; top: -1px; height: 1px; border-radius: 1px; pointer-events: none;
    background: linear-gradient(90deg, transparent, rgba(255, 50, 41, .55) 14%, rgba(255, 160, 10, .6) 32%, rgba(255, 204, 0, .55) 50%, rgba(0, 184, 66, .55) 68%, rgba(0, 137, 235, .6) 86%, transparent);
    filter: drop-shadow(0 0 3px rgba(255, 255, 255, .25));
  }
  .app .board-head .p112-dock::after {
    content: ""; position: absolute; left: 12%; right: 12%; bottom: -26px; height: 30px; border-radius: 50%; z-index: -1; pointer-events: none;
    background: radial-gradient(closest-side, rgba(0, 0, 0, .55), transparent); filter: blur(8px);
  }
  :root:not([data-theme="dark"]) .app .board-head .p112-dock::after { background: radial-gradient(closest-side, rgba(13, 15, 19, .16), transparent); }
  /* the views: a 40px well (r16, pad 4), 32px items (r12), the active one a
     lit plate (.vt-ind glides between them, placeViewIndicator) */
  .app .board-head .p112-dock .view-tabs {
    position: relative; display: flex; align-items: center; gap: 0; height: 40px; padding: 4px; margin: 0;
    border-radius: 16px; background: var(--p112-well); flex: 0 1 auto; min-width: 0;
    overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  }
  .app .board-head .p112-dock .view-tabs::-webkit-scrollbar { display: none; }
  .app .board-head .p112-dock .view-tab,
  .app .board-head.cond .p112-dock .view-tab {
    display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 12px; border-radius: 12px;
    color: var(--p112-t2); font: 400 13.5px/20px var(--font-body); background: none; white-space: nowrap; flex: none;
  }
  .app .board-head .p112-dock .view-tab > .ic,
  .app .board-head.cond .p112-dock .view-tab > .ic { width: 16px; height: 16px; padding: 0; stroke-width: 1.6; }
  .app .board-head .p112-dock .view-tab:hover { color: var(--p112-t1); }
  .app .board-head .p112-dock .view-tab.active { color: var(--p112-t1); background: none; box-shadow: none; }
  .app .board-head .p112-dock .view-tabs .view-tab .vt-label { display: inline; }
  .app .board-head .p112-dock .vt-wrap .view-tab.active { padding-right: 28px; }
  .app .board-head .p112-dock .vt-menu { right: 6px; color: var(--p112-t3); }
  .app .board-head .p112-dock .vt-ind {
    top: 4px; bottom: 4px; height: auto; border-radius: 12px;
    background: var(--p112-on); box-shadow: var(--p112-on-edge);
  }
  .app .board-head .p112-dock .view-add { width: 32px; height: 32px; border-radius: 12px; color: var(--p112-t3); flex: none; }
  .app .board-head .p112-dock .view-add > .ic { width: 16px; height: 16px; }
  .p112-vr { flex: none; width: 1px; height: 24px; margin: 0; background: var(--p112-vr); }
  .app .board-head .p112-dock .board-tools { display: flex; align-items: center; gap: 8px; margin: 0; padding: 0; flex: 0 1 auto; min-width: 0; }
  .app .board-head .p112-dock .p112-srch {
    display: flex; align-items: center; gap: 8px; flex: 0 1 200px; width: 200px; min-width: 160px; height: 40px;
    padding: 0 12px; border-radius: 16px; border-color: transparent; background: var(--p112-well);
    color: var(--p112-t3); font: 400 13.5px/20px var(--font-body);
  }
  .app .board-head .p112-dock .p112-srch > .ic { width: 16px; height: 16px; }
  .app .board-head .p112-dock .p112-srch .board-filter { flex: 1 1 auto; width: auto; min-width: 0; font-size: 13.5px; color: var(--p112-t1); }
  /* the keycap: Micro 11/14 on an 18px cap, r6 */
  .p112-kbd {
    flex: none; display: inline-flex; align-items: center; gap: 1px; margin-left: auto; height: 18px;
    padding: 0 5px; border: 1px solid var(--p112-kbd); border-radius: 6px;
    font: 500 11px/14px var(--font-body); color: var(--p112-t3);
  }
  .p112-kbd > .ic { width: 11px; height: 11px; }
  .app .board-head .p112-dock .p112-ib {
    height: 40px; padding: 0 12px; gap: 8px; border: 0; border-radius: 16px; background: none; box-shadow: none;
    color: var(--p112-t2); font: 400 13.5px/20px var(--font-body);
  }
  .app .board-head .p112-dock .p112-ib > .ic { width: 16px; height: 16px; }
  .app .board-head .p112-dock .p112-ib:hover { color: var(--p112-t1); background: var(--p112-hover); }
  .app .board-head .p112-dock .p112-ib.on { color: var(--p112-t1); background: var(--p112-on); box-shadow: var(--p112-on-edge); }
  /* New <item>: the landing's rimmed glass capsule with an arrow, 40 high,
     r16, 13.5/600. Page chrome carries no solid fill; the approved picture
     overrides its own first draft's brand fill with exactly this (spec §15). */
  .app .board-head .p112-dock .btn.primary.p112-new {
    height: 40px; padding: 0 16px; gap: 8px; border-radius: 16px; white-space: nowrap; flex: none;
    border: 1px solid rgba(255, 255, 255, .55); color: #fff; font: 600 13.5px/20px var(--font-body);
    background: linear-gradient(180deg, rgba(255, 255, 255, .12), rgba(255, 255, 255, .04));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .35), 0 0 24px rgba(255, 255, 255, .06), 0 8px 22px rgba(0, 0, 0, .4);
  }
  .app .board-head .p112-dock .btn.primary.p112-new:not(:disabled):hover {
    background: linear-gradient(180deg, rgba(255, 255, 255, .18), rgba(255, 255, 255, .07));
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .4), 0 0 24px rgba(255, 255, 255, .08), 0 8px 22px rgba(0, 0, 0, .4);
  }
  :root:not([data-theme="dark"]) .app .board-head .p112-dock .btn.primary.p112-new {
    border-color: rgba(13, 15, 19, .32); color: var(--p112-t1);
    background: linear-gradient(180deg, rgba(255, 255, 255, .96), rgba(255, 255, 255, .72));
    box-shadow: inset 0 1px 0 #fff, 0 6px 16px rgba(13, 15, 19, .12);
  }
  :root:not([data-theme="dark"]) .app .board-head .p112-dock .btn.primary.p112-new:not(:disabled):hover {
    background: #fff; box-shadow: inset 0 1px 0 #fff, 0 8px 20px rgba(13, 15, 19, .16);
  }
  .p112-arr { display: inline-flex; opacity: .85; }
  .p112-arr > .ic { width: 16px; height: 16px; stroke-width: 2; }
  /* the words come and go with .act-label (hidden below 1100px); the plus
     stands in for them there, the arrow goes with them */
  .app .board-head .p112-dock .p112-new > .ic[data-ic="plus"] { display: none; }
  .app .board-head .p112-dock .tool-chip { height: 40px; border-radius: 16px; }
  /* the last row scrolls clear of the dock (spec §5: 120); every view sits
     on the content column (x152). The table alone starts in the 24px gutter
     left of it (x128, below), where the group caret sits. */
  .app .board-body { padding: 0 48px 120px 48px; }
}
/* the search is 280 wide once the content column is 1600 or more (spec §3) */
@media (min-width: 1800px) {
  .app .board-head .p112-dock .p112-srch { flex-basis: 280px; width: 280px; }
}
@media (min-width: 701px) and (max-width: 1099px) {
  .app .board-head .p112-dock .p112-new > .ic[data-ic="plus"] { display: inline-block; width: 16px; height: 16px; }
  .app .board-head .p112-dock .p112-new .p112-arr { display: none; }
  .app .board-head .p112-dock .btn.primary.p112-new { padding: 0 12px; }
  .app .board-head .p112-dock .view-tabs .view-tab .vt-label { display: none; }
  .app .board-head .p112-dock .view-tabs .view-tab.active .vt-label,
  .app .board-head .p112-dock .view-tabs .view-tab .vt-label.vt-keep { display: inline; }
}

@media (min-width: 701px) {
  /* ---- 6. THE REGISTER (spec §5: group 48, title row 32, row 40, add row
     40, footer 40, 16 after an open group; §6: one text edge) ----------- */
  .app .board-body:has(> .board-scroll) { padding-left: 24px; padding-right: 48px; }
  .app .board-table td { font: 400 13.5px/20px var(--font-body); color: var(--p112-t2); height: 40px; }
  .app .board-table td.col-name, .app .board-table th.col-name { background: var(--ground); box-shadow: none; }
  .app .board-table td.col-name::after, .app .board-table th.col-name::after,
  [data-theme="dark"] .app .board-table td.col-name::after, [data-theme="dark"] .app .board-table th.col-name::after {
    background: linear-gradient(90deg, var(--ground), transparent);
  }
  /* the row's own quiet hover, opaque so the frozen name column stays solid */
  .app .item-row:hover, .app .item-row:hover td, .app .item-row:hover td.col-name { background: var(--p112-hover); }
  .app .item-row:hover td.col-name::after, [data-theme="dark"] .app .item-row:hover td.col-name::after {
    background: linear-gradient(90deg, var(--p112-hover), transparent);
  }
  /* the row's left status edge stands down: the picture's rows carry the
     state in the status column alone (the capsule), and a coloured edge on
     every row would be a second marker for the same fact */
  .app .board-table .row-accent { display: none; }
  /* the name: at the one text edge (+36 = 30 + the name's own 6px of fan
     padding), full ink, and it WRAPS rather than ellipsising (checklist 10) */
  .app .board-table td.col-name { padding: 0 6px 0 30px !important; }
  .app .board-table td.col-name .item-name {
    color: var(--p112-t1); font: 400 13.5px/20px var(--font-body);
    white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
  }
  /* comment count and the row menu: on the row's hover or while a control
     in the row holds the keyboard; always on a touch screen */
  .app .board-table td.col-name .chat-btn, .app .board-table td.col-name .row-menu {
    opacity: 0; color: var(--p112-t3); background: none; border-radius: 8px; transition: opacity var(--dur-micro-1) var(--ease-out);
  }
  .app .board-table td.col-name .chat-btn { width: auto; min-width: 32px; height: 24px; padding: 0 4px; font: 400 12px/16px var(--font-body); }
  .app .board-table td.col-name .chat-btn.has span { background: none; color: var(--p112-t3); font: 400 12px/16px var(--font-body); padding: 0; }
  .app .board-table td.col-name .row-menu { width: 24px; height: 24px; }
  .app .board-table .item-row:hover td.col-name .chat-btn, .app .board-table .item-row:hover td.col-name .row-menu,
  .app .board-table .item-row:focus-within td.col-name .chat-btn, .app .board-table .item-row:focus-within td.col-name .row-menu { opacity: 1; }
  /* people: an empty cell is empty at rest; its "+" comes with the row */
  .app .board-table .cell-empty-people { opacity: 0; transition: opacity var(--dur-micro-1) var(--ease-out); }
  .app .board-table .item-row:hover .cell-empty-people, .app .board-table .item-row:focus-within .cell-empty-people { opacity: 1; }
  /* figures in the UI face with tabular numerals, as the picture sets them
     (spec §4, §15; supersedes phase-99-a A5's numerals face on the board) */
  .app .board-table .cell-text.num, .app .board-table .cell-date, .app .board-table .sum-cell,
  .app .bcol[data-type="date"], .app .bcol[data-type="number"] {
    font-family: var(--font-body); font-size: 13.5px; font-variant-numeric: tabular-nums;
  }
  .app .board-table .cell-date { color: var(--p112-t2); }
  .app .board-table tr.add-row td { height: 40px; padding: 0 0 0 12px; }
  .app .board-table .addbar-btn { height: 32px; padding: 0 12px 0 4px; border-radius: 12px; font: 400 13.5px/20px var(--font-body); color: var(--p112-t2); }
  .app .board-table .summary-row td { height: 40px; }
  /* 16 of space after an open group; collapsed groups stack with none */
  .app .board-scroll > .group:not(.collapsed) { margin-bottom: 16px; }

  /* the group header: the caret in the gutter (x128-144), the 3x16 spine on
     the content edge (x152), the name at the text edge (x164) in the 15/20
     heading, the count quiet. 48 = 14 + 20 + 14, from padding alone (a
     height on anything inside .board-scroll is what D28 forbids). */
  .app .board-scroll > .group > .group-head { border-bottom: 1px solid var(--p112-hair); }
  .app .board-scroll > .group > .group-head:hover { border-bottom-color: var(--p112-hair); }
  .app .ghead-in { gap: 8px; padding: 14px 8px 14px 0; }
  .app .ghead-in::before {
    left: 24px; top: 50%; bottom: auto; width: 3px; height: 16px; margin-top: -8px;
    border-radius: 2px; background: var(--gcolor); box-shadow: none;
  }
  .app .group-head .group-caret { width: 16px; height: 20px; border-radius: 6px; color: var(--p112-t3); }
  .app .group-head .group-caret > .ic { width: 12px; height: 12px; }
  .app .group-head:hover .group-caret { color: var(--p112-t1); }
  .app .ghead-in .group-title {
    margin-left: 8px; padding: 0 2px;
    font: 600 15px/20px var(--font-display); letter-spacing: 0; color: var(--p112-t1);
    white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
  }
  .app .ghead-in .group-count { margin-left: 4px; font: 400 12px/16px var(--font-body); color: var(--p112-t3); letter-spacing: 0; }
  .app .ghead-in .group-count b, .app .ghead-in .group-count i { font-weight: 400; color: var(--p112-t3); }
  .app .ghead-in .group-menu { width: 24px; height: 24px; border-radius: 8px; opacity: 0; }
  .app .group-head:hover .group-menu, .app .group-head .group-menu[aria-expanded="true"] { opacity: 1; }

  /* THE COLUMN TITLES, directly above each open group's rows: 32 high,
     Small 12/16 at 500 in t3, one hairline under them. Centred over every
     column but the name (owner: "columns are not centered"); a title WRAPS
     rather than ellipsising (checklist 10). */
  .app .board-table thead.p112-colh th {
    height: 32px; padding: 0 6px; background: var(--ground); border: 0; border-bottom: 1px solid var(--p112-hair);
    font: 500 12px/16px var(--font-body); color: var(--p112-t3); text-align: center; white-space: normal; vertical-align: middle;
  }
  .app .board-table thead.p112-colh th.col-name { text-align: left; padding-left: 30px; min-width: 0; }
  /* the gutter's title cell declares NO width: this row is now the first
     row of a table-layout:fixed table, and a width here would pin the gutter
     and hand the surplus back to every column (defect 72) */
  .app .board-table thead.p112-colh th.col-add { width: auto; text-align: left; padding: 0 4px; border-left: 0; }
  .app .board-table thead.p112-colh .col-head,
  .app .board-cols .col-head {
    font: 500 12px/16px var(--font-body); letter-spacing: 0; text-transform: none; color: var(--p112-t3);
    white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; padding: 2px 4px; border-radius: 6px; background: none;
  }
  .app .board-table thead.p112-colh .col-head:hover, .app .board-cols .col-head:hover { color: var(--p112-t1); }
  .app .board-table thead.p112-colh th.sorted .col-head, .app .board-cols .bcol.sorted .col-head { color: var(--p112-t1); }
  .app .board-table thead.p112-colh .th-label, .app .board-cols .th-label { font: 500 12px/16px var(--font-body); color: var(--p112-t3); text-transform: none; letter-spacing: 0; }
  /* the lone "+" at the end of every title row: Add column, 17px, t3 */
  .p112-addcol { color: var(--p112-t3); width: 28px; height: 28px; border-radius: 8px; }
  .app .board-table thead.p112-colh .p112-addcol > .ic { width: 17px; height: 17px; }
  /* the strip is the SAME titles, pinned under the head only while the
     reader is inside an open group whose own title row has scrolled away
     (pinStrip, board.js). At rest it takes no room and paints nothing, so
     there is no title strip above the group names. */
  .app .board-body .board-cols {
    height: 0; border-bottom: 0; background: none; overflow: visible;
    visibility: hidden; pointer-events: none;
  }
  .app .board-body .board-cols .bcols-clip { background: var(--ground); border-bottom: 1px solid var(--p112-hair); }
  .app .board-body .board-cols.p112-pin { visibility: visible; pointer-events: auto; }
  .app .board-cols .bcol { padding: 0 6px; min-height: 32px; display: flex; align-items: center; justify-content: center; font: 500 12px/16px var(--font-body); color: var(--p112-t3); }
  .app .board-cols .bcol .th-in { width: 100%; }
  .app .board-cols .bcol-name { justify-content: flex-start; padding-left: 30px; background: var(--ground); }

  /* the status cell: the capsule or the plain word sits centred */
  .app .board-table td.cell > .status-pill { width: auto; max-width: calc(var(--cw, 140px) - 16px); justify-content: center; }
  .app .board-table .cell-people .avatar { font: 600 11px/1 var(--font-body) !important; }

  /* Row height is his (the account menu's Row height): compact keeps the
     same grid one step down - 32px rows and title row, a 32px group line. */
  [data-density="compact"] .app .board-table td,
  [data-density="compact"] .app .board-table tr.add-row td,
  [data-density="compact"] .app .board-table .summary-row td { height: 32px; }
  [data-density="compact"] .app .ghead-in { padding-top: 6px; padding-bottom: 6px; }
  [data-density="compact"] .app .board-table thead.p112-colh th { height: 32px; }

  /* phase-167 groundskeeping: removed the .p112-empty* rules (empty board, spec §9); phase-165's .p165-* empty states replaced them and nothing renders the class */
}
/* at every width: no dot on a status or priority value, anywhere */
.status-pill::before, .status-pill.unset::before, .board-table .status-pill.unset::before { display: none; }
.status-pill.p112-plain { color: var(--p112-t2, var(--text-2)); font-weight: 400; }
/* chip-edge: exempt - tinted capsule, its rim is the chip (POLISH-SPEC §15;
   the owner approved it 2026-09-23, "bingo"). Spec §7 and §14.20: 24 high,
   r8, padding 0 10, Small 12 at 500; fill the chip at 14%, rim the chip at
   32% (dark) / 60% (light); text --chip-ink in dark (the chip lifted 66%
   toward white, D45 §0c's one legal lift for a data colour as text) and t1
   in light. The fill is .14 alpha and never more: R2 (phase-112) holds it. */
.status-pill.p112-cap {
  display: inline-flex; align-items: center; justify-content: center; height: 24px; min-height: 24px; padding: 0 10px;
  border-radius: 8px; font: 500 12px/16px var(--font-body); color: var(--p112-t1, var(--text-1));
  background: color-mix(in srgb, var(--lab, var(--text-3)) 14%, transparent);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), inset 0 0 0 1px color-mix(in srgb, var(--lab, var(--text-3)) 60%, transparent);
}
[data-theme="dark"] .status-pill.p112-cap {
  color: color-mix(in srgb, var(--lab, var(--text-3)) 66%, #fff);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .14), inset 0 0 0 1px color-mix(in srgb, var(--lab, var(--text-3)) 32%, transparent);
}
.status-pill.sm.p112-cap { height: 20px; min-height: 20px; padding: 0 8px; font-size: 12px; border-radius: 8px; }
[data-density="compact"] .board-table .status-pill.p112-cap { height: 20px; min-height: 20px; padding: 0 8px; border-radius: 8px; }
@media (hover: none) {
  .app .board-table td.col-name .chat-btn, .app .board-table td.col-name .row-menu,
  .app .board-table .cell-empty-people, .app .ghead-in .group-menu { opacity: 1; }
}
.bp-close { display: none; }
/* the summary's tile: 10px, r2 (a mark under 12px, spec §3), in the
   state's own chip colour, and the chip edge every data-coloured fill of
   6px or more wears (D45 §0c, spec §14.10) */
.p112-tile {
  display: inline-block; flex: none; width: 10px; height: 10px; border-radius: 2px;
  background: var(--lab); box-shadow: var(--chip-ring);
}
.bsum-s > i.p112-tile { width: 10px; height: 10px; border-radius: 2px; margin: 0; }
/* the kanban column's title is the same object as the summary's word: a tile
   where it had a dot */
.kanban-col-head::before { border-radius: 2px; width: 10px; height: 10px; }

/* ---- the new markup under 701px: the phone keeps the layout it had ------ */
@media (max-width: 700px) {
  .bp-close { display: inline-flex; margin-left: 4px; }
  .rail-lab, .p112-crumb, .p112-bar, .p112-sheets, .p112-vr, .p112-kbd, .p112-arr, .p112-people-slot { display: none; }
  .board-table thead.p112-colh { display: none; }
}
/* paper: the per-group titles print; the pinned copy does not */
@media print { .board-cols { display: none; } }

/* ---- phones: the chrome is solid, restated for THIS block's glass (the
   rail, the panel and the dock carry the picture's blur above 701px only,
   but the reset is written out so no reading of the cascade can put a blur
   under a phone's fixed bar). KEEP ANY NEW GLASS RULE ABOVE THIS. ---- */
@media (max-width: 700px) {
  .app .rail, .app .sidebar.bp, .app .board-head .p112-dock {
    -webkit-backdrop-filter: none; backdrop-filter: none;
  }
}

/* 2026-09-23, walked at 1920 on a register-shaped board: the name column's
   title printed the board's item label as stored ("document"). Display only -
   the label stays as stored because other surfaces lower-case it in a
   sentence ("Search 28 documents"). */
.p112-colh th.col-name .th-label { display: inline-block; }
.p112-colh th.col-name .th-label::first-letter { text-transform: uppercase; }
/* a column title never breaks inside a word ("Fa mil y" at 1920, 2026-09-23):
   fitWidths now reserves the title's full width, so a whole word always fits */
.app .board-table thead.p112-colh .col-head, .app .board-cols .col-head { overflow-wrap: normal; word-break: normal; }

/* 2026-09-23, owner screenshots.
   1. "two very dark gray lines": the rail's 0 8px 24px shadow darkened a
      ~20px strip of page beside the rail. The rail casts nothing sideways
      now; its glass rim is its edge, and a negative-spread shadow stays
      tucked under its bottom end only. */
@media (min-width: 701px) {
  /* phase-115: the two .app .rail shadow lines that stood here moved, as
     they were, into the phase-115 block with the sidebar they belong to */
  /* 2. the dock search on focus: a second box appeared inside the rainbow
        edge (an older rule swaps the field's ground on focus-within, and the
        input kept its own), the edge's corners did not follow the field's,
        and "Search 487 documents" was cut off at 200px. */
  .app .board-head .p112-dock .p112-srch { flex: 0 1 260px; width: 260px; min-width: 220px; }
  .app .board-head .p112-dock .p112-srch:focus-within { background: var(--p112-well); border-color: transparent; }
  .app .board-head .p112-dock .p112-srch .board-filter,
  .app .board-head .p112-dock .p112-srch .board-filter:focus { background: transparent; border: 0; box-shadow: none; outline: none; padding: 0; }
  .app .board-head .p112-dock .p112-srch::before { border-radius: 16px; }
}

/* 2026-09-23, owner screenshot of the boards panel: the "Find a board" field
   drew a second, darker box inside itself (the input kept the generic
   .board-filter ground) and its outer well ran past the panel's own margins.
   One field, inside the panel's 4px side inset, the input transparent, and
   the focus edge on the same 12px corners as the field. */
.sidebar .bp-find { box-sizing: border-box; width: auto; align-self: stretch; margin: 0 4px 8px; max-width: calc(100% - 8px); }
.sidebar .bp-find:focus-within { background: var(--p112-well, var(--surface-2)); border-color: transparent; box-shadow: none; }
.sidebar .bp-find .board-filter,
.sidebar .bp-find .board-filter:focus { background: transparent; border: 0; box-shadow: none; outline: none; padding: 0; height: auto; }
.sidebar .bp-find::before { border-radius: 12px; }

/* 2026-09-23, the owner on the boards panel: "I can't even understand" - the
   workspace headings read as two more board names (12px regular grey beside
   13.5px regular grey). Headings are now clearly headings - 11.5px semibold,
   a hairline above every workspace but the first, more air before the group -
   and the board names beneath them are brighter and indented, so each list
   plainly belongs to the heading above it. */
.sidebar .bp-wk { font: 600 11.5px/16px var(--font-body); letter-spacing: .01em; color: var(--p112-t3, var(--text-3)); padding: 16px 12px 6px; }
.sidebar .bp-ws + .bp-ws { border-top: 1px solid var(--p112-hair, var(--border)); margin-top: 8px; }
.sidebar .bp-list > .bp-ws:first-child > .bp-wk { padding-top: 8px; }
.sidebar .bp-row { padding-left: 20px; color: var(--p112-t1, var(--text-1)); }

/* 2026-09-23, the owner: "make the rainbow edge on the menu at the bottom like
   the one on the landing page - the one you added seems subtle". The landing's
   rainbow is its flowing headline (.flow in welcome.css: the five logo hues
   twice across a 200% tile, sliding one run every 8s). The dock's edge is now
   that same flow, drawn as a 1.5px rim around the whole capsule (masked to the
   border, so nothing inside is tinted), instead of a faint still line along
   the top. UI palette only (logo hues). Still under reduced motion. */
/* phase-154: the tile runs out and back (red to blue to red) so blue never
   meets red (purple); one pass now moves the whole tile (200%, was 100%).
   The 16s infinite rule below is switched off by phase-112's `animation: none`
   further down; the live use is the one-shot `.sweep`, whose duration is
   doubled there so it keeps its old speed. */
@keyframes p112Flow { from { background-position: 0% 0; } to { background-position: 200% 0; } }
@media (min-width: 701px) {
  .app .board-head .p112-dock::before {
    left: 0; right: 0; top: 0; bottom: 0; height: auto; border-radius: inherit; padding: 1.5px;
    background: linear-gradient(90deg,
      var(--logo-1) 0%, var(--logo-2) 12.5%, var(--logo-3) 25%, var(--logo-4) 37.5%, var(--logo-5) 50%,
      var(--logo-4) 62.5%, var(--logo-3) 75%, var(--logo-2) 87.5%, var(--logo-1) 100%);
    background-size: 200% 100%; background-repeat: repeat-x;
    -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor;
    mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask-composite: exclude;
    filter: none; opacity: .9;
    animation: p112Flow 16s linear infinite;
  }
  :root:not([data-theme="dark"]) .app .board-head .p112-dock::before { opacity: .8; }
}
@media (prefers-reduced-motion: reduce) { .app .board-head .p112-dock::before { animation: none; } }

/* 2026-09-23, the owner: "all have broken look to it". The add-row field drew
   its own outlined box, cut its hint off ("Name a item and press En") and said
   "a item". At rest it is plain text on the ground, wide enough for its hint,
   with no box; focus gives it the same field ground as every other field
   (and the text controls' rainbow edge, which is theirs already). */
@media (min-width: 701px) {
  .app .addbar .add-item-input {
    flex: 0 1 280px; width: 280px; min-width: 220px; height: 32px; padding: 0 12px;
    border: 0; border-radius: 12px; background: transparent; box-shadow: none;
    font: 400 13.5px/20px var(--font-body); color: var(--p112-t1, var(--text-1));
  }
  .app .addbar .add-item-input::placeholder { color: var(--p112-t3, var(--text-3)); }
  .app .addbar .add-item-input:hover { background: var(--p112-hover, var(--hover-ground)); }
  .app .addbar .add-item-input:focus { background: var(--p112-well, var(--surface-2)); outline: none; }
}

/* 2026-09-23, the owner on the full flowing rim: "woah - just the top how it
   was". The flowing five-hue rainbow stays (the landing's .flow, sliding
   every 8s) but only along the TOP edge, as the first version had it, 1.5px,
   fading out at both ends; the rim round the rest of the dock is gone. */
@media (min-width: 701px) {
  .app .board-head .p112-dock::before {
    left: 6%; right: 6%; top: -1px; bottom: auto; height: 1.5px; padding: 0; border-radius: 2px;
    -webkit-mask: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent); -webkit-mask-composite: source-over;
    mask: linear-gradient(90deg, transparent, #000 14%, #000 86%, transparent); mask-composite: add;
    opacity: 1; filter: drop-shadow(0 0 2px rgba(255, 255, 255, .18));
  }
}

/* 2026-09-23, the owner: "should the tiles be removed? or the entire UI feels
   too much? ... I do not want to make the same mistake again of overdoing it".
   The designer's verdict, agreed ("proceed"): remove nothing, but nothing
   moves while he works.
   1. The sheets drift for ~10s when a board opens and glide to their places
      (the animation ENDS at the resting transform, so a later repaint cannot
      jump), and freeze at once on scroll, typing, a click or focus (.still). */
@keyframes p112Settle {
  from { transform: translate(var(--dx), var(--dy)) rotate(calc(var(--r) + var(--dr))) skewX(var(--k)); }
  to { transform: rotate(var(--r)) skewX(var(--k)); }
}
:root { --dur-settle: 10s; } /* one board-open settle; not a transition */
.p112-sheets.drift > i { animation: p112Settle var(--dur-settle) var(--ease-out) both; }
.p112-sheets.still > i { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .p112-sheets.drift > i { animation: none; } }
/* 2. The dock's five-hue line is a still 1px line along its top, and sweeps
      once (~1.2s) when the view changes. */
@media (min-width: 701px) {
  .app .board-head .p112-dock::before { height: 1px; animation: none; }
  .app .board-head .p112-dock.sweep::before { animation: p112Flow calc(var(--dur-scene-5) * 2) var(--ease-out) 1 both; }
}

/* ==================================================================== phase-113: the redesign, part 2a
   The item card, the kanban, the calendar, the timeline/dashboard/files
   restyle, many rows at once, the right-click row menu and the group totals
   row, built to the four board pictures in design/approved-2026-09-23/ and to
   POLISH-SPEC.md. The material is phase-112's and nothing else: the
   --p112-* tokens (ground, inks t1-t4, hair, hover, on, well, glass, rim, fx)
   and its type scale (44/48, 24/32, 15/20, 13.5/20, 12/16, 11/14). No new
   glass, no new colour.
     1. THE ITEM CARD   a glass pane on the right (panels.js SD.openItem)
     2. THE DOCK STEPS ASIDE while the card is open (html.p113-icard)
     3. THE SELECTION   the gutter tick box and the selection bar
     4. THE TOTALS ROW  the group's counts and sums under its columns
     5. THE KANBAN      columns by label, cards on the ground
     6. THE CALENDAR    the prev/Today/next strip, events that never clip
     7. TIMELINE, DASHBOARD, FILES: the same inks, sizes and radii
     8. NO DOTS         the last round marks in these views, gone
   Every desktop surface sits inside `@media (min-width: 701px)`; the block
   ends with its own phone reset after its glass (css.md, the two legal
   shapes), so under 701px the chrome stays solid. Transitions name the one
   curve token. The only keyframe added animates transform and opacity. ---- */

/* ---- tokens -------------------------------------------------------------- */
:root {
  /* the item card's width: 560 at 1920, 480 at 1440 and below (spec §3) */
  --p113-icw: clamp(480px, 29.2vw, 560px);
}

/* ---- 1. THE ITEM CARD, at every width (the phone keeps the bottom sheet
   every .modal becomes under 701px; this is only the card's own layout) --- */
.p113-ic { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
.p113-ic-head { display: flex; align-items: center; gap: 8px; flex: none; }
.p113-ic-crumb {
  flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
  font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3));
}
.p113-ic-crumb > span { overflow-wrap: anywhere; }
.p113-ic-crumb > .p113-sl { color: var(--p112-t4, var(--text-3)); }
.p113-ic-up { border: 0; padding: 0; background: none; font: inherit; color: inherit; cursor: pointer; overflow-wrap: anywhere; text-align: left; }
.p113-ic-up:hover { color: var(--p112-t1, var(--text-1)); }
.p113-ic-acts { display: inline-flex; align-items: center; gap: 4px; flex: none; margin-left: auto; }
.p113-ic-acts .icon-btn {
  width: 32px; height: 32px; padding: 0; border-radius: 12px; justify-content: center;
  color: var(--p112-t3, var(--text-3)); background: none;
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.p113-ic-acts .icon-btn:hover { background: var(--p112-hover, var(--hover-ground)); color: var(--p112-t1, var(--text-1)); }
.p113-ic-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: 0 -8px; padding: 0 8px; }
.p113-ic .ic-head { display: block; padding: 0; margin: 0; border: 0; }
/* the title: Title 24/32 (spec §4), full, wrapping, never ellipsised */
.p113-ic .ic-head h3 {
  margin: 16px 0 0 -6px; padding: 0 6px; border-radius: 8px;
  font: 600 24px/32px var(--font-display); letter-spacing: -.02em; color: var(--p112-t1, var(--text-1));
  white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
}
.p113-ic-status { display: flex; margin: 12px 0 0; gap: 0; }
.p113-ic-status .cell.inline { display: inline-flex; align-items: center; min-height: 24px; padding: 0; border-radius: 8px; cursor: pointer; }
.p113-ic-status .status-pill.p112-plain { padding: 0 2px; }
.p113-ic-status .status-pill.unset::after { content: "No status"; font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3)); }
/* the fields: label Small t3 in a 128 column, the value Body t1, rows 28
   (spec §14.15). Each value is the table's own editor. */
.p113-ic-fields { display: flex; flex-direction: column; gap: 0; margin: 20px 0 0; }
.p113-ic-fields .iv-row { display: grid; grid-template-columns: 128px minmax(0, 1fr); align-items: center; column-gap: 12px; min-height: 28px; }
.p113-ic-fields .iv-label {
  width: auto; font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3));
  white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
}
.p113-ic-fields .cell.inline {
  display: flex; align-items: center; justify-content: flex-start; min-width: 0; min-height: 28px;
  margin-left: -8px; padding: 4px 8px; border-radius: 8px; text-align: left; cursor: pointer;
  font: 400 13.5px/20px var(--font-body); color: var(--p112-t1, var(--text-1)); font-variant-numeric: tabular-nums;
  transition: background var(--dur-micro-1) var(--ease-out);
}
.p113-ic-fields .cell.inline:hover { background: var(--p112-hover, var(--hover-ground)); }
.p113-ic .cell.inline .cell-text, .p113-ic .cell.inline .cell-link, .p113-ic .cell.inline .cell-date {
  white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; max-width: none; text-align: left;
}
.p113-ic-meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: 16px 0 0;
  font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3));
}
.p113-ic-link {
  margin-left: auto; display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 10px;
  border: 0; border-radius: 8px; background: var(--p112-well, var(--surface-2)); cursor: pointer;
  font: 500 12px/16px var(--font-body); color: var(--p112-t2, var(--text-2));
  transition: color var(--dur-micro-1) var(--ease-out);
}
.p113-ic-link:hover { color: var(--p112-t1, var(--text-1)); }
.p113-ic-link > .ic { width: 14px; height: 14px; }
/* the tabs: Small, t3 at rest, t1 with a 2px t1 underline when on */
.p113-ic-tabs { display: flex; gap: 20px; margin: 20px 0 0; border-bottom: 1px solid var(--p112-hair, var(--border-soft)); }
.p113-ic-tabs .item-tab {
  position: relative; display: inline-flex; align-items: center; gap: 4px; height: 32px; padding: 0; margin: 0;
  border: 0; border-radius: 0; background: none; cursor: pointer;
  font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3));
  transition: color var(--dur-micro-1) var(--ease-out);
}
.p113-ic-tabs .item-tab:hover { color: var(--p112-t1, var(--text-1)); }
.p113-ic-tabs .item-tab.active { color: var(--p112-t1, var(--text-1)); font-weight: 500; border: 0; }
.p113-ic-tabs .item-tab.active::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 2px; border-radius: 2px; background: var(--p112-t1, var(--text-1)); }
.p113-ic-tabs .pill-count { background: none; padding: 0; min-width: 0; font: inherit; font-weight: 400; color: var(--p112-t3, var(--text-3)); }
.p113-ic-body { padding: 16px 0 8px; outline: none; }
/* the conversation: avatar 24, name Body 600 t1, the time Small t3, the text
   Body t1 under the name */
.p113-ic .update-list { display: flex; flex-direction: column; gap: 16px; }
.p113-ic .update { background: none; border: 0; border-radius: 0; padding: 0; margin: 0; box-shadow: none; }
.p113-ic .update-head { display: flex; align-items: center; gap: 10px; margin: 0; font: 600 13.5px/20px var(--font-body); color: var(--p112-t1, var(--text-1)); }
.p113-ic .update-head .muted { font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3)); }
.p113-ic .update-body { margin: 2px 0 0 34px; font: 400 13.5px/20px var(--font-body); color: var(--p112-t1, var(--text-1)); white-space: pre-wrap; overflow-wrap: anywhere; }
.p113-ic .like-btn { margin: 6px 0 0 34px; height: 24px; padding: 0 8px; border-radius: 8px; font: 400 12px/16px var(--font-body); }
.p113-ic .p113-at { font-weight: 500; color: var(--brand); }
.p113-ic .muted.center, .p113-none { margin: 8px 0; font: 400 13.5px/20px var(--font-body); color: var(--p112-t3, var(--text-3)); text-align: left; }
.p113-ic .activity-list { max-height: none; overflow: visible; }
/* the Files tab: the list, the file being read, then its chronology. A
   name is never cut: it wraps. */
.p113-ic .item-attach, .p113-ic .item-revs { gap: 8px; }
.p113-ic .item-revs { margin-top: 16px; }
.p113-ic .ic-sec-t { font: 600 15px/20px var(--font-display); letter-spacing: 0; text-transform: none; color: var(--p112-t1, var(--text-1)); }
.p113-ic .attach-name, .p113-ic .docrev-file, .p113-ic .ic-viewer-head b { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
.p113-ic.drop-hover { box-shadow: inset 0 0 0 1.5px var(--brand); border-radius: 16px; }
.p113-viewer { margin-top: 12px; border-radius: 16px; overflow: hidden; background: var(--p112-well, var(--surface-2)); }
.p113-viewer:has(> .ic-viewer:empty) { display: none; }
.p113-viewer .ic-viewer-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 8px 12px; border: 0; background: none; }
.p113-viewer .ic-viewer-acts { margin-left: auto; display: inline-flex; gap: 4px; }
.p113-viewer .ic-viewer { display: flex; flex-direction: column; height: 60vh; min-height: 320px; border: 0; background: none; }
/* the Subitems tab */
.p113-subs { display: flex; flex-direction: column; gap: 2px; }
.p113-sub { display: flex; align-items: center; gap: 8px; min-height: 36px; padding: 4px 4px 4px 8px; border-radius: 12px; transition: background var(--dur-micro-1) var(--ease-out); }
.p113-sub:hover { background: var(--p112-hover, var(--hover-ground)); }
.p113-sub-name { flex: 1 1 auto; min-width: 0; border: 0; padding: 0; background: none; cursor: pointer; text-align: left; font: 400 13.5px/20px var(--font-body); color: var(--p112-t1, var(--text-1)); overflow-wrap: anywhere; }
.p113-sub.done .p113-sub-name { color: var(--p112-t3, var(--text-3)); text-decoration: line-through; }
.p113-sub-add { align-self: flex-start; margin-top: 8px; }
/* the compose box at the foot: the well, the textarea on it, Post. The fan
   ring is the WRAPPER's (the dock search's shape, .board-search::before), so
   the textarea itself paints nothing and nothing is drawn twice. */
.p113-ic-compose { flex: none; padding-top: 12px; }
.p113-ic-compose .vo-none { margin: 0; font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3)); }
.p113-compose {
  position: relative; display: flex; align-items: flex-end; gap: 8px; margin: 0;
  padding: 6px 6px 6px 14px; border-radius: 16px; background: var(--p112-well, var(--surface-2));
}
.p113-compose::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; padding: var(--fan-ring-w, 1.5px); pointer-events: none; opacity: 0;
  background: var(--fan-ring);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); mask-composite: exclude;
  transition: opacity var(--dur-micro-2) var(--ease-out);
}
.p113-compose:focus-within::before { opacity: 1; animation: ringSpin 2.6s linear infinite; }
.p113-compose textarea, .p113-compose textarea:focus {
  flex: 1 1 auto; min-width: 0; min-height: 28px; max-height: 120px; margin: 0; padding: 4px 0; resize: none;
  border: 0; border-radius: 0; background: transparent none; box-shadow: none; outline: none; animation: none;
  font: 400 13.5px/20px var(--font-body); color: var(--p112-t1, var(--text-1));
}
.p113-compose textarea::placeholder { color: var(--p112-t3, var(--text-3)); }
.p113-compose .p113-send { flex: none; height: 32px; padding: 0 12px; gap: 6px; border-radius: 12px; }
@media (prefers-reduced-motion: reduce) { .p113-compose::before { animation: none !important; transition: none !important; } }
.p113-at-pop .menu-item { gap: 8px; }

@media (min-width: 701px) {
  /* the pane: right 16, top 16, bottom 16, r24, pad 24, the boards panel's
     film and rim (--p112-glass, --p112-rim). Its filter is the glass
     primitive's own layer (the card carries lg lg-regular, panels.js),
     with the primitive's chain pointed at the boards panel's --p112-fx BY
     NAME: joined, never copied (D46 §3d). It slides in 24px on arrival and
     never fades itself - a glass surface that takes opacity cuts its own
     backdrop (welcome.css:887). The scrim under it neither fades. */
  #modal-root .modal-overlay:has(> .p113-icm) {
    display: block; padding: 0; opacity: 1; transition: none;
    background: rgba(5, 6, 9, .24);
  }
  :root:not([data-theme="dark"]) #modal-root .modal-overlay:has(> .p113-icm) { background: rgba(20, 26, 43, .12); }
  .modal.p113-icm {
    --lg-fx: var(--p112-fx);
    position: fixed; top: 16px; right: 16px; bottom: 16px; left: auto;
    width: var(--p113-icw); max-width: calc(100vw - 32px); max-height: none; margin: 0;
    display: flex; flex-direction: column; padding: 24px; overflow: hidden;
    border: 0; border-radius: 24px;
    background: var(--p112-glass);
    box-shadow: var(--p112-rim), 0 26px 80px rgba(0, 0, 0, .5), 0 4px 14px rgba(0, 0, 0, .3);
    transform: translateX(24px);
    transition: transform var(--dur-micro-3) var(--ease-out);
  }
  :root:not([data-theme="dark"]) .modal.p113-icm { box-shadow: var(--p112-rim), 0 18px 50px rgba(13, 15, 19, .14), 0 4px 14px rgba(13, 15, 19, .07); }
  .modal-overlay.show > .modal.p113-icm { transform: none; }
  .modal-overlay.p113-swap > .modal.p113-icm { transition: none; }
  /* the rim is --p112-rim on the pane itself; the primitive's and the
     modal's own specular rim stand down rather than drawing a second edge */
  .modal.p113-icm::after { display: none; }
  .modal.p113-icm > .p113-ic { flex: 1 1 auto; }
}
/* at 1440 and wider the board stays in use behind the card: no scrim, and
   the page takes every click the card does not cover */
@media (min-width: 1440px) {
  #modal-root .modal-overlay:has(> .p113-icm),
  :root:not([data-theme="dark"]) #modal-root .modal-overlay:has(> .p113-icm) { background: none; pointer-events: none; }
  .modal.p113-icm { pointer-events: auto; }
}

/* ---- 2. THE DOCK STEPS ASIDE. With the card open at 1440+, the dock (and
   the selection bar, which stands in its place) centres on the part of the
   content column the card leaves uncovered: x = (152 + 100vw - 16 - W - 24)
   / 2, which is its resting x (50% + 52px) moved left by W/2 - 4px (x736 at
   1920, spec §3). Moved by transform, so nothing re-lays out. Below 1440 the
   card is modal behind its scrim and the dock simply steps out of view. --- */
@media (min-width: 701px) {
  .app .board-head .p112-dock {
    transition: transform var(--dur-micro-3) var(--ease-out), opacity var(--dur-micro-2) var(--ease-out), visibility 0s linear 0s;
  }
}
@media (min-width: 1440px) {
  html.p113-icard .app .board-head .p112-dock {
    transform: translateX(calc(-50% - (var(--p113-icw) / 2 - 4px)));
    /* phase-115: 336 = the content column's left edge, sidebar open */
    max-width: calc(100vw - var(--p113-icw) - 40px - 336px - 16px);
  }
}
@media (min-width: 701px) and (max-width: 1439px) {
  html.p113-icard .app .board-head .p112-dock { opacity: 0; visibility: hidden; pointer-events: none; }
}

/* ---- 3. MANY ROWS AT ONCE ------------------------------------------------ */
/* the tick box: 16px in the gutter left of the name, on the row's hover, on
   keyboard focus, always once anything is ticked, always on a touch screen */
@media (min-width: 701px) {
  .app .board-table td.col-name .p113-chk {
    position: absolute; left: 7px; top: 50%; margin-top: -8px; z-index: 3;
    display: inline-flex; align-items: center; justify-content: center;
    width: 16px; height: 16px; padding: 0; border: 0; border-radius: 6px; cursor: pointer;
    background: none; color: transparent; box-shadow: inset 0 0 0 1.5px var(--p112-t3);
    opacity: 0; transition: opacity var(--dur-micro-1) var(--ease-out);
  }
  .app .board-table td.col-name .p113-chk > .ic { width: 12px; height: 12px; stroke-width: 2.4; }
  .app .board-table .item-row:hover td.col-name .p113-chk,
  .app .board-table td.col-name .p113-chk:focus-visible,
  html.p113-selon .app .board-table td.col-name .p113-chk { opacity: 1; }
  .app .board-table td.col-name .p113-chk[aria-checked="true"] { background: #0070c1; color: #fff; box-shadow: none; opacity: 1; }
  /* a ticked row: the selected fill, opaque so the frozen name column stays
     solid over what scrolls under it (spec §7: brand at 8% dark, 7% light) */
  [data-theme="dark"] .app .item-row.p113-on, [data-theme="dark"] .app .item-row.p113-on td, [data-theme="dark"] .app .item-row.p113-on td.col-name { background: color-mix(in srgb, #0070c1 8%, var(--p112-ground)); }
  :root:not([data-theme="dark"]) .app .item-row.p113-on, :root:not([data-theme="dark"]) .app .item-row.p113-on td, :root:not([data-theme="dark"]) .app .item-row.p113-on td.col-name { background: color-mix(in srgb, #0070c1 7%, var(--p112-ground)); }
  .app .item-row.p113-on td.col-name::after, [data-theme="dark"] .app .item-row.p113-on td.col-name::after { background: none; }
}
@media (min-width: 701px) and (hover: none) {
  .app .board-table td.col-name .p113-chk { opacity: 1; }
}
@media (max-width: 700px) {
  /* the phone selects through its cards: Select in the tool row, or a 500ms
     hold on a card (phase-147, D52; the "part 3" this line promised); its
     table layout keeps the name cell it had and has no tick box */
  .p113-chk { display: none; }
}
/* the selection bar (board-crm-table.png): a second .p112-dock in the board
   head (board.js selBar), so its place, size, glass, shadow and still
   five-hue top line are the dock's own rules, not a copy. Here: its
   contents, and the real dock stepping back while it is up. It arrives with
   a 180ms rise when the first row is ticked (a response to his action,
   OWNER-CHECKLIST 39) and never moves again. */
@keyframes p113BarIn { from { opacity: 0; transform: translateX(-50%) translateY(8px); } to { opacity: 1; transform: translateX(-50%); } }
.p113-selbar { display: flex; align-items: center; gap: 4px; }
.p113-seln {
  flex: none; min-width: 24px; height: 24px; padding: 0 6px; border-radius: 8px;
  background: #0070c1; color: #fff; font: 600 12px/24px var(--font-body); text-align: center; font-variant-numeric: tabular-nums;
}
.p113-selt { flex: none; margin: 0 8px 0 4px; font: 600 13.5px/20px var(--font-body); color: var(--p112-t1, var(--text-1)); white-space: nowrap; }
.p113-sb {
  flex: none; display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: 40px; padding: 0 12px;
  border: 0; border-radius: 16px; background: none; cursor: pointer; white-space: nowrap;
  font: 400 13.5px/20px var(--font-body); color: var(--p112-t2, var(--text-2));
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.p113-sb:hover { background: var(--p112-hover, var(--hover-ground)); color: var(--p112-t1, var(--text-1)); }
.p113-sb.p113-danger, .p113-sb.p113-danger:hover { color: var(--danger); }
.p113-selx { width: 40px; padding: 0; color: var(--p112-t3, var(--text-3)); }
.p113-selbar .p112-vr { flex: none; width: 1px; height: 24px; margin: 0 4px; background: var(--p112-vr, var(--line-2)); }
@media (min-width: 701px) {
  .app .board-head .p112-dock.p113-selbar {
    z-index: 31; padding: 8px 8px 8px 16px; gap: 4px; overflow-x: auto; scrollbar-width: none;
    animation: p113BarIn var(--dur-micro-2) var(--ease-out) backwards;
  }
  .app .board-head .p112-dock.p113-selbar::-webkit-scrollbar { display: none; }
  /* the dock gives the bar its place: a 180ms crossfade (spec §10) */
  html.p113-selon .app .board-head .p112-dock:not(.p113-selbar) {
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: transform var(--dur-micro-3) var(--ease-out), opacity var(--dur-micro-2) var(--ease-out), visibility 0s linear var(--dur-micro-2);
  }
}
@media (prefers-reduced-motion: reduce) { .app .board-head .p112-dock.p113-selbar { animation: none; } }
@media (max-width: 700px) {
  /* solid on a phone, above the bottom bar, scrolling sideways if it must */
  .app .board-head .p112-dock.p113-selbar, .p113-selbar {
    position: fixed; left: 8px; right: 8px; z-index: 60;
    bottom: calc(var(--bar-h, 56px) + 8px);   /* phase-166 (N7): --bar-h already carries the safe area */
    min-height: 48px; padding: 4px 4px 4px 12px; border-radius: 16px; overflow-x: auto;
    background: var(--surface-1); box-shadow: inset 0 0 0 1px var(--line-2), 0 8px 24px rgba(0, 0, 0, .25);
  }
  .p113-selbar .p113-sb { height: 40px; }
}

/* ---- 4. THE GROUP'S TOTALS ROW (board-inventory.png): the count at the
   name's text edge in t3, every total centred under its column in t1 at 500,
   on a quiet band ------------------------------------------------------- */
@media (min-width: 701px) {
  .app .board-table .summary-row td {
    height: 40px; padding: 0 6px; background: var(--p112-hover); border: 0;
    font: 500 13.5px/20px var(--font-body); color: var(--p112-t1); text-align: center; font-variant-numeric: tabular-nums;
    white-space: normal; overflow-wrap: anywhere;
  }
  .app .board-table .summary-row td.p113-sum-n {
    position: sticky; left: 0; z-index: 2; text-align: left; padding-left: 36px;
    font-weight: 400; color: var(--p112-t3);
  }
  .app .board-table .summary-row td.p113-sum-s { font-weight: 500; color: var(--p112-t1); }
}

/* ---- 5. THE KANBAN (board-crm-pipeline.png; spec §8, §14.18) ------------ */
@media (min-width: 701px) {
  .p113-kanban { gap: 16px; padding: 4px 0 24px; align-items: flex-start; }
  .p113-kanban .kanban-col { width: 320px; min-width: 320px; }
  .p113-kanban .kanban-col-head {
    display: flex; align-items: baseline; gap: 8px; margin: 0 0 12px; padding: 0 4px 12px;
    border-bottom: 1px solid var(--p112-hair); background: none;
  }
  /* the column IS the status: no tile, no dot beside its name */
  .p113-kanban .kanban-col-head::before { display: none; }
  .p113-kanban .kanban-col-head > .p113-kh-t {
    font: 600 15px/20px var(--font-display); color: var(--p112-t1);
    white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; min-width: 0;
  }
  .p113-kanban .p113-kh-n, .p113-kanban .p113-kh-sum { flex: none; font: 400 12px/16px var(--font-body); color: var(--p112-t3); font-variant-numeric: tabular-nums; }
  .p113-kanban .p113-kh-sum { margin-left: auto; }
  .p113-kanban .kanban-list { display: flex; flex-direction: column; gap: 8px; min-height: 120px; padding: 0; background: none; box-shadow: none; border-radius: 16px; }
  /* a card on the ground (the --surface-1 step above it) with the material's
     own hairline rim; r16, pad 14/16 */
  .p113-kanban .kanban-card {
    display: flex; flex-direction: column; gap: 2px; padding: 14px 16px; border-radius: 16px; text-align: left;
    transition: box-shadow var(--dur-micro-1) var(--ease-out);
  }
  .p113-kanban .kanban-card:hover { transform: none; box-shadow: var(--elev-1); --lg-rim-a: 1.4; }
  .p113-kanban .kanban-card.dragging { opacity: .4; }
  .p113-kanban .kanban-card-name { margin: 0; font: 500 13.5px/20px var(--font-body); color: var(--p112-t1); overflow-wrap: anywhere; }
  .p113-kanban .p113-kc-sub { font: 400 12px/16px var(--font-body); color: var(--p112-t3); overflow-wrap: anywhere; }
  .p113-kanban .kanban-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px; margin-top: 10px; }
  .p113-kanban .p113-kc-num { font: 500 13.5px/20px var(--font-body); color: var(--p112-t2); font-variant-numeric: tabular-nums; }
  .p113-kanban .p113-kc-key .cell-date { font: 400 12px/16px var(--font-body); color: var(--p112-t2); }
  .p113-kanban .p113-kc-key .cell-date.overdue { color: var(--danger); }
  .p113-kanban .kanban-meta { gap: 8px; }
  .p113-kanban .kanban-meta .avatar { font: 600 11px/1 var(--font-body) !important; }
  .p113-kanban .kanban-files { font: 400 12px/16px var(--font-body); color: var(--p112-t3); gap: 4px; }
  .p113-kanban .kanban-empty { padding: 24px 0; border: 1px dashed var(--p112-hair); border-radius: 16px; font: 400 12px/16px var(--font-body); color: var(--p112-t3); }
}

/* ---- 6. THE CALENDAR (board-operations-calendar.png; spec §14.13) ------- */
@media (min-width: 701px) {
  /* the 32px strip above the weekdays: previous, Today, next, the month */
  .p113-strip { display: flex; align-items: center; gap: 4px; min-height: 32px; margin: 4px 0 12px; flex-wrap: wrap; }
  .p113-strip .btn.p113-nav { width: 32px; height: 32px; padding: 0; border-radius: 12px; justify-content: center; color: var(--p112-t2); }
  .p113-strip .btn.p113-today { height: 32px; padding: 0 12px; border-radius: 12px; font: 500 12px/16px var(--font-body); color: var(--p112-t2); }
  .p113-strip .btn:hover:not(:disabled) { background: var(--p112-hover); color: var(--p112-t1); }
  .p113-strip .p113-month { margin: 0 8px; min-width: 0; text-align: left; font: 600 15px/20px var(--font-display); letter-spacing: 0; color: var(--p112-t1); }
  .p113-strip .cal-note { margin-left: auto; font: 400 12px/16px var(--font-body); color: var(--p112-t3); }
  .p113-cal .cal-grid { border-radius: 16px; }
  .p113-cal .cal-dow { padding: 8px 0; font: 500 12px/16px var(--font-body); letter-spacing: 0; text-transform: none; color: var(--p112-t3); text-align: center; }
  .p113-cal .cal-cell { min-height: 120px; padding: 8px; }
  .p113-cal .cal-cell.today { background: var(--lg-ground, var(--surface-1)); }
  .p113-cal .cal-day { display: flex; align-items: center; margin: 0 0 6px; font: 400 12px/16px var(--font-body); color: var(--p112-t3); font-variant-numeric: tabular-nums; }
  /* today: a 24px brand plate, r8, the day in white 12/600 (spec §7) */
  .p113-cal .cal-cell.today .p113-dn {
    display: inline-flex; align-items: center; justify-content: center; min-width: 24px; height: 24px; padding: 0 4px;
    border-radius: 8px; background: #0070c1; color: #fff; font: 600 12px/16px var(--font-body);
  }
  /* an event is NEVER cut off: it wraps, in full (checklist 10) */
  .p113-cal .cal-item {
    display: block; width: 100%; margin: 0 0 4px; padding: 4px 8px; border: 0; border-radius: 8px; cursor: pointer;
    white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; text-align: left;
    font: 400 12px/16px var(--font-body); color: var(--p112-t1);
    background: var(--p112-well); box-shadow: inset 0 0 0 1px var(--p112-hair);
    transition: background var(--dur-micro-1) var(--ease-out);
  }
  .p113-cal .cal-item::before { display: none; }
  .p113-cal .cal-item.p113-ev:hover { background: var(--p112-hover); }
  /* chip-edge: exempt - tinted capsule, its rim is the chip (POLISH-SPEC §15,
     the status capsule's own recipe: fill 14%, rim 60% light / 32% dark,
     text t1 light and --chip-ink's lift in dark) */
  .p113-cal .cal-item.p113-ev-cap {
    background: color-mix(in srgb, var(--lab) 14%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lab) 60%, transparent);
    color: var(--p112-t1);
  }
  [data-theme="dark"] .p113-cal .cal-item.p113-ev-cap {
    color: color-mix(in srgb, var(--lab) 66%, #fff);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lab) 32%, transparent);
  }
  .p113-cal .cal-item.p113-ev-cap:hover { background: color-mix(in srgb, var(--lab) 22%, transparent); }
  .p113-cal .cal-more { display: inline-flex; padding: 2px 8px; border-radius: 8px; font: 500 12px/16px var(--font-body); color: var(--p112-t2); }
  .p113-cal .cal-more:hover { background: var(--p112-hover); color: var(--p112-t1); }
  .p113-cal .cal-gap { font: 400 13.5px/20px var(--font-body); color: var(--p112-t3); }
}

/* ---- 7. TIMELINE, DASHBOARD, FILES: the same inks, sizes and radii; no new
   layout. A name in the timeline wraps instead of clipping. ------------- */
@media (min-width: 701px) {
  .timeline-view .tl-row { height: auto; min-height: 40px; }
  .timeline-view .tl-row > .tl-label {
    align-self: stretch; display: flex; align-items: center; padding: 6px 12px 6px 0;
    font: 400 13.5px/20px var(--font-body); color: var(--p112-t1);
    white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere;
  }
  .timeline-view .tl-corner, .timeline-view .tl-day { font: 500 12px/16px var(--font-body); letter-spacing: 0; text-transform: none; color: var(--p112-t3); }
  .timeline-view .tl-scroll { border-radius: 16px; }
  .timeline-view .tl-bar { border-radius: 8px; font: 500 12px/16px var(--font-body); }
  .dash .dw, .dashboard .dw { border-radius: 24px; }
  .dw .dw-title, .dw h4, .dw header h3 { font: 600 15px/20px var(--font-display); letter-spacing: 0; text-transform: none; color: var(--p112-t1); }
  .dw .mi-name { font: 400 13.5px/20px var(--font-body); color: var(--p112-t1); }
  .dw .mi-meta { font: 400 12px/16px var(--font-body); color: var(--p112-t3); font-variant-numeric: tabular-nums; }
  .files-view .fv-card { border-radius: 16px; }
  .files-view .fv-doc-t { font: 400 12px/16px var(--font-body); color: var(--p112-t3); }
}
/* the tone the dashboard's due-date list used to put on a dot is on the date */
.mini-item .mi-meta.p113-bad { color: var(--danger); }
.mini-item .mi-meta.p113-warn { color: var(--warn); }

/* ---- 8. NO DOTS. A legend key and a colour swatch in these views are the
   summary's 10px square tile (r2, the chip edge), never a round dot; a
   group's colour in a menu is its 3x16 spine; the row menu opened at the
   pointer is the same menu. ------------------------------------------- */
.dw .dot, .dw .sch-dot, .ring-legend .dot, .legend-row .dot {
  width: 10px; height: 10px; border-radius: 2px; box-shadow: var(--chip-ring);
}
.p113-spine {
  display: inline-block; flex: none; width: 3px; height: 16px; border-radius: 2px;
  background: var(--gcolor, var(--text-3));
}
.menu-head > .p113-spine { margin-right: 2px; }
.p113-selmenu .menu-item { gap: 8px; }

/* ---- phones: the chrome is solid, restated for THIS block's glass (the
   item card and the selection bar carry the boards panel's blur above 701px
   only; the reset is written out so no reading of the cascade can put a blur
   under a phone's sheet). KEEP ANY NEW GLASS RULE ABOVE THIS. ---- */
@media (max-width: 700px) {
  .modal.p113-icm, .modal.p113-icm::before, .app .board-head .p112-dock.p113-selbar { -webkit-backdrop-filter: none; backdrop-filter: none; }
}

/* 2026-09-23, walked at 1920 against board-operations-calendar.png: every day
   was a filled surface-1 box on a hairline-coloured grid, so the month read as
   a wall of tiles. The picture's calendar is the page ground with hairlines
   between days - the data surface carries nothing (PLAN Part 1). */
@media (min-width: 701px) {
  .app .p113-cal .cal-grid { background: var(--p112-hair, var(--border)); border-radius: 0; overflow: visible; border-top: 1px solid var(--p112-hair, var(--border)); }
  .app .p113-cal .cal-cell,
  .app .p113-cal .cal-cell.out, .app .p113-cal .cal-cell.off, .app .p113-cal .cal-cell.empty { background: var(--p112-ground, var(--surface-0)); border-radius: 0; box-shadow: none; }
  .app .p113-cal .cal-dow, .app .p113-cal .cal-grid > .cal-wd, .app .p113-cal .cal-wk { background: var(--p112-ground, var(--surface-0)); }
}
/* the grid's line colour showed as a grey block where the month ends early:
   the lines are drawn by the days themselves now, not by the grid's ground */
@media (min-width: 701px) {
  .app .p113-cal .cal-grid { background: none; gap: 0; border-left: 1px solid var(--p112-hair, var(--border)); }
  .app .p113-cal .cal-grid > * { border-right: 1px solid var(--p112-hair, var(--border)); border-bottom: 1px solid var(--p112-hair, var(--border)); }
}

/* 2026-09-23: the head keeps one number - the board's total, quiet, on the
   title's baseline - and Board tools is quiet text that lights up (the dock's
   own glass highlight) only under the pointer, not a filled capsule. */
@media (min-width: 701px) {
  .app .board-head .p112-total { font: 400 13.5px/20px var(--font-body); color: var(--p112-t3, var(--text-3)); white-space: nowrap; align-self: baseline; margin-left: 2px; }
  .app .board-head .board-more { background: none; box-shadow: none; color: var(--p112-t2, var(--text-2)); }
  .app .board-head .board-more:hover { background: var(--p112-on, var(--hover-ground)); box-shadow: var(--p112-on-edge, none); color: var(--p112-t1, var(--text-1)); }
}
/* the phone keeps the total too, under its own head rules */

/* ---- phase-116: a search field answers focus with its CARET, not a ring.
   Owner, 2026-09-23, on a screenshot of "Find a board": "all the search boxes
   have that weird thing inside going on ... have the little stick change
   colors instead of the edge. would this be cleaner?" Yes, and here is why it
   looked wrong: the generic text-field rule at :2903/:2908 is
   `input:not([type=checkbox]):not(...)x5` = (0,5,1), (0,6,1) on :focus, so it
   OUTRANKED every `.board-search .board-filter { background: transparent }`
   written since (:1782 (0,2,0), :2911, :9758 (0,3,0)). The inner input kept
   painting its own surface-1 box and, on focus, its own conic border, INSIDE
   the wrapper's spinning ring: two frames, one of them a dark inset box.
   Fix: the selectors below restate the same five :not()s so they tie at
   (0,6,1)/(0,7,1) and win on order; the wrapper's ring is retired for search
   fields (it also spun while he typed, against RULE 39); focus shows as a
   brighter edge on the one box plus a caret that steps through the five logo
   hues, one hue per blink (Chrome's caret blinks ~1.06s). Every other text
   control (dialogs, renames, cells) KEEPS the fan ring - where several fields
   sit together the ring is what says which one is live.
   caret-color is a colour, not a layout or paint-heavy property: this is the
   same exception class as --ring-a (an animated custom property), not a
   breach of "keyframes animate transform and opacity only". Discrete stops
   written as pairs so no curve and no steps() timing is introduced (D36).
   Light theme deepens the yellow stop only: #ffcc00 on a white field is
   1.5:1 and the caret would vanish for one blink. ---- */
:root { --p116-c3: color-mix(in srgb, var(--logo-3) 72%, #000); }
[data-theme="dark"] { --p116-c3: var(--logo-3); }
@keyframes p116Caret {
  0%, 19.99% { caret-color: var(--logo-1); }
  20%, 39.99% { caret-color: var(--logo-2); }
  40%, 59.99% { caret-color: var(--p116-c3); }
  60%, 79.99% { caret-color: var(--logo-4); }
  80%, 100% { caret-color: var(--logo-5); }
}
.board-search input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]),
.actf-field input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]) {
  background: transparent; border: 0; border-radius: 0; box-shadow: none; outline: none;
  caret-color: var(--logo-5);
}
.board-search input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]):focus,
.actf-field input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]):focus {
  background: transparent; border: 0; box-shadow: none; outline: none;
  animation: p116Caret 5.3s linear infinite;
}
.board-search:focus-within::before, .actf-field:focus-within::before { opacity: 0; animation: none; }
.board-search:focus-within, .actf-field:focus-within,
.popover .board-search:focus-within, .sidebar .board-search:focus-within {
  box-shadow: none; /* groundskeeping (phase-135): the border-color declaration was removed, dead since phase-134's 1px --text-2 edge */
}
@media (prefers-reduced-motion: reduce) {
  .board-search input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]):focus,
  .actf-field input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]):focus { animation: none; }
}
/* groundskeeping (phase-135): the phase-116 rule for the dock search and the boards-panel search (a 34% focus border-colour) was removed; phase-134 restates both selectors with the 1px --text-2 edge, which is what wins. */

/* phase-116, second field: the "Type a name, press Enter" add field. The owner,
   same afternoon: "the rainbow edge cannot be seen on that search box neither".
   Same defect as the search boxes: the (0,5,1) generic rule painted its
   surface-1 fill at REST (the dark box in his screenshot, which :9804's
   (0,3,0) `background: transparent` never beat), and the fan had no border to
   draw on (`border: 0`), so the comment at :9800 promising "the text
   controls' rainbow edge" described nothing. It joins the search fields: one
   box, a soft edge on focus, the colour-stepping caret. The 1px transparent
   border at rest holds the edge's place so focus shifts nothing. */
@media (min-width: 701px) {
  .app .addbar input.add-item-input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]) {
    background: transparent; border: 1px solid transparent; box-shadow: none; outline: none; caret-color: var(--logo-5);
  }
  .app .addbar input.add-item-input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]):hover {
    background: var(--p112-hover, var(--hover-ground));
  }
  .app .addbar input.add-item-input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]):focus {
    background: var(--p112-well, var(--surface-2));
    animation: p116Caret 5.3s linear infinite; /* groundskeeping (phase-135): the 34% border-color removed, phase-134 sets the edge */
  }
}
@media (min-width: 701px) and (prefers-reduced-motion: reduce) {
  .app .addbar input.add-item-input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]):focus { animation: none; }
}

/* phase-116: the group totals row, off the band. Owner 2026-09-23 on the
   register: "what is this gray line with the text? seems none elegant
   obviously and does not match our style". Section 4 above (phase-113) put
   the totals "on a quiet band" (--p112-hover) and let them wrap anywhere, so
   "7 approved, 35 published, 1 queued" stacked three lines deep in a grey
   strip. Now: the page ground, one hairline above (the calendar's and the
   group line's own language), the totals quiet in t2 on ONE line, and no row
   at all where a group has nothing to total. The count is gone from the row
   (board.js renderSummaryRow): the group title already carries it. A status
   total wider than its column runs centred over the empty cells beside it
   rather than wrapping; a number total is short by nature. */
@media (min-width: 701px) {
  .app .board-table .summary-row td,
  .app .board-table .summary-row td.p113-sum-n {
    height: 36px; background: transparent; border: 0;
    border-top: 1px solid var(--p112-hair, var(--line-1));
    font: 400 12.5px/18px var(--font-body); color: var(--p112-t2, var(--text-2));
    white-space: nowrap; overflow-wrap: normal; overflow: visible;
  }
  .app .board-table .summary-row td.p113-sum-s { font-weight: 400; color: var(--p112-t2, var(--text-2)); }
  .app .board-table tfoot.p116-none { display: none; }
}

/* ---- phase-116-b: the row hover. Owner 2026-09-23, on a register row: "what's
   up with this gray and box looking edges when hovering the rows? this does
   not feel premium and elegant". Two things drew it: (1) the mouse-hover fan
   ring on the hovered CELL (:2350, a square, spinning frame - the "box"), which
   also broke RULE 39 (decoration never moves while he works); (2) the row's
   flat --p112-hover band with square ends (:9537 - the "gray").
   Now: mouse hover draws NO ring on any cell; the keyboard's cell keeps its
   fan (:6092, td.cell:focus-visible), because there it is the only answer to
   "where am I" - hence :not(:focus-visible). The row's hover is the same quiet
   ground with 10px rounded ends, one soft bar, the dock's corner language.
   SUPERSEDES design.md "the hovered CELL wears the five-hue ring" for mouse
   hover (checklist 53). border-collapse is separate (:863), so td radii hold. */
:root .app .board-table td.cell:hover:not(:focus-visible)::before,
:root .app .board-table td.col-name:hover::before { opacity: 0; animation: none; }
@media (min-width: 701px) {
  .app .board-table .item-row:hover > td:first-child { border-radius: 10px 0 0 10px; }
  .app .board-table .item-row:hover > td:last-child { border-radius: 0 10px 10px 0; }
}

/* phase-116-c: the detached comma. The owner's screenshots read "Jan 1 , 2025",
   "$1 , 200" and "7 approved , 35": Alioro Sans (Golos Text) sets its
   punctuation to figure width under `tnum`, so every tabular-nums run spaced
   its commas like digits. Inside the board every figure is CENTRED (checklist
   8) or prose, so column alignment was never bought by tnum there; the board,
   its totals, the kanban and the calendar read proportional figures. Lists that
   right-align numbers elsewhere keep tnum. */
.app .board-table td, .app .board-table td *,
.app .board-table .summary-row td,
.app .board-table .cell-text.num, .app .board-table .cell-date, .app .board-table .sum-cell,
.app .p113-kanban *, .app .p113-cal * { font-variant-numeric: normal; }

/* phase-116-d: every status/priority capsule in a column is ONE length, the
   column's longest label (--lw, board.js fitWidths, measured in the capsule's
   own type), capped inside the cell; the word centred in it. Owner
   2026-09-23: "should we make the labels of status one size? ... one length".
   The phase-99 lane rule (:9068) already said this; a later capsule rule
   (.status-pill.p112-cap, inline-flex with no width) let each shrink to its
   word, so "Done" sat at 50px beside "Working on it" at 94px. */
.app .board-table td.cell > .status-pill.p112-cap {
  box-sizing: border-box; width: min(var(--lw, 100%), calc(var(--cw, 140px) - 16px)); justify-content: center;
}

/* ---- phase-116-e: the title line shares ONE geometry. Owner 2026-09-23, on
   "Company DMS / 487 documents / star / Board tools": "It looks inconsistent
   and not premium and elegant". Measured: the row centred its items on the
   title's BOX while .p112-total alone asked for `baseline` - with no other
   baseline item it fell to the top of the row - so the count floated at the
   cap line, the star and Board tools sat on the title's baseline, and the star
   glyph sat 4px under the Board tools label. Now every item is placed from the
   title's own metrics (600 44px/48px: baseline 41.5px, cap middle 26px from
   the top of its 48px line box, measured in the browser at 1440):
     count       - ON the title's baseline (13.5/20 text, baseline at 15 -> +26)
     star        - its 16px glyph centred on the cap middle (32px box, +6)
     Board tools - its label's cap middle on the title's cap middle (+12)
   One text line, one optical centre for the controls. The condensed head keeps
   its own centred row. */
@media (min-width: 701px) {
  .app .board-head:not(.cond) .board-title-row { align-items: flex-start; }
  .app .board-head:not(.cond) .board-title-row > * { align-self: flex-start; }
  .app .board-head:not(.cond) .board-name > .board-tap { min-height: 48px; line-height: 48px; }
  .app .board-head:not(.cond) .p112-total { margin-top: 26px; margin-left: 4px; }
  .app .board-head:not(.cond) .icon-btn.star { margin-top: 6px; }
  .app .board-head:not(.cond) .board-more { margin-top: 12px; }
  .app .board-head:not(.cond) .p112-people-slot { margin-top: 14px; }
}
/* 116-e at tablet: the row never wraps a control onto a second line (at 768
   "Board tools" fell under the title); a long name wraps inside the title
   instead, and the count, star and Board tools stay on its first line. */
@media (min-width: 701px) {
  .app .board-head:not(.cond) .board-title-row { flex-wrap: nowrap; }
  .app .board-head:not(.cond) .board-name { flex: 0 1 auto; min-width: 0; }
  .app .board-head:not(.cond) .board-title-row > :not(.board-name) { flex: none; }
}
/* groundskeeping (phase-135): phase-116's block that pinned the switch chevron (`align-items: flex-start` on the button, `.ic` margin-top 18px) was removed; the button is display: block and the chevron sits inline in `.p131-tail` since phase-131. */
/* tablet: the 44px title split "Website relaunch" over two lines at 768 and
   crowded its own controls; between 701 and 1100 it is 32/36 and the offsets
   follow the same arithmetic, measured at 768 (baseline 31.5, cap middle 20.3). */
@media (min-width: 701px) and (max-width: 1100px) {
  .app .board-head:not(.cond) .board-name, .app .board-head:not(.cond) .board-name.tap { font-size: 32px; line-height: 36px; }
  .app .board-head:not(.cond) .board-name > .board-tap { min-height: 36px; line-height: 36px; }
  .app .board-head:not(.cond) .p112-total { margin-top: 16.5px; }
  .app .board-head:not(.cond) .icon-btn.star { margin-top: 0; }
  .app .board-head:not(.cond) .board-more { margin-top: 6.5px; }
  .app .board-head:not(.cond) .p112-people-slot { margin-top: 8px; }
}

/* ---- phase-116-f: a long glass menu scrolls INSIDE a still frame. Owner
   2026-09-23, on the row menu over his register ("this looks broken"): with
   his ~17 groups the menu passes 70vh and scrolls, and the popover WAS the
   scroll container - so its glass (.lg::before, the backdrop and inner
   shadows) and its rim (.lg::after), both `position: absolute; inset: 0`,
   scrolled away with the content: the frame's corner rode up into the list
   above "Legacy & Superseded" and the bottom of the menu lost its glass.
   css.md already names this ("an inset: 0 pseudo-element is never a rim inside
   its own scroll container"). The frame stops scrolling; its last child (the
   list) scrolls within it, so both layers stay on the visible pane. Phone
   sheets keep their own layout (superseded 2026-09-29 by phase-131 §1). ---- */
.popover.lg:not(.sheet) { overflow: hidden; display: flex; flex-direction: column; }
.popover.lg:not(.sheet) > * { flex: none; }
.popover.lg:not(.sheet) > :last-child { flex: 0 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
/* ==================================================================== phase-115: the floating glass sidebar
   The owner approved it on 2026-09-23 ("ok"): design/approved-2026-09-23/
   sidebar-open-1920-dark.png and sidebar-closed-1920-dark.png, with the
   values of sidebar-reference.css (the picture's own CSS) and the checklist's
   amendments, which win over the picture (OWNER-CHECKLIST 43-49):
     43  inset 16px, radius 22, the dock's glass; never opens on hover; one
         click or Cmd/Ctrl+\ toggles it and the choice is remembered
     44  open, 252px: the logo; My work, Chat, Claude, Alerts in the dock's
         inner well with glass counts - NO "Boards" row; Boards with search
         and "+"; Starred; Recent (max 5, no board twice); All N boards; the
         user and the theme. The current page lit like the dock's active
         view, its icon in its logo hue - only the current one
     45  closed: the compact capsule of the five icons, Boards included
     46  NO rainbow line on the sidebar: the picture's `.sb::after` prism is
         NOT ported (rule 39: a new ornament only by retiring one; the hue
         on the current icon carries the brand)
     47  long board names wrap in full; the count sits on the first line
     49  light: white glass, same structure. Phone: unchanged bottom bar
   It REPLACES phase-112's rail (section 1 of that block, removed). The
   element is the same <nav class="rail">, so the glass is written on
   `.app .rail` - one of the named floating panes of D46 §3a - and every
   desktop rule is inside `@media (min-width: 701px)`; the phone reset is
   restated at the end of this block. The width animates only while
   `html.snav-anim` is on, which board.js sets for the one change he asked
   for (rule 39: nothing moves while he works). One curve, --dur tokens.
   RETIRED here, as rule 39 asks of anything new: the old rail's badge pulse
   (`badgePulse`, infinite) and the chat entry's breathing presence dot do
   not run on the sidebar - the counts are still glass. ---- */
@media (min-width: 701px) {
  :root {
    /* light (49): white glass is phase-112's --p112-glass; these are the
       sidebar's own few: the hover wash, the count's glass, and the logo
       hues one shade deeper so an icon keeps 3:1 on the white lit plate
       (orange 2.0, yellow 1.5, green 2.6 as they are; 3.0/3.1/3.1 here) */
    --snav-hov: rgba(13, 15, 19, .045);
    --snav-n: rgba(13, 15, 19, .07);
    --snav-n-edge: inset 0 1px 0 rgba(255, 255, 255, .9), 0 0 0 .5px rgba(13, 15, 19, .1);
    --snav-h1: var(--logo-1);
    --snav-h2: color-mix(in srgb, var(--logo-2) 80%, #000);
    --snav-h3: color-mix(in srgb, var(--logo-3) 68%, #000);
    --snav-h4: color-mix(in srgb, var(--logo-4) 90%, #000);
    --snav-h5: var(--logo-5);
  }
  [data-theme="dark"] {
    --snav-hov: rgba(255, 255, 255, .05);
    --snav-n: rgba(255, 255, 255, .12);
    --snav-n-edge: inset 0 1px 0 rgba(255, 255, 255, .18);
    --snav-h1: var(--logo-1); --snav-h2: var(--logo-2); --snav-h3: var(--logo-3);
    --snav-h4: var(--logo-4); --snav-h5: var(--logo-5);
  }

  /* ---- the content column follows the sidebar: 16 + 252 + 20 open (the
     picture's x336 less the board's 48), 16 + 54 + 30 closed (x148) ------ */
  .app > .main { margin-left: 288px; }
  html[data-snav="closed"] .app > .main { margin-left: 100px; }
  html[data-snav="closed"] .p112-sheets { left: -100px; }
  html[data-snav="closed"] .app .board-head .p112-dock { left: calc(50% + 50px); max-width: calc(100vw - 100px - 48px - 32px); }
  html[data-snav="closed"] .app .sidebar.bp { left: 78px; }

  /* ---- the pane (43): x16 y16, r22, the dock's glass and rim ---------- */
  .app .rail {
    position: fixed; left: 16px; top: 16px; bottom: auto; z-index: 46;
    width: 252px; height: calc(100vh - 32px); box-sizing: border-box;
    display: flex; flex-direction: column; align-items: stretch; gap: 0;
    padding: 10px; border: 0; border-radius: 22px; overflow: hidden;
    background: var(--p112-glass);
    -webkit-backdrop-filter: var(--p112-fx); backdrop-filter: var(--p112-fx);
    box-shadow: var(--p112-rim), 0 18px 24px -18px rgba(0, 0, 0, .45);
  }
  :root:not([data-theme="dark"]) .app .rail { box-shadow: var(--p112-rim), 0 18px 24px -18px rgba(13, 15, 19, .12); }
  /* closed: the capsule. 332 = 6 + 48 head + 4 + five 42s with 2 between
     + 8 + the 42 toggle + 6, so the height can glide with the width */
  html[data-snav="closed"] .app .rail { width: 54px; height: min(332px, calc(100vh - 32px)); padding: 6px; }
  /* the inner columns keep their own width while the pane glides, so a
     change clips its contents rather than reflowing them every frame */
  .app .rail .snav-head, .app .rail .snav-well, .app .rail .snav-lists, .app .rail .snav-foot { width: 232px; flex: none; box-sizing: border-box; }
  html[data-snav="closed"] .app .rail .snav-head, html[data-snav="closed"] .app .rail .snav-well { width: 42px; }

  /* ---- the head: the 26px mark (checklist 1b) and the toggle ---------- */
  .app .rail .snav-head { display: flex; align-items: center; justify-content: space-between; padding: 4px 4px 12px 8px; }
  .app .rail-logo { margin: 0; line-height: 0; }
  .app .rail-logo .logo-mark { width: 26px; height: auto; }
  html[data-snav="closed"] .app .rail .snav-head { justify-content: center; height: 48px; padding: 0; margin-bottom: 4px; }
  .app .rail .snav-tg {
    flex: none; width: 30px; height: 30px; padding: 0; border: 0; border-radius: 10px; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    background: none; color: var(--p112-t3);
    transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
  }
  .app .rail .snav-tg:hover { background: var(--snav-hov); color: var(--p112-t1); }
  .app .rail .snav-tg-end { display: none; }
  html[data-snav="closed"] .app .rail .snav-tg-top { display: none; }
  html[data-snav="closed"] .app .rail .snav-tg-end { display: inline-flex; width: 42px; height: 42px; border-radius: 13px; margin-top: 8px; }

  /* ---- the well (44): the dock's inner well, 38px rows --------------- */
  .app .rail .snav-well {
    display: flex; flex-direction: column; gap: 1px; padding: 3px; border-radius: 16px;
    background: var(--p112-well);
  }
  html[data-snav="closed"] .app .rail .snav-well { gap: 2px; padding: 0; border-radius: 0; background: none; }
  .app .rail .rail-btn {
    position: relative; width: auto; height: 38px; flex: none;
    display: flex; flex-direction: row; align-items: center; justify-content: flex-start; gap: 11px;
    padding: 0 10px; border: 0; border-radius: 13px; background: none; box-shadow: none;
    color: var(--p112-t2); font: 400 13.5px/20px var(--font-body); letter-spacing: 0; text-align: left;
  }
  .app .rail .rail-btn > .ic { width: 18px; height: 18px; flex: none; stroke-width: 1.6; }
  .app .rail .rail-btn:hover { color: var(--p112-t1); background: var(--snav-hov); }
  .app .rail .rail-btn::before { display: none; }
  /* a row does not swell on a press: 3% of a 226px row is a visible jolt */
  .app .rail .snav-well .rail-btn:active { transform: none; }
  .app .rail .rail-lab { display: block; white-space: nowrap; }
  /* no "Boards" row open: the list right below is Boards (44) */
  .app .rail .snav-well .snav-boards { display: none; }
  html[data-snav="closed"] .app .rail .snav-well .snav-boards { display: flex; }
  html[data-snav="closed"] .app .rail .rail-btn { width: 42px; height: 42px; padding: 0; gap: 0; justify-content: center; }
  html[data-snav="closed"] .app .rail .rail-lab { display: none; }
  /* lit like the dock's active view; the icon in its own logo hue */
  .app .rail .rail-btn.active { color: var(--p112-t1); background: var(--p112-on); box-shadow: var(--p112-on-edge); }
  .app .rail .rail-btn.active[data-hue="1"] > .ic { color: var(--snav-h1); }
  .app .rail .rail-btn.active[data-hue="2"] > .ic { color: var(--snav-h2); }
  .app .rail .rail-btn.active[data-hue="3"] > .ic { color: var(--snav-h3); }
  .app .rail .rail-btn.active[data-hue="4"] > .ic { color: var(--snav-h4); }
  .app .rail .rail-btn.active[data-hue="5"] > .ic { color: var(--snav-h5); }
  /* the glass count: open it sits at the row's end, closed on the icon's
     corner; still, never the old pulse */
  .app .rail .badge {
    position: static; margin-left: auto; min-width: 18px; height: 18px; padding: 0 5px; box-sizing: border-box;
    border-radius: 9px; font: 600 10.5px/18px var(--font-body); text-align: center;
    color: var(--p112-t1); background: var(--snav-n); box-shadow: var(--snav-n-edge); animation: none;
  }
  html[data-snav="closed"] .app .rail .badge {
    position: absolute; top: 3px; right: 2px; left: auto; margin: 0; min-width: 15px; height: 15px; padding: 0 4px;
    border-radius: 8px; font: 600 9.5px/15px var(--font-body);
  }
  .app .rail .rail-dot { display: none; }

  /* ---- Boards, Starred, Recent, All N boards (44, 47) ---------------- */
  .app .rail .snav-lists { flex: 1 1 auto; min-height: 0; overflow-x: hidden; overflow-y: auto; scrollbar-width: none; }
  .app .rail .snav-lists::-webkit-scrollbar { display: none; }
  html[data-snav="closed"] .app .rail .snav-lists, html[data-snav="closed"] .app .rail .snav-foot { display: none; }
  .snav-sh {
    display: flex; align-items: center; justify-content: space-between;
    font: 600 11.5px/16px var(--font-body); color: var(--p112-t3, var(--text-3)); padding: 14px 10px 4px;
  }
  .snav-sh1 { padding-top: 20px; color: var(--p112-t2, var(--text-2)); }
  .snav-acts { display: flex; gap: 4px; }
  .snav-sb {
    width: 24px; height: 24px; padding: 0; border: 0; border-radius: 8px; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center; background: none; color: var(--p112-t3, var(--text-3));
    transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
  }
  .snav-sb:hover { background: var(--snav-hov); color: var(--p112-t1); }
  .snav-all {
    display: block; width: 100%; padding: 8px 10px; border: 0; border-radius: 12px; background: none; cursor: pointer;
    text-align: left; font: 400 12.5px/16px var(--font-body); color: var(--p112-t3);
    transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
  }
  .snav-all:hover { background: var(--snav-hov); color: var(--p112-t1); }

  /* ---- the foot: the user (the account menu) and the theme ----------- */
  .app .rail .snav-foot {
    margin-top: 8px; display: flex; align-items: center; gap: 4px; padding: 10px 0 2px;
    border-top: 1px solid var(--p112-hair);
  }
  .app .rail .snav-foot .avatar-btn {
    flex: 1 1 auto; min-width: 0; width: auto; height: auto; display: flex; align-items: center; gap: 10px;
    padding: 4px 6px; border: 0; border-radius: 12px; background: none; text-align: left; color: var(--p112-t1);
  }
  .app .rail .snav-foot .avatar-btn:hover { background: var(--snav-hov); }
  .app .rail .snav-foot .avatar-btn:active, .app .rail .snav-foot .theme-btn:active { transform: none; }
  .app .rail .snav-foot .avatar-btn .avatar { width: 26px !important; height: 26px !important; font: 600 10px/1 var(--font-body) !important; }
  .snav-who { min-width: 0; overflow-wrap: anywhere; }
  .snav-who b { display: block; font: 500 13px/16px var(--font-body); color: var(--p112-t1); }
  .snav-who em { display: block; font: 400 11px/14px var(--font-body); font-style: normal; color: var(--p112-t3); }
  .app .rail .snav-foot .theme-btn { width: 30px; height: 30px; padding: 0; gap: 0; justify-content: center; border-radius: 10px; color: var(--p112-t3); }
  .app .rail .snav-foot .theme-btn > .ic { width: 16px; height: 16px; }

  /* ---- the width change: on only for his own toggle (board.js) -------- */
  html.snav-anim .app .rail {
    transition: width var(--dur-scene-1) var(--ease-out), height var(--dur-scene-1) var(--ease-out),
                padding var(--dur-scene-1) var(--ease-out);
  }
  html.snav-anim .app > .main {
    transition-property: margin-left, margin-right;
    transition-duration: var(--dur-scene-1); transition-timing-function: var(--ease-out);
  }
  html.snav-anim .p112-sheets { transition: left var(--dur-scene-1) var(--ease-out); }
  html.snav-anim .app .board-head .p112-dock {
    transition: left var(--dur-scene-1) var(--ease-out), max-width var(--dur-scene-1) var(--ease-out),
                transform var(--dur-micro-3) var(--ease-out), opacity var(--dur-micro-2) var(--ease-out), visibility 0s linear 0s;
  }
}
@media (min-width: 1440px) {
  /* the item card's dock, closed: the content column starts at 100 + 48 */
  html[data-snav="closed"].p113-icard .app .board-head .p112-dock {
    max-width: calc(100vw - var(--p113-icw) - 40px - 148px - 16px);
  }
}
/* the rows: Starred and Recent on the sidebar, and on the phone's boards
   sheet (the same markup, .bp-quick) - so they read the p112 tokens with
   the older ones behind them for the phone */
.snav-grp { display: flex; flex-direction: column; gap: 1px; }
.snav-row {
  position: relative; display: flex; align-items: flex-start; gap: 10px; min-height: 34px; padding: 8px 10px;
  box-sizing: border-box; border-radius: 12px; color: var(--p112-t1, var(--text-1)); text-decoration: none;
  font: 400 13.5px/18px var(--font-body);
  transition: background var(--dur-micro-1) var(--ease-out);
}
.snav-row:hover { background: var(--snav-hov, var(--hover-ground)); }
.snav-row.on { background: var(--p112-on, var(--brand-soft)); box-shadow: var(--p112-on-edge, none); }
/* the icon and the count sit on the FIRST line of a name that wraps (47) */
.snav-ri { flex: none; height: 18px; display: inline-flex; align-items: center; color: var(--p112-t3, var(--text-3)); }
.snav-row.on .snav-ri { color: var(--snav-h2, var(--logo-2)); }
.snav-rn { flex: 1 1 auto; min-width: 0; white-space: normal; overflow-wrap: anywhere; }
.snav-c { flex: none; margin-left: auto; font: 400 11.5px/18px var(--font-body); font-style: normal; color: var(--p112-t3, var(--text-3)); font-variant-numeric: tabular-nums; }
/* dragging a starred board: the row lifts to half, a 2px line marks the drop */
.snav-row.snav-dragging { opacity: .5; }
.snav-row.snav-drop-b::before, .snav-row.snav-drop-a::after {
  content: ""; position: absolute; left: 10px; right: 10px; height: 2px; border-radius: 1px; background: var(--brand);
}
.snav-row.snav-drop-b::before { top: -1px; }
.snav-row.snav-drop-a::after { bottom: -1px; }
/* the phone's sheet leads with them; the desktop sidebar already shows them */
.bp-quick { padding: 0 0 4px; }
.bp-quick:empty { display: none; }
.bp-quick .snav-sh { padding: 12px 12px 4px; }
.bp-quick .snav-row { min-height: 44px; padding: 13px 12px; border-radius: 16px; }
@media (min-width: 701px) { .bp-quick { display: none; } }

/* ---- the phone keeps its bottom bar exactly: the sidebar's wrappers step
   out of the way (their children are the bar's items, as before), and the
   parts the bar never had are not drawn. The chrome is solid: no blur. ---- */
@media (max-width: 700px) {
  .snav-head, .snav-lists, .snav-tg, .snav-who { display: none; }
  .snav-well, .snav-foot { display: contents; }
  .app .rail { -webkit-backdrop-filter: none; backdrop-filter: none; }
}

/* phase-116-g: the dock never clips a view tab. With the sidebar open at 1440
   the dock is 1072px: the tabs need 665 and were given 607 ("Fil..." cut),
   while the search held its 260. The tabs keep their size; the search is the
   one that gives, down to 150px (its placeholder shortens on its own). */
@media (min-width: 701px) {
  .app .board-head .p112-dock .view-tabs { flex-shrink: 0; }
  .app .board-head .p112-dock .p112-srch { flex: 1 1 260px; width: auto; min-width: 150px; max-width: 260px; }
  .app .board-head .p112-dock .board-tools { flex: 0 1 auto; min-width: 0; }
}
/* 116-g at tablet: the dock is ~588px at 768 with the capsule sidebar; the
   view tabs show icons (the current one keeps its word) and take 369, so the
   search is the part that gives - down to 84px, its shortcut hint hidden -
   and Filter and New never leave the dock. */
@media (min-width: 701px) and (max-width: 1100px) {
  .app .board-head .p112-dock .p112-srch { min-width: 84px; }
  .app .board-head .p112-dock .p112-srch .p112-kbd { display: none; }
}

/* ---- phase-116-h: the phone board, three of the owner's standing rules.
   Walked at 375 on 2026-09-23: (1) the title ellipsised ("Website relaunc")
   and "5 items" broke over two lines beside it - names are never truncated
   (checklist 5); the title now takes its own line(s) and the count, star and
   Board tools sit on the line below. (2) The cards' field labels and dates were
   Alioro Mono in caps - the "typewriter" face he rejected on the register -
   now the body sans, sentence case, t3. (3) The group jump chips wore a round
   dot - "no dots" (checklist 15) - now the group's short spine, the same
   mark the group line carries. ---- */
@media (max-width: 700px) {
  .app .board-head .board-title-row { flex-wrap: wrap; row-gap: 4px; align-items: center; }
  .app .board-head .board-name, .app .board-head .board-name.tap {
    flex: 1 0 100%; min-width: 0; max-width: none; white-space: normal; overflow: visible; text-overflow: clip;
  }
  .rcard, .rcard *, .sub-cell, .sub-cell * { font-variant-numeric: normal; }
  .app .board-head .board-name > .board-tap { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; text-align: left; }
  .app .board-head .p112-total { white-space: nowrap; margin-left: 0; }
  .app .board-head .board-more { margin-left: auto; }
  .rcard-cell em, .sub-cell > em {
    font: 400 12px/16px var(--font-body); letter-spacing: 0; text-transform: none; color: var(--text-3); font-style: normal;
  }
  .rcard-foot { font-family: var(--font-body); font-size: 12px; }
  .rcard .cell-date, .rcard-cell .cell-date { font-family: var(--font-body); font-weight: 500; letter-spacing: 0; }
  .gjump-chip i { width: 3px; height: 14px; border-radius: 2px; }
}

/* ════════════════════════════════════════════════════════════════ phase-117
   SUBITEMS ARE ROWS. The owner, 2026-09-23: "when adding a subitem the
   columns can not be edited is that how our competitors do it?". A child is
   now a <tr class="item-row p117-sub"> of its parent's own table (board.js
   subRowHTML), so every one of its cells is the table's own td.cell and needs
   nothing here: same <col>, same width, same centring, same editor. This
   block only sets the child's name cell one step in, quiets a done child's
   name, lines the add row up under the children, and lays out the phone's
   child cards. No backdrop-filter, no blur on a row, no keyframes. */

/* One step in (24px) from the parent's text edge, which with the tick puts
   the child's name under the parent's name (the parent's caret is about as
   wide as the tick). On .cn, not the td: the td's own left padding is the
   p112 block's !important gutter, where a parent's selection tick lives; a
   child has no selection tick, so that gutter stays empty on its row. */
.board-table tr.p117-sub td.col-name .p117-cn { padding-left: 24px; }
.board-table tr.p117-sub .p117-cn > .sub-check { flex: none; }
/* done is quieter ink, never a line through the words (:4441) */
.board-table tr.p117-sub.done td.col-name .item-name { color: var(--p112-t3, var(--text-3)); }
/* the loader row on a first open, and the add row, sit under the ticks */
.board-table tr.p117-load td { height: 40px; }
.p117-note { position: sticky; left: 0; display: flex; width: max-content; padding: 0 12px 0 58px; }
@media (min-width: 701px) {
  /* the add button's plus centred under the children's tick boxes: the td's
     12 + this 44 + the button's own 4 puts the 14px icon at 60-74, the tick
     box's centre being 30 + 24 + 13 = 67 */
  .app .board-table tr.p117-add .addbar { padding-left: 44px; }
}
@media (max-width: 700px) {
  .board-table tr.p117-add .addbar { padding-left: 40px; }

  /* the phone's cards: each child is a card of its own under the parent's,
     carrying the same fields (board.js subCardHTML), ruled like the phone
     register's rows (phase-103: a hairline, no box), its tick on the left
     and its options on the right, every target 44px */
  .p117-subcards { display: flex; flex-direction: column; padding: 0 0 6px; border-top: var(--hair, 1px) solid var(--row-rule, var(--line-2)); }
  .p117-sc { display: flex; align-items: flex-start; padding-left: 12px; }
  .p117-sc + .p117-sc { border-top: var(--hair, 1px) solid var(--row-rule, var(--line-2)); }
  .p117-sc > .sub-check { flex: none; width: 44px; height: 44px; margin-top: 2px; }
  .p117-sc > .p117-subcard {
    flex: 1 1 auto; min-width: 0; border: 0; border-radius: 0;
    background: transparent; box-shadow: none; padding: 12px 4px 12px 4px;
  }
  /* the tick is the child's left edge; a status spine between the tick and
     the name would be a second edge (the capsule in its fields carries it) */
  .p117-sc > .p117-subcard::before { display: none; }
  .p117-sc.done .rcard-name { color: var(--text-3); }
  .p117-sc > .p117-sc-menu { flex: none; width: 44px; height: 44px; margin-top: 2px; color: var(--text-2); }
  .p117-sc-add {
    display: flex; align-items: center; gap: 8px; min-height: 44px; margin: 0; padding: 0 12px 0 26px;
    border: 0; background: none; color: var(--text-2); font: 400 13.5px/20px var(--font-body);
    text-align: left; cursor: pointer;
  }
  .p117-sc-add > .ic { flex: none; color: var(--text-3); }
  .p117-sc-add:active { background: var(--surface-2); }
  .p117-sc-add:focus-visible { box-shadow: var(--focus-ring); outline: none; }
  .p117-subcards > .p117-note { position: static; padding: 10px 0 10px 56px; }
}

/* ==== phase-119-a: the register reads clean. Builder A of two; one block.
   Every rule cites the qa audit (design/approved-2026-09-23/AUDIT-2026-09-23.md)
   and the owner's checklist item it answers. ================================ */

/* (audit 2, checklist 44/48) THE [hidden] ATTRIBUTE HIDES. "Find a board"
   set row.hidden = true on every row that did not match, and nothing
   disappeared: `.sidebar .bp-row { display: flex }` (an author rule) beats
   the browser's own `[hidden] { display: none }` (a user-agent rule) on
   every element that has a display of its own. That is the cause, and it
   is not the boards panel's alone - any element given a display and then
   hidden by script had the same fault. One rule, for all of them. */
[hidden] { display: none !important; }

/* (audit 3, checklist 54) FIGURES ARE PROPORTIONAL, APP-WIDE, IN ONE RULE.
   Alioro Sans sets its comma and full stop at FIGURE width under `tnum`
   (8.57px against 3.59px), so every tabular-nums run printed "$1 , 200",
   "EOH-HWO . 1 . 004 . 04" and "operations manager , scanned copy only".
   Phase-116-c fixed the board table only; about twenty other rules still
   asked for tnum (the item card's fields at the worst, :9682). They are
   overruled here rather than edited one by one, so no later rule can bring
   the detached comma back by adding a twenty-first. THE ONE EXCEPTION: a
   right-aligned column of counts, where the digits must stack - the
   dashboard's legend and workload lists (.legend-row, .wl-row). Nothing
   else in the app is a right-aligned numeric list: every figure in the
   board is centred (checklist 2), and a centred or inline figure gains
   nothing from equal-width digits. Add a selector to the second rule only
   for a genuinely right-aligned list of numbers, never for prose. */
body, body * { font-variant-numeric: normal !important; }
body .legend-row b, body .legend-row .muted, body .wl-row b { font-variant-numeric: tabular-nums !important; }

/* (audit 17) the native controls follow the theme: a date field, a select's
   list and the scrollbars were drawn in the LIGHT scheme in dark theme,
   because nothing declared the page's scheme (getComputedStyle(html)
   .colorScheme read "normal"). There is no glass date picker in the app to
   port, so the native one is at least drawn in the theme he is in. */
:root[data-theme="dark"] { color-scheme: dark; }
:root:not([data-theme="dark"]) { color-scheme: light; }

/* (audit 1, checklist 50/52) THE GROUP'S STATUS TOTAL is one small capsule
   per state, in the state's own colour, carrying its count (the words are
   its title and the cell's accessible name, board.js renderSummaryRow). It
   WRAPS INSIDE ITS OWN CELL: phase-116's `white-space: nowrap; overflow:
   visible` (:10178) let 471px of words run through the 140px cell and over
   the Review state total beside it, 211px of overprinted text. The cell
   now clips to itself, so no total can ever overlap a neighbour; with the
   status column at least its widest label wide (fitWidths), six two-digit
   counts sit on one line. */
@media (min-width: 701px) {
  .app .board-table .summary-row td.p119-sum { white-space: normal; overflow: hidden; padding: 6px 8px; }
}
.p119-tcs { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 3px; max-width: 100%; }
/* chip-edge: exempt - tinted capsule, its rim is the chip (the status
   capsule's own treatment, .status-pill.p112-cap, POLISH-SPEC §15) */
.p119-tc {
  display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box;
  min-width: 22px; height: 20px; padding: 0 5px; border-radius: 6px;
  font: 500 12px/16px var(--font-body); color: var(--p112-t1, var(--text-1));
  background: color-mix(in srgb, var(--lab, var(--text-3)) 14%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lab, var(--text-3)) 60%, transparent);
}
[data-theme="dark"] .p119-tc {
  color: color-mix(in srgb, var(--lab, var(--text-3)) 66%, #fff);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lab, var(--text-3)) 32%, transparent);
}
/* "no status": a hollow capsule, no colour to claim */
.p119-tc.p119-tc-none, [data-theme="dark"] .p119-tc.p119-tc-none {
  background: none; color: var(--p112-t3, var(--text-3));
  box-shadow: inset 0 0 0 1px var(--p112-hair, var(--line-1));
}

/* (audit 29) OVERDUE HAS ONE TREATMENT: the date in the red signal hue at
   the ordinary weight, in the table, the item card, My work and the kanban
   alike. The table showed it bold in the same grey as every other date (a
   later `.app .board-table .cell-date { color: t2 }` took the red away) and
   the card and My work showed it red and bold. The owner reads colour first
   (checklist 50): the colour stays, quieter, and bold goes everywhere. Due
   soon loses its bold for the same reason and keeps each surface's colour. */
.cell-date.overdue, .app .board-table .cell-date.overdue, .mywork-row .cell-date.overdue,
.p113-ic .cell.inline .cell-date.overdue, .p113-kanban .p113-kc-key .cell-date.overdue { color: var(--danger); font-weight: 400; }
.cell-date.soon, .app .board-table .cell-date.soon, .mywork-row .cell-date.soon { font-weight: 400; }

/* (audit 27, 28; checklist 15, 50) THE PICKERS ARE ONE OBJECT. The status
   picker was a plain list with an 8px dot per label and a lowercase "clear";
   the dropdown picker was a stack of grey slabs. Both now list each value as
   its own capsule (the cell's .p112-cap; a dropdown option is the neutral
   capsule, it has no colour), the current one ticked at the row's end, and
   "Clear" is the same capitalised button at the foot of both. */
.p119-pick { gap: 2px; min-width: 200px; }
.p119-pick .label-opt.p119-opt {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 4px 8px; border-radius: 10px; font-weight: 400;
}
.p119-pick .label-opt.p119-opt::before { display: none; }
.p119-pick .label-opt.p119-opt:hover { background: var(--p112-hover, var(--hover-ground)); }
.p119-pick .label-opt.p119-opt > .ic { flex: none; width: 14px; height: 14px; color: var(--p112-t2, var(--text-2)); }
.p119-pick .p119-opt > .status-pill.p112-cap {
  min-width: 0; max-width: 100%; height: auto; min-height: 24px; padding-top: 2px; padding-bottom: 2px;
  white-space: normal; overflow-wrap: anywhere; text-align: left; justify-content: flex-start;
}
.p119-pick .p119-pick-foot {
  justify-content: space-between; margin-top: 4px; padding-top: 6px;
  border-top: 1px solid var(--p112-hair, var(--line-1));
}
.p119-pick .p119-pick-foot > .label-edit:only-child { margin-left: auto; }

/* (audit 27) the dots that stood for a state or a group become the 3px
   spine the group line and the menus already use (.p113-spine): the filter
   chips' value mark, the dashboard legends and keys, and My work's group
   mark. The radius stays 2px (the phase-113 legend-key lock). */
.qf-chip .qf-dot, .dw .dot, .dw .sch-dot, .ring-legend .dot, .legend-row .dot, .wl-row .dot, .sch-dot, .mywork-row .dot-mark {
  width: 3px; height: 12px; border-radius: 2px; box-shadow: none;
}
.dw .dot.lg, .mywork-row .dot-mark { height: 14px; }

/* (audit 12) the item card's status capsule stands alone: `.item-values`
   is a flex COLUMN, which stretched the one cell to the card's full width,
   and the old `.cell.inline` border (:927) drew a 343px frame round it. */
.p113-ic-status.item-values { flex-direction: row; align-items: center; }
.p113-ic-status .cell.inline { flex: none; width: auto; border: 0; background: none; }

/* (audit 32, checklist 18) the row's name opens the item: it reads as the
   link it now is - the pointer, and on hover a quiet underline - and while
   it is being renamed it is the text field it always was (the fan ring is
   the contenteditable's own, phase-68). */
.app .board-table td.col-name .p119-name:not([contenteditable]) { cursor: pointer; }
@media (hover: hover) {
  .app .board-table td.col-name .p119-name:not([contenteditable]):hover {
    text-decoration: underline; text-decoration-color: var(--p112-t3, var(--text-3));
    text-decoration-thickness: 1px; text-underline-offset: 3px;
  }
}

/* (audit 18) the row menu's ONE "Move to group" row names the current
   group and opens the picker; the picker scrolls its own list, never the
   menu, so Delete is always on screen. Names wrap whole (checklist 10). */
.menu-item.p119-sub { display: flex; align-items: center; gap: 8px; }
.menu-item.p119-sub > .p119-sub-v {
  margin-left: auto; max-width: 150px; white-space: normal; overflow-wrap: anywhere; text-align: right;
  font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3));
}
.menu-item.p119-sub > .ic:last-child { flex: none; width: 14px; height: 14px; margin-left: auto; color: var(--p112-t3, var(--text-3)); }
.menu-item.p119-sub > .p119-sub-v + .ic:last-child { margin-left: 0; }
.p119-gpick, .p119-wspick { min-width: 240px; max-width: 340px; }
.p119-glist { display: flex; flex-direction: column; max-height: min(360px, 55vh); overflow-y: auto; overscroll-behavior: contain; }
.p119-gpick .menu-item, .p119-wspick .menu-item { display: flex; align-items: center; gap: 8px; }
.p119-gn { flex: 1 1 auto; min-width: 0; white-space: normal; overflow-wrap: anywhere; text-align: left; }
.p119-gq { display: flex; align-items: center; gap: 8px; margin: 0 4px 6px; }
.p119-gq .board-filter { flex: 1 1 auto; min-width: 0; }
.p119-gnone { margin: 0; padding: 8px 10px; font-size: 13px; color: var(--p112-t3, var(--text-3)); }

/* (phase-118 wired) My work's count: due today in the glass count, overdue
   beside it in the red signal hue. Closed, and on the phone's bar, there is
   room for one corner count and the red one is it. */
.app .rail .badge.p119-mw-o {
  color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--danger) 45%, transparent);
}
:root:not([data-theme="dark"]) .app .rail .badge.p119-mw-o { color: color-mix(in srgb, var(--danger) 78%, #000); }
.app .rail .badge.p119-mw-d + .badge.p119-mw-o { margin-left: 4px; }
html[data-snav="closed"] .app .rail .badge.p119-mw-both { display: none; }
@media (max-width: 700px) { .rail .badge.p119-mw-both { display: none; } }

/* (audit 15) THE DOCK'S SEARCH, where it cannot be 200px (board.js dockFit
   adds .p119-sq): one 40px search button in the dock, lit while a search is
   in force. Focused (a click, Tab, Cmd/Ctrl+K), the field opens OVER the
   view tabs: its width is --p119-sw (the dock's inner left edge to the
   button's right edge) and its margin gives the same amount back, so it
   still occupies exactly its 40px of the row and Filter and New never move.
   The tabs under it are hidden, not removed, for the same reason. */
@media (min-width: 701px) {
  .app .board-head .p112-dock.p119-sq .p112-srch {
    position: relative; flex: 0 0 40px; width: 40px; min-width: 40px; max-width: none;
    padding: 0; gap: 0; justify-content: center; cursor: pointer;
  }
  .app .board-head .p112-dock.p119-sq .p112-srch .board-filter { flex: 0 0 0px; width: 0; min-width: 0; padding: 0; opacity: 0; }
  .app .board-head .p112-dock.p119-sq .p112-srch .p112-kbd { display: none; }
  .app .board-head .p112-dock.p119-sq .p112-srch:has(.board-filter:not(:placeholder-shown)) {
    color: var(--p112-t1); background: var(--p112-on); box-shadow: var(--p112-on-edge);
  }
  .app .board-head .p112-dock.p119-sq .p112-srch:focus-within {
    z-index: 3; flex: 0 0 auto; width: var(--p119-sw, 280px); margin-left: calc(40px - var(--p119-sw, 280px));
    padding: 0 12px; gap: 8px; justify-content: flex-start; cursor: text;
  }
  .app .board-head .p112-dock.p119-sq .p112-srch:focus-within .board-filter { flex: 1 1 auto; width: auto; opacity: 1; }
  .app .board-head .p112-dock.p119-sq:has(.p112-srch:focus-within) .view-tabs,
  .app .board-head .p112-dock.p119-sq:has(.p112-srch:focus-within) .p112-vr { visibility: hidden; }
}

/* ════════════════════════════════════════════════════════════════ phase-119-b
   EVERY VIEW IN ONE MATERIAL. The qa audit of 2026-09-23
   (design/approved-2026-09-23/AUDIT-2026-09-23.md) walked the whole app at
   1920/1440/768/375 in both themes; this block answers its findings in the
   dock, the panels, the item card, the kanban, the calendar, the phone and
   the figures' face. Each rule cites its audit number. No backdrop-filter is
   written here (the panels JOIN the boards panel's glass through --lg-fx, as
   the item card does, above 701px only), no keyframes, no blur on a card or
   a row, and every transition names the one curve token. */

/* ---- 1. ONE FACE FOR FIGURES (audit 26; OWNER-CHECKLIST 38, 54) ---------
   The owner rejected the mono face for data ("typewriter"), and it was still
   on dates outside the table, the phone's Doc ID and Revision, chat, the
   dashboard, Admin, the Files summary and the sub-count "0/3": about ninety
   rules naming --font-mono. SUPERSEDED here by token, not rule by rule:
   --font-num read var(--font-mono) ("the only face with tabular figures",
   phase-99-a, :8814) and --font-mono read "Alioro Mono" (:138). Both now
   resolve to the body sans, the way --font-display and --font-body converged
   in D31: no consumer moves, the face under all of them changes. --font-code
   is the real monospace, kept for the one thing on screen that IS code, a
   keyboard hint. The figures go proportional where the mono face used to
   space them evenly: under tabular figures this sans draws a comma as wide
   as a digit, the detached comma of checklist 54 ("Jan 1 , 2025"). Lists
   that right-align a column of bare numbers (legends, workload) keep tnum. */
:root {
  --font-code: "Alioro Mono", "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --font-mono: var(--font-body);
  --font-num: var(--font-body);
}
#tip kbd, .pr-kbd, .p112-kbd { font-family: var(--font-code); }
.cell-text.num, .cell-date, .group-count, .pill-count, .sum-cell, .ch-time, .ch-meta span,
.attach-ver-chip, .qf-chip b, .fv-meta, .pf-meta, .tl-bar b, .tl-years, .bcols-grp-c,
.kpi-n, .ring-center b, .dw .mi-meta, .kanban-count, .rcard-cell[data-ident] { font-variant-numeric: normal; }

/* ---- 2. ONE RIGHT-HAND PANEL (audit 31) ---------------------------------
   Three shapes become one, the item card's. Chat was flush and full height,
   Claude flush from y52 at 380 wide; both now float like the card and the
   sidebar - top, right and bottom 16, --p113-icw wide, r22 - on the boards
   panel's glass, JOINED through --lg-fx (D46 §3d), never a copied chain.
   They slide in by transform, never by width (a width animation re-lays the
   pane's contents every frame) and never by opacity (a glass pane that takes
   opacity cuts its own backdrop). The primitive's specular rim stands down;
   the edge is --p112-rim, as on the card. core.js closeOverlays keeps ONE of
   card, Chat, Claude and Alerts open. */
@media (min-width: 701px) {
  .app .chat-panel, .app .ai-panel {
    --lg-fx: var(--p112-fx);
    top: 16px; right: 16px; bottom: 16px; left: auto;
    width: var(--p113-icw); max-width: calc(100vw - 32px);
    border: 0; border-radius: 22px; overflow: hidden;
    background: var(--p112-glass);
    box-shadow: var(--p112-rim), 0 26px 80px rgba(0, 0, 0, .5), 0 4px 14px rgba(0, 0, 0, .3);
    transform: translateX(calc(100% + 24px)); visibility: hidden;
    transition: transform var(--dur-micro-3) var(--ease-out), margin-bottom var(--dur-micro-3) var(--ease-out),
                visibility 0s linear var(--dur-micro-3);
  }
  :root:not([data-theme="dark"]) .app .chat-panel, :root:not([data-theme="dark"]) .app .ai-panel {
    box-shadow: var(--p112-rim), 0 18px 50px rgba(13, 15, 19, .14), 0 4px 14px rgba(13, 15, 19, .07);
  }
  .app .chat-panel.open, .app .ai-panel.open {
    transform: none; visibility: visible;
    transition: transform var(--dur-micro-3) var(--ease-out), margin-bottom var(--dur-micro-3) var(--ease-out),
                visibility 0s linear 0s;
  }
  /* Claude empties its pane as it closes (panels.js renderAiPanel), so it
     leaves at once rather than sliding an empty sheet of glass away */
  .app .ai-panel:not(.open) { transition: none; }
  .app .chat-panel::after, .app .ai-panel::after { display: none; }
  .app .chat-panel .ch-window { width: 100%; }
  .modal.p113-icm { border-radius: 22px; }
  /* the card takes the focus itself (panels.js, audit 7): no ring on the pane */
  .modal.p113-icm:focus, .modal.p113-icm:focus-visible { outline: none; }
  /* a panel pushes the board only from 1280, by the panel's own room; below
     that it would leave less board than panel, so it floats over it */
  body.chat-open .main, body.ai-open .main { margin-right: 0; }
}
@media (min-width: 1280px) {
  body.chat-open .main, body.ai-open .main { margin-right: calc(var(--p113-icw) + 16px); }
}
@media (prefers-reduced-motion: reduce) {
  .app .chat-panel, .app .ai-panel, .app .chat-panel.open, .app .ai-panel.open { transition: none; }
}

/* ---- 3. THE DOCK NEVER GOES UNDER A PANEL (audit 4) ---------------------
   core.js SD.p119Dock decides, by measuring the dock in its compact form:
   html.p119-side  it fits beside the panel: centred on the uncovered part
                   of the content column, x = (L + 100vw - W - 40) / 2, the
                   picture's x736 at 1920 (POLISH-SPEC §3), every view but
                   the current one down to its icon (each keeps its name as
                   a tip, core.js) and the shortcut hint put away;
   otherwise       it stays where it rests and the panel stops 16px above it
                   (32 + 56 + 16 = 104 from the bottom).
   Nothing is covered and nothing spills: Filter and New item were under the
   chat at 1920, and New item spilled past the dock's edge beside the card. */
@media (min-width: 701px) {
  /* moved by transform from its resting `left`, the card's own step aside
     (phase-113): the glide is his action's answer and nothing re-lays out;
     `left` and the width only ever glide under his sidebar toggle (39) */
  html.p119-side .app .board-head .p112-dock {
    transform: translateX(calc(-50% - (var(--p113-icw) / 2 - 4px)));
    max-width: calc(100vw - var(--p113-icw) - 40px - 336px - 16px);
  }
  html.p119-side[data-snav="closed"] .app .board-head .p112-dock {
    max-width: calc(100vw - var(--p113-icw) - 40px - 148px - 16px);
  }
  html.p119-side .app .board-head .p112-dock .view-tabs .view-tab:not(.active) .vt-label:not(.vt-keep) { display: none; }
  html.p119-side .app .board-head .p112-dock .p112-srch .p112-kbd { display: none; }
  /* not beside: the dock keeps its resting place (the card's own step
     aside, phase-113, stands down with it) and the panel lifts above it */
  html.p119-rp:not(.p119-side) .app .board-head .p112-dock {
    transform: translateX(-50%); max-width: calc(100vw - 288px - 48px - 32px);
  }
  html.p119-rp:not(.p119-side)[data-snav="closed"] .app .board-head .p112-dock {
    max-width: calc(100vw - 100px - 48px - 32px);
  }
  /* the lift is a bottom MARGIN on a pane pinned top and bottom: its edge
     rises 88px (to 104) while `bottom` stays the one inset all panes share */
  html.p119-rp:not(.p119-side):has(.app .board-head .p112-dock) .app .chat-panel,
  html.p119-rp:not(.p119-side):has(.app .board-head .p112-dock) .app .ai-panel,
  html.p119-rp:not(.p119-side):has(.app .board-head .p112-dock) .modal.p113-icm { margin-bottom: 88px; }
}

/* ---- 4. THE ITEM CARD: THE TABS ABOVE THE FOLD (audit 13) ---------------
   The card leads with its key fields and a labelled "Show all N fields"
   (panels.js); the compose box shows on the Updates tab only. [hidden] has
   to be restated: .iv-row is a grid, which outranks the attribute. */
.p113-ic-fields .iv-row[hidden], .p113-ic-compose[hidden] { display: none; }
.p119-ic-all {
  align-self: flex-start; display: inline-flex; align-items: center; height: 28px; margin: 4px 0 0 -8px; padding: 0 8px;
  border: 0; border-radius: 8px; background: none; cursor: pointer;
  font: 500 12px/16px var(--font-body); color: var(--p112-t2, var(--text-2));
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
}
.p119-ic-all:hover { background: var(--p112-hover, var(--hover-ground)); color: var(--p112-t1, var(--text-1)); }
.p119-ic-all:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.p113-ic-tabs { margin-top: 16px; }

/* ---- 5. THE KANBAN (audit 6 and 30) -------------------------------------
   As tall as the room above the dock (views.js measures where it starts,
   --p119-kt): each lane scrolls its own cards, so the lane titles stay put
   and the sideways scrollbar is on screen, a drawn one a mouse can take.
   Every lane title is the status capsule and every card carries the lane's
   colour as a 3px spine (OWNER-CHECKLIST 50: quieter, never absent). */
.p119-kcol .kanban-col-head::before { display: none; }
.kanban-col-head > .p119-kcap {
  height: auto; min-height: 24px; max-width: none; padding: 2px 10px; border-radius: 8px;
  white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; justify-content: flex-start;
}
.p119-kk { font-style: normal; font-weight: 400; color: var(--p112-t3, var(--text-3)); }
.p119-kcol .kanban-card { position: relative; padding-left: 20px; }
.p119-kspine {
  position: absolute; left: 8px; top: 12px; bottom: 12px; z-index: 2; width: 3px; border-radius: 2px;
  background: var(--lab, var(--text-3)); pointer-events: none;
}
@media (min-width: 701px) {
  #board-body:has(> .p113-kanban) { padding-bottom: 0; }
  .app .p113-kanban {
    box-sizing: border-box; height: calc(100vh - var(--p119-kt, 280px) - 112px); min-height: 360px;
    overflow-x: auto; overflow-y: hidden; padding-bottom: 12px;
    scrollbar-width: thin; scrollbar-color: var(--p112-t4, var(--line-1)) transparent;
  }
  .app .p113-kanban::-webkit-scrollbar { height: 10px; }
  .app .p113-kanban::-webkit-scrollbar-thumb { border-radius: 5px; background: var(--p112-t4, var(--line-1)); }
  .app .p113-kanban::-webkit-scrollbar-track { background: transparent; }
  .app .p113-kanban .kanban-col { display: flex; flex-direction: column; max-height: 100%; min-height: 0; }
  .app .p113-kanban .kanban-col-head { flex: none; align-items: center; }
  .app .p113-kanban .kanban-col-head > .p119-kcap { font: 600 13.5px/20px var(--font-body); }
  .app .p113-kanban .kanban-list { flex: 0 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 0 2px 2px 0; }
  .app .p113-kanban .kanban-card { padding-left: 24px; }
  .app .p113-kanban .p119-kspine { left: 10px; top: 14px; bottom: 14px; }
}

/* ---- 6. THE CALENDAR ON THE GROUND (audit 14; checklist 53) -------------
   What re-boxed the days after the hairline pass: the solid-card layers
   every .cal-cell still carries (the inner-shadow ::before and the lit
   ::after rim of phase-91, :8169-8200), 35 framed tiles. The month keeps
   the one pane edge .cal-grid draws round it (phase-91 point 7). Days are
   the ground with hairlines only; today is a quiet tint in the brand ink,
   not a filled plate; every event is a capsule in its status colour, and
   one with no status wears the grey chip. */
@media (min-width: 701px) {
  .app .p113-cal .cal-cell::before, .app .p113-cal .cal-cell::after { display: none; }
  .app .p113-cal .cal-cell.today .p113-dn {
    background: color-mix(in srgb, var(--brand) 14%, transparent); color: var(--brand); font-weight: 600;
  }
  /* chip-edge: exempt - tinted capsule, its rim is the chip (the status
     capsule's own recipe, .status-pill.p112-cap), here in the grey chip */
  .app .p113-cal .cal-item.p113-ev:not(.p113-ev-cap) {
    background: color-mix(in srgb, #c4c4c4 14%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, #c4c4c4 60%, transparent);
  }
  [data-theme="dark"] .app .p113-cal .cal-item.p113-ev:not(.p113-ev-cap) {
    box-shadow: inset 0 0 0 1px color-mix(in srgb, #c4c4c4 32%, transparent);
  }
}
/* the phone's day cell: the round dot becomes a short capsule mark (15) */
@media (max-width: 700px) {
  .cal-item { width: 14px; height: 5px; border-radius: 3px; box-shadow: var(--chip-ring); }
}

/* ---- 7. THE PHONE (audit 10, 24, 33, 35) --------------------------------
   The bottom bar says what each place is: a short word under every icon,
   "Me" under the avatar (checklist 18, 26). Group titles and the jump chips
   run in full, never "Health, Safety & Environm..." (checklist 10). The "+"
   is the dock's rim-lit create, not a solid brand circle (checklist 38). The
   Boards sheet's close and "+" share one 44px line. */
@media (max-width: 700px) {
  .app .rail .rail-btn, .app .rail .avatar-btn { flex-direction: column; gap: 3px; }
  .app .rail .rail-lab { display: block; font: 500 10.5px/12px var(--font-body); letter-spacing: 0; white-space: nowrap; }
  .app .rail .avatar-btn::after { content: "Me"; font: 500 10.5px/12px var(--font-body); color: var(--text-2); }
  .app .rail .rail-btn .badge { top: 4px; }
  .group-head .group-title { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
  .gjump-chip { max-width: none; }
  .gjump-chip span { overflow: visible; text-overflow: clip; }
  .app .board-head .btn.primary.p112-new {
    background: var(--surface-2); color: var(--text-1); border: 1px solid rgba(255, 255, 255, .55);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .2), 0 4px 12px rgba(0, 0, 0, .35);
  }
  :root:not([data-theme="dark"]) .app .board-head .btn.primary.p112-new {
    background: #fff; border-color: rgba(13, 15, 19, .32); box-shadow: inset 0 1px 0 #fff, 0 4px 12px rgba(13, 15, 19, .12);
  }
  .app .sidebar .bp-head .bp-new, .app .sidebar .bp-head .bp-close {
    width: 44px; height: 44px; min-height: 44px; margin-top: 0; margin-bottom: 0; align-self: center;
  }
}
@media (max-width: 900px) and (max-height: 440px) {
  .app .rail .rail-btn, .app .rail .avatar-btn { gap: 1px; }
}

/* ============================================================== phase-122
   CUSTOMIZATION (customize.js): views that remember their setup, the table
   grouped by any column, filter conditions, Undo, spreadsheet import. One
   block, every selector prefixed p122-. Nothing here sets backdrop-filter or
   blurs anything: the Filter, View settings and the import dialog are the
   existing popover and modal, so they keep their own material, and the
   phone's solid chrome is untouched. Tokens are the app's own (--text-*,
   --line-*, --surface-*), because the --p112-* set exists only above 700px
   and every surface here is also a phone sheet. Hover answers are builder
   C's interaction language on the classes reused here (.menu-item,
   .chip-btn, .btn.ghost, .icon-btn, .qf-chip); none is restyled here. */

/* ---- 1. a view just for him: a quiet person mark on its tab, no dot ---- */
.view-tab .p122-mine { display: inline-flex; align-items: center; margin-left: -2px; color: var(--text-3); }
.view-tab .p122-mine > .ic { width: 11px; height: 11px; }
.view-tab.active .p122-mine { color: var(--text-2); }

/* ---- 2. the shared small parts ----------------------------------------- */
.p122-seg { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.p122-muted { font-size: 12px; line-height: 16px; color: var(--text-3); }
.p122-sel, .p122-in {
  height: 32px; padding: 0 8px; font-size: 13px; line-height: 30px; border-radius: 8px;
  min-width: 0; max-width: 100%;
}
.p122-sel { padding-right: 4px; }
.p122-num { width: 96px; }
.p122-and { font-size: 12px; color: var(--text-3); }
.p122-x { flex: none; }
.p122-link {
  background: none; border: 0; padding: 0; font: inherit; color: var(--brand); cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
}
.p122-note {
  display: flex; align-items: flex-start; gap: 8px; margin: 2px 4px 6px; padding: 8px 10px;
  border-radius: 10px; background: var(--surface-2); color: var(--text-2); font-size: 12.5px; line-height: 17px;
}
.p122-note > .ic { flex: none; margin-top: 2px; }
.p122-add { gap: 6px; }

/* ---- 3. the Filter's conditions, above the quick value chips ----------- */
.p122-conds { padding: 12px 0 10px; border-bottom: 1px solid var(--line-2); }
.p122-ch { display: flex; align-items: center; gap: 10px; min-height: 28px; margin-bottom: 6px; }
.p122-ch b { font-size: 13px; font-weight: 600; }
.p122-ch .p122-seg { margin-left: auto; }
.p122-cond { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 4px 0; }
.p122-cond .p122-sel { max-width: 200px; }
.p122-cond > .p122-in[type="text"] { flex: 1 1 140px; }
.p122-picks { display: flex; flex-wrap: wrap; gap: 6px; flex: 1 1 100%; order: 5; padding: 2px 0 4px; }
.p122-picks .qf-chip { height: 28px; }
/* a label's colour, as a small square with the chip edge (never a dot) */
.p122-sw { width: 9px; height: 9px; border-radius: 3px; flex: none; background: var(--lab, var(--text-3)); box-shadow: var(--chip-ring); }
.p122-cf { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 6px; }
.p122-cf .p122-muted { flex: 1 1 100%; }
/* "Save to this view" in the head's condition line reads as the one action
   there, by its rim and its weight, never a fill */
.p122-save { gap: 6px; font-weight: 600; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--brand) 55%, transparent); color: var(--brand); }
@media (max-width: 700px) {
  /* the bar's save stays a labelled 44px control on a phone, beside "N filters" */
  .bfil .p122-save { display: inline-flex; }
  .p122-cond .p122-sel { max-width: none; flex: 1 1 40%; height: 40px; }
  .p122-cond .p122-in { height: 40px; }
  .p122-picks .qf-chip { height: 36px; }
}

/* ---- 4. View settings -------------------------------------------------- */
.p122-vs-pop { padding: 8px; }
.p122-vs { width: min(400px, calc(100vw - 32px)); display: flex; flex-direction: column; }
.p122-vs-head { display: flex; flex-direction: column; gap: 2px; padding: 6px 6px 10px; }
.p122-vs-head h3 { margin: 0; font: 600 16px/22px var(--font-display); letter-spacing: -.01em; color: var(--text-1); }
.p122-vs-head span { font-size: 12px; color: var(--text-3); overflow-wrap: anywhere; }
.p122-vsec { padding: 10px 6px; border-top: 1px solid var(--line-2); }
.p122-vh { display: flex; align-items: center; gap: 8px; min-height: 28px; margin-bottom: 6px; }
.p122-vh h4 { margin: 0; font: 600 12.5px/16px var(--font-body); letter-spacing: 0; color: var(--text-2); }
.p122-vh .btn { margin-left: auto; }
.p122-cols { list-style: none; margin: 0; padding: 0; max-height: 280px; overflow-y: auto; }
.p122-col { display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 1px 2px; border-radius: 8px; }
.p122-col.p122-lift { background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--line-1); }
.p122-grip {
  flex: none; display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 28px;
  padding: 0; border: 0; border-radius: 6px; background: none; color: var(--text-3); cursor: grab; touch-action: none;
}
.p122-grip:disabled { cursor: default; opacity: .4; }
.p122-col.p122-lift .p122-grip { cursor: grabbing; }
/* a column's name is never cut: it wraps */
.p122-ct { flex: 1 1 auto; min-width: 0; font-size: 13px; line-height: 18px; color: var(--text-1); overflow-wrap: anywhere; }
.p122-col.off .p122-ct { color: var(--text-3); }
.p122-ty { flex: none; font-size: 11.5px; color: var(--text-3); }
.p122-eye { flex: none; }
.p122-col.off .p122-eye { color: var(--text-3); }
.p122-srow { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 6px; }
.p122-sn { flex: none; width: 56px; font-size: 12px; color: var(--text-3); }
.p122-srow .p122-sel:first-of-type { flex: 1 1 140px; }
.p122-wide { width: 100%; }
/* the base `label` and `label input` rules (app.css:411) are block-level;
   this one row is a tick box beside its words */
label.p122-def { display: flex; align-items: center; gap: 10px; margin: 0; font-weight: 500; font-size: 13px; color: var(--text-1); cursor: pointer; }
label.p122-def input { display: inline-block; width: 16px; height: 16px; margin: 0; flex: none; }
@media (max-width: 700px) {
  .p122-vs { width: auto; }
  .p122-col { min-height: 44px; }
  .p122-grip { width: 40px; height: 40px; }
  .p122-vs .p122-sel { height: 40px; }
  .p122-vs .chip-btn { height: 40px; padding: 0 14px; }
  .p122-cols { max-height: none; }
}

/* ---- 5. the table: a group drawn from a column's value ------------------
   Its header is a real group's header; its colour is the value's own chip
   colour on the spine (--gcolor). Its title is not renamed in place. */
.group-title.p122-dg { cursor: default; }

/* ---- 6. the view's row height ------------------------------------------- */
.app .board-body[data-p122-rowh="short"] .board-table tr.item-row > td { height: 32px; }
.app .board-body[data-p122-rowh="medium"] .board-table tr.item-row > td { height: 40px; }
.app .board-body[data-p122-rowh="tall"] .board-table tr.item-row > td { height: 64px; }
/* tall: text wraps to two lines instead of ending in an ellipsis */
.app .board-body[data-p122-rowh="tall"] .board-table tr.item-row > td .cell-text,
.app .board-body[data-p122-rowh="tall"] .board-table tr.item-row > td.col-name .item-name {
  white-space: normal; overflow: hidden; text-overflow: clip; overflow-wrap: anywhere;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
}

/* ---- 7. frozen columns (above 700px, where the name column is frozen) ---
   The first N of the view's columns are sticky beside the name at the
   offsets board.js writes (--p122-l1..3), in every row the table has: the
   title row, the rows, the subitem rows and the totals. Opaque, like the
   name column, with the row's own hover and selected grounds kept; the last
   one carries the hairline edge the name column carries. */
@media (min-width: 701px) {
  .app .board-scroll[data-p122-fz] .board-table tr > :nth-child(2),
  .app .board-scroll[data-p122-fz="2"] .board-table tr > :nth-child(3),
  .app .board-scroll[data-p122-fz="3"] .board-table tr > :nth-child(3),
  .app .board-scroll[data-p122-fz="3"] .board-table tr > :nth-child(4) { position: sticky; z-index: 2; background: var(--ground); }
  .app .board-scroll[data-p122-fz] .board-table tr > :nth-child(2) { left: var(--p122-l1); }
  .app .board-scroll[data-p122-fz] .board-table tr > :nth-child(3) { left: var(--p122-l2); }
  .app .board-scroll[data-p122-fz] .board-table tr > :nth-child(4) { left: var(--p122-l3); }
  .app .board-scroll[data-p122-fz="1"] .board-table tr > :nth-child(2),
  .app .board-scroll[data-p122-fz="2"] .board-table tr > :nth-child(3),
  .app .board-scroll[data-p122-fz="3"] .board-table tr > :nth-child(4) { box-shadow: inset -1px 0 0 var(--p112-hair); }
  .app .board-scroll[data-p122-fz] .board-table tr.item-row:hover > td { background: var(--p112-hover); }
  [data-theme="dark"] .app .board-scroll[data-p122-fz] .board-table tr.item-row.p113-on > td { background: color-mix(in srgb, #0070c1 8%, var(--p112-ground)); }
  :root:not([data-theme="dark"]) .app .board-scroll[data-p122-fz] .board-table tr.item-row.p113-on > td { background: color-mix(in srgb, #0070c1 7%, var(--p112-ground)); }
  /* the strip's frozen titles ride with the name's (board.js colsStripHTML) */
  .app .board-cols .bcols-pin .bcol.p122-fzs { flex: 0 0 auto; pointer-events: auto; background: var(--ground); }
  .app .board-cols .bcols-pin .bcol.p122-fzs:last-child { box-shadow: inset -1px 0 0 var(--p112-hair); }
}

/* ---- 8. Undo ------------------------------------------------------------ */
.toast.p122-undo { display: flex; align-items: center; gap: 14px; padding: 7px 7px 7px 18px; }
.p122-undo-btn {
  display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 12px; border-radius: 999px;
  border: 1px solid color-mix(in srgb, currentColor 45%, transparent); background: transparent;
  color: inherit; font: 600 13px/1 var(--font-body); cursor: pointer;
}
.p122-undo-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* on a board the dock owns the bottom centre, so a toast stands above it
   rather than over it (its Undo must be reachable, and the dock too) */
@media (min-width: 701px) {
  html:has(.app .board-head .p112-dock) #toasts { bottom: 104px; }
}
@media (max-width: 700px) {
  .p122-undo-btn { height: 40px; }
}

/* ---- 9. import a spreadsheet ------------------------------------------- */
.p122-imp-wait { padding: 32px 0; display: flex; justify-content: center; }
.p122-ipv { display: flex; flex-direction: column; gap: 10px; }
.p122-isum { display: flex; align-items: flex-start; gap: 8px; margin: 0; font-size: 13px; color: var(--text-2); overflow-wrap: anywhere; }
.p122-isum > .ic { flex: none; margin-top: 2px; color: var(--text-3); }
.p122-itarget { margin: 2px 0; }
.p122-ifields { display: flex; flex-wrap: wrap; gap: 10px 14px; }
.p122-ifields label { flex: 1 1 200px; margin: 0; }
.p122-ih { margin: 6px 0 0; font: 600 12.5px/16px var(--font-body); letter-spacing: 0; color: var(--text-2); }
.p122-icols { display: flex; flex-direction: column; max-height: 300px; overflow-y: auto; border-top: 1px solid var(--line-2); }
.p122-icol { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 6px 2px; border-bottom: 1px solid var(--line-2); }
.p122-icol.off .p122-ititle { color: var(--text-3); }
label.p122-inc { display: inline-flex; align-items: center; margin: 0; flex: none; }
label.p122-inc input { display: inline-block; width: 16px; height: 16px; margin: 0; }
.p122-ititle { flex: 1 1 160px; }
.p122-icol .p122-sel { max-width: 240px; }
.p122-lost { font-size: 12px; color: var(--text-2); font-weight: 600; }
.p122-ptable { max-height: 260px; overflow: auto; border: 1px solid var(--line-2); border-radius: 10px; }
.p122-ptable:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.p122-ptable table { border-collapse: collapse; font-size: 12.5px; min-width: 100%; }
.p122-ptable th, .p122-ptable td { padding: 6px 10px; text-align: left; white-space: nowrap; border-bottom: 1px solid var(--line-2); max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
.p122-ptable th { position: sticky; top: 0; background: var(--surface-1); font-weight: 600; color: var(--text-1); }
.p122-ptable th span { display: block; font-weight: 500; font-size: 11px; color: var(--text-3); }
.p122-ptable .off { color: var(--text-3); text-decoration: line-through; }
.p122-ptable .p122-pname { font-weight: 600; }
.p122-idone { display: flex; flex-direction: column; gap: 6px; padding: 8px 0 0; }
.p122-idone p { margin: 0; font-size: 13.5px; color: var(--text-2); }
.p122-idone .p122-ilead { font: 600 17px/24px var(--font-display); color: var(--text-1); }
/* a spreadsheet dragged over the board: its edge says it will be taken */
html.p122-dropping #board-body { box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--brand) 55%, transparent); border-radius: 14px; }
@media (max-width: 700px) {
  .p122-icol .p122-sel, .p122-ititle { flex: 1 1 100%; max-width: none; height: 40px; }
}

/* phase-122-b (lead's walk at 375, 2026-09-23): View settings on a phone had
   22 controls under 36px - the column eyes at 27x25, Show all and Add sort at
   28, the default tick at 16. The owner reviews on a phone; every control he
   must hit is 44px there. The eye keeps its 16px glyph inside a 44px target. */
@media (max-width: 700px) {
  .p122-vs-pop .p122-eye { width: 44px; height: 44px; min-width: 44px; min-height: 44px; }
  .p122-vs-pop [data-act="p122-vs-showall"], .p122-vs-pop .p122-add { min-height: 44px; height: auto; padding-left: 14px; padding-right: 14px; }
  .p122-vs-pop .p122-def { min-height: 44px; display: flex; align-items: center; gap: 12px; }
  .p122-vs-pop .p122-def input[type=checkbox] { width: 22px; height: 22px; flex: none; }
}

/* ============================================================== phase-124
   SHORTCUTS, COLUMN WIDTHS BY HAND, WHAT A CARD SHOWS (customize.js; the
   handle is drawn by board.js, the card by panels.js, the kanban by
   views.js). One block, every selector prefixed p124-. Nothing here sets
   backdrop-filter or blurs anything (the "?" sheet is the existing modal and
   Customize card the existing popover, so each keeps its own material and a
   phone's solid chrome is untouched), nothing restyles an existing hover,
   and nothing colours data with the logo hues. Tokens fall back to the
   app's own (--text-*, --line-*) because the --p112-* set exists only above
   700px. No transition of its own: the controls that fade reuse a class
   that already does (.p119-ic-all), and the phase-122 block's own reader
   (tests) reads to the end of the file. No td or th is rounded (D37). */

/* ---- 1. the key cap: quiet, at the row's right edge --------------------
   The cap IS the dock search's keycap (.p112-kbd), so its face is the one
   monospace from the rule that already answers keyboard hints (:10853,
   phase-119); this rule sets its box everywhere (the .p112-kbd box is
   drawn above 700px only) and never its family. */
.p124-keys { display: inline-flex; align-items: center; gap: 3px; flex: none; margin-left: auto; padding-left: 12px; }
.p124-kc {
  display: inline-flex; align-items: center; justify-content: center; min-width: 18px; height: 18px; margin: 0; padding: 0 5px;
  border: 1px solid var(--p112-kbd, var(--line-2)); border-radius: 6px; background: none; box-shadow: none;
  font-size: 11px; line-height: 14px; font-weight: 500; color: var(--p112-t3, var(--text-3));
}
.p124-kc > .ic { width: 11px; height: 11px; }
/* the palette's row already ends in its hint; the keys follow it */
.palette-row .p124-keys { margin-left: 8px; padding-left: 0; }

/* ---- 2. the "?" sheet ------------------------------------------------- */
@media (min-width: 701px) {
  .modal.p124-keys-m { width: min(780px, 94vw); }
}
.p124-sheet { display: flex; flex-direction: column; gap: 18px; }
.p124-sh-head h2 { margin: 0; font: 600 20px/28px var(--font-display); letter-spacing: -.01em; color: var(--text-1); }
.p124-sh-head p { margin: 4px 0 0; font-size: 13px; line-height: 18px; color: var(--text-3); }
.p124-sh-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(216px, 1fr)); gap: 20px 28px; align-items: start; }
.p124-sh-sec h3 { margin: 0 0 6px; font: 600 12.5px/16px var(--font-body); letter-spacing: 0; text-transform: none; color: var(--text-2); }
.p124-sh-sec dl { margin: 0; }
.p124-sh-row { display: flex; align-items: center; gap: 12px; min-height: 32px; padding: 4px 0; border-top: 1px solid var(--line-2); }
.p124-sh-row:first-child { border-top: 0; }
.p124-sh-row dt { flex: none; display: inline-flex; align-items: center; gap: 4px; min-width: 84px; }
.p124-sh-row dd { margin: 0; min-width: 0; font-size: 13px; line-height: 18px; color: var(--text-1); overflow-wrap: anywhere; }
.p124-then { padding: 0 1px; font-size: 11px; line-height: 14px; color: var(--text-3); }

/* ---- 3. the column title's edge: a 6px handle, pointer only ------------
   It straddles the divider, shows a 1px line under the pointer, and is not
   there at all on a phone or a touch screen (the column menu's Width row
   is theirs). The title's own cell is already positioned (th: relative,
   :1986; a frozen th: sticky; .bcol: relative, :6623). */
.p124-rz { position: absolute; top: 0; bottom: 0; right: -3px; z-index: 3; width: 6px; cursor: col-resize; touch-action: none; }
.p124-rz::after {
  content: ""; position: absolute; top: 6px; bottom: 6px; left: 2.5px; width: 1px; border-radius: 1px;
  background: var(--p112-t3, var(--text-3)); opacity: 0;
}
.p124-rz:hover::after { opacity: .8; }
html.p124-sizing, html.p124-sizing * { cursor: col-resize; user-select: none; }
@media (max-width: 700px), (hover: none), (pointer: coarse) {
  .p124-rz { display: none; }
}

/* ---- 4. the row J and K land on ---------------------------------------
   The row header takes the focus; the row wears the same soft bar the
   pointer gives it (phase-116-b), and the header the one 2px focus ring
   every non-text control answers with (checklist 17/24). A ticked row keeps
   its own tint. Square ends: D37 rounds the hover's end caps and no other
   cell. */
.app .board-table tr.item-row > td.col-name:focus-visible { outline: none; box-shadow: none; } /* 124-b: no blue ring, see below */
.app .board-table tr.item-row:not(.p113-on):has(> td.col-name:focus-visible) > td { background: var(--p112-hover, var(--hover-ground)); }

/* ---- 5. the item card: pinned fields beside the title, Customize card -- */
.p124-pins { display: flex; flex-wrap: wrap; gap: 8px 24px; margin: 12px 0 0; }
.p124-pins[hidden] { display: none; }
.p124-pin { display: flex; flex-direction: column; gap: 2px; min-width: 0; max-width: 100%; }
.p124-pin-l { font: 400 12px/16px var(--font-body); color: var(--p112-t3, var(--text-3)); overflow-wrap: anywhere; }
.p124-pins .cell.inline {
  display: inline-flex; align-items: center; min-width: 0; min-height: 28px; margin-left: -8px; padding: 4px 8px;
  border-radius: 8px; cursor: pointer; text-align: left;
  font: 400 13.5px/20px var(--font-body); color: var(--p112-t1, var(--text-1));
}
.p124-pins .cell.inline:hover { background: var(--p112-hover, var(--hover-ground)); }
.p124-icf-acts { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 4px 0 0 -8px; }
.p124-icf-acts .p119-ic-all { margin: 0; }
/* Customize card is the "Show all N fields" control's twin (.p119-ic-all:
   its box, its ink, its hover and its focus), with an icon */
.p124-cc-btn { gap: 6px; }
.p124-cc-btn > .ic { width: 14px; height: 14px; }

/* Customize card: View settings' own parts (p122-), in its own wrapper so
   the Columns list's drag (customize.js, .p122-vs) never answers here */
.p124-cc-pop { padding: 8px; }
.p124-cc { width: min(400px, calc(100vw - 32px)); display: flex; flex-direction: column; }
.p124-cc .p122-col.p124-on .p122-ct { font-weight: 600; }
.p124-pinb { flex: none; display: inline-flex; align-items: center; gap: 5px; }
.p124-pinb > .ic { width: 13px; height: 13px; }
.p124-cc-full { margin: 6px 2px 0; }

/* ---- 6. a kanban card's chosen fields ------------------------------------ */
.p124-kl {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; min-width: 0;
  font: 400 12px/16px var(--font-body); color: var(--p112-t2, var(--text-2));
}
.p124-kv { min-width: 0; overflow-wrap: anywhere; }
.p124-kf-note { margin: 8px 0 0; }

/* ---- 7. phones: every control here is a 44px target -------------------- */
@media (max-width: 700px) {
  .p124-cc { width: auto; }
  .p124-cc .p122-grip { width: 44px; height: 44px; }
  .p124-cc .p124-pinb { min-height: 44px; padding: 0 14px; }
  .p124-cc .p122-def { min-height: 44px; display: flex; align-items: center; gap: 12px; }
  .p124-cc .p122-def input[type=checkbox] { width: 22px; height: 22px; flex: none; }
  .p124-cc-btn, .p124-icf-acts .p119-ic-all { min-height: 44px; }
  .p122-vs-pop .p124-kf .chip-btn { min-height: 44px; padding: 0 14px; }
  .p122-vs-pop .p124-kf-note .p122-link { display: inline-flex; align-items: center; min-height: 44px; }
}

/* phase-124-b (lead, 2026-09-23): the row J/K lands on wore the 2px BLUE
   --focus-ring on its name cell (phase-124 §4). The owner's own words about
   exactly this: "the blue edge keeps coming back" (2026-09-22). The row now
   answers the keyboard the way it answers the pointer - the one soft bar with
   rounded ends (checklist 53) - a step brighter than hover so "where am I"
   still reads without any ring. No blue anywhere on the row. */
.app .board-table tr.item-row > td.col-name:focus-visible { box-shadow: none; outline: none; }
.app .board-table tr.item-row:not(.p113-on):has(> td.col-name:focus-visible) > td {
  background: color-mix(in srgb, var(--text-1) 7%, var(--ground, var(--surface-0)));
}
@media (min-width: 701px) {
  .app .board-table tr.item-row:has(> td.col-name:focus-visible) > td:first-child { border-radius: 10px 0 0 10px; }
  .app .board-table tr.item-row:has(> td.col-name:focus-visible) > td:last-child { border-radius: 0 10px 10px 0; }
}

/* ════════════════════════════════════════════════════════════════ phase-127-b
   The second whole-app audit, board side (design/approved-2026-09-23/
   AUDIT-2026-09-23-b.md; numbers are the audit's). No glass, no blur, no
   transition in this block: it sits after the phone "chrome is solid" blocks
   and adds nothing they would have to answer. It stands just BEFORE the
   phase-126 block, not after it: that block's own suite check requires it
   to be the last in the file, and nothing in it styles what this one does,
   so the cascade is the same either way. ---- */

/* ---- 1. the dock never overflows (audit-b 1). board.js dockFit folds the
   views that do not fit into "More views"; a folded tab takes no room. The
   control is the dock's own 32px item in the views' well, with its word. */
.app .board-head .p112-dock .view-tabs .view-tab.p127-out { display: none; }
.p127-more[hidden] { display: none !important; }
.app .board-head .p112-dock .p127-more {
  position: relative; z-index: 1; flex: none; display: inline-flex; align-items: center; gap: 6px;
  height: 32px; padding: 0 10px; margin: 0; border: 0; border-radius: 12px; background: none; cursor: pointer;
  color: var(--p112-t2); font: 400 13.5px/20px var(--font-body); white-space: nowrap;
}
.app .board-head .p112-dock .p127-more > .ic { width: 14px; height: 14px; }
.app .board-head .p112-dock .p127-more:hover { color: var(--p112-t1); background: var(--p112-hover); }
@media (max-width: 700px) { .p127-more { display: none !important; } }
.p127-vmenu a.menu-item { display: flex; align-items: center; gap: 8px; color: var(--text-1); text-decoration: none; }
.p127-vmenu .p127-vn { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }

/* ---- 3. the frozen totals cell is solid (audit-b 3). Its background was
   transparent (phase-116), so at scrollLeft 400 the status capsules slid
   under the sticky name cell and showed through it. It takes the page
   ground the rows' name cells take, in both themes (--ground is one token),
   and the same 12px fade edge the rows draw while the table is scrolled
   (--shade, board.js). The earlier `.summary-row td:first-child::after
   { display: none }` is out-ranked here, on purpose. */
@media (min-width: 701px) {
  .app .board-table .summary-row td.p113-sum-n { background: var(--ground); }
  .app .board-table .summary-row td.p113-sum-n::after {
    content: ""; display: block; position: absolute; top: 0; bottom: 0; right: -12px; width: 12px; pointer-events: none;
    opacity: var(--shade, 0); background: linear-gradient(90deg, var(--ground), transparent);
  }
}

/* ---- 8. View settings: one scroll, above the dock (audit-b 8). The sheet
   is placed and capped by customize.js vsPlace (its bottom 8px over the
   dock's top); inside it the column list no longer scrolls on its own (680
   of content in a 280 box inside a sheet that also scrolled): the sheet is
   the one scroller, and its offset survives every change (vsRepaint). */
@media (min-width: 701px) {
  .p122-vs-pop .p122-cols { max-height: none; overflow: visible; }
}
.p127-sortnote { margin: 4px 0 6px; }

/* ---- 9. Add column shows all sixteen (audit-b 9, first audit 20). 16 types
   in a 320px box hid Created, Last updated and ID number below the fold
   with ~440px free under it. Above 700px the menu is two columns, its
   headings across both, and no box of its own: the popover's still frame
   (phase-116-f) scrolls it only when the window is truly short. */
@media (min-width: 701px) {
  .menu.cols {
    max-height: none; display: grid; grid-template-columns: repeat(2, minmax(150px, 1fr));
    column-gap: 4px; align-content: start;
  }
  .menu.cols > .menu-label, .menu.cols > .menu-sep { grid-column: 1 / -1; }
}

/* ---- first audit 11: names cut elsewhere. The dashboard's "By group" (and
   every list it draws with the same row) and the Files view's file names
   were cut with an ellipsis; a name is never cut (checklist 10): it wraps.
   NOT the Files card's document line (.fv-doc-t): defect 53 made that
   ellipsis fire on purpose and a suite check holds it there, so it is the
   lead's call, reported, not changed here. The board's own name column on
   a narrow board is phase-121's rule, not repeated here. */
.wl-name { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
.files-view .fv-name { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }

/* ---- 10. The toast stands on the dock's centre line (designer G1). On a
   board the dock is centred on the content column (50% + 144px sidebar open,
   50% + 50px closed), and the toast above it was centred on the window:
   144px off it. Same left, same step aside for the item card / a side panel
   (the dock's own transform), the gap above the dock unchanged. Phone and
   off-board pages keep the window centre (no dock there to agree with). */
@media (min-width: 701px) {
  html:has(.app .board-head .p112-dock) #toasts { left: calc(50% + 144px); }
  html[data-snav="closed"]:has(.app .board-head .p112-dock) #toasts { left: calc(50% + 50px); }
  html.p119-side:has(.app .board-head .p112-dock) #toasts { transform: translateX(calc(-50% - (var(--p113-icw) / 2 - 4px))); }
  /* a panel is open but the dock did not fit beside it (html.p119-rp without
     p119-side): the dock keeps its resting place, so the toast does too. This
     is the dock's own rule and it beats the item card's step aside (qa Q1) */
  html.p119-rp:not(.p119-side):has(.app .board-head .p112-dock) #toasts { transform: translateX(-50%); }
}
@media (min-width: 1440px) {
  html.p113-icard:has(.app .board-head .p112-dock) #toasts { transform: translateX(calc(-50% - (var(--p113-icw) / 2 - 4px))); }
}

/* ---- 11. One toast per action (designer G2). A status move's hint rides in
   the Undo toast as a second, quieter line: same material, no fill of its
   own, the words exactly as the server wrote them. Two lines are not a
   pill, so the capsule eases to a soft card only when the hint is there. */
.toast.p122-undo > span:first-child { display: block; min-width: 0; }
.p127-hint { display: block; margin-top: 3px; font-weight: 400; font-size: 12.5px; line-height: 1.35; opacity: .78; }
.toast.p122-undo:has(.p127-hint) { border-radius: 22px; }

/* ---- 12. View settings keeps its scroll exactly (qa Q5). The browser's scroll
   anchoring moves a scrolled box when something above the visible part changes
   height (a hint line, an unsaved note): 424.5 became 386.5. The sheet is
   redrawn on every change and puts its own offset back, so it opts out. */
/* groundskeeping (phase-135): the `.popover[data-p122-vs] { overflow-anchor: none; }` rule was removed; phase-129 §3's `.p122-vs-pop` rule covers the same element (the sheet carries both the class and the attribute) and its two scrollers. */

/* ==========================================================================
   The second audit, card and phone side: phase-127-c (design/approved-
   2026-09-23/AUDIT-2026-09-23-b.md). One block, every selector prefixed
   p127- or scoped to the surface it corrects. Nothing here sets
   backdrop-filter, blurs or animates anything (the phase-122, -124 and -126
   readers each hold every block after theirs to that; the heading does not
   open on the word "phase" for the same reason), nothing colours data with
   a logo hue, no dots, and a keyboard focus on these controls is the lit
   ground, never a blue ring (checklist 55: "the blue edge keeps coming
   back").
   It sits just ABOVE the phase-126 block, not after it, and that is the
   cascade's end for every selector here: phase-126 holds only p126- rules,
   and its suite reader reads from its heading to the end of the file as its
   own block, so anything appended after it turns that reader red. Until qa
   bounds that reader at the next heading, this is the one place a later
   block can go. */

/* ---- 1. the item card: the fields scroll inside their own box (audit-b 7)
   Open, "Show all N fields" no longer pushes the tabs below the fold or
   "Show fewer fields" off the card: the rows scroll in a box that leaves
   the card's head, meta, tabs and composer on screen (about 520px of them
   at every width). -8px out and 8px in keeps a value's hover ground whole. */
.p127-icf-list { display: flex; flex-direction: column; min-height: 0; }
.p113-ic-fields.p127-open .p127-icf-list {
  max-height: max(168px, calc(100vh - 520px));
  overflow-y: auto; overscroll-behavior: contain;
  margin: 0 -8px; padding: 0 8px;
  box-shadow: inset 0 -1px 0 var(--p112-hair, var(--line-2));
}
@supports (height: 100dvh) {
  .p113-ic-fields.p127-open .p127-icf-list { max-height: max(168px, calc(100dvh - 520px)); }
}

/* ---- 2. the pinned fields read as fields, not text boxes (audit-b 11) --
   .p124-pins also wears .item-values, a flex COLUMN, so each pin was a
   full-width 56px block; and .cell.inline's old 1px border (:927) framed
   the value. Beside the title, in a row, unframed: the status fix of
   audit 12, done here too. */
.p124-pins.item-values { flex-direction: row; align-items: flex-start; }
.p124-pin { flex: 0 1 auto; }
.p124-pins .cell.inline { border: 0; background: none; width: auto; }

/* ---- 3. the palette: whole names, the hint beside them (audit-b 20) ---- */
.palette-row .pr-label { flex: 1 1 auto; min-width: 0; white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; }
.palette-row .pr-hint { flex: 0 1 auto; min-width: 0; max-width: 45%; text-align: right; overflow-wrap: anywhere; }
.pr-more { margin: 0; padding: 8px 10px 4px; font: 400 12px/16px var(--font-body); color: var(--text-3); }

/* ---- 4. the date picker (first audit 17) -------------------------------
   The popover's own material; inside the Filter it sits in a quiet well.
   A day is a 34px button (44px on a phone); the chosen day and the days
   of a range take a ground, today is underlined, the keyboard's day is
   lit a step brighter. All colour comes from --text-1, so it inverts with
   the theme by itself. */
.p127-dp-pop { padding: 12px; }
.p127-dp {
  --p127-hov: var(--p112-hover, var(--hover-ground));
  --p127-mid: color-mix(in srgb, var(--text-1) 6%, transparent);
  --p127-key: color-mix(in srgb, var(--text-1) 11%, transparent);
  --p127-on: color-mix(in srgb, var(--text-1) 16%, transparent);
  --p127-onkey: color-mix(in srgb, var(--text-1) 24%, transparent);
  display: flex; flex-direction: column; gap: 8px; width: 292px; max-width: 100%;
}
.p127-dp.p127-inline {
  width: 100%; max-width: 336px; margin: 4px 0 6px; padding: 10px;
  border-radius: 14px; background: var(--p112-well, var(--surface-2));
}
.p127-dp-type { display: flex; gap: 8px; }
.p127-dp-f { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 4px; margin: 0; }
.p127-dp-fl { font: 500 12px/16px var(--font-body); color: var(--text-3); overflow-wrap: anywhere; }
.p127-dp-in { width: 100%; height: 34px; margin: 0; padding: 0 10px; border-radius: 10px; font: 400 13.5px/20px var(--font-body); }
.p127-dp-read { margin: 0; min-height: 16px; font: 400 12px/16px var(--font-body); color: var(--text-3); overflow-wrap: anywhere; }
.p127-dp-read.err { color: var(--danger); }
.p127-dp-nav { display: flex; align-items: center; gap: 8px; }
.p127-dp-title { flex: 1 1 auto; min-width: 0; text-align: center; font: 600 14px/20px var(--font-display); color: var(--text-1); }
.p127-dp-step, .p127-dp-b, .p127-cd-cal, .p127-kp-b {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; flex: none;
  border: 0; cursor: pointer; background: none; color: var(--text-2);
}
.p127-dp-step { width: 32px; height: 32px; padding: 0; border-radius: 10px; }
.p127-dp-b { height: 30px; padding: 0 10px; border-radius: 10px; font: 500 12.5px/16px var(--font-body); }
.p127-dp-b > .ic, .p127-dp-step > .ic { flex: none; }
.p127-dp-step:hover, .p127-dp-b:hover, .p127-cd-cal:hover, .p127-kp-b:hover:not(:disabled) { background: var(--p127-hov, var(--hover-ground)); color: var(--text-1); }
.p127-dp-step:focus-visible, .p127-dp-b:focus-visible, .p127-cd-cal:focus-visible, .p127-kp-b:focus-visible {
  outline: none; box-shadow: none; background: color-mix(in srgb, var(--text-1) 11%, transparent); color: var(--text-1);
}
.p127-dp-grid { width: 100%; border-collapse: separate; border-spacing: 2px; table-layout: fixed; margin: 0; }
.p127-dp-grid th { padding: 0 0 4px; font: 500 11px/14px var(--font-body); color: var(--text-3); text-align: center; overflow: hidden; }
.p127-dp-grid td { padding: 0; text-align: center; }
.p127-dp-day {
  width: 100%; height: 34px; display: inline-flex; align-items: center; justify-content: center;
  padding: 0; border: 0; border-radius: 10px; background: none; cursor: pointer;
  font: 400 13px/1 var(--font-body); font-variant-numeric: normal; color: var(--text-1);
}
.p127-dp-day:hover { background: var(--p127-hov); }
.p127-dp-day.mid { background: var(--p127-mid); }
.p127-dp-day.on { background: var(--p127-on); font-weight: 600; }
.p127-dp-day.now { font-weight: 700; text-decoration: underline; text-decoration-thickness: 2px; text-underline-offset: 4px; }
.p127-dp-day:focus-visible { outline: none; box-shadow: none; background: var(--p127-key); font-weight: 600; }
.p127-dp-day.on:focus-visible { background: var(--p127-onkey); }
.p127-dp-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; margin: 0 -4px -2px; }
.p127-dp-save { margin-left: auto; color: var(--text-1); }

/* the Filter's date values: a field and its calendar button, the picker
   under the condition at the row's whole width */
.p127-cd { display: inline-flex; align-items: center; gap: 4px; flex: 1 1 150px; min-width: 0; }
.p127-cd-in { flex: 1 1 auto; min-width: 0; }
.p127-cd-cal { width: 32px; height: 32px; padding: 0; border-radius: 10px; }
.p127-cd-cal[aria-expanded="true"] { background: color-mix(in srgb, var(--text-1) 16%, transparent); color: var(--text-1); }
.p127-cd-pick { flex: 1 0 100%; min-width: 0; }

/* ---- 5. the phone kanban pager (audit-b 24): hidden where every lane is
   on screen side by side */
.p127-kpager { display: none; }

@media (max-width: 700px) {
  /* ---- 6. the phone item sheet sits ABOVE the bottom bar (audit-b 10) --
     The card was the generic sheet, 92vh tall and scrolling as a whole, so
     the update box ran to y 822 on an 812 screen. The scrim now stops at
     the bar (--bar-h already carries the safe area), the sheet is no taller
     than the room above it, and the card's own middle scrolls while the
     composer stays pinned at its foot. */
  #modal-root .modal-overlay:has(> .p113-icm) { bottom: var(--bar-h); }
  .modal.p113-icm {
    display: flex; flex-direction: column; overflow: hidden;
    max-height: calc(100vh - var(--bar-h) - 12px); padding-bottom: 12px;
  }
  .modal.p113-icm > .p113-ic { flex: 1 1 auto; min-height: 0; }
  /* (audit-b 28) the sheet's tabs are 44px targets: Files measured 34x44 */
  .p113-ic-tabs .item-tab { min-width: 44px; min-height: 44px; }

  /* ---- 7. the phone kanban (audit-b 24): one lane per screen, snapping;
     the lane's title stays put and its cards scroll under it; the page is
     no taller than the screen above the bar (views.js measures where the
     lanes start, --p119-kt, now on a phone too). It was a 16,973px page
     whose lane titles scrolled away. */
  #board-body:has(> .p113-kanban) { padding-bottom: 0; }
  .p127-kpager { display: flex; align-items: center; gap: 8px; margin: 0 0 8px; }
  .p127-kp-b {
    min-width: 44px; min-height: 44px; padding: 0 12px; border-radius: 12px;
    background: var(--surface-2); color: var(--text-1); font: 500 13.5px/20px var(--font-body);
  }
  .p127-kp-b:disabled { opacity: .4; cursor: default; }
  .p127-kp-at { flex: 1 1 auto; min-width: 0; text-align: center; font: 500 13.5px/18px var(--font-body); color: var(--text-2); overflow-wrap: anywhere; }
  .app .p113-kanban {
    display: flex; gap: 12px; align-items: stretch; box-sizing: border-box;
    height: calc(100vh - var(--p119-kt, 220px) - var(--bar-h) - 12px); min-height: 320px;
    margin: 0; padding: 0 0 4px; overflow-x: auto; overflow-y: hidden; overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory; scrollbar-width: none;
  }
  .app .p113-kanban::-webkit-scrollbar { display: none; }
  .app .p113-kanban .kanban-col {
    flex: 0 0 100%; width: 100%; min-width: 0; max-height: 100%; min-height: 0;
    display: flex; flex-direction: column; scroll-snap-align: start; scroll-snap-stop: always;
  }
  .app .p113-kanban .kanban-col-head { flex: none; }
  .app .p113-kanban .kanban-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }

  /* ---- 8. the date picker as a sheet: 44px days and controls, and a
     16px field so the phone does not zoom into it */
  .p127-dp { width: auto; }
  .p127-dp-day { height: 44px; font-size: 15px; }
  .p127-dp-step, .p127-cd-cal { width: 44px; height: 44px; }
  .p127-dp-b { height: 44px; padding: 0 14px; }
  .p127-dp-in { height: 44px; font-size: 16px; }

  /* ---- 9. the Boards sheet's close X sits level with the + (audit-b 12)
     .bp-close is inline-flex with no cross-axis rule, so its icon stood at
     the top of its 44px box, 10px above the + beside it */
  .app .sidebar .bp-head .bp-close { align-items: center; justify-content: center; padding: 0; }
}
@supports (height: 100dvh) {
  @media (max-width: 700px) {
    .modal.p113-icm { max-height: calc(100dvh - var(--bar-h) - 12px); }
    .app .p113-kanban { height: calc(100dvh - var(--p119-kt, 220px) - var(--bar-h) - 12px); }
  }
}

/* ==========================================================================
   phase-126: created, last updated and ID columns; colour and icon for boards
   and workspaces; notification settings (the client half of phase-125).
   Every selector is prefixed p126-. Nothing here sets backdrop-filter, blurs,
   animates or restyles a hover the interaction language owns; the pickers
   and the settings are the existing popover (a bottom sheet under 701px).
   A board or workspace colour is DATA: one of the twelve chips, a filled
   swatch carries --chip-ring, and as an icon it takes the chip-ink lift
   (DESIGN_TOKENS §0c: the chip in light, 66% toward white in dark).
   ========================================================================== */

/* ---- 1. the filled-in columns: read-only, so nothing promises an editor -- */
.cell:is([data-type="created"], [data-type="updated"], [data-type="auto_id"]) { cursor: default; }
.p126-id { white-space: nowrap; }
.menu-item.p126-addauto { display: flex; align-items: center; gap: 8px; }
.menu-item.p126-addauto > .ic { flex: none; color: var(--text-3); }
.p126-auto-note { color: var(--text-3); }
.p126-eg { margin: -4px 0 12px; }

/* ---- 2. a board's mark and a workspace's spine -------------------------- */
.p126-mk { --chip-ink: var(--chip); color: var(--chip-ink); }
[data-theme="dark"] .p126-mk { --chip-ink: color-mix(in srgb, var(--chip) 66%, #fff); }
.p126-mk.p126-mk-plain { color: var(--p112-t3, var(--text-3)); }
/* the sidebar's current row lights its icon in the logo hue; a board's own
   mark is data and keeps its chip colour there */
.snav-row .snav-ri.p126-mk, .snav-row.on .snav-ri.p126-mk { color: var(--chip-ink); }
.snav-row .snav-ri.p126-mk.p126-mk-plain, .snav-row.on .snav-ri.p126-mk.p126-mk-plain { color: var(--p112-t3, var(--text-3)); }
/* the Boards panel: one 14px slot before every name, on the name's first line */
.sidebar .bp-row .p126-bmk { flex: none; align-self: flex-start; width: 14px; height: 20px; display: inline-flex; align-items: center; }
.sidebar .bp-row .p126-bmk > .ic { width: 14px; height: 14px; }
.p126-wsp { height: 14px; margin-right: 2px; }
.p126-wsi { flex: none; display: inline-flex; align-items: center; }
.p126-wsi > .ic { width: 13px; height: 13px; }
/* the board's eyebrow: its mark, then the workspace with its spine */
.p126-cmk { flex: none; display: inline-flex; align-items: center; }
.p126-cws { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.p126-cwn { min-width: 0; overflow-wrap: anywhere; }

/* ---- 3. the colour and icon picker -------------------------------------- */
.p126-look-pop { padding: 10px; }
.p126-look { width: min(300px, calc(100vw - 32px)); display: flex; flex-direction: column; }
.p126-lk-head { display: flex; align-items: center; gap: 8px; padding: 2px 4px 10px; min-width: 0; }
.p126-lk-head b { min-width: 0; font: 600 14px/20px var(--font-body); color: var(--text-1); overflow-wrap: anywhere; }
.p126-lk-mk { flex: none; display: inline-flex; }
.p126-lk-mk > .ic { width: 18px; height: 18px; }
.p126-lk-t { padding: 8px 4px 6px; font: 600 12.5px/16px var(--font-body); color: var(--text-2); }
.p126-sws, .p126-ics { display: flex; flex-wrap: wrap; gap: 8px 6px; padding: 0 4px; }
.p126-sw {
  width: 32px; height: 32px; padding: 0; border: 0; border-radius: 8px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--chip); box-shadow: var(--chip-ring); color: var(--p126-tick);
}
.p126-sw > .ic { width: 14px; height: 14px; }
.p126-sw:hover { outline: 1.5px solid color-mix(in srgb, var(--text-1) 40%, transparent); outline-offset: 2px; }
.p126-sw.on { outline: 2px solid var(--text-1); outline-offset: 2px; }
.p126-sw:focus-visible { outline: none; box-shadow: var(--chip-ring), var(--focus-ring); }
.p126-ic {
  width: 32px; height: 32px; padding: 0; border: 0; border-radius: 8px; cursor: pointer; background: none;
  display: inline-flex; align-items: center; justify-content: center; color: var(--text-2);
}
.p126-ics[style*="--chip"] .p126-ic { --chip-ink: var(--chip); color: var(--chip-ink); }
[data-theme="dark"] .p126-ics[style*="--chip"] .p126-ic { --chip-ink: color-mix(in srgb, var(--chip) 66%, #fff); }
.p126-ic > .ic { width: 18px; height: 18px; }
.p126-ic:hover { background: var(--p112-hover, var(--hover-ground)); }
.p126-ic.on { background: var(--p112-on, var(--brand-soft)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text-1) 28%, transparent); }
.p126-ic:focus-visible, .p126-none:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.p126-none {
  min-height: 32px; padding: 0 12px; border: 0; border-radius: 8px; cursor: pointer; background: none;
  font: 500 12.5px/16px var(--font-body); color: var(--text-2);
  box-shadow: inset 0 0 0 1px var(--line-1);
}
.p126-none:hover { color: var(--text-1); background: var(--p112-hover, var(--hover-ground)); }
.p126-none.on { color: var(--text-1); background: var(--p112-on, var(--brand-soft)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text-1) 28%, transparent); }
.p126-lk-note { margin: 8px 4px 0; font-size: 12px; line-height: 16px; color: var(--text-3); }

/* ---- 4. notifications ---------------------------------------------------- */
.p126-nt-pop { padding: 10px; }
.p126-nt { width: min(420px, calc(100vw - 32px)); display: flex; flex-direction: column; }
.p126-nt-head { display: flex; align-items: baseline; gap: 12px; padding: 4px 6px 8px; }
.p126-nt-head h2 { margin: 0; font: 600 16px/22px var(--font-display); letter-spacing: -.01em; color: var(--text-1); }
.p126-nt-saved { margin-left: auto; min-height: 16px; font: 400 12px/16px var(--font-body); color: var(--text-3); }
.p126-nt-row {
  display: grid; grid-template-columns: minmax(0, 1fr) 64px 64px; align-items: center; column-gap: 4px;
  min-height: 40px; padding: 2px 6px; border-top: 1px solid var(--line-2);
}
.p126-nt-row.p126-nt-cols { min-height: 24px; border-top: 0; font: 500 12px/16px var(--font-body); color: var(--text-3); text-align: center; }
.p126-nt-l { min-width: 0; font: 400 13px/18px var(--font-body); color: var(--text-1); overflow-wrap: anywhere; }
label.p126-tg { display: flex; align-items: center; justify-content: center; margin: 0; min-height: 36px; cursor: pointer; }
.p126-tg .switch { display: block; }
.p126-tg .switch input:disabled + span { opacity: .4; }
.p126-nt-why { margin: 8px 6px 2px; font-size: 12px; line-height: 16px; color: var(--text-3); }

/* ---- 5. phones: the pickers are sheets and every control a 44px target --- */
@media (max-width: 700px) {
  .p126-look, .p126-nt { width: auto; }
  .p126-sws, .p126-ics { gap: 8px; }
  .p126-sw, .p126-ic { width: 44px; height: 44px; border-radius: 10px; }
  .p126-none { min-height: 44px; padding: 0 16px; }
  .p126-nt-row { grid-template-columns: minmax(0, 1fr) 60px 60px; min-height: 48px; }
  label.p126-tg { min-height: 44px; min-width: 44px; }
}

/* ==========================================================================
   phase-129: the small client fixes (defects 85 and 86, the narrow-board
   gutter, scroll anchoring). Item 3 and defect 85 are script; this block
   carries the other two. Appended last; nothing here shares a selector with
   the phase-126 block above (every 126 rule is p126- prefixed).
   ========================================================================== */

/* ---- 1. defect 86: the board title with the sidebar open on a tablet.
   At 768 with the sidebar open the content column is 480px, the title row
   372px, and the count, star, Board tools and people slot are fixed at 251
   of them (phase-116-e: `flex: none`), so the name was left 121px: "Budget
   vs actual" broke inside a word ("Budge / t vs / actual") and a long name
   was a tower of fragments. Names wrap by word and never inside one
   (checklist 10), so in that one situation, sidebar open and 701-1000px, the
   name takes the row's whole width and the controls sit on the line under
   it, the way the phone's head already does (phase-116-h). With the sidebar
   closed (the default below 1440) the name has 300px or more beside them
   and 116-e stands, untouched. The controls' first-line offsets (margin-top
   16.5 / 12 / 14) were tuned to sit on a 36px title line; on a line of
   their own they centre instead. A single word wider than the row still
   breaks, at the last resort, rather than leaving the window. */
@media (min-width: 701px) and (max-width: 1000px) {
  html:not([data-snav="closed"]) .app .board-head:not(.cond) .board-title-row { flex-wrap: wrap; align-items: center; row-gap: 4px; }
  html:not([data-snav="closed"]) .app .board-head:not(.cond) .board-title-row > * { align-self: center; margin-top: 0; }
  html:not([data-snav="closed"]) .app .board-head:not(.cond) .board-title-row > .board-name { flex: 1 0 100%; max-width: 100%; align-self: flex-start; }
  html:not([data-snav="closed"]) .app .board-head:not(.cond) .board-name > .board-tap { overflow-wrap: break-word; }
  html:not([data-snav="closed"]) .app .board-head:not(.cond) .p112-total { margin-left: 0; }
  /* the star's glyph rides 4px low inside its 32px box (a block button with a
     vertical-align tuned beside a title line); on the controls' own line the
     button centres its glyph, so the glyph and its hover plate sit on the
     count's and Board tools' centre line together (designer, phase-129) */
  html:not([data-snav="closed"]) .app .board-head:not(.cond) .board-title-row > .icon-btn.star { display: inline-flex; align-items: center; justify-content: center; }
}

/* ---- 2. defect 86: the dock stays whole beside the open sidebar. Its cap was
   100vw - 288 - 48 - 32 = 400px at 768, and the views (the current one, "5
   more", the plus), the search button, Filter and New need 416: New item
   stood 16px outside the dock's glass. The dock is centred on the content
   column (50% + 144px), so the cap is the column's width less 16px each side
   (100vw - 288 - 32): 448 at 768, still 16px inside the window on the right.
   Where a panel is open (p119-side / p119-rp) the dock's own rules keep their
   caps; the selection bar has its own. Below 736px even that is not enough
   (dockFit adds .p129-tight once every view is folded): tighter gaps, no
   divider, the same controls. */
@media (min-width: 701px) and (max-width: 1100px) {
  html:not([data-snav="closed"]):not(.p119-side):not(.p119-rp) .app .board-head .p112-dock:not(.p113-selbar) { max-width: calc(100vw - 288px - 32px); }
}
@media (min-width: 701px) {
  .app .board-head .p112-dock.p129-tight { gap: 4px; padding-left: 6px; padding-right: 6px; }
  .app .board-head .p112-dock.p129-tight .p112-vr { display: none; }
  .app .board-head .p112-dock.p129-tight .board-tools { gap: 4px; }
  .app .board-head .p112-dock.p129-tight .p127-more { padding: 0 4px; gap: 4px; }
  .app .board-head .p112-dock.p129-tight .btn.primary.p112-new { padding: 0 10px; }
}

/* ---- 3. scroll anchoring belongs to the box that scrolls. The phase-127-b
   rule sat on `.popover[data-p122-vs]`, which on a desktop is a frame with
   `overflow: hidden` (phase-116-f: it never scrolls, its last child does), so
   the sheet's `.p122-vs` kept the browser's anchoring and 424.5 could still
   become 386.5. The rule is written on every box customize.js vsScrollers
   writes an offset back to: the sheet (a phone's bottom sheet scrolls itself; 2026-09-29 phase-131: the sheet's last child scrolls now),
   the `.p122-vs` list and the column list. The earlier rule was removed in
   phase-135 (this one covers the same sheet); the phase-127-b suite check now reads THIS rule. */
.p122-vs-pop, .p122-vs-pop .p122-vs, .p122-vs-pop .p122-cols { overflow-anchor: none; }

/* ---- phase-130: the import's date sentence sits on its own line under the
   column's controls (the count beside the order menu is unchanged). The
   "Call each row" field is an ordinary .p122-ifields label. */
.p122-icol .p130-why { flex: 1 1 100%; }

/* ==================================================================== phase-131
   Five qa findings, client only. Nothing here sets a backdrop-filter, blurs
   a card or animates anything, so the phone "chrome is solid" block is
   untouched; every selector this phase adds carries the p131- prefix.

   ---- 1. defect 87: a phone sheet's frame does not scroll. `.popover.sheet`
   was itself the scroll container (View settings: 1,493px of content in a
   732px sheet), and the `.lg::after` rim and `.lg::before` inner shadows
   (both `inset: 0`) and the `::before` grab handle scrolled away with the
   content, so the rim and the rounded corner ended mid-sheet. Phase-116-f
   solved this for the desktop popover and said phone sheets kept their own
   layout; they now take the same model, and it is safe because every
   SD.popover call site hands over ONE wrapper element (a .menu, .qf-pop,
   .p122-vs, .p124-cc, .p126-look, .p127-dp ...), which becomes the scroller.
   The wrapper's own sticky children now stick to it, not to the sheet. The
   scroll-anchoring opt-out for View settings already sits on `.p122-vs`
   (phase-129 §3), the box that scrolls now. ---- */
.popover.lg.sheet { overflow: hidden; display: flex; flex-direction: column; }
.popover.lg.sheet > * { flex: none; }
.popover.lg.sheet > :last-child { flex: 0 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }

/* round 4 (designer): the sheet's scroller shows a thin overlay-style thumb, not a
   plain browser bar beside the rim: 4px, rounded, the quiet text tone at 35%, a
   transparent track, 4px in from the rim (a transparent border on the thumb).
   --p112-t3 exists from 701px; on a phone it falls back to --text-3. The top 12px
   fades out under the grab handle once the content has moved (core.js marks
   `.p131-scrolled`); at rest nothing is masked, so the first row is never dimmed.
   No blur, no backdrop-filter. */
.popover.lg.sheet > :last-child {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--p112-t3, var(--text-3)) 35%, transparent) transparent;
}
.popover.lg.sheet > :last-child::-webkit-scrollbar { width: 8px; background: transparent; }
.popover.lg.sheet > :last-child::-webkit-scrollbar-track { background: transparent; }
.popover.lg.sheet > :last-child::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--p112-t3, var(--text-3)) 35%, transparent);
  border: 2px solid transparent; border-radius: 4px; background-clip: padding-box;
}
.popover.lg.sheet > .p131-scrolled {
  -webkit-mask-image: linear-gradient(to bottom, transparent, black 12px);
  mask-image: linear-gradient(to bottom, transparent, black 12px);
}

/* ---- 2. defect 83: a group's sticky head is bound to the VISIBLE width.
   `.ghead-in` is sticky to the left of `.board-scroll`, but its containing
   block is the whole table (--tw, 2,685px on the register), so `max-width:
   100%` was the table's, and a long group title ran on past the scrollport
   and was clipped with its count and menu. board.js observeScrollport
   publishes the scroller's clientWidth as --p131-vw on `.board-scroll`; the
   head is capped at it (its own left is 0: sticky and at rest), so the title
   WRAPS (it already computes `white-space: normal` above 700px) and the
   count and menu stay in view. Unset, the cap is the old 100%. The phone's
   cards path (`.group-head` without `.ghead-in`) already wraps its title
   at every width it is used (:11035). ---- */
.ghead-in { max-width: min(100%, var(--p131-vw, 100%)); }

/* ---- 3. defect 84: the title row's text stays legible over the sheet tiles.
   The decorative `.p112-sheets > i` tiles (.32-alpha hue, or a faint grey) run
   under the title band and showed through "Board tools" and the star.
   `--p112-well` is not itself transparent (rgba(13,15,19,.05) light,
   rgba(0,0,0,.2) dark); the 2026-09-23 rule at :10077 sets `background:
   none` on purpose ("quiet text that lights up under the pointer, not a
   filled capsule") and it did not foresee the tiles. That rule STANDS: the
   rest state stays background-less. Designer's verdict 2026-09-29: give the
   marks a halo of the page ground instead of a chip, so a tile passing under
   a letter is knocked back behind it and the control stays quiet text.
   Text gets a three-step text-shadow, the icons a two-step drop-shadow, both
   in `--p112-ground` (no new colour, no blur of a card: a shadow is not a
   backdrop-filter). Under the pointer or the keyboard the control already
   has its own lit surface (`--p112-on`), so the halo goes off there and
   does not smear over it. Tiles do not exist under 701px, nor in the
   condensed head (`.cond` hides them), so neither does this. ---- */
@media (min-width: 701px) {
  .app .board-head:not(.cond) .p112-total,
  .app .board-head:not(.cond) .p112-crumb,
  .app .board-head:not(.cond) .board-more,
  .app .board-head:not(.cond) .board-more .pill-count {
    text-shadow: 0 0 1px var(--p112-ground), 0 0 4px var(--p112-ground), 0 0 10px var(--p112-ground);
  }
  .app .board-head:not(.cond) .board-more > .ic,
  .app .board-head:not(.cond) .icon-btn.star > .ic {
    filter: drop-shadow(0 0 1px var(--p112-ground)) drop-shadow(0 0 4px var(--p112-ground));
  }
  .app .board-head:not(.cond) .board-more:hover,
  .app .board-head:not(.cond) .board-more:focus-visible,
  .app .board-head:not(.cond) .board-more:hover .pill-count,
  .app .board-head:not(.cond) .board-more:focus-visible .pill-count { text-shadow: none; }
  .app .board-head:not(.cond) .board-more:hover > .ic,
  .app .board-head:not(.cond) .board-more:focus-visible > .ic,
  .app .board-head:not(.cond) .icon-btn.star:hover > .ic,
  .app .board-head:not(.cond) .icon-btn.star:focus-visible > .ic { filter: none; }
}

/* ---- 4. (phase-167 groundskeeping removed the empty board's .p112-empty-acts rules: no element carries the class since phase-165) ---- */

/* ---- 5. the board title's chevron follows the last word. The button around
   the name was `display: inline-flex` and a wrapped name is one anonymous flex
   item that fills it, so the chevron sat at the button's far edge. The button
   is now a block of ordinary text that shrink-wraps to its max-content up to
   the row (`width: fit-content; max-width: 100%`), and board.js p131TitleHTML puts the chevron
   inside a nowrap span with the last word, so it flows at the end of the last
   line and never alone on a line of its own. Round 3 (qa, 2026-09-29): the
   first try was `display: grid; align-content: center` to keep the name
   centred in the phone's 44px, but a grid button's intrinsic width fell to
   its MIN-content, so short names wrapped ("Document register" 394x48 became
   208x104). A block shrink-wraps correctly; the phone's centring is padding
   instead: a 1.2em line with `(44px - 1.2em) / 2` above and below is exactly
   44px for one line at any title size, and a wrapped name just grows. Desktop
   centres by its own explicit line-height (48px, 36px at tablet). Its offset is `.35em - 8px`: the
   cap middle of the title's face (0.7 cap height) for a 16px glyph, which is
   the owner-tuned offset at every size (11.2px at 32px). Focus ring: the
   button is still the one focus box. ---- */
.app .board-head .board-name > .board-tap { display: block; width: fit-content; max-width: 100%; }
@media (max-width: 700px) {
  .app .board-head .board-name > .board-tap { line-height: 1.2; padding-block: max(0px, calc((44px - 1.2em) / 2)); }
}
.app .board-head .board-name > .board-tap:focus-visible { border-radius: 8px; outline-offset: 4px; }
.board-tap > .p131-tail { white-space: nowrap; }
.board-tap > .p131-tail > .ic {
  display: inline-block; margin: 0 0 0 4px; flex: none; color: var(--text-3);
  vertical-align: calc(.35em - 7px);
}
@media (min-width: 701px) {
  .board-tap > .p131-tail > .ic { width: 16px; height: 16px; margin-left: 8px; color: var(--p112-t3); vertical-align: calc(.35em - 8px); }
}

/* ---- 6. print (phase-131): print is not capped at the screen's scrollport. --p131-vw is
   the width of the scroller on screen; a printed page lays the table out at
   the page's own width, so the head returns to its old cap. ---- */
@media print {
  .ghead-in { max-width: 100%; }
}

/* ==================================================================== phase-132
   Two designer/qa findings from the phase-131 review, client only. No blur, no
   backdrop-filter, no keyframe of its own (the caret keyframe is phase-116's),
   so the phone "chrome is solid" block is untouched. Every class this phase
   touches already exists; nothing new is invented, so there is no p132- class.

   ---- 1. the tag editor is ONE field. The grey `.tag-chips` box (--wash)
   held a white input: the generic text-field rule at :2904 is (0,5,1) and
   paints `background-image: linear-gradient(var(--surface-1) ...)` plus a
   conic focus border on ANY input, and `.tag-input`'s (0,1,0)
   `background-image: none` (:2911) never beat it. So the input drew its own
   surface-1 box inside the wash box, and its focus fan (border-box, radius
   6px, on a 0 border) showed as a blue sliver at the left edge. The box
   itself is now the field, on the same tokens a plain input wears (fill
   --surface-1, edge --line-strong, radius --r-sm); the input is borderless
   and transparent (the `background` shorthand also resets the image layer),
   restated at an id selector so it outranks the (0,6,1) focus rule.
   Focus is the app's answer for a wrapped field (phase-116: board-search,
   actf-field): the wrapper's edge steps to --text-2 (stronger than the --line-strong rest edge,
   >= 3:1 in both themes) at 1px, so nothing shifts (2px read as heavy next to the sibling
   fields, designer 2026-09-29; a box-shadow ring would trip the D46 ratchet), the caret walks the five logo hues, and nothing is drawn outside the box, so the sheet
   scroller (`.tag-editor`, overflow auto, no padding) has nothing to clip.
   The label rows' `.lab-text` inputs and the `.edit-box` inputs are single
   fields on a plain ground, not nested boxes, and are left alone. ---- */
.tag-editor .tag-chips {
  background: var(--surface-1); border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  transition: border-color var(--dur-micro-2) var(--ease-out);
}
.tag-editor .tag-chips:focus-within { border-color: var(--text-2); }
.tag-editor .tag-chips > #tag-input,
.tag-editor .tag-chips > #tag-input:focus {
  background: transparent; border: 0; border-radius: 0; box-shadow: none; outline: none;
  animation: none; caret-color: var(--logo-5);
}
.tag-editor .tag-chips > #tag-input:focus { animation: p116Caret 5.3s linear infinite; }
@media (prefers-reduced-motion: reduce) {
  .tag-editor .tag-chips > #tag-input:focus { animation: none; }
}

/* ---- 2. the quick filter's own scroller. The sheet's last child is `.qf-pop`,
   which holds the header (title, search, Clear all) and `.qf-body`; it is
   `.qf-body` that scrolls, so it never met phase-131's `> :last-child` rules
   and wore the global thumb (--line-1, rgb(211,215,224) in light) and no top
   fade. Same thin overlay thumb and the same 12px fade once moved (core.js
   marks `.p131-scrolled` on it too). The rules are restated, not merged into
   phase-131's selector lists: that block is read by selector, and its cascade
   is not this phase's to rewrite. Phones only: the desktop popover keeps the
   global thumb, because no desktop popover wears an overlay thumb anywhere
   (the only ones are the phone sheet's and the kanban track's). ---- */
.popover.lg.sheet .qf-body {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--p112-t3, var(--text-3)) 35%, transparent) transparent;
  overscroll-behavior: contain;
}
.popover.lg.sheet .qf-body::-webkit-scrollbar { width: 8px; background: transparent; }
.popover.lg.sheet .qf-body::-webkit-scrollbar-track { background: transparent; }
.popover.lg.sheet .qf-body::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--p112-t3, var(--text-3)) 35%, transparent);
  border: 2px solid transparent; border-radius: 4px; background-clip: padding-box;
}
.popover.lg.sheet .qf-body.p131-scrolled {
  -webkit-mask-image: linear-gradient(to bottom, transparent, black 12px);
  mask-image: linear-gradient(to bottom, transparent, black 12px);
}

/* ==================================================================== phase-134
   Two small visible fixes, client only. No blur, no backdrop-filter, no
   keyframe, no transition of its own, so the phone "chrome is solid" block is
   untouched.

   ---- 1. defect 91: on a phone a toast never lands on an open sheet. qa saw
   "That is not a complete zip archive" printed over the Import-an-archive
   sheet's drop zone at 375: #toasts stands at the bottom bar (:3276) and the
   sheet rises over the same strip. core.js (p134Toasts) measures the open
   sheets while a toast shows (a phone .popover.sheet and the .modal every
   dialog becomes at 700px and under, the item card included) and publishes
   the highest top edge as --p134-st: .p134-above stands the column 12px over
   that edge (the gap it keeps over the bar), .p134-top puts it 12px under the
   safe area when the sheet leaves no room. The toast's own look and motion
   (its fill, pill, rise and fade) are not touched; only the column moves.
   --p134-sat / --p134-sab are registered as lengths so core.js reads the safe
   areas in px.
   Second pass (designer, FIX): the column was centred at left: 50% and so
   capped at half the screen, and its top fallback covered a tall sheet's
   header. On a phone the column now grows to its content (never wider than the
   screen less 16px each side), and .p134-below is tried FIRST: a tall sheet
   ends about 748-756px and leaves the bottom bar's 56px strip free, so when that
   strip (lowest sheet edge to the bottom safe area) is at least the column's
   own height + 9px + 6px the column sits 9px over the bottom, in that strip. Else .p134-above.
   Third pass (qa walk 3, lead ruling): the item card and View settings span nearly the whole phone (top
   12-16px, bottom 748-756 of 812), so a 3-line toast (about 70px) fitted nowhere clear and .p134-top
   covered their header and Close. When no placement is fully clear core.js now measures each
   candidate's overlap with the sheet and takes the LEAST (a tie prefers below, then above, then top):
   below overlaps the card's bottom edge by only 15-25px, top by 66-70px. .p134-top is still the
   answer when it overlaps least (a short sheet, or the Quick filter sheet, which reaches the screen
   bottom). No new rule: the three classes above are unchanged. */
@property --p134-sat { syntax: "<length>"; inherits: true; initial-value: 0px; }
@property --p134-sab { syntax: "<length>"; inherits: true; initial-value: 0px; }
:root { --p134-sat: env(safe-area-inset-top, 0px); --p134-sab: env(safe-area-inset-bottom, 0px); }
@media (max-width: 700px) {
  #toasts { width: max-content; max-width: calc(100vw - 32px); }
  html.p134-below #toasts { top: auto; bottom: calc(env(safe-area-inset-bottom) + 9px); }
  html.p134-above #toasts { top: calc(var(--p134-st) - 12px); bottom: auto; transform: translate(-50%, -100%); }
  html.p134-top #toasts { top: calc(var(--p134-sat) + 12px); bottom: auto; }
}

/* ---- 2. the wrapped fields' focus edge. Phase-116 gave every wrapped
   field (the search boxes, the dock search, the boards-panel find, the
   activity filter, the desktop add field) a focus edge of
   color-mix(--text-1 34%): about 2.1:1, fainter than the --line-strong edge a
   plain field wears at REST. Phase-132 settled the tag editor on a 1px
   --text-2 edge (about 9:1; the designer ruled 2px heavy), and these join it
   so the family answers focus one way. Colour only: every one of them already
   draws a 1px border at rest (--border-soft, --line-2, the glass rim, or
   transparent, all lighter than --text-2), so nothing shifts. The selectors
   are phase-116's own (its 34% rules were removed in phase-135); the caret walk
   (p116Caret) and every `box-shadow: var(--focus-ring)` form are untouched. */
.board-search:focus-within, .actf-field:focus-within,
.popover .board-search:focus-within, .sidebar .board-search:focus-within { border-color: var(--text-2); }
.app .board-head .p112-dock .p112-srch:focus-within,
.sidebar .bp-find.board-search:focus-within { border-color: var(--text-2); }
@media (min-width: 701px) {
  .app .addbar input.add-item-input:not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=range]):focus {
    border-color: var(--text-2);
  }
}

/* ==================================================================== phase-136
   Private boards (customize.js, the phase-136 block). The lock
   beside a private board's name, the Template center's "Who can see it"
   choice, and the Board tools sheet "Who can see this board". Every rule
   is p136- prefixed; nothing here sets backdrop-filter, a transition or an
   animation, so the phone "chrome is solid" blocks above are untouched.
   Focus: the buttons take the app's one ring (--focus-ring, via the floor
   rule, checklist 17/24) with no override here; the Find field keeps the
   search fields' 1px --text-2 edge (checklist 51). UI colours only: the one data colour is the
   avatars' own chip, drawn by SD.avatar.
   ========================================================================== */

/* ---- 1. the lock: right after the name in the sidebar lists and the
   Boards panel, and a word in the eyebrow. The name keeps its own span (the
   suite reads it whole) and its own growth; the lock is a fixed box in ONE
   place, just before the star and the count at the right end, however long
   the name (a long name still wraps in full). The lock keeps the first
   line's height, where the count sits (checklist 47). ---- */
.p136-lk { flex: none; display: flex; align-items: center; height: 18px; color: var(--p112-t3, var(--text-3)); }
.sidebar .bp-row .p136-lk { height: 20px; }
.p136-lk > .ic { flex: none; width: 12px; height: 12px; }
.p136-cl { flex: none; display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; color: var(--p112-t3, var(--text-3)); }
.p136-cl > .ic { flex: none; width: 12px; height: 12px; }

/* ---- 2. the Template center: who can see the new board ---- */
.p136-new { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 12px; }
.p136-new-t { margin-right: 4px; font: 600 12.5px/16px var(--font-body); color: var(--text-2); }
.p136-seg {
  display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 12px;
  border: 1px solid var(--line-1); border-radius: 999px; cursor: pointer; background: none;
  font: 500 12.5px/16px var(--font-body); color: var(--text-2);
}
.p136-seg > .ic { flex: none; width: 15px; height: 15px; }
.p136-seg:hover { color: var(--text-1); background: var(--p112-hover, var(--hover-ground)); }
.p136-seg.on { color: var(--text-1); background: var(--p112-on, var(--brand-soft)); border-color: color-mix(in srgb, var(--text-1) 28%, transparent); }

/* ---- 3. Board tools > Who can see this board ---- */
.p136-pop { padding: 10px; }
.p136-vis { width: min(340px, calc(100vw - 32px)); display: flex; flex-direction: column; }
.p136-head { display: flex; align-items: flex-start; gap: 8px; min-width: 0; padding: 2px 4px 10px; color: var(--text-2); }
.p136-head > .ic { flex: none; width: 18px; height: 18px; margin-top: 1px; }
.p136-head b { min-width: 0; font: 600 14px/20px var(--font-body); color: var(--text-1); overflow-wrap: anywhere; }
.p136-opts { display: flex; flex-direction: column; gap: 6px; }
.p136-opt {
  display: flex; align-items: center; gap: 10px; width: 100%; min-height: 48px; padding: 8px 10px;
  border: 1px solid var(--line-1); border-radius: 12px; cursor: pointer; background: none; text-align: left;
  font: inherit; color: var(--text-1);
}
.p136-opt:hover:not(:disabled) { background: var(--p112-hover, var(--hover-ground)); }
.p136-opt.on { background: var(--p112-on, var(--brand-soft)); border-color: color-mix(in srgb, var(--text-1) 28%, transparent); }
.p136-opt:disabled { cursor: default; }
.p136-opt:disabled:not(.on) { opacity: .6; }
.p136-oi { flex: none; display: inline-flex; color: var(--text-2); }
.p136-oi > .ic { width: 16px; height: 16px; }
.p136-ot { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.p136-ot b { font: 600 13px/18px var(--font-body); color: var(--text-1); }
.p136-ot span { font: 400 12px/16px var(--font-body); color: var(--text-3); }
.p136-ok { flex: none; width: 16px; display: inline-flex; color: var(--text-1); }
.p136-ok > .ic { width: 16px; height: 16px; }
.p136-why { margin: 8px 4px 0; font-size: 12px; line-height: 16px; color: var(--text-3); }
.p136-err { margin: 8px 4px 0; font-size: 12.5px; line-height: 17px; color: var(--danger); }
.p136-err:empty { display: none; }
.p136-vis .loading { padding: 14px 4px 4px; }
.p136-t { display: flex; align-items: baseline; gap: 6px; padding: 14px 4px 6px; font: 600 12.5px/16px var(--font-body); color: var(--text-2); }
.p136-t em { font-style: normal; font-weight: 400; color: var(--text-3); font-variant-numeric: tabular-nums; }
.p136-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.p136-row { display: flex; align-items: center; gap: 10px; min-height: 44px; padding: 6px 4px; border-top: 1px solid var(--line-2); }
.p136-row:first-child { border-top: 0; }
.p136-row[hidden] { display: none; }
.p136-row > .avatar { flex: none; }
.p136-nm { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.p136-nm b { font: 500 13px/18px var(--font-body); color: var(--text-1); overflow-wrap: anywhere; }
.p136-nm span { font: 400 12px/16px var(--font-body); color: var(--text-3); overflow-wrap: anywhere; }
/* "Creator" is a fact, not a control: plain muted text, no outline */
.p136-tag { flex: none; font: 500 12px/16px var(--font-body); color: var(--text-3); }
.p136-btn {
  flex: none; display: inline-flex; align-items: center; gap: 5px; min-height: 32px; padding: 0 12px;
  border: 1px solid var(--line-1); border-radius: 999px; cursor: pointer; background: none;
  font: 500 12.5px/16px var(--font-body); color: var(--text-2);
}
.p136-btn > .ic { flex: none; width: 14px; height: 14px; }
.p136-btn:hover { color: var(--text-1); background: var(--p112-hover, var(--hover-ground)); }
.p136-find { margin: 0 0 6px; }
/* the find field sits in the sheet's flex COLUMN, so the phone tools-row rule
   `.board-search { flex: 1 1 150px }` (~:4607) would size its HEIGHT to 150px
   and grow it; it keeps its own height instead */
.p136-vis .p136-find { flex: none; }
.p136-none { margin: 4px 4px 0; font-size: 12px; line-height: 16px; color: var(--text-3); }
.p136-none[hidden] { display: none; }

/* ---- 4. the phone head's own "Private" (the eyebrow is hidden at 700px and under) ---- */
.p136-ph { display: none; }

/* ---- 5. phones: the sheet fits 375 and every control is a 44px target ---- */
@media (max-width: 700px) {
  .p136-vis { width: auto; }
  .p136-new-t { flex: 1 1 100%; }
  .p136-seg { flex: 1 1 100%; min-height: 44px; padding: 0 16px; }
  .p136-opt { min-height: 56px; }
  .p136-btn { min-height: 44px; padding: 0 16px; }
  .p136-row { min-height: 52px; }
  .p136-vis .p136-find { height: 44px; min-height: 44px; }
  .app .board-head .p136-ph { display: inline-flex; align-items: center; align-self: center; font: 400 13px/20px var(--font-body); }
}

/* ================================================================ phase-138
   The empty Timeline and Files panes offer one button, and two of them carry
   an item's name ("Set dates on ...", "Add a file to ..."). The name is cut
   to 32 characters, but 32 wide letters with no space in them still measured
   457px at 375 and pushed #board-body sideways. So these buttons, and only
   these, stay inside the pane and break a long word anywhere. */
.empty-acts > .btn.p138-act {
  max-width: 100%;
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: center;
}

/* phase-138: the timeline head on a phone. Under 701px the p113 strip rules
   do not apply, so the head is the plain .cal-head no-wrap row: prev, Today,
   next, the window label, Full range and the count note, all shrinking
   together. Measured at 375 on the register: the label "Mar 2026 - Feb 2027"
   crushed to 34px over five lines, the note a 99px column beside it. On a
   phone the label keeps its one line and the note takes its own row under
   the controls. 701px and up keep the p113 strip (it already wraps). */
@media (max-width: 700px) {
  .cal-head.tlw-head { flex-wrap: wrap; column-gap: 6px; row-gap: 6px; }
  .cal-head.tlw-head .tlw-range { flex: 0 0 auto; min-width: max-content; white-space: nowrap; margin: 0 4px; }
  .cal-head.tlw-head .tlw-note { flex: 1 0 100%; order: 1; margin-left: 0; }
}

/* ================================================================ phase-142
   Two phone fixes; nothing here reaches 701px and up.
   1. The Template center's category chips. `.tpl-nav` has scrolled on one
      line under 701px since the phone edition (:3327), but the modal is a
      flex column (.tpl-modal, :2743) and a flex item with `overflow-x: auto`
      loses its automatic minimum height, so the row was squeezed to 17px and
      clipped its 44px chips to a sliver above the first category. The row
      keeps its own height; it scrolls sideways the way the Files view's
      kind chips do (.fv-kinds, :3378), no scrollbar.
   2. The Board tools sheet's head. The board's name cut to "…" after about
      28 characters at 375; the owner has to see which board he is on. In
      the sheet (a phone's popover, core.js SD.popover) the name takes the
      full width and wraps to at most three lines (the whole name is under
      "About this board"), and the item count always sits on its own muted
      line under it, left-aligned, so it never jumps between beside and
      below with the name's length (designer ruling). The base count rule
      pushes it right with `margin-left: auto`, which in a column would
      push it to the far edge, so the sheet zeroes it. The desktop popover
      keeps its one-line head. */
@media (max-width: 700px) {
  .tpl-modal .tpl-nav { flex: none; overscroll-behavior-x: contain; }
}
.popover.sheet .p112-tools .menu-head { flex-direction: column; align-items: flex-start; gap: 2px; }
.popover.sheet .p112-tools .menu-head b { max-width: 100%; min-width: 0; white-space: normal; overflow-wrap: anywhere; line-height: 1.3; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; overflow: hidden; }
.popover.sheet .p112-tools .menu-head span { margin-left: 0; }

/* ================================================================ phase-143
   D50 (DESIGN_DIRECTION-APP.md §13, defect 104): a row that scrolls sideways
   fades at the edge where there is more. The fade is a MASK on the scroller,
   so it shows whatever is behind the row in either theme: no colour, no
   tint, no blur, no shadow. Each edge is as wide as the distance left to
   scroll that way, up to 40px; SD.edgeFade (core.js) writes the two numbers
   from the finger, so nothing pops at a threshold and there is no motion
   for reduced-motion to still. The mask is on only while the row overflows
   (.p143-over, set by the script when there is more than 1px to scroll): a
   mask clips to the border box and makes a stacking context, so a row that
   fits carries none, and a chip's 2px focus ring at 2px offset (:1472) is
   never cut at any width. The fade never reaches nothing (§13 §2a): 25%
   black holds until the outermost 15% of the fade width, so the next icon
   past the edge still shows and the row does not read as ending there;
   that last 15% feathers from 25% to clear, so a chip or the lit lozenge
   cut by the edge shows no hard vertical step (§2a "Feathered edge"). With
   a width of 0 every stop on that side sits at 0px: nothing shows.
   scroll-padding keeps a focused chip clear of the 40px.
   The board views take it on an INNER strip (.p143-vts, board.js), never on
   .view-tabs: that element is the rounded track, and a fade on it dissolves
   the pill's own ends and rim. Above 700px the strip is display: contents,
   so the desktop head and its "More" fold lay out exactly as before. Under
   701px the track stops scrolling and the strip scrolls inside it; the
   strip is positioned, so the lozenge (.vt-ind) and every tab's offsetLeft
   measure from it, and the lozenge is pinned to the strip's own height
   (the tab's). */
.p143-edge { --p143-l: 0px; --p143-r: 0px; scroll-padding-inline: 40px; }
.p143-edge.p143-over {
  -webkit-mask-image: linear-gradient(to right, transparent 0, rgba(0,0,0,.25) calc(var(--p143-l) * .15), #000 var(--p143-l), #000 calc(100% - var(--p143-r)), rgba(0,0,0,.25) calc(100% - var(--p143-r) * .15), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, rgba(0,0,0,.25) calc(var(--p143-l) * .15), #000 var(--p143-l), #000 calc(100% - var(--p143-r)), rgba(0,0,0,.25) calc(100% - var(--p143-r) * .15), transparent 100%);
}
.p143-vts { display: contents; }
@media (max-width: 700px) {
  .app .board-head .view-tabs { overflow: hidden; }
  .app .board-head .p143-vts {
    display: flex; align-items: center; gap: inherit; position: relative; flex: 1 1 auto; min-width: 0;
    overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
  }
  .app .board-head .p143-vts::-webkit-scrollbar { display: none; }
  .app .board-head .p143-vts > .vt-ind { top: 0; bottom: 0; }
  /* the strip is the scroller and is exactly a tab tall, so a ring 2px
     outside the tab loses its top, bottom and the first tab's left side;
     inside the strip it is drawn on the tab's own edge instead (as the
     table's cells do, :3830). No box moves: the track and lozenge keep
     their size. */
  .app .board-head .p143-vts a:focus-visible,
  .app .board-head .p143-vts button:focus-visible { outline-offset: -2px; }
}

/* ================================================================ phase-144
   The room a phone card's scroller borrows while "Set dates on" has the
   range picker open as a sheet (panels.js p144Room): an empty block at the
   scroller's foot, its height set inline from what the field lacks to rise
   above the sheet, removed when the sheet closes. It never shrinks in a
   flex column and never takes a tap. Nothing here sets backdrop-filter, a
   transition or a keyframe, so the phone "chrome is solid" blocks above are
   untouched. transition-property is none because the reduced-motion stiller
   gives every element a .01ms transition on all properties, and under it the
   room's height lagged a frame behind each write: p144Room measures the
   shortfall right after sizing the room, read the old height, and over-sized
   it (418px for 250px at 375, field left 84px low). The glide back sets the
   height frame by frame from script and wants no transition either. */
.p113-ic-scroll > .p144-room { flex: none; pointer-events: none; transition-property: none; }
/* ==================================================================== phase-145 (classes keep the p144 prefix; see DECISIONS P145)
   The selection bar rises ABOVE the dock (designer ruling D51,
   DESIGN_DIRECTION-APP.md §14). Until now the bar took the dock's place with
   the dock hidden under it (phase-113 §3, :9895), so every bar control landed
   on a different dock control and a click aimed at Filter deleted 4 register
   rows (2026-09-30). That hiding rule is WITHDRAWN here, overridden rather
   than deleted (earlier CSS is the groundskeeper's to remove): the dock stays
   where it is and keeps working; only its top five-hue line fades, so one
   line shows, on the bar. The bar stands 12px above it (32 + 56 + 12 = 100),
   and while rows are ticked the table's end padding, the toasts and the
   panels lifted above the dock all rise by the bar's 56 + 12 = 68px.
   Delete and Clear are pinned at the bar's right end at every width; only
   the middle (.p144-strip) scrolls, with the D50 edge fade (SD.edgeFade).
   The armed Delete (.p144-armed, board.js p144Machine) is a filled red
   with white 600 text: the logo red mixed with 18% black (#d12922), the
   rule --brand-fill uses on the logo blue, one fill in both themes. White
   on it reads 5.18:1; on --danger it read 4.24:1 in light and 3.25:1 in
   dark (review, 2026-09-30). --danger itself is unchanged. No blur, no
   backdrop-filter, no keyframe: the phone "chrome is solid" blocks above
   are untouched. ---- */
.p144-strip {
  display: flex; align-items: center; gap: 4px; flex: 0 1 auto; min-width: 0;
  overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
}
.p144-strip::-webkit-scrollbar { display: none; }
/* the strip clips vertically, so a ring drawn outside a 40px button in a
   40px strip would lose its top and bottom: drawn on the button's own edge */
.p144-strip .p113-sb:focus-visible { outline-offset: -2px; }
.p113-selbar > .p113-danger, .p113-selbar > .p113-selx { flex: none; }
.p113-sb.p113-danger.p144-armed,
.p113-sb.p113-danger.p144-armed:hover { background: #d12922; color: #fff; font-weight: 600; }
@media (min-width: 701px) {
  .app .board-head .p112-dock.p113-selbar { bottom: 100px; overflow: visible; }
  /* its soft under-shadow would darken the dock's glass beneath it */
  .app .board-head .p112-dock.p113-selbar::after { display: none; }
  /* the dock stays: the phase-113 hiding rule, withdrawn (the item card's
     own hiding below 1440, html.p113-icard, still stands) */
  html.p113-selon:not(.p113-icard) .app .board-head .p112-dock:not(.p113-selbar) {
    opacity: 1; visibility: visible; pointer-events: auto;
    transition: transform var(--dur-micro-3) var(--ease-out), opacity var(--dur-micro-2) var(--ease-out), visibility 0s linear 0s;
  }
  html.p113-selon .app .board-head .p112-dock:not(.p113-selbar)::before { opacity: 0; transition: opacity var(--dur-micro-2) var(--ease-out); }
  /* nothing sits under the bar: the last row, the toasts, the lifted panes */
  html.p113-selon .app .board-body { padding-bottom: 188px; }
  html.p113-selon:has(.app .board-head .p112-dock) #toasts { bottom: 168px; }
  html.p113-selon.p119-rp:not(.p119-side):has(.app .board-head .p112-dock) .app .chat-panel,
  html.p113-selon.p119-rp:not(.p119-side):has(.app .board-head .p112-dock) .app .ai-panel,
  html.p113-selon.p119-rp:not(.p119-side):has(.app .board-head .p112-dock) .modal.p113-icm { margin-bottom: 156px; }
}
@media (min-width: 1440px) {
  /* from 1440 the item card stands beside the dock and the dock shows */
  html.p113-selon.p113-icard .app .board-head .p112-dock:not(.p113-selbar) { opacity: 1; visibility: visible; pointer-events: auto; }
}
/* 1099px and below: "4 selected" (the number is in the badge); the item
   word stays for a screen reader */
@media (max-width: 1099px) {
  .p144-selw {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; border: 0;
    overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
}
@media (max-width: 700px) {
  /* the bar stays above the bottom bar; it no longer scrolls, its strip does,
     so Delete and Clear stand at its right end; every control is 44px */
  .app .board-head .p112-dock.p113-selbar, .p113-selbar { overflow: visible; }
  .p144-strip { flex: 1 1 auto; }
  .p113-selbar .p113-sb { height: 44px; }
  .p113-selbar .p113-selx { width: 44px; }
  /* designer FIX: the hairline before Delete comes back (the phone hides
     every dock divider, :9473), so the strip's fade ends 8px short of a
     line and Delete starts 8px past it, as at every desktop width */
  .p113-selbar .p144-strip + .p112-vr { display: block; }
  /* designer FIX: a toast stands clear above the bar, not on its Delete. The
     bar's bottom is --bar-h + 8px (--bar-h carries the safe area, phase-166)
     and it is 52px tall (44px controls in 4px padding), so its top is --bar-h + 60px;
     the toasts keep their 12px over it. An open sheet (phase-134 classes)
     still places them. */
  html.p113-selon:not(.p134-below):not(.p134-above):not(.p134-top) #toasts {
    bottom: calc(var(--bar-h, 56px) + 72px);
  }
}

/* ==================================================================== phase-147
   D52 (DESIGN_DIRECTION-APP.md §15): selecting on a phone. A labelled Select
   in the tool row turns the cards into things he ticks (board.js
   SD._sel.pick, html.p147-pick). Being selected is a state of the app's own
   chrome, so it wears the logo blue the desktop tick and row already wear,
   never a data colour; the card's status edge keeps its own colour. No
   backdrop-filter, no blur, nothing animates (the bar keeps its own rise). */
.p147-sel, .p147-tk, .p147-gsel { display: none; }
/* Select: only on the card list (the phone's Table layout, every other view
   and every desktop width hide it); 44px, and the width of "Select" whatever
   word it shows, so the search box beside it never jumps */
html[data-board-rows="cards"] .board-tools .p147-sel {
  display: inline-grid; place-items: center; flex: none; min-height: 44px; height: 44px; padding: 0 12px;
}
.p147-sel > span { grid-area: 1 / 1; }
.p147-sel > .p147-sw { visibility: hidden; }
/* "Done" is lit as an active Filter is lit (#qf-btn.on) */
.board-tools .p147-sel.on { background: var(--brand-soft); color: var(--brand); }
/* the tick box, before the name: 22px, 6px corners, 10px to the name (the
   top line's own gap is 8px); a 1.5px t3 ring empty, the desktop's #0070c1
   with a white tick when ticked */
html.p147-pick .rcard-top > .p147-tk {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 22px; height: 22px; margin: -1px 2px 0 0; border-radius: 6px;
  color: transparent; box-shadow: inset 0 0 0 1.5px var(--p112-t3, var(--text-3));
}
.p147-tk > .ic { width: 14px; height: 14px; stroke-width: 2.4; }
html.p147-pick .rcard.p113-on .rcard-top > .p147-tk { background: #0070c1; color: #fff; box-shadow: none; }
/* a tap ticks rather than opens, so the arrow that promised opening goes */
html.p147-pick .rcard-top > .ic { display: none; }
/* a ticked card: the desktop's selected fill, opaque (8% dark, 7% light) */
[data-theme="dark"] #board-body .rcard.p113-on,
[data-theme="dark"] #board-body .rcard-wrap:has(> .rcard.p113-on) { background: color-mix(in srgb, #0070c1 8%, var(--p112-ground)); }
:root:not([data-theme="dark"]) #board-body .rcard.p113-on,
:root:not([data-theme="dark"]) #board-body .rcard-wrap:has(> .rcard.p113-on) { background: color-mix(in srgb, #0070c1 7%, var(--p112-ground)); }
/* Select N / Clear N in each group head, in the count's place, while
   selecting: "Select N" carries the count, so the count stands down and the
   group's name keeps its width (a long name on one or two lines at 375px,
   the head at 53px, not four lines and 88px); 8px sides, not 12 */
html.p147-pick #board-body .group-head .group-count { display: none; }
html.p147-pick .group-head .p147-gsel:not([hidden]) {
  display: inline-flex; align-items: center; flex: none; min-height: 44px; height: 44px; padding: 0 8px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* the bar with nothing ticked: the hint and the X, nothing else */
.p147-hint { flex: 1; min-width: 0; padding: 0 4px; font: 400 13.5px/20px var(--font-body); color: var(--p112-t2, var(--text-2)); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* ", 2 hidden": ticked rows the search or a filter keeps out of sight */
.p147-hid { font-weight: 400; color: var(--p112-t3, var(--text-3)); }
@media (max-width: 700px) {
  /* the phone's own hold (the iOS callout, Android's text selection) stands
     down on a card; the item sheet keeps every value as selectable text */
  .rcard { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
  /* the last card scrolls clear of the bar: 60px more (52px bar, 8px gap) */
  html.p113-selon .board-body { padding-bottom: calc(var(--bar-h, 56px) + 100px); }
}

/* ==================================================================== phase-148
   The bottom bar's tap, and the boards panel's close (the owner: "the menu bar
   at the bottom has a weird transition when clicking to move to different
   boards"). The bar's look, material, colours and lit-marker motion are not
   touched. No backdrop-filter, no background on the bar, no keyframe: the
   phone "chrome is solid" blocks above are untouched.
   (a) The browser's own tap block (a square grey flash, dark theme only) stands
       down on every control that already draws its own pressed state: the press
       rule's eight classes (:8672) and .lg-ctl (:6140), the card and calendar
       cell (.rcard, .cal-cell.has), the subitem row, the "+" chip, and the
       boards panel's rows, which get their pressed ground here. On the bar the
       lit marker moving under the finger is the answer.
   (b) A bar item does not swell or lift under a finger, on a phone width or on
       any touch screen (the press rule's scale(1.03) and --up; the desktop's
       sidebar already stood the swell down, :10396). No hover motion either on
       the bar at phone widths.
   (c) A panel sliding away (core.js SD.p148Out) takes no taps: what it shows
       is the board he left.
   (d) With the swell and the tap block gone, a bar tile answers a finger with
       the panel rows' quiet pressed ground: background only, on the one curve,
       no scale, no lift. The lit tile keeps its own look. ---- */
.btn, .rail-btn, .avatar-btn, .chip-btn, .qf-chip, .gjump-chip, .addbar-btn, .lg-press, .lg-ctl,
.rcard, .cal-cell.has, .sub-row, .p117-sc-add,
.sidebar .bp-row, .sidebar .snav-row { -webkit-tap-highlight-color: transparent; }
.sidebar .bp-row:not(.active):active, .sidebar .snav-row:not(.on):active { background: var(--p112-hover, var(--hover-ground)); }
@media (max-width: 700px), (hover: none) {
  .app .rail .rail-btn:not([aria-disabled="true"]):not(:disabled):active,
  .app .rail .avatar-btn:not([aria-disabled="true"]):not(:disabled):active,
  .app .rail .rail-btn:not([aria-disabled="true"]):not(:disabled):hover,
  .app .rail .avatar-btn:not([aria-disabled="true"]):not(:disabled):hover { --up: 0; transform: none; }
  .app .rail .rail-btn, .app .rail .avatar-btn {
    transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out);
  }
  .app .rail .rail-btn:not(.active):not([aria-disabled="true"]):not(:disabled):active,
  .app .rail .avatar-btn:not([aria-disabled="true"]):not(:disabled):active { background: var(--p112-hover, var(--hover-ground)); }
}
@media (max-width: 700px) {
  .app .rail .rail-btn:hover > .ic, .app .rail .avatar-btn:hover .ic { animation: none; }
}
.sidebar.p148-out { pointer-events: none; }

/* ==================================================================== phase-149: light-theme parity
   D53 (DESIGN_DIRECTION-APP.md §16, DESIGN_TOKENS.md §0g). Owner
   2026-09-30: light "feels more opaque ... less strong than the nice
   contrast achieved in the dark theme", and the phone bar, the rainbow edge
   and the edges look different. Seven light-only washes, each a layer laid
   OVER a colour in light and not in dark; every rule below is light-only or
   restates what dark already computes, so dark is unchanged. The twelve
   chips and the five hues are not touched. Supersedes, in light only:
   :9441-9450 capsule word and 14% fill (word was --p112-t1 ink), :4132
   --spine-edge (38% ink ring on a 3px spine), :9169 header sheets at .7,
   :9560/:9585-9590 dock edge at .8 with a white glow (and the 1px of :9611),
   :5893-5900 the keyboard cell ring at .9 (the mouse-hover ring of
   :2348-2356 is already off, phase-116 :10198), :10681-10687 the group's
   count capsules in ink at 14%, :8992-8993 dock glass .82/.64 and
   rim .04/.10, :3249-3254 phone bar --line-2 top and its upward shadow.
   Nothing here sets backdrop-filter, blurs a card or a row, or animates
   anything: the phone "chrome is solid" blocks above are untouched, the
   dock rules sit inside the computer block so the phone never takes glass,
   and the .rail rule sits inside the phone block. ---- */
/* D53 1-2: light capsule carries its hue in the word, at dark's strength.
   The word is the label at 47% with black (§16.7, §0g), not §16.3's first
   52% nor the 49% first shipped: the floor is the darkest row a capsule sits
   on, the ticked row (phase-147, brand 7% over the page ground), where 49%
   put yellow at 4.48:1. 47% gives yellow 4.77 there, 4.54 if hover and tick
   ever stack, 5.17 on the page row. The floor wins, as at :4126. */
:root:not([data-theme="dark"]) .status-pill.p112-cap {
  color: color-mix(in srgb, var(--lab, var(--text-3)) 47%, #000);
  background: color-mix(in srgb, var(--lab, var(--text-3)) 20%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lab, var(--text-3)) 60%, transparent);
}
/* D53 1-2, §16.7: the group's count capsules are the same capsule, word and
   fill (their rim stays the base rule's 60%). The empty "no status" capsule
   stays hollow and grey in both themes, hence :not(.p119-tc-none). */
:root:not([data-theme="dark"]) .p119-tc:not(.p119-tc-none) {
  color: color-mix(in srgb, var(--lab, var(--text-3)) 47%, #000);
  background: color-mix(in srgb, var(--lab, var(--text-3)) 20%, transparent);
}
/* D53 3: 3px spines are under 6px, exempt in both themes (DESIGN_TOKENS §0c) */
/* chip-edge: exempt - 3px spine, under the 6px floor */
:root { --spine-edge: transparent; }
/* D53 4: no light-only fades. The cell ring is the keyboard's only: mouse
   hover draws none (phase-116 :10198), so no hover selector here. */
:root:not([data-theme="dark"]) .p112-sheets > i:not(.n) { opacity: 1; }
.board-table td.cell:focus-visible::before { opacity: 1; }
@media (min-width: 701px) {
  :root:not([data-theme="dark"]) .app .board-head .p112-dock::before {
    opacity: 1; height: 2px; top: -1px; filter: none;
  }
  /* D53 5: the dock is glass, not a veil; its edge is a shadowed edge.
     Applies to all six glass panes by design, §16.7. */
  :root:not([data-theme="dark"]) {
    --p112-glass: linear-gradient(180deg, rgba(255,255,255,.6), rgba(255,255,255,.42));
    --p112-rim: inset 0 1px 0 rgba(255,255,255,.95),
                inset 0 -1px 0 rgba(13,15,19,.10), 0 0 0 1px rgba(13,15,19,.16);
  }
}
/* D53 6: phone bar, one clean line, no smear */
@media (max-width: 700px) {
  :root:not([data-theme="dark"]) .rail { border-top-color: var(--line-1); box-shadow: none; }
}

/* ---- phase-150: one grammar, measured (design/audit-2026-09-30/ALIGNMENT.md)
   Twenty-three alignment findings, each a few pixels, fixed to the
   designer's measured values; tests/geometry_test.py walks the invariants
   (G1-G11) at 375/768/1440/1920 in both themes. Every rule here is a
   position or a size: no colour, no material, no motion. In place, outside
   this block: the .icon-btn base (:449, display) and the menu tile selector
   (:2275, :2279, the theme row's visible icon). ---- */

/* 10. column titles: the box always holds its ink and wraps only at a space;
   the sort control sits inside .th-in, never under the title */
.app .board-cols .col-head, .app .board-table thead.p112-colh .col-head { min-width: min-content; overflow-wrap: normal; }
.th-in { position: relative; }
.th-sort { right: 0; }
.board-table th .th-in, .board-cols .th-in { padding-right: 24px; }
/* C1 (ALIGNMENT.md §4): a title is centred over its column whenever it fits;
   one that does not fit moves left only as far as it must to stay 8 clear
   of the sort control. Reversed, the title is anchored at the control's
   side, and the far 22 of the 24 that mirror the control is a spacer
   (::after, the leftmost item) rather than padding: a fitting title centres
   over the same span as before, a long one takes the spacer's room, and
   nothing ever overflows the row, so justify-content stays center (qa's
   "columns are centred" check) and never moves anything. min-width: 0
   overrides min-content above for these titles, and a long single word in
   a column dragged to its 70px floor ellipsises inside its own box (the
   whole title is in the column menu) instead of running under the sort
   control or the neighbouring title. A title that fits is never cut: the
   fit reserves its full width (board.js titleNeed). The row rule is kept
   at (0,3,1)/(0,3,0) so a later per-type rule of equal weight wins. */
.board-table th[data-type] .th-in, .bcol[data-type] .th-in {
  padding-left: 2px; padding-right: 24px; gap: 0;
  flex-direction: row-reverse; justify-content: center; }
.app .board-table th[data-type] .th-in::after, .app .board-cols .bcol[data-type] .th-in::after {
  content: ""; flex: 0 1 22px; min-width: 0; }
.app .board-table th[data-type] .col-head, .app .board-cols .bcol[data-type] .col-head {
  margin-inline: auto; flex-shrink: 0; max-width: 100%; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; }

/* 23. sign-in: the column may be narrower than its content, the card fits
   the column, the label is centred in its capsule */
@media (max-width: 1020px) { .auth-page { grid-template-columns: minmax(0, 1fr); } }
.auth-card { width: min(420px, 100%); }
.auth-card .btn.primary { justify-content: center; }

/* 12. a menu's rename field runs the plate span */
.menu > .edit-box:first-child, .col-menu > .edit-box:first-child, .cols-menu > .edit-box:first-child { padding: 0 0 10px; }
.menu-sep { margin: 6px 0; }
/* 13. the Filter card's two ghost buttons are placed by their words, not
   their box: each keeps 8 of plate round its words (hover and .on draw there)
   and hands that 8 back by a margin on the line's side, so the ink sits on
   the card's ink line. 8, not .btn.sm's 11: the body scrolls, so it clips,
   and its content edge is 8 outside the words. Scoped to the two the audit
   measured; a bare `.popover .btn.ghost.sm` took the plate off every one. */
.qf-head > .btn.ghost.sm[data-act="quick-clear"] { padding-inline: 8px; margin-right: -8px; }
.qf-popover .btn.ghost.sm.p122-add { padding-inline: 8px; margin-left: -8px; }
/* the Filter card: one ink line, its section rules on the card's span */
.qf-head { padding-inline: 8px; }
.qf-body { padding-inline: 0; }
.qf-section, .p122-conds { padding-inline: 8px; }
/* 14. twelve swatches, two even rows of six */
.menu-colors { display: grid; grid-template-columns: repeat(6, 22px); justify-content: space-between; row-gap: 8px; }
/* 15. the picker's footer: one height, one centre line */
.p119-pick .p119-pick-foot { align-items: center; }
.p119-pick .p119-pick-foot > .label-edit { height: 32px; padding: 0 8px; }
/* C2: a block box leaves its words on the baseline, 4 below "Clear" */
.p119-pick .p119-pick-foot > .label-edit { display: inline-flex; align-items: center; gap: 8px; }

/* 18. the item card: the links on the field labels, the composer's Send on
   the card column; 19. its close glyph 16 from the card edge */
.p124-icf-acts { margin-left: 2px; }
.p113-compose { padding-right: 0; }
.p113-ic-acts .ic-close { margin-right: -16px; }
/* 19, 20. Chat and Claude: the same close, the same insets */
.chat-panel .ch-thread-head { padding-left: 16px; padding-right: 8px; }
.chat-panel .ch-thread-head .icon-btn[aria-label^="Close"] { width: 32px; height: 32px; padding: 0; }
.chat-panel .ch-compose { padding-left: 16px; padding-right: 16px; }
.ai-head { padding-right: 8px; }
.ai-head .icon-btn[aria-label^="Close"] { width: 32px; height: 32px; padding: 0; }
.ai-input { padding-left: 16px; padding-right: 16px; }

/* 17. the Me menu from the open sidebar runs the sidebar's column (panels.js
   sets its width and left from the row it opened from) */
.popover.p150-me-pop { min-width: 0; max-width: none; }
.popover.p150-me-pop .me-head > div { min-width: 0; overflow-wrap: anywhere; }
/* "Keyboard shortcuts" and its key were the widest row, 1.5px past the
   column; 16px between them instead of 22 keeps it on one line */
.popover.p150-me-pop .p124-keys { padding-left: 6px; }

/* 21. the boards panel's search field runs its rows' span */
.sidebar .bp-find { margin-left: 0; margin-right: 0; max-width: 100%; }

/* 22. the desktop sidebar: one glyph-centre line (48), one label line (68) */
@media (min-width: 701px) {
  .app .rail .snav-row { padding-left: 15px; gap: 13px; }
  .app .rail .snav-sh { padding-left: 13px; padding-right: 5px; }
  .app .rail .snav-foot .avatar-btn .avatar { margin-left: 3px; }
  /* every trailing glyph ends on 248: the section "+"s were at 243, the
     theme switch at 251 */
  .app .rail .snav-foot .theme-btn { margin-right: 3px; }
}

@media (max-width: 700px) {
  /* 6. one column, 12 to 363 */
  :root { --board-pad-x: 12px; }
  /* 1. the active pill sits 3px in on every side of its track */
  .app .board-head .view-tabs { padding: 3px; }
  .app .board-head .p143-vts { border-radius: 999px; }
  /* 2. the title's ink on the column; its rename ring keeps its room. The
     ring is a 1.5px border (--fan-ring-w) the browser draws at 1 to 1.5 by
     device scale, so the pull is its padding plus the ring: ink at 11.5 to 12
     on any phone, never the 13.5 a bare -6.5 left at scale 3 (qa) */
  .app .board-head .board-name { margin-left: calc(-6.5px - var(--fan-ring-w, 1.5px)); }
  /* 5. "Board tools" ends on the column */
  .app .board-head .board-more { padding-right: 0; }
  /* 7. trailing glyphs on the column; the card's chevron 16 in */
  .app .group-head { padding-right: 0; }
  /* the glyph is 14 in a 44 box: 15 either side, so the glyph ends on 363;
     the box's last 3px run past the screen edge, which clips them */
  .app .group-head .group-menu { margin-right: -15px; }
  .app .rcard:not(.rcard-add) { padding-right: 16px; }
  /* 3, 4. six even columns, one label line: the avatar draws at 28 and
     takes the icons' 20 of height, so "Me" (the ::after label) sits on the
     other labels' line */
  .app .rail .rail-btn, .app .rail .avatar-btn { padding: 0; height: auto; }
  .app .rail .avatar-btn .avatar { margin-block: -4px; }
  /* 13. the Filter sheet on the sheet's ink line (18) */
  .qf-head { padding: 14px 10px; }
  .qf-body { padding: 4px 0 12px; }
  .qf-section, .p122-conds { padding-inline: 10px; }
  /* 18, 19. the item card on the phone: a 44 close, glyph 16 from the edge */
  .p113-ic-acts .ic-close { width: 44px; height: 44px; margin: -6px -14px -6px 0; }
  /* 19, 20. Chat and Claude on the phone */
  .ch-list-head .ch-close { width: 44px; height: 44px; padding: 0; align-items: center; justify-content: center; margin-right: -14px; }
  .ai-head { padding-right: 2px; }
  .ai-head .icon-btn[aria-label^="Close"] { width: 44px; height: 44px; margin-block: -5px; }
  /* the open conversation closes like every other phone sheet: a 44 box, its
     glyph 16 from the edge (the head gives back 6 of its 8) */
  .chat-panel .ch-thread-head { padding-right: 2px; }
  .chat-panel .ch-thread-head .icon-btn[aria-label^="Close"] { width: 44px; height: 44px; margin-block: -6px; }
  /* and its Back mirrors that close: the glyph centred in its 44 (a phone
     rule had made it a flex row, glyph on the left), the box pulled 14 so the
     glyph starts 16 from the left edge, as the close ends 16 from the right */
  .chat-panel .ch-thread-head .ch-back { display: inline-grid; place-items: center; padding: 0; margin-left: -14px; }
  /* 21. the boards panel is a sheet: plates 8 to 367, rows 44, ink 18 */
  .bp-head { padding: 6px 2px 8px 18px; }
  .sidebar .bp-find { margin: 0 8px 8px; max-width: calc(100% - 16px); padding-left: 10px; }
  .bp-quick .snav-sh { padding: 12px 18px 4px; }
  .bp-quick .snav-row { margin: 0 8px; padding: 13px 10px; border-radius: 12px; }
  .sidebar .bp-wk { padding-left: 18px; padding-right: 13px; }
  .sidebar .bp-row { margin: 0 8px; min-height: 44px; padding: 12px 10px; border-radius: 12px; }
  /* C3: the workspace divider runs the plates' span, 8 to 367; drawn by a
     pseudo-element because a margin on the section would move its rows too */
  .sidebar .bp-ws + .bp-ws { border-top: 0; position: relative; }
  .sidebar .bp-ws + .bp-ws::before { content: ""; position: absolute; top: 0; left: 8px;
    right: 8px; border-top: 1px solid var(--p112-hair, var(--border)); }
}

/* ---- phase-157: the table's item name is ONE line, cut with an ellipsis ----
   Owner ruling 2026-10-01: in the table a long item name ends in "..." on one
   line, the full name in the tooltip the span has carried since phase-27
   (title=), so a row keeps its height. Since phase-112 (b1548a1) the >700px
   rule at :9325 set `white-space: normal; overflow: visible` (checklist 10,
   "names wrap"), which grew the register's rows to two and three lines; that
   wrap is superseded for this one cell (OWNER-CHECKLIST item 10 notes it).
   Unscoped, so it covers 375 too (the phone's "Table layout"); it beats :9323
   on specificity and keeps the hover (:910) from re-wrapping. Exempt: a name
   being renamed (contenteditable, shows its whole text) and the view's "tall"
   row height (:11174), which the owner picks to get two clamped lines.
   Phone CARDS (.rcard-name) are another surface and still show the full name. */
.app .board-table td.col-name .item-name:not([contenteditable="true"]) {
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; overflow-wrap: normal;
}

/* ---- phase-161: the sidebar's logo is a Home button --------------------
   The mark now goes to My work (SD.HOME_HREF, core.js), the same address as
   the My work row, so it answers like the sidebar's other controls: the
   toggle's hover plate (r10, --snav-hov) on hover and press, and the global
   2px --brand outline on keyboard focus, which follows the r10. Never the
   active pill: SD.syncBar lights .rail-btn only, and the mark is not one.
   The 4px padding is paid back by a -4px margin, so the mark itself does not
   move a pixel, open or closed. The phone hides the mark (:3262); its bottom
   bar's My work button is Home there. */
@media (min-width: 701px) {
  .app .rail .rail-logo {
    padding: 4px; margin: -4px; border-radius: 10px;
    transition: background var(--dur-micro-1) var(--ease-out);
  }
  .app .rail .rail-logo:hover, .app .rail .rail-logo:active { background: var(--snav-hov); }
}

/* ---- phase-159: the files hover card ----
   1. The per-row download was a brand-tinted, brand-rimmed 26px square
      (:3413, :7515, :7564), a different material from everything else on the
      card. It now wears the card's own quiet control: the size, radius and
      bare rest of the card's own "Download all" (.btn.ghost.sm, 28px, --r-sm),
      the item card's icon-button behaviour (.p113-ic-acts .icon-btn: no plate
      at rest, a plate on hover), and the pane plate every control on glass
      takes on hover (--on-glass-hi + --on-glass-rim, as .popover .qf-chip).
      It stays visible at rest: an arrow in --on-glass-sub, never hover-only.
   2. The version chips sat right after a 1fr name, so chips of different
      widths left a ragged left edge. The rows now share ONE set of columns
      (subgrid), so the chip column is as wide as the widest chip and every
      chip starts on the same x; the button column is one width too.
   3. The card's pointer-events (:3417) were live even while it was hidden
      (opacity 0), an invisible pane taking clicks and hovers from the rows
      under it. They are live only while it shows. No filter, no blur here. */
.peek.peek-files:not(.show) { pointer-events: none; }
@supports (grid-template-columns: subgrid) {
  .peek .peek-files { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; column-gap: 8px; row-gap: 2px; }
  .peek .peek-files > * { grid-column: 1 / -1; }
  .peek .peek-files > .pf-row { grid-template-columns: subgrid; }
}
.peek .pf-row .attach-ver-chip { justify-self: start; }
.peek .pf-row .pf-dl {
  grid-area: d; align-self: center; justify-self: end;
  width: 28px; height: 28px; padding: 0; border-radius: var(--r-sm);
  /* at rest it wears the version chip's plate (:7516), not transparent: content
     on glass takes its contrast from an opaque plate (phase-87, suite q31c) */
  color: var(--on-glass-sub); background: var(--on-glass); box-shadow: inset 0 0 0 1px var(--on-glass-rim);
  transition: background var(--dur-micro-1) var(--ease-out), color var(--dur-micro-1) var(--ease-out),
    box-shadow var(--dur-micro-1) var(--ease-out);
}
.peek .pf-row .pf-dl:hover {
  color: var(--text-1); background: var(--on-glass-hi); box-shadow: inset 0 0 0 1px var(--on-glass-rim);
}

/* ==================================================================== phase-162: the view pill never grows in from the left
   The owner, of the dock: "this weird animation ... which makes the button
   show up from the left to the right". The lit pill (.vt-ind) carried a .46s
   transition on transform AND width (:2678, :6850) and was born at
   translateX(0), width 0, so every first paint slid and grew it in from the
   track's left end. At rest it now has NO transition: every write lands in
   place (first load, Back/Forward, resize, theme, a redraw). It moves only
   while board.js placeViewIndicator holds .p162-glide, after a tap on a view
   in the dock: transform alone (never width, never opacity), the one curve,
   --dur-micro-2. Reduced motion never gets the class, and is stilled here
   too in case it ever does. */
.app .vt-ind { transition: none; }
.app .vt-ind.p162-glide { transition: transform var(--dur-micro-2) var(--ease-out); }
@media (prefers-reduced-motion: reduce) { .app .vt-ind.p162-glide { transition: none; } }

/* ==================================================================== phase-164: the board holds still (phone pass P-A)
   design/phone-pass-2026-10-01.md, P-A and D-P1. Findings 1, 2, 23 (head row),
   NATIVE N1 and defect 107. Everything in this block is scoped to 700px and
   under, or to a touch device (N1), except the capsule's resting
   `display: none`, so 1440 and 1920 LAY OUT exactly as before. Motion does
   change at every width: this phase deleted the fadeUp arrival from .group,
   .calendar, .timeline-view, .files-view and .dw (finding 2), so boards,
   views and dashboard widgets appear in place instead of sliding up; only
   .auth-card keeps fadeUp. No backdrop-filter here: the phone "chrome is
   solid" order is untouched.

   1. The condensed phone head (D-P1). board.js p164Latch adds `cond` once the
      list has scrolled past the full head and removes it on 32px up or at the
      top. The head becomes ONE 56px row: title (17/600, one line, ellipsis,
      its caret), the Search capsule, the "+". It is an OVERLAY: the box drops
      to 56px and margin-bottom gives back exactly the room the full head took
      (--p164-rest, written by setCond just before the class lands), so the
      list never moves, with or without the browser's scroll anchoring.
      Nothing is removed: count, star, Board tools, the view capsule, Filter
      and Select are one short upward scroll away (D41). */
.p164-cap { display: none; }
@media (max-width: 700px) {
  .app .board-head.cond {
    display: flex; align-items: center; gap: 8px;
    height: 56px; padding-top: 0; padding-bottom: 0; flex-shrink: 0; /* .main is a flex column: a fixed height still shrinks to content (measured 47) */
    margin-bottom: calc(var(--p164-rest, 56px) - 56px);
  }
  /* named, not :not(): the floating selection bar (.p113-selbar, fixed) is
     also a child of the head and must keep its exit while condensed */
  .app .board-head.cond > .p112-sheets,
  .app .board-head.cond > .p112-crumb,
  .app .board-head.cond > .bfil,
  .app .board-head.cond > .vo-bar { display: none; }
  .app .board-head.cond > .board-title-row { flex: 1 1 auto; min-width: 0; flex-wrap: nowrap; margin: 0; row-gap: 0; }
  .app .board-head.cond > .board-title-row > :not(.board-name) { display: none; }
  .app .board-head.cond .board-name,
  .app .board-head.cond .board-name.tap { flex: 0 1 auto; min-width: 0; max-width: 100%; font-size: 17px; font-weight: 600; line-height: 22px; letter-spacing: 0; }
  /* the ellipsis lives on the button (display: block); the caret leaves the
     text run and sits at the button's right edge, so a long name is cut with
     "..." and still shows its caret. Short names: the button is fit-content,
     so the caret lands right after the last letter, as at rest. A cut name's
     "..." ends up to a character short of the edge; board.js p164FitCaret
     measures that slack into --p164-cut so the caret follows the "..." with
     the same ~10px ink gap as an uncut name (designer review). */
  .app .board-head.cond .board-name > .board-tap {
    position: relative; max-width: 100%; padding-right: 22px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis; overflow-wrap: normal;
  }
  .app .board-head.cond .board-tap > .p131-tail > .ic {
    position: absolute; right: var(--p164-cut, 0px); top: 50%; margin: -8px 0 0; width: 16px; height: 16px; vertical-align: 0;
  }
  .app .board-head.cond > .board-switch-row { flex: none; flex-wrap: nowrap; margin: 0; gap: 8px; }
  .app .board-head.cond > .board-switch-row > :not(.board-tools) { display: none; }
  .app .board-head.cond .board-switch-row > .board-tools { flex: none; flex-wrap: nowrap; gap: 8px; margin: 0; padding: 0; }
  .app .board-head.cond .board-switch-row > .board-tools > :not(.p164-cap):not(.p112-new) { display: none; }
  .app .board-head.cond .board-tools > .p164-cap {
    position: relative; display: inline-flex; align-items: center; gap: 6px; flex: none;
    height: 36px; max-width: 150px; padding: 0 14px 0 12px; margin: 0;
    border: 1px solid var(--line-2); border-radius: 999px; background: var(--surface-2);
    color: var(--text-2); font: 500 14px/20px var(--font-body); white-space: nowrap; cursor: pointer;
  }
  .app .board-head.cond .p164-cap > .ic { flex: none; width: 16px; height: 16px; color: var(--text-3); }
  .app .board-head.cond .p164-cap > .p164-cap-t { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  /* a narrowed list never looks complete: "12 of 487" in brand ink */
  .app .board-head.cond .p164-cap.on,
  .app .board-head.cond .p164-cap.on > .ic { color: var(--brand); }
  .app .board-head.cond .p164-cap:focus-visible { outline: none; box-shadow: var(--focus-ring); }

  /* the motion: the row's contents settle in, and the full head's contents
     settle back in on the way out; opacity and transform only, under 200ms
     (--dur-micro-2, 180ms), the house curve. p164-ex is set by setCond on an
     expand only, so opening a board draws instantly. Two keyframe names, so
     each class change restarts its own. */
  .app .board-head.cond > .board-title-row,
  .app .board-head.cond > .board-switch-row { animation: p164-fold var(--dur-micro-2) var(--ease-out) backwards; }
  .app .board-head.p164-ex:not(.cond) > .board-title-row,
  .app .board-head.p164-ex:not(.cond) > .board-switch-row,
  .app .board-head.p164-ex:not(.cond) > .bfil { animation: p164-open var(--dur-micro-2) var(--ease-out) backwards; }
}
@keyframes p164-fold { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes p164-open { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@media (max-width: 700px) and (prefers-reduced-motion: reduce) {
  .app .board-head.cond > .board-title-row,
  .app .board-head.cond > .board-switch-row,
  .app .board-head.p164-ex:not(.cond) > .board-title-row,
  .app .board-head.p164-ex:not(.cond) > .board-switch-row,
  .app .board-head.p164-ex:not(.cond) > .bfil { animation: none; }
}

/* 2. Tap targets on the head row (finding 23): the star (34x30), each
      inactive view tab (39 wide), the view caret (36 wide), Filter (33 wide),
      the capsule (36 tall) and each group's dashed "Add item" (38 tall) reach
      44x44 through a transparent pseudo-element centred on the control. The
      drawn size does not change. Each pseudo used here was measured unused at
      375 (the caret's ::after and the add button's ::before are taken).
      Six view tabs sit on a 41px pitch at 375, so neighbouring tab areas
      overlap by 3px; the later tab takes the overlap. */
@media (max-width: 700px) {
  .app .board-head .icon-btn.star,
  .app .board-head #qf-btn { position: relative; }
  .app .board-head .icon-btn.star::before,
  .app .board-head .view-tab:not(.active)::before,
  .app .board-head .vt-menu::before,
  .app .board-head #qf-btn::before,
  .app .board-head .p164-cap::before {
    content: ""; position: absolute; inset: min(0px, calc(50% - 22px));
  }
  .app .rcard.rcard-add::after { content: ""; position: absolute; left: 0; right: 0; top: min(0px, calc(50% - 22px)); bottom: min(0px, calc(50% - 22px)); }
}

/* 3. No zoom on focus (defect 107). iOS zooms the page into any text control
      under 16px on focus and leaves him to pinch out. At 700px and under every
      text-entry control computes 16px. !important on purpose: twenty-odd class
      rules size fields from 13 to 15px at higher specificity, and the
      invariant is "no field under 16px" whatever a later phase writes.
      Contenteditable renames (.th-label 12px, .group-title 15px) are text at
      rest and must keep their size, so they reach 16px only while focused,
      i.e. while he is actually editing; the item card's title (h3, 24px) is
      already above the floor and is left alone. */
@media (max-width: 700px) {
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]):not([type="hidden"]):not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]),
  select, textarea,
  [contenteditable]:not([contenteditable="false"]):not(h3):focus { font-size: 16px !important; }
}

/* 4. No whole-app stretch (NATIVE N1). The shell does not move when he pulls
      past the top or the end; only the list bounces. Phones and touch devices
      only: on a desktop the root's overscroll is also Chrome's two-finger
      swipe back, which this must not take away. Inside the app only
      (body.in-app, core.js): the landing and the sign-in pages keep the
      platform's own pull. */
@media (max-width: 700px), (hover: none) and (pointer: coarse) {
  html:has(> body.in-app), body.in-app { overscroll-behavior: none; }
  .main { overscroll-behavior-y: contain; }
}

/* ================================================================ phase-165
   P-B, first screens are finished (design/phone-pass-2026-10-01.md, D-P2,
   D-P3, findings 21, 4, 5, 9). Nothing here sets backdrop-filter, a
   transition or an animation, so the phone "chrome is solid" block keeps
   winning and there is no motion to still.
   1. The empty mark. The five dots are the loader and only the loader: an
      empty pane drew them too, so "nothing here" read as "still loading".
      Each empty pane now carries its own surface's icon in a 56px soft tile
      (the menu rows' tile language), its top placed at 38% of the pane's
      visible height by SD.p165Place (core.js) through --p165-pt; a pane the
      script never measured keeps the old 70px. Text is capped at 420px. */
.empty-state::before { content: none; background: none; }
.empty-state:has(> .p165-mark) { padding-top: var(--p165-pt, 70px); padding-bottom: 32px; }
.p165-mark {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 56px; height: 56px; margin: 0 auto 16px; border-radius: 16px;
  background: var(--surface-2); color: var(--text-3);
  box-shadow: inset 0 0 0 1px var(--border); /* the tile keeps an edge on a --surface-2 ground (the chat panel) */
}
.empty-state h3, .empty-state > p { max-width: 420px; margin-left: auto; margin-right: auto; }
.empty-state h3, .empty-state:has(> .p165-mark) > p, .ch-empty.p165-ch > p { text-wrap: balance; } /* no one-word last line */
/* the Files button names its item on a quiet line of its own (the button
   says only "Add a file"; the name, cut at a word, goes under it) */
.p165-to { flex: 1 0 100%; margin: 0; font-size: 12px; line-height: 16px; color: var(--text-3); overflow-wrap: anywhere; }
.p165-to b { color: var(--text-2); font-weight: 500; } /* the item's name reads; "to" stays quiet */
/* "Add a group": a quiet text link, one tap away under the doors */
.p165-link {
  background: none; border: 0; padding: 6px 10px; border-radius: var(--r-1); cursor: pointer;
  font: 500 13px/20px var(--font-body); color: var(--text-2);
  text-decoration: underline; text-decoration-color: var(--line-strong); text-underline-offset: 3px;
}
.p165-link:hover { color: var(--text-1); }
.p165-board .empty-acts .p165-br { flex: 1 0 100%; height: 0; }
/* New <item> is the first door: primary without a fill (D3a keeps the fill on
   the board head's own create button), so a brighter rim and a heavier label */
.p165-board .empty-acts > .btn.quiet.p165-first {
  color: var(--text-1); font-weight: 650;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--text-1) 42%, transparent);
}
/* the chat room: the mark at 38% of the thread, not floated to its middle */
.ch-empty.p165-ch { margin: 0 auto; padding: var(--p165-pt, 24px) 16px 16px; display: flex; flex-direction: column; align-items: center; max-width: 420px; }
.ch-empty.p165-ch .p165-mark { margin-bottom: 8px; }
.ch-empty.p165-ch .btn { margin-top: 16px; }
/* the calendar's short month is for the phone only */
.p165-ms { display: none; }

@media (max-width: 700px) {
  /* 2. The empty board's doors stack full width, 44 each (D-P2) */
  .p165-board .empty-acts { flex-direction: column; align-items: stretch; max-width: 420px; margin-left: auto; margin-right: auto; }
  .p165-board .empty-acts > .btn { min-height: 44px; justify-content: center; }
  .p165-board .empty-acts .p165-br { display: none; }
  .p165-board .empty-acts > .p165-link { align-self: center; min-height: 44px; }
  /* The one button in every other first screen (Files, calendar, chat) is 44 too */
  .empty-state:has(> .p165-mark) .empty-acts > .btn, .ch-empty.p165-ch > .btn { min-height: 44px; }
  /* 3. My work's filters are one row that scrolls sideways (D-P3, #21);
        SD.edgeFade fades it while it overflows. The row clips its own
        height, so a focused chip draws its ring on its own edge. */
  .mw-filters { flex-wrap: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; scroll-padding-inline: 40px; }
  .mw-filters::-webkit-scrollbar { display: none; }
  .mw-filters > .qf-chip { flex: none; }
  .mw-filters > .qf-chip:focus-visible { outline-offset: -2px; }
  /* 4. The calendar head on the phone: "Oct 2026" on one line, the long
        name kept for screen readers; Today is a capsule with a 44 hit area */
  .p165-ms { display: inline; white-space: nowrap; }
  .p165-ml { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
  .p113-cal .cal-head .btn.p113-today {
    position: relative; height: 32px; padding: 0 14px; border-radius: 999px;
    background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--border);
  }
  .p113-cal .cal-head .btn.p113-today::before { content: ""; position: absolute; inset: min(0px, calc(50% - 22px)); }
  /* the month never shrinks under its own words, and the count line takes
     its own row beneath the strip, so the two can never overlap (reached through
     the short month, never the timeline's shared .cal-head) */
  .p113-cal > :has(.p165-ms) { flex-wrap: wrap; row-gap: 4px; }
  .p113-cal h3:has(> .p165-ms) { flex: 0 0 auto; }
  .p113-cal .p165-caln { flex: 1 0 100%; margin-left: 0; }
}

/* ==================================================================== phase-166
   P-C: the sheets read as one family (design/phone-pass-2026-10-01.md). No
   transition, keyframe, blur or backdrop-filter in this block, so the phone
   "chrome is solid" blocks above are untouched. Every class it adds carries
   the p166- prefix; the rest re-places existing rows.
   1. Boards sheet (#18): an empty workspace says so in a row of its own,
      with New board beside the words (Recent is dropped in board.js). */
.sidebar .p166-none {
  display: flex; align-items: center; gap: 8px; min-height: 36px; margin: 0; padding: 0 12px 0 20px;
  font: 400 13.5px/20px var(--font-body); color: var(--p112-t3, var(--text-3));
}
.sidebar .p166-none > span { flex: 1 1 auto; min-width: 0; }
.sidebar .p166-new {
  flex: none; display: inline-flex; align-items: center; gap: 6px; min-height: 32px; margin-right: -8px; padding: 0 8px;
  border: 0; border-radius: var(--r-1); background: none; cursor: pointer;
  font: 500 13px/20px var(--font-body); color: var(--p112-t1, var(--text-1));
}
.sidebar .p166-new:hover { background: var(--p112-hover, var(--hover-ground)); }
.sidebar .p166-new:focus-visible { outline: none; box-shadow: var(--focus-ring); }
@media (max-width: 700px) {
  /* the row's ink on the rows' ink line: 8 of plate, 10 of padding */
  .sidebar .p166-none { min-height: 44px; margin: 0 8px; padding: 0 10px; }
  .sidebar .p166-new { min-height: 44px; margin-right: -10px; padding: 0 10px; }
}
/* 2. Filter header (#14): "Filter 487 items" and Clear all share row one,
      the search runs the whole plate span on row two. The paddings pinned
      by phase-150 (8 inline on desktop, 14 10 on a phone) stand. */
.qf-head {
  display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "t c" "s s";
  align-items: center; column-gap: 12px; row-gap: 10px;
}
.qf-head > .qf-t { grid-area: t; flex-direction: row; align-items: baseline; gap: 8px; }
.qf-head > .qf-t > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.qf-head > [data-act="quick-clear"] { grid-area: c; }
.qf-head > .qf-search { grid-area: s; min-width: 0; margin: 0 -8px; }
@media (max-width: 700px) { .qf-head > .qf-search { margin: 0 -10px; } }
/*    the group headings' icons stand bare: no tile, no outline */
.popover .qf-sec-head .qf-ic, .qf-sec-head .qf-ic { background: transparent; box-shadow: none; }
/* 3. Board tools (#17): every trailing value is quiet and stands on one
      right ink edge; a separator never shrinks inside the scrolling menu */
.menu-sep { flex: none; height: 1px; margin: 6px 0; }
.p112-tools .menu-item > .muted.sm {
  flex: 0 1 auto; min-width: 0; margin-left: auto;
  font-size: 12px; font-weight: 400; text-align: right; color: var(--p112-t3, var(--text-3));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 5. No hold menus (N2): on a phone a held control never raises the link
      menu or starts a selection; what a person reads or types still selects */
@media (max-width: 700px) {
  .rail, .sidebar, .board-head, .group-head, .p112-dock, .p113-selbar, .popover .menu-item, button, [data-act] {
    -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
  }
  input, textarea, [contenteditable]:not([contenteditable="false"]),
  .iv-row .cell, .update-body, .ch-line {
    -webkit-user-select: text; user-select: text;
  }
}

/* ==================================================================== phase-168
   Small finishing touches (design/phone-pass-2026-10-01.md, "## Phase-168").
   No transition, keyframe, blur or backdrop-filter in this block, so the
   phone "chrome is solid" blocks above keep winning. New classes carry p168-.
   a. Chat: in a workspace of one, an admin's "People" heading stands over an
      invite row built like a person row (.ch-row, 7 + 30 + 7 = 44); the plus
      sits in the avatar slot, a 30px circle on surface-2 with a 1px edge. */
.p168-plus {
  width: 30px; height: 30px; flex: none; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--text-2);
  box-shadow: inset 0 0 0 1px var(--border);
}
/* d. The timeline's cut name is for the phone only */
.p168-tl-cut { display: none; }
@media (max-width: 700px) {
  /* b. Calendar and timeline on the phone lead with what they show: the
        title first at the leading edge, previous / Today / next together at
        the trailing edge (also when Full range or all of them wrap to a
        second line). The heading is not focusable, so Tab order is as before. */
  .cal-head.p113-strip { justify-content: flex-end; }
  .cal-head.p113-strip > .p113-month { order: -1; margin: 0 auto 0 0; }
  /* d. Timeline names end at a whole word: the full name stays for screen
        readers (the .p165-ml pattern), the name cut at 26 shows and wraps */
  .timeline-view .tl-row { height: auto; min-height: 40px; }
  .timeline-view .tl-row > .tl-label {
    position: relative; padding: 6px 10px 6px 0; font-size: 13px; line-height: 18px;
    white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: break-word;
  }
  .p168-tl-full { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
  .p168-tl-cut { display: inline; }
}

/* ============================================================ phase-169
   Native phone feel (design/phone-pass-2026-10-01.md, Phase-169, N3 N5 N6).
   No backdrop-filter here: the phone "chrome is solid" block (above) keeps
   winning. Motion is written from core.js on the one curve (var(--ease-out))
   and only as transform and opacity; reduced motion closes instantly there.
   N3: a sheet follows the finger. While the finger holds it nothing eases. */
@property --p169-k { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --p169-sal { syntax: "<length>"; inherits: true; initial-value: 0px; }
:root { --p169-sal: env(safe-area-inset-left, 0px); }
.p169-drag { transition: none !important; }
/* the modal's backdrop fades with the drag; opacity on the overlay would fade the card too */
.modal-overlay.p169-k { background: color-mix(in srgb, var(--scrim) calc(var(--p169-k, 1) * 100%), transparent); }
/* N6: the standalone edge swipe moves the page with a shadow at its left edge */
.p169-edge { box-shadow: -12px 0 24px rgba(0, 0, 0, .12); }

/* N5: the keyboard is open (html.p169-kb, set by SD.kb from visualViewport).
   Every sheet sits on the keys' top edge; the bar and the selection bar step
   aside (hidden, not cleared); the action row stays on screen. */
@media (max-width: 700px) {
  html.p169-kb .popover.sheet { bottom: calc(var(--p169-kb) + 8px); max-height: calc(var(--p169-vvh) - 16px); }
  html.p169-kb #modal-root .modal-overlay { bottom: var(--p169-kb); }
  html.p169-kb .modal { max-height: calc(var(--p169-vvh) - 12px); }
  html.p169-kb .rail, html.p169-kb .p113-selbar { visibility: hidden; }
  /* chat and Claude keep phase-163's bottom: var(--bar-h); while the keys are
     up that floor is the keys' top edge, never lower than the bar itself.
     --p169-bar is the bar as the root computes it, so this is not a cycle. */
  :root { --p169-bar: var(--bar-h); }
  html.p169-kb .chat-panel, html.p169-kb .ai-panel { --bar-h: max(var(--p169-bar), var(--p169-kb)); }
  html.p169-kb .modal .modal-foot, html.p169-kb .modal .edit-row, html.p169-kb .modal .p127-dp-foot,
  html.p169-kb .popover.sheet .modal-foot, html.p169-kb .popover.sheet .edit-row, html.p169-kb .popover.sheet .p127-dp-foot {
    position: sticky; bottom: 0; z-index: 1; background: var(--surface-1);
  }
  /* a phone dialog keeps 24px of padding under its content and sticky stops
     at that padding: the row reaches into it, so nothing scrolls through
     a strip below the buttons */
  html.p169-kb .modal .modal-foot, html.p169-kb .modal .edit-row, html.p169-kb .modal .p127-dp-foot {
    bottom: -24px; padding-bottom: 24px; margin-bottom: -24px;
  }
  /* a short visible area (landscape with the keys up): edge to edge, top corners only */
  html.p169-kb-short .popover.sheet { bottom: var(--p169-kb); left: 0; right: 0; max-height: var(--p169-vvh); border-radius: 18px 18px 0 0; }
  html.p169-kb-short .modal { max-height: var(--p169-vvh); }
}
/* landscape phones are wider than 700, so a popover is not a sheet there; the
   modal still has to fit what the keys leave */
@media (max-width: 900px) and (max-height: 440px) {
  html.p169-kb #modal-root .modal-overlay { bottom: var(--p169-kb); }
  html.p169-kb .modal { max-height: calc(var(--p169-vvh) - 12px); }
  /* with the keys up a placed popover has no room left to be placed in: it
     takes the visible area edge to edge, like the phone sheet (inline
     top/left/max-height come from SD.popPlace, hence !important) */
  html.p169-kb-short .popover { top: auto !important; bottom: var(--p169-kb) !important; left: 0 !important; right: 0 !important;
    width: auto !important; max-height: var(--p169-vvh) !important; border-radius: 18px 18px 0 0; }
}

/* ================================================================ phase-170-b
   Many files at once (uploads.js): the upload tray and the board drop
   overlay. Solid surfaces only, no backdrop-filter anywhere in this block, so
   the phone "chrome is solid" ordering is untouched. Bars and rims are UI
   colour (brand, ok, danger), never a data chip. Layering: above the board
   and the item card (modal 80), below sheets (89) and popovers (90). */
.p170-tray { position: fixed; right: 20px; bottom: 20px; z-index: 86; width: min(380px, calc(100vw - 40px));
  display: flex; flex-direction: column; max-height: min(60vh, 520px); overflow: hidden;
  background: var(--surface-1); border: 1px solid var(--line-2); border-radius: var(--r-md); box-shadow: var(--elev-3); }
/* above the board's view dock, where the toasts stand too (:11198), so the
   tray never covers the Files tab it may have just been opened from */
@media (min-width: 701px) { html:has(.app .board-head .p112-dock) .p170-tray { bottom: 104px; max-height: min(calc(100vh - 140px), 520px); } }
.p170-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 8px 8px 16px; border-bottom: 1px solid var(--line-1); }
.p170-ttl { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; font-weight: 600; color: var(--text-1); }
.p170-close { flex: none; }
.p170-list { list-style: none; margin: 0; padding: 6px 0; overflow-y: auto; min-height: 0; }
.p170-row { display: grid; grid-template-columns: 18px minmax(0, 1fr) auto; align-items: center; gap: 10px; padding: 8px 10px 8px 16px; }
.p170-ic { display: flex; color: var(--text-3); }
.p170-main { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.p170-line { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.p170-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 13px; color: var(--text-1); }
.p170-size { flex: none; font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.p170-bar { display: block; height: 4px; border-radius: 2px; background: var(--surface-3); overflow: hidden; }
.p170-bar i { display: block; height: 100%; background: var(--brand); transform-origin: 0 50%; transform: scaleX(0);
  transition: transform var(--dur-micro-2) var(--ease-out); }
.p170-row[data-state="done"] .p170-bar i { background: var(--ok); }
.p170-row[data-state="failed"] .p170-bar i { background: var(--danger); transform: scaleX(1) !important; }
.p170-st { font-size: 12px; color: var(--text-2); font-variant-numeric: tabular-nums; }
.p170-row[data-state="done"] .p170-st { color: var(--ok); }
.p170-row[data-state="failed"] .p170-st { color: var(--danger); }
.p170-retry[hidden] { display: none; }
.p170-retry { flex: none; }
/* the drop overlay: a frame over the board while files are over it, with one
   line saying how many and where they go; the lit row or group is the answer
   to "which one", so a row drop clears the frame's tint */
.p170-drop { position: fixed; z-index: 85; pointer-events: none; display: flex; justify-content: center; align-items: flex-start;
  padding-top: 16px; border-radius: 14px; box-shadow: inset 0 0 0 2px var(--brand); background: color-mix(in srgb, var(--brand) 5%, transparent); }
.p170-drop.on-row { background: transparent; }
.p170-drop-tag { display: inline-flex; align-items: center; gap: 8px; max-width: calc(100% - 32px); padding: 9px 16px; border-radius: 999px;
  background: var(--surface-1); border: 1.5px solid var(--brand); color: var(--text-1); font-size: 14px; font-weight: 600; box-shadow: var(--elev-3); }
.p170-drop-tag .ic { flex: none; color: var(--brand); }
.p170-drop-tag span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
html.p170-on #board-body { box-shadow: none; }
tr.item-row.p170-hit > td, tr.item-row.p170-hit > th { background: var(--brand-soft) !important; box-shadow: inset 0 2px 0 var(--brand), inset 0 -2px 0 var(--brand); }
.kanban-card.p170-hit, .rcard.p170-hit { background: var(--brand-soft) !important; box-shadow: 0 0 0 2px var(--brand) !important; }
section.group.p170-hit { border-radius: 12px; box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand) 70%, transparent); }
/* the empty library's many-at-once line */
.p170-hint { display: flex; flex-direction: column; align-items: center; gap: 10px; max-width: 420px; margin: 0 auto 24px; padding: 0 16px; text-align: center; }
@media (max-width: 700px) {
  .p170-tray { left: 8px; right: 8px; width: auto; bottom: calc(var(--bar-h) + 8px); max-height: 50vh; }
  .p170-close, .p170-retry { min-height: 40px; }
  .p170-drop-tag { font-size: 13px; }
}
/* phase-170-b (review 5): the tray stands clear of an open item card. The
   tray is above the card (86 over 80), so it moves instead of hiding: on a
   phone it goes to the top of the screen and the card, a bottom sheet,
   gives up exactly the tray's height (--p170-h, measured by uploads.js), so
   the card's head and its pinned composer both stay in view; with the keys
   up the card owns the screen and the tray waits out of sight. From 1000px
   the tray stands to the left of the card. */
@media (max-width: 700px) {
  html:has(#modal-root .modal.p113-icm) .p170-tray { top: calc(8px + env(safe-area-inset-top, 0px)); bottom: auto; max-height: 34vh; }
  html:not(.p169-kb):has(#p170-tray):has(#modal-root .modal.p113-icm) .modal.p113-icm {
    max-height: calc(100dvh - var(--bar-h) - var(--p170-h, 0px) - 28px); }
  html.p169-kb:has(#modal-root .modal.p113-icm) .p170-tray { visibility: hidden; }
}
@media (min-width: 1000px) {
  html:has(#modal-root .modal.p113-icm) .p170-tray { right: calc(var(--p113-icw) + 32px); }
}
/* phase-170-b (designer review): between 701 and 999 there is no room
   beside the card, so the tray takes the card's width at the card's foot
   and the card gives up the tray's height plus 12. The card's own lift
   above a resting dock (margin-bottom 88, :10930) stays, and the tray
   stands on the same line the card would have ended on. */
@media (min-width: 701px) and (max-width: 999px) {
  html:has(#modal-root .modal.p113-icm) .p170-tray { right: 16px; bottom: 16px; width: var(--p113-icw); max-width: calc(100vw - 32px); max-height: 34vh; }
  html.p119-rp:not(.p119-side):has(.app .board-head .p112-dock):has(#modal-root .modal.p113-icm) .p170-tray { bottom: 104px; }
  html:has(#p170-tray):has(#modal-root .modal.p113-icm) .modal.p113-icm { bottom: calc(28px + var(--p170-h, 0px)); }
}
/* the drop target's lines run the whole row: the register's frozen name
   cell (:9299) is opaque ground with no shadow and outranked the plain rule */
.app .board-table tr.item-row.p170-hit > td, .app .board-table tr.item-row.p170-hit > th {
  background: var(--brand-soft) !important; box-shadow: inset 0 2px 0 var(--brand), inset 0 -2px 0 var(--brand) !important; }
.app .board-table tr.item-row.p170-hit > td.col-name {
  background: linear-gradient(var(--brand-soft), var(--brand-soft)), var(--ground) !important; }
.app .board-table tr.item-row.p170-hit > td.col-name::after { background: none !important; }
/* Add files keeps its words at every width (the bar's act-label rule,
   :1494, would leave a bare plus beside New item's) and stands as tall as
   its neighbours: 30 in the bar, 44 under a thumb */
.p170-pick { margin: 0; }   /* a <label>: the form rule (:411) adds a 12px foot */
.p170-pick .act-label { display: inline; white-space: nowrap; }
.fv-right .p170-pick { height: 30px; }
@media (max-width: 700px) { .fv-right .p170-pick { height: 44px; } }
/* the empty library's line only speaks of dropping, which needs a pointer */
@media (hover: none) { .p170-hint { display: none; } }
/* tray rows: Retry sits on the status line so every bar ends at the same
   right edge; the icon centres on the name line; failed rows lead the list
   so they are in view on a phone (order only, the DOM keeps file order) */
.p170-row { grid-template-columns: 18px minmax(0, 1fr); align-items: start; }
.p170-ic { height: 16px; align-items: center; }
.p170-list { display: flex; flex-direction: column; }
.p170-row[data-state="failed"] { order: -1; }
.p170-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-width: 0; min-height: 18px; }
.p170-foot .p170-st { min-width: 0; }
.p170-foot .p170-retry { margin: -4px 0; }
@media (max-width: 700px) { .p170-foot .p170-retry { margin: 0; } }
/* the item card's Add file keeps its word at every width (the hint below
   names it), and the hint speaks of dropping only where there is a pointer */
.attach-btn .act-label { display: inline; white-space: nowrap; }
.attach-empty .p170-touch { display: none; }
@media (hover: none) {
  .attach-empty .p170-ptr { display: none; }
  .attach-empty .p170-touch { display: inline; }
}
