/* =============================================================================
   PLAY DESIGNER v2 — the standalone page's LOOK. Sep 22 2026 (PR 2, M1).

   ⛔ LIFTED, NOT REINTERPRETED. Every token block below comes from
   design-directions/play-designer-v2.html as of branch
   design/motion-follows-the-field (PR #1509) — the DESIGN RECORD named in the
   brief. Its `:root`, Day, Night, Broadcast-chrome, Whiteboard-chrome, the
   three `[data-field]` boards, `[data-tokens="colour"]`, the accent table, the
   kind-by-line rules, defence red + outline, the white-letter halo, the
   QB-is-ink rule and the count-is-a-numeral rule are all here, value for value.

   WHY THIS IS ITS OWN FILE, not more of css/install-design.css:
   css/install-design.css is loaded by coach/install.html, where the Design
   rail ships today and where NO field dial exists. Everything here is scoped
   under `[data-field]` / `[data-chrome]` / `[data-theme="whiteboard"]`, which
   that page never sets — but a shared file is a shared blast radius, and the
   honest boundary is the file. coach/play-designer.html loads BOTH; Install
   loads only its own. One file, as the brief asked: this one.

   FOUR DEPARTURES FROM A MECHANICAL LIFT, each forced, each named:

   1. THE MOCK'S BOARD IS `.board`; THIS BOARD IS `.pd-field`, painted by
      js/install-design-svg.js, whose class/attribute vocabulary is a contract
      (its header says so). So `.board .route.run` becomes
      `.pd-field .route[data-kind="run"]`, `.board .ball` becomes
      `.pd-field .chain .football`, `.board .def-label` has no equivalent yet
      (defence labels arrive with the Defense tab, milestone 3) and is left out
      rather than written against markup that does not exist.

   2. THE MOCK PAINTS ITS TURF WITH `<rect class="turf" fill="url(#turf)">`.
      This surface draws no background rect at all — the board is transparent
      over `.pd-stage`. Adding one means editing the shared paint module for a
      chrome-only effect, so the field is a CSS `background` on the svg element
      instead. Same gradient, same stops (a · c · a, horizontal).

   3. ONE NEW TOKEN: `--tok-disc`, the MONO disc colour, declared beside the
      mock's six `--tok-Q..H` in every field block with the same value they
      carry there. The mock only ever draws six positions; a real play can put
      an R, a second H or a coach's own letter on the board, and a per-position
      rule set leaves those discs unpainted. `--tok-disc` is what an unnamed
      position gets, which is exactly what the mock's six all are in mono.

   4. THE `--pd-*` BRIDGE (bottom of this file). css/install-design.css paints
      the board through its own `--pd-route / --pd-ball / --pd-select / …`
      indirection. Rather than duplicate and outrank thirty rules, this file
      REDEFINES those tokens from the field's own under `:root[data-field]`.
      One indirection layer, no `!important`, and every existing `.pd-field`
      rule keeps working — including ones written after this file was.

   NOTHING HERE IS SAVED. Mode, field, tokens, label, sizes and weights are
   paint: js/designer-prefs.js is their one store and `plays.canvas_data` never
   sees them.
   ========================================================================== */

/* ── MONO IS THE BASE (mock:38-47) ───────────────────────────────────────
   #1509: v1's six hues used to live on `:root`, so any state that reached the
   base — a fieldless board, a future product path, a bug — resurrected
   white-on-cyan at 2.43:1. The hues now exist ONLY under
   [data-tokens="colour"]. What the base owns is the safe default: a white disc
   with an ink letter. This block OUTRANKS css/install-design.css's `:root`
   (same specificity, later file) on purpose. */
:root {
    --tok-Q: #ffffff; --tok-C: #ffffff; --tok-X: #ffffff; --tok-Y: #ffffff; --tok-Z: #ffffff; --tok-H: #ffffff;
    --tok-disc: #ffffff;                 /* departure 3 */
    --tok-ink: #0d1220;
    --select: var(--volt);
    --read: var(--volt);                 /* the defender an option keys on */
    --route-w: 2.5;                      /* the LINE dial's default; js/designer-prefs.js sets the live value */
    --tok-halo-w: 1.9px;
    --sel-glow: var(--route-pass);       /* mock:334 — the glow follows the leg, never the key */
    --fs-turf: #3e7842; --fs-broadcast: #171b21; --fs-white: #ffffff;   /* the FIELD picker's own swatches */
}

/* ══ CHROME ══════════════════════════════════════════════════════════════
   data-theme = the CHROME (head, board card, inspector, controls)
   data-field = the BOARD (routes, discs, chalk, the ball, the LOS)
   THE FIELD OWNS WHAT SITS ON IT. THE CHROME OWNS THE CONTROLS. ────────── */

/* NIGHT = BROADCAST (mock:100-121). Deep charcoal under glass: the board card
   and the inspector are real backdrop blur over an ambient wash, hairline-lit.
   One control accent — grass green since Oct 10 2026 (Carlos: "the winner is
   charcoal black with the green"); it was volt. Depth does the work colour used
   to. The --volt NAME stays: every rule below reads it as "the accent". */
:root[data-theme="dark"][data-chrome="broadcast"] {
    --canvas: #08090b;
    --surface: #101216; --surface-elevated: #0b0c0f;
    --hover: rgba(255,255,255,0.07);
    --border: rgba(255,255,255,0.10); --border-strong: rgba(255,255,255,0.22);
    --ink: #f6f7f8; --ink-muted: #a8abb2; --ink-faint: #74777e; --on-ink: #08090b;
    --volt: #22c55e; --volt-deep: #16a34a; --volt-glow: rgba(34,197,94,0.38); --on-volt: #06080a;   /* ink on green 8.5:1 */
    --live: #ff5a5f;
    --seg-accent: var(--volt);
    --pm-ink: rgba(255,255,255,0.05); --pm-ink-raised: rgba(255,255,255,0.14); --pm-ink-text: #9ba0a8; --pm-ink-text-hover: #d8dbdf; --pm-ink-text-active: #ffffff;
    --seg-inset: inset 0 0 0 1px rgba(255,255,255,0.08);
    --seg-raise: inset 0 1px 0 rgba(255,255,255,0.16), 0 3px 10px rgba(0,0,0,0.5);
    --pd-glass: linear-gradient(180deg, rgba(255,255,255,0.10), rgba(255,255,255,0.035));
    --pd-glass-edge: rgba(255,255,255,0.18);
    --ambient-body: radial-gradient(1200px 760px at 46% -8%, rgba(34,197,94,0.075), transparent 64%), radial-gradient(880px 680px at 104% 106%, rgba(34,197,94,0.045), transparent 60%);
    --ambient-board: radial-gradient(72% 80% at 50% 46%, rgba(34,197,94,0.11), transparent 72%);
}

/* WHITEBOARD chrome (mock:125-137) — the coaches' room. One white plate,
   hairline dividers, and the blue pen as the control accent. Save stays ink so
   the primary verb never wears the warning colour. */
:root[data-theme="whiteboard"] {
    --canvas: #ffffff; --surface: #ffffff; --surface-elevated: #ffffff;
    --hover: #f1f2f4;
    --border: rgba(17,19,22,0.13); --border-strong: rgba(17,19,22,0.32);
    --ink: #111316; --ink-muted: #4f5052; --ink-faint: #6f757f; --on-ink: #ffffff;
    --volt: #1d4ed8; --volt-deep: #1a43b8; --volt-glow: rgba(29,78,216,0.26); --on-volt: #ffffff;
    --live: #d92d20;   /* destructive stays red, but only on a control's hover — on the BOARD red means defence, and nothing else */
    --seg-accent: var(--volt);
    --pm-ink: #ebedf0; --pm-ink-raised: #ffffff; --pm-ink-text: #5b6472; --pm-ink-text-hover: #2a2e34; --pm-ink-text-active: #111316;
    --seg-inset: inset 0 0 0 1px rgba(17,19,22,0.12);
    --seg-raise: inset 0 1px 0 #ffffff, 0 1px 3px rgba(17,19,22,0.18);
}

/* DAY chrome is css/practice.css's light ladder — nothing is added here, on
   purpose (the mock says the same, mock:139-140). */

/* ══ FIELD ═══════════════════════════════════════════════════════════════ */

/* TURF (mock:145-157) — v1's green turf, kept. Its MARKS are not: Carlos
   unfroze the palette on Sep 22, so the discs go white with ink letters and
   the routes go monochrome with kind in the line, exactly like the other two
   fields. ONE GRAMMAR ON EVERY FIELD. */
:root[data-field="turf"] {
    --board-los-line: #2f7dff;   /* the LINE OF SCRIMMAGE's own colour — broadcast-TV blue, a hue no route, token or ball uses (Sep 24 2026) */
    --board-turf-a: #264c28; --board-turf-b: #316934; --board-turf-c: #3e7842;
    --board-chalk: rgba(255,255,255,0.28); --board-chalk-strong: rgba(255,255,255,0.7);
    --board-los: rgba(255,255,255,0.92); --board-ez: #1c3a1e;
    --board-ez-a: 0.62; --board-ez-word: rgba(255,255,255,0.3); --board-goal: #ffffff; --board-ez-pat: rgba(0,0,0,0.2);   /* chunk H: tinted end zone, stadium-paint lettering, white goal/end lines */
    --route-pass: #ffffff; --route-run: #ffffff; --route-pa: #ffffff;
    --ball: var(--key);
    --tok-Q: #ffffff; --tok-C: #ffffff; --tok-X: #ffffff; --tok-Y: #ffffff; --tok-Z: #ffffff; --tok-H: #ffffff;
    --tok-disc: #ffffff;
    --tok-ink: #0d1220;                  /* ink letter in a white disc = 18.7:1, where white-on-cyan was 2.4:1 */
    --key: #b8ff00;                      /* volt reads 4.4:1 on turf; ink would vanish into the dark green */
}

/* BROADCAST (mock:159-170) — the telestrator board. Charcoal bands,
   monochrome white routes with a soft bloom, ink discs with white letters. */
:root[data-field="broadcast"] {
    --board-los-line: #2f7dff;   /* the LINE OF SCRIMMAGE's own colour — broadcast-TV blue, a hue no route, token or ball uses (Sep 24 2026) */
    --board-turf-a: #0e1014; --board-turf-b: #12151a; --board-turf-c: #171b21;
    --board-chalk: rgba(255,255,255,0.15); --board-chalk-strong: rgba(255,255,255,0.56);
    --board-los: rgba(255,255,255,0.78); --board-ez: #0a0b0e;
    --board-ez-a: 0.7; --board-ez-word: rgba(255,255,255,0.24); --board-goal: #ffffff; --board-ez-pat: rgba(255,255,255,0.04);
    --route-pass: #ffffff; --route-run: #ffffff; --route-pa: #ffffff;
    --ball: var(--key);
    --tok-Q: #ffffff; --tok-C: #ffffff; --tok-X: #ffffff; --tok-Y: #ffffff; --tok-Z: #ffffff; --tok-H: #ffffff;
    --tok-disc: #ffffff;                 /* Sep 22 ruling: solid white on a dark field */
    --tok-ink: #0d1220;
    --key: #c8ff00;
}

/* WHITE (mock:174-185) — the paper board the wristband prints. Day's board
   with its ONE measured defect closed: Day's run gold is 1.92:1 on white, so
   this field draws all three kinds in ink and moves KIND into the line. */
:root[data-field="white"] {
    --board-los-line: #1d64d8;   /* the LINE OF SCRIMMAGE's own colour — broadcast-TV blue, a hue no route, token or ball uses (Sep 24 2026) */
    --board-turf-a: #ffffff; --board-turf-b: #ffffff; --board-turf-c: #ffffff;
    --board-chalk: rgba(13,18,32,0.11); --board-chalk-strong: rgba(13,18,32,0.42);
    --board-los: rgba(13,18,32,0.72); --board-ez: #eceef1;   /* = designer-palette.js chalk.paper.endzone */
    --board-ez-a: 1; --board-ez-word: rgba(13,18,32,0.14); --board-goal: #0d1220; --board-ez-pat: rgba(13,18,32,0.02);   /* paper: white lines would vanish — the goal line is ink */
    --route-pass: #0d1220; --route-run: #0d1220; --route-pa: #0d1220;
    --ball: var(--key);
    --tok-Q: #0d1220; --tok-C: #0d1220; --tok-X: #0d1220; --tok-Y: #0d1220; --tok-Z: #0d1220; --tok-H: #0d1220;
    --tok-disc: #0d1220;                 /* Sep 22 ruling: solid black on the white board */
    --tok-ink: #ffffff;
    --key: #0d1220;                      /* volt on white is 1.21:1 — the key is INK here, under every chrome... */
}
/* ...except under whiteboard chrome, where the coach has a second pen. Red is
   DEFENCE everywhere, so the key is the BLUE pen — 6.7:1 on white, and it
   cannot be mistaken for a defender. (mock:187) */
:root[data-theme="whiteboard"][data-field="white"] { --key: #1d4ed8; }

/* the key drives the three board marks it owns, in every field (mock:190) */
/* ⛔ THE BOARD'S KEY IS A DIAGRAM COLOUR, NOT THE CHROME ACCENT (Oct 10 2026).
   The chrome went charcoal + grass green; the board's select/read key, the QB
   accent disc and the at-rest ball keep the look Carlos ruled on Oct 6 (and the
   canvas renderer, js/play-renderer.js, paints the same values). Allowlisted in
   scripts/audit-coach-palette.js as play-diagram colours. */
:root[data-field] { --select: var(--key); --read: var(--key); }

/* the selection glow follows the LEG, not the key (mock:349-351) — where those
   differ, the glow weaves a second colour through the motion squiggle. */
:root[data-field="turf"],
:root[data-field="broadcast"] { --sel-glow: var(--key); }
:root[data-field="white"] { --sel-glow: rgba(13,18,32,0.55); }

/* ══ THE BOARD, re-pointed at .pd-field ══════════════════════════════════ */

/* departure 2: the field is the svg's own background, same gradient as the
   mock's <rect class="turf" fill="url(#turf)"> (mock:797). */
:root[data-field] .pd-field {
    background: linear-gradient(90deg, var(--board-turf-a) 0%, var(--board-turf-c) 50%, var(--board-turf-a) 100%);
    border-radius: var(--r-xs);
}

/* KIND WITHOUT HUE (mock:199-200): pass solid · run dashed 9/5 · play-action
   dotted. The END CAP is NOT asked to carry kind as well — it already means
   the ending (hitch · curl · sit · block), and a mark with two meanings lies. */
:root[data-field] .pd-field .route { stroke-width: var(--route-w); }
:root[data-field] .pd-field .route[data-kind="run"]:not(.ballpath):not(.motion) { stroke-dasharray: 9 5; stroke-width: calc(var(--route-w) + 0.5); }
:root[data-field] .pd-field .route[data-kind="pa"]:not(.ballpath):not(.motion) { stroke-dasharray: 0.5 5.5; stroke-width: calc(var(--route-w) + 0.9); }
/* an OPTION branch is dashed because it is a choice, not because of its kind —
   css/install-design.css:467's rule, re-declared at this file's specificity so
   the kind rules above cannot take the meaning off it. */
:root[data-field] .pd-field .route.dashed { stroke-dasharray: 5 4; }
:root[data-field] .pd-field .route.opt { stroke-dasharray: 5 4; }
/* THE PER-ROUTE WEIGHT STILL WINS. `#pdFootWeight` / IDD.setRouteWeight is a
   property OF THE RECORD; the LINE dial above is the board's default. These
   restate css/install-design.css:224-225 at this file's weight so the override
   is still an override. */
:root[data-field] .pd-field .route[data-weight="s"],
:root[data-field] .pd-field .rgroup[data-weight="s"] .route { stroke-width: 1.6; }
:root[data-field] .pd-field .route[data-weight="l"],
:root[data-field] .pd-field .rgroup[data-weight="l"] .route { stroke-width: 4; }

/* ── ROUTE CASING (Oct 1 2026, "the turf fix") ────────────────────────────
   Carlos: a green route on a GREEN turf field disappeared completely, and a
   yellow one vanished the same way on the WHITE board. js/install-design-svg.js
   (buildRoutePath) draws one extra path per route, `class="route-casing"`,
   same `d`, BEFORE the real route in the DOM — a wider, solid, mildly
   translucent stroke tinted toward the field's own ink or paper, the way a
   road survives its map's background on a road atlas. It carries no hue of
   its own and no dash pattern (a dashed casing would fight the route's own
   kind-line), and it is unstyled — so invisible, an SVG default fill: black
   paints nothing on a zero-area open path — on any page that loads
   install-design-svg.js without this stylesheet (coach/install.html has no
   field dial and no turf/white/broadcast risk to begin with). */
:root[data-field] .pd-field .route-casing {
    /* ⛔ (Oct 8 2026) NOT 1.8x ANY MORE, AND NOT UNDER THE LINE: it is the INLAY
       that rides on top of a cased route at 4/7 of its width (the huddle's
       1.5/7 edge, js/play-renderer.js ROUTE_CASING_EDGE). Transparent unless
       the look says this route needs a casing (--lk-ri-*, rules below). */
    --pd-inlay-k: 0.5714;
    fill: none;
    stroke: transparent;
    stroke-width: calc(var(--route-w) * var(--pd-inlay-k));
    stroke-linecap: round;
    stroke-linejoin: round;
    pointer-events: none;
}
:root[data-field] .pd-field .route-casing[data-kind="run"] { stroke-dasharray: 9 5; stroke-width: calc((var(--route-w) + 0.5) * var(--pd-inlay-k)); }
/* a DOTTED (play-action) route: the dot must read as a coloured dot, not a
   ring — the inlay fills 3/4 of it, a thin dark rim stays (Oct 8 2026) */
:root[data-field] .pd-field .route-casing[data-kind="pa"] { --pd-inlay-k: 0.75; stroke-dasharray: 0.5 5.5; stroke-width: calc((var(--route-w) + 0.9) * var(--pd-inlay-k)); }
:root[data-field] .pd-field .route-casing[data-weight="s"],
:root[data-field] .pd-field .rgroup[data-weight="s"] .route-casing { stroke-width: calc(1.6 * var(--pd-inlay-k)); }
:root[data-field] .pd-field .route-casing[data-weight="l"],
:root[data-field] .pd-field .rgroup[data-weight="l"] .route-casing { stroke-width: calc(4 * var(--pd-inlay-k)); }
:root[data-field="turf"]      { --route-casing: #0d1220; }
:root[data-field="white"]     { --route-casing: #0d1220; }
:root[data-field="broadcast"] { --route-casing: #ffffff; }

/* THE BALL IS NOT A ROUTE KIND (mock:217-219). v1 draws its flight dashed,
   which on a monochrome board is the same sentence as "this is a run". Where
   kind lives in the line, the ball chain becomes a THIN SOLID arc in the
   field's key — a weight and a colour no route can wear. */
:root[data-field] .pd-field .route.ballpath { stroke-dasharray: none; stroke-width: 1.6; }
:root[data-field] .pd-field .chain .snapline { stroke-dasharray: none; stroke-width: 1; }
/* the football keeps its seam on a board its own colour (mock:234-236) */
:root[data-field="white"] .pd-field .chain .football { stroke: #ffffff; }
:root[data-field="broadcast"] .pd-field .chain .football { stroke: #06080a; }
:root[data-field="turf"] .pd-field .chain .football { stroke: #0d1220; }

/* ══ THE LOOK (Oct 6 2026 — Carlos: "I actually love it") ═════════════════
   Theme · Background · Tokens · Palette · Polished. Every colour below comes
   from js/designer-palette.js, written onto <html> as custom properties by
   js/designer-prefs.js (paintLook). This file carries NO token hex: the
   module is the one palette, and the canvas renderer (cards, huddle, export)
   and the wristband read the same table, so the board, the card and the
   printed band are one picture.
   THE DISC: the look's fill (a matte gradient, url(#pdTokMat-<slot>), when
   Polished), the letter the module chose by ratio (white, or ink where white
   fails 3:1 at every stop), and EXACTLY ONE stroke — the casing, 0.075 × the
   token radius, ink on Chalk, white on the dark boards. No halo, no ring
   stacking: a selected / ball-holding / caught player changes his ONE
   casing, he never wears a second. The contact shadow is a filter, not a
   stroke. `data-slot` is stamped by js/install-design-svg.js buildTokens. */
/* the BASE every --lk-* property falls back to: the field's own tokens, so a
   page that paints a look without the module (none today) still has a board.
   js/designer-prefs.js paintLook() writes the real values inline on <html>,
   which outrank this rule. No hex: every value is an existing token. */
:root {
    --tok-qb: var(--tok-Q); --tok-qb-ink: var(--tok-ink);
    --lk-casing: var(--tok-ink); --lk-casing-w: 0.075; --lk-shadow: none;
    --lk-fill-M: var(--tok-disc); --lk-hex-M: var(--tok-disc); --lk-ink-M: var(--tok-ink);
    --lk-fill-Q: var(--tok-qb); --lk-hex-Q: var(--tok-qb); --lk-ink-Q: var(--tok-qb-ink);
    --lk-fill-C: var(--tok-C); --lk-hex-C: var(--tok-C); --lk-ink-C: var(--tok-ink);
    --lk-fill-X: var(--tok-X); --lk-hex-X: var(--tok-X); --lk-ink-X: var(--tok-ink);
    --lk-fill-Y: var(--tok-Y); --lk-hex-Y: var(--tok-Y); --lk-ink-Y: var(--tok-ink);
    --lk-fill-Z: var(--tok-Z); --lk-hex-Z: var(--tok-Z); --lk-ink-Z: var(--tok-ink);
    --lk-fill-H: var(--tok-H); --lk-hex-H: var(--tok-H); --lk-ink-H: var(--tok-ink);
    --lk-ball: var(--key); --lk-ball-ring: var(--tok-ink);
}
:root[data-look] .pd-field:not([data-unit="defense"]) .tok {
    fill: var(--lk-fill-M);
    stroke: var(--lk-casing);
    stroke-width: max(1px, calc(var(--pd-tok-r, 11px) * var(--lk-casing-w)));
    filter: var(--lk-shadow);
}
:root[data-look] .pd-field:not([data-unit="defense"]) .tok[data-slot="Q"] { fill: var(--lk-fill-Q); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok[data-slot="C"] { fill: var(--lk-fill-C); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok[data-slot="X"] { fill: var(--lk-fill-X); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok[data-slot="Y"] { fill: var(--lk-fill-Y); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok[data-slot="Z"] { fill: var(--lk-fill-Z); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok[data-slot="H"] { fill: var(--lk-fill-H); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok-label {
    fill: var(--lk-ink-M);
    stroke: none;
    filter: none;
}
:root[data-look] .pd-field:not([data-unit="defense"]) .tok-label[data-slot="Q"] { fill: var(--lk-ink-Q); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok-label[data-slot="C"] { fill: var(--lk-ink-C); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok-label[data-slot="X"] { fill: var(--lk-ink-X); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok-label[data-slot="Y"] { fill: var(--lk-ink-Y); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok-label[data-slot="Z"] { fill: var(--lk-ink-Z); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok-label[data-slot="H"] { fill: var(--lk-ink-H); }
/* the states change the ONE casing — colour and width — never add a stroke */
:root[data-look] .pd-field:not([data-unit="defense"]) .tok.is-sel { stroke: var(--select); stroke-width: max(2px, calc(var(--pd-tok-r, 11px) * var(--lk-casing-w) * 2.6)); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok.has-ball { stroke: var(--pd-ball); stroke-width: max(2px, calc(var(--pd-tok-r, 11px) * var(--lk-casing-w) * 2.6)); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok.caught { stroke: var(--volt); stroke-width: max(2px, calc(var(--pd-tok-r, 11px) * var(--lk-casing-w) * 2.6)); }
:root[data-look] .pd-field:not([data-unit="defense"]) .tok.ghost { filter: none; }

/* THE BALL OWNS A HUE NO TOKEN HAS. Chalk keeps the leather football (the
   pdLeather pattern below); the dark boards' resting footballs become the
   volt DOT — the same at-rest rule the canvas card follows. A ball in flight
   (the run's `.ball`) stays a football everywhere. */
:root[data-look="night"] .pd-field .chain .football,
:root[data-look="broadcast"] .pd-field .chain .football {
    fill: var(--lk-ball);
    stroke: var(--lk-ball-ring);
    stroke-width: 1;
    rx: 4.6px;
    ry: 4.6px;
}
/* THE BLOOM BELONGS TO THE FIELD, NOT THE CHROME (mock:240-245). White routes
   glowing on a WHITE board would be a smear, so only the charcoal field
   blooms. */
:root[data-field="broadcast"] .pd-field .route:not(.ballpath) { filter: drop-shadow(0 0 2.5px rgba(255,255,255,0.55)) drop-shadow(0 0 7px rgba(255,255,255,0.18)); }
:root[data-field="broadcast"] .pd-field .sel { filter: drop-shadow(0 0 7px var(--sel-glow)); }
:root[data-field="broadcast"] .pd-field .chain .football,
:root[data-field="broadcast"] .pd-field .route.ballpath { filter: drop-shadow(0 0 5px var(--key)); }

/* A ROUTE WEARS ITS RUNNER'S COLOUR (Carlos, Sep 25 2026: "the routes of the
   color tokens should match. So if token is purple, route is purple").
   js/play-renderer.js:1022-1030's rule for the library cards — in a colour
   display a route inherits its player's colour — now on the board too. The
   route carries its runner's position word in `data-pos` (the token's own
   label, js/install-design-svg.js buildRoutePath); these rules only re-point
   the two custom properties css/install-design.css's kind rule already paints
   with, so `.motion`, `.open` and `.ballpath` keep their own colours (that
   rule's :not() guards) and MONO is untouched. KIND still reads in the line
   (run dashed, play-action dotted, rules above).
   The caps are the pdEnd-<cap>-<pos> set js/install-design-svg.js mounts,
   painted with these same --tok-* properties, so the palette lives here and
   nowhere else. (Not context-stroke: WebKit paints it black.) */
:root { --classic-run: #f97316; --classic-pa: #3b82f6; }
:root[data-tokens="classic"][data-field] .pd-field .route[data-classic="run"] {
    --pd-kind: var(--classic-run);
    --cap-arrow: url(#pdEnd-arrow-RUN); --cap-chevron: url(#pdEnd-chevron-RUN); --cap-block: url(#pdEnd-block-RUN); --cap-dot: url(#pdEnd-dot-RUN); --cap-hitch: url(#pdEnd-hitch-RUN); --cap-curl: url(#pdEnd-curl-RUN);
}
:root[data-tokens="classic"][data-field] .pd-field .route[data-classic="play_action"] {
    --pd-kind: var(--classic-pa);
    --cap-arrow: url(#pdEnd-arrow-PA); --cap-chevron: url(#pdEnd-chevron-PA); --cap-block: url(#pdEnd-block-PA); --cap-dot: url(#pdEnd-dot-PA); --cap-hitch: url(#pdEnd-hitch-PA); --cap-curl: url(#pdEnd-curl-PA);
}
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="C"] { --pd-kind: var(--tok-C); }
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="X"] { --pd-kind: var(--tok-X); }
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="Y"] { --pd-kind: var(--tok-Y); }
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="Z"] { --pd-kind: var(--tok-Z); }
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="H"] { --pd-kind: var(--tok-H); }
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="Q"],
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="QB"] { --pd-kind: var(--tok-qb); }
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="C"] {
    --cap-arrow: url(#pdEnd-arrow-C); --cap-chevron: url(#pdEnd-chevron-C); --cap-block: url(#pdEnd-block-C); --cap-dot: url(#pdEnd-dot-C); --cap-hitch: url(#pdEnd-hitch-C); --cap-curl: url(#pdEnd-curl-C);
}
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="X"] {
    --cap-arrow: url(#pdEnd-arrow-X); --cap-chevron: url(#pdEnd-chevron-X); --cap-block: url(#pdEnd-block-X); --cap-dot: url(#pdEnd-dot-X); --cap-hitch: url(#pdEnd-hitch-X); --cap-curl: url(#pdEnd-curl-X);
}
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="Y"] {
    --cap-arrow: url(#pdEnd-arrow-Y); --cap-chevron: url(#pdEnd-chevron-Y); --cap-block: url(#pdEnd-block-Y); --cap-dot: url(#pdEnd-dot-Y); --cap-hitch: url(#pdEnd-hitch-Y); --cap-curl: url(#pdEnd-curl-Y);
}
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="Z"] {
    --cap-arrow: url(#pdEnd-arrow-Z); --cap-chevron: url(#pdEnd-chevron-Z); --cap-block: url(#pdEnd-block-Z); --cap-dot: url(#pdEnd-dot-Z); --cap-hitch: url(#pdEnd-hitch-Z); --cap-curl: url(#pdEnd-curl-Z);
}
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="H"] {
    --cap-arrow: url(#pdEnd-arrow-H); --cap-chevron: url(#pdEnd-chevron-H); --cap-block: url(#pdEnd-block-H); --cap-dot: url(#pdEnd-dot-H); --cap-hitch: url(#pdEnd-hitch-H); --cap-curl: url(#pdEnd-curl-H);
}
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="Q"],
:root[data-tokens="colour"][data-field] .pd-field .route[data-pos="QB"] {
    --cap-arrow: url(#pdEnd-arrow-Q); --cap-chevron: url(#pdEnd-chevron-Q); --cap-block: url(#pdEnd-block-Q); --cap-dot: url(#pdEnd-dot-Q); --cap-hitch: url(#pdEnd-hitch-Q); --cap-curl: url(#pdEnd-curl-Q);
}
/* ── DEFENCE IS ALWAYS RED (mock:365-373) ─────────────────────────────────
   One red for defenders on every field, in every mode. Red alone dies on the
   turf board — #d92d20 against #3e7842 is 1.09:1 — so every defender carries
   an OUTLINE that does the separating: white on the two dark boards, ink on
   the white one. The triangle becomes a filled mark rather than a bare stroke,
   because an outline needs something to outline.
   Declared AFTER the field blocks on purpose: both are (0,2,0). */
:root[data-field] { --defense: #d92d20; --def-ring: #ffffff; }
:root[data-field="white"] { --def-ring: #0d1220; }
:root[data-field] .pd-field .def { fill: var(--defense); stroke: var(--def-ring); stroke-width: 1.6; opacity: 1; }
:root[data-field] .pd-field .def.rush { stroke-dasharray: none; }   /* the rusher is told by his R, not by a dash the outline would break */
/* the defender the option READS keeps the key as his ring — he is the one mark
   on the board that is both a defender and the coach's answer */
:root[data-field] .pd-field .def.read { stroke: var(--key); stroke-width: 2.6; }

/* ── ZONES AND MAN LINKS (M3) ─────────────────────────────────────────────
   v1 gave every zone type its own hue — purple flat, yellow hook, teal match,
   blue deep — which on this board would be four more colours competing with
   the five receiver hues Colour mode owns and with the one red that means
   defence. The settled look's answer: a zone is the DEFENCE's colour, and what
   distinguishes one from another is its WORD, which is already drawn in it.
   Same grammar as kind-in-the-line: the mark carries the meaning, not a hue
   nobody can name under pressure.
   The oval is a wash the routes read through, with the field's own outline so
   it exists on every board (the same job --def-ring does for a defender). */
:root[data-field] .pd-field .zone-oval {
    fill: color-mix(in srgb, var(--defense) 18%, transparent);
    stroke: var(--defense);
    stroke-width: 1.6;
}
:root[data-field] .pd-field .zone-tether {
    stroke: var(--defense);
    stroke-width: 1.4;
    stroke-dasharray: 8 6;
}
/* ⛔ THE CASING IS WHAT MAKES RED LEGIBLE ON GREEN. #d92d20 on turf is 1.09:1 —
   the record's own number — so a red DISC is ringed and a red LINE is drawn
   over a wider line in the same ring colour. Measured by the defence cells in
   scripts/audit-designer-looks.mjs; without these three rules they read 1.09:1
   on turf and the marks were invisible on the field most coaches use. */
:root[data-field] .pd-field .zone-oval-casing {
    fill: none;
    stroke: var(--def-ring);
    stroke-width: 3.4;
}
:root[data-field] .pd-field .zone-tether-casing {
    stroke: var(--def-ring);
    stroke-width: 3;
    stroke-dasharray: 8 6;
}
:root[data-field] .pd-field .manlink-casing {
    stroke: var(--def-ring);
    stroke-width: 3.6;
    stroke-dasharray: 12 8;
    fill: none;
    pointer-events: none;
}
/* the word sits in its own oval: defence red would vanish into the wash, so it
   wears the board's own strong chalk — white on the two dark fields, ink on
   the white one — which is the same rule the yard numbers follow. */
/* the word in the oval: `--board-chalk-strong` measured 2.78:1 on the white
   board (the defence cells again), because chalk is a whisper by design. The
   LOS colour is the board's own strongest mark and clears on all three:
   0.92 white on turf, 0.78 white on charcoal, 0.72 ink on white. */
:root[data-field] .pd-field .zone-label {
    fill: var(--board-los);
    font-family: var(--font-ui);
    font-weight: 800;
    font-size: 11px;
    letter-spacing: 0.08em;
    text-anchor: middle;
    dominant-baseline: central;
    pointer-events: none;
}
:root[data-field] .pd-field .zone-hit { fill: transparent; stroke: none; pointer-events: all; cursor: move; }
:root[data-field] .pd-field .zone.selected .zone-oval { stroke: var(--key); stroke-width: 2.6; }

/* A MAN LINK IS A LEASH, AND IT IS NOT A ROUTE. Routes are solid/dashed/dotted
   by kind in the route colour; this is the defence's red, dashed longer than
   any route's dash (12/8, v1's own — playmaker.js:1974) so the two cannot be
   confused at a glance on a board that carries both. */
:root[data-field] .pd-field .manlink-line {
    stroke: var(--defense);
    stroke-width: 1.8;
    stroke-dasharray: 12 8;
    fill: none;
    pointer-events: none;
}
:root[data-field] .pd-field .manlink-end { fill: var(--defense); stroke: var(--def-ring); stroke-width: 1.2; }
:root[data-field] .pd-field .manlink-hit { fill: transparent; stroke: none; pointer-events: all; cursor: pointer; }
:root[data-field] .pd-field .manlink.selected .manlink-line { stroke: var(--key); }

/* ⛔ A DEFENCE PLAY'S TOKENS ARE DEFENDERS. On an offence board the discs are
   the five receivers and the QB; on a defence board every disc on it is a
   defender, and defence is red — the one colour law that holds on every field
   and in both token modes. The per-field ring does the separating, exactly as
   it does for the look's ghosted defenders. */
:root[data-field] .pd-field[data-unit="defense"] .tok {
    fill: var(--defense);
    stroke: var(--def-ring);
    stroke-width: 1.6;
}
:root[data-field] .pd-field[data-unit="defense"] .tok-label {
    fill: #ffffff;
    stroke: #0d1220;
    stroke-width: var(--tok-halo-w);
    stroke-linejoin: round;
    paint-order: stroke;
}
/* the white board's defence red is dark enough for a white letter to sit on it
   without a halo doing the work, and the ink halo there would ring every
   defender in a second colour */
:root[data-field="white"] .pd-field[data-unit="defense"] .tok-label { stroke-width: 0; }
/* ⛔ ONE STROKE PER TOKEN under a look (Oct 6 2026): the defender's outline is
   his one stroke; white on the defence red is 4.6:1 without a halo. */
:root[data-look] .pd-field[data-unit="defense"] .tok-label { stroke-width: 0; }
/* Colour mode is about the five RECEIVERS (the QB is deliberately not one of
   them); it has nothing to say about a board where every disc is a defender,
   so it does not get to repaint one. */
:root[data-tokens="colour"][data-field] .pd-field[data-unit="defense"] .tok { fill: var(--defense); }

/* the count is a NUMERAL, so it is the one thing on a token that may be
   Orbitron. The letter stays Rajdhani. (mock:352-354) */
:root[data-label="num"] .pd-field .tok-label { font-family: var(--font-data); }

/* ── THE --pd-* BRIDGE (departure 4) ──────────────────────────────────────
   css/install-design.css paints the board through its own indirection layer.
   Re-point that layer at the field's tokens and every rule it already wrote
   draws the settled board — including rules written after this file. */
:root[data-field] {
    --pd-chalk: var(--board-chalk);
    --pd-chalk-strong: var(--board-chalk-strong);
    --pd-los: var(--board-los);
    --pd-route: var(--route-pass);
    --pd-ball: var(--key);
    --pd-defense: var(--defense);
    --pd-select: var(--key);
    --pd-motion: var(--key);
    --pd-tree: var(--board-chalk-strong);
    --pd-paper: var(--tok-disc);
}
:root[data-field] .pd-field .sel-halo { fill: var(--sel-halo); }
:root[data-field="turf"], :root[data-field="broadcast"] { --sel-halo: var(--volt-glow); }
:root[data-field="white"] { --sel-halo: rgba(13,18,32,0.18); }

/* the head's Kind tabs say kind the same way the board does: a coloured dot
   says nothing when all three routes are one colour, so the swatch becomes the
   LINE it stands for (mock:206-211). */
:root[data-field] .seg-tab[data-kind] .pd-swatch {
    width: 26px; height: 0; border-radius: 0; box-shadow: none;
    border-top: 2.5px solid var(--route-pass);
}
:root[data-field] .seg-tab[data-kind="run"] .pd-swatch { border-top-width: 2.5px; border-top-style: dashed; }
:root[data-field] .seg-tab[data-kind="pa"] .pd-swatch { border-top-width: 3px; border-top-style: dotted; }

/* ══ CHROME-SCOPED SHELL ═════════════════════════════════════════════════ */

:root[data-theme="dark"][data-chrome="broadcast"] .seg,
:root[data-theme="whiteboard"] .seg { box-shadow: var(--seg-inset); }
:root[data-theme="dark"][data-chrome="broadcast"] .seg-tab.active,
:root[data-theme="whiteboard"] .seg-tab.active { box-shadow: var(--seg-raise); }

/* BROADCAST chrome: depth instead of colour — ambient behind the board, real
   glass in front of it (mock:396-408). */
:root[data-theme="dark"][data-chrome="broadcast"] body { background: var(--ambient-body), var(--canvas); }
:root[data-theme="dark"][data-chrome="broadcast"] .pd-stage { background: transparent; }
:root[data-theme="dark"][data-chrome="broadcast"] .pd-canvas { background: var(--surface); }
/* the volt wash only lifts a DARK board — behind a white one it is a halo
   around a page-white rectangle, which reads as a rendering fault */
:root[data-theme="dark"][data-chrome="broadcast"]:not([data-field="white"]) .pd-canvas { background: var(--ambient-board), var(--surface); }
:root[data-theme="dark"][data-chrome="broadcast"] .pd-canvas-foot,
:root[data-theme="dark"][data-chrome="broadcast"] .pd-inspector {
    background: var(--pd-glass);
    -webkit-backdrop-filter: blur(22px) saturate(165%);
    backdrop-filter: blur(22px) saturate(165%);
    box-shadow: inset 0 1px 0 var(--pd-glass-edge);
}
:root[data-theme="dark"][data-chrome="broadcast"] .pd-field .tok-delay { font-family: var(--font-data); }

/* ══ THE STANDALONE PAGE'S OWN SHELL ═════════════════════════════════════
   On coach/install.html `.pd-main` sits inside `.pr-main`, a grid cell of the
   rail shell. Standalone it needs a host of its own; `.pd-page` is it, and it
   is the ONLY layout this file adds. Same padding as the mock's `.pv-shell`. */
.pd-page {
    height: 100vh;
    height: 100dvh;
    padding: 12px 14px 14px;
    padding-top: calc(12px + env(safe-area-inset-top, 0px));
    padding-left: calc(14px + env(safe-area-inset-left, 0px));
    padding-right: calc(14px + env(safe-area-inset-right, 0px));
    box-sizing: border-box;
    overflow: hidden;
}
.pd-page > .pd-main { height: 100%; }

/* ── THE DIALS ROW (mock:308-322) ─────────────────────────────────────────
   Carlos, Sep 22: "11" and "2.5" mean nothing to a coach. Both dials are
   segmented controls in the same grammar as POS / #, and there is no numeric
   readout anywhere in the UI. The px each step maps to lives in a comment and
   in js/designer-prefs.js, never on screen.
   The block takes its own row rather than squeezing the bar: at iPad landscape
   the board head is ~800px and the three groups want more. Something had to
   give, and it is not going to be the 44px targets. */
.pd-dials-wrap { flex: 1 0 100%; border-top: 1px solid var(--border); padding-top: 6px; }
.pd-dials { display: flex; align-items: center; gap: 8px; flex-wrap: nowrap; }
.pd-dials > .pr-label { margin: 0; flex: none; }
.pd-dials .seg { padding: 3px; flex: none; }
.pd-dials .seg-tab { min-height: 38px; }             /* 38 + 3 + 3 = 44: the touch target survives */
.pd-dials .seg.pd-sml .seg-tab { min-width: 38px; padding: 0 10px; }
/* ⛔ `margin-left:auto` MOVED OFF THIS GROUP (M4-1). Two auto margins on one
   flex row SPLIT the free space between them, so leaving it here and adding
   one to #pdHistory would have floated Mirror/Erase into the middle of the
   row instead of keeping the existing groups packed left. The row has exactly
   ONE free-space owner now, and it is the history group at the end.
   ⛔ AND THE 24px IS A REAL SPACER, NOT `margin-left:auto` DOING ITS JOB.
   MEASURED (Sep 23 2026, 1024 and 834-portrait): this row's content is 751px
   wide against a board head of 646px / 456px — it was ALREADY wider than its
   column before M4-1 put a seventh group on it, so the free space an auto
   margin distributes is ZERO and Undo/Redo would sit packed against Erase at
   the row's own 8px gap. Carlos asked for daylight between the two groups;
   daylight that only exists on a wide desk is not daylight. 24 + the row's
   8px gap = 32px, at every width, free space or none. */
.pd-dial-icons { display: flex; align-items: center; justify-content: flex-end; gap: 2px; flex: none; margin-right: 24px; }

/* ── UNDO / REDO, THE ROW'S RIGHT-HAND GROUP (M4-1) ──────────────────────
   ICON-ONLY, by Carlos's ruling — the name lives in `aria-label`, not on the
   glyph. `.pd-icon` is 40x40 everywhere else on the page; on THIS row it is
   44, because the row it joined is the 44px-target row (its seg tabs reach 44
   as 38 + 3 + 3 of seg padding, which these buttons do not have) and a
   whole-board action a coach reaches for mid-draw does not get the smaller
   target. Disabled is `.pd-icon:disabled` — greyed, never hidden. */
.pd-dials .pd-history { display: flex; align-items: center; gap: 4px; margin-left: auto; flex: none; }
.pd-dials .pd-history .pd-icon { width: 44px; height: 44px; }
.pd-canvas-head { flex-wrap: wrap; row-gap: 6px; }

/* ── THE FIELD DIAL, ON THE BOARD (mock:379-386) ──────────────────────────
   It is about the board, so it lives on the board: bottom-left of the stage,
   clear of the tool rail (which ends at x=66). 44px targets at a smaller
   visual scale, same segmented grammar as every other switch. */
.pd-field-dial { position: absolute; left: 78px; bottom: 12px; z-index: 5; padding: 3px; }
.pd-field-dial .seg-tab { min-height: 44px; min-width: 44px; padding: 0; }
.pd-field-dial .pd-swatch { width: 13px; height: 13px; margin: 0; border-radius: 50%; box-shadow: 0 0 0 1px var(--border-strong); }
/* ── M4-3: PLAY IT, ON THE BOARD ──────────────────────────────────────────
   It plays the board, so it sits on it: bottom-right, opposite the field dial,
   same 12px from the edge. Same .pd-run skin (ink fill, volt bottom edge);
   mid-run it wears .live and reads Stop. The bar under it is the run's
   progress — a 4px volt fill along the stage's bottom edge, driven by
   scaleX from runFrame so it costs no layout per frame. */
.pd-run--board { position: absolute; right: 14px; bottom: 12px; z-index: 5; min-height: 44px; padding: 0 18px 0 14px; font-size: 13px; min-width: 116px; justify-content: center; }
/* The contract line shares the bottom edge with the dial (left) and PLAY IT
   (right), so it is bounded by both. On a stage too narrow to hold it on one
   line between them it steps out rather than running under the button —
   measured on the 834 portrait shot, where it wrapped to three lines into it. */
.pd-stage { container-type: inline-size; }
.pd-canvas .pd-contract { right: 146px; }
@container (max-width: 860px) { .pd-canvas .pd-contract { display: none; } }
/* THE SCRUBBER (Sep 24 2026) replaced M4-3's 4px bar: while a play runs the
   field dial steps aside (it is about the board, not the run) and the strip
   from the tool rail's column to PLAY IT becomes the run's own controls —
   ↺ back to the snap · the track (44px tall to a finger; the visible line is
   4px, the fill volt, a knob at the playhead) · the routes toggle. */
.pd-canvas.running .pd-field-dial { display: none; }
.pd-scrub { position: absolute; left: 78px; right: 142px; bottom: 12px; height: 44px; z-index: 6; display: flex; align-items: center; gap: 8px; }
.pd-scrub[hidden] { display: none; }
.pd-scrub .pd-icon { width: 44px; height: 44px; flex: none; background: var(--surface); border: 1px solid var(--border); }
.pd-scrub .pd-icon[aria-pressed="false"] { color: var(--ink-faint); }
.pd-scrub .pd-icon[aria-pressed="true"] { color: var(--ink); box-shadow: inset 0 -2px 0 var(--volt); }
.pd-scrub-track { position: relative; flex: 1; height: 44px; cursor: pointer; touch-action: none; outline: none; }
.pd-scrub-track::before { content: ''; position: absolute; left: 0; right: 0; top: 20px; height: 4px; border-radius: 2px; background: var(--border-strong); }
.pd-scrub-track i { position: absolute; left: 0; right: 0; top: 20px; height: 4px; border-radius: 2px; background: var(--volt); transform-origin: left center; transform: scaleX(0); }
.pd-scrub-track b { position: absolute; top: 15px; width: 14px; height: 14px; margin-left: -7px; left: 0; border-radius: 50%; background: var(--volt); box-shadow: 0 0 0 2px var(--surface); }
.pd-scrub-track:focus-visible::before { box-shadow: 0 0 0 2px var(--pd-select); }
/* paused on a frame: PLAY IT is a play button again (Resume) */
.pd-run.live.paused .ico-pause { display: none; }
.pd-run.live.paused .ico-run { display: inline-flex; }
/* ROUTES OFF: only the players and the ball move on the turf — every line,
   the static chain glyphs and the read badges step aside while it runs. */
.pd-field.running.hide-routes .pd-play .route,
.pd-field.running.hide-routes .pd-play .route-hit,
.pd-field.running.hide-routes .pd-play .reads,
.pd-field.running.hide-routes .pd-play .chain { display: none; }
/* the contract line steps aside rather than sitting under the dial */
.pd-canvas .pd-contract { left: 238px; }
/* ⛔ AND IT IS READ ON THE FIELD, NOT ON THE CHROME. `.pd-contract` is
   `--ink-muted` (css/install-design.css:325) because on the Install rail it
   sits on `--surface-elevated`, the stage's own plate. Giving the board a
   field (departure 2) put it on TURF, where muted chrome ink measured about
   3.4:1 — found on this page's first screenshot, not by the contrast gate,
   which does not carry a cell for it. It now wears the field's own route
   colour: 5.29:1 on turf, 17.28:1 on the charcoal board, 18.68:1 on white. */
/* ⛔ REVERSED Sep 24 2026 (Carlos: "there's text at the bottom but we can't
   see it because it's white on white"). Since v1519 the board reserves a
   STRIP UNDER THE FIELD for the dial and PLAY IT, and this line moved into it
   — it no longer sits on the field at all. The field's route colour (white on
   turf) on the light chrome's strip was white on near-white. It sits on the
   CHROME now, so it wears the chrome's ink, which every chrome theme defines
   to read on its own surface. */
:root[data-field] .pd-canvas .pd-contract { color: var(--ink-muted); }
/* ⛔ AND THE BOLD WORDS WITH IT — the instance was closed, the class was not
   (method.md #5). css/install-design.css:326 gives `.pd-contract b` the chrome's
   `--ink`, which is near-white; on the Install rail that sits on a dark plate and
   reads, but on THIS page the line sits on the BOARD, and on the WHITE board the
   three words a coach is actually meant to read — Drag, Hold, Pencil — were
   white on white. Found in this milestone's own screenshot, the same way the rule
   above was found. They wear the field's route colour too, at full weight. */
:root[data-field] .pd-canvas .pd-contract b { color: var(--ink); }

/* ── THE OVERFLOW MENU ────────────────────────────────────────────────────
   One button, one sheet. The classic designer's door lives HERE and not in the
   head: it is a way out for one release, not a control the board needs. */
.pd-more { position: relative; }
.pd-more-sheet {
    position: absolute; right: 0; top: 46px; z-index: 40;
    min-width: 248px; padding: 6px;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--r-sm); box-shadow: var(--pd-shadow-lift);
}
.pd-more-sheet button, .pd-more-sheet a {
    display: flex; align-items: center; gap: 10px; width: 100%;
    min-height: 44px; padding: 0 12px; border-radius: var(--r-xs);
    color: var(--ink); font-family: var(--font-ui); font-weight: 600; font-size: 14px;
    text-align: left; text-decoration: none;
}
.pd-more-sheet button:hover, .pd-more-sheet a:hover { background: var(--hover); }
.pd-more-sheet svg { width: 16px; height: 16px; flex: none; }
.pd-more-note { padding: 6px 12px 8px; color: var(--ink-faint); font-size: 12px; font-weight: 600; }

/* ⛔ THE DEFENCE EMPTY-STATE IS GONE (M3). The board used to say it could not
   author zones and man links and point at the classic designer. It can now, so
   the note is deleted with its markup, its handler and its rule — a guard left
   around a feature that has shipped is how a page goes on apologising for
   something it does. */

/* the standalone page gives the title the whole left column, so the 420px cap
   css/install-design.css puts on it (a rail page's budget) only clips it —
   "SPREAD RIGHT" came back as "SPREAD RIG" on the first iPad screenshot. */
.pd-page .pd-name input { max-width: 100%; }

/* ══ THE NAME READS AS A BOX YOU TYPE IN (Sep 26 2026) ════════════════════
   Carlos: "there's no way to name the play." Measured on origin/main: the
   input was focusable and nothing covered it, but it was 22px tall on a
   phone, its underline was transparent until hover (a phone has no hover),
   and a new play was pre-filled with the words "Untitled play" — so it read
   as the page's heading, not a field. Now: the LABEL is the tap target (44px,
   the whole row, pencil included), a dashed underline is always there,
   solid on hover, volt on focus, and the pencil says "edit" without a word.
   The prompt ("Name this play") is the placeholder, a quieter weight than a
   real name so an untitled play never looks named. */
.pd-page .pd-name-field { display: flex; align-items: center; gap: 8px; min-height: 44px; min-width: 0; cursor: text; }
.pd-page .pd-name-field input { flex: 1 1 auto; min-width: 0; min-height: 44px; border-bottom: 1px dashed var(--border); }
.pd-page .pd-name-field:hover input { border-bottom-style: solid; border-bottom-color: var(--ink-faint); }
.pd-page .pd-name-field input:focus { border-bottom-style: solid; border-bottom-color: var(--volt); }
.pd-page .pd-name-field input::placeholder { color: var(--ink-faint); font-weight: 700; opacity: 1; }
.pd-page .pd-name-field svg { flex: none; width: 18px; height: 18px; color: var(--ink-faint); }
.pd-page .pd-name-field:hover svg, .pd-page .pd-name-field:focus-within svg { color: var(--ink); }

/* ══ THE TOP STRIP IS AN APP BAR, NOT A CENTRED ISLAND (Sep 24 2026) ══════
   Carlos: "the top bar shouldn't be floating like that, with the save/export
   just kind of in the middle." Mechanism, measured: css/install-design.css:137
   lays the strip out as `1fr auto 1fr` — three columns for a strip that has
   TWO children (.pd-titleblock, .pd-topbar-actions) — so every action landed
   in the MIDDLE track and floated there, ending ~360px short of the page's
   right edge at 1366. That is the rail page's grid; the standalone page gets
   the two tracks it actually has: the title takes the room, the actions sit
   flush to the same right edge the inspector card ends on.
   The actions read as two groups, left to right: HOW THE BOARD LOOKS (mode,
   Sheet — both change the view, neither writes) | WHAT HAPPENS TO THE PLAY
   (the saved status, Export, Save, the overflow). A hairline separates them.
   Reordered with `order`, not by moving markup: ids and handlers untouched. */
.pd-page .pd-topbar { grid-template-columns: minmax(0, 1fr) auto; gap: 24px; }
.pd-page .pd-topbar-actions { gap: 8px; }
.pd-page .pd-topbar-actions > #pdMode { order: 1; }
.pd-page .pd-topbar-actions > #pdSheetBtn { order: 2; }
/* View in 3D changes the view (it writes nothing), so it sits in the first group,
   after Sheet — same order value, DOM order breaks the tie */
.pd-page .pd-topbar-actions > #pd3dBtn { order: 2; }
/* AR (js/play-ar.js) is the same kind of verb, right after 3D. It is ABSENT until
   the native plugin, a tracking device and the rollout all say yes — and
   `.pr-btn { display: inline-flex }` (css/practice.css) would otherwise beat
   the hidden attribute, so hidden is spelled here. */
.pd-page .pd-topbar-actions > #pdArBtn { order: 2; }
#pdArBtn[hidden] { display: none; }
#pd3dBtn[hidden] { display: none; }
/* ══ ONE CLOSE X (chunk A3, Oct 8 2026 — Carlos: the Look sheet's X was
   "small, thin"). EVERY modal, sheet and popover on this page that closes
   wears .pd-x and nothing else: a 44px target, a 22px lucide x at a 2.5
   stroke, the full ink colour, pinned top-right at ONE inset of its card.
   House styles were checked first: .gc-modal-close (css/design-system.css)
   is a muted text glyph with no size, MODAL_DESIGN_SYSTEM.md's .close-btn
   is 40px and muted — neither meets the brief, so it is defined once here.
   scripts/test-designer-close-x.js holds every close to the same computed
   box and stroke. ══ */
.pd-x {
    position: absolute; top: var(--gc-space-2, 8px); right: var(--gc-space-2, 8px); z-index: 2;
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; min-width: 44px; min-height: 44px; padding: 0; margin: 0;
    background: transparent; border: 1px solid transparent; border-radius: var(--r-xs, 6px);
    color: var(--ink); cursor: pointer;
    transition: background-color .15s ease, border-color .15s ease, transform .1s ease;
    -webkit-tap-highlight-color: transparent;
}
.pd-x svg, .pd-x i { width: 22px; height: 22px; stroke-width: 2.5; flex: none; }
.pd-x:hover { background: var(--hover); border-color: var(--border); }
.pd-x:active { background: var(--hover); border-color: var(--border-strong); transform: scale(.94); }
.pd-x:focus { outline: none; }
.pd-x:focus-visible { outline: 2px solid var(--volt); outline-offset: 2px; }
/* the head never runs under the X */
.ip-modal-card:has(> .ip-modal-head > .pd-x) > .ip-modal-head { padding-right: 52px; }
.pd-page .pd-topbar-actions > .pd-saved { order: 3; margin-left: 8px; padding-left: 16px; border-left: 1px solid var(--border); min-height: 28px; }
.pd-page .pd-topbar-actions > .pd-why { order: 4; }
.pd-page .pd-topbar-actions > #pdExportBtn { order: 5; }
.pd-page .pd-topbar-actions > #pdSaveBtn { order: 6; }
.pd-page .pd-topbar-actions > .pd-more { order: 7; }

/* ── PHONE (390) ──────────────────────────────────────────────────────────
   ⛔ WHAT THIS IS AND WHAT IT IS NOT. The brief asks for the head seg to
   become a SHEET on a phone rather than five tabs in a row. It does not, yet —
   that is milestone 5's whole-surface pass, and it is stated here rather than
   half-built. What M1 does is stop the page being BROKEN at 390: before these
   rules the topbar, the dial row and the inspector all ran off the right edge
   and the whole document scrolled sideways, with the board squeezed to a strip
   (see m1/08-phone-390.png in the first pass).

   The treatment is the one this surface already uses for a control row wider
   than its container: `.pd-foot-groups` scrolls horizontally inside its own
   bar (css/install-design.css:690) rather than wrapping or shrinking below
   44px. The head's dial row now does the same, the bench stacks board-then-
   inspector, and the page scrolls vertically. No target goes under 44px. */
@media (max-width: 720px) {
    .pd-page { height: auto; min-height: 100dvh; padding: 8px 10px 10px; overflow: visible; }
    body { overflow-y: auto; }
    /* ⛔ A ONE-COLUMN GRID IS max-content WIDE UNLESS YOU SAY OTHERWISE. This
       was the whole phone defect, measured rather than guessed: the document
       scrolled to 534px at a 390px viewport because `.pd-main`'s single
       implicit column sized to its widest child — the top strip's action row,
       which is a nowrap flex and wanted 524px. Every "the board is cut off"
       symptom came from that one number. minmax(0,1fr) lets the column be the
       container's width, and the row below is then free to wrap. */
    .pd-main { grid-template-rows: auto auto; grid-template-columns: minmax(0, 1fr); height: auto; }
    .pd-page > .pd-main { height: auto; }
    .pd-bench, .pd-canvas, .pd-inspector { min-width: 0; }

    /* the top strip: who and what on one line, the actions under it */
    .pd-topbar { display: flex; flex-wrap: wrap; row-gap: 8px; min-width: 0; }
    .pd-titleblock { flex: 1 1 100%; min-width: 0; }
    .pd-topbar-actions { flex-wrap: wrap; row-gap: 8px; }
    .pd-canvas-head { overflow-x: auto; scrollbar-width: none; }
    .pd-canvas-head::-webkit-scrollbar { display: none; }
    .pd-name input { font-size: 20px; }
    /* `.pd-history { order: 3 }` lived here while undo/redo were in the top
       strip. M4-1 moved them to the dials row, so the rule had no element to
       order — deleted rather than guarded (method.md #7). */
    .pd-topbar-actions { order: 3; flex: 1 1 auto; justify-content: flex-end; }

    /* the bench stacks: the board he draws on, then the inspector he reads */
    .pd-bench { grid-template-columns: 1fr; }
    .pd-canvas { min-height: 62vh; }
    .pd-inspector { max-height: none; }

    /* the dial row scrolls rather than wrapping or shrinking (see above).
       The 4px skirt / -4px margin pair is `.pd-foot-groups`' own trick: an
       overflow box forces overflow-y to auto too, which would clip the tap
       skirt each seg tab wears below it. */
    /* ⛔ Oct 7 2026: a scrolled row hid "LINE" past the right edge, which read
       as a clipped label. At phone width the row WRAPS instead — every label
       beside its seg, nothing off-screen (one row stays the iPad law). */
    .pd-dials { flex-wrap: wrap; row-gap: 8px; overflow-x: visible; padding: 0; margin: 0; }
    .pd-dial-icons { margin-left: 8px; }

    /* the field dial stays on the board — three 44px targets from x=78 fit a
       390px stage — but the contract line beside it does not, and it is about a
       PENCIL and a right-click, neither of which a phone has. A sentence with
       no truth on the device it is shown on is copy that lies (method.md #6). */
    .pd-field-dial { left: 78px; bottom: 10px; }
    .pd-canvas .pd-contract { display: none; }
}

/* ═══ THE UNIT DECIDES WHICH CONTROLS EXIST (M3c) ═══════════════════════════
   A defence play has no formation, no Vs look, no read progression and no
   route tree — so on a defence unit those controls are not disabled, greyed or
   left showing empty: they are not there. ONE rule over one class, marked in
   coach/play-designer.html, rather than a hide written at each of the nine
   sites (the "single display resolver" law: same display logic in three places
   is a bug). js/install-design.js sets the attribute, from the record's own
   canvas_data.playType and nothing else. */
body[data-unit="defense"] .pd-off-only { display: none; }

/* ═══ ONE TOAST ════════════════════════════════════════════════════════════
   The only thing on this page that speaks in a box. It exists for M3c ruling
   B: the unit pill starts a NEW play rather than rewriting the one he has, and
   the coach is owed the sentence that says his original is still there.
   Nothing is destroyed, so there is no confirm — the toast is the whole
   announcement, and it leaves on its own. */
.pd-toast {
    position: fixed;
    left: 50%;
    bottom: calc(18px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    z-index: 9000;                       /* QUICK_REF's own toast layer */
    max-width: min(520px, calc(100vw - 32px));
    padding: 11px 16px;
    border-radius: 12px;
    background: var(--surface-elevated);
    color: var(--ink);
    border: 1px solid var(--border-strong);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45);
    font-family: var(--font-ui);
    font-size: 14px;
    line-height: 1.35;
    text-align: center;
    opacity: 0;
    transition: opacity 180ms ease;
    pointer-events: none;
}
.pd-toast.on { opacity: 1; }

/* the create sheet's unit switch sits in the head, beside the count */
#pdSheetUnit { margin-left: auto; }

@media (prefers-reduced-motion: reduce) {
    .pd-toast { transition: none; }
}

/* ═══ BASIC IS WHAT EVERY COACH GETS (M4) ══════════════════════════════════
   Carlos, Sep 22: "1st/2nd grade coaches aren't going to care about concepts,
   reads and keys." So the Pro fields are not greyed, not collapsed behind a
   disclosure, and not left showing empty — on a Basic inspector they are not
   there. ONE rule over one class, exactly as `.pd-off-only` does it for the
   unit, because the same display logic written at four sites is the bug the
   single-resolver law exists to stop. js/install-design.js sets the attribute,
   from the coach's preference AND the SCViews capability, and nothing else. */
body[data-inspector="basic"] .pd-pro-only { display: none; }

/* the inspector header's own controls: the Basic|Pro switch and Analyze.
   They sit under the identity, on one row, and wrap before they clip. */
/* ⛔ THE HEAD IS A ROW, SO THE TOOLS TAKE THEIR OWN LINE. `.pd-insp-head` is
   `display:flex` with `justify-content:space-between` (css/install-design.css
   :354), so a third child sits BESIDE the identity and runs out of a fixed
   column — Analyze hung off the panel's right edge in M4's first two
   screenshots. The head wraps and the tools claim a full row of their own. */
.pd-insp-head { flex-wrap: wrap; }
.pd-insp-tools {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    flex: 1 0 100%;
    margin-top: 2px;
}
.pd-insp-tools .seg { flex: 0 0 auto; }
/* ⛔ IT WRAPS, IT DOES NOT CLIP. A first screenshot had Analyze pushed off the
   panel's right edge by `margin-left:auto` — the inspector is a fixed column,
   so a row that grows has to fold onto a second line rather than run out of
   the box (the .pd-main `minmax(0,1fr)` lesson, one panel down). */
.pd-insp-tools .pr-btn { flex: 0 0 auto; }
.pd-insp-head .pd-says { flex: 1 0 100%; margin-top: 6px; }

/* ⛔ A RUNNING ANALYZE IS A STATE, AND IT SAYS ONE SENTENCE. No spinner that
   outlives the request, no progress bar with nothing behind it. */
#pdAnalyzeBtn[data-busy="1"] { opacity: 0.6; pointer-events: none; }

/* ══ THE BAR UNDER THE BOARD SAYS WHAT EACH GROUP IS (Sep 24 2026) ════════
   It became the ONLY door to a route's kind · line · end · weight when the
   inspector's copies were deleted — and at every iPad width it was folding
   its labels away (css/install-design.css:746-749, the pdfoot container) and
   dropping the Kind words, leaving three swatches that in MONO are three
   identical white dots. Four unlabelled rows of glyphs is a puzzle, not a
   toolbar. So on this page: the group's name is a CAPTION ABOVE its seg (the
   10px label tier), which costs height, never width; and the Kind tabs keep a
   word at every width — the long "Play action" swaps to "PA" when the foot
   folds, the tab's aria-label keeps the full name. Scoped to .pd-page:
   coach/install.html's rail keeps its own fold. */
.pd-page .pd-canvas-foot .pd-foot-groups .pd-group { flex-direction: column; align-items: flex-start; gap: 4px; }
.pd-page .pd-canvas-foot .pd-foot-groups .pd-group > .pr-label { display: block; margin: 0 0 0 2px; font-size: 10px; line-height: 1; letter-spacing: 1.2px; }
.pd-page #pdFootKind .pd-tab-short { display: none; }
@container pdfoot (max-width: 1048px) {
    .pd-page #pdFootKind .pd-tab-word { display: inline; }
    .pd-page #pdFootKind .seg-tab[data-kind="pa"] .pd-tab-word { display: none; }
    /* Motion's in-tab word folds where Kind's do; the group's CAPTION above the
       key ("MOTION", the 10px label tier this page shows at every width) still
       names it — the bar must not grow a row (hero-layout's floors: a board no
       shorter than 501px at 1280x800, 601 at 1440x900) */
    .pd-page #pdFootMotion .pd-tab-word { display: none; }
    .pd-page #pdFootKind .pd-tab-short { display: inline; }
    /* folded, the WORD is the tab; the swatch goes (in Mono it is three
       identical white marks anyway) — it was the 30px that clipped Weight's L at 1194. */
    .pd-page #pdFootKind .pd-swatch { display: none; }
    .pd-page #pdFootKind .seg-tab { padding: 0 10px; }
}
/* ⛔ NOTHING IN THE FOOT IS BEHIND AN INVISIBLE SCROLL (Oct 6 2026). Below
   ~760px of foot (an iPad at 1024x768: foot 622px, groups 722px) the bar
   scrolled sideways with its scrollbar hidden, so WEIGHT sat 100px past the
   edge with nothing to say it was there — a control a coach cannot see is a
   control he does not have. Narrow, the groups WRAP to a second row instead:
   it costs height under the board, never a hidden control. */
@container pdfoot (max-width: 760px) {
    .pd-page .pd-canvas-foot .pd-foot-groups { flex-wrap: wrap; justify-content: flex-start; row-gap: 8px; overflow-x: visible; }
}
/* ⛔ THE UPRIGHT TABLET WRAPS TOO (VP1, Oct 11 2026). The 760px threshold
   above was measured on a LANDSCAPE foot, whose labels sit inline at 36px
   keys. Held upright the labels sit ABOVE 44px keys and the four groups need
   924px — so the iPad 10th (foot 766) and the iPad Pro 11 (foot 780) cleared
   760, never wrapped, and WEIGHT sat at x 874-948 of an 820 screen behind the
   hidden scroll. Upright there is height to spare under the board: wrap. */
@media (min-width: 721px) and (max-width: 1000px) and (orientation: portrait) {
    .pd-page .pd-canvas-foot .pd-foot-groups { flex-wrap: wrap; justify-content: flex-start; row-gap: 8px; overflow-x: visible; }
}
/* a route's two actions are verbs, one line each — "THROW HERE" was wrapping
   onto two lines inside a 97px button on the 1366 walk. */
.pd-page .pd-actions-row .pr-btn { white-space: nowrap; }
.pd-page .pd-actions-row { grid-template-columns: repeat(2, max-content); justify-content: start; }
.pd-page .pd-actions-row .pr-btn { padding: 0 14px; }

/* ══ iPAD PORTRAIT: THE BOARD TAKES THE WIDTH (Sep 24 2026) ═══════════════
   Measured at 834x1112 before this rule: the bench kept its landscape grid
   (1fr | 336px), so the BOARD — the thing a coach is here to draw on — got a
   458px column and painted a 364x275 field with ~190px of dead stage above and
   below it, while the dials row ran off the card and the title clipped to
   "SPREAD RIGHT FI". Portrait is tall, not wide: the board takes the full
   width (806px) and the inspector docks UNDER it as a scrolling panel (its
   body already scrolls, css/install-design.css:379). The top strip's two
   view/export buttons fold to their icons — each keeps an aria-label — so the
   play's name gets the room. Phones (<=720) keep their own stacked, scrolling
   page above; this band is the tablet held upright. */
@media (min-width: 721px) and (max-width: 1000px) and (orientation: portrait) {
    .pd-page .pd-bench { grid-template-columns: minmax(0, 1fr); grid-template-rows: minmax(0, 1fr) minmax(220px, 28%); }
    .pd-page .pd-inspector { min-height: 0; }
    .pd-page .pd-topbar { gap: 16px; }
    .pd-page .pd-name input { font-size: 22px; }
    .pd-page #pdSheetWord, .pd-page #pdExportBtn .pd-btn-word { display: none; }
    .pd-page #pdSheetBtn, .pd-page #pdExportBtn { width: 44px; padding: 0; justify-content: center; }
}

/* ══ THE BOARD'S OWN FOOT STRIP (Sep 24 2026) ═════════════════════════════
   The field dial (bottom-left) and PLAY IT (bottom-right) are absolutely
   placed 12px off the stage's bottom edge, and the stage padded only 14px —
   so both sat ON the field, over the deep backfield where a QB or a back
   actually stands (the dial covered the "10" on every walk shot). The stage
   now reserves their row: 12 + 44 + 12 = 68px under the field, and the field
   scales into what is left. Nothing a coach draws is ever under a control. */
.pd-page .pd-stage { padding-bottom: 68px; }

/* the scrubber on a phone: while it runs, PLAY IT folds to its icon (Stop /
   Resume — the button keeps its aria name) and the track takes the room; at
   390 the track measured 40px wide before this, too short to scrub. */
@media (max-width: 720px) {
    .pd-canvas.running .pd-run--board { min-width: 44px; width: 44px; padding: 0; }
    .pd-canvas.running .pd-run--board .pd-run-word { display: none; }
    .pd-scrub { right: 66px; }
    /* ⛔ AND THE TRACK GETS ITS OWN ROW (Sep 24 2026). The 3 sec | Real speed
       switch joined this bar and at 390 the track measured 0px (141-141) — the
       switch spilled past the bar onto PLAY IT and scrubbing died. On a phone
       the track rides a full-width row just above the buttons, reaching under
       PLAY IT's column; it overlays the field's bottom edge only while the play
       runs, when nothing on the board can be edited anyway. */
    .pd-scrub .pd-scrub-track { position: absolute; left: 0; right: -52px; bottom: calc(100% + 2px); flex: none; }
    .pd-scrub { justify-content: space-between; }
}

/* the board's own labels (PAST LOS, TRAFFIC, OUT OF BOUNDS) sit on busy ink —
   a halo in the FIELD's colour cuts them out of the lines they cross (the
   token letters' paint-order pattern above), one rule per field the dial has */
.pd-field .warn-tag, .pd-field .chain .rule-tag { stroke-width: 3px; stroke-linejoin: round; paint-order: stroke; }
:root[data-field="turf"] .pd-field .warn-tag, :root[data-field="turf"] .pd-field .chain .rule-tag { stroke: var(--fs-turf); }
:root[data-field="broadcast"] .pd-field .warn-tag, :root[data-field="broadcast"] .pd-field .chain .rule-tag { stroke: var(--fs-broadcast); }
:root[data-field="white"] .pd-field .warn-tag, :root[data-field="white"] .pd-field .chain .rule-tag { stroke: var(--fs-white); }

/* HOW FAR OFF THE BALL (Sep 24 2026): the depth slider under Vs look — a
   44px-tall track to a finger, the product accent on the thumb */
.pd-depth { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; }
.pd-depth[hidden] { display: none; }
.pd-depth-range { width: 100%; min-height: 44px; accent-color: var(--volt); margin: 0; }
/* ONE ROW (chunk A2, Oct 8 2026): label (one line, never wraps) · the slider
   filling what is left · the value in a fixed slot at the right end, so the
   row never jumps while he drags. A tick a yard under the track. */
.pd-depth-line { display: flex; align-items: center; gap: var(--gc-space-2, 8px); min-width: 0; }
.pd-depth-lab { flex: 0 0 auto; white-space: nowrap; margin: 0; }
.pd-depth-track { position: relative; flex: 1 1 auto; min-width: 72px; display: flex; align-items: center; }
.pd-depth-track::after {
    content: ""; position: absolute; left: 10px; right: 10px; bottom: 6px; height: 5px; pointer-events: none;
    background: repeating-linear-gradient(to right, var(--ink-muted) 0 1px, transparent 1px calc(100% / var(--pd-depth-steps, 19)));
    border-right: 1px solid var(--ink-muted); opacity: .55;
}
.pd-depth-val { flex: 0 0 auto; width: 5ch; text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; }

/* ══ THE LINE OF SCRIMMAGE STANDS OUT (Sep 24 2026) ══════════════════════
   Carlos: "I feel like the LOS should be more prominent? As far as color? In
   chalkboard mode it's easy to see." Mechanism: the LOS wore --board-los, the
   SAME white as every route on turf and charcoal — on the white board it is
   the only dark line, which is why it popped there. It now has its own hue,
   broadcast-TV line-of-scrimmage blue (--board-los-line: 3.4:1 on the
   lightest turf, 6.9:1 on charcoal, 5.4:1 on white), and more weight. The
   zone labels keep --board-los; the denied flash stays --live. */
:root[data-field] .pd-field .los:not(.denied) { stroke: var(--board-los-line); stroke-width: 3.2; }
/* Carlos, Sep 24: "I don't like the baby blue." Turf takes the SAME saturated
   broadcast blue as the charcoal board; a hairline dark edge carries it on the
   lighter centre of the turf, where a saturated blue alone sits near the green's
   own lightness. The white board needs no edge. */
:root[data-field="turf"] .pd-field .los:not(.denied),
:root[data-field="broadcast"] .pd-field .los:not(.denied) { filter: drop-shadow(0 0 1px rgba(0,0,0,0.7)); }
:root[data-field] .pd-field .los-label { fill: var(--board-los-line); }

/* ══ AN EMPTY SEAT IS A CONTROL (Sep 24 2026) ════════════════════════════
   The mock's dashed ghost tile, made real: one per empty seat in Players on
   the board; tap = back to his formation spot, drag = where he is dropped.
   44px touch target around the 40px disc (Coach Dave). */
.pd-tok.ghost.pd-seat { cursor: grab; position: relative; font: inherit; font-family: var(--font-ui); font-weight: 800; font-size: 12px; padding: 0; touch-action: none; color: var(--ink-muted); }
.pd-tok.ghost.pd-seat::before { content: ''; position: absolute; inset: -4px; }
.pd-tok.ghost.pd-seat:hover, .pd-tok.ghost.pd-seat:focus-visible { border-color: var(--ink); color: var(--ink); outline: none; }
.pd-seat-plus { font-size: 11px; margin-right: 1px; color: var(--ink); }   /* never volt TEXT: the chrome has a light theme */
.pd-seat-drag { position: fixed; left: 0; top: 0; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-family: var(--font-ui); font-weight: 800; font-size: 13px; background: var(--surface); color: var(--ink); border: 2px dashed var(--ink); pointer-events: none; z-index: 2000; }

/* ══ HOW THE PLAY RUNS (Sep 24 2026) ═════════════════════════════════════
   A joined segmented control in the scrub bar (the control grammar: a
   view switch is a segment, never a row of pills). 44px targets. */
.pd-scrub .pd-timing { flex: none; padding: 0; background: var(--surface); }
.pd-scrub .pd-timing .seg-tab { min-height: 42px; padding: 0 12px; font-size: 11px; white-space: nowrap; }
.pd-timing .pd-w-short { display: none; }
@media (max-width: 720px) {
    .pd-timing .pd-w-long { display: none; }
    .pd-timing .pd-w-short { display: inline; }
    .pd-scrub .pd-timing .seg-tab { min-width: 44px; padding: 0 8px; }
}

/* ══ ON TURF THE BALL IS A FOOTBALL (Sep 24 2026) ═════════════════════════
   Carlos: "when the background is in the green canvas mode, that means we're
   in realistic football mode. That football needs to be brownish… the Volt
   green just clashes with everything with that turf color." The turf board
   paints the ball as leather with a white edge. Brown alone sits near the
   turf's own lightness, so the WHITE edge does the separating (the same job the
   white ring does for every disc on this board). Only the football moves off
   --pd-ball: the throw's flight line, the handoff rings and the read key keep
   the board's key colour. Charcoal and white boards are unchanged. */
/* ⛔ AND ON EVERY BOARD (Carlos, Oct 1 2026: "why is the football solid
   black?"). The white board painted the football in --pd-ball = --key, which
   is INK there (#0d1220, line ~166) — a black blob. The football is leather on
   every field now, the same ball the huddle flies; its EDGE is what changes:
   white on turf and charcoal, ink on the white board. */
:root[data-field] { --pd-football: url(#pdLeather); --pd-football-edge: #ffffff; }
:root[data-field="white"] { --pd-football-edge: #0d1220; }
:root[data-field] .pd-field .chain .football,
:root[data-field] .pd-field .ball { fill: var(--pd-football); stroke: var(--pd-football-edge); stroke-width: 0.9; }
/* AND NO VOLT ON TURF AT ALL for the play's own marks (Carlos, Sep 24: "On
   turf mode, I don't like the volt green clashes… on the motion, get rid of
   it. The ball handoff needs to be a different color"). The motion leg is
   WHITE — its squiggle already says "motion", the way a broadcast
   telestrator draws it — and the ball's flight, its arrow and the handoff
   rings are broadcast GOLD (the first-down-line family; 3.66:1 on the
   lightest turf band). The selection/read key is not a play mark and keeps
   the board's key. */
:root[data-field="turf"] { --pd-motion: #ffffff; --pd-ball: #ffd23f; }

/* ══ THE PLAYERS PANEL SPEAKS COLOUR MODE TOO (Sep 24 2026) ══════════════
   Carlos: "also should be white text here not black". The chips under
   Players on the board wear the colour-mode hues, but their letter stayed the
   mono ink (--tok-ink, near-black) — dark on the deep blue/teal/green. Same
   rule as the board: a white letter on a colour disc (the hues were deepened
   so white reads alone, ≥ 4.5:1). Mono chips keep the ink letter. */
:root[data-tokens="colour"] .pd-tok[data-pos] { color: #ffffff; }

/* ══ THE FIELD RUNS EDGE TO EDGE (Sep 24 2026) ═══════════════════════════
   Carlos: "I do hate that the sidelines are shown… I don't want to crop too
   much". The out-of-bounds strips were tinted, so a 30-yd field read as a
   narrow court inside a wider board. The tint goes; the two sideline stripes
   stay (the boundary is still there to design to — the spacing warnings and
   OUT OF BOUNDS tags still read it) and nothing is cropped or rescaled. */
/* ⛔ OUT OF BOUNDS READS AS NOT-THE-FIELD (Oct 10 2026, lane D1 — Carlos on
   the white board, a 5v5: the 5 yd past each sideline looked like more
   field). Every format keeps ONE board — the same aspect (deep 480/324,
   deeper 480/372, below) and the same 24 px a yard — so a 30-yd 5v5 field
   shows 5 yd past each sideline, and that strip is painted as the
   sideline-to-bench area in the field's own tone (--board-oob), behind the
   solid sideline css/install-design.css already draws (--pd-los at 0.8 —
   4.6:1 on white, held by scripts/test-designer-field-fit.js T2). */
:root[data-field] .pd-field .sidelines .oob { fill: var(--board-oob); fill-opacity: 1; }
:root[data-field="turf"] { --board-oob: rgba(0,0,0,0.34); }
:root[data-field="broadcast"] { --board-oob: rgba(0,0,0,0.55); }
:root[data-field="white"] { --board-oob: rgba(13,18,32,0.09); }


/* ══ THE DEEP WINDOW (Sep 24 2026) ═══════════════════════════════════════
   17 yd downfield to 10 yd back (js/install-design-svg.js mount, DEEP_TOP):
   the board is shaped to its view, so a wide window shows no blank turf; a
   narrower one shows the field continuing (the chalk runs past the view). */
.pd-stage svg.pd-field[data-window="deep"] { aspect-ratio: 480 / 324; }
/* the DEEPER window (chunk A2): 21 yd downfield while a defensive look is on */
.pd-stage svg.pd-field[data-window="deeper"] { aspect-ratio: 480 / 372; }
/* THE ONE BOARD (Oct 10 2026, lane D1 — Carlos, iterate-4: "I think that's it
   right there"): js/install-design-svg.js SC_BOARD_GEOMETRY. 3:2 for every
   format, the field running sideline to sideline (no strip), 26 yd deep. */
.pd-stage svg.pd-field[data-window="board"] { aspect-ratio: 3 / 2; }   /* SC_BOARD_GEOMETRY.aspect — 8 behind + 18 ahead, the field sideline to sideline */

/* ══ WHO HAS THE BALL PULSES (Sep 24 2026) ═══════════════════════════════
   With the Ball tool in hand, the man holding it breathes in the ball's own
   colour (--pd-ball: gold on turf, the key elsewhere): "click where it goes
   next". Motion-sensitive coaches get a steady ring instead. */
@keyframes pdBallPulse { 0%, 100% { filter: drop-shadow(0 0 0 var(--pd-ball)); } 50% { filter: drop-shadow(0 0 6px var(--pd-ball)) drop-shadow(0 0 2px var(--pd-ball)); } }
.pd-field .tok.has-ball { animation: pdBallPulse 1.1s ease-in-out infinite; stroke: var(--pd-ball); stroke-width: 2.5; }
@media (prefers-reduced-motion: reduce) { .pd-field .tok.has-ball { animation: none; filter: drop-shadow(0 0 4px var(--pd-ball)); } }

/* ══ THE BALL'S FLIGHT: DOTTED, FADING IN (Sep 24 2026) ══════════════════
   Carlos picked it from four on the three fields. --pd-flight is the ball's
   ONE colour family on every board — gold on turf and charcoal, true ORANGE
   on white (bright gold vanishes there) — and it never shares a hue with the
   routes (white/ink), the LOS (blue), the defence or a rule flag (red), or the
   selection (volt). `.route.ballpath` keeps the chain's geometry and its
   arrowhead and paints no line of its own; the legs paint over it
   (js/install-design-svg.js buildBall).
   Oct 1 2026 (Carlos, "Ace Mesh Wheel"): #b45309 reads BROWN on white, not
   orange. #ea580c measures 3.56:1 on white (WCAG sRGB relative luminance) —
   #db7706 was tried first and only measures 2.80:1, so it is NOT the token.
   Same value as js/play-renderer.js ballFlightColor() — keep them in sync. */
:root[data-field="turf"]      { --pd-flight: #ffd23f; }
:root[data-field="broadcast"] { --pd-flight: #ffd23f; }
:root[data-field="white"]     { --pd-flight: #ea580c; }
:root[data-field] .pd-field .route.ballpath { stroke: transparent; filter: none; }
:root[data-field] .pd-field .ball-leg { fill: none; pointer-events: none; }
:root[data-field] .pd-field .ball-leg[data-leg="pass"],
:root[data-field] .pd-field .ball-leg[data-leg="pitch"] { stroke-width: 2.6; stroke-dasharray: 0.1 5.5; stroke-linecap: round; }
:root[data-field] .pd-field .ball-leg[data-leg="handoff"] { stroke: var(--pd-flight); stroke-width: 1.3; stroke-opacity: 0.7; }
:root[data-field] .pd-field .ballflight .fade-from { stop-color: var(--pd-flight); stop-opacity: 0.22; }
:root[data-field] .pd-field .ballflight .fade-to { stop-color: var(--pd-flight); stop-opacity: 1; }
/* the arrowhead at the catch wears the flight's colour */
:root[data-field] #pdArrowBall path { fill: var(--pd-flight); }

/* THE FOOTBALL BECKONS (Sep 24 2026 — Carlos: "the center football needs to
   glow as if to say, where do you want me"). With the Ball tool in hand, the
   ball in the holder's hands swells and glows in the ball's own colour. */
@keyframes pdBallBeckon { 0%, 100% { filter: drop-shadow(0 0 1px var(--pd-flight, var(--pd-ball))); stroke-width: 0.9; } 50% { filter: drop-shadow(0 0 6px var(--pd-flight, var(--pd-ball))) drop-shadow(0 0 3px var(--pd-flight, var(--pd-ball))); stroke-width: 2.2; } }
/* no scale: the ball carries its own rotate() (it points where it goes next), and a CSS
   transform would replace it — the beckon is a glow and a rim, never a move */
.pd-field .football.beckon { stroke: var(--pd-flight, var(--pd-ball)); animation: pdBallBeckon 1s ease-in-out infinite; }
@media (prefers-reduced-motion: reduce) { .pd-field .football.beckon { animation: none; filter: drop-shadow(0 0 4px var(--pd-flight, var(--pd-ball))); } }
/* the SNAP is the ball's first flight — the same dotted gold as every pass
   and pitch (Sep 24 2026), so the ball visibly leaves the center's hands */
:root[data-field] .pd-field .chain .snapline { stroke: var(--pd-flight, var(--pd-ball)); stroke-width: 2.2; stroke-dasharray: 0.1 4.5; stroke-linecap: round; opacity: 0.95; }
/* THE POSSESSION COLLAR (Sep 25 2026 — live states only: the Ball tool in
   hand, and the play running). The ball's own gold, just outside the disc. */
.pd-field .ball-collar { fill: none; stroke: var(--pd-flight, var(--pd-ball)); stroke-width: 2.6; stroke-linecap: round; pointer-events: none; }

/* ══ THE FORMATION EDITOR SAYS WHAT TO DO (Oct 7 2026) ════════════════════
   Carlos: "Drag the players" was a 12px sub-line — too small to be the
   instruction. It is the editor's one instruction now, in the display face,
   until his first drag (js/designer-new-play.js markFirstDrag). */
.fr-fe-hint { display: flex; flex-direction: column; gap: 2px; margin: 0 0 var(--gc-space-2, 8px); }
.fr-fe-hint[hidden] { display: none; }
.fr-fe-hint-t { font-family: var(--gc-font-display); font-weight: 800; font-size: 20px; line-height: 1.1; letter-spacing: 0.3px; text-transform: uppercase; color: var(--ink); }
.fr-fe-hint-s { font-family: var(--font-ui); font-weight: 600; font-size: 14px; line-height: 1.3; color: var(--ink-muted); }
@media (max-width: 480px) { .fr-fe-hint-t { font-size: 17px; } }
/* Token size and QB share ONE labelled row (the foot bar's grammar: an
   11px label beside its seg) — no row spent on three bare letters */
.fr-fe-dials { display: flex; flex-wrap: wrap; align-items: center; gap: var(--gc-space-2, 8px) var(--gc-space-4, 16px); margin: var(--gc-space-2, 8px) 0; }
.fr-fe-dial { display: inline-flex; align-items: center; gap: 8px; }
.fr-fe-dial .pr-label { font-size: 11px; letter-spacing: 1.2px; text-transform: uppercase; color: var(--ink-muted); white-space: nowrap; }
.fr-fe-dial .seg .seg-tab { min-height: 44px; min-width: 44px; }
/* the locked center: no grab cursor, and a small padlock on his shoulder */
.fr-fe .fr-fe-tok.is-locked { cursor: not-allowed; }
.fr-fe-lock circle { fill: var(--surface); stroke: var(--ink-muted); stroke-width: 2; }
.fr-fe-lock rect { fill: var(--ink-muted); stroke: none; }
.fr-fe-lock .fr-fe-lock-shackle { fill: none; stroke: var(--ink-muted); stroke-width: 3; stroke-linecap: round; }

/* ⛔ THE KIND TRAY READS ON CHALK (Oct 7 2026, measured: Classic's run
   orange is 2.39:1 on Chalk's light tray). The TRAY GLYPH takes a darker
   variant of the same hue there — 75% the run colour, 25% ink, ≈3.8:1. The
   board's route keeps the Oct 1 value exactly (scripts/test-designer-kind-colours.js).
   Outranks the base Classic glyph rule by the theme attribute, not by order. */
:root[data-theme="whiteboard"][data-tokens="classic"] #pdFootKind .seg-tab[data-kind="run"] .pd-kind-glyph { --kg: color-mix(in srgb, var(--classic-run) 75%, var(--ink)); }

/* the LINE group's two segs share ONE row: shape (Straight | Curve) beside
   style (solid · dashed · motion) — Oct 7 2026, Curve left the rail */
.pd-line-row { display: flex; align-items: center; gap: 6px; flex-wrap: nowrap; }

/* THE NAME BOX FITS ITS NAME (Oct 7 2026): sized by js/install-design.js
   sizeTitle, so the pencil sits right after the last letter; a name longer
   than the room ends in an ellipsis instead of sliding behind the pencil. */
.pd-page .pd-name-field input { flex: 0 1 auto; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; }
/* D-1 (Oct 7 2026): the name's column TAKES the title block's free room — it
   was sized by its own content, so sizeTitle's "room" was the box's current
   width and a long name capped itself there (~145px at 1024 with 130px free) */
.pd-page .pd-titleblock > .pd-name { flex: 1 1 auto; }

/* his formation's delete door (edge f): over the tile's corner, 44px */
.fr-fm-mine-wrap { position: relative; display: flex; min-width: 0; }
.fr-fm-mine-wrap > .fr-fm-tile { flex: 1 1 auto; min-width: 0; }
.fr-fm-del { position: absolute; top: 0; right: 0; width: 44px; height: 44px; display: inline-flex; align-items: center; justify-content: center; background: transparent; border: 0; color: var(--ink-muted); cursor: pointer; }
.fr-fm-del svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.fr-fm-del:hover, .fr-fm-del:focus-visible { color: var(--live); }

/* ══ THE BOARD IS THE HERO (Carlos, Oct 7 2026, chunk E) ══════════════════
   15 · THE RAIL IS AN INSTRUMENT. It was the page's own --surface with a
   --border hairline — on Chalk 1.00:1 against the stage AND the chrome, i.e.
   a strip of the background (measured). It is now its own step: the surface
   tinted 7% toward ink (derived from the look's own tokens, no new colour),
   a --border-strong hairline and the shared card shadow. The keys read in
   --ink; the ACTIVE key keeps its ink fill and moves its edge to the BOTTOM
   (the Volt control grammar, .claude/rules/platform-css.md). Keys stay 44px. */
/* ONE INSTRUMENT RECIPE (Oct 7 2026, chunk F 17a): the rail and the right
   panel are the same kind of object, so they read the SAME three values —
   never a second set of numbers. The surface is a step off the STAGE's own
   token (.pd-stage paints --surface-elevated; a tint of --surface only
   stepped 1.05:1 on Night Turf, measured). The volt bottom edge is the one
   the active rail key, the segmented controls and the panel's plate share. */
.pd-page {
    --pd-instrument-bg: color-mix(in srgb, var(--ink) 9%, var(--surface-elevated));
    --pd-instrument-border: var(--border-strong);
    --pd-instrument-shadow: var(--gc-shadow-md);
    --pd-volt-edge: inset 0 -3px 0 var(--volt);
    /* F-2 (Oct 7 2026): a hairline ON the instrument is derived from the
       instrument's own surface — --ink mixed in, so it is DARKER on Chalk and
       Night Turf and LIGHTER on Broadcast (whose --ink is light), ≥ 1.4:1 in
       all three. The page's --border could not be reused: Night Turf's is the
       opaque #e2e6ec (css/practice.css:25), made for a WHITE panel, so on the
       instrument step it painted lighter than the surface (1.06:1). */
    --pd-instrument-rule: color-mix(in srgb, var(--ink) 22%, var(--pd-instrument-bg));
}
.pd-page .pd-tools,
.pd-page .pd-inspector {
    background: var(--pd-instrument-bg);
    border-color: var(--pd-instrument-border);
    box-shadow: var(--pd-instrument-shadow);
}
/* every rule INSIDE an instrument — section borders (.pd-sec + .pd-sec), the
   head's bottom rule, the keycap row (.pd-keys), the foot, the rail's divider
   — reads --border; inside the instruments --border IS the instrument rule.
   One property, every rule routed through it (the outer frame keeps
   --pd-instrument-border, set above with border-color, unaffected: a custom
   property's var() resolves where it is declared, on .pd-page). */
.pd-page .pd-tools > *,
.pd-page .pd-inspector > * {
    --border: var(--pd-instrument-rule);
    /* the keycaps' outline (.pd-keys kbd, css/install-design.css:500) reads
       --border-strong — Night Turf's #c9d0da sat 1.17:1 on the instrument */
    --border-strong: var(--pd-instrument-rule);
}
.pd-page .pd-tools .pr-tool-divider { background: var(--pd-instrument-rule); }
.pd-page .pd-tool { color: var(--ink); }
/* Broadcast's glass panel (:root[data-theme="dark"][data-chrome="broadcast"]
   .pd-inspector, above) outranked the recipe — it painted 1.06:1 off the
   stage while the rail read 1.16. Same recipe, same weight as the glass rule,
   later in the file; the glass blur stays. */
:root[data-theme="dark"][data-chrome="broadcast"] .pd-page .pd-inspector {
    background: var(--pd-instrument-bg);
    box-shadow: var(--pd-instrument-shadow);
}
/* the active key keeps its light ink on the ink fill (it must outrank the
   line above, which is later than css/install-design.css's .pd-tool.on) */
.pd-page .pd-tool.on { color: var(--pd-on-ink); box-shadow: var(--pd-volt-edge); }
.pd-page .pd-tool:disabled { color: var(--ink-muted); }
/* 16 · THE RIGHT PANEL GIVES ITS WIDTH TO THE BOARD wherever the two sit
   side by side (not the phone stack, not the upright tablet, where the panel
   docks UNDER the board): 336px → 272px. What it holds wraps rather than
   clips — a seg row of four or five tabs breaks onto a second line, the
   action rows stack two-up. */
/* ══ CHUNK H — THE PLAY'S ACTIONS ON ITS PLATE (Carlos, Oct 7 2026) ═══════
   Beside the board, coach/play-designer.html placePlayActions moves the
   EXISTING Saved chip, Why, Export, Save and ⋯ onto the plate
   (html.pd-actions-on-plate). Row 2: name · Saved chip. Row 3: "deuce · 6v6"
   · Export · Save · ⋯. SAVE is the one filled key: the look's own volt with
   its own text-on-volt (--on-volt: white on Chalk's blue, ink on lime) —
   --gc-text-on-accent is the green accent's dark text and fails on blue.
   Export and ⋯ are quiet 36px keys in the plate's ink with a hairline that
   is the instrument rule's twin, mixed from the PLATE's own surface.
   Docked (portrait, phone) nothing here applies: they stay in the header. */
.pd-page .pd-insp-head > .pd-player { --pd-plate-bg: var(--surface-elevated); }
:root[data-theme="whiteboard"] .pd-page .pd-insp-head > .pd-player,
:root[data-theme="light"] .pd-page .pd-insp-head > .pd-player { --pd-plate-bg: var(--ink); }
.pd-page .pd-insp-head > .pd-player {
    --pd-plate-quiet: color-mix(in srgb, var(--pd-plate-ink) 88%, var(--pd-plate-bg));
    --pd-plate-rule: color-mix(in srgb, var(--pd-plate-ink) 34%, var(--pd-plate-bg));
    /* the chip's error red, lifted toward the plate's ink so "Not saved" /
       "Updated on another device" read on the ink plate (syncSaveChip paints
       var(--live) inline; inside the plate --live IS this) */
    --pd-live-on-plate: color-mix(in srgb, var(--live) 62%, var(--pd-plate-ink));
}
.pd-plate-state { --live: var(--pd-live-on-plate); }
.pd-page .pd-insp-head > .pd-player > div { flex: 1 1 auto; }
.pd-plate-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.pd-plate-row > .pd-insp-title,
.pd-plate-row > .pd-insp-sub { flex: 1 1 auto; min-width: 0; }
.pd-plate-state:empty, .pd-plate-actions:empty { display: none; }
.pd-plate-state { flex: none; display: inline-flex; align-items: center; gap: 6px; }
.pd-plate-actions { flex: none; display: inline-flex; align-items: center; gap: 6px; margin-top: 6px; }
/* the chip in the plate's quiet text (the error state keeps its live red,
   set inline by syncSaveChip) */
.pd-plate-state .pd-saved { color: var(--pd-plate-quiet); font-size: 11px; }
.pd-plate-state .pd-why { min-height: 28px; background: transparent; color: var(--pd-plate-ink); border-color: var(--pd-plate-rule); }
.pd-plate-actions .pr-btn,
.pd-plate-actions .pd-icon { min-height: 36px; height: 36px; border-radius: var(--r-xs); font-size: 12px; }
.pd-plate-actions #pdExportBtn,
.pd-plate-actions #pdMoreBtn {
    width: 36px; padding: 0; justify-content: center;
    background: transparent; color: var(--pd-plate-ink); border: 1px solid var(--pd-plate-rule); box-shadow: none;
}
.pd-plate-actions #pdExportBtn .pd-btn-word { display: none; }
.pd-plate-actions #pdSaveBtn {
    padding: 0 12px; background: var(--volt); color: var(--on-volt);
    /* Chalk's volt is blue: 2.79:1 against the ink plate, so the key is
       delineated by a ring in the plate's own ink (lime on Night/Broadcast
       needs none, but one rule for every look) */
    border: 1px solid var(--pd-plate-quiet); box-shadow: none;
}
.pd-plate-actions #pdExportBtn svg, .pd-plate-actions #pdMoreBtn svg, .pd-plate-actions #pdSaveBtn svg { width: 16px; height: 16px; }
/* the overflow sheet opens DOWN from the plate, inside the panel's width
   (the panel clips at its edges; 232px fits the 272px panel) */
.pd-plate-actions .pd-more-sheet { top: 42px; right: 0; min-width: 232px; }

/* E-1 (Oct 7 2026): a count never breaks mid-phrase. In the 272px panel a
   section head ("PLAYERS ON THE BOARD · 6 OF 6 · CHANGE ALIAS") broke inside
   "6 OF 6" and "CHANGE ALIAS". The count and the action are each one unit
   (nowrap); the HEAD wraps between units — the title may break between its
   own words, the action drops to its own line. */
.pd-page .pd-sec-head { flex-wrap: wrap; column-gap: 8px; row-gap: 2px; }
.pd-page .pd-sec-head > .pd-sec-t { flex: 1 1 auto; min-width: 0; }
.pd-page .pd-sec-head > .pr-label,
.pd-page .pd-sec-head > .pd-quiet { white-space: nowrap; flex: none; }
/* ══ THE PANEL'S PLATE (Carlos, Oct 7 2026, chunk F 17b–e) ═══════════════
   "that whole box thing there on the right: how do we make that its own
   thing?" The identity at the top of the panel — eyebrow, name, sub — sits on
   a plate flush with the panel's top edge and corners (the panel's own
   radius clips it), carrying the shared volt bottom edge. Chalk + Night Turf: an INK
   plate with --pd-on-ink text (the active rail key's pair; NOT
   --gc-text-on-accent, which is the DARK text for the green accent and would
   vanish on ink). Broadcast: --surface-elevated with --ink.
   Analyze and the Basic|Pro switch stay below it, on the body. The name still
   ellipsizes inside it (D-2: one name, every path). Every state's head —
   play, player, route, ball — wears it, docked or beside the board. */
.pd-page .pd-insp-head { padding-top: 0; }
.pd-page .pd-insp-head > .pd-player {
    flex: 0 0 calc(100% + 28px);
    max-width: calc(100% + 28px);
    margin: 0 -14px 6px;
    padding: 12px 14px 13px;
    background: var(--surface-elevated);
    box-shadow: var(--pd-volt-edge);
    --pd-plate-ink: var(--ink);
}
/* F-1 (Oct 7 2026): Chalk AND Night Turf (data-theme "whiteboard" / "light",
   js/designer-prefs.js THEME_MODE) take the INK plate — their chrome is light
   and their segmented controls are already ink fills with the volt edge;
   only Broadcast keeps --surface-elevated */
:root[data-theme="whiteboard"] .pd-page .pd-insp-head > .pd-player,
:root[data-theme="light"] .pd-page .pd-insp-head > .pd-player { background: var(--ink); --pd-plate-ink: var(--pd-on-ink); }
.pd-page .pd-insp-head > .pd-player #pdInspEyebrow,
.pd-page .pd-insp-head > .pd-player .pd-insp-title,
.pd-page .pd-insp-head > .pd-player .pd-insp-sub { color: var(--pd-plate-ink); }
/* the panel's PLAY title ends in an ellipsis inside the panel: its row
   (.pd-player) was content-sized, so a long name ran past the edge instead */
.pd-page .pd-insp-head > .pd-player { min-width: 0; flex: 1 1 auto; }
@media (min-width: 721px) and (orientation: landscape), (min-width: 1001px) {
    .pd-page .pd-bench { grid-template-columns: minmax(0, 1fr) 272px; }
    .pd-page .pd-inspector .seg.wide { flex-wrap: wrap; }
    .pd-page .pd-inspector .seg.wide .seg-tab { flex: 1 1 auto; }
    .pd-page .pd-inspector .pd-actions-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .pd-page .pd-inspector .pd-actions-row > .pr-btn { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}

/* the player panel's route row is HIS (js/install-design.js paintTokRouteRow);
   .pd-sec is display:flex, which outranks the UA's [hidden] — say it here */
#pdTokRouteSec[hidden] { display: none; }

/* the QB's snap chip, beside the selected QB (js/install-design.js placeQbChip) */
.pd-qb-chip { position: absolute; z-index: 6; display: inline-flex; background: var(--surface); border: 1px solid var(--border-strong); border-radius: 4px; box-shadow: var(--gc-shadow-md); padding: 2px; }
.pd-qb-chip[hidden] { display: none; }
.pd-qb-chip .seg-tab { min-height: 44px; min-width: 44px; padding: 0 10px; font-size: 11px; }

/* ── THE THEME DIAL'S SWATCHES (Oct 6 2026) ─────────────────────────────── */
.pd-field-dial .pd-swatch--chalk { background: var(--fs-white); }
.pd-field-dial .pd-swatch--night { background: var(--fs-turf); }
.pd-field-dial .pd-swatch--broadcast { background: var(--fs-broadcast); }

/* ── THE LOOK SHEET (Oct 6 2026) — js/designer-look-card.js's second mount.
   A modal panel over the designer (z 1000, the modal tier); the card inside
   lays itself out (tray under the board on a phone, beside it on an iPad). */
.pd-look-sheet {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    padding: var(--gc-space-4);
    background: var(--gc-overlay);
}
.pd-look-sheet[hidden] { display: none; }
.pd-look-sheet__panel {
    position: relative;
    width: min(980px, 100%);
    max-height: calc(100dvh - 2 * var(--gc-space-4));
    overflow: auto;
    padding: var(--gc-space-5) var(--gc-space-4) var(--gc-space-4);
    border-radius: var(--gc-radius-xl);
    border: 1px solid var(--gc-border-subtle);
    background: var(--gc-bg-panel);
    box-shadow: var(--gc-shadow-2xl);
}
@media (min-width: 720px) { .pd-look-sheet { align-items: center; } }
/* its X is the page's one .pd-x (chunk A3) */
/* the card speaks the gc-* vocabulary; inside the designer it wears the
   designer's chrome (whiteboard / day / broadcast), not the app's */
.pd-look-sheet {
    --gc-bg-panel: var(--surface);
    --gc-bg-sunken: var(--pm-ink);
    --gc-bg-elevated: var(--pm-ink-raised);
    --gc-text-color-primary: var(--ink);
    --gc-text-color-secondary: var(--ink-muted);
    --gc-border-subtle: var(--border);
    --gc-border-strong: var(--border-strong, var(--border));
    --gc-accent-primary: var(--seg-accent, var(--volt));
}

/* THE INSPECTOR CHIPS WEAR THE LOOK TOO (Oct 6 2026): the "players on the
   board" chips are the same men as the discs — same fill (the base hex), same
   letter as the module chose. */
:root[data-look] .pd-tok:not(.ghost)[data-pos="Q"] { background: var(--lk-hex-Q); color: var(--lk-ink-Q); }
:root[data-look] .pd-tok:not(.ghost)[data-pos="C"] { background: var(--lk-hex-C); color: var(--lk-ink-C); }
:root[data-look] .pd-tok:not(.ghost)[data-pos="X"] { background: var(--lk-hex-X); color: var(--lk-ink-X); }
:root[data-look] .pd-tok:not(.ghost)[data-pos="Y"] { background: var(--lk-hex-Y); color: var(--lk-ink-Y); }
:root[data-look] .pd-tok:not(.ghost)[data-pos="Z"] { background: var(--lk-hex-Z); color: var(--lk-ink-Z); }
:root[data-look] .pd-tok:not(.ghost)[data-pos="H"] { background: var(--lk-hex-H); color: var(--lk-ink-H); }
:root[data-look] .pd-tok:not(.ghost)[data-pos="QB"] { background: var(--lk-hex-Q); color: var(--lk-ink-Q); }
/* inside the sheet the panel IS the surface — the card drops its own frame */
.pd-look-sheet .sc-look { border: 0; padding: 0; background: none; }

/* ══ ROUND 3 (Oct 6 2026, Carlos on the Chalk / Classic / Polished board) ══ */

/* (1) ONE STROKE IN EVERY STATE. The selection HALO is a disc of colour
   around the token — on paper it read as a second ring. Under a look a
   selected player changes his one casing (.tok.is-sel above) and nothing
   else is drawn around him. */
:root[data-look] .pd-field .tokens .sel-halo { display: none; }

/* (3) A ROUTE IS CASED ONLY WHERE IT WOULD GET LOST — the canvas's rule
   (js/play-renderer.js routeNeedsCasing), from js/designer-palette.js
   routeCasing via --lk-rc-*: 'transparent' where the route clears 3:1 on its
   own board. The casing is one stroke under the LINE; it never carries a
   marker, a filter or a round cap that pokes past the arrowhead. */
/* the inlay's colour and the route's edge, one family per rule — the SAME
   families the casing always used (pass · classic run/pa · colour per slot) */
:root[data-look] .pd-field .route-casing { stroke: var(--lk-ri-pass, transparent); filter: none; marker-end: none; }
:root[data-look] .pd-field .route[data-kind]:not(.motion):not(.open):not(.ballpath) { stroke: var(--lk-re-pass, var(--pd-kind)); }
:root[data-look][data-tokens="classic"] .pd-field .route-casing[data-classic="run"] { stroke: var(--lk-ri-run, transparent); }
:root[data-look][data-tokens="classic"] .pd-field .route[data-kind][data-classic="run"]:not(.motion):not(.open):not(.ballpath) { stroke: var(--lk-re-run, var(--pd-kind)); }
:root[data-look][data-tokens="classic"] .pd-field .route-casing[data-classic="play_action"] { stroke: var(--lk-ri-pa, transparent); }
:root[data-look][data-tokens="classic"] .pd-field .route[data-kind][data-classic="play_action"]:not(.motion):not(.open):not(.ballpath) { stroke: var(--lk-re-pa, var(--pd-kind)); }
:root[data-look][data-tokens="colour"] .pd-field .route-casing[data-pos="C"] { stroke: var(--lk-ri-C, transparent); }
:root[data-look][data-tokens="colour"] .pd-field .route[data-kind][data-pos="C"]:not(.motion):not(.open):not(.ballpath) { stroke: var(--lk-re-C, var(--pd-kind)); }
:root[data-look][data-tokens="colour"] .pd-field .route-casing[data-pos="X"] { stroke: var(--lk-ri-X, transparent); }
:root[data-look][data-tokens="colour"] .pd-field .route[data-kind][data-pos="X"]:not(.motion):not(.open):not(.ballpath) { stroke: var(--lk-re-X, var(--pd-kind)); }
:root[data-look][data-tokens="colour"] .pd-field .route-casing[data-pos="Y"] { stroke: var(--lk-ri-Y, transparent); }
:root[data-look][data-tokens="colour"] .pd-field .route[data-kind][data-pos="Y"]:not(.motion):not(.open):not(.ballpath) { stroke: var(--lk-re-Y, var(--pd-kind)); }
:root[data-look][data-tokens="colour"] .pd-field .route-casing[data-pos="Z"] { stroke: var(--lk-ri-Z, transparent); }
:root[data-look][data-tokens="colour"] .pd-field .route[data-kind][data-pos="Z"]:not(.motion):not(.open):not(.ballpath) { stroke: var(--lk-re-Z, var(--pd-kind)); }
:root[data-look][data-tokens="colour"] .pd-field .route-casing[data-pos="H"] { stroke: var(--lk-ri-H, transparent); }
:root[data-look][data-tokens="colour"] .pd-field .route[data-kind][data-pos="H"]:not(.motion):not(.open):not(.ballpath) { stroke: var(--lk-re-H, var(--pd-kind)); }
:root[data-look][data-tokens="colour"] .pd-field .route-casing[data-pos="Q"] { stroke: var(--lk-ri-Q, transparent); }
:root[data-look][data-tokens="colour"] .pd-field .route[data-kind][data-pos="Q"]:not(.motion):not(.open):not(.ballpath) { stroke: var(--lk-re-Q, var(--pd-kind)); }
:root[data-look][data-tokens="colour"] .pd-field .route-casing[data-pos="QB"] { stroke: var(--lk-ri-Q, transparent); }
:root[data-look][data-tokens="colour"] .pd-field .route[data-kind][data-pos="QB"]:not(.motion):not(.open):not(.ballpath) { stroke: var(--lk-re-Q, var(--pd-kind)); }
/* material is the DISCS' alone — no route, arrowhead, motion leg or ball
   path wears a shadow (the broadcast bloom below is the field's glow on
   the charcoal board and stays) */
:root[data-look]:not([data-look="broadcast"]) .pd-field .route,
:root[data-look] .pd-field .route-casing,
:root[data-look] .pd-field .motion-leg { filter: none; }

/* (4) KIND SAYS ITSELF IN THE LINE (Carlos: "Isn't there supposed to be a
   difference on these?"). The swatch dot was three identical marks in Mono
   and was hidden outright once the foot folded. Each tab now carries the
   board's own line for its kind — pass solid, run dashed 9/5, play action
   dotted (the route rules at the top of this file) — over its word, at every
   width. The tab's ink in Mono; Classic paints run and play action in the Oct 1 route
   colours (--classic-run / --classic-pa). */
.pd-kind-glyph { display: block; width: 24px; height: 8px; flex: none; overflow: visible; fill: none; stroke: var(--kg, currentColor); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* the foot's glyph tabs paint every svg in currentColor (css/install-design.css);
   these outrank that rule by id so Classic can colour run / play action.
   ⛔ THE PASS LINE IS THE TAB'S OWN INK (Oct 7 2026 hotfix): it was --ink, the
   BOARD's ink — dark on Night Turf's dark tray, so the solid pass line
   vanished there. currentColor is the colour the tab's word is already set
   in, so the line reads wherever its label reads, in every theme. */
#pdFootKind .seg-tab[data-kind] .pd-kind-glyph { --kg: currentColor; stroke: var(--kg); fill: none; width: 24px; height: 8px; }
:root[data-tokens="classic"] #pdFootKind .seg-tab[data-kind="run"] .pd-kind-glyph { --kg: var(--classic-run); }
:root[data-tokens="classic"] #pdFootKind .seg-tab[data-kind="pa"] .pd-kind-glyph { --kg: var(--classic-pa); }
#pdFootKind .seg-tab[data-kind] .pd-kind-glyph path { stroke: inherit; fill: none; }
.pd-page #pdFootKind .seg-tab { flex-direction: column; justify-content: center; gap: 3px; line-height: 1; }

/* ══ THE BOARD IS THE HERO: THE VERTICAL BUDGET (Carlos, Oct 7 2026, chunk G
   — "do it") ═════════════════════════════════════════════════════════════════
   iPad landscape + desktop only (portrait and the phone never match). Every
   height recovered goes to the board; the field's viewBox and aspect are not
   touched (it is height-bound, so it grows in both directions). One row is not
   possible at 1024 (measured: the strip's controls are 958px at rest, the foot
   bar's 1063px, against 686px of room), so the strips wrap where the width
   forces it — never overflow, never scroll — with 36px keys, inline group
   labels and tight gaps. At 1280+ the strip above the board is one row. */
@media (min-width: 721px) and (orientation: landscape), (min-width: 1001px) {
    /* G-3 · the header is one 56px band: same controls, same order */
    .pd-page .pd-topbar { min-height: 56px; }
    /* the name and its one-line hint share the band: the hint (the first
       gesture, #pdTitleSub) sits AFTER the name on the same line and
       ellipsizes; the name box is capped so the hint keeps its room
       (js/install-design.js sizeTitle reads this max-width) */
    .pd-page .pd-name { display: flex; align-items: center; gap: 14px; min-width: 0; }
    .pd-page .pd-name-field { flex: 0 1 auto; }
    /* capped only while a hint shares the line — with none, the name takes
       the whole room (D-1) */
    .pd-page .pd-name:has(> .pd-title-sub:not(:empty)) .pd-name-field { max-width: 62%; }
    .pd-page .pd-title-sub { flex: 1 1 0; min-width: 0; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .pd-page .pd-title-sub:empty { display: none; }
    .pd-page .pd-name input { font-size: 22px; }
    .pd-page .pd-main { gap: 8px; }

    /* G-1 · the strip above the board: compact keys, one row where it fits */
    .pd-page .pd-canvas-head { padding: 4px 8px; min-height: 0; row-gap: 4px; column-gap: 8px; }
    .pd-page .pd-canvas-head > .pd-group { row-gap: 4px; column-gap: 6px; }
    .pd-page .pd-dials-wrap { flex: 0 1 auto; border-top: 0; padding-top: 0; }
    .pd-page .pd-dials { gap: 6px; }
    .pd-page .pd-canvas-head .seg { padding: 2px; }
    .pd-page .pd-canvas-head .seg-tab { min-height: 36px; padding: 0 6px; }
    .pd-page .pd-canvas-head .pd-icon,
    .pd-page .pd-canvas-head .pd-group > button { min-height: 36px; height: 36px; min-width: 36px; width: 36px; }
    /* ⛔ UNDO / REDO KEEP 44px (M4-1's law, scripts/test-designer-layout.js):
       they are the row's right-aligned pair and the one undo a coach reaches
       for mid-drag — the compact 36px is for the dials, not for these */
    .pd-page .pd-dials .pd-history .pd-icon { width: 44px; height: 44px; min-width: 44px; min-height: 44px; }

    /* G-2 · the foot bar: group labels LEAD their seg on the same line
       (the 10–11px label tier), keys 36px, tighter groups */
    .pd-page .pd-canvas-foot { padding: 4px 8px; min-height: 0; }
    .pd-page .pd-canvas-foot .pd-foot-groups { flex-wrap: wrap; justify-content: flex-start; overflow-x: visible; row-gap: 4px; column-gap: 12px; }
    .pd-page .pd-canvas-foot .pd-foot-groups .pd-group > .pr-label { margin: 0; }
    .pd-page .pd-canvas-foot .seg { padding: 2px; }
    .pd-page .pd-canvas-foot .seg-tab { min-height: 36px; padding-top: 0; padding-bottom: 0; }
}
/* where the foot bar must wrap anyway (below 1201px — 1024 measured 1063px of
   controls for 686px of room), each label LEADS its seg on the same line, so
   two rows of keys cost two key heights instead of four lines; wider than
   that, the foot already holds ONE row with its labels above, and inlining
   them would push it onto two (measured at 1280) */
@media (min-width: 721px) and (max-width: 1200px) and (orientation: landscape) {
    .pd-page .pd-canvas-foot .pd-foot-groups .pd-group { flex-direction: row; align-items: center; gap: 6px; }
    .pd-page #pdFootKind .seg-tab { flex-direction: row; gap: 4px; }
}

/* ══ CHUNK B (Oct 8 2026, OIA polish) ═══════════════════════════════════════
   B2 · THE SHAPE, AT THE LEFT THUMB. Two stacked 56px keys directly under the
   Route tool, shown only while Route is in hand (js/install-design.js
   paintShapeSeg owns `hidden`, `.on` and aria-pressed). Lit = the rail's own
   on-state: ink fill, on-ink glyph, volt left edge. The rail widens to hold
   them; its 44px tools stay centred in it. */
.pd-page .pd-tools { align-items: center; }
.pd-rail-shape { display: flex; flex-direction: column; gap: 4px; padding: 4px 0; margin: 2px 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.pd-rail-shape[hidden] { display: none; }
.pd-rail-shape-key {
    position: relative; width: 56px; min-height: 56px; padding: 6px 0 4px;
    display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
    border: 1px solid transparent; border-radius: var(--r-xs); background: transparent; color: var(--ink);
    font-family: var(--font-ui); font-size: 10px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
    -webkit-tap-highlight-color: transparent; cursor: pointer;
}
.pd-rail-shape-key svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 2.4; stroke-linecap: round; }
.pd-rail-shape-key:hover { background: var(--hover); }
.pd-rail-shape-key.on { background: var(--ink); color: var(--pd-on-ink); box-shadow: inset 3px 0 0 var(--volt); }
.pd-rail-shape-key:focus { outline: none; }
.pd-rail-shape-key:focus-visible { outline: 2px solid var(--volt); outline-offset: 2px; }
/* the rail holds them inside the stage's existing 78px gutter: with the keys
   shown it tightens its padding to 3px (14 + 3 + 56 + 3 + 2 = 78), so the
   field never moves and never sits under the rail */
.pd-page .pd-tools:has(> .pd-rail-shape:not([hidden])) { padding: 3px; }

/* B3 · THE THEME KEY, BESIDE LOOK. The key names the theme; its popover holds
   the SAME data-pref="theme" dial that used to sit on the board. */
.pd-theme-pick { position: relative; display: inline-flex; }
#pdThemeKey { gap: 8px; }
#pdThemeKey .pd-swatch { display: inline-block; width: 14px; height: 14px; border-radius: 50%; box-shadow: 0 0 0 1px var(--border-strong); flex: none; }
#pdThemeKey .pd-swatch--chalk { background: var(--fs-white); }
#pdThemeKey .pd-swatch--night { background: var(--fs-turf); }
#pdThemeKey .pd-swatch--broadcast { background: var(--fs-broadcast); }
.pd-theme-name { white-space: nowrap; }
.pd-theme-pop {
    position: absolute; top: calc(100% + 6px); left: 0; z-index: 40; padding: 6px;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-sm); box-shadow: var(--pd-shadow-lift);
}
.pd-theme-pop[hidden] { display: none; }
.pd-theme-pop .pd-field-dial { position: static; padding: 3px; display: flex; flex-direction: column; align-items: stretch; }
.pd-theme-pop .pd-field-dial .seg-tab { min-height: 44px; min-width: 168px; padding: 0 12px; display: flex; align-items: center; justify-content: flex-start; gap: 10px; }
.pd-theme-tab-word { font-family: var(--font-ui); font-weight: 700; font-size: 14px; letter-spacing: 0; text-transform: none; white-space: nowrap; }

/* B4(i) · PLAY IT, IN THE FIELD'S BOTTOM-RIGHT CORNER — the look, size, inset
   and states of Call This Play's board key (#modalPlayBtn, the sibling lane
   fix/ctp-play-button-back-in-the-corner, css/theme.css): a 56px glass disc
   of board ink, inset 16px, the glyph in board paper, a volt rim while it
   runs. Placed by the stage's --pd-field-r / --pd-field-b (the page measures
   the field). The word stays in the DOM as its accessible name only. */
.pd-page .pd-stage > .pd-run.pd-run--board {
    right: calc(var(--pd-field-r, 14px) + 16px); bottom: calc(var(--pd-field-b, 68px) + 16px);
    width: 56px; height: 56px; min-width: 0; min-height: 0; padding: 0; gap: 0;
    border-radius: 50%; justify-content: center;
    background: color-mix(in srgb, var(--gc-board-ink) 72%, transparent);
    -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
    border: 1.5px solid color-mix(in srgb, var(--gc-board-paper) 55%, transparent);
    box-shadow: 0 6px 20px color-mix(in srgb, var(--gc-board-ink) 45%, transparent);
    color: var(--gc-board-paper);
}
.pd-page .pd-stage > .pd-run.pd-run--board svg { width: 22px; height: 22px; fill: currentColor; }
.pd-page .pd-stage > .pd-run.pd-run--board .pd-run-word { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.pd-page .pd-stage > .pd-run.pd-run--board.live {
    background: color-mix(in srgb, var(--gc-board-ink) 85%, transparent);
    border-color: var(--volt); color: var(--gc-board-paper);
}
.pd-page .pd-stage > .pd-run.pd-run--board:focus-visible { outline: 2px solid var(--volt); outline-offset: 2px; }
/* the run's controls take the whole strip under the field now */
.pd-page .pd-stage > .pd-scrub { right: 14px; }
.pd-page .pd-canvas .pd-contract { right: 14px; left: 92px; }
@media (max-width: 720px) {
    /* the phone's own track row ran under PLAY IT's old column (right: -52px); the disc is on the field now */
    .pd-page .pd-stage > .pd-scrub .pd-scrub-track { right: 0; }
}
@media (max-width: 720px) {
    /* a phone's field is ~270px wide: a 56px disc in its corner is 8 yards of
       backfield and sat on a receiver. Call This Play keeps its phone key off
       the board too (ctpPlacePlayBtn), so here the disc rides the strip under
       the field, right-aligned, exactly where Play It lived before B4. */
    .pd-page .pd-stage > .pd-run.pd-run--board { right: 14px; bottom: 6px; }
    .pd-page .pd-stage > .pd-scrub { right: 80px; }
}
/* B3 in the upright tablet band: the theme key keeps its NAME (that is its
   job); Look and 3D fold to their icons (each keeps its aria-label), and the
   play's name keeps a floor so the new key can never squeeze it to a caret —
   measured at 820x1180 with 3D shown, where it did. */
@media (min-width: 721px) and (max-width: 1000px) and (orientation: portrait) {
    .pd-page #pdLookSheetBtn .pd-btn-word, .pd-page #pd3dBtn .pd-btn-word { display: none; }
    .pd-page #pdLookSheetBtn, .pd-page #pd3dBtn { width: 44px; padding: 0; justify-content: center; }
    .pd-page .pd-titleblock { min-width: 220px; }
}
@media (max-width: 720px) {
    /* B3 on a phone: the theme key keeps its NAME; Look and 3D fold to their
       icons (aria-labels kept) so the header keeps its rows — measured 186px
       before B3, 234px with the words */
    .pd-page #pdLookSheetBtn .pd-btn-word, .pd-page #pd3dBtn .pd-btn-word { display: none; }
    .pd-page #pdLookSheetBtn, .pd-page #pd3dBtn { width: 44px; padding: 0; justify-content: center; }
}

/* ══ THE MOTION LEG IS ITS ROUTE'S WIDTH (Oct 8 2026) ════════════════════════
   The pre-snap overlay was a fixed 2.5 (css/install-design.css .motion-leg)
   over a route that is 3.0 when it is a Run (+0.5) and 3.4 for Play action —
   so a CASED route's dark edge peeked out round every motion dash and the
   squiggle read outlined and heavy. The overlay now takes its route's exact
   width (kind and weight), so its dashes cover the footprint and the gaps
   show the route's own edge + inlay. The motion colour clears 3:1 on all
   three boards (Chalk key blue, Night Turf white, Broadcast volt): it needs
   no casing of its own. Order: route, .route-casing (inlay), .motion-leg. */
:root[data-field] .pd-field .route + .route-casing + .motion-leg { stroke-width: var(--route-w); }
:root[data-field] .pd-field .route[data-kind="run"]:not(.ballpath) + .route-casing + .motion-leg { stroke-width: calc(var(--route-w) + 0.5); }
:root[data-field] .pd-field .route[data-kind="pa"]:not(.ballpath) + .route-casing + .motion-leg { stroke-width: calc(var(--route-w) + 0.9); }
:root[data-field] .pd-field .route[data-weight="s"] + .route-casing + .motion-leg,
:root[data-field] .pd-field .rgroup[data-weight="s"] .route + .route-casing + .motion-leg { stroke-width: 1.6; }
:root[data-field] .pd-field .route[data-weight="l"] + .route-casing + .motion-leg,
:root[data-field] .pd-field .rgroup[data-weight="l"] .route + .route-casing + .motion-leg { stroke-width: 4; }

/* ══ FIT (Oct 8 2026, chunk C — Carlos picked "Fit") ═════════════════════════
   The board's BOX is the field's own shape at every stage shape. Before, the
   svg was width:100% (css/install-design.css:221), so on a wide, short stage
   max-height clamped the height, the width stayed 100%, the field scaled
   "meet" into the middle, and the svg's own turf background
   (:root[data-field] .pd-field, above) painted turf past the sidelines.
   Now width and height are both auto under max-width/max-height and the
   aspect-ratio of the window in use (deep 480/324, deeper 480/372): the box
   IS the viewBox, nothing stretches, and the stage around it is the panel.
   Coordinates are untouched — this is layout only (960x600, 24 px/yd). */
.pd-page .pd-stage svg.pd-field { width: auto; height: auto; max-width: 100%; max-height: 100%; flex: 0 1 auto; margin: auto; }
.pd-page .pd-stage { background: var(--surface); }
:root[data-theme="dark"][data-chrome="broadcast"] .pd-page .pd-stage { background: var(--surface); }

/* ══ THE CLASSIC DEFENCE LOOK (Oct 8 2026, chunk D2/D3 — Carlos: "look at our
   EXISTING defense ... I like it way better"; the reference is the classic
   designer, js/playmaker.js drawZoneOval :3633 and drawPlayer :4148). COPIED,
   not redrawn: each zone wears its TYPE's hue — deep blue, flat purple, hook
   olive, match cyan — a 40% fill, a 60% border, its word in white at 60%, and
   the defender is joined to it by a dashed leader (8/6 at 3px in the classic
   designer's 960 space = 4/3 at 1.5 here, K = 0.5) in the hue at 80%. A
   defender tethered to a zone wears its hue; anyone else his position group's.
   THE ONE TABLE is js/field-lineup.js ZONE_COLORS_MAP + DEFENSE_COLORS_MAP; the
   tokens below mirror it by hand (CSS cannot import JS) and
   scripts/test-zone-colour-parity.js proves them equal.
   PER-THEME CONTRAST: on the white Chalk board the classic white word would
   vanish, so the word takes the board's own strongest mark (--board-los) and
   the border goes to 85% — the hues themselves never change. ══ */
:root {
    --zone-deep: #3b82f6; --zone-flat: #a855f7; --zone-hook: #b4b450; --zone-match: #22d3ee;
    --defpos-R: #ef4444; --defpos-LB: #3b82f6; --defpos-CB: #a855f7; --defpos-N: #14b8a6; --defpos-D: #0ea5e9; --defpos-S: #22c55e;
    --def-letter-light: #ffffff; --def-letter-dark: #0d1220;   /* the letter on a dark / light hue */
}
.pd-field .zone[data-zone-type="ZONE_DEEP"] { --zc: var(--zone-deep); }
.pd-field .zone[data-zone-type="ZONE_FLAT"] { --zc: var(--zone-flat); }
.pd-field .zone[data-zone-type="ZONE_HOOK"] { --zc: var(--zone-hook); }
.pd-field .zone[data-zone-type="ZONE_MATCH"] { --zc: var(--zone-match); }
:root[data-field] .pd-field .zone .zone-oval {
    fill: color-mix(in srgb, var(--zc, var(--defense)) 40%, transparent);
    stroke: color-mix(in srgb, var(--zc, var(--defense)) 60%, transparent);
    stroke-width: 1;
}
:root[data-field] .pd-field .zone .zone-oval-casing,
:root[data-field] .pd-field .zone .zone-tether-casing { display: none; }
:root[data-field] .pd-field .zone .zone-tether {
    stroke: color-mix(in srgb, var(--zc, var(--defense)) 80%, transparent);
    stroke-width: 1.5;
    stroke-dasharray: 4 3;
}
:root[data-field] .pd-field .zone .zone-label { fill: color-mix(in srgb, var(--board-los) 60%, transparent); font-size: 9px; }
:root[data-field="white"] .pd-field .zone .zone-oval { stroke: color-mix(in srgb, var(--zc, var(--defense)) 85%, transparent); }
:root[data-field="white"] .pd-field .zone .zone-label { fill: color-mix(in srgb, var(--board-los) 75%, transparent); }
:root[data-field] .pd-field .zone.selected .zone-oval { stroke: var(--key); stroke-width: 2; }

/* D3 — the defenders: CLASSIC and COLOUR tokens wear the classic colours (zone
   hue, else position group); MONO keeps the one defence red, as the offence's
   Mono keeps one ink. The same key paints the board's disc and the players
   panel's chip. White letters, ink on the two light hues (hook, match, safety). */
:root[data-field] .pd-field [data-def="ZONE_DEEP"] { --dc: var(--zone-deep); --dci: var(--def-letter-light); }
:root[data-field] .pd-field [data-def="ZONE_FLAT"] { --dc: var(--zone-flat); --dci: var(--def-letter-light); }
:root[data-field] .pd-field [data-def="ZONE_HOOK"] { --dc: var(--zone-hook); --dci: var(--def-letter-dark); }
:root[data-field] .pd-field [data-def="ZONE_MATCH"] { --dc: var(--zone-match); --dci: var(--def-letter-dark); }
:root[data-field] .pd-field [data-def="R"] { --dc: var(--defpos-R); --dci: var(--def-letter-light); }
:root[data-field] .pd-field [data-def="LB"] { --dc: var(--defpos-LB); --dci: var(--def-letter-light); }
:root[data-field] .pd-field [data-def="CB"] { --dc: var(--defpos-CB); --dci: var(--def-letter-light); }
:root[data-field] .pd-field [data-def="N"] { --dc: var(--defpos-N); --dci: var(--def-letter-dark); }
:root[data-field] .pd-field [data-def="D"] { --dc: var(--defpos-D); --dci: var(--def-letter-dark); }
:root[data-field] .pd-field [data-def="S"] { --dc: var(--defpos-S); --dci: var(--def-letter-dark); }
:root:not([data-tokens="mono"])[data-field] .pd-field[data-unit="defense"] .tok[data-def] { fill: var(--dc); }
:root:not([data-tokens="mono"])[data-field] .pd-field[data-unit="defense"] .tok-label[data-def] { fill: var(--dci); }
.pd-tok[data-def="ZONE_DEEP"] { --dc: var(--zone-deep); --dci: var(--def-letter-light); }
.pd-tok[data-def="ZONE_FLAT"] { --dc: var(--zone-flat); --dci: var(--def-letter-light); }
.pd-tok[data-def="ZONE_HOOK"] { --dc: var(--zone-hook); --dci: var(--def-letter-dark); }
.pd-tok[data-def="ZONE_MATCH"] { --dc: var(--zone-match); --dci: var(--def-letter-dark); }
.pd-tok[data-def="R"] { --dc: var(--defpos-R); --dci: var(--def-letter-light); }
.pd-tok[data-def="LB"] { --dc: var(--defpos-LB); --dci: var(--def-letter-light); }
.pd-tok[data-def="CB"] { --dc: var(--defpos-CB); --dci: var(--def-letter-light); }
.pd-tok[data-def="N"] { --dc: var(--defpos-N); --dci: var(--def-letter-dark); }
.pd-tok[data-def="D"] { --dc: var(--defpos-D); --dci: var(--def-letter-dark); }
.pd-tok[data-def="S"] { --dc: var(--defpos-S); --dci: var(--def-letter-dark); }
:root:not([data-tokens="mono"]) .pd-tok[data-def] { background: var(--dc); color: var(--dci); }
:root[data-tokens="mono"] .pd-tok[data-def] { background: var(--defense); color: var(--def-letter-light); }

/* WHERE THE PLAY LIVES (chunk F): "in Eagles Playbook · Draft · Save publishes it" */
/* it sits on the play's plate beside the sub line, so it takes that line's own colour */
.pd-play-home { margin-top: 4px; font-family: var(--font-ui); font-size: 12px; font-weight: 700; letter-spacing: .02em; color: inherit; opacity: .85; }
.pd-play-home[hidden] { display: none; }
.pd-play-home.is-draft { opacity: 1; }

/* ══ D4 · FLIP VIEW (Oct 8 2026) ════════════════════════════════════════════
   Only on a defence play. The board turns 180° about its centre (CSS on the
   svg: the pointer maps through getScreenCTM, which includes it, so a drag
   still lands where the finger is); every WORD and NUMBER turns back so it
   reads upright. Routes, arrowheads and zones turn with the board, which is
   what a turned board does. Stored coordinates never change. */
.pd-def-view { display: none; }
.pd-page .pd-canvas-head .pd-def-view .seg-tab { min-height: 44px; }
.pd-page:has(#pdField[data-unit="defense"]) .pd-def-view { display: inline-flex; }
:root[data-def-view="offense"] .pd-page .pd-stage svg.pd-field[data-unit="defense"] { transform: rotate(180deg); }
:root[data-def-view="offense"] .pd-page svg.pd-field[data-unit="defense"] text { transform-box: fill-box; transform-origin: center; transform: rotate(180deg); }

/* 2b · zone handles (Oct 8 2026): only on the selected zone; a 44px target */
:root[data-field] .pd-field .zone-handle { display: none; }
:root[data-field] .pd-field .zone-handle.on { display: inline; cursor: nwse-resize; }
/* the hit radius is sized so it is 44px on screen at every board size — the
   page writes --pd-hr (user units) from the field's own scale */
:root[data-field] .pd-field .zone-handle-hit { fill: transparent; stroke: none; pointer-events: all; r: var(--pd-hr, 16px); }
:root[data-field] .pd-field .zone-handle-dot { fill: var(--key); stroke: var(--def-ring); stroke-width: 1.2; pointer-events: none; }
/* 2c · a RUSHER wears the rush red in every token style (the classic designer:
   isRusher → #ef4444, js/playmaker.js:4158) */
:root[data-field] .pd-field[data-unit="defense"] .tok[data-rusher] { fill: var(--defpos-R); }
:root[data-field] .pd-field[data-unit="defense"] .tok-label[data-rusher] { fill: var(--def-letter-light); }
.pd-def-only { display: none; }
body[data-unit="defense"] .pd-def-only:not([hidden]) { display: block; }
:root[data-field] .pd-field .rush-lane { stroke: var(--defpos-R); stroke-width: 1.6; stroke-dasharray: 5 3; pointer-events: none; }
:root[data-field] .pd-field .rush-head { fill: var(--defpos-R); pointer-events: none; }

/* Play It has nothing to run on a DEFENCE play (js/install-design.js runStart
   returns for playType 'defense'): a dead control is removed, not left to say
   nothing (defence surface pass, Oct 8 2026). */
.pd-page:has(#pdField[data-unit="defense"]) .pd-stage > .pd-run.pd-run--board { display: none; }

/* WHERE ON THE FIELD (Oct 8 2026): the goal line, end zone and pylons — tokens only */
:root { --pylon: #f97316; }
/* A PAINTED END ZONE (Oct 8 2026, chunk H): the turf continues inside under a
   darker tint of the theme's end-zone colour; past the end line, out of bounds,
   that colour is opaque. The lines inside are not drawn at all (data-in-endzone). */
:root[data-field] .pd-field .goal .endzone { fill: var(--board-ez); fill-opacity: var(--board-ez-a, 1); pointer-events: none; }
:root[data-field] .pd-field .goal .endzone-oob { fill: var(--board-ez); pointer-events: none; }
/* the end-zone grain — Carlos's stripes (js/designer-palette.js EZ_PAINT) */
:root[data-field] .pd-field .goal .endzone-pat { fill: url(#pdEzStripes); pointer-events: none; }
.ez-pat-ink { fill: var(--board-ez-pat, transparent); }
/* the goal line: bold, the theme's line colour, 3x the 1-unit chalk (2.5x the
   2-unit sideline) — clearly the heaviest line on the field after the LOS */
:root[data-field] .pd-field .goal .goal-line { stroke: var(--board-goal, #ffffff); stroke-width: 3; pointer-events: none; }
:root[data-field] .pd-field .goal .end-line { stroke: var(--board-goal, #ffffff); stroke-width: 2; stroke-opacity: 0.85; pointer-events: none; }
:root[data-field] .pd-field .goal .pylon { fill: var(--pylon); stroke: rgba(0,0,0,0.28); stroke-width: 0.75; pointer-events: none; }
/* the end zone's name, never yard numerals (Oct 8 2026): the cards' lettering —
   the UI face, bold, wide-tracked, the chalk's own ink */
:root[data-field] .pd-field .goal .endzone-word { font-family: var(--gc-font-display); font-weight: 800; text-transform: uppercase; fill: var(--board-ez-word, var(--pd-chalk-strong)); pointer-events: none; }
.pd-field [data-depth-label][data-in-endzone],
.pd-field .chalk > line[data-in-endzone],
.pd-field .hash > line[data-in-endzone] { display: none; }

/* WHERE ON THE FIELD is ONE ROW OF THREE at every panel width (Oct 8 2026, chunk H:
   "COMING OUT" wrapped onto a clipped second row under the inspector's
   `.seg.wide { flex-wrap: wrap }`). Equal thirds, tighter padding and tracking;
   the words are already the short ones. */
.pd-page .pd-inspector #pdFieldPos.seg.wide,
#pdFieldPos.seg.wide { flex-wrap: nowrap; width: 100%; box-sizing: border-box; }
.pd-page .pd-inspector #pdFieldPos.seg.wide .seg-tab,
#pdFieldPos.seg.wide .seg-tab { flex: 1 1 0; min-width: 0; padding: 0 var(--gc-space-1, 4px); letter-spacing: 0.02em; font-size: 11px; }

/* ══ YOUR DRAFTS (Oct 10 2026, lane DR1) ═════════════════════════════════════
   Carlos: the drafts live in the designer, "very small". The key is the strip's
   own ghost key (icon + word, like Look) with the count after the word; it
   folds to icon + count exactly where Look folds, and stays a 44px target.
   The sheet is the page's `ip-modal` sheet and each draft is an ask plate
   (`pd-ask-opt`) laid sideways for its small board. The theme's tokens only —
   Chalk, Night Turf and Broadcast each repaint it with no rule of its own. */
.pd-page .pd-drafts-key { min-height: 44px; min-width: 44px; gap: 6px; }
.pd-page .pd-drafts-key[hidden] { display: none; }
.pd-drafts-n {
    font-family: var(--font-ui);
    font-weight: 800;
    font-variant-numeric: tabular-nums;
    color: var(--ink);
}
.pd-drafts-list { display: flex; flex-direction: column; gap: 10px; }
.pd-ask-opt.pd-draft-opt {
    flex-direction: row;
    align-items: center;
    gap: 12px;
    padding: 8px 14px 8px 8px;
}
.pd-draft-thumb {
    flex: 0 0 auto;
    width: 72px;
    height: 45px;
    border: 1px solid var(--border);
    border-radius: var(--r-sm);
    background: var(--hover);
}
.pd-draft-text { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pd-draft-name, .pd-draft-meta { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (min-width: 721px) and (max-width: 1000px) and (orientation: portrait) {
    .pd-page #pdDraftsBtn .pd-btn-word { display: none; }
    .pd-page #pdDraftsBtn { padding: 0 10px; justify-content: center; }
}
@media (max-width: 720px) {
    .pd-page #pdDraftsBtn .pd-btn-word { display: none; }
    .pd-page #pdDraftsBtn { padding: 0 10px; justify-content: center; }
}

/* ══ A PHONE IS TOLD THE TRUTH (VP1, Oct 11 2026) ═══════════════════════════
   coach/play-designer.html #pdRotateGuard + #pdPhoneNote. The guard is pure CSS
   so it paints before any script: a phone held sideways (coarse pointer, under
   500px tall — every iPhone, no iPad: the mini is 744 tall in landscape) gets a
   full-screen "turn it upright" instead of a 0x0 board. The native shell marks
   html[data-sc-native] (js/made-for-ipad.js) and the guard stands down there.
   The note is shown once, upright, by js/made-for-ipad.js noteOnce. */
.pd-rotate-guard {
    /* above every designer layer — the first-run formation sheet is 10020, the ask 10030 */
    display: none; position: fixed; inset: 0; z-index: 10100;
    background: var(--gc-bg-app); color: var(--gc-text-color-primary);
    flex-direction: column; align-items: center; justify-content: center;
    gap: var(--gc-space-3); text-align: center; padding: var(--gc-space-6);
}
.pd-rotate-guard svg { width: 52px; height: 52px; color: var(--gc-accent-primary); animation: pd-rg-turn 2.4s ease-in-out infinite; }
@keyframes pd-rg-turn { 0%, 20% { transform: rotate(90deg); } 55%, 100% { transform: rotate(0deg); } }
.pd-rg-title { font-family: var(--gc-font-display); font-weight: 700; font-size: 24px; text-transform: uppercase; letter-spacing: 0.06em; }
.pd-rg-sub { font-family: var(--gc-font-body); font-size: 16px; color: var(--gc-text-color-secondary); max-width: 360px; }
@media (orientation: landscape) and (max-height: 500px) and (pointer: coarse) {
    html:not([data-sc-native]) .pd-rotate-guard { display: flex; }
}
@media (prefers-reduced-motion: reduce) { .pd-rotate-guard svg { animation: none; } }

.pd-phone-note { position: fixed; inset: 0; z-index: 10050; /* over the first-run formation sheet (10020) and the ask (10030); under the rotate guard */ display: flex; align-items: center; justify-content: center; padding: var(--gc-space-4); background: var(--gc-overlay); }
.pd-phone-note[hidden] { display: none; }
.pd-phone-note-card {
    width: 100%; max-width: 340px; padding: var(--gc-space-6); text-align: center;
    background: var(--gc-bg-panel); color: var(--gc-text-color-primary);
    border: 1px solid var(--gc-border-subtle); border-radius: var(--gc-radius-lg);
}
.pd-phone-note-title { margin: 0 0 var(--gc-space-3); font-family: var(--gc-font-display); font-weight: 700; font-size: 24px; text-transform: uppercase; letter-spacing: 0.04em; }
.pd-phone-note-text { margin: 0 0 var(--gc-space-6); font-family: var(--gc-font-body); font-size: 16px; line-height: 1.45; color: var(--gc-text-color-secondary); }
.pd-phone-note-btn {
    width: 100%; min-height: 52px; border: 0; border-radius: var(--gc-radius-lg); cursor: pointer;
    background: var(--gc-accent-primary); color: var(--gc-text-on-accent);
    font-family: var(--gc-font-body); font-weight: 700; font-size: 17px; text-transform: uppercase; letter-spacing: 0.04em;
}


/* ══ OPTIONS, THE LOOK, MOTION, THE SPEED (Oct 11 2026, lane DSG-OPT — the
   approved mock design-directions/designer-options-and-look.html) ═══════════
   Every value below is a token the designer already uses; no new colour, no
   new face. Controls are the .seg raised keys, never pills. */

/* the eye's popover — the theme key's popover (.pd-theme-pop) on the board head */
.pd-page .pd-look-pick { position: relative; }
/* the eye is a KEY beside Offense | Defense (Oct 12 2026): the segment's own
   raised-key tray, one tab — eye + the active look at the label tier. 4px off
   the segment; the same 36px key the head's segs are, with the foot tabs' 4px
   skirt above and below so it taps at 44. */
/* a TRUE 4px off the segment: the head group lays its children 8px apart
   (css/install-design.css .pd-group), 6px in the landscape/desktop band
   (:1609) — the key pulls back by the difference. */
.pd-page #pdLookSeg { margin-left: -4px; }
@media (min-width: 721px) and (orientation: landscape), (min-width: 1001px) {
    .pd-page #pdLookSeg { margin-left: -2px; }
}
.pd-page #pdLookSeg .seg-tab { position: relative; gap: 7px; padding: 0 12px; }
.pd-page #pdLookSeg .seg-tab::before { content: ''; position: absolute; inset: -4px 0; }
.pd-page #pdLookSeg .seg-tab svg { width: 16px; height: 16px; }
.pd-page #pdLookSeg .pd-look-word { font-family: var(--font-ui); font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; }
.pd-page .pd-look-pop {
    position: absolute; top: calc(100% + 6px); left: 0; z-index: 40; min-width: 304px; padding: 8px;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-sm); box-shadow: var(--pd-shadow-lift);
}
.pd-page .pd-look-pop[hidden] { display: none; }
.pd-page .pd-look-pop > .pr-label { display: block; margin: 6px 4px 6px; }
.pd-page .pd-look-pop .pd-look-list { display: flex; flex-direction: column; align-items: stretch; padding: 3px; }
.pd-page .pd-look-pop .pd-look-list .seg-tab { min-height: 44px; justify-content: flex-start; padding: 0 12px; text-transform: none; letter-spacing: 0; font-size: 14px; }
.pd-page .pd-look-foot { margin: 6px 4px 2px; }
.pd-page .pd-look-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 44px; padding: 4px 4px; }
.pd-page .pd-look-row + .pd-look-row { border-top: 1px solid var(--border); }
.pd-page .pd-look-row .pd-look-w { font-family: var(--font-ui); font-weight: 700; font-size: 14px; color: var(--ink); }
.pd-page .pd-look-row .pd-look-sub { display: block; font-weight: 500; font-size: 11px; color: var(--ink-faint); margin-top: 1px; }
.pd-page .pd-look-row .seg.sm .seg-tab { min-height: 44px; min-width: 52px; }
/* the rail's value row: the look's name and the one door to change it */
.pd-page #pdLookRow { display: flex; align-items: center; gap: 8px; }
.pd-page #pdLookRow .pd-value { flex: 1; font-weight: 700; }

/* the option tabs on the board */
.pd-page .pd-stage > .pd-board-opts { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 7; display: flex; align-items: center; gap: 6px; }
.pd-page .pd-stage > .pd-board-opts[hidden] { display: none; }
.pd-page .pd-board-opts .seg { background: color-mix(in srgb, var(--gc-board-ink) 82%, transparent); -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
/* THE TRAY IS ALWAYS INK, SO AN INACTIVE TAB IS ALWAYS LIGHT (Carlos, Oct 12
   2026: "1 MAIN" read as disabled). The theme's --pm-ink-text is picked for the
   theme's own trays (dark text on Chalk) — on this board-ink tray it went faint.
   The paper tone at 82% reads as a tappable choice; the active tab keeps the
   raised key and its own text colour. */
.pd-page .pd-board-opts .seg-tab { min-height: 44px; padding: 0 14px; gap: 8px; color: color-mix(in srgb, var(--gc-board-paper) 82%, transparent); }
.pd-page .pd-board-opts .seg-tab:hover { color: var(--gc-board-paper); }
.pd-page .pd-board-opts .seg-tab.active { color: var(--pm-ink-text-active); }
.pd-page .pd-board-opts .seg-tab .pd-opt-n { font-size: 10px; letter-spacing: 0.1em; opacity: 0.7; }
.pd-page .pd-board-opts .seg-tab .pd-opt-pen { width: 13px; height: 13px; opacity: 0.75; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* the rename sheet, under the tabs */
.pd-page .pd-stage > .pd-opt-rename { position: absolute; top: 62px; left: 50%; transform: translateX(-50%); z-index: 8; width: 280px; padding: 10px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-sm); box-shadow: var(--pd-shadow-lift); }
.pd-page .pd-stage > .pd-opt-rename[hidden] { display: none; }
.pd-page .pd-opt-rename .pr-label { display: block; margin-bottom: 6px; }
.pd-page .pd-opt-rename .pr-input { width: 100%; min-height: 44px; box-sizing: border-box; }
.pd-page .pd-opt-rename .pd-actions-row { margin-top: 8px; display: flex; gap: 6px; justify-content: flex-end; }
.pd-page .pd-opt-rename .pr-btn { min-height: 40px; }
/* B: the landing the active option does not throw to */
.pd-page .pd-field .route.opt-ghost, .pd-page .pd-field .route-casing.opt-ghost, .pd-page .pd-field .motion-leg.opt-ghost { opacity: 0.28; }
.pd-page .pd-field.running .route.opt-ghost, .pd-page .pd-field.running .route-casing.opt-ghost { opacity: 0.28; }
/* the run's caption: which option is running */
.pd-page .pd-stage > .pd-play-cap { position: absolute; left: calc(var(--pd-field-l, 14px) + 16px); top: 10px; z-index: 7; display: inline-flex; align-items: center; gap: 8px; min-height: 36px; padding: 0 12px; border-radius: 6px; background: color-mix(in srgb, var(--gc-board-ink) 82%, transparent); color: var(--gc-board-paper); font-family: var(--font-ui); font-weight: 700; font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; box-shadow: inset 0 -2px 0 var(--seg-accent); }
.pd-page .pd-stage > .pd-play-cap[hidden] { display: none; }
.pd-page .pd-play-cap .pd-play-cap-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--live); }
.pd-page .pd-play-cap .pd-play-cap-name { text-transform: none; letter-spacing: 0; font-size: 13px; }

/* the speed key, beside the Play disc — the same corner, the same inset */
.pd-page .pd-stage > .pd-speed-board { position: absolute; z-index: 6; right: calc(var(--pd-field-r, 14px) + 16px + 56px + 10px); bottom: calc(var(--pd-field-b, 68px) + 16px + 6px); background: color-mix(in srgb, var(--gc-board-ink) 82%, transparent); }
.pd-page .pd-stage > .pd-speed-board.seg.sm .seg-tab { min-height: 44px; min-width: 48px; padding: 0 10px; gap: 5px; }
.pd-page .pd-speed-board .seg-tab svg { width: 14px; height: 14px; }
.pd-page .pd-speed-board .pd-speed-w { font-size: 12px; letter-spacing: 0.02em; text-transform: none; }
@media (max-width: 720px) {
    /* the disc rides the strip under the field on a phone (above); the speed key sits beside it there */
    .pd-page .pd-stage > .pd-speed-board { right: 80px; bottom: 6px; }
    .pd-page .pd-stage > .pd-scrub { right: 190px; }
}

/* Motion: its own labelled key (the word on the key where the bar is wide,
   the group caption above it everywhere) */
.pd-page #pdFootMotion .seg-tab { gap: 7px; padding: 0 11px; }
.pd-page #pdFootMotion .seg-tab svg { width: 28px; height: 12px; }
.pd-page #pdFootMotion .seg-tab .pd-tab-word { font-size: 11px; letter-spacing: 0.06em; }

/* SIT1 (Oct 13 2026): the Situation slot. THE PANEL never grows — the
   play's own situations as small read chips (two lines, then "+N more",
   js/install-design.js clampSituationReads) and one 44px button that opens
   the picker. Square everywhere (platform-css.md rule 5 — never pills). */
.pd-page .pd-toks.pd-sits { display: flex; flex-direction: column; align-items: stretch; gap: 8px; }
.pd-page .pd-sit-reads { display: flex; flex-wrap: wrap; gap: 6px; }
.pd-page .pd-sit-read {
    display: inline-flex; align-items: center; min-height: 26px; padding: 0 8px; border-radius: 3px;
    background: var(--ink); color: var(--on-ink); box-shadow: inset 0 -2px 0 var(--volt);
    font-family: var(--font-ui); font-size: 12px; font-weight: 700; letter-spacing: 0.02em; white-space: nowrap;
}
.pd-page .pd-sit-read[hidden] { display: none; }
.pd-page .pd-sit-more { background: transparent; color: var(--ink-muted); box-shadow: none; border: 1px solid var(--border); }
.pd-page .pd-sit-open {
    display: inline-flex; align-items: center; justify-content: space-between; gap: 10px;
    min-height: 44px; padding: 0 12px; border-radius: 3px;
    border: 1px solid var(--border-strong); background: var(--surface-elevated); color: var(--ink);
    font: inherit; font-family: var(--font-ui); font-size: 14px; font-weight: 700; letter-spacing: 0.02em;
    cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.pd-page .pd-sit-open:hover { border-color: var(--ink-muted); }
.pd-page .pd-sit-open:focus-visible { outline: 2px solid var(--volt); outline-offset: 2px; }
.pd-sit-chev { color: var(--ink-muted); font-size: 12px; }

/* THE PICKER: .ip-modal's card, 560px centred, 80vh at most; the list is
   the only thing that scrolls. Upright iPad and phone: a full-height sheet. */
.pd-sit-modal .pd-sit-card { width: min(560px, 100%); max-height: 80vh; height: auto; }
.pd-sit-done {
    min-height: 44px; min-width: 76px; padding: 0 16px; border-radius: 3px; border: 1px solid var(--ink);
    background: var(--ink); color: var(--on-ink); box-shadow: inset 0 -3px 0 var(--volt);
    font: inherit; font-family: var(--font-ui); font-size: 14px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase; cursor: pointer;
}
.pd-sit-searchrow { padding: 12px 0 8px; }
.pd-sit-search {
    width: 100%; min-height: 44px; padding: 0 12px; border-radius: 3px;
    border: 1px solid var(--border-strong); background: var(--surface-elevated); color: var(--ink);
    font: inherit; font-family: var(--font-ui); font-size: 16px; font-weight: 600;
}
.pd-sit-search:focus { outline: none; border-color: var(--volt); }
.pd-sit-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; margin: 0 -18px; padding: 0 18px 4px; }
.pd-sit-group {
    padding: 14px 0 6px; font-family: var(--font-ui); font-size: 11px; font-weight: 700;
    letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink-faint);
}
.pd-sit-row {
    display: flex; align-items: center; gap: 12px; width: 100%; min-height: 48px; padding: 0 10px;
    border: 0; border-bottom: 1px solid var(--border); background: transparent; color: var(--ink);
    font: inherit; font-family: var(--font-ui); font-size: 16px; font-weight: 600; text-align: left;
    cursor: pointer; touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.pd-sit-row:hover { background: var(--hover, transparent); }
.pd-sit-check {
    flex: none; width: 22px; height: 22px; border-radius: 3px; border: 1.5px solid var(--border-strong);
    display: inline-flex; align-items: center; justify-content: center; color: transparent;
}
.pd-sit-row.active .pd-sit-check { background: var(--ink); border-color: var(--ink); color: var(--on-ink); box-shadow: inset 0 -2px 0 var(--volt); }
.pd-sit-row.active .pd-sit-lab { font-weight: 800; }
/* SIT3: SOLID, never dashed (Carlos's rule: no dashed outlines) */
.pd-sit-create { color: var(--ink); font-weight: 800; border: 1px solid var(--border-strong); border-radius: 3px; margin-top: 4px; }

/* SIT3 (Oct 13 2026): MANAGE — the same square check rows, flat type, solid
   1px hairlines, 44px+ targets. No switches, no pills, no thick edges. */
.pd-sit-headacts { display: flex; align-items: center; gap: 8px; }
.pd-sit-manage {
    min-height: 44px; padding: 0 14px; border-radius: 3px; border: 1px solid var(--border-strong);
    background: transparent; color: var(--ink); cursor: pointer;
    font: inherit; font-family: var(--font-ui); font-size: 14px; font-weight: 700; letter-spacing: 0.04em;
    touch-action: manipulation; -webkit-tap-highlight-color: transparent;
}
.pd-sit-manage.active { background: var(--ink); color: var(--on-ink); border-color: var(--ink); box-shadow: inset 0 -3px 0 var(--volt); }
.pd-sit-manage:focus-visible, .pd-sit-act:focus-visible, .pd-sit-btn:focus-visible, .pd-sit-link:focus-visible, .pd-sit-hiddenrow:focus-visible { outline: 2px solid var(--volt); outline-offset: 2px; }
.pd-sit-searchrow[hidden] { display: none; }
.pd-sit-foot { display: flex; align-items: center; flex-wrap: wrap; padding: 12px 0 4px; margin-top: 8px; border-top: 1px solid var(--border); font-family: var(--font-ui); font-size: 14px; font-weight: 600; color: var(--ink-muted); }
.pd-sit-link { min-height: 44px; padding: 0 4px; border: 0; background: none; color: var(--ink); font: inherit; font-weight: 800; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
.pd-sit-note { padding: 10px 0 2px; font-family: var(--font-ui); font-size: 14px; font-weight: 600; color: var(--ink-muted); }
.pd-sit-flash { color: var(--ink); font-weight: 800; }
.pd-sit-mhead { display: flex; align-items: baseline; gap: 10px; border-bottom: 1px solid var(--border-strong); }
.pd-sit-mhead b { color: var(--ink); font-weight: 800; }
.pd-sit-msub { text-transform: none; letter-spacing: 0; font-size: 13px; font-weight: 600; color: var(--ink-faint); }
.pd-sit-mn { margin-left: auto; text-transform: none; letter-spacing: 0; font-size: 13px; font-weight: 600; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.pd-sit-mrow { display: flex; align-items: center; gap: 8px; min-height: 48px; padding: 0 2px 0 10px; border-bottom: 1px solid var(--border); font-family: var(--font-ui); color: var(--ink); }
.pd-sit-mname { flex: 1 1 auto; min-width: 0; font-size: 16px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pd-sit-muses { flex: none; margin-left: auto; padding-left: 8px; font-family: var(--font-ui); font-size: 13px; font-weight: 600; color: var(--ink-faint); white-space: nowrap; font-variant-numeric: tabular-nums; }
.pd-sit-act { flex: none; min-height: 44px; min-width: 44px; padding: 0 8px; border: 0; background: none; color: var(--ink); cursor: pointer; font: inherit; font-family: var(--font-ui); font-size: 14px; font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }
.pd-sit-act--danger { color: var(--live); }
.pd-sit-act:disabled { color: var(--ink-faint); cursor: default; text-decoration: none; }
.pd-sit-cols { display: grid; grid-template-columns: 1fr; column-gap: 28px; }
.pd-sit-row.is-hidden .pd-sit-lab { color: var(--ink-muted); }
.pd-sit-hiddenrow { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 48px; margin-top: 14px; padding: 0 10px; border: 0; border-top: 1px solid var(--border-strong); border-bottom: 1px solid var(--border); background: none; color: var(--ink); cursor: pointer; font: inherit; font-family: var(--font-ui); font-size: 15px; font-weight: 800; text-align: left; }
.pd-sit-hiddenrow .pd-sit-chev { margin-left: auto; }
.pd-sit-edit { margin: 0 -10px; padding: 12px; border-bottom: 1px solid var(--border); background: var(--hover, transparent); }
.pd-sit-editin { width: 100%; min-height: 48px; padding: 0 12px; border-radius: 3px; border: 2px solid var(--ink); background: var(--surface-elevated); color: var(--ink); font: inherit; font-family: var(--font-ui); font-size: 17px; font-weight: 700; }
.pd-sit-editin:focus { outline: none; }
.pd-sit-edith { margin: 0; font-family: var(--font-ui); font-size: 17px; font-weight: 800; color: var(--ink); }
.pd-sit-editsay { padding: 10px 0 0; font-family: var(--font-ui); font-size: 15px; font-weight: 600; color: var(--ink-muted); }
.pd-sit-editsay b { color: var(--ink); font-weight: 800; }
.pd-sit-edit--danger .pd-sit-editsay b { color: var(--live); }
.pd-sit-editlist { padding: 6px 0 0; font-family: var(--font-ui); font-size: 13px; font-weight: 600; color: var(--ink-faint); }
.pd-sit-editmsg { padding: 10px 0 0; font-family: var(--font-ui); font-size: 15px; font-weight: 800; color: var(--ink); }
.pd-sit-editacts { display: flex; justify-content: flex-end; gap: 8px; padding-top: 12px; }
.pd-sit-btn { min-height: 44px; padding: 0 16px; border-radius: 3px; border: 1px solid var(--border-strong); background: transparent; color: var(--ink); cursor: pointer; font: inherit; font-family: var(--font-ui); font-size: 14px; font-weight: 800; letter-spacing: 0.04em; }
.pd-sit-btn--quiet { border-color: transparent; color: var(--ink-muted); }
.pd-sit-btn--go { background: var(--ink); color: var(--on-ink); border-color: var(--ink); box-shadow: inset 0 -3px 0 var(--volt); }
.pd-sit-btn--danger { background: var(--live); color: var(--on-ink); border-color: var(--live); }
.pd-sit-btn:disabled { opacity: 0.5; cursor: default; }
/* landscape: the card widens while managing, so the standard list sits in
   two columns and the coach sees the whole of it */
@media (orientation: landscape) and (min-width: 900px) {
    .pd-sit-modal .pd-sit-card:has(.pd-sit-mhead) { width: min(900px, 100%); max-height: 86vh; }
    .pd-sit-cols { grid-template-columns: 1fr 1fr; }
}
@media (orientation: portrait), (max-width: 700px) {
    .pd-sit-modal { padding: 0; }
    .pd-sit-modal .pd-sit-card { width: 100%; height: 100%; max-height: 100%; border-radius: 0; }
}
