/* ============================================================
   Nexus 2.0 — The Experience Lab design system
   Brand: orange #FF4000, charcoal "wet slate", yellow accent
   Type:  Bebas Neue (display) + Poppins (UI)
   ============================================================ */
:root{
  /* WCAG AA, measured 2026-07-17 (audit I1/I2/I3). TWO oranges — which one you want depends
     on whether small text touches it:
       --orange     = the BRAND MARK. Logo, nav accent, borders, focus rings, and big display
                      type (the 40px job badge clears AA-large at 3.15). A white label on it is
                      only 3.15, under the 4.5 AA floor — so NEVER put small text on it.
       --orange-600 = the INTERACTIVE orange. TEL "Red Ochre" #BC5026 nudged to #B44A22 so it
                      clears 4.5 on the page bg too, not just on cards. White on it = 5.33; as
                      text 4.85 on --bg / 5.33 on --card / 4.70 on --orange-50. Button fills
                      carrying a white label, and orange text/links, use THIS one.
     Greg supplied the brand palette 2026-07-17. #D46536 "Spicy Paprika" reads like a primary-
     button colour but fails as one (white on it = 3.68) — don't reach for it. Full palette map
     + ratios: memory `tel-brand-orange-fails-wcag-aa`. */
  --orange:#F16622; --orange-600:#B44A22; --orange-700:#953C1C; --orange-50:#fdeee5;
  --yellow:#FFC400;
  /* --ink = TEL "Shadow Grey", the brand black. 14.90; was #222526 at 14.07 — same readability. */
  --ink:#221F20; --slate:#3a3d3e; --slate-2:#5b6770; --grey:#888686;
  --line:#e9e6e4; --line-2:#f1eeec;
  /* --muted was #8a8785 = 3.25, FAILING AA while carrying nearly every field hint on the booking
     form. #6f6c6a = 4.75 on --bg / 5.21 on --card. TEL "Grey" #787777 is NOT enough (4.07) — it
     looks like the obvious muted colour and still fails. */
  --bg:#f6f4f2; --card:#ffffff; --muted:#6f6c6a;
  /* --good was #2e9e5b (3.41, fail). --bad was #e53935 (4.23, fail) — and was LESS accessible
     than the #c62828 templates kept hand-typing, so the token moved TO the literal, not the
     other way round. --warn is a fill/border only; never put small text on it. */
  --good:#237a46; --warn:#f0a300; --bad:#c62828;
  /* Added 2026-09-12 (audit DS-1) — the states templates had been hand-typing for months:
       --amber-600/-50  the "check this / not yet / awaiting" pill. Was goldenrod #b8860b
                        (45 uses) — 3.6:1 on white, fails AA as text. #8a5a00 = 6.8:1.
       --purple/-50     notifications, "Offered", action-needed. Was #7c4dff (4.4:1, fails
                        by a hair). #5b3fd6 = 6.3:1 on white.
       --pink           birthdays. Was #d8638f — white on it 3.3:1, fails as a button fill.
                        #b8477a = 5.3:1. views.py's BIRTHDAY_COLOUR carries the same value
                        for FullCalendar, which can't read a CSS var.
     --good-50 / --bad-50 are the matching tints for green/red pills, replacing the
     #e6f6ec / #fde7e7 literals. */
  --amber-600:#8a5a00; --amber-50:#fff6e0;
  --purple:#5b3fd6; --purple-50:#efeafc;
  --pink:#b8477a;
  --blue-600:#1d5fa6; --blue-50:#e3eefb;   /* calm notice pill (travel time between jobs) */
  --tab-idle:#efece7;   /* resting tab on the booking page + run sheet (DS-4) */
  --good-50:#e6f6ec; --bad-50:#fde7e7;
  /* .pill.muted's fill: --muted on it is 4.63:1 (AA; the old #f0eeec was 4.50, on the
     line). Never darken it. */
  --pill-muted-bg:#f3f1ef;
  /* The booking page's per-tab panel tints (Logistics / Production / Financial / Feedback /
     Record). They existed only as five hexes inside booking_detail.html; named here
     (2026-09-12, audit DS-4) so any other surface that colour-codes the same five areas
     reaches for the same values. Fills only — never small text on them. */
  --tint-logistics:#d6e6fa; --tint-production:#d8f0da; --tint-financial:#e6d7f5;
  --tint-feedback:#fce0b8; --tint-record:#dcdcdc;
  --radius:16px; --radius-sm:11px;
  --shadow:0 1px 2px rgba(20,18,16,.04), 0 8px 24px rgba(20,18,16,.06);
  --shadow-lg:0 12px 40px rgba(20,18,16,.14);
  /* iPhone home-screen app: base.html sets viewport-fit=cover, so the page runs up under
     the clock and camera. --safe-top is that strip (0 in a browser tab and on desktop).
     --header-h INCLUDES it, so every sticky/fixed offset below the bar stays right. */
  --safe-top:env(safe-area-inset-top,0px);
  --nav-w:230px; --header-h:calc(62px + var(--safe-top));
  /* Height the mobile save rail reserves once it goes position:fixed (see #160). One row of
     buttons + its padding; kept here so the bar and the space it occupies can't drift apart. */
  --saverail-h:54px;
  --sans:'Poppins',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  --display:'Bebas Neue','Poppins',sans-serif;
  /* Added 2026-10-19 (design-system cleanup): every hex that was hand-typed 3+ times
     outside :root got ONE name here, at its exact value, so the swap changed no pixels.
     Two retired colours were moved onto their token on purpose: #7c4dff (annotation
     purple, fails AA) -> --purple, #b8860b (goldenrod, fails AA as text) -> --amber-600. */
  --white:#fff;   /* text, icons and rings ON a coloured fill (a surface uses --card) */
  --surface-2:#fbfaf9; /* quiet panel / table-head fill, one step off --card */
  --surface-3:#faf8f6; /* second quiet fill (hover rows, inset boxes) */
  --wash:#fdf6f1; /* warm orange wash (kit grid booked cells) */
  --ink-2:#23211f; /* near-ink used by chip menus and pills */
  --grey-light:#ccc; /* neutral chip / divider default */
  --grey-cool:#9aa0a6; /* neutral status (no state yet) */
  --good-2:#2e7d32; /* secondary green (log-check ticks) */
  --good-tint:#e7f4ec; /* green row/cell tint */
  --bad-700:#a41d1d; /* dark red text on a red tint */
  --bad-tint:#fbeaea; /* red row/cell tint */
  --bad-tint-2:#fdecea; /* red cell tint (kit grid) */
  --amber-400:#d99a06; /* amber fill / edge (not for text) */
  --nav-hover:#2c2f30; /* dark sidebar: hover / user box */
  --nav-raised:#34373a; /* dark sidebar: raised row + its borders */
  --nav-edge:#46494b; /* dark sidebar: scrollbar, badge, rules */
  --nav-text:#b9b5b1; /* dark sidebar: link text */
  --nav-muted:#9a9692; /* dark sidebar: secondary text */
  /* Calendar/session category colours (edge + its pale fill), from .ml-cat-*. */
  --cat-office:#f0a85c; --cat-office-50:#fdf5eb; --cat-roleplay:#9c6b3f; --cat-roleplay-50:#ece3d9;
  --cat-meeting:#b07bbd; --cat-meeting-50:#f6eff7; --cat-delivery:#5fae84; --cat-delivery-50:#ecf5f0;
  --cat-filming:#3f87c9; --cat-filming-50:#e8f1f9; --cat-cancelled:#d64545; --cat-cancelled-50:#fae9e9;
}
/* ============================================================
   SPACING / TYPE / BREAKPOINT TOKENS — roadmap I4/I6/I7, added 2026-07-19.
   Additive only — nothing above this block changed. Derived from what this
   file actually uses (counted first, scaled second), not invented from
   theory. Adopted into tel.css itself immediately below (equivalent
   substitutions only — this pass changes zero rendered pixels); anything
   that didn't fit the scale was left hardcoded on purpose rather than
   rounded onto the nearest step.
   ============================================================ */
:root{
  /* --- SPACING ---------------------------------------------------------
     323 padding/margin/gap declarations in this file before this pass.
     Raw px-number frequency across all of them (each shorthand value
     counted separately, e.g. "padding:14px 16px" counts both 14 and 16):
       8px:52  10px:51  6px:49  12px:49  14px:46  4px:28  16px:26  9px:25
       7px:24  3px:19  18px:19  2px:18  11px:16  5px:14  1px:11  22px:4
       20px:4  15px:3  13px:3  26px:2  34px:1  24px:1
     This file was never built on an 8pt (or any) grid — odd integers
     (5/7/9/11px…) run just as often as even ones, so there's no clean
     ratio to derive. Instead: one named step per raw value used >=4
     times (covers 313 of the ~360 raw numbers above). 13px/15px/24px/
     26px/34px are real but too rare (<=3 uses) to justify a token —
     left hardcoded rather than force-fit onto a neighbouring step. */
  --space-1:1px;  --space-2:2px;  --space-3:3px;  --space-4:4px;
  --space-5:5px;  --space-6:6px;  --space-7:7px;  --space-8:8px;
  --space-9:9px;  --space-10:10px; --space-11:11px; --space-12:12px;
  --space-14:14px; --space-16:16px; --space-18:18px; --space-20:20px;
  --space-22:22px;
  /* Added 2026-10-19: raw values used 3+ times in margin/padding/gap. 13/15 sit
     between steps on purpose (they are what the rules use); 26/30 extend the top. */
  --space-13:13px; --space-15:15px; --space-26:26px; --space-30:30px;
  /* .content's live left/right inset — redeclared alongside .content's own padding
     override at <=520px below, so anything reading this var stays in lockstep with
     whatever .content's padding actually is at the current breakpoint. */
  --content-pad:var(--space-22);
  /* Extra RIGHT-only clearance so nothing on the booking page runs under .chk-rail's
     traffic-light dots (position:fixed, 38px, pinned to the viewport's right edge — see
     tel.css's own "Progress checklist" section). Added on top of --content-pad by
     `.content:has(.chk-rail)` (below) — the ONE place this is reserved, now that
     `.content` itself is the full-bleed box every section of the booking page (Sessions,
     Logistics, Production, Financial…) sits inside as a plain child. */
  --rail-clear:56px;

  /* --- TYPE --------------------------------------------------------
     201 font-size declarations before this pass, running 9px-48px in
     near-duplicate .5 steps (9/9.5, 10/10.5, 11/11.5, 12/12.5, 13/13.5,
     14/14.5) that read as accidental drift, not deliberate sizes.
     Collapsed to one named step per whole pixel, counting the .5 variant
     into its integer neighbour:
       10px:13(+10 as 10.5)  11px:24(+16 as 11.5)  12px:31(+22 as 12.5)
       13px:32(+9 as 13.5)   14px:9(+1 as 14.5)     16px:2   18px:4
       20px:4   24px:3   32px:1   40px:2   48px:1
     Only the WHOLE-pixel values get a token; the .5 variants and other
     one-off odd sizes (15/17/22/23/26/30/34px) are real but not reused
     enough to name — left hardcoded, not rounded onto this scale. */
  --text-10:10px; --text-11:11px; --text-12:12px; --text-13:13px;
  --text-14:14px; --text-16:16px; --text-18:18px; --text-20:20px;
  --text-24:24px; --text-32:32px; --text-40:40px; --text-48:48px;
  /* Small uppercase LABELS (table heads, field labels, section sub-heads): one
     tracking value, weight 600, --text-11/12, --muted. Display-face headings
     (var(--display)) keep their own spacing. Added 2026-10-19 (13 values -> 1). */
  --caps-track:.05em;

  /* --- BREAKPOINTS (documentation + a target for future rules ONLY) ----
     ⚠ CSS custom properties DO NOT resolve inside an @media query
     condition — `@media(max-width:var(--bp-phone))` is invalid CSS and
     silently fails to apply. These three vars are therefore NOT
     substituted into any @media rule below; they exist so a *future*
     rule has a named value to reach for. NORMALISED 2026-09-12 (audit
     RM-16): the 11 raw widths became four tiers — 600 (phone), 700
     (large phone: base.html's mobile font/fold rules), 900 (tablet),
     1180 (narrow desktop). 520/640 → 600, 720 → 700, 820/960 → 900.
     New rules use one of those four and nothing else. The old map:
       Phone   (<=~720px):   520, 600, 601*, 640, 700, 720
       Tablet  (~820-960px): 820, 900, 901*, 960
       Desktop (>960px):     1180
     (* 601 and 901 are the min-width complements of the 600/900
     max-width splits — same boundary, written from the other side.) */
  --bp-phone:600px; --bp-tablet:900px; --bp-desktop:1180px;
}
*{box-sizing:border-box}
html,body{margin:0;padding:0}
/* Every jump to a spot on the page (#anchor links, scrollIntoView, the section shortcuts)
   lands BELOW the sticky top bar, not behind it (Greg, 2026-10-01). One rule for the whole
   app — so a per-element scroll-margin-top now only adds what is extra (e.g. a sticky save
   bar); it must not repeat the header height, or the two add up. Below 1360px the booking,
   run sheet and actor job pages also have the 44px .bk-strip stuck under the bar. */
html{scroll-padding-top:calc(var(--header-h) + var(--space-16))}
@media (max-width:1359px){
  html:has(.bk-strip){scroll-padding-top:calc(var(--header-h) + 44px + var(--space-16))}
}
svg.ic{width:18px;height:18px;flex:0 0 auto}
.search svg{width:16px;height:16px}
.btn svg.ic{width:15px;height:15px}
.profile .meta svg.ic,.crumb svg.ic{width:14px;height:14px;vertical-align:-2px}
.bk .meta svg.ic{width:13px;height:13px;vertical-align:-2px}
/* Phase C1 (18 Oct 2026, docs/UX-RULES.md): the emoji left the interface, so icons now sit
   inside running text and pills. `.pill svg.ic` keeps a pill's glyph at pill size; `.ict`
   wraps an icon that must read at the size of the text around it; a `.chev` that used to
   hold a typed ▶ now holds the `chev` icon (drawn pointing down) turned to point right, and
   the accordion's existing `[open] .chev{rotate(90deg)}` turns it back down. `.dchev` is
   the small down chevron at the end of a summary's words ("Run sheet PDF ▾" before); it
   flips while its <details> is open. */
.pill svg.ic{width:12px;height:12px;flex:none}
.ict{display:inline-flex;align-items:center;vertical-align:-.15em}
.ict svg.ic{width:1em;height:1em;flex:none}
.chev>svg.ic{width:1em;height:1em;display:block;transform:rotate(-90deg)}
.dchev{display:inline-flex;align-items:center;vertical-align:-.1em;margin-left:var(--space-2)}
.dchev svg.ic{width:.95em;height:.95em;transition:transform .15s}
details[open]>summary .dchev svg.ic{transform:rotate(180deg)}
.hamb svg{width:20px;height:20px}
body{
  font-family:var(--sans); color:var(--ink); background:var(--bg);
  font-size:var(--text-14); line-height:1.5; -webkit-font-smoothing:antialiased;
}
a{color:inherit;text-decoration:none}
h1,h2,h3{margin:0;font-weight:600;letter-spacing:-.01em}
/* Bebas Neue ships ONE weight, so a bold heading in it (every <h2 class="sec-title">) was
   the browser's fake bold, smeared and heavy (Greg, 2026-09-13: "all of the Bebas Neue font
   headers should not be in bold"). No faked weights anywhere: Poppins has its real
   400/500/600/700 loaded, so only Bebas is affected. iOS 12 ignores this (the wall iPad). */
body{font-synthesis-weight:none}

/* ---------- Layout shell ---------- */
.shell{display:flex;min-height:100vh}
.side{
  width:var(--nav-w);flex:0 0 var(--nav-w);background:var(--ink);color:#cfccc9;
  position:sticky;top:0;height:100vh;display:flex;flex-direction:column;gap:var(--space-4);
  padding:var(--space-18) var(--space-14);z-index:30;
}
.brand{display:flex;align-items:center;gap:var(--space-10);padding:var(--space-10) var(--space-8) var(--space-16);flex:0 0 auto}
/* Annotation #159 — the phone drawer's close X. Hidden by default: on desktop the sidebar
   is permanent and there's nothing to close (it's turned on in the <=600px block). */
.nav-x{display:none}
.brand img{height:30px;width:auto;max-width:100%}
.nav{display:flex;flex-direction:column;gap:var(--space-2);margin-top:var(--space-2);flex:1 1 auto;min-height:0;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--nav-edge) transparent}
/* Rows never shrink — the nav scrolls instead. The collapsed rail gives rows overflow:hidden,
   which drops their flex min-height to 0, so on a tablet an opened (peek) menu with every group
   listed inline squashed each row to ~22px, text overlapping (found 2026-09-14). */
.nav>*{flex-shrink:0}
.nav::-webkit-scrollbar{width:6px}
.nav::-webkit-scrollbar-thumb{background:var(--nav-edge);border-radius:3px}
.nav::-webkit-scrollbar-track{background:transparent}
/* group header rows read exactly like the nav links above them (uniform text/size/colour) */
.nav-sec{display:flex;align-items:center;gap:var(--space-11);padding:var(--space-11) var(--space-12);border-radius:11px;
  font-size:var(--text-14);font-weight:500;color:var(--nav-text);text-transform:none;letter-spacing:0;transition:.12s}
.nav-sec .ic{width:18px;height:18px;flex:0 0 18px;opacity:.85}
.userbox{display:flex;align-items:center;gap:var(--space-10);padding:var(--space-10);border-radius:12px;background:var(--nav-hover);margin-bottom:var(--space-8)}
.userbox:hover{background:var(--nav-raised)}
.userbox .ub-meta{flex:1;min-width:0;line-height:1.25}
.userbox .ub-meta b{color:var(--white);font-size:var(--text-13);display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.userbox .ub-meta small{color:var(--nav-muted);font-size:var(--text-11)}
.userbox .ic{width:16px;height:16px;color:var(--nav-muted)}
/* Your name → a small pop-up menu (your own pages + Sign out), opening upward over the nav.
   A <details>; base.html closes it on an outside tap / Esc. Menu regroup, 2026-09-14. */
.userbox-menu{position:relative}
.userbox-menu>summary{list-style:none;cursor:pointer}
.userbox-menu>summary::-webkit-details-marker{display:none}
.userbox-menu[open]>.userbox{background:var(--nav-raised)}
.ub-pop{position:absolute;left:0;bottom:calc(100% + var(--space-6));width:100%;min-width:220px;
  background:var(--ink);border:1px solid var(--nav-raised);border-radius:12px;padding:var(--space-6);
  box-shadow:var(--shadow-lg);z-index:60}
.ub-pop a{display:flex;align-items:center;gap:var(--space-11);padding:var(--space-9) var(--space-12);
  border-radius:9px;color:var(--nav-text);font-weight:500;font-size:var(--text-14);white-space:nowrap}
.ub-pop a:hover{background:var(--nav-hover);color:var(--white)}
.ub-pop a.on{background:var(--orange-600);color:var(--white)}
.ub-pop .ic{width:18px;height:18px;flex:0 0 18px;opacity:.85}
.ub-head{font-size:var(--text-10);text-transform:uppercase;letter-spacing:.08em;color:#8a8683;
  font-weight:600;padding:var(--space-5) var(--space-12) var(--space-6)}
.ub-sep{height:1px;background:var(--nav-raised);margin:var(--space-6) var(--space-4)}
.nav a{
  display:flex;align-items:center;gap:var(--space-11);padding:var(--space-11) var(--space-12);border-radius:11px;
  color:var(--nav-text);font-weight:500;transition:.12s;
}
.nav a .ic{width:18px;height:18px;flex:0 0 18px;opacity:.85}
.nav a.on{background:var(--orange-600);color:var(--white)}
.nav a.on .ic{opacity:1}
/* Count badge on a nav row (Notifications, Drafts, Suggested folders): `.badge.nav-badge`,
   defined with the other markers under "Bits". `.badge[hidden]` hides one that live-updates
   to zero (annotations inbox). */
/* Notifications with anything unread: purple + a slow pulse, in every sidebar state. This
   is the ONLY notifications control since the top-bar bell went (2026-09-14) — see
   _nav_notifications.html. On a phone the menu is shut, so .hamb carries it (below). */
.nav a.nav-notif{position:relative}
.nav a.nav-notif.has-unread{color:var(--white);font-weight:600;animation:telNotifPulse 1.8s ease-in-out infinite}
.nav a.nav-notif.has-unread:not(.on){background:var(--purple)}
.nav a.nav-notif.has-unread .ic{opacity:1}
.nav a.nav-notif.has-unread .nav-badge{background:var(--card);color:var(--purple)}
@keyframes telNotifPulse{
  0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--purple) 60%,transparent)}
  50%{box-shadow:0 0 0 7px color-mix(in srgb,var(--purple) 0%,transparent)}}
.hamb{position:relative}
/* No tap flash on the menu button or its ✕ (Greg, 2026-09-23: "I don't mind the little blue
   state that happens when you tap things on mobile, except it needs to not happen on the
   hamburger menu because it's still showing as the menu appears and it looks like a really
   bad animation"). Android paints that highlight for a beat AFTER the touch ends, and the
   drawer takes 200ms to slide in, so the two overlap and the square of blue rides in with the
   menu. Everything else on the site keeps its tap feedback — this is the one control whose
   own animation collides with it.
   `:focus:not(:focus-visible)` goes with it: a tap also leaves the button focused, so without
   this the ring simply replaces the flash. Keyboard focus (`:focus-visible`) is untouched. */
.hamb,.nav-x{-webkit-tap-highlight-color:transparent}
.hamb:focus:not(:focus-visible),.nav-x:focus:not(:focus-visible){outline:none}
.hamb.has-unread{border-color:var(--purple);color:var(--purple);animation:telNotifPulse 1.8s ease-in-out infinite}
/* The phone menu button's unread count is `.badge.hamb-badge` (see "Bits"). */
@media(prefers-reduced-motion:reduce){.nav a.nav-notif.has-unread,.hamb.has-unread{animation:none}}
.side .spacer{flex:0 0 8px}
.side .foot{font-size:var(--text-11);color:#6f6b67;padding:var(--space-8);border-top:1px solid #34373880;line-height:1.6}
.side .foot a{color:var(--nav-muted);text-decoration:underline}

/* ---------- Collapsible sidebar (icon-only rail) ---------- */
/* Body gets .nav-mini (from JS in base.html) when: desktop toggle/auto-collapse, OR
   tablet (601-900px, always a rail). Phone (<=600px) uses the off-canvas hamburger
   instead, so this whole block is gated to >600px and never fights that nav.
   In mini mode the rail is a FIXED overlay (main reserves a 68px gutter) so that
   expanding it — hover on desktop, tap on touch — floats over content, no reflow. */
.grp-head{display:none}   /* only shown inside the pointer-mini group pop-out panel */
@media(min-width:601px){
  /* --- collapsed rail (icons only) --- */
  body.nav-mini{--nav-w:68px}
  body.nav-mini .side{position:fixed;top:0;left:0;height:100vh;width:68px;
    padding-left:var(--space-10);padding-right:var(--space-10);overflow:visible;z-index:40;
    transition:width .18s ease}
  body.nav-mini .main{margin-left:68px}
  body.nav-mini .brand{display:none}   /* no logo in the collapsed rail */
  /* justify-content:flex-start + a fixed padding-left, not justify-content:center — Greg,
     2026-09-04: centering means the icon's x-position depends on the ROW's total width,
     so the hover-expand below (which widens the row 68px→230px) dragged the icon sideways
     with it, on top of an instant snap the moment :hover matched (justify-content can't be
     transitioned, so it jumped straight to its new alignment before the width even started
     animating) — together they read as a shake/settle. Left-anchored with a padding-left
     that never changes between collapsed and hover means the icon sits at the exact same
     x always; only the row's right edge grows to make room for the label. --space-14 lands
     within a couple px of where centering used to put it, so the collapsed rail still
     reads the same at rest. */
  body.nav-mini .nav a,
  body.nav-mini .nav .nav-sec{display:flex;align-items:center;justify-content:flex-start;
    gap:0;font-size:0;white-space:nowrap;overflow:hidden;
    padding-left:var(--space-14);padding-right:0;list-style:none}
  body.nav-mini .nav .nav-sec{color:var(--nav-text)}   /* match nav-link icon brightness */
  body.nav-mini .nav .nav-sec .ic{width:18px;height:18px;opacity:.85}
  /* Symmetric — Greg, 2026-09-04: the old 12/4 split visibly pushed every group-header
     icon (Bookings, Inbox, Team, Directory, Reports, Support/Report, Manage) 4px low in
     its collapsed-rail pill; showed up clearest on Bookings' orange active highlight,
     but it was every group header, not just that one. Same total (16px) so the pill's
     own height is unchanged — just centered now. */
  body.nav-mini .nav-sec{padding-top:var(--space-8);padding-bottom:var(--space-8)}
  body.nav-mini .userbox{justify-content:center;padding:var(--space-10) 0}
  body.nav-mini .userbox .ub-meta{display:none}
  body.nav-mini .userbox>.ic{display:none}
  body.nav-mini .side .foot{display:none}
  /* group children hidden in the rail — pointer shows them as a hover pop-out,
     touch reveals them inline when the rail is expanded (peek) */
  body.nav-mini .nav-group>.grp-items{display:none}
  /* The rail hides labels (font-size:0), so a top-level count badge (Notifications) sits on
     the icon's top-right corner instead of trailing a label nobody can see. It goes back
     inline wherever the rail opens up — touch peek below, pointer hover further down. */
  body.nav-mini .nav>a{position:relative}
  body.nav-mini .nav>a .nav-badge{position:absolute;top:3px;left:calc(var(--space-14) + 9px);
    min-width:16px;height:16px;padding:0 var(--space-4)}
  body.nav-mini.nav-peek .side .nav>a .nav-badge{position:static;min-width:18px;height:18px;padding:0 var(--space-5)}

  /* --- TOUCH: tap expands the whole rail (peek); tap outside / Esc collapses.
         (Floating labels below need hover, which touch lacks, so touch gets the
         full slide-out instead.) --- */
  body.nav-mini.nav-peek .side{width:230px;box-shadow:var(--shadow-lg)}
  body.nav-mini.nav-peek .side .brand{display:flex;justify-content:flex-start;padding:var(--space-10) var(--space-8) var(--space-16)}   /* peek → wordmark back */
  body.nav-mini.nav-peek .side .brand img{height:30px}
  body.nav-mini.nav-peek .side .nav a,
  body.nav-mini.nav-peek .side .nav .nav-sec{justify-content:flex-start;gap:var(--space-11);
    font-size:var(--text-14);padding-left:var(--space-12);padding-right:var(--space-12)}
  /* Group headers at full size (was 10.5px): with the accordion they are the thing you tap. */
  body.nav-mini.nav-peek .side .nav .nav-sec{font-size:var(--text-14);padding-left:var(--space-12)}
  body.nav-mini.nav-peek .side .userbox{justify-content:flex-start;padding:var(--space-10)}
  body.nav-mini.nav-peek .side .userbox .ub-meta{display:block}
  body.nav-mini.nav-peek .side .userbox>.ic{display:block}
  body.nav-mini.nav-peek .side .foot{display:block}
  /* reveal sub-items inline — hanging-indented under their header with a rule down the left,
     same as the phone drawer (2026-09-14), so they read as that header's submenu */
  /* ACCORDION (Angela's iPad, 1 Oct 2026): only the tapped group (.tel-open, set by the
     nav-group script in base.html) shows its sub-items. Showing every group at once made one
     very long list. Header lights up + chevron, same as the phone drawer. */
  body.nav-mini.nav-peek .side .nav-group.tel-open>.grp-items{display:block;
    margin:var(--space-2) 0 var(--space-6) calc(var(--space-12) + var(--space-8));
    padding-left:var(--space-8);border-left:2px solid var(--nav-edge)}
  body.nav-mini.nav-peek .side .nav-group>.nav-sec{position:relative;cursor:pointer}
  body.nav-mini.nav-peek .side .nav-group.tel-open>.nav-sec{color:var(--white)}
  body.nav-mini.nav-peek .side .nav-group.tel-open>.nav-sec:not(.on){background:var(--nav-hover)}
  body.nav-mini.nav-peek .side .nav-group>.nav-sec::after{content:'';position:absolute;right:12px;top:50%;
    width:7px;height:7px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;
    transform:translateY(-50%) rotate(-45deg);opacity:.55;transition:transform .15s}
  body.nav-mini.nav-peek .side .nav-group.tel-open>.nav-sec::after{transform:translateY(-50%) rotate(45deg)}
  body.nav-mini.nav-peek .scrim{position:fixed;inset:0;background:rgba(0,0,0,.28);z-index:35;display:block}

  /* --- POINTER: hovering anywhere on the rail expands the WHOLE sidebar (labels next
         to every icon, like the old full menu) instead of showing one tooltip at a time —
         Greg, 2026-09-04. Pure CSS :hover on .side, no JS: grows on mouseenter, folds back
         on mouseleave. .side is position:fixed and floats OVER content (.main's margin
         never changes), same as the touch peek state above.
         The brand logo stays HIDDEN here (unlike touch peek) so it can't add height above
         the icon rows — that's exactly what made the icons jump down on expand. Icon
         x-position is pinned above (justify-content:flex-start + a constant padding-left
         on the base collapsed rule, unchanged here) — this block only grows padding-right
         and reveals the label, never touches left/top/bottom padding, so nothing shifts
         in any direction, it just grows to the right of where the icon already sits.
         Group headers still only reveal their children via their OWN hover flyout below —
         "hover over each one for sub menus" — the expand just makes every top-level icon's
         label readable without that per-icon step. --- */
  @media(hover:hover){
    body.nav-mini .nav{overflow:visible}                 /* let flyouts escape the rail */
    body.nav-mini .side:hover{width:230px;box-shadow:var(--shadow-lg)}
    /* line-height:18px pins the now-visible label's text line box to the icon's own
       height (.ic is 18x18) — without it the label's line box (bigger than the icon at
       a real font-size, where the collapsed rail's font-size:0 made it 0) is the taller
       child, growing the row and compounding down the list. */
    body.nav-mini .side:hover .nav a,
    body.nav-mini .side:hover .nav .nav-sec{gap:var(--space-11);
      font-size:var(--text-14);line-height:18px;padding-right:var(--space-12)}
    body.nav-mini .side:hover .nav>a .nav-badge{position:static;min-width:18px;height:18px;padding:0 var(--space-5)}
    body.nav-mini .side:hover .userbox{justify-content:flex-start;padding:var(--space-10)}
    body.nav-mini .side:hover .userbox .ub-meta{display:block}
    body.nav-mini .side:hover .userbox>.ic{display:block}
    body.nav-mini .side:hover .foot{display:block}
    /* collapsible groups → pop-out submenu panel listing the child links */
    body.nav-mini .nav-group{position:relative}
    body.nav-mini .nav-group>.grp-items{
      position:absolute;left:100%;top:0;margin-left:var(--space-10);min-width:190px;display:block;
      background:var(--ink);border:1px solid var(--nav-raised);border-radius:12px;padding:var(--space-6);
      box-shadow:var(--shadow-lg);opacity:0;visibility:hidden;transform:scale(.96);
      transform-origin:left top;z-index:50;max-height:calc(100vh - var(--space-22));overflow-y:auto;
      transition:opacity .13s ease,transform .13s ease,visibility 0s .13s}
    /* Annotation #254 (2026-08-24) — the LAST group ("Manage") sits at the bottom of the
       rail, so its flyout anchored top:0 could trail off the bottom of a short viewport.
       Anchor it to the bottom of the trigger instead so it grows upward, staying on-screen. */
    body.nav-mini .nav-group:last-of-type>.grp-items{top:auto;bottom:0;transform-origin:left bottom}
    body.nav-mini .nav-group>.grp-items::before{content:"";position:absolute;
      left:-12px;top:0;bottom:0;width:12px}       /* invisible bridge across the gap */
    body.nav-mini .nav-group:hover>.grp-items{opacity:1;visibility:visible;
      transform:scale(1);transition-delay:0s}
    body.nav-mini .nav-group>.grp-items>a{justify-content:flex-start;gap:var(--space-11);
      font-size:var(--text-14);padding:var(--space-9) var(--space-12);white-space:nowrap;overflow:visible}
    body.nav-mini .grp-head{display:block;font-size:var(--text-11);text-transform:uppercase;
      letter-spacing:.08em;color:#8a8683;font-weight:600;padding:var(--space-5) var(--space-12) var(--space-6)}
  }

}

/* Full (wide, non-collapsed) menu: keep groups compact — only the section header
   shows — and reveal their children as a hover pop-out, same as the rail, so the
   top-level items are never pushed down/off. Phone drawer (hover:none) is excluded
   and keeps children inline. */
@media(hover:hover) and (min-width:901px){
  body:not(.nav-mini) .nav{overflow:visible}      /* let the flyout escape the nav scroll box */
  body:not(.nav-mini) .nav-group{position:relative}
  body:not(.nav-mini) .nav-group>.grp-items{
    position:absolute;left:100%;top:0;margin-left:var(--space-10);min-width:190px;
    background:var(--ink);border:1px solid var(--nav-raised);border-radius:12px;padding:var(--space-6);
    box-shadow:var(--shadow-lg);opacity:0;visibility:hidden;transform:scale(.96);
    transform-origin:left top;z-index:50;max-height:calc(100vh - var(--space-22));overflow-y:auto;
    transition:opacity .13s ease,transform .13s ease,visibility 0s .13s}
  /* Annotation #254 — same bottom-anchor fix as the mini-rail block above, for the wide nav. */
  body:not(.nav-mini) .nav-group:last-of-type>.grp-items{top:auto;bottom:0;transform-origin:left bottom}
  body:not(.nav-mini) .nav-group>.grp-items::before{content:"";position:absolute;
    left:-12px;top:0;bottom:0;width:12px}          /* invisible hover bridge across the gap */
  body:not(.nav-mini) .nav-group:hover>.grp-items{opacity:1;visibility:visible;
    transform:scale(1);transition-delay:0s}
  body:not(.nav-mini) .nav-group>.grp-items>a{white-space:nowrap}
  body:not(.nav-mini) .nav-group:hover>.nav-sec{background:var(--nav-hover);color:var(--white)}   /* header feedback on hover */
}

.main{flex:1;min-width:0;display:flex;flex-direction:column}
.topbar{
  height:var(--header-h);display:flex;align-items:center;gap:var(--space-14);padding:var(--safe-top) var(--space-22) 0;
  background:rgba(246,244,242,.85);backdrop-filter:saturate(1.4) blur(8px);
  border-bottom:1px solid var(--line);position:sticky;top:0;z-index:20;
}
.topbar h1{font-size:var(--text-18)}
.topbar .crumb{color:var(--muted);font-size:var(--text-13);font-weight:500}
.search{flex:0 0 auto;max-width:none;min-width:0;margin-left:auto;position:relative}
.search input{
  width:100%;border:1px solid var(--line);background:var(--card);border-radius:11px;
  padding:var(--space-9) var(--space-12) var(--space-9) 34px;font-family:inherit;font-size:var(--text-14);color:var(--ink);
}
.search input:focus{outline:none;border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-50)}
.search svg{position:absolute;left:11px;top:50%;transform:translateY(-50%);opacity:.45}
/* #64/#297: the topbar search collapses to just its icon button (base.html #tel-search-toggle)
   at every screen size — Greg, annotation #297 (2026-08-27): a long page title/breadcrumb (run
   sheets especially) was getting cramped by the always-open search box. Click the icon
   (telSearchToggle() in base.html) to expand it into a full-width row across the topbar with
   the cursor already in the input; click away or Escape closes it. Started phone-only (#64,
   the @media(max-width:600px) block below used to hold this); widened to every width for #297
   rather than building a second collapse mechanism. */
#tel-search-toggle{position:static;width:38px;height:38px;border-radius:10px;
  border:1px solid var(--line);background:var(--card);display:flex;align-items:center;
  justify-content:center;cursor:pointer;flex-shrink:0}
#tel-search-toggle svg{position:static;left:auto;top:auto;transform:none}
#tel-search-input{display:none}
/* Open state: a ✕ inside the bar's own right edge closes it, replacing the toggle icon
   rather than sitting alongside it — Greg, 2026-09-08: two search-shaped controls side by
   side read as one too many once the bar itself is right there. */
#tel-search-close{display:none;position:absolute;right:6px;top:50%;transform:translateY(-50%);
  width:26px;height:26px;border:none;background:none;border-radius:7px;cursor:pointer;
  font-size:19px;line-height:1;color:var(--muted)}
#tel-search-close:hover{background:var(--line-2)}
body.search-open .hamb,body.search-open #tel-back,
body.search-open .topbar>div,body.search-open .topbar>a,body.search-open #tel-search-toggle{display:none}
body.search-open .search{display:flex;align-items:center;gap:var(--space-8);flex:1 1 auto;max-width:none}
body.search-open #tel-search-input{display:block;flex:1;padding-left:var(--space-12);padding-right:34px}
body.search-open #tel-search-close{display:block}

/* Topbar button group that keeps to ONE line and drops to a row of its own under the title
   when it doesn't fit beside it (Greg, 8 Oct 2026, the play page: squeezed, the buttons
   folded into a 2x2 block and "+ Client version" broke across lines). Put the page's
   buttons in one `<span class="tb-actions">` in {% block topbar_extra %}.
   A container query on the bar, not plain flex-wrap: wrapping alone dropped whichever item
   ran out of room — the search icon on a row by itself, the phone menu button under the
   buttons. Below the width the group needs beside a readable title, the WHOLE group moves
   to a full row of its own (order + 100% basis) and the title, search and menu stay up top.
   Inline-size containment makes the bar a containing block for fixed-position children;
   nothing in the bar is fixed, and only bars holding a .tb-actions get it. */
.tb-actions{display:flex;align-items:center;gap:var(--space-8);flex:0 0 auto}
.tb-actions .btn{white-space:nowrap}
.topbar:has(> .tb-actions){container:tbar / inline-size;flex-wrap:wrap;height:auto;
  min-height:var(--header-h)}
.topbar:has(> .tb-actions) > div{min-width:0;flex:1 1 0}
@container tbar (max-width:820px){
  .tb-actions{order:9;flex:1 0 100%;flex-wrap:wrap;padding-bottom:var(--space-10)}
  .topbar:has(> .tb-actions) > div{padding-top:var(--space-10)}
}

/* Topbar "more" menu — a compact three-dot dropdown for page controls that don't earn a
   button of their own (Greg, annotation #339, 2026-09-29: the /kit/ page's whole "wall
   iPad" card moved up here, collapsed behind one icon). Same <details>-as-menu shape as
   .userbox-menu, but light/card-themed for the topbar instead of the dark sidebar, and
   opens DOWN (the topbar sits at the top of the screen) instead of up. Reusable by any
   page: put a `.tb-menu` <details> with a `.tb-menu-btn` summary and a `.tb-menu-pop` div
   in {% block topbar_extra %} — the close-on-outside-click/Escape listener is wired once,
   generically, in base.html right next to the identical one for .userbox-menu. */
.tb-menu{position:relative;flex:none}
.tb-menu>summary{list-style:none;cursor:pointer}
.tb-menu>summary::-webkit-details-marker{display:none}
.tb-menu-btn{display:flex;align-items:center;justify-content:center;width:38px;height:38px;
  border-radius:10px;border:1px solid var(--line);background:var(--card);font-size:18px;
  line-height:1;color:var(--ink)}
.tb-menu[open]>.tb-menu-btn,.tb-menu-btn:hover{background:var(--line-2)}
.tb-menu-pop{position:absolute;right:0;top:calc(100% + var(--space-8));z-index:30;
  width:300px;max-width:calc(100vw - 32px);background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius-sm);box-shadow:var(--shadow-lg);padding:var(--space-12) var(--space-14);
  font-size:var(--text-13)}
.tb-menu-head{font-weight:700;margin-bottom:var(--space-4)}
.tb-menu-text{color:var(--muted);font-size:var(--text-12);line-height:1.4;margin:0 0 var(--space-8)}
.tb-menu-sub{margin-bottom:var(--space-8);font-size:var(--text-12)}
.tb-menu-sub summary{cursor:pointer;color:var(--orange-600)}
.tb-menu-sep{height:1px;background:var(--line);margin:var(--space-8) 0}
.tb-menu-link{display:block;padding:var(--space-6) 0;color:var(--ink);text-decoration:none;font-size:var(--text-13)}
.tb-menu-link:hover{color:var(--orange-600)}
@media (max-width:600px){.tb-menu-pop{width:260px}}

.content{padding:var(--content-pad);max-width:1320px;width:100%}
.hamb{display:none}

/* ---------- Bits ---------- */
.pill{display:inline-flex;align-items:center;gap:var(--space-6);font-size:var(--text-11);font-weight:600;
  padding:var(--space-3) var(--space-9);border-radius:999px;background:#eee;color:#444;white-space:nowrap}
.pill .dot{width:7px;height:7px;border-radius:50%;background:currentColor}
.pill.lead{background:var(--orange-50);color:var(--orange-600)}
.btn{display:inline-flex;align-items:center;gap:var(--space-8);font-weight:600;font-size:var(--text-13);
  padding:var(--space-9) var(--space-15);border-radius:11px;border:1px solid var(--line);background:var(--card);
  color:var(--ink);cursor:pointer;transition:.12s}
.btn:hover{border-color:#d8d4d1;box-shadow:var(--shadow)}
.btn.primary{background:var(--orange-600);border-color:var(--orange-600);color:var(--white)}
.btn.primary:hover{background:var(--orange-700);border-color:var(--orange-700)}
.btn.ghost{background:transparent;border-color:transparent;color:var(--slate-2)}
.btn.danger{color:var(--bad)}
.btn.danger:hover{background:var(--bad);border-color:var(--bad);color:var(--white)}
/* The confirm / needs-change pair (client sign-off, kit tags). Was defined per page
   (2026-09-12, audit DS-3) — one home so the two never drift apart. */
.btn.ok{background:var(--good);border-color:var(--good);color:var(--white)}
.btn.ok:hover{background:#1c6238;border-color:#1c6238}
/* Annotation #549 (Greg, 6 Oct 2026) — a sent/not-sent EMAIL button (masterlist card),
   same tint as the .pill.ok/.pill.muted status pills, not the loud solid .btn.ok fill
   (that one means "confirm this now", not "here's the state of something"). */
.btn.ghost.ok{background:var(--good-50);border-color:transparent;color:var(--good)}
.btn.ghost.ok:hover{background:#d9efe1}
.btn.ghost.muted{background:#f0eeec;border-color:transparent;color:var(--muted)}
.btn.ghost.muted:hover{background:#e6e3e0}
.btn.change{background:var(--amber-50);border-color:#e0a040;color:var(--amber-600)}
.btn.change:hover{background:#fdecc2;border-color:#c98a2a}
/* Pill states (2026-09-12, audit DS-2). Templates had ~60 inline colour/background
   variants for five meanings; these are the five. Keep any size/weight override inline.
     ok    = done / confirmed / approved        warn = check / not yet / awaiting
     bad   = missing / refused / cancelled      info = offered / notification / neutral fact
     muted = inactive / secondary label */
/* .st-active: legacy alias still used by play_detail.html — swap it for .ok there, then drop it. */
.pill.ok,.pill.st-active{color:var(--good);background:var(--good-50)}
.pill.warn{color:var(--amber-600);background:var(--amber-50)}
.pill.bad{color:var(--bad);background:var(--bad-50)}
.pill.info{color:var(--purple);background:var(--purple-50)}
.pill.travel{color:var(--blue-600);background:var(--blue-50)}
/* The size is restated on .muted because the phone type pass bumps every `.muted` (html .muted,
   0,1,1) to 13px; a pill keeps pill size. (The old `.pill.sm` did this by accident.) */
.pill.muted{color:var(--muted);background:var(--pill-muted-bg);font-size:var(--text-11)}
/* Session DRAFT/OFFERED tag (session_status_pill). */
.pill.sess-st-search{margin-left:var(--space-6)}
/* A `.pill` that's actually a <button> (the inbox on/off switch) — strip the browser's
   own button chrome so only the pill's own look shows through. */
.pill-btn{border:none;cursor:pointer;font:inherit}
/* ONE pill, ONE chip, ONE badge (19 Oct 2026 — docs/UX-RULES.md appendix "Marker classes").
     .pill  = the STATE of a thing (above: ok / warn / bad / info / muted / lead).
     .chip  = something you pick, filter by or remove: a token, a toggle, a filter. Shape lives
              here; a family modifier (.wa-chip, .cat-chip, .tickchip …) only sets the three
              colour variables plus any real size need (tap target, a dense grid clamp).
     .badge = a COUNT (unread, drafts, new messages). Position modifiers place it. */
.chip{--chip-bg:var(--orange-50);--chip-fg:#7a3a16;--chip-bd:var(--orange-200,#f6c9ad);
  display:inline-flex;align-items:center;gap:var(--space-4);background:var(--chip-bg);
  border:1px solid var(--chip-bd);color:var(--chip-fg);border-radius:999px;
  padding:var(--space-3) var(--space-9);font-size:var(--text-13);line-height:1.4}
.chip[hidden]{display:none}
.chip a{color:inherit;text-decoration:none}
.chip a:hover{text-decoration:underline}
/* A neutral chip at rest (filters, tick-boxes, presets) that turns orange when on. */
.chip.plain{--chip-bg:var(--card);--chip-fg:var(--ink);--chip-bd:var(--line)}
.chip.on,.chip.plain:has(input:checked){--chip-bg:var(--orange-50);--chip-bd:var(--orange);--chip-fg:var(--orange-700)}
a.chip{text-decoration:none;cursor:pointer}
.chip.sm{font-size:var(--text-12);padding:var(--space-2) var(--space-8)}
.chip-note{cursor:help;opacity:.7;font-size:var(--text-11)}
.chip-x{background:none;border:none;color:#b0613a;cursor:pointer;font-size:var(--text-14);
  line-height:1;padding:0 0 0 var(--space-2);font-family:inherit}
.chip-x:hover{color:var(--bad)}
.badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;
  padding:0 var(--space-5);border-radius:999px;background:var(--purple);color:var(--white);
  font-size:var(--text-10);font-weight:700;line-height:1}
.badge[hidden]{display:none}
/* On a sidebar row (Notifications, Drafts, Suggested folders, Annotations): pushed right. */
.badge.nav-badge{margin-left:auto;background:var(--nav-edge)}
/* On the corner of a round/square button (phone menu button, comms launcher). */
.badge.hamb-badge,.badge.ccw-badge{position:absolute;top:-6px;right:-6px;box-shadow:0 0 0 2px var(--card)}
.badge.ccw-badge{top:-3px;right:-3px;background:var(--bad)}
.muted{color:var(--muted)}
/* 2026-09-04, Greg: "Services & Delivery... it's a font that's very small when it's the
   same size as other fonts" — Bebas Neue is a condensed display face, and at the same
   point size as the Poppins UI text around it (~13-14px) it reads smaller, not just
   different. Bumped 15px -> 19px; site-wide, since .sec-title is the one heading
   component every section-title uses (not just this one on the booking page). */
.sec-title{font-family:var(--display);font-weight:400;letter-spacing:.04em;font-size:19px;
  text-transform:uppercase;color:var(--slate-2);margin:var(--space-26) var(--space-2) var(--space-12)}
/* Annotation #306 — pull this one instance's heading down next to its card instead of
   the page-section-sized 26px gap above, so it reads as attached to what's below it. */
/* Annotation #591 (Jacob, 6 Oct 2026) — "Design overlap with the triangle and the word
   email". `.sec-title` used as a `<summary>` (Client sign-off / Emails / Edits log on the
   booking page) had no layout rule of its own: the "▶" chev sat right against the label
   with no gap, at the heading's own 19px, so the glyph ran into the word beside it. Scoped
   to the element so a plain `<h2 class="sec-title">` elsewhere is untouched. */
summary.sec-title{display:flex;align-items:baseline;gap:var(--space-6);cursor:pointer;list-style:none}
summary.sec-title::-webkit-details-marker{display:none}
summary.sec-title .chev{font-size:var(--text-11);color:var(--muted);flex:none}

/* ---------- Cards & grids ---------- */
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);transition:border-color .18s ease}
/* EMAIL COMPOSE FORM (15 Oct 2026) — the small set of pieces a from-scratch send/preview
   page needs (label, text input, notice box, button row). service_email_send.html and
   confirmation_send.html repeat these as inline styles (grandfathered in
   style_baseline.json); a NEW compose page uses these classes instead of growing that
   inline-style debt — see CLAUDE.md "New UI reaches for tel.css first". */
.email-compose-card{padding:var(--space-22);max-width:820px}
.email-fld-label{display:block;font-size:var(--text-12);font-weight:600;color:var(--muted);
  text-transform:uppercase;letter-spacing:var(--caps-track);margin:var(--space-14) 0 var(--space-4)}
.email-fld-label:first-child{margin-top:0}
.email-fld-text{width:100%;border:1px solid var(--line);border-radius:var(--radius);
  padding:var(--space-8) var(--space-10);font-family:inherit;font-size:var(--text-13)}
.email-notice{margin-top:var(--space-10);padding:var(--space-8) var(--space-12);
  border-left:3px solid var(--line);font-size:var(--text-12)}
.email-notice-bad{border-left-color:var(--bad);background:#fdecec}
.email-compose-actions{margin-top:var(--space-18);display:flex;gap:var(--space-10);align-items:center}
/* Hover outline (2026-09-07, Greg: feedback was "hard to understand what you're looking at
   with all the information on the screen" — outlining the box under the cursor helps the eye
   find which one it's in). Site-wide because `.card` is the one shared box primitive (KPI
   tiles, session rows, list rows, panels…) — a single rule reaches all of them, and a second
   copy anywhere else would drift. Border-color only (not box-shadow/background) so it never
   fights a card's own hover behaviour underneath (e.g. `.svc-acc>summary:hover`). Transition
   is on `.card` itself (above) so both hovering IN and OUT fade — Greg: "subtle... nicer than
   a very hard flash" — a transition declared only in `:hover` only animates one direction. */
.card:hover{border-color:var(--orange-600)}
/* …but never TWO orange rings at once. A card holding a focused field already shows where
   the eye is — .fld:focus draws its own orange border + halo — so the hover outline on the
   card around it stops being "which box am I in" and starts being noise (Greg, 2026-09-23,
   on /shopping/: "horrible highlighting thing that's inconsistent and doubled up"). The
   focus ring wins; the hover cue comes back the moment focus leaves the card. */
.card:hover:focus-within{border-color:var(--line)}
.grid{display:grid;gap:var(--space-16)}
/* Session card body, booking page + bookings list (2026-09-13, Greg: "the booking rows could
   be 3 columns when it's a full width screen … I want to save vertical space and have more
   info per screen"). One markup, three shapes by viewport — grid AREAS, so nothing moves in
   the template: wide = Delivery | Team | Notes; laptop/tablet = the old two columns with
   notes stacked under delivery; phone = one column. The team column is the one that grows
   with the cast, so it gets the most width. `var()` can't be used in @media — literal px.
   2026-09-14, Greg (1280px laptop window, 1066px card): "it could definitely handle the 3
   column layout". So the switch is keyed to the CARD's width, not the window's — the booking
   page and the list have different chrome, and a window size says nothing about how much
   room the card really has. Three columns once the card body is 1000px+ (≈1215px window on
   the booking page, ≈1150px on the list). Browsers without container queries keep the old
   1440px window rule. */
.svc-body{container-type:inline-size}
.svc-3col{grid-template-columns:1fr 1fr;grid-template-areas:"deliv team" "notes team";align-items:start}
.svc-3col>.svc-col-deliv{grid-area:deliv;min-width:0}
.svc-3col>.svc-col-team{grid-area:team;min-width:0}
.svc-3col>.svc-col-notes{grid-area:notes;min-width:0}
@container (min-width:1000px){.svc-3col{grid-template-columns:3fr 4fr 3fr;grid-template-areas:"deliv team notes";gap:var(--space-16) var(--space-22)}}
@supports not (container-type:inline-size){@media (min-width:1440px){.svc-3col{grid-template-columns:3fr 4fr 3fr;grid-template-areas:"deliv team notes";gap:var(--space-16) var(--space-22)}}}
@media (max-width:700px){.svc-3col{grid-template-columns:1fr;grid-template-areas:"deliv" "team" "notes"}}
/* Let grid/flex tracks shrink below their content's intrinsic width so nowrap
   text ellipses instead of forcing the whole card wider than the viewport. */
.grid>*,.fin-grid>*,.form-grid>*,.bkedit>*{min-width:0}
.kpis{grid-template-columns:repeat(4,1fr)}
.kpi{padding:var(--space-18) var(--space-18) var(--space-16);position:relative;overflow:hidden}
.kpi .lab{font-size:var(--text-12);font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:var(--caps-track);position:relative;z-index:1}
.kpi .num{font-family:var(--display);font-size:var(--text-48);line-height:.95;margin-top:var(--space-8);color:var(--ink);white-space:nowrap;position:relative;z-index:1}
.kpi .num.cur{font-size:34px;letter-spacing:.01em}
.kpi .sub{font-size:var(--text-12);color:var(--muted);margin-top:var(--space-4);position:relative;z-index:1}
.kpi .accent{position:absolute;right:-18px;top:-18px;width:84px;height:84px;border-radius:50%;
  background:var(--orange-50);z-index:0}
.kpi.k-orange .num{color:var(--orange-600)}
.cols-2{grid-template-columns:1fr 1fr}
.cols-3{grid-template-columns:repeat(3,1fr)}
/* #62: client-detail financial strip is always 5 tiles (fixed period list from
   finance.py:client_summary) — its own class so it doesn't collide with the
   dashboard's 4-column .kpis, which must keep its own layout unchanged. */
.fin-strip{grid-template-columns:repeat(5,1fr)}

/* ---------- Lists / rows ---------- */
.rowlist{display:flex;flex-direction:column}
.row{display:flex;align-items:center;gap:var(--space-12);padding:var(--space-12) var(--space-16);border-top:1px solid var(--line)}
.row:first-child{border-top:0}
.row:hover{background:var(--surface-2)}
/* One line, "Tue 21 Jul" (annotation #150) — day number still emphasised for scanning. */
.row .date-chip{flex:0 0 auto;white-space:nowrap;text-align:center;background:#f4f1ef;border-radius:10px;padding:var(--space-6) var(--space-9);align-self:flex-start}
.row .date-chip .d{font-family:var(--display);font-size:var(--text-20);line-height:1;color:var(--ink)}
.row .date-chip .w,.row .date-chip .m{font-size:var(--text-10);text-transform:uppercase;color:var(--muted);letter-spacing:.06em}
.row .main-txt{flex:1;min-width:0}
.row .main-txt b{display:block;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.row .main-txt small{color:var(--muted)}
.card-head{display:flex;align-items:center;justify-content:space-between;padding:var(--space-15) var(--space-16);border-bottom:1px solid var(--line)}
.card-head h3{font-size:var(--text-14)}
/* Shopping list tick — the whole button is the tap target (one-handed use). */
.shop-tick{width:32px;height:32px;flex:0 0 auto;border:0;background:none;font-size:var(--text-18);
  cursor:pointer;color:var(--muted);line-height:1}
.shop-tick.on{color:var(--good)}
/* Shopping list "add an item" bar. Deliberately NOT a .card: it is a control, not a box
   of information, and .card:hover paints an orange border (see "Cards & grids" above) —
   which landed a second orange ring around a focused input that already has its own, on
   the one box you are always typing in. It sits bare on the page, the same way the
   "+ Add store" box below it does, so the two controls on this page read alike. */
.shop-add{display:flex;align-items:center;gap:var(--space-8);max-width:640px;margin-bottom:var(--space-14)}
.shop-add .fld{flex:1}
.card-head .count{font-size:var(--text-12);color:var(--muted)}

/* ---------- Alerts ---------- */
.alert-grp .row .ic{width:28px;height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex:0 0 28px}
.ic.warn{background:var(--amber-50);color:#b07c00}
.ic.bad{background:var(--bad-50);color:var(--bad)}
.ic.info{background:var(--orange-50);color:var(--orange-600)}
.empty{padding:var(--space-26) var(--space-16);text-align:center;color:var(--muted);font-size:var(--text-13)}

/* ---------- Pipeline / board ---------- */
.legend{display:flex;flex-wrap:wrap;gap:var(--space-14);margin:var(--space-4) var(--space-2) 0}
.legend span{display:inline-flex;align-items:center;gap:var(--space-6);font-size:var(--text-12);color:var(--slate-2)}
.legend i{width:10px;height:10px;border-radius:50%}
.board{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(270px,1fr);gap:var(--space-14);overflow-x:auto;padding-bottom:var(--space-8)}
/* .board.wrap — buckets flow onto the next row instead of scrolling sideways (shopping list). Kanban boards keep the horizontal scroll. */
.board.wrap{grid-auto-flow:row;grid-template-columns:repeat(auto-fill,minmax(270px,1fr));overflow-x:visible}
.col{background:#efece9;border-radius:14px;padding:var(--space-10);display:flex;flex-direction:column;gap:var(--space-9);min-height:120px}
.col-head{display:flex;align-items:center;justify-content:space-between;padding:var(--space-4) var(--space-6) var(--space-2)}
.col-head .nm{display:flex;align-items:center;gap:var(--space-8);font-weight:600;font-size:var(--text-13)}
.col-head .tot{font-size:var(--text-12);color:var(--muted);background:var(--card);border-radius:999px;padding:var(--space-1) var(--space-9)}
.bk{background:var(--card);border-radius:12px;padding:var(--space-12);box-shadow:var(--shadow);border:1px solid var(--line);transition:.12s;display:block}
.bk:hover{box-shadow:var(--shadow-lg);transform:translateY(-1px)}
.bk .job{font-family:var(--display);font-size:var(--text-18);letter-spacing:.03em;color:var(--orange-600)}
.bk .cli{font-weight:600;margin:var(--space-1) 0 var(--space-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.bk .meta{font-size:var(--text-12);color:var(--muted);display:flex;flex-wrap:wrap;gap:var(--space-4) var(--space-10);margin-top:var(--space-6)}
.bk .quote{font-weight:600;color:var(--ink)}
.col .more{text-align:center;font-size:var(--text-12);color:var(--muted);padding:var(--space-4)}

/* ---------- Tables (actors / detail) ---------- */
.tbl{width:100%;border-collapse:collapse}
.tbl th{font-size:var(--text-11);text-transform:uppercase;letter-spacing:var(--caps-track);color:var(--muted);text-align:left;padding:var(--space-10) var(--space-14);border-bottom:1px solid var(--line);font-weight:600}
.tbl td{padding:var(--space-12) var(--space-14);border-bottom:1px solid var(--line);vertical-align:top}
.tbl tr:hover td{background:var(--surface-2)}
.tbl th.sortable{cursor:pointer;user-select:none}
.tbl th.sortable:hover{color:var(--orange-600)}
.sort-ind{color:var(--orange-600);font-size:var(--text-10)}
/* Annotation #239 — "these should still be circles, not ovals". `flex:0 0 34px` pinned the
   flex-basis, and flex-basis beats `width` on a row's main axis — so every override that
   resized an avatar (the 46px dashboard faces from #235, the 24px whereabouts ones) got its
   new HEIGHT and kept the old 34px width. `flex:0 0 auto` hands control back to `width`, so
   width and height always agree and a resize can't make an oval again. */
.avatar{width:34px;height:34px;border-radius:50%;background:var(--ink);color:var(--white);display:flex;align-items:center;justify-content:center;font-weight:600;font-size:var(--text-13);flex:0 0 auto}
.chips{display:flex;flex-wrap:wrap;gap:var(--space-6)}

/* ---------- Booking detail ---------- */
.detail-head{display:flex;align-items:flex-start;gap:var(--space-18);flex-wrap:wrap}
.detail-head .job-badge{font-family:var(--display);font-size:var(--text-40);letter-spacing:.03em;color:var(--white);background:var(--orange);border-radius:14px;padding:var(--space-8) var(--space-18);line-height:1}
.detail-head .ttl h1{font-size:23px}
.detail-head .ttl .sub{color:var(--muted);margin-top:var(--space-3)}
.detail-head .actions{margin-left:auto;display:flex;gap:var(--space-8);flex-wrap:wrap}
.facts{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--space-1);background:var(--line);border-radius:14px;overflow:hidden;margin-top:var(--space-18)}
.facts .f{background:var(--card);padding:var(--space-14) var(--space-16)}
.facts .f .k{font-size:var(--text-11);text-transform:uppercase;letter-spacing:var(--caps-track);color:var(--muted);font-weight:600}
.facts .f .v{font-weight:600;margin-top:var(--space-3)}
.svc{padding:0;overflow:hidden}
.svc .svc-top{display:flex;align-items:center;gap:var(--space-12);padding:var(--space-14) var(--space-16);background:var(--surface-3);border-bottom:1px solid var(--line)}
.svc .svc-top .num{font-family:var(--display);font-size:var(--text-20);color:var(--slate-2)}
.svc .svc-body{padding:var(--space-6) var(--space-16) var(--space-14)}
.kv{display:flex;gap:var(--space-8);font-size:var(--text-13);padding:var(--space-5) 0}
.kv .k{color:var(--muted);min-width:96px}
.team{display:flex;flex-direction:column;gap:var(--space-3);margin-top:var(--space-8)}
.team .m{display:flex;align-items:center;gap:var(--space-10);font-size:var(--text-13)}
.team .m .role{color:var(--muted);font-size:var(--text-12)}
/* Cancelled/released cast member (annotation #497): dim the WHOLE row (face, name, role,
   badges) — `.strike` alone only faded the name text, so the avatar and role label still
   read as if they were still on the job. */
.team .m.off-job{opacity:.55}
.strike{text-decoration:line-through;opacity:.6}

/* Draft rows (/drafts/, _draft_rows.html) — annotation #178. These used to borrow
   `.svc-top`, but that rule is scoped to `.svc .svc-top` and /drafts/ has no `.svc`
   ancestor, so the rows landed with NO padding and NO flex: text flush against the card
   edge and the Open/Delete buttons stacked under the title instead of sitting right.
   Their own component rather than widening `.svc .svc-top`, which would restyle every
   session card on the booking pages. */
.draft-row{display:flex;align-items:center;gap:var(--space-12);padding:var(--space-14) var(--space-16)}
.draft-row .draft-main{display:flex;gap:var(--space-12);flex:1;min-width:0;text-decoration:none;color:inherit}
.draft-row .draft-ttl{display:flex;align-items:center;gap:var(--space-8);flex-wrap:wrap}
.draft-row .draft-meta{color:var(--muted);font-size:var(--text-13);margin-top:var(--space-3);line-height:1.45}
.draft-row .draft-acts{display:flex;gap:var(--space-6);align-items:center;flex-shrink:0}
@media (max-width:600px){
  /* Phone: the actions can't share the line with a wrapping title — drop them onto their
     own full-width row, separated by a rule so they don't read as part of the meta text. */
  .draft-row{align-items:stretch;flex-direction:column;gap:var(--space-10);padding:var(--space-13) var(--space-14)}
  .draft-row .draft-acts{border-top:1px solid var(--line);padding-top:var(--space-10)}
}

/* ---------- Calendar ---------- */
#cal{--fc-border-color:var(--line);--fc-today-bg-color:var(--orange-50);background:var(--card);border-radius:var(--radius);
  border:1px solid var(--line);box-shadow:var(--shadow);padding:var(--space-14)}
.fc .fc-toolbar-title{font-family:var(--display);letter-spacing:.03em;font-weight:400;font-size:var(--text-24)}
.fc .fc-button{background:var(--card)!important;border:1px solid var(--line)!important;color:var(--ink)!important;
  text-transform:capitalize;font-weight:600;box-shadow:none!important}
.fc .fc-button-primary:not(:disabled).fc-button-active{background:var(--orange-600)!important;border-color:var(--orange-600)!important;color:var(--white)!important}
.fc .fc-daygrid-day-number{color:var(--slate-2);font-weight:500}
/* ONE toolbar row, shared by every calendar in the app (Greg, 2026-09-23): the today tile
   on the far left, then the period label with a bare chevron either side of it, then the
   view control. The arrows come out of their boxes — the only thing in a box up here is
   the date tile. */
.fc .fc-toolbar-chunk{display:flex;align-items:center;gap:var(--space-6);min-width:0}
/* nowrap on the MIDDLE chunk only: the chunks wrap on a phone (see the rule further down),
   which stacked ‹, the range and › into three rows once they shared a chunk. */
.fc .fc-toolbar.fc-header-toolbar > .fc-toolbar-chunk:nth-child(2){
  flex:1 1 auto;justify-content:center;flex-wrap:nowrap;min-width:0}
.fc .fc-toolbar-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}
/* !important throughout, because .fc .fc-button above is !important — a FullCalendar button
   is a bordered white box by house style, and these two are the deliberate exception. */
.fc .fc-prev-button.fc-button,.fc .fc-next-button.fc-button{
  background:none!important;border:0!important;box-shadow:none!important;
  color:var(--muted)!important;padding:0 var(--space-2);height:auto}
.fc .fc-prev-button.fc-button:hover,.fc .fc-next-button.fc-button:hover{
  background:none!important;color:var(--ink)!important}
.fc .fc-prev-button.fc-button:focus,.fc .fc-next-button.fc-button:focus{box-shadow:none!important}
.fc .fc-prev-button.fc-button:focus-visible,.fc .fc-next-button.fc-button:focus-visible{
  outline:2px solid var(--orange);outline-offset:2px;border-radius:var(--radius-sm)}
.fc .fc-prev-button .fc-icon,.fc .fc-next-button .fc-icon{font-size:var(--text-20)}
/* Today as a date tile, Google-Calendar style: today's date NUMBER in a small rounded box.
   It only appears once the view no longer contains today — each calendar hides it from
   datesSet, and [hidden] needs saying explicitly because .fc-button's display beats the
   attribute's own UA style. */
.fc .fc-todayBox-button{min-width:34px;padding:var(--space-4) var(--space-6);
  border-radius:var(--radius-sm)!important;font-weight:700;font-size:var(--text-13);line-height:1.3}
.fc .fc-todayBox-button[hidden]{display:none}
/* The views as ONE control (calviewmenu.js) rather than a row of buttons — at 390px the
   buttons alone were wider than the screen, and on a laptop they were still the noisiest
   thing on the page. */
.fc .fc-viewMenu-button{white-space:nowrap;font-size:var(--text-13)}
.cal-viewmenu{position:fixed;z-index:9999;background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius-sm);box-shadow:var(--shadow-lg);padding:var(--space-4);
  min-width:168px;max-width:calc(100vw - 24px);max-height:calc(100vh - 96px);overflow:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
  display:flex;flex-direction:column}
.cal-viewmenu[hidden]{display:none}
.cal-viewmenu-item{appearance:none;-webkit-appearance:none;background:none;border:0;cursor:pointer;
  text-align:left;font:inherit;font-size:var(--text-13);color:var(--ink);text-decoration:none;
  padding:var(--space-8) var(--space-10);border-radius:var(--radius-sm)}
.cal-viewmenu-item:hover{background:var(--orange-50)}
.cal-viewmenu-item:focus-visible{outline:2px solid var(--orange);outline-offset:-2px;background:var(--orange-50)}
.cal-viewmenu-item[aria-current="true"]{font-weight:700;background:var(--tab-idle)}
.cal-viewmenu-link{font-weight:600}
.cal-viewmenu-sep{margin-top:var(--space-4);padding-top:var(--space-10);border-top:1px solid var(--line)}
/* ONE menu on the office calendar (Greg, 2026-09-23: "those two menus could probably
   collapse into one", and "I don't think anything should be in the view switcher except
   the views"). The old Options panel moves inside this menu, under the views, and stops
   being a floating popover of its own: the menu does the floating now. */
.cal-viewmenu:has(.cal-viewmenu-panel){width:320px;max-width:calc(100vw - 24px)}
.cal-viewmenu .cal-viewmenu-panel{position:static;width:auto;max-width:none;max-height:none;
  overflow:visible;background:none;border:0;border-top:1px solid var(--line);border-radius:0;
  box-shadow:none;padding:var(--space-10) var(--space-4) var(--space-2);
  margin-top:var(--space-6)}
.fc-event{border:none;border-radius:6px;padding:var(--space-1) var(--space-5);font-size:var(--text-12);font-weight:600;cursor:pointer}
/* ---- the calendar on a phone: every pixel goes to the days ---------------------------
   Greg, 2026-09-23: "on mobile, the calendar grid can take up the entire width of the page.
   And the first column with the hours of the day can be much thinner so that there's just
   more space to see everything."

   The phone block near the end of this file already pulls `#cal` out to both screen edges
   with a negative margin; what was still holding the grid in was the card's OWN 14px padding
   and its side borders. Both calendars share this now — the actor one had it in its own
   template, the office one never did, which is what the screenshot showed. */
/* The all-day row's fold-back control (annotation #481) — sits under the "all-day" label once
   a "+N more" has opened the whole row for the week. */
.cal-allday-less{display:block;margin-top:var(--space-4);padding:0;border:0;background:none;
  color:var(--muted);font:inherit;font-size:var(--text-11);font-weight:600;cursor:pointer;
  text-decoration:underline;white-space:normal;line-height:1.25}
.cal-allday-less:hover{color:var(--ink)}
@media (max-width:700px){
  #cal{padding:0;border-left:0;border-right:0}
  #cal .fc-scrollgrid,#cal .fc-list{border-left-width:0;border-right-width:0}
  /* The hours are a label, not a column of content. FullCalendar sizes this column from
     its widest cushion, so the cushion is what has to give — width alone does nothing. */
  .fc .fc-timegrid-axis,.fc .fc-timegrid-slot-label{width:42px}
  .fc .fc-timegrid-slot-label-cushion{padding:0 var(--space-4);font-size:var(--text-11)}
  .fc .fc-timegrid-axis-cushion{padding:0 var(--space-4);font-size:var(--text-10);
    max-width:42px;white-space:normal;line-height:1.25;text-align:left}
  /* The kit suitcase is a drag-and-drop control for a mouse: you drag it onto a job to
     connect a case. There is no dragging it with a thumb, so on a phone it is a sticker
     sitting on top of the calendar (Greg: "we can remove that suitcase icon on mobile"). */
  /* `body` prefix on purpose: calendar.html declares this FAB in its own <style> INSIDE the
     swapped region, which renders after this stylesheet, so an equal-specificity rule here
     would lose the tie on source order. */
  body #kit-drag-fab{display:none}
}
/* ---- the calendar's controls, inside the ordinary title bar (phone) ----------------------
   Greg, 2026-09-23: "can the calendar header move up into the title bar, accommodating for
   the menu hamburger, of course, but get rid of the word calendar? I don't need to see the
   word calendar" — and then, on the first attempt: "I meant moving the BUTTONS of the
   calendar's header into the title bar rather than replacing the title bar. So keeping it in
   the same theme as every other page's title bar."

   So the title bar stays exactly what it is everywhere else — same height, same tinted blur,
   same hairline underneath, same sticky behaviour — and only its CONTENTS change: the word
   "Calendar" goes, and FullCalendar's own toolbar is laid into the same band, ending short of
   the menu button. The toolbar is positioned rather than moved in the DOM on purpose:
   FullCalendar re-renders that element whenever the view or the date changes, and a node we
   had relocated by hand would be rebuilt back where it started.

   `.main.cal-page` is set by both calendar templates — the actor one only on your OWN
   calendar, where the name in the bar is not news. */
@media (max-width:600px){
  .main.cal-page .topbar > div,.main.cal-page .topbar > a,.main.cal-page .topbar > #tel-back{display:none}
  /* With the title block hidden, the menu button is the bar's only child — and `.hamb`
     orders itself LAST, which does nothing when there is nothing to come after. Same trap the
     full-bleed pages hit (memory: the menu button ended up on the left). Push it to the end
     explicitly; the toolbar below is positioned into the space it leaves. */
  .main.cal-page .topbar{justify-content:flex-end}
  .main.cal-page .fc .fc-toolbar.fc-header-toolbar{
    position:fixed;top:0;left:0;right:52px;height:var(--header-h);
    margin:0;padding:var(--safe-top) var(--space-14) 0;z-index:21;flex-wrap:nowrap;
    /* The bar underneath supplies the background, the blur and the border. */
    background:none}
  /* The toolbar is out of the flow now, so the grid starts directly under the bar. */
  .main.cal-page .content{padding-top:var(--space-10)}
  /* It has to fit between the screen edge and the menu button: 390 − 28 of padding − 52 for
     the button leaves 310. The range at 14px and a view control sized like the label it is
     come to ~276, so nothing is ever squeezed or dropped onto a second line. */
  .main.cal-page .fc .fc-toolbar-title{font-size:var(--text-14)}
  .main.cal-page .fc .fc-viewMenu-button{font-size:var(--text-12);
    padding:var(--space-6) var(--space-8)}
  .main.cal-page .fc .fc-todayBox-button{font-size:var(--text-12);min-width:28px}
}
/* ---- the same, on a touch tablet (Angela's iPad, 1 Oct 2026) ----------------------------
   "Less header above the calendar, and the search in the calendar's own row." The phone
   pattern above, applied to an iPad in either orientation: the word "Calendar" goes, and
   FullCalendar's toolbar (Today · ‹ range › · view menu) is laid into the title bar band,
   ending short of the search icon — so the search shares the toolbar's row and the grid
   starts right under the bar. Laptops too since 1 Oct 2026 (Greg: "marry the header bar of the
   calendar with the title bar" — the separate bar was losing screen space everywhere). */
@media (min-width:601px){
  .main.cal-page .topbar > div{display:none}
  .main.cal-page .topbar{justify-content:flex-end}
  .main.cal-page .fc .fc-toolbar.fc-header-toolbar{
    position:fixed;top:0;left:var(--nav-w);right:64px;height:var(--header-h);
    margin:0;padding:var(--safe-top) var(--space-12) 0 var(--space-22);z-index:21;flex-wrap:nowrap;
    background:none}
  /* The installed-app Back button sits at the bar's left — start the toolbar after it. */
  .main.cal-page .topbar:has(> #tel-back:not([hidden])) ~ .content .fc .fc-toolbar.fc-header-toolbar{
    left:calc(var(--nav-w) + 92px)}
  .main.cal-page .content{padding-top:var(--space-10)}
  .main.cal-page #cal{padding:var(--space-6)}
  /* Annotation #499 (Greg, 2026-10-05): "can this fill the full size of the enclosing
     container?" — the box only grew as tall as its rows needed, leaving grey space
     below it on a tall screen. A min-height, not a height: a view that needs MORE room
     than the screen (a full day's hours, Agenda's growing list) still pushes the box
     taller than this floor and the page keeps scrolling past it exactly as before —
     this only stretches the box down to fill what's already empty. */
  .main.cal-page #cal{min-height:calc(100vh - var(--header-h) - var(--space-10) - var(--content-pad))}
}
/* Search opened on the tablet calendar: the toolbar steps aside so the field can take the
   whole bar, then comes back when search closes. (A phone has no top-bar search — its search
   is at the top of the menu drawer.) */
@media (min-width:601px){
  body.search-open .main.cal-page .fc .fc-toolbar.fc-header-toolbar{visibility:hidden}
}
.fc-event:hover{filter:brightness(.94)}
/* Public-holiday background band (nexus2/views.py) — FullCalendar's own default opacity
   for display:"background" events is .3, which read as barely-there (Greg 2026-07-17).
   Bumping the CSS var only affects background events, so school holidays (a normal
   all-day event, not display:"background") are untouched. */
.fc-bg-event{--fc-bg-event-opacity:.55}

/* ---------- Money / finance ---------- */
.money-tile{padding:var(--space-16) var(--space-18)}
.money-tile .lab{font-size:var(--text-12);font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:var(--caps-track)}
.money-tile .big{font-family:var(--display);font-size:var(--text-32);margin-top:var(--space-6);color:var(--ink)}
.money-tile .big.in{color:var(--good)} .money-tile .big.out{color:var(--orange-600)}
.periods{display:flex;flex-wrap:wrap;gap:var(--space-8);margin-bottom:var(--space-16)}
.periods a{padding:var(--space-7) var(--space-13);border-radius:999px;border:1px solid var(--line);background:var(--card);font-size:var(--text-13);font-weight:600;color:var(--slate-2)}
.periods a.on{background:var(--orange-600);border-color:var(--orange-600);color:var(--white)}
.barchart{display:flex;align-items:flex-end;gap:var(--space-10);height:200px;padding:var(--space-10) var(--space-4) 0}
.barchart .mb{flex:1;display:flex;flex-direction:column;justify-content:flex-end;align-items:center;gap:var(--space-3);height:100%}
.barchart .pair{display:flex;align-items:flex-end;gap:var(--space-3);height:100%;width:100%;justify-content:center}
.barchart .bar{width:42%;border-radius:5px 5px 0 0;min-height:2px}
.barchart .bar.in{background:var(--good)} .barchart .bar.out{background:var(--orange)}
.barchart .mlab{font-size:var(--text-11);color:var(--muted);white-space:nowrap}
.fin-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-16);margin-top:var(--space-16)}

/* ---------- Actor cards ---------- */
.headshot{width:84px;height:84px;border-radius:50%;object-fit:cover;background:var(--ink);margin:0 auto var(--space-10);display:flex;align-items:center;justify-content:center;color:var(--white);font-family:var(--display);font-size:30px;overflow:hidden}
.headshot img{width:100%;height:100%;object-fit:cover;background:var(--card)}
/* Transparent circle PNGs: white backdrop so they match the opaque jpeg headshots
   (no grey rim where the circle is transparent inside the round clip). */
.headshot:has(img){background:var(--card)}
/* Greg, 2026-09-22: an initials circle should be "a white background with black initials…
   it will just match the style of the rest of the headshots better". `.person-hs.init` (the
   24px one by every name) was changed then; THIS is the app's other, older avatar, and it
   kept the black fill — so the same person showed up two different ways depending on which
   component drew them. Scoped to the initials case: a `.headshot` holding a photo already
   gets --card from the rule above and must not grow a ring. Matches `.person-hs.init`'s
   orange ring, which is what carries the shape once the fill is white.
   `:has()` is already load-bearing on the line above, so this needs no new support. */
.headshot:not(:has(img)){background:var(--card);color:var(--ink);
  box-shadow:0 0 0 1px var(--orange)}

/* ---------- Team directory list (actor rows) ---------- */
.hs-row{display:flex;align-items:center;gap:var(--space-10)}
/* 40px → 28px on 2026-09-13 (Greg: "anywhere that we've got headshots, I like the idea of
   the smaller version"). Rows that sit two lines of text beside it still fit. */
.headshot.hs-sm{width:28px;height:28px;font-size:var(--text-11);margin:0;flex:none}

/* ---------- Person chip: small headshot beside a name, app-wide ----------
   Greg, 2026-09-13 (after the 15-concept booking-page mockups): a face at name height,
   ~24px, next to every person's name everywhere — "these small headshots really save
   vertical space and allow for more information in a tighter environment". The bigger
   photo + contact details sit behind hover/tap (the infotip bubble), never inline.
   Markup comes from {% person actor %} (nexus2_extras) — do not hand-write it. */
/* `.person.infotip` (two classes) on purpose: the generic `.infotip` rule further down
   sets display:inline-block + line-height:0 + vertical-align:middle for the little "i",
   and it comes LATER in this file, so a plain `.person` would lose the tie
   ([[css-later-rule-wins-a-specificity-tie]]) and the chip collapses to a 0-height line
   with the name thrown onto the row above. Inline, not inline-flex: the name must sit on
   the SAME baseline as the text around it ("· Actor", "· In Training"); a flex chip drops
   the name a few px below its neighbours. The face hangs below the baseline instead
   (-7px centres a 24px circle on 13px text). */
.person,.person.infotip{display:inline;line-height:inherit;vertical-align:baseline;white-space:nowrap}
/* 2026-09-21 (Greg, meeting follow-up): the generic .infotip-btn below paints an orange
   SQUARE behind the round face on hover — `all:unset` here had already wiped the circle
   clip, so restore it and kill that background for the person variant. The ring itself
   now shows on hover/focus (see .person-hs below), not just once infotip.js opens the card. */
.person-btn.infotip-btn{all:unset;display:inline;cursor:pointer;margin:0 var(--space-6) 0 0;border-radius:50%;background:transparent}
.person-btn.infotip-btn:hover,.person-btn.infotip-btn:focus-visible{background:transparent}
/* The FACE carries the vertical alignment, never the <button> around it: an inline-block
   button with line-height:0 has its baseline at its top, so the same -7px threw the face
   a whole line below the name. An <img> has no baseline (its bottom edge is used), and
   the initials box gets overflow:hidden for the same reason — that makes an inline-block's
   baseline its bottom edge too, so both kinds of face sit identically. */
.person-hs{width:24px;height:24px;border-radius:50%;object-fit:cover;background:var(--card);display:inline-block;vertical-align:-7px;box-shadow:0 0 0 1px var(--line);transition:box-shadow .12s}
/* Greg, 2026-09-22 round 2: "right now it's defaulted to a black background with white
   initials. I would rather it default to a white background with black initials… it will
   just match the style of the rest of the headshots better." A real headshot sits on
   --card behind a 1px --line ring (the rule above), so an initials circle now does too —
   and because white-on-white needs the ring to carry the shape, the ring goes ORANGE here
   only, which is the outline Greg described seeing. */
.person-hs.init{overflow:hidden;line-height:24px;text-align:center;background:var(--card);color:var(--ink);font-size:var(--text-10);font-weight:600;letter-spacing:.02em;font-style:normal;box-shadow:0 0 0 1px var(--orange)}
.person-hs.tbc{background:var(--tab-idle);color:var(--muted)}
.person>.person-hs{margin-right:var(--space-6)}   /* a face with no button round it (To Be Cast) */
.person.sm .person-hs{width:20px;height:20px;line-height:20px;font-size:9px;vertical-align:-5px}
.person.lg .person-hs{width:32px;height:32px;line-height:32px;font-size:var(--text-11);vertical-align:-10px}
.person-nm{color:inherit;text-decoration:none}
a.person-nm:hover{color:var(--orange-600);text-decoration:underline}
/* The name joined the photo as a second infotip trigger on the same card (Greg, 1 Oct
   2026: tapping the name should open the person card, not jump straight to the profile —
   "Open profile →" inside the card is the one way there now). Same escape hatch as
   `.person-btn.infotip-btn` just above: undo the generic "i in a circle" sizing and keep
   the plain-text look the old `<a class="person-nm">` had, then restate the hover/focus
   colour explicitly so it beats (not loses to) the generic `.infotip-btn:hover` orange
   fill — two classes outrank one everywhere this cascade is read. */
.person-nm.infotip-btn{all:unset;color:inherit;text-decoration:none;cursor:pointer;display:inline}
.person-nm.infotip-btn:hover,.person-nm.infotip-btn:focus-visible,
.infotip.is-open .person-nm.infotip-btn{color:var(--orange-600);text-decoration:underline;background:transparent}
.person-nm.infotip-btn:focus-visible{outline:2px solid var(--orange);outline-offset:1px;border-radius:2px}
.person-btn:hover .person-hs,.person-btn:focus-visible .person-hs,.person.is-open .person-hs{box-shadow:0 0 0 2px var(--orange)}
.person.strike .person-nm{text-decoration:line-through;opacity:.6}
/* The hover card, rendered inside the shared (dark) infotip bubble. */
.pcard{display:flex;gap:var(--space-12);align-items:flex-start;min-width:220px}
.pcard-hs{width:64px;height:64px;border-radius:50%;object-fit:cover;background:var(--card);flex:none}
.pcard-hs.init{display:grid;place-items:center;background:var(--grey);color:var(--white);font-family:var(--display);font-size:var(--text-24)}
.pcard-body{display:flex;flex-direction:column;gap:var(--space-2);min-width:0}
.pcard-body b{font-size:var(--text-14);color:var(--ink)}
/* The name + the pencil (edit-this-engagement, 2026-10-01) share one row, pencil pinned
   right — the only header-area control the card carries. */
.pcard-head{display:flex;align-items:center;gap:var(--space-8);justify-content:space-between}
.pcard-head .pcard-edit{flex:none}
.pcard-sub{color:var(--muted);font-size:var(--text-12)}
.pcard-body a{text-decoration:none;font-size:var(--text-12);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pcard-body a:hover{text-decoration:underline}
.pcard-link{margin-top:var(--space-4);font-weight:600}
/* The person card is a LIGHT bubble (Greg, 2026-09-13: "I don't like the hover box being
   black") — infotip.js adds .person-pop to the shared bubble while a chip is open. Two-class
   selectors so this beats the generic .infotip-pop rules further down the file. */
.infotip-pop.person-pop{background:var(--card);color:var(--ink);border:1px solid var(--line);box-shadow:var(--shadow-lg);padding:var(--space-12) var(--space-14)}
.infotip-pop.person-pop a{color:var(--orange-600);text-decoration:none}
.infotip-pop.person-pop a:hover{text-decoration:underline}
.infotip-pop.person-pop b{color:var(--ink)}
.infotip-pop.person-pop .infotip-arrow{background:var(--card);border-left:1px solid var(--line);border-top:1px solid var(--line)}
.infotip-pop.person-pop.flip .infotip-arrow{border:0;border-right:1px solid var(--line);border-bottom:1px solid var(--line)}
/* Tap a face ON A PHONE and the photo is the point (Greg, actor phone walkthrough, 24 Sept
   2026 — the 64px card photo was barely bigger than the face he had tapped). Touch-only and
   narrow: `(hover:none)` alone would also catch the office iPad, whose person card carries a
   note thread and wants the side-by-side layout. The photo goes to 200px — it plus the card's
   padding is ~230px, inside a 375px screen with room to spare — with the name centred under
   it, and the whole card grows out of the face you tapped (infotip.js sets --pop-origin to
   that face's position) in 200ms. Literal px in the @media: var() does not resolve there.
   The 720px print-tier photo comes in via the <img>'s srcset (_person.html), so it is sharp. */
@keyframes person-pop-grow{from{transform:scale(.12);opacity:.3}to{transform:none;opacity:1}}
@media (hover:none) and (max-width:600px){
  .infotip-pop.person-pop{animation:person-pop-grow .2s ease-out;transform-origin:var(--pop-origin,50% 0)}
  .infotip-pop.person-pop .pcard{flex-direction:column;align-items:center;text-align:center;min-width:0}
  .infotip-pop.person-pop .pcard-hs{width:200px;height:200px}
  .infotip-pop.person-pop .pcard-hs.init{font-size:var(--text-48)}
  .infotip-pop.person-pop .pcard-body{align-items:center;max-width:200px}
  .infotip-pop.person-pop .pcard-body b{font-size:var(--text-16)}
}
@media (prefers-reduced-motion:reduce){.infotip-pop.person-pop{animation:none}}

/* The per-person note thread inside that same card (_eng_notes.html, 2026-09-23 — Greg:
   "those notes need to be visible on hover over that actor or that team member, and then
   there needs to be a quick way within that hover state to add a note"). A block UNDER the
   photo-and-details row rather than a third column beside it: the card is 420px wide
   (data-pop-wide) and a running list of faces and words needs all of it. The rows and the
   box are the site-wide .note-row / .note-add components — this only tightens their type
   and spacing for a hover card. */
.pcard-notes{display:block;margin-top:var(--space-10);padding-top:var(--space-10);border-top:1px solid var(--line)}
/* Make the card actually USE the extra width data-pop-wide allows — without a floor it
   shrink-wraps to the contact details above it and the thread reads in a 300px column.
   Behind a media query, not unconditional: infotip.js clamps the bubble to the viewport,
   and a floor wider than a small phone would push it back out again. */
@media (min-width:420px){.pcard-notes{min-width:320px}}
.pcard-notes-head{color:var(--muted);font-size:var(--text-11);text-transform:uppercase;letter-spacing:var(--caps-track);margin-bottom:var(--space-4);font-weight:600}
.pcard-notes .note-row{padding:var(--space-4) 0}
.pcard-notes .note-row-text{font-size:var(--text-12)}
.pcard-notes .empty{font-size:var(--text-12);padding:var(--space-4) 0}
.pcard-notes .note-add{margin-top:var(--space-8)}
.pcard-notes .note-add-box{font-size:var(--text-12)}

/* ---------- Profile header ---------- */
.profile{display:flex;gap:var(--space-20);align-items:center;flex-wrap:wrap}
.profile .headshot{width:110px;height:110px;font-size:var(--text-40);margin:0}
.profile h1{font-size:var(--text-24)}
.profile .meta{display:flex;flex-wrap:wrap;gap:var(--space-6) var(--space-16);color:var(--slate-2);font-size:var(--text-13);margin-top:var(--space-6)}
.profile .actions{margin-left:auto;display:flex;gap:var(--space-8);flex-wrap:wrap}

/* ---------- Forms ---------- */
.form-card{max-width:780px}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-14) var(--space-18)}
/* Per-ROW widths inside a form card (session editor: the four times on one line, venue
   beside its room). Deliberately NOT the global .cols-2/.cols-3 above — those are declared
   before .form-grid, so .form-grid's own 1fr 1fr would win and silently flatten the row
   back to 2-up. These carry .form-grid too, so they out-specify it. */
.form-grid.fg-1{grid-template-columns:1fr}
.form-grid.fg-2{grid-template-columns:1fr 1fr}
.form-grid.fg-3{grid-template-columns:repeat(3,1fr)}
.form-grid.fg-4{grid-template-columns:repeat(4,1fr)}
.form-grid+.form-grid{margin-top:var(--space-14)}
/* Optional span on _field.html (span=2/3/4) for the wider .fg-4 grid. */
.form-grid .fspan-2{grid-column:span 2}
.form-grid .fspan-3{grid-column:span 3}
.form-grid .fspan-4{grid-column:1 / -1}
/* Character edit page (Greg, 15 Oct 2026): the whole width, four columns, the cast list in
   four columns, and the three cards below side by side — no scrolling to edit a character. */
.chf-card{max-width:none;padding:var(--space-22)}
.chf-card .cast-pick{grid-template-columns:repeat(3,minmax(0,1fr));max-height:300px}
.cast-pick-item .cast-note{width:200px;font-size:var(--text-12);padding:var(--space-4) var(--space-8)}
.chf-card .cast-pick-item .cast-note{width:auto;flex:1 1 60px;min-width:60px}
.chf-card .cast-pick-item .pill{flex:none;white-space:nowrap}
.chf-card textarea[data-autogrow]{field-sizing:content}
.chf-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:var(--space-14);margin-top:var(--space-14)}
.chf-cards>.form-card{max-width:none;margin:0}
@media (max-width:900px){.chf-card .cast-pick{grid-template-columns:1fr 1fr}.chf-card .form-grid.fg-4{grid-template-columns:1fr 1fr}}
@media (max-width:600px){.chf-card .cast-pick,.chf-card .form-grid.fg-4{grid-template-columns:1fr}.chf-card .form-grid .fspan-2,.chf-card .form-grid .fspan-3{grid-column:auto}}
/* Annotation #157 — "put this under Cohort, not Title". Title and Cohort share a row, and
   .form-grid flows row-by-row, so the disclosure that follows Cohort dropped back into
   column 1 and read as if it belonged to Title. Pin it to Cohort's column. On mobile the
   grid is one column, where an explicit grid-column:2 would invent a phantom second one —
   so it reverts to simply following Cohort. */
.fgroup.under-col2{grid-column:2}
/* 2026-07-17 restructure — a group heading inside the booking form's field grid. The form
   had grown to ~25 fields in one undifferentiated run; these split it by the question each
   block answers, so the eye can skip to the part it wants. Full-width, ruled above, and NOT
   a card — the card is the accordion; another box inside it would just add walls. */
/* Annotation #173 — "can the horizontal lines dividing sections be orange so that it's more
   clear you're moving into a new section?" A hairline in the same grey as every field border
   read as just more furniture; brand orange makes the boundary a signal. */
/* Annotations #236/#237/#238 — "this goes next to 'The job'". The heading's info "i" was
   rendering on its own line UNDER the heading. Not a markup bug: this rule is written
   `.bk-grp` (0,1,0) but `.fgroup` further down the file (also 0,1,0) sets
   flex-direction:column, and the later rule wins a specificity tie. Every element carrying
   .bk-grp also carries .fgroup, so qualify the selector rather than reorder the file. */
.fgroup.bk-grp{grid-column:1/-1;flex-direction:row;align-items:baseline;gap:var(--space-9);flex-wrap:wrap;
  margin:var(--space-18) 0 var(--space-2);padding-top:var(--space-12);border-top:2px solid var(--orange)}
.fgroup.bk-grp:first-child{margin-top:var(--space-2);padding-top:0;border-top:none}
/* 2026-09-07 (Greg) — "the section headers need to stand out more." Was
   font-size:var(--text-16) (16px); bumped ~19% to an explicit 19px rather than
   changing the shared --text-16 var itself, which .run-rich (a wholly unrelated
   run-sheet text block) also reads — this rule and .acc>summary .acc-ttl below are
   the two section-header styles this is actually about. Deliberately SITE-WIDE: both
   classes are shared components (booking_detail.html and others use .acc-ttl too),
   not scoped to the booking form — matches Greg's own framing that this is "a shared
   style used across the whole form, so changing it affects every section header,
   which is the intent." Checked against the .acc>summary flex row (no
   white-space:nowrap, wraps freely) and the narrowest mobile breakpoints in this
   file — nothing here truncates or forces a single line, so the extra 3px doesn't
   introduce new wrapping/overflow. */
.bk-grp-t{font-family:var(--display);font-size:19px;letter-spacing:.4px;color:var(--ink)}
.bk-grp-s{font-size:var(--text-12);color:var(--muted)}
/* Annotation #158 — fields that only exist BECAUSE a tick is on, shown as a box hanging off
   that tick so the connection is visible instead of inferred. Indented + ruled on the left,
   which is the "submenu" read. */
.submenu-box{margin:var(--space-8) 0 var(--space-2) 24px;padding:var(--space-10) var(--space-12);border:1px solid var(--line);
  border-left:3px solid var(--orange);border-radius:0 9px 9px 0;background:#faf9f7}
/* The "role on this job" add-row, inside the Contacts box (2026-07-17 restructure). As a flex
   row its .fld controls collapsed one-per-line, because .fld carries width:100% and this box
   is narrower than the full-width accordion the row came from. An explicit grid sizes them
   instead of fighting that; one column on a phone, where four controls never fit. */
.role-add{display:grid;grid-template-columns:2fr 1.2fr 1.2fr 1fr auto;gap:var(--space-6);
  margin-top:var(--space-10);align-items:center}
/* The fold-away optional field (Access) — a "+ Add" link standing exactly where the field
   itself will appear. .fgroup is a flex column, so the &nbsp; label is a spacer that drops
   the link past the labels of the fields beside it; the padding then centres it on their
   inputs (.fld is ~38px tall) instead of leaving it floating at the label line.
   Revealing is ONE-WAY on purpose — re-hiding a field that now holds a value would take it
   off the form while still saving it, which is precisely the surprise this all exists to
   avoid. A field with a value or an error is never given this treatment (see the view). */
.fld-optional>.fld-optional-w{display:none}
.fld-optional.on>label,.fld-optional.on>a{display:none}
.fld-optional.on>.fld-optional-w{display:block}
.fld-optional>a{align-self:start;padding-top:var(--space-10)}
.fgroup{display:flex;flex-direction:column;gap:var(--space-5)}
.fgroup.full{grid-column:1 / -1}
.fgroup label{font-size:var(--text-12);font-weight:600;color:var(--slate-2)}
.fgroup .help{font-size:var(--text-11);color:var(--muted)}
/* Booking form Personnel (12 Oct 2026): the "↺ Use derived" link beside the label, and the
   line under the box that says the saved text no longer matches the team on the sessions. */
.personnel-reset{margin-left:var(--space-8);font-weight:600;font-size:var(--text-11)}
.fgroup .help.personnel-stale{color:var(--amber-600);font-weight:600}
.fgroup .err{font-size:var(--text-12);color:var(--bad)}
.fld{width:100%;border:1px solid var(--line);border-radius:10px;padding:var(--space-9) var(--space-11);font-family:inherit;font-size:var(--text-14);background:var(--card);color:var(--ink)}
.fld:focus{outline:none;border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-50)}
/* Client/Organisation search-with-add-new (_search_select_combo.html, 2026-09-08) — the
   real <select> stays in the DOM (it's what #bkform submits and what every other script
   reads/writes) but is never shown; the visible text input in front of it is the only UI.
   `display:none` (not visibility/opacity) is what keeps a `required` select from blocking
   submit — browsers skip constraint validation on elements that aren't rendered, exactly
   the same trick `#role-contact-select` already relies on. */
.tel-search-select>select{display:none}
/* Same shared `.tel-search-select` combo fronting the booking DETAIL page's "Choose a
   handout" box (14 Oct 2026, Greg: it was a bare dropdown, should type-to-filter like the
   run sheet builder's search boxes) — scoped the same way `.bf-search-combo` is, so the
   positioning/look doesn't touch Client/Organisation or the Who picker's own inline rules. */
.handoutqadd-combo{position:relative;flex:1 1 auto;min-width:0}
.handoutqadd-combo .tel-search-suggestions{display:none;position:absolute;left:0;right:0;top:100%;
  margin-top:var(--space-2);background:var(--card);border:1px solid var(--line);border-radius:var(--radius-sm);
  box-shadow:var(--shadow-lg);max-height:230px;overflow:auto;z-index:20}
textarea.fld{min-height:74px;resize:vertical}
/* Annotation #275 — an autogrow textarea (autogrow.js) sets its own height from
   content, starting at 1 line; the generic .fld min-height was overriding that and
   forcing every Delivery Materials note box open to ~3 lines regardless. */
textarea.fld[data-autogrow]{min-height:0}
.fld-check{width:18px;height:18px;accent-color:var(--orange)}
/* Type-ahead job/booking picker (BookingPickerWidget) — replaces a giant <select>. */
.bkpick{position:relative}
.bkpick-box{display:flex;align-items:center;gap:var(--space-6)}
.bkpick-box .bkpick-q{flex:1;min-width:0}
.bkpick-clear{flex-shrink:0;width:34px;height:34px;border:1px solid var(--line);border-radius:9px;background:var(--card);color:var(--muted);font-size:var(--text-18);line-height:1;cursor:pointer}
.bkpick-clear:hover{color:var(--ink);border-color:var(--muted)}
.bkpick-results{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:40;background:var(--card);border:1px solid var(--line);border-radius:10px;box-shadow:0 8px 24px #0002;max-height:260px;overflow:auto}
.bkpick-opt{padding:var(--space-9) var(--space-12);font-size:var(--text-13);cursor:pointer;border-bottom:1px solid var(--line-2)}
.bkpick-opt:last-child{border-bottom:none}
.bkpick-opt:hover{background:var(--orange-50)}
.bkpick-none{color:var(--muted);cursor:default}
.bkpick-none:hover{background:none}
.check-row{display:flex;align-items:center;gap:var(--space-9);padding:var(--space-6) 0}
/* A check-row plus a trailing action (e.g. the call-sheet "Preview" link, annotation
   #372) on the same line — the row's own flex only sizes the checkbox/icon/label. */
.cs-row{display:flex;align-items:center;gap:var(--space-9)}
.cs-row .check-row{padding:var(--space-6) 0}
/* "Times TBC" picker under the session editor's Finish box (20 Oct 2026) — small and
   quiet, so it reads as a note on the time, not a field of its own. */
.fin-tbc{display:flex;align-items:center;gap:var(--space-5);margin-top:var(--space-4)}
.fin-tbc select.fld{flex:1;min-width:0;padding:var(--space-3) var(--space-22) var(--space-3) var(--space-6);font-size:var(--text-12)}
/* Qty + Description rows — see qtydesc.js header for the wiring contract. */
.qtydesc{padding:var(--space-8)}
.qtydesc-row{display:grid;grid-template-columns:64px 1fr 24px;gap:var(--space-8);align-items:center;padding:var(--space-3) 0}
.qtydesc-head{font-size:var(--text-11);font-weight:600;color:var(--muted);text-transform:uppercase;
  letter-spacing:var(--caps-track);padding-bottom:var(--space-6);margin-bottom:var(--space-4);border-bottom:1px solid var(--line)}
.qtydesc-rm{border:none;background:none;color:var(--muted);font-size:var(--text-18);line-height:1;
  cursor:pointer;padding:var(--space-2) var(--space-4)}
.qtydesc-rm:hover{color:var(--bad)}
/* Character mode (annotation #638): the first column is a Character dropdown, so it needs room for a name. */
.qtydesc-chars .qtydesc-row{grid-template-columns:minmax(120px,30%) 1fr 24px}
/* The heading a qtydesc list sits under (Props / Costuming) carries its "+ Add" hard right. */
.qtydesc-h{display:flex;align-items:center;gap:var(--space-8)}
/* Handouts picker — see handoutpicker.js header for the wiring contract. */
.handoutpick-list{display:flex;flex-direction:column;gap:var(--space-2)}
.handoutpick-empty{padding:var(--space-6) 0}
.handoutpick-row{display:flex;align-items:center;gap:var(--space-8);padding:var(--space-6) var(--space-6);
  border:1px solid var(--line);border-radius:8px;background:var(--card);cursor:grab}
.handoutpick-row.dragging{opacity:.4}
.handoutpick-row.is-requested{background:var(--orange-50);border-color:var(--orange-50)}
.handoutpick-drag{color:var(--muted);font-size:11px;line-height:1;flex-shrink:0;cursor:grab}
.handoutpick-name{flex:1;font-size:13px;color:var(--ink)}
.handoutpick-rm{border:none;background:none;color:var(--muted);font-size:var(--text-18);
  line-height:1;cursor:pointer;padding:var(--space-2) var(--space-6);flex-shrink:0}
.handoutpick-rm:hover{color:var(--bad)}
.handoutpick-name .pill{font-size:var(--text-10)}
/* The host's Handouts heading carries THE "+ Add" button (.btn-add, below) hard right; it
   opens ONE search box with a dropdown — same shape as the Client / contact search bars
   (Greg, 2026-09-15). */
/* telTypeahead (typeahead.js) — the one type-ahead core's rows. Every suggestion box
   adds these classes next to its own (.handoutpick-opt, .bkpick-opt …); the highlight the
   arrow keys and the mouse share is .is-hi + aria-selected. */
.ta-opt{cursor:pointer}
.ta-opt.is-hi{background:var(--bg)}
.ta-offer{font-weight:600;color:var(--orange-600)}
.ta-none{color:var(--muted);cursor:default}
.ta-head{padding:var(--space-6) var(--space-10) var(--space-2);font-size:var(--text-11);font-weight:700;
  letter-spacing:.02em;color:var(--muted);text-transform:uppercase}
.ta-row{padding:var(--space-7) var(--space-10);font-size:var(--text-14)}
.handoutpick-h{display:flex;align-items:center;justify-content:space-between;gap:var(--space-8)}
.handoutpick-addbox{position:relative;margin:0 0 var(--space-8)}
.handoutpick-addbox[hidden]{display:none}
.handoutpick-sugg{position:absolute;left:0;right:0;top:100%;margin-top:var(--space-2);background:var(--card);
  border:1px solid var(--line);border-radius:8px;box-shadow:var(--shadow-lg);max-height:260px;overflow:auto;z-index:20}
.handoutpick-sugg[hidden]{display:none}
.handoutpick-opt{display:flex;align-items:center;flex-wrap:wrap;gap:var(--space-6);padding:var(--space-6) var(--space-10);
  font-size:13px;color:var(--ink);cursor:pointer}
.handoutpick-opt:hover,.handoutpick-opt.is-hi{background:var(--bg)}
.handoutpick-opt.is-requested{color:var(--muted)}
.handoutpick-opt .pill{font-size:var(--text-10)}
/* The "+ Request '<name>'" row — same idea as the contact typeahead's "+ Add '<name>' as a
   new contact". Always last in the dropdown. */
.handoutpick-offer{font-weight:600;color:var(--orange-600)}
.handoutpick-none{padding:var(--space-6) var(--space-10);font-size:var(--text-12)}
.cs-pick-row .infotip{flex:1;min-width:0}
.cs-pick-topics{font-size:var(--text-11)}
/* Case study Topics / themes (annotation #649) — _topic_picker.html + topicpicker.js.
   Tick boxes use .chk-grid; this is only the new-topic box the heading's "+ Add" opens. */
.topicpick-new{display:flex;gap:var(--space-8);align-items:center;margin-top:var(--space-8);max-width:420px}
.topicpick-new[hidden]{display:none}
/* A run sheet row's "+ Handouts" / "+ Slides" (7 Oct 2026) — _runsheet_row_picks.html +
   rowpicks.js. Sits inside the row's Resources cell (.run-body .rf-label), so it resets the
   caption's small grey text. Chips are the shared .chip/.chip-x; the handout type-ahead is
   the .handoutpick-addbox widget above. */
.rowpicks{margin-top:var(--space-6);font-size:var(--text-13);color:var(--ink)}
.rowpicks-chips:empty{display:none}
.rowpick-list{display:flex;flex-direction:column;border-top:1px solid var(--line);margin-bottom:var(--space-6)}
.rowpick-item{display:flex;align-items:center;gap:var(--space-8);padding:var(--space-4) var(--space-2);
  border-bottom:1px solid var(--line)}
.rowpick-item.is-requested .rowpick-name{color:var(--muted)}
.rowpick-ic{display:inline-flex;color:var(--muted);flex-shrink:0}
.rowpick-ic .ic{width:14px;height:14px}
.rowpick-name{flex:1;min-width:0}
.rowpick-thumb{width:48px;height:27px;object-fit:cover;border-radius:var(--radius-sm);
  border:1px solid var(--line);background:var(--card);flex-shrink:0}
.rowpick-card{display:inline-flex;align-items:center;justify-content:center;overflow:hidden;
  font-size:var(--text-10);line-height:1.1;color:var(--muted);text-align:center;padding:0 var(--space-2)}
.rowpicks-bar{display:flex;flex-wrap:wrap;gap:var(--space-6)}
.rowpicks-panel{margin-top:var(--space-6);padding:var(--space-8);border:1px solid var(--line);
  border-radius:var(--radius-sm);background:var(--bg)}
.rowpicks-panel[hidden]{display:none}
.rowpicks-panel .handoutpick-addbox{margin:0}
.rowpicks-liblink{display:inline-block;margin-top:var(--space-6);font-size:var(--text-12)}
.slidepick-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:var(--space-6)}
.slidepick-tile{display:flex;flex-direction:column;gap:var(--space-3);padding:var(--space-4);
  border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--card);cursor:pointer;
  text-align:left;font:inherit;color:var(--ink)}
.slidepick-tile:hover,.slidepick-tile:focus-visible{border-color:var(--orange)}
.slidepick-tile img,.slidepick-card{width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:var(--radius-sm)}
.slidepick-tile .slidepick-card{display:flex;align-items:center;justify-content:center;padding:var(--space-4);
  background:var(--bg);color:var(--muted);font-size:var(--text-11);text-align:center;overflow:hidden}
.slidepick-title{font-size:var(--text-11);line-height:1.25;overflow:hidden;display:-webkit-box;
  -webkit-line-clamp:2;-webkit-box-orient:vertical}
.rs-slides-dl[hidden]{display:none}
/* Tick-chips — a real checkbox inside a pill-shaped label, ticked = orange. Keyboard works
   natively (Tab / Space). First used by a run sheet row's Who / Techniques (15 Oct 2026,
   _runsheet_row_tags.html + rowtags.js) and the block library's Techniques. `.run-body
   label` (caption text, display:block) is undone here by the more specific selector. */
.tickchips{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-4) var(--space-6)}
/* `.chip.plain.sm.tickchip`: the chip gives shape + colours (orange when ticked, via
   `.chip.plain:has(input:checked)`); this only undoes `.run-body label`. */
.tickchip,.run-body .tickchip{display:inline-flex;margin:0;font-size:var(--text-12);font-weight:400;
  color:var(--chip-fg);cursor:pointer}
.tickchip input[type=checkbox]{margin:0;width:auto;min-width:0;accent-color:var(--orange)}
.tickchip:focus-within{outline:2px solid var(--orange);outline-offset:1px}
/* The row's Who / Techniques box: full width under the row body's two columns. Unticked
   techniques hide until "+ Techniques" opens the group (.is-open). */
.rowtags{grid-column:1/-1;display:flex;flex-direction:column;gap:var(--space-8);
  padding-top:var(--space-6);border-top:1px solid var(--line)}
.rowtags.is-saving{opacity:.6}
.rowtags-group{border:0;margin:0;padding:0;min-width:0}
.rowtags-cap{display:block;padding:0;margin:0 0 var(--space-4);font-size:var(--text-11);
  color:var(--muted)}
.rowtags-none{font-size:var(--text-12)}
.rowtags-group:not(.is-open) .tickchip.is-more{display:none}
/* "This session needs" card on the run sheet builder (_runsheet_skills_rollup.html). */
.skillroll-line{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-4) var(--space-8);
  margin-top:var(--space-8);font-size:var(--text-13)}
.skillroll-cap{font-weight:600;color:var(--slate-2)}
.skillroll-hint{margin:var(--space-8) 0 0;font-size:var(--text-12)}
/* "Anyone playing X needs" — character card on the play page and the needs card on the
   character page (character_skills / _character_skill_needs.html). */
.charneeds{margin-top:var(--space-4);font-size:var(--text-12);color:var(--slate-2)}
.charneeds .chips{margin-top:var(--space-4)}
.charneeds-src{color:var(--muted);font-size:var(--text-11)}
.charneed-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);
  padding:var(--space-6) 0;border-bottom:1px solid var(--line)}
.charneed-name{flex:1 1 180px;min-width:0}
.charneed-add{display:flex;flex-wrap:wrap;gap:var(--space-8);align-items:center;margin-top:var(--space-8)}
.charneed-add[hidden]{display:none}
.charneed-add .fld,.charneed-row .fld{width:auto;max-width:280px}
/* Slide library page (slide_library.html). */
.list-head .slidelib-search{max-width:320px;flex:1 1 260px}
.slidelib-deck{margin-bottom:var(--space-14)}
.slidelib-deck h3{font-size:var(--text-14);margin:0 0 var(--space-6)}
.slidelib-tile{cursor:default}
.slidelib-req{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);padding:var(--space-6) 0;
  border-bottom:1px solid var(--line)}
.slidelib-req form{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);margin-left:auto}
.slidelib-req form + form{margin-left:0}
/* Editing the slide library on its own page (7 Oct 2026) — slide_library.html,
   _slidelib_*.html, slidelib.js. A tile's edit box spans the whole grid row under it. */
.slidelib-searchform{flex:1 1 260px;max-width:320px}
.list-head .slidelib-searchform .slidelib-search{max-width:none;width:100%}
.slidelib-tiles{align-items:start}
.slidelib-face{display:flex;flex-direction:column;gap:var(--space-3)}
.slidelib-pic{display:block}
.slidelib-pic.is-hidden{opacity:.4}
.slidelib-meta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-3);font-size:var(--text-11)}
.slidelib-meta .pill{font-size:var(--text-10)}
.slidelib-editbtn{align-self:flex-start}
.slidelib-edit{grid-column:1/-1;padding:var(--space-12);border:1px solid var(--orange);
  border-radius:var(--radius-sm);background:var(--card)}
.slidelib-edit[hidden]{display:none}
.slidelib-editform{display:flex;flex-direction:column;gap:var(--space-6)}
.slidelib-lbl{font-size:var(--text-12);font-weight:600;color:var(--slate)}
.slidelib-note{font-size:var(--text-12);margin:0}
.slidelib-check{display:flex;align-items:center;gap:var(--space-6);font-size:var(--text-13)}
.slidelib-groupset{border:0;padding:0;margin:0;display:flex;flex-wrap:wrap;gap:var(--space-6) var(--space-12)}
.slidelib-groupset legend{padding:0;margin-bottom:var(--space-4);width:100%}
.slidelib-gchk{display:inline-flex;align-items:center;gap:var(--space-4);font-size:var(--text-13)}
.slidelib-editfoot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);margin-top:var(--space-10)}
.slidelib-editfoot [data-slide-edit-close]{margin-left:auto}
.slidelib-thumbform{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);margin:0}
.slidelib-deckhead{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);margin-bottom:var(--space-6)}
.slidelib-deck .slidelib-deckhead h3{margin:0}
.slidelib-deckface{flex:1 1 auto;min-width:0}
.slidelib-deckhead form{margin:0}
.slidelib-deckedit{padding:var(--space-10);margin-bottom:var(--space-8);border:1px solid var(--line);
  border-radius:var(--radius-sm);background:var(--bg)}
.slidelib-deckedit[hidden]{display:none}
.slidelib-inline{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);margin:0}
.slidelib-inline[hidden]{display:none}
.slidelib-inline .fld{flex:1 1 220px;width:auto;max-width:420px}
.slidelib-toggle .btn-add-on{display:none}
.slidelib-toggle[aria-expanded="true"] .btn-add-off{display:none}
.slidelib-toggle[aria-expanded="true"] .btn-add-on{display:inline}
.slidelib-filter{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6)}
.slidelib-filter a.pill{text-decoration:none}
.slidelib-filter .btn-add{margin-left:auto}
.slidelib-filter + .slidelib-inline{margin-top:var(--space-10)}
.slidelib-groupman{margin-top:var(--space-10)}
.slidelib-groupman summary{cursor:pointer;font-size:var(--text-13);color:var(--muted)}
.slidelib-grouprow{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);padding:var(--space-6) 0;
  border-bottom:1px solid var(--line)}
.slidelib-grouprow .slidelib-inline{flex:1 1 240px}
.slidelib-grouprow form{margin:0}
.slidelib-upload[hidden]{display:none}
.slidelib-uploadgrid{display:grid;grid-template-columns:minmax(140px,max-content) minmax(0,1fr);
  gap:var(--space-8) var(--space-12);align-items:center;margin:var(--space-10) 0}
@media (max-width:600px){.slidelib-uploadgrid{grid-template-columns:1fr}}
/* A run sheet row's Resources list can be dragged into one order (7 Oct 2026). */
.rowpick-drag{color:var(--muted);font-size:var(--text-11);line-height:1;flex-shrink:0;cursor:grab;
  padding:var(--space-2);border-radius:var(--radius-sm)}
.rowpick-drag:focus-visible{outline:2px solid var(--orange);outline-offset:1px}
.rowpick-item.dragging{opacity:.4}
/* Handouts library page (handout_library.html). Its search box is the LITERAL same
   .handoutpick-addbox widget as the run sheet's "add a handout" box above — just
   dropped straight into .list-head, same width rule as .search gets there. */
.list-head .handoutlibsearch{margin:0;max-width:320px;flex:1 1 260px}
.handoutlib{padding:var(--space-4) 0;overflow-x:auto}
.handoutlib .num{text-align:right}
/* Phone: drop the two count columns so Edit/Delete stay on screen (var() can't sit in
   @media — 600px = --bp-phone). */
@media (max-width:600px){.handoutlib .num{display:none}}
.handoutlib-acts{white-space:nowrap;text-align:right}
.handoutlib-acts form{display:inline}
.handoutlib tr.is-hidden-handout td{color:var(--muted)}
.form-actions{display:flex;gap:var(--space-10);margin-top:var(--space-20);padding-top:var(--space-16);border-top:1px solid var(--line)}
.msg{padding:var(--space-11) var(--space-14);border-radius:11px;margin-bottom:var(--space-14);font-size:var(--text-13);font-weight:500}
.msg.success{background:#e7f6ec;color:#1c7a3f} .msg.error{background:var(--bad-50);color:var(--bad)}
.msg.warning{background:var(--amber-50);color:var(--amber-600)}
/* Flash messages render hidden and are lifted into a toast (base.html telFlashToasts). */
.msg[data-flash]{display:none}

/* ---------- Simple list pages ---------- */
.list-head{display:flex;align-items:center;gap:var(--space-12);margin-bottom:var(--space-16);flex-wrap:wrap}
.list-head .search{margin:0;max-width:320px}
.list-head .btn{margin-left:auto}
/* A heading row with another button before its + Add: the pair sits together at the right. */
.list-head .btn+.btn.btn-add{margin-left:0}
/* A filter-tab strip living in the topbar (plays.html, 2026-09-16) rather than its own
   row on the page — scrolls instead of wrapping so it can never spill out of the fixed-
   height sticky header, same idea as .bk-strip .bk-jump above. */
.plays-tabs{display:flex;align-items:center;gap:var(--space-6);min-width:0;flex:0 1 auto;
  overflow-x:auto;scrollbar-width:none;white-space:nowrap}
.plays-tabs::-webkit-scrollbar{display:none}
/* Year break-line — a subtle divider where a dated list (e.g. the actor call sheet)
   crosses a calendar-year boundary, so a January row doesn't read as "this year". */
.year-break{display:flex;align-items:center;gap:var(--space-10);margin:var(--space-18) 0 var(--space-12);color:var(--muted);
  font-size:var(--text-12);font-weight:700;letter-spacing:.04em}
.year-break::before,.year-break::after{content:'';flex:1 1 auto;height:1px;background:var(--line)}
.formset-row{border:1px solid var(--line);border-radius:12px;padding:var(--space-12);margin-bottom:var(--space-10);background:var(--surface-2)}
.tabs{display:flex;gap:var(--space-6);margin-bottom:var(--space-16);border-bottom:1px solid var(--line);overflow-x:auto;-webkit-overflow-scrolling:touch}
.tabs a{padding:var(--space-9) var(--space-14);font-weight:600;font-size:var(--text-13);color:var(--muted);border-bottom:2px solid transparent;cursor:pointer;white-space:nowrap;flex:0 0 auto}
.tabs a.on{color:var(--orange-600);border-bottom-color:var(--orange)}
/* Phones (2026-09-12, audit UX-1): a client page has seven tabs and a phone showed three
   with nothing to say more existed — the strip scrolled sideways silently. Wrap instead. */
@media (max-width:600px){
  .tabs{flex-wrap:wrap;gap:var(--space-2) var(--space-4);overflow:visible}
  .tabs a{padding:var(--space-6) var(--space-10);font-size:var(--text-12)}
}

/* ---------- Call sheet ---------- */
.cs-row.cancelled{opacity:.6}
.cs-date{flex:0 0 54px;text-align:center;background:var(--ink);color:var(--white);border-radius:11px;padding:var(--space-7) var(--space-4)}
.cs-date .d{font-family:var(--display);font-size:var(--text-24);line-height:1}
.cs-date .m{font-size:var(--text-10);text-transform:uppercase;letter-spacing:.06em}
.cs-date .y{font-size:var(--text-10);color:var(--nav-text);text-transform:uppercase}
.cs-fee{font-family:var(--display);font-size:22px;color:var(--orange-600);white-space:nowrap}
/* 21 Sep 2026 [20:09] — the cancellation line that sits under a struck-out fee (.strike).
   Its own row in .svc-head-row (flex-basis:100%) so a full sentence wraps on a phone
   instead of forcing the card wide; never nowrap, unlike the figure above it. */
/* The money on a CANCELLED card. Greg, 23 Sept 2026: "the cancellation fee is actually
   missing from where the fee normally is in the top right corner of the card. So it
   shouldn't actually go anywhere." It reads as money, in the fee's own colour — the struck
   original sits immediately before it, so the pair says "was this, now this" in the slot a
   live job's fee already occupies. Empty when nothing has been settled, never a $0 stand-in. */
.cs-fee-amt{margin-left:var(--space-6)}
.cs-fee-cancel{flex-basis:100%;text-align:right;font-size:var(--text-13);font-weight:700;color:var(--bad);white-space:normal}
/* 22 Sep 2026 (round 4) — the same slot, for a RELEASED row: "Released — no fee (booked on
   another job)". Same geometry, deliberately NOT --bad and not bold: nobody has lost any
   money, they are being paid on the job the office moved them to, and red bold on that
   sentence reads as bad news about their pay. --slate matches the Released pill. */
/* Greg, 2026-09-23 [13:55]: "just underline the address, so it looks like a link, you know,
   make it look like a link." The address has opened Google Maps since the 2026-07-06 pass
   (Venue.maps_url), but nothing on screen said so and Jacob [13:40] had assumed it was dead
   text — "it does do it, but the user might not necessarily know that". Greg ruled out both
   of the usual tells in the same breath: no icon ([13:55] "I have a tiny little button that
   says map... I hate little more buttons... it just gets so busy") and no added words
   ([14:56] "I hate more words"). An underline is the whole affordance. */
.cs-addr{text-decoration:underline}

/* `.cs-times` (the tinted Access/Times/Sessions chips) was REMOVED on 22 Sep 2026: Greg,
   with a screenshot — "'Times' on the call sheet should just look like the other listed
   items below it, not its own box." _session_card.html now draws those three as ordinary
   `.kv` rows beside Venue and Address, so the chip block, its `>div` and its `>div span`
   caption had no markup left in the app to style. NOTE for anyone reinstating a class of
   this name: `booking_callsheet.html` has a `<span class="cs-times">` of its own with its
   own local styles, and it was silently inheriting the `display:flex` from here — which
   dropped the timings onto a line of their own under the date, something its own
   `margin-left:6px` shows was never intended. That span reads inline again now. */

/* ---------- Yellow delta (v1 parity) ----------
   A call-sheet / master-list field that changed since the last ObjectHistory
   snapshot is flagged yellow so the actor/office spots what moved. */
.chg{background:var(--yellow)!important;border-radius:6px;
  box-shadow:0 0 0 2px var(--yellow)}
.chg-inline{background:var(--yellow);border-radius:5px;padding:0 var(--space-4);
  box-shadow:0 0 0 1px var(--yellow)}
/* On a yellow "date changed" chip the muted greys go dark so they stay legible. */
.date-chip.chg .m,.date-chip.chg .w{color:var(--ink)}

@media print{
  .side,.topbar,.periods,.list-head,.no-print{display:none!important}
  .content{padding:0;max-width:none}.shell{display:block}.main{display:block}
  body{background:var(--card)}
  .card{box-shadow:none;border:1px solid #ccc;break-inside:avoid}
  .detail-head .job-badge{print-color-adjust:exact;-webkit-print-color-adjust:exact}
  .cs-fee,.headshot,.pill{print-color-adjust:exact;-webkit-print-color-adjust:exact}
  .chg,.chg-inline{print-color-adjust:exact;-webkit-print-color-adjust:exact}
  a[href]:after{content:""}
}

/* ---------- HR matrix table ---------- */
.hr-table{font-size:var(--text-13);white-space:nowrap}
.hr-table thead th{position:sticky;top:0;background:var(--card);z-index:2;user-select:none}
.hr-table th.sortable{cursor:pointer}
.hr-table th.sortable:hover{color:var(--orange-600)}
.hr-table .sort-ind{color:var(--orange-600);font-size:var(--text-10)}
.hr-table .filter-row td{position:sticky;top:35px;background:var(--card);z-index:3;padding:var(--space-5) var(--space-8);overflow:visible}
.hr-table tbody td{max-width:240px;overflow:hidden;text-overflow:ellipsis}
.dd{position:relative}
.dd-btn{width:100%;min-width:96px;text-align:left;border:1px solid var(--line);border-radius:7px;background:var(--card);padding:var(--space-4) var(--space-22) var(--space-4) var(--space-8);font-family:inherit;font-size:var(--text-12);color:var(--muted);cursor:pointer;white-space:nowrap;position:relative}
.dd-btn:after{content:"";position:absolute;right:var(--space-8);top:calc(50% - 5px);width:5px;height:5px;
  border-right:1.5px solid var(--muted);border-bottom:1.5px solid var(--muted);transform:rotate(45deg)}
.dd-btn.active{color:var(--orange-600);border-color:var(--orange);font-weight:600}
.dd-panel{position:absolute;top:calc(100% + 3px);left:0;z-index:40;width:230px;max-width:80vw;background:var(--card);border:1px solid var(--line);border-radius:10px;box-shadow:var(--shadow-lg);padding:var(--space-8)}
.dd-search{width:100%;border:1px solid var(--line);border-radius:7px;padding:var(--space-5) var(--space-8);font-family:inherit;font-size:var(--text-12);margin-bottom:var(--space-6)}
.dd-list{max-height:230px;overflow:auto;display:flex;flex-direction:column;gap:var(--space-1)}
.dd-list label{display:flex;align-items:center;gap:var(--space-7);font-size:var(--text-12);font-weight:400;padding:var(--space-3) var(--space-4);border-radius:6px;cursor:pointer;white-space:nowrap}
.dd-list label:hover{background:#f4f1ef}
.dd-list input{accent-color:var(--orange)}
.dd-foot{border-top:1px solid var(--line);margin-top:var(--space-6);padding-top:var(--space-6);text-align:right}
.dd-clear{font-size:var(--text-12);color:var(--orange-600);cursor:pointer;font-weight:600}

/* ---------- Email actions ---------- */
/* 2026-09-13 (Greg: "a tighter solution with these email buttons") — was a big ghost button
   with "Not sent" centred on a second line under each, three of them in a row two lines
   tall. Now each is ONE pill: the mail icon + name as the link, the status as a small suffix
   inside the same outline ("✉ General · Not sent" / "✉ Team Briefing · ✓ Sent 3rd Sep").
   Same markup in _booking_svc_card.html and _ml_card.html, so both pages tighten at once. */
.email-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6) var(--space-8);margin-top:var(--space-8);padding-top:var(--space-8);border-top:1px dashed var(--line)}
.email-btn{display:inline-flex;align-items:center;gap:0;border:1px solid var(--line);border-radius:999px;background:var(--card);padding-right:var(--space-10);max-width:100%}
.email-btn .btn{background:transparent;border-color:transparent;padding:var(--space-3) var(--space-6) var(--space-3) var(--space-10);font-size:var(--text-12);gap:var(--space-5);border-radius:999px}
/* Status = a plain grey tick once sent, nothing before that; the date/time is the tick's
   title (hover). General never carries a date (Greg, 2026-09-13). */
.email-btn .sent-marker{min-height:0;white-space:nowrap}
.email-btn .sent-marker:empty{display:none}
.email-btn .sent{font-size:var(--text-13);color:var(--muted);font-weight:400;cursor:default;margin-left:var(--space-2)}
.email-btn .unsent{display:none}
/* Annotation #549 (6 Oct 2026) coloured the masterlist's button itself (ok/muted) and
   dropped its separate sent-marker checkmark, but kept the shared .email-btn/.btn markup —
   so the OLD outer pill (white fill, grey border, right padding reserved for a checkmark
   that no longer exists) wrapped the NEW inner pill (its own colour, its own radius),
   two mismatched rounded-rects producing a visible crescent on the right edge. _ml_card.html
   now marks its version `.email-btn-solo` — one pill, not a pill around a pill. */
.email-btn-solo{display:inline-flex;max-width:100%}
.email-btn-solo .btn{border-radius:999px;padding:var(--space-3) var(--space-10);font-size:var(--text-12);gap:var(--space-5)}

/* ---------- Masterlist card category tints ---------- */
/* Greg, 15 Oct 2026: "I like the coloured cards... let's keep those background colours
   throughout the bookings list even when expanded". One class per calendar category
   (views_masterlist.py: card_cat) — only 7 possible values (CATEGORY_META's 6 + "hold"),
   so a class ladder, not an inline colour (CLAUDE.md: "a value that is data... goes in
   a class ladder", never inline). The fill is the category colour mixed 12% into white
   (OPAQUE, not an alpha blend), computed by hand to match nexus2.views.CATEGORY_META —
   keep the two in step if a category colour changes. The border is the RAW category
   colour, full strength, not mixed — Greg, 15 Oct 2026, on an earlier 30%-mixed attempt:
   "I liked in the mockup how you had the solid colour borders... right now they're
   blending into the background colour" (two low-saturation mixes of the same hue sit
   too close together to read as a distinct ring).
   `.svc-acc.ml-cat-X > summary.sess-sum` is the part that actually shows on a CLOSED
   card: that summary is `position:sticky` and carries its own opaque
   `background:var(--card)` (`.svc-acc>summary.sess-sum`, "Session accordion (shared)"
   below) so it stays legible scrolling over the rows underneath — same specificity as a
   bare `.ml-cat-X>summary.sess-sum` would have had (two classes each), so without the
   redundant `.svc-acc` here the summary rule's position EARLIER in this file than the
   shared one it needs to beat would have lost the cascade tie. The `<details>` tint
   above doesn't have this problem (`.card{background:var(--card)}` is one class, weaker
   regardless of order) — only the sticky summary override needs the extra class. A
   translucent background tried first also left every closed card plain white (Greg,
   15 Oct 2026: "my bookings list doesn't have any of those
   colours yet"). The open card's body is white again — see the `[open]` rule below. */
.ml-cat-office{background:var(--cat-office-50);border-color:var(--cat-office);--cat-edge:var(--cat-office)}
.svc-acc.ml-cat-office>summary.sess-sum{background:var(--cat-office-50)}
.ml-cat-roleplay{background:var(--cat-roleplay-50);border-color:var(--cat-roleplay);--cat-edge:var(--cat-roleplay)}
.svc-acc.ml-cat-roleplay>summary.sess-sum{background:var(--cat-roleplay-50)}
.ml-cat-meeting{background:var(--cat-meeting-50);border-color:var(--cat-meeting);--cat-edge:var(--cat-meeting)}
.svc-acc.ml-cat-meeting>summary.sess-sum{background:var(--cat-meeting-50)}
.ml-cat-delivery{background:var(--cat-delivery-50);border-color:var(--cat-delivery);--cat-edge:var(--cat-delivery)}
.svc-acc.ml-cat-delivery>summary.sess-sum{background:var(--cat-delivery-50)}
.ml-cat-filming{background:var(--cat-filming-50);border-color:var(--cat-filming);--cat-edge:var(--cat-filming)}
.svc-acc.ml-cat-filming>summary.sess-sum{background:var(--cat-filming-50)}
.ml-cat-cancelled{background:var(--cat-cancelled-50);border-color:var(--cat-cancelled);--cat-edge:var(--cat-cancelled)}
.svc-acc.ml-cat-cancelled>summary.sess-sum{background:var(--cat-cancelled-50)}
/* A booking on HOLD (not necessarily cancelled) wins the colour the same way it won the
   old left strip — its own red (#c62828, = tel.css --bad), one shade off CATEGORY_META's
   "cancelled" red (#d64545) so the two still read as different states next to each
   other. */
.ml-cat-hold{background:#f8e5e5;border-color:var(--bad);--cat-edge:var(--bad)}
.svc-acc.ml-cat-hold>summary.sess-sum{background:#f8e5e5}
/* Greg, 15 Oct 2026: "change the bodies of the bookings when they're expanded back to
   white? so the header still stays coloured". An OPEN card goes back to the plain card
   white; its sticky summary keeps the tint from its own rules above, so only the header
   stays coloured. A closed card shows only its summary, so it is unchanged. */
.svc-acc[class*="ml-cat-"][open]{background:var(--card)}
/* Greg, 15 Oct 2026: "Roleplay session borders are disappearing when the card is expanded
   and i hover my mouse over them". Clicking a card's header leaves focus on its <summary>,
   so `.card:hover:focus-within` (Cards & grids, above: "never two orange rings") painted
   the grey --line over the category edge. A coloured card falls back to its OWN edge
   instead (--cat-edge, set on each .ml-cat-X above). Roleplay's fill went from a 12% to a
   ~20% mix the same day (Greg: the mockup's brown card "had more contrast to the
   background than my current ones do"). */
.card[class*="ml-cat-"]:hover:focus-within{border-color:var(--cat-edge)}
/* Internal meetings on the Bookings list (_ml_meeting.html, Greg 20 Oct 2026) — their own
   card, tinted by the same .ml-cat-X ladder above; the whole card is the link to the meeting.
   The pill is outlined in the card's own category edge colour (ink text: white on the
   meeting purple fails AA), so it reads "meeting", not a job. */
.ml-mtg{display:flex;flex-direction:column;gap:var(--space-4);padding:var(--space-10) var(--space-14);
  font-size:var(--text-13);color:var(--ink);text-decoration:none}
.ml-mtg .sess-date{font-family:var(--sans);font-size:var(--text-12);font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--muted)}
.ml-mtg-title{font-size:var(--text-16)}
.ml-mtg:hover{border-color:var(--cat-edge);text-decoration:none}
.ml-mtg.is-past{opacity:.72}
.ml-mtg.is-past:hover{opacity:1}
.ml-mtg-line1,.ml-mtg-line2,.ml-mtg-who{display:flex;align-items:center;gap:var(--space-12);min-width:0;flex-wrap:wrap;row-gap:var(--space-4)}
.ml-mtg-line1{flex-wrap:nowrap}
.ml-mtg-pill{background:var(--card);border:1px solid var(--cat-edge);color:var(--ink)}
.ml-mtg-title{min-width:0}
.ml-mtg-line2,.ml-mtg-who{font-size:var(--text-12)}
@media print{.ml-mtg.is-past{opacity:1}}

/* ---------- Subscribe popover ---------- */
.subscribe{position:relative;display:inline-block}
.subscribe>summary{list-style:none;cursor:pointer}
.subscribe>summary::-webkit-details-marker{display:none}
.subscribe[open]>summary{border-color:var(--orange);color:var(--orange-600)}
.sub-pop{position:absolute;right:0;top:calc(100% + 6px);z-index:60;width:330px;max-width:86vw;background:var(--card);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow-lg);padding:var(--space-14)}
.sub-pop input{width:100%}
/* Annotation #778 (Greg, 13 Oct 2026): on a phone a pop-up menu hung off its button and ran past
   the screen edge. It now sits inside the screen with a margin each side. */
@media (max-width:600px){.subscribe.pop>.sub-pop{position:fixed;left:var(--space-8);right:var(--space-8);width:auto;max-width:none;top:auto;margin-top:var(--space-6)}}
/* Same content as .sub-pop but sitting inline inside an already-open menu (the calendar
   Options panel) instead of floating behind its own Subscribe toggle — no border/shadow/
   position of its own, it just IS the menu content at that point. */

/* ---------- Scroll to top ---------- */
#scrolltop{position:fixed;right:22px;bottom:22px;width:42px;height:42px;border-radius:50%;border:none;background:var(--ink);color:var(--white);font-size:var(--text-18);cursor:pointer;box-shadow:var(--shadow-lg);opacity:0;pointer-events:none;transform:translateY(8px);transition:.18s;z-index:50}
#scrolltop.show{opacity:1;pointer-events:auto;transform:translateY(0)}
#scrolltop.kb-hide{opacity:0!important;pointer-events:none!important}  /* hidden while the on-screen keyboard has a field focused — see base.html */
#scrolltop:hover{background:var(--orange-600)}
@media print{#scrolltop{display:none}}
/* Phones (UX-RULES 5.7, 18 Oct 2026): the 42px circle at right:22px sat on the Edit/Delete
   column of list rows. Smaller, tucked into the corner, and above the home-bar safe area.
   The kb-hide rule above still applies unchanged. */
@media(max-width:600px){
  #scrolltop{width:36px;height:36px;right:var(--space-12);
    bottom:calc(var(--space-12) + env(safe-area-inset-bottom, 0px));font-size:var(--text-16)}
}

/* ---------- Update-available toast (PLAN-LIVE-UPDATES.md Phase 2) ----------
   Passive, bottom-LEFT (so it never fights #scrolltop, bottom-right) — Momence-style:
   appears when a deploy lands under an open tab, never auto-refreshes. Built + torn down
   entirely by updatetoast.js (not just hidden), so "does it exist" and "is it visible" are
   the same question — see memory hidden-attribute-loses-to-display-rule for why that matters.
   left: clears the sidebar via --nav-w (0 on phone, 68 collapsed-rail, 230 full — same
   variable .main's own margin uses, so this always sits just past wherever the nav ends).
   bottom:80px clears .tel-anno-fab, the 46px purple annotate pencil that also lives at
   left:16/bottom:16 for managers/superusers — stacking above it rather than covering it. */
.update-toast{position:fixed;left:calc(var(--nav-w) + 16px);bottom:80px;z-index:55;display:flex;align-items:center;
  gap:var(--space-10);background:var(--orange-600);color:var(--white);border:none;border-radius:999px;
  padding:var(--space-10) var(--space-8) var(--space-10) var(--space-18);
  box-shadow:var(--shadow-lg);font-family:inherit;font-size:var(--text-13);font-weight:600;
  cursor:pointer;animation:ut-in .18s ease-out;
  padding-bottom:calc(var(--space-10) + env(safe-area-inset-bottom,0px))}
.update-toast:hover{background:var(--orange-700)}
.update-toast .ut-x{display:flex;align-items:center;justify-content:center;width:22px;height:22px;
  border-radius:50%;background:rgba(255,255,255,.18);font-size:var(--text-14);line-height:1;
  flex:0 0 auto}
.update-toast .ut-x:hover{background:rgba(255,255,255,.32)}
@keyframes ut-in{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
@media print{.update-toast{display:none}}

/* ---------- Calendar category toggles ---------- */
.cat-toggles{display:flex;flex-wrap:wrap;gap:var(--space-8);margin-left:auto}
.cat-chip svg.ic{width:14px;height:14px}
.alert-act{display:inline-flex;align-items:center;justify-content:center}
.alert-act svg.ic{width:15px;height:15px}
/* `.chip.cat-chip`: --c is the category colour; the modifier maps it onto the chip's
   three colour variables and keeps the filter's bigger tap size. */
.cat-chip{--chip-bg:var(--card);--chip-bd:var(--c,var(--grey-light));--chip-fg:var(--c,#888);
  gap:var(--space-7);border-width:1.6px;padding:var(--space-6) var(--space-14);font-family:inherit;
  font-size:var(--text-13);font-weight:600;line-height:normal;cursor:pointer;transition:.12s}
.cat-chip:before{content:"";width:9px;height:9px;border-radius:50%;background:var(--c,var(--grey-light))}
.cat-chip.on{--chip-bg:var(--c,var(--grey-light));--chip-fg:var(--white);--chip-bd:var(--c,var(--grey-light))}
.cat-chip.on:before{background:var(--card)}
.cat-chip:not(.on){opacity:.85}
.cat-chip-recurring{--c:#9a978f}  /* "Recurring" calendar filter — standing patterns like Grant's, off by default (Greg, 18 Oct 2026) */
.birthday-chip:before{background:conic-gradient(from 90deg,#e5493c,var(--cat-office),#e6c84a,var(--cat-delivery),#3d7fb8,#8157aa,#e5493c)}
/* Pastel, not full-strength (Greg, 2026-09-03) — same colours as the conic dot above,
   each tinted toward white the way every other category tile's fill already is. */
.birthday-chip.on{background:linear-gradient(90deg,#f8d0cc,#f8e8d5,#f8f1d0,#d5eadf,#cddeed,#ded3e9);--chip-fg:#8a2f53;--chip-bd:#eab8cc}
.birthday-chip.on:before{background:var(--card)}
.cat-quick-pop-chips{display:flex;flex-direction:column;gap:var(--space-3)}
.cat-quick-chip{display:flex;align-items:center;gap:var(--space-7);border:0;background:transparent;border-radius:6px;padding:var(--space-6) var(--space-8);font:inherit;font-size:var(--text-13);color:var(--ink-2);cursor:pointer;text-align:left;width:100%}
.cat-quick-chip:hover,.cat-quick-chip:focus-visible{background:#f5f2ee;outline:none}
.cat-quick-chip .dot{width:9px;height:9px;border-radius:50%;flex:none;background:var(--c,var(--grey-light))}
.cat-quick-chip.current{font-weight:700}
.cat-quick-chip.current:after{content:"";margin-left:auto;margin-right:var(--space-3);width:4px;height:8px;
  border-right:2px solid var(--good);border-bottom:2px solid var(--good);transform:rotate(45deg) translateY(-1px)}
.fc-ev{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:var(--text-12)}

/* ---------- Compare chart ---------- */
.compare-wrap{position:relative;height:300px}

/* ---------- Responsive ---------- */
/* Wide data tables get their own horizontal scroll on smaller screens instead
   of pushing the whole page sideways. Scoped to the card so the table keeps its
   normal layout and only the table scrolls. */
@media(max-width:900px){
  .tbl{display:block;overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}
  /* FullCalendar's toolbar (view buttons) wraps instead of overflowing */
  .fc .fc-header-toolbar{flex-wrap:wrap;gap:var(--space-8) var(--space-12)}
  .fc .fc-toolbar-chunk{display:flex;flex-wrap:wrap;gap:var(--space-6)}
}
/* Mobile audit 2026-09-03 — actor_detail's "Engagements" table read as .tbl's default
   horizontal-scroll table (correct for a dense admin report a manager scrolls
   sideways through), but this ONE is the list an ACTOR reads about their own jobs on
   their own phone: the status pill and fee sat off the right edge of the screen with
   no visual hint they were scrollable, so they silently read as MISSING rather than
   "swipe for more" — the "Confirmed" pill showed only as "Confi" on every row.
   Scoped to .tbl-eng only — every other .tbl on the site keeps the scroll-table
   behaviour, which is correct for those. */
@media(max-width:600px){
  .tbl-eng{overflow-x:visible}
  .tbl-eng, .tbl-eng tbody, .tbl-eng tr{display:block}
  .tbl-eng tr{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-4) var(--space-10);
    padding:var(--space-10) var(--space-14);border-bottom:1px solid var(--line-2)}
  .tbl-eng td{display:block;padding:0;border:none;white-space:normal}
  .tbl-eng td:nth-child(1){order:1;flex:0 0 auto;font-size:var(--text-12);color:var(--muted)}
  .tbl-eng td:nth-child(2){order:2;flex:1 1 100%}
  .tbl-eng td:nth-child(3){order:3}
  .tbl-eng td:nth-child(4){order:4}
  .tbl-eng td:nth-child(5){order:5;margin-left:auto}
}
/* /me/pay/ tables (fee sessions: Date/Job/Session/Pay; salaried: Date/Job/Hours/My
   cost) — a THIRD table pattern, distinct from .tbl and .tbl-eng above: on an iPhone
   (390px) the 4 columns overflowed the card, cutting off Pay, and Job wrapped onto up
   to 6 lines (Jamie, 2026-09-15). Desktop keeps a plain table; phone turns each row
   into a small stacked card with the money right-aligned beside the date, which reads
   better here than .tbl-eng's swipe-to-scroll (this list is short and the amount is
   the thing an actor opens the page to see). */
.pay-tbl{width:100%;border-collapse:collapse;font-size:var(--text-14)}
.pay-tbl thead tr{text-align:left;color:var(--muted)}
.pay-tbl th,.pay-tbl td{padding:var(--space-6) var(--space-8)}
.pay-tbl tbody tr{border-top:1px solid var(--line)}
.pay-tbl td.pay-date{white-space:nowrap}
/* A wage row with no hours logged yet carries a call to action ("log your hours") instead of
   a figure — it has to read as the link it is, or the person never taps it and never gets paid. */
.pay-tbl td.pay-amt a{color:var(--orange-600);text-decoration:underline;text-underline-offset:2px}
/* 21 Sep 2026 [20:09] — a cancellation is a SENTENCE, not a figure ("50% cancellation fee:
   $150 + Super"). It can't live in a column sized for "$300.00": on a phone it takes its own
   full-width line under the date, where it reads, instead of being clipped off the edge. */
.pay-tbl td.pay-amt .pay-cancel{color:var(--bad)}
@media(max-width:700px){
  .pay-tbl td.pay-amt.is-cancel{flex:1 1 100%;margin-left:0;margin-top:var(--space-2);text-align:left}
}
@media(max-width:700px){
  .pay-tbl thead{display:none}
  .pay-tbl,.pay-tbl tbody{display:block;width:100%}
  .pay-tbl tbody tr{display:flex;flex-wrap:wrap;padding:var(--space-8) 0}
  .pay-tbl td{display:block;padding:0;border:none;white-space:normal;font-size:var(--text-13)}
  .pay-tbl td.pay-date{order:1;flex:1 1 auto;font-weight:600;font-size:var(--text-14)}
  .pay-tbl td.pay-amt{order:2;flex:0 0 auto;margin-left:auto;font-weight:600;font-size:var(--text-14)}
  .pay-tbl td.pay-job{order:3;flex:0 0 100%;margin-top:var(--space-4)}
  .pay-tbl td.pay-sub{order:4;flex:0 0 100%;margin-top:var(--space-2);color:var(--muted)}
}
/* Layout reflow for tablet & down (nav handled separately below). */
@media(max-width:900px){
  .kpis{grid-template-columns:repeat(2,1fr)}
  .cols-2,.cols-3{grid-template-columns:1fr}
  /* #62: 5-across financial strip squishes on tablet; 3-across wraps the 5th tile
     onto its own row instead. */
  .fin-strip{grid-template-columns:repeat(3,1fr)}
  .fin-grid,.form-grid{grid-template-columns:1fr}
  .role-add{grid-template-columns:1fr}   /* four controls never fit a phone */
  /* One column here, so pinning to column 2 would create a phantom one (see under-col2). */
  .fgroup.under-col2{grid-column:auto}
  /* Session-editor rows on a phone: stack like everything else, EXCEPT the times — four
     narrow time fields read fine 2-up and stacking them makes the card needlessly long.
     These need .form-grid on them to beat the single-column rule immediately above. */
  .form-grid.fg-2,.form-grid.fg-3{grid-template-columns:1fr}
  .form-grid.fg-4{grid-template-columns:1fr 1fr}
  /* ...except a 4-across row carrying a CHECKBOX, which stacks all the way. A checkbox
     has no column header of its own, so in a 2x2 wrap it reads as belonging to whatever
     lands above it rather than to its neighbour — that's how "Client asked for this date"
     kept reading as part of Session status instead of Date (Greg, 2026-09-08). Set by
     _row_stacks_on_narrow in views_booking.py; the times row has no checkbox and stays
     2-up as above. */
  .form-grid.fg-4.fg-stack{grid-template-columns:1fr}
  .facts{grid-template-columns:repeat(2,1fr)}
  /* #16: on tablet the OPEN/expanded search bar was squishing down to nothing. Hold its
     size with a min-width + no-shrink; the topbar title/crumb block (min-width:0,
     ellipsis) yields and truncates first instead of the search input. Scoped to
     search-open — Greg, 2026-09-08: applying this unscoped forced the collapsed <form>
     (containing just the 38px icon button, everything else display:none) to be 220px
     wide anyway, leaving ~180px of invisible dead space trailing the visible icon
     before the bell — it LOOKED like the icon was centered/floating, not right-aligned. */
  body.search-open .search{max-width:none;flex:1 1 220px;min-width:220px}
  .topbar > div{min-width:0;flex:1 1 0}
}
/* Phone only: off-canvas hamburger drawer. Tablet (601-900px) uses the icon rail
   (see the .nav-mini overlay block above), so the drawer is scoped to <=600px. */
@media(max-width:600px){
  :root{--nav-w:0px}
  /* The top bar's search goes on a phone (Greg, 2026-09-23: "on the entire mobile version of
     the app, the search bar in the title bar can disappear because it's already at the top of
     the mobile menu"). Nothing is lost: the drawer's copy (.nav-search, base.html) is on every
     page for every user, including crew, who never had this one. The element stays in the
     markup rather than being dropped from base.html — the office's desktop search is the same
     element, and telSearchToggle() still addresses it above this width. The title then gets
     the width back, which is the point at 390px. */
  .topbar #tel-search{display:none}
  /* #63: full-screen drawer on phones (not a narrow 240px strip). Slide from fully
     off-screen (-100%) to full width/flex-basis when open. .side's own z-index:30
     (base rule above) already sits above .scrim's z-index:25 below, so no stacking
     change needed there. The base.html @media(max-width:700px) rule that gives the
     drawer 100dvh + safe-area padding for the logout box still applies on top of
     this — it only touches height/overflow/padding, not width/position. */
  /* It comes in from the RIGHT, because that is where the button that opens it is (Greg,
     2026-09-23: "I'm clicking on mobile, the top right menu, hamburger, and then the menu is
     flying on from the left instead of the right"). A panel that flies in from the opposite
     side of the screen to the thing you pressed reads as a different object arriving.
     `left:auto` is load-bearing: the desktop rule above pins .side to the left, and without
     resetting it an element with both left and right set stretches instead of sliding. */
  .side{position:fixed;left:auto;right:-100%;width:100vw;flex-basis:100vw;transition:.2s;
    box-shadow:var(--shadow-lg)}
  body.nav-open .side{right:0}
  /* #159 — the way OUT of the full-screen drawer. Sits in the brand row, opposite the logo,
     where the hamburger that opened it would be if the drawer weren't covering it. */
  /* #161 — "it should be a grey x on a transparent background and only appear in the top
     right of the menu on mobile... like a clean/modern website". No chip, no border: just
     the mark. */
  .nav-x{display:flex;align-items:center;justify-content:center;position:absolute;
    top:calc(16px + var(--safe-top));right:14px;width:38px;height:38px;
    background:none;border:none;color:#8f8c88;font-size:26px;line-height:1;
    cursor:pointer;font-family:inherit;z-index:1}
  .nav-x:active{color:var(--white)}
  .hamb{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;border-radius:10px;border:1px solid var(--line);background:var(--card);cursor:pointer}
  .scrim{position:fixed;inset:0;background:rgba(0,0,0,.35);z-index:25;display:none}
  body.nav-open .scrim{display:block}

  /* Phase T — nav submenus (Team/Directory/Financials/etc.) collapsed-with-tap-to-
     expand on phone. Desktop only ever collapses .grp-items via the ≥601px
     collapsed-rail rule or the ≥901px (hover:hover) pop-out rule above — neither
     applies below 601px, so on phone .grp-items fell through to its un-media-queried
     default (a plain <div>, always visible) and every submenu rendered fully
     expanded. Start collapsed here and reveal on tap of the header (.nav-sec),
     toggled by JS adding/removing .tel-open on the parent .nav-group (see base.html). */
  .nav-group>.grp-items{display:none}
  /* Hanging indent (2026-09-14, Greg: "opening a sub menu needs to be hanging indented so we
     can tell it's a submenu"). An open group's links sit in under the header's LABEL, with a
     rule down their left edge starting at the header icon's centre line, and the header
     itself lights up — so the children read as belonging to it, not as more top-level rows. */
  .nav-group.tel-open>.grp-items{display:block;
    margin:var(--space-2) 0 var(--space-6) calc(var(--space-12) + var(--space-8));
    padding-left:var(--space-12);border-left:2px solid var(--nav-edge)}
  .nav-group>.grp-items>a{padding-top:var(--space-9);padding-bottom:var(--space-9)}
  .nav-group.tel-open>.nav-sec{color:var(--white)}
  .nav-group.tel-open>.nav-sec:not(.on){background:var(--nav-hover)}
  .nav-group>.nav-sec{position:relative;cursor:pointer}
  .nav-group>.nav-sec::after{content:'';position:absolute;right:12px;top:50%;
    width:7px;height:7px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;
    transform:translateY(-50%) rotate(-45deg);opacity:.55;transition:transform .15s}
  .nav-group.tel-open>.nav-sec::after{transform:translateY(-50%) rotate(45deg)}
}
@media(max-width:600px){
  .kpis{grid-template-columns:1fr 1fr}
  /* #62: 2-across on phone (5th tile wraps to its own row) instead of squished-5. */
  .fin-strip{grid-template-columns:repeat(2,1fr)}
  /* #65: shrink the kpi tiles further on phone so the dashboard's first row of
     tiles doesn't eat the whole screen (see the dash-money rules below for the
     matching money-tile row). */
  .kpi{padding:var(--space-12) var(--space-14) var(--space-10)}
  .kpi .num{font-size:34px}
  .kpi .num.cur{font-size:22px}
  .facts{grid-template-columns:1fr}
  .detail-head .job-badge{font-size:30px}
  :root{--content-pad:var(--space-16)}
  .content{padding:var(--content-pad)}
  /* break long emails / handles instead of overflowing (not table number cells —
     those stay whole and the table scrolls instead) */
  .profile .meta span,.kv span{overflow-wrap:anywhere}
  .fc .fc-toolbar-title{font-size:var(--text-20)}
}

/* Plays library — cast chips */
/* .chip / .chip-x / .chip-note live under "Bits" (one chip, 19 Oct 2026). */

/* Plays library — cast as headshot circles */
/* 2026-09-13: was a grid of 56px circles with the first name underneath (88px wide, three
   lines tall). Greg wants the small face beside the name everywhere, so a cast is now a
   row of name-height chips: 24px face, first name, badges, and the note/remove buttons
   at the end (still hover-revealed). Same markup (_castface.html), drag/drop untouched. */
.castrow{display:flex;flex-direction:column;align-items:stretch;gap:var(--space-6) var(--space-8);margin-top:var(--space-8)}
.castface{display:flex;align-items:center;gap:var(--space-6);width:100%;position:relative;
  padding:var(--space-2) var(--space-8) var(--space-2) var(--space-2);border:1px solid var(--line);border-radius:999px;background:var(--card)}
.castface>a{display:inline-flex;flex:none}
.castface .headshot.castface-hs{width:24px;height:24px;font-size:var(--text-10);margin:0}
.castface-nm{font-size:var(--text-12);line-height:1.2;color:var(--ink);overflow-wrap:anywhere}
.castface-actions{display:flex;gap:var(--space-3);align-items:center;margin-left:var(--space-2);order:9;
  opacity:0;transition:opacity .12s;pointer-events:none}
.castface:hover .castface-actions,.castface:focus-within .castface-actions{opacity:1;pointer-events:auto}
.cf-note-btn{background:var(--card);border:1px solid var(--line);border-radius:50%;width:18px;height:18px;
  line-height:1;box-shadow:0 1px 2px rgba(0,0,0,.15);color:var(--ink)}
.cf-note-dot{font-size:var(--text-10);color:var(--muted);cursor:help;margin-left:var(--space-2);position:relative}
.cf-note-pop{display:none;position:absolute;bottom:140%;left:50%;transform:translateX(-50%);
  background:#222;color:var(--white);padding:var(--space-6) var(--space-9);border-radius:6px;font-size:var(--text-11);line-height:1.35;
  width:170px;white-space:normal;text-align:left;z-index:10;box-shadow:0 2px 8px rgba(0,0,0,.25)}
.cf-note-pop::after{content:"";position:absolute;top:100%;left:50%;transform:translateX(-50%);
  border:5px solid transparent;border-top-color:#222}
.cf-note-dot:hover .cf-note-pop{display:block}
/* Plays — character cards 4-up on a wide screen so dragging people between roles is
   easier and more cards are visible at once (Greg, 5 Oct 2026). .content:has(.char-grid)
   below drops this page's own max-width, same pattern as .bkedit / .ml-toolbar above. */
.char-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--space-10);margin-bottom:var(--space-10)}
/* Head / skills-detail / cast align across the SAME visual row of characters — "casting
   starts at the same height" (Greg, 15 Oct 2026), even when one character's skill list is
   longer than its neighbours'. Row-subgrid: a card doesn't size its own three rows, it
   reads the tallest cell for each from every card sharing this row-group — same idea as
   .team-cols' column-subgrid above, just rows instead of columns. `grid-row:span 3` is
   what makes auto-placement treat every 4 (or 2, or 1, at the breakpoints below) cards as
   one row-group sharing those 3 tracks; an engine without subgrid just stacks normally,
   no misalignment risk, just no longer locked to the row. */
.char-grid>.card{display:grid;grid-template-rows:subgrid;grid-row:span 3}
@media(max-width:1100px){.char-grid{grid-template-columns:1fr 1fr}}
@media(max-width:700px){.char-grid{grid-template-columns:1fr}}
.content:has(.char-grid){max-width:none;width:calc(100vw - var(--nav-w))}
/* Character description — one line by default, tap to read the rest (Greg, 5 Oct
   2026: "hide behind a one line description… but by default it starts collapsed").
   Same <details>+.chev idiom as the booking session accordions. */
.char-desc{margin-top:var(--space-4)}
.char-desc>summary{display:flex;align-items:baseline;gap:var(--space-6);cursor:pointer;list-style:none}
.char-desc>summary::-webkit-details-marker{display:none}
.char-desc>summary .chev{transition:transform .15s;font-size:var(--text-10);color:var(--muted);flex:none}
.char-desc[open]>summary .chev{transform:rotate(90deg)}
.char-desc:not([open])>summary .char-desc-text{display:-webkit-box;-webkit-line-clamp:1;
  -webkit-box-orient:vertical;overflow:hidden}
.castface-rm .chip-x{background:var(--card);border:1px solid var(--line);border-radius:50%;
  width:18px;height:18px;line-height:1;box-shadow:0 1px 2px rgba(0,0,0,.15)}

/* Plays — character cast tick-box picker */
.cast-pick{max-height:360px;overflow:auto;border:1px solid var(--line);border-radius:10px;
  padding:var(--space-8);display:grid;grid-template-columns:1fr 1fr;gap:var(--space-4)}
.cast-pick-item{display:flex;align-items:center;gap:var(--space-8);padding:var(--space-4) var(--space-6);border-radius:8px;cursor:pointer;font-size:var(--text-13)}
.cast-pick-item:hover{background:var(--orange-50)}
.cast-pick-item input{margin:0;flex:none}
.cast-pick-item .headshot{flex:none}
@media (max-width:600px){.cast-pick{grid-template-columns:1fr}}
.pool-filter{margin-bottom:var(--space-6);max-width:280px}
/* Annotation #613 — the per-character "+ add" box keeps the tick list in the DOM (the
   Enter-to-add JS still reads it) but never shows it; only the name field is visible. */
.cast-pick.pool-hidden{display:none}
/* Annotation #790: the per-character add box is full width and drops a list of matching names. */
.pool-filter.pool-filter-wide{max-width:none;width:100%}
.cast-pick.cast-suggest{display:none;grid-template-columns:1fr}
.cast-pick.cast-suggest.open{display:grid}
/* A play's pool "+ Add" sits hard right of its card's heading row; open, it takes the
   whole row so its box has room (the summary stays right, see summary.btn-add). */
.char-grid .pool-add{margin-left:auto}
.char-grid .pool-add[open]{flex:1 1 100%}
.pool-add[open]:has(.cast-suggest-form){flex:1 1 100%;margin-left:0}

/* ---------- Site-wide search dropdown (top bar, base.html) ---------- */
#tel-search-pop{position:absolute;top:100%;left:0;right:0;margin-top:var(--space-6);background:var(--card);
  border:1px solid var(--line);border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.14);
  overflow-y:auto;max-height:min(620px,80vh);z-index:60;padding:0 0 var(--space-6)}
/* Each category gets a banded header so Clients / Upcoming / Historical read as
   separate blocks rather than one long list (Greg, 2026-07-31). :first-child keeps
   the top of the popover clean. */
#tel-search-pop .tsr-head{position:sticky;top:0;z-index:1;
  padding:var(--space-7) var(--space-14) var(--space-5);font-size:var(--text-11);font-weight:600;
  letter-spacing:var(--caps-track);text-transform:uppercase;color:var(--muted);
  background:var(--bg);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
#tel-search-pop .tsr-head:first-child{border-top:0;margin-top:0}
#tel-search-pop .tsr-item{display:block;padding:var(--space-7) var(--space-14);color:inherit;text-decoration:none}
#tel-search-pop .tsr-item b{display:block;font-size:var(--text-14);font-weight:600;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#tel-search-pop .tsr-item small{display:block;color:var(--muted);font-size:var(--text-12);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
#tel-search-pop .tsr-item.on,#tel-search-pop .tsr-item:hover{background:var(--orange-50)}
#tel-search-pop .tsr-more{font-size:var(--text-13);font-weight:600;color:var(--orange-600);
  padding-top:var(--space-6);padding-bottom:var(--space-8)}

/* ---------- Run sheet "start one" options + "copy from…" picker (_runsheet_block.html) ----
   Was four buttons in a wrapping flex row with literal "or" text between them — ragged at
   narrow widths (Greg, 7 Oct 2026: "this is messy"). Now a clean one-per-row list (.sc-stack
   handles the spacing/gap), each option full width so they read as a single list of ways to
   start, not a sentence. Same visual language as the top-bar search dropdown above, scoped to
   its own popover since it isn't the site-wide search (it picks a run sheet to copy, never
   navigates). */
.rs-start{padding-top:var(--space-10)}
.rs-start-bordered{border-top:1px solid var(--line)}
.rs-start .btn{width:100%}
/* Linked run sheets (8 Oct 2026) — a job that uses a play's master directly
   (BookingRunSheetLink). One row of pill + note + Separate button under a sheet on the
   booking page, and per job in the "Shared with" banner on the master editor. */
/* A small orange icon+text action, inline with others in a row — a play master's
   "Open builder / Run sheet (PDF) / Facilitator's guide (PDF) / Duplicate as new master"
   row is both <a> and <button> (some actions navigate, some POST), so this resets a
   button back to looking like its neighbouring links. */
.rs-action-link{display:inline-flex;align-items:center;gap:var(--space-4);
  font-size:var(--text-13);color:var(--orange);text-decoration:none;
  background:none;border:0;padding:0;font-family:inherit;cursor:pointer}
.rs-linked{display:flex;align-items:center;gap:var(--space-8);flex-wrap:wrap;margin-top:var(--space-6)}
.rs-linked form{display:inline-flex;margin:0}
.rs-linked-note{font-size:var(--text-12);color:var(--muted)}
.msg .rs-linked-note{color:inherit}
.rs-copyfrom{position:relative}
.rs-copyfrom-pop{position:absolute;z-index:20;top:100%;left:0;margin-top:var(--space-4);
  min-width:260px;max-height:280px;overflow:auto;background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius);box-shadow:var(--shadow-lg)}
.rs-copyfrom-pop .tsr-item{display:block;padding:var(--space-7) var(--space-14);cursor:pointer}
.rs-copyfrom-pop .tsr-item b{display:block;font-size:var(--text-14);font-weight:600}
.rs-copyfrom-pop .tsr-item small{display:block;color:var(--muted);font-size:var(--text-12)}
.rs-copyfrom-pop .tsr-item:hover{background:var(--orange-50)}

/* ---------- Session edit page: grouped cards + collapsible team blocks ---------- */
.svc-edit .svc-sec{padding:var(--space-18) var(--space-20);margin-bottom:var(--space-14)}
.svc-edit{max-width:980px}
/* Greg, 2026-09-04: "the page is already stretching wide... let's take advantage of it" —
   same 1400px breakpoint + two-column idea as Logistics' `.bk-cols` (booking_detail.html),
   but as a CSS Grid with named areas instead of flex, so the DOM order that #209 depends on
   (When & where, Team, Details — narrow screens keep exactly that stack) never has to change
   just to seat Team beside When&where+Details at a wide one. */
@media(min-width:1400px){
  .svc-edit{max-width:none}
  /* Greg, 2026-09-08 — expanding a team member pushed Details halfway down the screen: a
     CSS Grid item spanning two auto-sized rows (Team spans "when"+"details") inflates the
     EARLIER row to help fit itself, even though only the later row's height should ever
     answer for its overflow. Per spec 11.6/12.6, a spanning item is excluded from that
     inflate-earlier-rows step for any row it spans that has a FLEXIBLE (fr) sizing function —
     so making row 2 `1fr` (row 1 stays `auto`, sized only by When & where) routes all of
     Team's extra height into row 2, where Details already lives at the top. Details now
     always sits right under When & where; only the leftover space below Details (to Team's
     left) grows/shrinks with Team's height. No DOM change, no JS — one line.*/
  .svc-edit .svc-cols{display:grid;grid-template-columns:1fr 1fr;grid-template-rows:auto 1fr;
    column-gap:var(--space-20);grid-template-areas:"when team" "details team"}
  .svc-edit .svc-grp-when{grid-area:when}
  .svc-edit .svc-grp-team{grid-area:team}
  .svc-edit .svc-grp-details{grid-area:details}
  /* Greg, 2026-09-08 — "I don't need the orange line above Team since it's the start of
     its own column, just when it's stacked in tablet and mobile." Team's `.svc-sec-flat`
     divider (tel.css, "session editor field-groups") makes sense stacked BELOW When &
     where at every narrower width, but at this grid breakpoint Team is the first (only)
     thing in its own column — same reasoning `.svc-sec-flat-first` already uses for When &
     where, just applied here ONLY at this width rather than unconditionally like that
     class is. */
  .svc-edit .svc-grp-team>.svc-sec-flat{border-top:none;padding-top:0}
}
.svc-sec-h{font-size:var(--text-14);margin:0 0 var(--space-12)}
/* Session editor sub-section headers (When & where / Team / Details), inline only — the
   `<summary>` variant (a plain `<h3>` on the standalone /service/<id>/edit/ page is
   untouched by the element-scoped selector below). 2026-09-16, Greg: "the chevrons are too
   small ... for When & where, team, and Details" — a real .chev (site-wide component,
   .acc>summary .chev above) replaces the old static, opacity-dimmed ▾ that never changed
   between open and closed. Moved in from the two templates' own inline `style=` at the same
   time (CLAUDE.md: no fresh inline styles).
   ⚠️ Every rule here is qualified with the parent `.svc-sec` (not just `summary.svc-sec-h`)
   on purpose: this `<details>` also carries `.svc-acc` (the shared session-accordion class),
   and `.svc-acc>summary .chev` (below, "Session accordion (shared)") ties this selector's
   specificity exactly — without the extra `.svc-sec` qualifier, cascade ORDER decides and
   that older, smaller (--text-11) rule wins, which is what made the chevron look unchanged
   here after a first attempt at this fix. */
.svc-sec>summary.svc-sec-h{cursor:pointer;list-style:none;display:flex;align-items:center;gap:var(--space-8)}
.svc-sec>summary.svc-sec-h::-webkit-details-marker{display:none}
.svc-sec>summary.svc-sec-h .chev{margin-left:auto;transition:transform .15s;font-size:var(--text-16);color:var(--muted)}
/* Team's "+ Add" sits before the chevron and takes the auto margin; the chevron follows it. */
.svc-sec>summary.svc-sec-h .btn-add+.chev{margin-left:0}
.svc-sec[open]>summary.svc-sec-h .chev{transform:rotate(90deg)}
.svc-team-head{display:flex;align-items:baseline;gap:var(--space-10);margin-bottom:var(--space-12);flex-wrap:wrap}
.svc-slots{margin-top:var(--space-16);padding-top:var(--space-12);border-top:1px dashed var(--line)}
.svc-slots-h{font-size:var(--text-12);font-weight:600;color:var(--slate-2);margin-bottom:var(--space-8)}
.svc-slot-lbl{font-size:var(--text-11);color:var(--muted)}
.svc-slot-lbl .fld{width:100%;display:block;margin-top:var(--space-4)}
/* Annotation #622 — one Start/Finish/bin row of the "Has multiple sessions" time-block
   list (_session_time_blocks.html). `.form-grid` doesn't set align-items, so the bin
   button (one line tall) stretched to match its taller time-input neighbours without this. */
.svc-timeblocks-row{align-items:end;margin-bottom:var(--space-8)}
details.more-opts>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:var(--space-8);
  font-size:var(--text-14);font-weight:600;color:var(--slate-2);user-select:none}
details.more-opts>summary::-webkit-details-marker{display:none}
.eng-block{border:1px solid var(--line);border-radius:12px;background:var(--surface-2);margin-bottom:var(--space-10)}
/* Greg, 2026-09-04: "if I've expanded an actor's row... can I get an orange outline
   around it so I know that's the box I'm looking in" — several rows can be open at once
   (they're independent <details>, not an accordion), so this marks EVERY expanded one,
   not just the most-recent. */
.eng-block[open]{box-shadow:0 1px 4px rgba(0,0,0,.05)}
/* 12 Oct 2026 (#771, #770) — the card is tinted by the person's status (--st is set on the
   <details> by _eng_form.html / telRefreshEngHead), replacing the traffic-light dot. The
   header keeps the same tint when open, so an open row's top line matches a folded one. */
.eng-block{background:color-mix(in srgb,var(--st,var(--grey-cool)) 12%,var(--card))}
.eng-block[open]{background:var(--card)}
.eng-block[open] > .eng-head{background:color-mix(in srgb,var(--st,var(--grey-cool)) 12%,var(--card));border-radius:12px 12px 0 0}
/* 12 Oct 2026 — every row now starts open, so an orange ring on every open row would mark
   nothing. The ring moves to the row you are WORKING in (focus inside it). */
.eng-block:focus-within{border-color:var(--orange)}
/* The brief "that's the one you jumped to" ring (team popover's pencil → this row,
   2026-10-01) — same escape hatch `.run-row.is-flash` uses, just stronger than the
   permanent `[open]` border above since it has to stand out against it too. JS adds/
   removes the class; see _session_editor.html's hash-jump script. */
.eng-block.is-flash{box-shadow:0 0 0 3px var(--orange)}
.eng-block.deleting{opacity:.55}
.eng-block.deleting .eng-who{text-decoration:line-through}
.eng-head{display:flex;align-items:center;gap:var(--space-9);padding:var(--space-11) var(--space-14);min-height:44px;cursor:pointer;
  list-style:none;user-select:none;flex-wrap:wrap}
.eng-head::-webkit-details-marker{display:none}
/* Meeting 2026-09-21 round 3, Greg — "the 'who' box could be in the main row before the
   accordion, so that you can just start typing someone's name right away". The Who
   type-ahead now lives in the collapsed header (see _eng_form.html); this sizes it so it
   reads as part of the summary line rather than a form field parked in it. Width is set
   on THIS wrapper, not on `.tel-search-select` — that div carries an inline
   `max-width:340px`, which no class rule could beat, but it is a block box so it simply
   fills whatever this span gives it. The suggestion list keeps its own comfortable width
   so long names aren't clipped by the narrower box. */
.eng-head-who{flex:0 1 230px;min-width:150px;user-select:text}
.eng-head-who input[type=text].fld{width:100%;padding:var(--space-4) var(--space-8);font-size:13px;cursor:text}
.eng-head-who .tel-search-suggestions{min-width:260px}
/* The S3 cast-status pill (badge() in _session_editor.html) is injected next to the real
   <select>, which now means INSIDE this header. "✓ Available" on every cast row turned a
   one-line team list into a two-line one, which is the opposite of the "shorten the team
   member form" this move is for — and the header already carries the coloured status dot
   and the ⚠ mirror. So the reassuring state is dropped HERE only; a clash, a soft hold or
   an unavailable day still shows, still where the eye already is, and is worth the line. */
.eng-head-who .tel-cast-available{display:none}
.eng-block.deleting .eng-head-who input[type=text]{text-decoration:line-through}
.eng-who{font-weight:600;font-size:var(--text-14)}
.eng-meta{color:var(--muted);font-size:var(--text-13)}
.eng-meta:empty{display:none}
/* 12 Oct 2026, Greg's hand-drawn notes — "shorten the team member box". Role, Character
   and Fee are real inputs in the collapsed header now (_eng_form.html), beside Who:
     dot · who · [role · character] · ask-trail · ⚠ · spacer · fee · ⧉ · 🗑 · ▸
   Sized by the BLOCK's width, not the window's (the team list is one column of a
   two-column session editor, so a wide window can still hold a narrow block): the
   block is a size container, and the two @container steps below move Role/Character to
   a second line, then Fee with them. `order` only moves them visually; tab order stays
   who → role → character → fee. */
.eng-block{container-type:inline-size}
/* Flex BASES are kept small on purpose: a flex line breaks on the items' basis sizes,
   before any shrinking, so generous bases wrapped Fee and the buttons onto a second line
   in a ~580px team column that could hold the whole row. Items grow into spare room. */
.eng-block .eng-head-who{flex:1 1 140px;min-width:120px;max-width:230px}
.eng-head-fields{display:flex;gap:var(--space-8);flex:2 1 190px;min-width:0}
.eng-head-field{user-select:text;cursor:auto;min-width:0}
.eng-head-field .fld{width:100%;padding:var(--space-4) var(--space-8);font-size:var(--text-13)}
.eng-head-role{flex:0 1 120px;min-width:92px}
.eng-head-char{flex:1 1 100px;min-width:72px}
.eng-head-fee{flex:0 0 76px;margin-left:auto}
/* No empty gap between Character and Fee (Greg, 12 Oct 2026: "there's space between
   character and rate that can be utilised") — the spacer stops growing, so the
   role/character group takes the room and Character widens into it. */
.eng-head .eng-spacer{flex:0 0 0}
.eng-head-fee .fld{text-align:right}
/* B4, 2026-09-09 — a facilitator / co-facilitator plays no character, so the box goes.
   CSS, not JS, so rows cloned from the empty-form <template> behave the same with no
   script; `:has()` + `option:checked` stay live as the role <select> changes. H5's
   telSyncFacilitator (_session_editor.html) still disables the box and clears it on a
   real role change. */
.eng-block:has(select[name$="-role"] option[value="facilitator"]:checked) .eng-head-char,
.eng-block:has(select[name$="-role"] option[value="co_facilitator"]:checked) .eng-head-char{display:none}
@container (max-width:520px){
  .eng-head-fields{order:5;flex:1 1 100%;padding-left:var(--space-18)}
}
@container (max-width:420px){
  .eng-head-fields{flex-basis:calc(100% - 90px)}
  .eng-head-fee{order:6}
}
/* The row of small ticks in the block's body (12 Oct 2026): Is lead · Estimate ·
   Override session time · Include in booking form, inline on one line, wrapping when the
   block is narrow. The four override time boxes follow underneath in the form grid. */
.eng-ticks{grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2) var(--space-12)}
.eng-ticks .fgroup{gap:0}
.eng-ticks .check-row{padding:var(--space-2) 0;gap:var(--space-5);font-size:var(--text-11);font-weight:500;white-space:nowrap}
.eng-ticks .fld-check{width:14px;height:14px;margin:0}
/* Status + the four ticks on ONE line (Greg, 12 Oct 2026). The ticks keep their own
   wrapping flex row; on a narrow box they drop under the status. */
.eng-statusrow{grid-column:1 / -1;display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-4) var(--space-12)}
.eng-status{display:flex;align-items:center;gap:var(--space-5)}
.eng-status select{width:160px;max-width:100%;padding:var(--space-3) var(--space-8);font-size:var(--text-13);text-overflow:ellipsis}
.eng-statusrow .eng-ticks{flex:1 1 auto;column-gap:var(--space-8)}
/* "Set everyone to" beside "+ Add team member": its own small inline control, set apart
   by a divider, so the two don't read as one (Greg, 12 Oct 2026). */
.team-status-all{max-width:100%;box-sizing:border-box;display:inline-flex;align-items:center;gap:var(--space-5);margin-left:var(--space-12);padding-left:var(--space-12);border-left:1px solid var(--line)}
.team-status-all > label{white-space:nowrap;margin:0}
.team-status-all[hidden]{display:none}
.team-status-all select.fld{width:auto;min-width:0;flex:0 1 160px;padding:var(--space-3) var(--space-8);font-size:var(--text-13)}
.eng-warn{font-size:var(--text-13);cursor:help}
.eng-spacer{flex:1}
.eng-tbc{font-size:var(--text-11)}
/* Trash-icon "remove this row" toggle — replaces the raw DELETE checkbox (still in the
   DOM, just hidden via .eng-del-hidden, so it keeps submitting with the form). */
.eng-del-hidden{display:none}
.eng-del-btn{background:none;border:0;padding:var(--space-3);cursor:pointer;color:var(--muted);
  display:inline-flex;opacity:.55;line-height:0;border-radius:6px;flex:none}
.eng-del-btn:hover{opacity:1;color:var(--bad);background:var(--bad-tint)}
.eng-del-btn .ic{width:15px;height:15px}
.eng-block.deleting .eng-del-btn{opacity:1;color:var(--bad);background:var(--bad-tint)}
/* Duplicate-team-member icon (Greg, 2026-09-04: back after #313 dropped it — "keep it
   simple, keep it gray, don't want it standing out"). Same footprint as .eng-del-btn but
   its hover stays neutral grey, not the trash's red — this one isn't destructive. */
.eng-dup-btn{background:none;border:0;padding:var(--space-3);cursor:pointer;color:var(--muted);
  display:inline-flex;opacity:.55;line-height:0;border-radius:6px;flex:none}
.eng-dup-btn:hover{opacity:1;background:var(--panel-2,#f0f0f0)}
.eng-dup-btn .ic{width:15px;height:15px}
.eng-chev{color:var(--muted);font-size:var(--text-12);flex:none;transition:transform .15s ease}
details[open]>summary .eng-chev{transform:rotate(90deg)}
/* Inside a team block the old formset-row chrome is redundant — the block is the card.
   2026-09-21 round 3: `formset-row` now sits on the `<details>` itself (the Who combo
   moved into the header, and every row-scoped lookup in the session editor's JS goes
   `closest('.formset-row').querySelector('select[name$="-who"]')` — see _eng_form.html's
   header note), so this targets `.eng-body` by name and the block cancels the padding
   `.formset-row` would otherwise give the whole card. */
.eng-block.formset-row{padding:0}
.eng-block > .eng-body{border:0;background:none;border-radius:0;margin:0;
  padding:var(--space-12) var(--space-14) var(--space-14);border-top:1px dashed var(--line)}
/* S3 — cast-picker conflict statuses: pill under the who-picker for the selected
   person, plus greyed/tinted dropdown options carrying a " — ⚠ booked (26-078)" suffix. */
.tel-cast-badge{display:inline-block;margin-top:var(--space-4);padding:var(--space-1) var(--space-8);border-radius:9px;
  font-size:var(--text-12);font-weight:600;max-width:100%;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;vertical-align:top}
.tel-cast-available{background:#e5f3e8;color:#20794a}
.tel-cast-soft{background:#fdf3d7;color:#8a6d00}
.tel-cast-conflict,.tel-cast-unavailable{background:#fbe4e4;color:#b02a2a}
/* 18 Oct 2026: the pill's status glyph and each availability-warning line lead with a
   registry icon (cloned by _session_editor.html seIcon) instead of an emoji. */
.tel-cast-badge>.ict{margin-right:var(--space-4)}
.tel-avwarn-line{display:flex;align-items:baseline;gap:var(--space-6)}
.tel-avwarn-line>.ict{flex:none}
select option.tel-opt-soft{color:#8a6d00}
select option.tel-opt-conflict,select option.tel-opt-unavailable{color:var(--muted)}

/* ---------- Booking form: one-page accordion + progress checklist (2026-07-02 #6/#6b) ---------- */
/* 2026-09-03 (Greg): "the progress bar takes up a lot of space and an entire column of
   screen. What if the rest of the page was full width, and the progress bar was just
   hidden off to the side with just the traffic lights showing." The checklist now leaves
   the grid entirely (see .chk-rail below — it is position:fixed against the right edge, so
   it is not a grid item at all), and the form's save/cancel stack became a bar across the
   top of the form. That leaves ONE column on both pages, no longer capped at 880px.
   2026-09-04: the 20px `padding-right` this rule used to carry (an earlier, smaller,
   never-quite-enough attempt at the same "clear the rail" problem `--rail-clear` now solves
   properly on `.content` itself) is gone — it was ON TOP OF `.content`'s own reserve, since
   `.bkedit` is `.content`'s direct child, so it only ever pushed Sessions 20px further left
   than Logistics/Production/Financial (`.bkpanel.on`, which bypasses `.bkedit` entirely) —
   the exact misalignment Greg flagged. `.content:has(.chk-rail)`'s padding is the one place
   this is set now. */
.bkedit{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-18);align-items:start}
/* Annotation #148 — the page's one save/cancel, still always in view, but a sticky BAR
   across the top of the form rather than the top of a right-hand column (2026-09-03: that
   column is gone). Two consequences worth keeping in mind:
   • It is deliberately width:max-content, NOT full width. The expanded checklist rail
     reaches ~264px in from the right edge, and a full-width bar would sit under it — a
     pinned rail would then swallow the click on Save.
   • position:sticky now WORKS on every screen. Annotation #160 had to fall back to
     position:fixed on mobile only because sticky can never escape its parent's box and the
     old parent (.bkedit-side) was barely taller than the card. Its parent is the whole form
     now, so one rule covers both, and the old --saverail-h spacer hack is gone.
   The buttons sit OUTSIDE <form id="bkform">, so they carry form="bkform" to submit it
   (HTML5 form-owner attribute). */
.bkedit-actions{position:sticky;top:calc(var(--header-h) + 14px);z-index:16;
  display:flex;flex-direction:row;align-items:center;gap:var(--space-8);
  width:max-content;max-width:100%;background:var(--card);
  border:1px solid var(--line);border-radius:12px;padding:var(--space-8) var(--space-12);
  margin-bottom:var(--space-14);box-shadow:0 2px 8px rgba(0,0,0,.06)}
.bkedit-actions .btn{width:auto;flex:0 0 auto;justify-content:center}
.bkedit-actions .save-state{text-align:left;min-height:0}
/* Annotation #148 — the autosave status line under the Save button. It now carries real
   sentences ("Not saved — Client needs fixing"), so it has to wrap legibly instead of
   being the old one-word min-height:14px strip. `.dirty` = amber: unsaved OR rejected —
   the two states where the user still has work the server hasn't got. */
.save-state{font-size:var(--text-12);color:var(--muted);text-align:center;min-height:14px;
  line-height:1.45;overflow-wrap:break-word}
.save-state.dirty{color:var(--amber-600);font-weight:600}
/* The same status line next to an INLINE save button (travel terms, and any section that
   autosaves through its own form) rather than under the pinned rail — it sits in the
   button's flex row, so centring it would read as detached from the button it reports on. */
.form-actions .save-state{text-align:left;align-self:center;min-height:0}
.acc{background:var(--card);border:1px solid var(--line);border-radius:14px;margin-bottom:var(--space-14)}
.acc>summary{display:flex;align-items:center;gap:var(--space-10);cursor:pointer;list-style:none;
  padding:var(--space-14) var(--space-18);user-select:none}
.acc>summary::-webkit-details-marker{display:none}
/* 2026-09-07 (Greg) — same "stand out more" bump as .bk-grp-t above, same reasoning
   (explicit px, not the shared --text-16 var; deliberately site-wide). */
/* De-scoped from `.acc>summary .acc-ttl` (2026-09-08) — Greg: session sub-section headings
   (When & where / Team / Details) should read "the same way headers are in booking
   details", i.e. reuse this exact style, not a copy of its values. Bare class now so it
   works wherever it's used, inside `.acc` or not; every existing use (booking_form.html's
   own `.acc` headers) was already a descendant of `.acc>summary` so nothing there changes. */
.acc-ttl{font-family:var(--display);letter-spacing:.05em;font-size:19px;
  text-transform:uppercase;color:var(--slate)}
.acc>summary .acc-sub{font-size:var(--text-12);font-weight:400}
.acc>summary .chev{margin-left:auto;transition:transform .15s;font-size:var(--text-11);color:var(--muted)}
.acc[open]>summary .chev{transform:rotate(90deg)}
.acc-body{padding:var(--space-4) var(--space-18) var(--space-18);border-top:1px solid var(--line)}
.cdot{width:10px;height:10px;border-radius:50%;flex:none;display:inline-block}
/* Sessions inside the edit accordion: one row each, edit stays on its own page.
   2026-09-08 (Greg, round 2) — "I don't want the sessions in their own card on the edit
   page. It's still a card within a card." Round 1 gave `.sess-row` `.acc`'s card look
   (border/radius/background) so it would read as A component rather than a smaller nested
   one — wrong call: `.acc` (Sessions) is ALREADY the card, so a card underneath it is
   exactly the "card within a card" problem, whatever radius it uses. Fixed the same way
   `.svc-sec-flat` below fixed the level under THIS one: no border/radius/background at
   all, just a flat row told apart from the next by a divider — reusing the SAME
   orange-rule idiom (`.fgroup.bk-grp`, `.svc-sec-flat`) for the third time now, not a
   fourth different one. `.sess-row + .sess-row` (adjacent-sibling) puts the divider
   between rows without a template flag: the first row in the list — or the lone
   "New session" row, which never has a `.sess-row` sibling before it — simply never
   matches, no `-first` modifier class needed this time. */
/* Greg, 2026-09-08 — "it doesn't actually need to be indented... that line could take up
   the side border of the card rather than being indented." The rail was inset same as any
   other content because it sat inside `.acc-body`'s own left padding (--space-18); pulling
   the row out by exactly that amount (negative margin) puts the rail flush with the white
   card's true edge — like a side border of the card. Right edge pulled out to match (same
   amount, opposite side) so the zebra banding fills the card edge-to-edge rather than
   stopping short on the right only.
   padding-left is `--space-18 minus the 3px border`, not `--space-18 plus` — round 5's
   `+` was wrong and left session content sitting ~15px further right than every other
   section's fields (Greg: "the left hand margins are different from Booking Details...
   to Sessions"). The border eats 3px of the 18px inset that content everywhere else on
   the page gets for free, so what's left over for padding is 18-3, putting the
   chevron/date/title at the SAME x as "Services"/"Money"/etc. above it. */
.sess-row{border:none;border-radius:0;background:none;box-shadow:none;
  margin:0 calc(-1 * var(--space-18));
  padding:var(--space-12) var(--space-18) var(--space-12) calc(var(--space-18) - var(--space-3));
  border-left:3px solid var(--line)}
/* 2026-09-08 (round 7) — orange rule between rows dropped: Greg picked the cool blue-grey
   zebra tint below and asked for the rule gone with it. Zebra + rail carry the row-to-row
   separation now; the orange rule was a second, redundant divider once zebra shipped. */
/* Left accent rail (2026-09-08, Greg — "a few suggestions for how to visually establish
   those sessions without going back into cards", picked "left accent bar"): a session's
   progress state used to be a `.cdot` dot inside the summary; with the rail carrying that
   same colour down the WHOLE row (header + body when open), the dot became a second copy
   of the same signal right next to it, so Greg asked to drop the dot too ("if we do that...
   we can get rid of the status dot"). State now reaches this element directly as a class
   (`.sess-row.st-green/.st-amber/.st-red`, set server-side from `row.prog.state` — see
   _session_row.html) rather than being read off a child `.cdot` that no longer exists.
   ALWAYS the true status colour now, never overridden — round 3 also had an inline orange
   override on an open/highlighted row ("this one's being edited/just added"), which Greg
   caught as a mismatch against the Sessions header's own (still-amber) status dot; the
   "Editing" pill already carries that signal, so the rail no longer needs to. */
.sess-row.st-green{border-left-color:var(--good)}
.sess-row.st-amber{border-left-color:var(--amber-400)}
.sess-row.st-red{border-left-color:var(--bad)}
/* Zebra banding (2026-09-08, round 4-7) — "add the very subtle background difference for
   every other session so we can see the separate rows." Every `.sess-row` under Sessions
   is a `<details>` and none of its siblings there are, so `:nth-of-type` (which counts by
   tag, not class) safely lands on every other SESSION specifically. Round 4 used `--bg`
   (the page's own off-white ground) — wrong tint, read as a hole punched through to the
   page. Round 5's `#fbfaf9` fixed that but Greg found it still too close to the page bg
   once seen side by side. Shown 4 swatches (neutral grey / line-grey / warm orange /
   cool blue-grey); picked cool blue-grey — most contrast against both card white and the
   warm page bg, since it leans the opposite direction (cool vs the page's warm grey). */
#sec-sessions .sess-row:nth-of-type(even){background:#f4f6f7}
.sess-row>summary{display:flex;align-items:center;gap:var(--space-10);padding:0;cursor:pointer;
  list-style:none;font-size:var(--text-13);user-select:none}
.sess-row>summary::-webkit-details-marker{display:none}
.sess-row>summary .ell{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Chevron: FRONT in round 1 (before the status dot, now-removed), trailing again in round 4
   ("move that chevron over to the right of the duplicate button so that it's out of the
   way") — Duplicate (_session_dup_button.html, now in BOTH summaries) claims the flex
   row's leftover space via its own margin-left:auto, so the chevron just lands immediately
   after it. It must NOT also carry margin-left:auto itself — two auto margins on the same
   flex line split the leftover space between them, which is exactly why round 5 shipped
   with Duplicate floating mid-row instead of sitting flush right next to the chevron
   (Greg: "the duplicate button is floating in the middle of the row, it should be right
   aligned next to the chevron"). */
.sess-row>summary .chev{font-size:var(--text-10);color:var(--muted);transition:transform .15s}
/* Greg, 2026-09-08 (round 2) — "remove that separator line between the session header and
   'When & Where'." The row's own divider-from-the-next-row (`.sess-row + .sess-row`,
   above) already marks where one session ends and the next starts; this second line,
   directly under an OPEN row's own heading, was a redundant reprise of the same signal one
   line lower. */
.sess-row[open]>summary{padding-bottom:var(--space-10)}
.sess-row[open]>summary .chev{transform:rotate(90deg)}
/* "Opening…" while a collapsed row is fetching its editor — Greg, 2026-09-08: "the down
   chevron next to a session is taking almost 3 seconds to respond". Clicking a collapsed
   row POSTs and waits (see _session_row.html); ajaxform.js puts `.tel-busy` on the row for
   the duration. Default display is `none` and the busy rule turns it ON — never the
   `hidden` attribute, which a later `display:` rule silently beats. The chevron keeps
   spinning so the control the user actually aimed at is the one that reacts. */
.sess-busy{display:none;font-size:var(--text-11,11px);font-weight:600;color:var(--orange);white-space:nowrap}
.sess-row.tel-busy>summary .sess-busy{display:inline}
.sess-row.tel-busy>summary{cursor:progress}
.sess-row.tel-busy>summary .chev{animation:tel-spin .9s linear infinite;color:var(--orange)}
@keyframes tel-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.sess-row.tel-busy>summary .chev{animation:none}}
.sess-body{padding:var(--space-10) 0 0}
/* Session editor field-groups (When & where / Team / Details), INLINE only — no longer
   boxed `.card`s nested inside the session row's own card ("this can all fold into
   Details... it's already inside a card, another box just adds walls" — same reasoning as
   `.fgroup.bk-grp` above). Same orange-rule-as-divider idiom, reused rather than invented:
   a flat section, told apart by a 2px orange top rule instead of its own border/shadow/
   radius/background. Still a `.svc-acc` `<details>` underneath — collapsible by its own
   chevron exactly as before, just undressed. */
.svc-sec.svc-sec-flat{border:none;border-radius:0;background:none;box-shadow:none;
  border-top:2px solid var(--orange);padding:var(--space-12) 0;margin-bottom:0}
.svc-sec.svc-sec-flat.svc-sec-flat-first{border-top:none;padding-top:0}
.svc-sec.svc-sec-flat>summary{padding:0 0 var(--space-10)}
/* Greg, 2026-09-08 — "there shouldn't be a horizontal line after When and where, Team, and
   Details" — the shared `.svc-acc[open]>summary{border-bottom:...}` rule (still needed
   elsewhere) was drawing one under every open section's own heading; these flat sections
   already get their divider from the NEXT section's orange top rule, so this one directly
   under the heading is a redundant, unwanted second line. Higher specificity than that
   shared rule (two classes + [open] vs. one), so it wins without touching the shared one. */
.svc-sec.svc-sec-flat[open]>summary{border-bottom:none}
/* Contacts & roles: one collapsible row per person (2026-09-08, Greg — chevron per
   contact instead of their roles/cc printed twice on the page). Flat divider, not a
   boxed card, to match the list it replaced (contact_roles_grouped's old rows). */
.contact-row{border-bottom:1px solid var(--line);padding:var(--space-6) 0}
.contact-row>summary{display:flex;align-items:center;gap:var(--space-8);flex-wrap:wrap;
  cursor:pointer;list-style:none;font-size:var(--text-14);user-select:none}
.contact-row>summary::-webkit-details-marker{display:none}
.contact-row>summary .chev{color:var(--muted);font-size:var(--text-10);transition:transform .15s}
.contact-row[open]>summary .chev{transform:rotate(90deg)}
.contact-row-body{padding:var(--space-8) 0 var(--space-4) var(--space-18)}
.sess-ticks{display:flex;flex-wrap:wrap;gap:var(--space-4) var(--space-16);font-size:var(--text-13)}
.sess-ticks .tick{color:var(--bad)}
.sess-ticks .tick.ok{color:var(--good)}
.sess-ticks .tick.amb{color:var(--amber-600)}
/* Right-hand checklist */
.chk-card{padding:var(--space-12) var(--space-14)}
.chk-head{font-size:var(--text-11);text-transform:uppercase;letter-spacing:var(--caps-track);color:var(--muted);
  font-weight:600;margin-bottom:var(--space-4)}
.chk-row{border-bottom:1px solid var(--line)}
.chk-row:last-child{border-bottom:none}
.chk-row>summary{display:flex;align-items:center;gap:var(--space-8);padding:var(--space-8) var(--space-2);font-size:var(--text-13);
  font-weight:600;cursor:pointer;list-style:none;user-select:none}
.chk-row>summary::-webkit-details-marker{display:none}
.chk-row>summary .cchev{margin-left:auto;color:var(--muted);font-size:var(--text-10);transition:transform .15s}
.chk-row[open]>summary .cchev{transform:rotate(90deg)}
.chk-ticks{padding:0 var(--space-2) var(--space-8) var(--space-18);font-size:var(--text-12)}
.chk-ticks .tick{color:var(--bad);padding:var(--space-1) 0}
.chk-ticks .tick.ok{color:var(--good)}
.chk-ticks .tick.amb{color:var(--amber-600)}
/* ---------- Progress checklist as a slide-out EDGE RAIL (2026-09-03) ---------- */
/* Collapsed it is a 38px strip of traffic lights against the right edge of the viewport;
   hover, click/tap or keyboard focus slides the full card in (chkrail.js adds .open).
   It grows LEFTWARDS out of a right-anchored strip, so expanding never reflows the page
   and — because nothing ever extends past right:0 — it can never create horizontal
   overflow either. Scoped to .chk-rail throughout: the SAME partial still renders as a
   plain inline card on /service/<id>/edit/, which is not a rail and must not change.
   z-index 52 puts it over #scrolltop (50), and `bottom:132px` then stops the strip short of
   the bottom-right corner entirely — that corner is already tenanted by #scrolltop (a 42px
   circle at bottom:22px, pushed to bottom:78px when the chat widget is present, so it can
   reach 120px up). Without that stand-off the strip would swallow clicks on the top of the
   scroll-to-top button. It needs nothing against .topbar: it starts below the header. */
.chk-rail{position:fixed;right:0;top:calc(var(--header-h) + 14px);bottom:132px;
  width:38px;z-index:52;pointer-events:none}
.chk-rail .chk-card{pointer-events:auto;position:absolute;right:0;top:0;width:38px;
  max-height:100%;overflow-y:auto;scrollbar-width:none;padding:var(--space-8) 0;
  border-radius:12px 0 0 12px;border-right:0;
  transition:width .16s ease,box-shadow .16s ease,padding .16s ease}
.chk-rail.open .chk-card{width:264px;scrollbar-width:thin;box-shadow:var(--shadow-lg);
  padding:var(--space-8) var(--space-14) var(--space-12)}
/* A very long checklist scrolls inside the strip; a scrollbar in a 38px gutter is just
   noise, so it is hidden until the card is open. */
.chk-rail:not(.open) .chk-card::-webkit-scrollbar{width:0;height:0}
/* One line per row so a clipped label can never make the strip tall; the dot is first in
   the flex row, so at 38px the dot is all that survives the clip. */
.chk-rail .chk-row>summary{white-space:nowrap;padding:var(--space-8) var(--space-12)}
.chk-rail.open .chk-row>summary{padding:var(--space-8) var(--space-2)}
.chk-rail:not(.open) .chk-row{border-bottom:none}
.chk-rail:not(.open) .chk-ticks{display:none}
.chk-rail:not(.open) .chk-row>summary .cchev{display:none}
/* Collapsed, only the dot is meant to show. Clipping alone left a 5px sliver of the first
   letter of every label down the strip ("B", "T", "F"…), so fade the words out instead —
   opacity, NOT display:none or visibility:hidden, because the labels must stay in the
   accessibility tree: they are what a screen reader reads for each row. */
.chk-rail .chk-row>summary>span:not(.cdot),.chk-rail .chk-toggle-ttl{
  transition:opacity .16s ease}
.chk-rail:not(.open) .chk-row>summary>span:not(.cdot),
.chk-rail:not(.open) .chk-toggle-ttl{opacity:0}
/* Collapsed, a click anywhere on the strip must OPEN the rail rather than toggle whichever
   <details> happened to be under the pointer — so the whole row stops taking clicks and the
   card itself becomes the target (chkrail.js pins on a click that is NOT on a row). It has
   to be the <details>, not just its <summary>: pointer-events:none only makes an element
   transparent to the hit test, so the event would just retarget onto the row above it.
   None of this affects Tab — pointer-events never removes anything from the focus order. */
.chk-rail:not(.open) .chk-row{pointer-events:none}
/* The "ticked off here after the booking is created" fallback (a new booking, no
   sessions yet) is a wrapped paragraph — it has no dot, so there is nothing for it to say
   in a 38px strip. */
.chk-rail:not(.open) .chk-card>.muted{display:none}
/* The rail's own handle: a chevron when collapsed, chevron + "Progress" when open. */
.chk-toggle{display:flex;align-items:center;gap:var(--space-8);width:100%;background:none;
  border:0;cursor:pointer;font:inherit;color:var(--muted);text-align:left;white-space:nowrap;
  padding:var(--space-4) var(--space-12) var(--space-6);
  font-size:var(--text-11);text-transform:uppercase;letter-spacing:var(--caps-track);font-weight:600}
.chk-toggle-ico{font-size:var(--text-14);line-height:1;transition:transform .16s ease}
.chk-rail.open .chk-toggle{padding-left:var(--space-2);padding-right:var(--space-2)}
.chk-rail.open .chk-toggle-ico{transform:rotate(180deg)}
.chk-toggle:focus-visible{outline:2px solid var(--orange);outline-offset:2px;border-radius:6px}
@media(max-width:900px){
  /* Greg, 2026-09-07: hidden below tablet width on purpose (was briefly narrowed to
     600px, but Greg wants it gone on tablet too, not just phone) — it doesn't fit
     alongside the content at these widths. The save bar below is NOT part of the rail
     and is untouched by this. */
  .chk-rail{display:none}
  /* The padding-right override for when the rail is hidden lives next to
     `.content:has(.chk-rail)` itself (further down this file) — a copy of the override
     placed HERE loses the cascade to that later, same-specificity, unconditional rule
     regardless of this block's media condition: source order breaks the tie, not query
     narrowness. Learned the hard way (2026-09-07) — shipped this exact bug once already. */
  .bkedit{grid-template-columns:1fr;padding-right:0}
  /* Annotations #154/#160/#305 (kept for the reasoning, superseded 2026-09-03): on a phone
     the stacked rail burned ~230px before the first field, so the save controls were
     flattened into one row and had to be position:fixed — sticky could never escape the
     short .bkedit-side box, and being fixed then meant clearing the nav rail with
     left:var(--nav-w) and reserving --saverail-h of space above the content. None of that
     is needed now: the checklist column is gone, the save bar lives at the top of the form
     itself, and plain position:sticky (base rule) holds for the whole page. All that is
     left to do here is let it span the narrow screen. */
  .bkedit-actions{width:auto;top:var(--header-h);padding:var(--space-8) var(--space-10);
    margin-bottom:var(--space-10)}
  .bkedit-actions .btn{padding:var(--space-7) var(--space-14);font-size:var(--text-13)}
  /* Greg, 2026-09-08: the primary button was stretching to fill the row on tablet —
     dropped the flex:1 1 auto override so it sizes to its own text like the desktop
     rule (line ~1139) already does. */
  .bkedit-actions .save-state{text-align:right;white-space:nowrap;
    font-size:var(--text-11)}
  /* The PLAIN (non-rail) checklist — /service/<id>/edit/ — still flattens into a chip row
     on a narrow screen. The booking pages' rail is display:none above. */
  .chk-card{display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-16);align-items:center;padding:var(--space-8) var(--space-12)}
  .chk-head{flex-basis:100%;margin-bottom:var(--space-2)}
  .chk-row{border-bottom:none}
  .chk-row>summary{padding:var(--space-4) 0;font-size:var(--text-12)}
  .chk-row:not([open]) .chk-ticks{display:none}
}

/* --- S2 availability overlay (availability_overlay.html) ------------------ */
.avail{border-collapse:separate;border-spacing:0;width:max-content;min-width:100%}
.avail th,.avail td{border-bottom:1px solid var(--line-2);border-right:1px solid var(--line-2);
  padding:var(--space-6);vertical-align:top;min-width:108px;max-width:150px}
.avail thead th{font-size:var(--text-11);text-transform:uppercase;letter-spacing:var(--caps-track);color:var(--muted);
  text-align:left;background:var(--surface-2);position:sticky;top:0;z-index:2;font-weight:600}
.avail thead th span{display:block;font-weight:600}
.avail thead th b{font-size:var(--text-12);color:var(--slate);letter-spacing:0;text-transform:none}
.avail thead th.today b{color:var(--orange-600)}
.avail th.who,.avail td.who{position:sticky;left:0;background:var(--card);z-index:3;min-width:150px;
  border-right:1px solid var(--line);font-size:var(--text-13);font-weight:600}
.avail thead th.who{background:var(--surface-2);z-index:4}
.avail td.who a{color:inherit;text-decoration:none}
.avail td.who a:hover{color:var(--orange-600)}
.avail th.we,.avail td.we{background:#f3f0ed}
.avail td.free{background:#eef7f0}
.avail .evt{display:block;font-size:var(--text-11);line-height:1.3;border-radius:7px;padding:var(--space-3) var(--space-7);margin:0 0 var(--space-3);
  background:var(--orange-50);color:var(--orange-600);border:1px solid transparent;border-left:3px solid var(--orange)}
.avail .evt a{color:inherit;text-decoration:none;font-weight:700}
.avail .evt a:hover{text-decoration:underline}
.avail .evt small{display:block;font-size:var(--text-10);opacity:.8}
.avail .evt.blk{background:#f4f3f2;color:#555;border-left:3px solid var(--grey-cool);font-weight:600}

/* ---------- Casting sheet grid (S2+) — adopts the Team Availability look ---------- */
.cs-wrap{overflow-x:auto;width:fit-content;max-width:100%;background:var(--card);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}
.cs-grid{border-collapse:separate;border-spacing:0;width:max-content;font-size:var(--text-13)}
.cs-grid th,.cs-grid td{border-bottom:1px solid var(--line-2);border-right:1px solid var(--line-2);padding:0}
.cs-grid thead th{background:var(--surface-2);padding:var(--space-7) var(--space-8);text-align:center;white-space:nowrap;font-weight:600;
  font-size:var(--text-12);color:var(--slate)}
.cs-grid thead th.who,.cs-grid td.who{position:sticky;left:0;z-index:2;background:var(--card);text-align:left;
  padding:var(--space-7) var(--space-10);min-width:190px}
.cs-grid thead th.who{background:var(--surface-2)}
/* Greg, 15 Oct 2026: the date header stays just under the top bar while the PAGE scrolls.
   .cs-wrap scrolls sideways, which makes CSS sticky stick to it rather than the page, so
   csstickyhead.js slides the header down by --cs-head-y (clamped to the grid's own height). */
.cs-grid thead th{position:relative;z-index:3;transform:translateY(var(--cs-head-y,0px))}
.cs-grid thead th.who{z-index:5}
.cs-grid thead th{vertical-align:top}
.cs-grid thead th.who{vertical-align:bottom}
.cs-dhead-date{font-size:var(--text-12);color:var(--ink);font-weight:700}
.cs-dhead-date a{color:inherit;text-decoration:none}
.cs-dhead-date a:hover{color:var(--orange-600)}
.cs-dhead-tools{display:flex;align-items:center;justify-content:center;gap:var(--space-4);margin-top:var(--space-4)}
.cs-dhead-form{display:inline-flex;margin:0}
.cs-push-icon{padding:var(--space-2) var(--space-4)}
.cs-push-icon .ic{width:14px;height:14px}
.cs-grid td.who a{color:inherit;text-decoration:none;font-weight:600}
.cs-grid td.who a:hover{color:var(--orange-600)}
.cs-grid td.who small{display:block;color:var(--muted);font-weight:400;font-size:var(--text-11)}
.cs-grid td.who .hs-row{gap:var(--space-8)}
/* #23 — role/character group header row with a clear visual break. */
.cs-grouprow td{background:#f6f2ee;border-top:2px solid var(--orange-200,#f0d9c0)}
.cs-grouphead{font-weight:700;font-size:var(--text-12);letter-spacing:.02em;text-transform:uppercase;
  color:var(--orange-600);padding:var(--space-6) var(--space-10)!important}
.cs-grouphead.who{position:sticky;left:0;z-index:2;background:#f6f2ee}
.cs-cell{width:100%;height:42px;min-width:56px;border:0;cursor:pointer;font-weight:700;
  font-size:var(--text-12);color:#20303a;font-family:inherit;position:relative}
.cs-cell small{display:block;font-weight:500;font-size:var(--text-10);opacity:.8}
.cs-cell.auto{opacity:.72}
.cs-cell.auto::after{content:'';position:absolute;top:3px;right:3px;width:4px;height:4px;
  border-radius:50%;background:#8a97a0}
.cs-cell:hover{box-shadow:inset 0 0 0 2px var(--orange)}
.cs-green{background:#d9f2e2}.cs-red{background:#f6d6d6}.cs-amber{background:#fbe6c6}
.cs-tent{background:#fdf3c4}.cs-grey{background:#e6e6e6;color:#8a8a8a}
/* I5 — "unasked": we have NOT asked, so no availability is assumed. A faint grey, quieter
   than "not interested", with a dot marker (see .auto::after) so it reads as blank/pending. */
.cs-unasked{background:#f0f0f0;color:#b5b5b5}
/* #113 — a known baseline conflict (booked elsewhere / time off) with no manual reply now
   renders as its real cs-red/cs-tent colour (see views_casting._auto_status_cell), not a
   grey "unasked" cell with a corner-dot marker — the old cs-clash-hard/soft rules are gone. */
/* Lifecycle states beyond a plain reply. */
.cs-await{background:#e7ecf1;color:#5b6b7a}          /* availability check sent — awaiting */
/* #30/#114 — "Selected (pre-offer)": a distinct BLUE, clearly not green/red/orange/yellow;
   an OUTLINE star (☆) tells it apart from Confirmed's filled star at a glance. */
.cs-cast{background:#cfe4fb;color:#12406e}
.cs-offer{background:#e6dcfb;color:#4a2f8f}          /* work offer SENT — purple */
/* #115 — the ✉ glyph renders visibly smaller than ★ at the same font-size in most system
   fonts; bump just this state's marker so the two read as the same visual weight. */
.cs-cell.cs-offer{font-size:17px}
/* #116 — "Confirmed": a FILLED star (★), still on the locked-in green. */
.cs-confirmed{background:#bfe9cf;color:#0f5a33;font-weight:700}  /* confirmed / locked */
.cs-dcol-off{color:var(--muted);font-weight:400;font-size:var(--text-11)}
/* #29 — quieter remove control (a muted trash icon that reddens on hover). */
.cs-remove{background:none;border:0;padding:var(--space-2);cursor:pointer;color:var(--muted);
  display:inline-flex;opacity:.55;line-height:0;border-radius:6px}
.cs-remove:hover{opacity:1;color:var(--bad);background:var(--bad-tint)}
.cs-remove .ic{width:14px;height:14px}
/* #30 — push-to-session button on a session-linked date column. */
.cs-push{display:inline-flex;align-items:center;gap:var(--space-3);background:#eaf2fc;border:1px solid #bcd6f4;
  color:#12406e;border-radius:7px;padding:var(--space-2) var(--space-6);font-size:var(--text-11);font-weight:600;cursor:pointer;
  font-family:inherit}
.cs-push:hover{background:#dcebfb}
.cs-push .ic{width:12px;height:12px}
/* I7 — the per-date "Send offers" button reads as the work-offer (purple) action. */
.cs-push-offer{background:#efe8fc;border-color:#c9b6f2;color:#4a2f8f}
.cs-push-offer:hover{background:#e5dafa}
/* Book this time (1 Oct 2026, nexus2/booktime.py) — the date header's "Everyone free
   13:00–16:00" line, its button, and the confirm panel (a native <dialog>). */
.cs-bookslot{display:inline-flex;align-items:center;gap:var(--space-3);background:var(--good-50);
  border:1px solid var(--good);color:var(--good);border-radius:7px;padding:var(--space-1) var(--space-6);
  font-size:var(--text-10);font-weight:700;margin-top:var(--space-2);cursor:help;white-space:nowrap}
.cs-bookslot.is-booked{cursor:default}
.cs-push-book{margin-top:var(--space-2)}
.cs-bookdlg{border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow-lg);
  padding:var(--space-16);width:min(360px,calc(100vw - 32px));background:var(--card);color:var(--ink);
  text-align:left;font-weight:400;white-space:normal}
.cs-bookdlg::backdrop{background:rgba(0,0,0,.3)}
/* Edit-casting popup on the play page (_casting_edit_modal.html, 15 Oct 2026). */
.castdlg{border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow-lg);
  padding:var(--space-18);width:min(520px,calc(100vw - 32px));max-height:calc(100vh - 48px);
  overflow:auto;background:var(--card);color:var(--ink)}
.castdlg::backdrop{background:rgba(0,0,0,.3)}
.castdlg-head{display:flex;justify-content:space-between;align-items:flex-start;gap:var(--space-12);
  margin-bottom:var(--space-12)}
.castdlg-name{font-size:var(--text-16);font-weight:700}
.castdlg-sub{font-size:var(--text-12);margin:var(--space-2) 0 var(--space-12)}
.castdlg-actions{display:flex;gap:var(--space-6);flex:none}
.castdlg-foot{font-size:var(--text-12);margin:var(--space-12) 0 0}
.castdlg-loading{padding:var(--space-22);text-align:center;color:var(--muted)}
.cs-bookdlg h4{margin:0 0 var(--space-6);font-size:var(--text-14)}
.cs-bookdlg label{display:block;font-size:var(--text-12);color:var(--muted);margin:var(--space-8) 0 0}
.cs-bookdlg .fld{display:block;width:100%;margin-top:var(--space-2)}
.cs-bookdlg-note{font-size:var(--text-12);margin:0 0 var(--space-4)}
.cs-bookdlg-times{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-8)}
.cs-bookdlg-online{display:flex !important;align-items:center;gap:var(--space-6);color:var(--ink) !important}
.cs-bookdlg-actions{display:flex;justify-content:flex-end;gap:var(--space-8);margin-top:var(--space-14)}
/* #24 — floating "why" tooltip. */
.cs-tip{position:absolute;z-index:60;max-width:240px;background:#20303a;color:var(--white);font-size:var(--text-12);
  line-height:1.35;padding:var(--space-7) var(--space-9);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.22);pointer-events:none}
/* Coverage badge — merged into the grid header `<th>` from the old coverage strip
   (S2g/h). It is a status `.pill` — ok = every role filled, warn = some, muted = none
   (19 Oct 2026, one pill); this class only spaces it under the date and tightens the gap. */
.cs-covbadge{gap:var(--space-3);margin-top:var(--space-2)}
.cs-covbadge .pinch{color:var(--bad);font-size:var(--text-11);cursor:help;line-height:1}
/* S2j — client scheduling-rule flag on a casting-sheet date column (advisory only). */
.cs-clientflag{display:inline-flex;align-items:center;gap:var(--space-3);background:#fdf0e2;border:1px solid #f0cfa0;
  color:#8a5a10;border-radius:7px;padding:var(--space-1) var(--space-6);font-size:var(--text-10);font-weight:600;margin-top:var(--space-2);cursor:help}
.cs-clientflag .ic{width:10px;height:10px}
/* S2l — "Suggest 5 to offer" advisory highlight (client-side only, never a DB fact). */
th.cs-suggested{background:#e7f7ee;box-shadow:inset 0 0 0 2px var(--good)}
.cs-legend{display:flex;gap:var(--space-12);flex-wrap:wrap;font-size:var(--text-12);margin:var(--space-10) 0}
.cs-legend span{display:inline-flex;align-items:center;gap:var(--space-5)}
.cs-legend i{width:13px;height:13px;border-radius:3px;display:inline-block}
.cs-legend i.cs-green{background:#d9f2e2}.cs-legend i.cs-cast{background:#cfe4fb}
.cs-legend i.cs-red{background:#f6d6d6}.cs-legend i.cs-amber{background:#fbe6c6}
.cs-legend i.cs-tent{background:#fdf3c4}.cs-legend i.cs-grey{background:#e6e6e6}
.cs-editor{position:absolute;z-index:50;background:var(--card);border:1px solid var(--line);
  border-radius:12px;box-shadow:0 12px 34px rgba(0,0,0,.18);padding:var(--space-12);width:238px}
.cs-editor h4{margin:0 0 var(--space-8);font-size:var(--text-13)}
.cs-editor label{display:block;font-size:var(--text-12);color:var(--muted);margin:var(--space-7) 0 var(--space-2)}
.cs-editor select,.cs-editor input[type=text]{width:100%;font-family:inherit;
  font-size:var(--text-13);padding:var(--space-6) var(--space-7);border:1px solid var(--line);border-radius:8px}
.cs-editor .row2{display:flex;gap:var(--space-6)}
.cs-editor .acts{display:flex;gap:var(--space-6);margin-top:var(--space-10)}
.addpanel summary{cursor:pointer;font-weight:600;font-size:var(--text-14);padding:var(--space-6) 0}

/* ---------- Whereabouts (Phase W) ---------- */
/* `.chip.wa-chip`: kind colours below set the chip's three colour variables; this keeps the
   dense-grid size (11px, 130px clamp with an ellipsis). */
.wa-chip{--chip-bg:transparent;--chip-fg:currentColor;--chip-bd:transparent;font-size:var(--text-11);
  font-weight:600;line-height:inherit;padding:var(--space-2) var(--space-9);white-space:nowrap;cursor:default;
  max-width:130px;overflow:hidden;text-overflow:ellipsis;margin:var(--space-1) 0}
/* Annotation #840 (Jacob): a long client list was cut off with an ellipsis. Let the chip
   wrap onto more lines at the same 130px width so all the text shows. */
.wa-grid td .wa-chip{white-space:normal;overflow:visible;text-overflow:clip;border-radius:var(--space-12);text-align:left;line-height:1.3}
a.wa-chip{cursor:pointer}
a.wa-chip:hover{filter:brightness(.96)}
/* Kind colours mirror the calendar's category legend (CATEGORY_META + travel/time-off
   in views.py) so a category reads the same on the whereabouts grid and the calendar. */
.wa-chip.office{--chip-bg:#fbeeda;--chip-fg:#9a5b12;--chip-bd:#f2d6ac}    /* Office  #f0a85c */
.wa-chip.remote{--chip-bg:#efe6f7;--chip-fg:#6b3fa0;--chip-bd:#ddccef}    /* Remote — purple, distinct from travel */
.wa-chip.travel{--chip-bg:#e6eef7;--chip-fg:#2b5c88;--chip-bd:#cadcee}    /* Travel  #3d7fb8 */
.wa-chip.delivery{--chip-bg:#e6f3ec;--chip-fg:#3a7a58;--chip-bd:#cbe7d6}  /* Delivery #5fae84 */
.wa-chip.off{--chip-bg:#e3f0fc;--chip-fg:#1a5ba8;--chip-bd:#c3dcf5}       /* Time off — blue, matches calendar's Time off category */
.wa-chip.off_recurring{--chip-bg:#f0eeec;--chip-fg:var(--muted);--chip-bd:var(--line)}  /* A standing weekly/fortnightly pattern, not real leave — deliberately quieter than .off (Greg, 18 Oct 2026) */
.wa-chip.holiday{--chip-bg:#fbe4e4;--chip-fg:#8a2f53;--chip-bd:#f3c6d8}   /* Public holiday */
.wa-chip.overcap{--chip-bg:#fbe4e0;--chip-fg:var(--bad);--chip-bd:#f3c6bd}  /* Over desk capacity — advisory only */
/* Hold = tentative (W9a): hatched + translucent, everywhere. */
.wa-chip.tentative{opacity:.72;border-style:dashed;
  background-image:repeating-linear-gradient(45deg,transparent,transparent 4px,rgba(255,255,255,.65) 4px,rgba(255,255,255,.65) 8px)}
/* Held sessions on the main FullCalendar read tentative, not solid-busy: a diagonal
   hatch layered over the (inline-set) tint + a dashed border. Applies in month/week/day;
   day + agenda also get a 'HOLD' text marker (calendar.html eventDidMount). */
.fc-event.tentative{border-style:dashed!important;
  background-image:repeating-linear-gradient(45deg,transparent,transparent 5px,rgba(255,255,255,.6) 5px,rgba(255,255,255,.6) 10px)}
.fc-event.tentative:hover{filter:none}
/* Team-today (dashboard) + roster avatars for people whose day is only tentatively held. */
.avatar.tentative{opacity:.7;outline:1.5px dashed var(--amber-600);outline-offset:1px}
/* Suggested/pattern rows: grey until the member confirms. */
.wa-chip.suggested{opacity:.6;border-style:dotted}
/* Personal (non-TEL) commitments read in from someone's own Outlook calendar
   (ROADMAP W9c/S4b). Deliberately the quietest thing on the calendar: it is context for
   the person whose calendar it is, never TEL work, so it must not compete with a real
   booking for attention. Grey fill + dotted edge, no category colour. */
.fc-event.personal{background-color:var(--tint-record)!important;
  border-color:var(--grey)!important;border-style:dotted!important;color:var(--ink)!important}
.fc-event.personal:hover{filter:none}
.wa-chip.personal{--chip-bg:var(--tint-record);--chip-bd:var(--grey);--chip-fg:var(--muted);border-style:dotted}
/* Add / edit your own whereabouts straight from the grid (W5). */
.wa-cell{position:relative}
.wa-add,.wa-chip-edit{display:inline-block}
.wa-add>summary{list-style:none;cursor:pointer;width:18px;height:18px;line-height:16px;
  text-align:center;border-radius:50%;border:1px dashed var(--line);color:#9a938a;
  font-size:var(--text-12);margin-top:var(--space-2);opacity:0;transition:opacity .1s}
.wa-add>summary::-webkit-details-marker{display:none}
.wa-cell:hover .wa-add>summary,.wa-add[open]>summary{opacity:1}
.wa-add[open]>summary{background:var(--cat-office);color:var(--white);border-style:solid;border-color:var(--cat-office)}
/* Own pill = click-to-edit; pencil only on hover, never persistent. */
.wa-chip-edit>summary{list-style:none;cursor:pointer}
.wa-chip-edit>summary::-webkit-details-marker{display:none}
.wa-pencil{margin-left:var(--space-4);font-size:var(--text-10);opacity:0;transition:opacity .1s}
.wa-chip-edit>summary:hover .wa-pencil,.wa-chip-edit[open] .wa-pencil{opacity:.85}
/* Whereabouts kind icons (KIND_META, 18 Oct 2026 — they replaced the emoji): text-sized. */
.wa-pencil svg.ic{width:10px;height:10px;display:block}
.wa-chip svg.ic,.wa-grid th svg.ic,.wa-today-tbl th svg.ic,.wa-kind svg.ic{width:12px;height:12px;vertical-align:-2px}
/* The add/edit popover. */
.wa-add-menu{position:absolute;z-index:50;margin-top:var(--space-4);background:var(--card);
  border:1px solid var(--line);border-radius:9px;box-shadow:0 8px 24px rgba(0,0,0,.16);
  padding:var(--space-8);display:flex;flex-direction:column;gap:var(--space-6);min-width:172px;max-width:90vw;text-align:left}
/* Right-hand day columns: open the popover leftward so it stays on-screen. */
td.wa-cell:nth-last-child(-n+2) .wa-add-menu{left:auto;right:0}
/* The grid scrolls on narrow screens, but on desktop let popovers overflow the
   card instead of being clipped by overflow-x:auto. */
.wa-scroll{overflow-x:auto}
@media(min-width:900px){.wa-scroll{overflow:visible}}
/* Annotation #492 — office utilisation's two trend tables have no add/edit popover to
   protect (unlike whereabouts_team.html's grid above), so the desktop `overflow:visible`
   escape hatch just above only uncovers a bug for them: at padding:0, the table's square
   corners sit flush with the card's rounded border, breaking the curve at all four
   corners once nothing clips them. Keep these two tables clipped at every width. */
.wa-scroll.wa-static{overflow:hidden}
/* Headshots inside the small round avatars (dashboard 'Team today' etc.). */
.avatar{overflow:hidden}
.avatar img{width:100%;height:100%;object-fit:cover;border-radius:50%;display:block}
.avatar.avatar-photo{background:transparent}   /* ringed headshots carry their own clear edge */
/* Transient toast (e.g. the one-click "Report a problem" confirmation) — created ONLY
   by waToast() in base.html, which is the site-wide notification component. The bare
   .tel-toast rules below keep the standalone/fallback case working; inside the
   #tel-toasts stack the container owns the positioning. */
.tel-toast{position:fixed;bottom:22px;left:50%;transform:translateX(-50%) translateY(10px);
  background:var(--ink-2);color:var(--white);padding:var(--space-11) var(--space-18);border-radius:11px;font-size:var(--text-13);font-weight:500;
  z-index:10000;opacity:0;transition:opacity .25s,transform .25s;box-shadow:0 10px 30px rgba(0,0,0,.28);
  max-width:90vw;text-align:center}
.tel-toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
/* Stack: top-right, just under the header, floating over the page so it never moves the
   layout (Greg, 2026-09-14 — page flash messages now arrive here too, via telFlashToasts in
   base.html). Toasts queue downward instead of overlapping. The width is set explicitly
   rather than shrink-to-fit so a short toast doesn't wrap onto three lines on a phone. */
.tel-toasts{position:fixed;top:calc(var(--header-h) + var(--space-8));right:var(--space-14);z-index:10000;
  width:min(380px, calc(100vw - 2 * var(--space-14)));
  display:flex;flex-direction:column;align-items:stretch;gap:var(--space-8);pointer-events:none}
.tel-toasts .tel-toast{position:static;transform:translateY(-8px);max-width:100%;
  display:flex;align-items:center;gap:var(--space-12);text-align:left;pointer-events:auto;
  padding:var(--space-11) var(--space-12) var(--space-11) var(--space-18)}
.tel-toasts .tel-toast.show{transform:translateY(0)}
.tel-toast-msg{flex:1 1 auto;min-width:0}
/* A toast that ASKS something (waToast opts.cls:'ask' — the room-change prompt, 24 Sep
   2026): the question gets the full width and its answers sit on a row underneath, so two
   buttons don't squeeze the words into a column on a phone. */
.tel-toasts .tel-toast.ask{flex-wrap:wrap;row-gap:var(--space-6)}
.tel-toast.ask .tel-toast-msg{flex-basis:100%}
/* Action (Undo). Orange on the near-black toast measures 5.2:1 — passes AA (the same
   orange on white does NOT; don't reuse this pairing on a light surface). */
.tel-toast-act{flex:none;background:none;border:0;font:inherit;font-weight:700;cursor:pointer;
  color:var(--orange);padding:var(--space-3) var(--space-6);border-radius:6px;text-decoration:underline;text-underline-offset:2px}
.tel-toast-act:hover{background:rgba(255,255,255,.1)}
.tel-toast-x{flex:none;background:none;border:0;cursor:pointer;color:#d8d3ce;
  font-size:17px;line-height:1;padding:var(--space-3) var(--space-6);border-radius:6px}
.tel-toast-x:hover{background:rgba(255,255,255,.1);color:var(--white)}
.tel-toast-act:focus-visible,.tel-toast-x:focus-visible{outline:2px solid var(--white);outline-offset:1px}
/* A coloured edge, not a coloured background, for success / warning flash messages: the
   white text stays on the near-black toast, so contrast is unchanged. */
.tel-toast.ok{box-shadow:inset 4px 0 0 var(--good),0 10px 30px rgba(0,0,0,.28)}
.tel-toast.warn{box-shadow:inset 4px 0 0 var(--amber-600),0 10px 30px rgba(0,0,0,.28)}
/* Error variant — orange would fall to 2.8:1 on this red, so the action goes pale. */
.tel-toast.err{background:#8e1f1f}
.tel-toast.err .tel-toast-act{color:#ffd9c9}
/* On a phone the toast is nearly full-width, so it runs under the two fixed bottom
   controls — .tel-anno-fab (bottom-left, admins) and #scrolltop (bottom-right, 42px).
   The Annotate pill was covering the job number, i.e. WHICH booking moved. Lift the
   stack clear of both; on desktop the toast is centred and never reaches them. */
@media(max-width:700px){.tel-toasts{bottom:76px}}
details.past-log>summary::-webkit-details-marker{display:none}
details.past-log[open]>summary .chev{transform:rotate(90deg)}
/* Report-a-problem confirm dialog (attaches context automatically, but the user
   writes a note + confirms Send, so a stray click never files a blank report). */
.wa-modal{position:fixed;inset:0;z-index:10001;background:rgba(20,18,16,.5);
  display:flex;align-items:center;justify-content:center;padding:var(--space-20)}
.wa-modal[hidden]{display:none}   /* class display:flex would otherwise beat the hidden attr */
.wa-modal-card{background:var(--card);border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,.3);
  padding:var(--space-20) var(--space-22);max-width:440px;width:100%}
.wa-modal-card h3{display:flex;align-items:center;gap:var(--space-8);margin:0 0 var(--space-6)}
.wa-modal-card h3 .ic{width:20px;height:20px}
.wa-modal-card p{font-size:var(--text-13);margin:0 0 var(--space-12)}
.wa-modal-card textarea{width:100%;box-sizing:border-box;min-height:96px;font-family:inherit;
  font-size:var(--text-14);padding:var(--space-10);border:1px solid var(--line);border-radius:9px;resize:vertical}
.wa-modal-actions{display:flex;justify-content:flex-end;gap:var(--space-10);margin-top:var(--space-14)}
.wa-add-menu .wa-f{width:100%;font-family:inherit;font-size:var(--text-13);padding:var(--space-5) var(--space-7);
  border:1px solid var(--line);border-radius:6px;background:var(--card);color:var(--ink-2)}
.wa-times input{flex:1}
.wa-hint{font-size:var(--text-11);color:#9a938a}
.wa-row2{display:flex;gap:var(--space-6)}
.wa-add-menu .wa-save{flex:1;border:none;background:var(--cat-delivery);color:var(--white);font-family:inherit;
  font-size:var(--text-13);font-weight:600;padding:var(--space-6) var(--space-9);border-radius:6px;cursor:pointer}
.wa-add-menu .wa-save:hover{filter:brightness(.95)}
.wa-add-menu .wa-del{border:1px solid #e0b4b4;background:var(--card);color:#b23b3b;font-family:inherit;
  font-size:var(--text-13);font-weight:600;padding:var(--space-6) var(--space-9);border-radius:6px;cursor:pointer}
.wa-add-menu .wa-del:hover{background:var(--bad-tint)}

/* Set-my-week day cards (mobile-first) */
/* 18 Oct 2026 redesign (Greg: "tacky", too much text/options, sections unclear on mobile):
   one .card per section (.wa-section) with ONE boundary, not a stack of boxed days inside
   it — each day is now a plain row separated by a hairline, same idiom as .row elsewhere. */
.wa-section{margin-top:var(--space-18)}
.wa-section:first-child{margin-top:0}
/* Phone: the week card sits right under the title bar — no grey band above it (Greg, 18 Oct 2026). */
@media (max-width:600px){.wa-section.wa-weekcard{margin-top:calc(-1 * var(--space-16));border-top:0;border-top-left-radius:0;border-top-right-radius:0}}
.wa-body{padding:0 var(--space-16) var(--space-14)}
/* A collapsible section card (My regular week): the heading row IS the toggle, a chevron on
   the right says so, and a closed card has no bottom rule or empty body under the heading. */
.wa-disclose{cursor:pointer;list-style:none;gap:var(--space-8)}
.wa-disclose::-webkit-details-marker{display:none}
.wa-disclose h3{margin-right:auto}
.wa-chev{display:inline-flex;color:var(--muted);transition:transform .15s}
.wa-chev .ic{width:18px;height:18px}
details[open]>.wa-disclose .wa-chev{transform:rotate(180deg)}
details:not([open])>.wa-disclose{border-bottom:0}
.wa-weeknav{display:flex;align-items:center;gap:var(--space-10);flex-wrap:wrap;margin-bottom:var(--space-6)}
.wa-weeknav-periods{margin:0}
/* My Availability week bar: ‹ [This week · 19 – 25 Oct] › on one line, then the week's state
   + "Same as last week" on one quiet line. The save status is the top-right pill's job. */
.wa-weekbar{display:grid;grid-template-columns:44px minmax(0,1fr) 44px;align-items:center;gap:var(--space-8);
  padding:var(--space-12) 0 var(--space-6)}
.wa-wk-arrow{display:flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:50%;
  border:1px solid var(--line);background:var(--card);color:var(--slate)}
.wa-wk-arrow:hover{border-color:var(--orange);color:var(--orange-700)}
.wa-wk-arrow .ic{width:18px;height:18px}
.wa-wk-prev .ic{transform:rotate(90deg)}
.wa-wk-next .ic{transform:rotate(-90deg)}
.wa-wk-title{display:flex;flex-direction:column;align-items:center;text-align:center;line-height:1.25;min-width:0}
.wa-wk-title b{font-size:var(--text-16)}
.wa-wk-title .muted{font-size:var(--text-12)}
.wa-weektools{display:flex;align-items:center;justify-content:center;gap:var(--space-10);flex-wrap:wrap;
  padding-bottom:var(--space-6)}
.wa-copylast{display:inline-flex;align-items:center;gap:var(--space-5);font-size:var(--text-12);font-weight:600;color:var(--slate)}
.wa-copylast .ic{width:14px;height:14px}
.wa-copylast:hover{color:var(--orange-700)}
.wa-savestate{display:none}
.wa-weekform{display:flex;flex-direction:column}
.wa-day{padding:var(--space-14) 0;border-top:1px solid var(--line)}
.wa-day:first-child{border-top:0;padding-top:var(--space-6)}
.wa-day-head{display:flex;align-items:center;gap:var(--space-8);flex-wrap:wrap;font-size:var(--text-14)}
.wa-blocked-msg{font-size:var(--text-12);margin-top:var(--space-6)}
.wa-derived{display:flex;gap:var(--space-6);flex-wrap:wrap;margin-top:var(--space-7)}
.wa-derived .wa-chip{filter:grayscale(.25)}
/* The common case (a weekend with nothing set) collapses to one line — Greg, 18 Oct 2026:
   work on a weekend is rare enough that showing the full picker by default every week is
   "redundant". Opens to the exact same picker as a weekday; nothing is lost, just hidden
   until asked for, same native-disclosure idiom "My regular week" already uses. */
.wa-weekend-pick>summary{cursor:pointer;color:var(--muted);font-size:var(--text-13);margin-top:var(--space-6);list-style:none}
.wa-weekend-pick>summary::-webkit-details-marker{display:none}
.wa-weekend-pick>summary:hover,.wa-weekend-pick[open]>summary{color:var(--ink)}
.wa-pick{display:flex;gap:var(--space-6);flex-wrap:wrap;margin-top:var(--space-9)}
.wa-opt input{position:absolute;opacity:0;pointer-events:none}
.wa-opt span{display:inline-flex;align-items:center;gap:var(--space-5);border:1px solid var(--line);border-radius:999px;
  padding:var(--space-7) var(--space-14);font-size:var(--text-13);font-weight:600;color:var(--slate);cursor:pointer;
  background:var(--card);transition:.1s;user-select:none}
.wa-opt span .ic{width:14px;height:14px}
.wa-opt input:checked + span{border-color:currentColor}
.wa-opt.office input:checked + span{background:var(--good-tint);color:#1f7a45}
.wa-opt.remote input:checked + span{background:#e8f1f8;color:#2a628f}
.wa-opt.travel input:checked + span{background:#f1eaf8;color:#6a3fb5}
.wa-opt.off input:checked + span{background:#ececec;color:#5f5f5f}
.wa-opt.tbc input:checked + span{background:#fdf3de;color:#8a6a1a}
.wa-opt:not(.office):not(.remote):not(.travel):not(.tbc) input:checked + span{background:#f4f3f2}
.wa-times{display:flex;gap:var(--space-8);flex-wrap:wrap;align-items:flex-end;margin-top:var(--space-9)}
.wa-times.off{display:none}
.wa-times label{font-size:var(--text-11);color:var(--muted);display:flex;flex-direction:column;gap:var(--space-2)}
.wa-times [hidden]{display:none}  /* the wk-off/wk-work toggle (_wa_day_picker.html JS) sets the `hidden` attribute, not style= — ".wa-times label{display:flex}" above is author CSS and would otherwise beat the UA [hidden] rule */
.wa-times input{border:1px solid var(--line);border-radius:8px;padding:var(--space-7);font-family:inherit;font-size:var(--text-13)}
.wa-wide{flex:1;min-width:130px}
.wa-alldayhint{font-size:var(--text-11);align-self:center}
/* My Availability day (Greg, 18 Oct 2026: "too long and too much"). One row of four equal
   segments; a picked day shows ONE line — "Full day" or the hours — that opens to From/To/
   Notes only when asked. Time off shows its Type select straight away. */
.wa-seg{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:0;border:1px solid var(--line);
  border-radius:var(--radius-sm);overflow:hidden;background:var(--card)}
.wa-seg .wa-clear{position:absolute;opacity:0;pointer-events:none;width:0;height:0}
.wa-seg .wa-opt{display:block;min-width:0;border-left:1px solid var(--line)}
.wa-seg .wa-opt:first-of-type{border-left:0}
.wa-seg .wa-opt span{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-2);
  border:0;border-radius:0;min-height:44px;padding:var(--space-6) var(--space-2);font-size:var(--text-12);
  white-space:nowrap;text-align:center}
.wa-seg .wa-opt span .ic{width:16px;height:16px}
.wa-seg .wa-opt input:focus-visible + span{outline:2px solid var(--ink);outline-offset:-2px}
.wa-times.wa-fields{display:flex;flex-direction:column;align-items:stretch;gap:var(--space-8);margin-top:var(--space-8)}
.wa-times.wa-fields.off{display:none}
.wa-times.wa-fields [hidden]{display:none}
.wa-times.wa-fields select,.wa-more-body input{width:100%;box-sizing:border-box;min-width:0;border:1px solid var(--line);
  border-radius:8px;padding:var(--space-7);font-family:inherit;font-size:var(--text-13);background:var(--card);color:var(--ink)}
.wa-type{font-size:var(--text-11);color:var(--muted);display:flex;flex-direction:column;gap:var(--space-2)}
.wa-more>summary{display:flex;align-items:center;justify-content:space-between;gap:var(--space-8);cursor:pointer;
  list-style:none;font-size:var(--text-13);color:var(--slate);min-height:32px}
.wa-more>summary::-webkit-details-marker{display:none}
.wa-more-link{color:var(--orange-600);font-weight:600;font-size:var(--text-12);flex:none}
.wa-more[open] .wa-more-link{color:var(--muted)}
.wa-more-sum{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.wa-more-body{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-10);margin-top:var(--space-6)}
.wa-more-body label{font-size:var(--text-11);color:var(--muted);display:flex;flex-direction:column;gap:var(--space-2);min-width:0}
.wa-more-body .wa-wide,.wa-more-body .wa-alldayhint{grid-column:1/-1}
.wa-more-body .wa-alldayhint{font-size:var(--text-11);margin-top:calc(-1 * var(--space-4))}
/* Already on the calendar that day: time first, so the day can be planned around it. */
.wa-cal{list-style:none;margin:var(--space-8) 0 0;padding:0;display:flex;flex-direction:column;gap:var(--space-4)}
.wa-cal-row{display:grid;grid-template-columns:16px 92px minmax(0,1fr);align-items:center;gap:var(--space-8);
  font-size:var(--text-13);padding:var(--space-6) var(--space-10);border-radius:var(--radius-sm);background:var(--bg)}
.wa-cal-row .ic{width:16px;height:16px;color:var(--muted)}
.wa-cal-time{font-weight:600;color:var(--ink);font-variant-numeric:tabular-nums;white-space:nowrap}
.wa-cal-what{min-width:0;color:var(--slate)}
.wa-cal-row.tentative{background:transparent;border:1px dashed var(--line)}
.wa-confirm:not(:has(.btn)){display:none}
.wa-confirm{position:sticky;bottom:0;background:linear-gradient(transparent,var(--bg) 30%);
  padding:var(--space-12) 0 var(--space-8);display:flex;align-items:center;gap:var(--space-12);flex-wrap:wrap}
.wa-confirm-note{font-size:var(--text-12)}
.wa-pattern-form{display:flex;flex-direction:column;gap:var(--space-6)}
.wa-pattern-row{display:flex;gap:var(--space-6);align-items:center;flex-wrap:wrap}
.wa-pattern-row select,.wa-pattern-row input{border:1px solid var(--line);border-radius:8px;
  padding:var(--space-6);font-family:inherit;font-size:var(--text-13)}
.wa-pattern-day{width:86px;font-size:var(--text-13)}
.wa-pattern-save{margin-top:var(--space-10);font-size:var(--text-13)}

/* Team grid (W5a) */
.wa-grid{width:100%;border-collapse:collapse;font-size:var(--text-12)}
.wa-grid th,.wa-grid td{border-bottom:1px solid var(--line);border-right:1px solid #f0eeea;
  padding:var(--space-7) var(--space-8);vertical-align:top;min-width:104px}
.wa-grid thead th{font-size:var(--text-11);text-transform:uppercase;letter-spacing:var(--caps-track);color:var(--muted);
  text-align:left;background:var(--surface-2);font-weight:600}
.wa-grid td.wa-name,.wa-grid th:first-child{position:sticky;left:0;background:var(--card);z-index:2;
  min-width:150px;font-weight:600;font-size:var(--text-13);white-space:nowrap;border-right:1px solid var(--line)}
.wa-grid td.wa-name .avatar{display:inline-flex;align-items:center;justify-content:center;
  border-radius:50%;background:#cfc9c0;color:var(--white);margin-right:var(--space-6);vertical-align:middle}
.wa-grid th.wa-today,.wa-grid td.wa-today{background:#fdf6ef}
.wa-grid td.wa-holiday-col{background:#fdf5f7}
.wa-grid td .wa-chip{display:inline-flex;margin:0 var(--space-3) var(--space-3) 0;cursor:pointer}
.wa-legend{display:flex;gap:var(--space-5);flex-wrap:wrap;margin-left:auto}
.wa-tip{position:fixed;z-index:9999;pointer-events:none;max-width:300px;background:var(--card);
  border:1px solid var(--line);border-left:3px solid var(--orange);border-radius:8px;
  box-shadow:0 8px 28px rgba(0,0,0,.18);padding:var(--space-8) var(--space-11);font-size:var(--text-12);line-height:1.45;
  color:var(--ink);opacity:0;transition:opacity .08s ease}
.wa-tip.show{opacity:1}

/* Dashboard "Team today" card (W5c) */
.wa-today-card{margin-top:var(--space-16);display:flex;align-items:center;gap:var(--space-14);flex-wrap:wrap}
.wa-today-card h3{font-size:var(--text-14);margin:0}
.wa-today-grp{display:flex;align-items:center;gap:var(--space-6);font-size:var(--text-13)}
.wa-today-grp .avatar{width:24px;height:24px;font-size:var(--text-10);display:inline-flex;align-items:center;
  justify-content:center;border-radius:50%;background:#cfc9c0;color:var(--white);margin-left:calc(-1 * var(--space-6));border:2px solid var(--white)}
.wa-today-grp .avatar:first-of-type{margin-left:0}

/* --- Q2: element annotation toolbar (Vercel-toolbar style) ------------------ */
body.tel-anno-on, body.tel-anno-on * { cursor: crosshair !important; }
.tel-anno-outline{ position:fixed; z-index:2147483000; pointer-events:none;
  border:2px solid var(--purple); background:rgba(124,77,255,.10); border-radius:4px;
  display:none; box-shadow:0 0 0 2px rgba(124,77,255,.15); transition:all .04s linear; }
/* Symbol only, and round — the pen, then the x (Greg, 2026-07-31, the same call he made on
   the messages bubble). Sized to match .ccw-fab in the opposite corner so the two floating
   controls read as a pair rather than as two unrelated widgets. */
.tel-anno-fab{ position:fixed; left:16px; bottom:16px; z-index:2147483001; cursor:pointer;
  display:inline-flex; align-items:center; justify-content:center;
  width:46px; height:46px; padding:0; line-height:1;
  border:1px solid var(--purple); background:var(--card); color:var(--purple); font-weight:700;
  font-size:var(--text-18);
  border-radius:50%; box-shadow:0 4px 14px rgba(0,0,0,.14); }
.tel-anno-fab.on{ background:var(--purple); color:var(--white); }
/* On a touch tablet the pen sat exactly on your name at the foot of the icon rail, so a tap
   on your name hit the pen instead (Angela's iPad, 1 Oct 2026). Park it just right of the rail. */
@media (min-width:601px) and (hover:none){ body.nav-mini .tel-anno-fab{ left:calc(var(--nav-w) + var(--space-12)); } }
/* On a phone the menu is a full-screen drawer (#63), and its own fixed z-index sits under
   the pen's — so with the drawer open the pen covered your profile row at the foot of the
   menu (ann #381). The drawer already has its own way out (.nav-x); hide the pen while
   it's open and it's back the instant you close the drawer. */
@media(max-width:600px){ body.nav-open .tel-anno-fab{ display:none; } }
/* Above a phone the pen moves INTO the menu, just above your headshot (Greg, 5 Oct 2026: the
   floating pen covered his face at the foot of the rail). Same 34px as .avatar, so in the
   collapsed rail and the open menu alike it sits in the headshot's own column. The floating
   pen stays for phones only. */
.nav-anno{ display:none; }
@media(min-width:601px){
  .tel-anno-fab{ display:none !important; }
  .nav-anno{ display:flex; align-items:center; justify-content:center; width:34px; height:34px;
    margin:0 0 var(--space-8) var(--space-10); padding:0; border-radius:50%; cursor:pointer;
    border:1px solid var(--purple); background:var(--card); color:var(--purple);
    font-size:var(--text-16); font-weight:700; line-height:1; }
  .nav-anno:hover{ background:var(--purple-50); }
  body.nav-mini .nav-anno{ margin-left:auto; margin-right:auto; }
  body.nav-mini.nav-peek .side .nav-anno{ margin-left:var(--space-10); }
  body.tel-anno-on .nav-anno{ background:var(--purple); color:var(--white); }
}
@media(min-width:601px) and (hover:hover){
  body.nav-mini .side:hover .nav-anno{ margin-left:var(--space-10); }
}
.nav-anno .na-on, body.tel-anno-on .nav-anno .na-off{ display:none; }
.tel-anno-fab svg.ic{ width:20px; height:20px; }
.nav-anno svg.ic, .nav-update .nu-ic svg.ic{ width:16px; height:16px; display:block; }
body.tel-anno-on .nav-anno .na-on{ display:inline; }
body.tel-anno-hidden .nav-anno{ display:none !important; }
@media print{ .nav-anno{ display:none !important; } }
/* "Update available" in the menu, above the pen (Greg, 5 Oct 2026) — an orange circle whose ↻
   turns slowly, in the same 34px column as the pen and the headshot. updatetoast.js sets
   body.tel-update-ready; above a phone that shows this and hides the floating pill. */
.nav-update{ display:none; }
.nav-update .nu-lbl{ position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); }
@media(min-width:601px){
  body.tel-update-ready .update-toast{ display:none !important; }
  body.tel-update-ready .nav-update{ display:flex; align-items:center; justify-content:center;
    width:34px; height:34px; margin:0 0 var(--space-8) var(--space-10); padding:0;
    border:none; border-radius:50%; cursor:pointer; background:var(--orange-600); color:var(--white);
    font-size:var(--text-18); font-weight:700; line-height:1; box-shadow:var(--shadow); }
  .nav-update:hover{ background:var(--orange-700); }
  body.nav-mini .nav-update{ margin-left:auto; margin-right:auto; }
  body.nav-mini.nav-peek .side .nav-update{ margin-left:var(--space-10); }
  /* With the rail fully open (or peeking/hover-open), the circle grows into a pill and the
     sr-only label becomes real text reading "Update available" (Greg, 5 Oct 2026 follow-up on
     #532 — collapsed it's just the spinning circle; expanded it should say what it is). */
  body:not(.nav-mini) .nav-update,
  body.nav-mini.nav-peek .side .nav-update{ width:auto; border-radius:17px; justify-content:flex-start;
    padding:0 var(--space-14) 0 var(--space-8); gap:var(--space-8); }
  body:not(.nav-mini) .nav-update .nu-lbl,
  body.nav-mini.nav-peek .side .nav-update .nu-lbl{ position:static; width:auto; height:auto;
    overflow:visible; clip:auto; font-size:var(--text-13); font-weight:600; white-space:nowrap; }
}
@media(min-width:601px) and (hover:hover){
  body.nav-mini .side:hover .nav-update{ margin-left:var(--space-10); width:auto; border-radius:17px;
    justify-content:flex-start; padding:0 var(--space-14) 0 var(--space-8); gap:var(--space-8); }
  body.nav-mini .side:hover .nav-update .nu-lbl{ position:static; width:auto; height:auto;
    overflow:visible; clip:auto; font-size:var(--text-13); font-weight:600; white-space:nowrap; }
}
.nav-update .nu-ic{ display:inline-block; animation:nu-spin 3s linear infinite; }
@keyframes nu-spin{ to{ transform:rotate(360deg); } }
@media(prefers-reduced-motion:reduce){ .nav-update .nu-ic{ animation:none; } }
@media print{ .nav-update{ display:none !important; } }
.tel-anno-dialog{ position:fixed; z-index:2147483002; width:300px; background:var(--card);
  border:1px solid var(--line); border-radius:12px; box-shadow:0 12px 34px rgba(0,0,0,.22);
  padding:var(--space-12); cursor:auto; }
.tel-anno-dialog .tel-anno-picked{ font-size:var(--text-12); color:var(--muted); background:#f6f5f2;
  border-radius:8px; padding:var(--space-7) var(--space-9); margin-bottom:var(--space-8); max-height:52px; overflow:auto;
  word-break:break-word; }
.tel-anno-dialog textarea{ width:100%; min-height:64px; border:1px solid var(--line);
  border-radius:8px; padding:var(--space-8); font:inherit; font-size:var(--text-13); resize:vertical; box-sizing:border-box; }
.tel-anno-actions{ display:flex; justify-content:flex-end; gap:var(--space-8); margin-top:var(--space-9); }
.tel-anno-btn{ cursor:pointer; border-radius:8px; font-size:var(--text-13); padding:var(--space-7) var(--space-13); border:1px solid var(--line); }
.tel-anno-btn.ghost{ background:var(--card); color:var(--slate-2); }
.tel-anno-btn.primary{ background:var(--purple); color:var(--white); border-color:var(--purple); font-weight:600; }
.tel-anno-btn:disabled{ opacity:.6; cursor:default; }
/* Phase 2 (2f): the inline "couldn't save, try again" message — the dialog stays open
   with the typed note intact rather than closing on a failed save. */
.tel-anno-err{ color:var(--bad); font-size:var(--text-12); margin-top:var(--space-6); }
.tel-anno-err[hidden]{ display:none; }

/* ---- Parity Phase 2: list filter bars + active pill state + raw-JSON editor ---- */
.filterbar{ display:flex; align-items:center; flex-wrap:wrap; gap:var(--space-7); margin:0 0 var(--space-14); }
.filterbar-lbl{ font-size:var(--text-12); font-weight:600; text-transform:uppercase;
  letter-spacing:var(--caps-track); color:var(--muted); }
a.pill{ text-decoration:none; border:1px solid var(--line); cursor:pointer; }
a.pill:hover{ background:#e4e4e4; }
.pill.on{ background:var(--orange-600); border-color:var(--orange-600); color:var(--white); }
.pill.on:hover{ background:var(--orange-700); }

/* ---- Reports landing page (reports.html): one card per report, in groups ---- */
.rpt-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:var(--space-14)}
a.rpt-card{display:block;padding:var(--space-18);text-decoration:none;color:inherit}
.rpt-card h3{margin:0 0 var(--space-6);color:var(--orange)}
.rpt-card p{margin:0;font-size:var(--text-13)}

/* ---- Reports → Booking forms (report_booking_forms.html, 12 Oct 2026) ---- */
.bf-filter{display:flex;flex-direction:column;gap:var(--space-12);margin-bottom:var(--space-14)}
.bf-search-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);margin-top:var(--space-4)}
.bf-search-row .fld{flex:1 1 220px;min-width:0}
/* The search box's type-ahead is the shared `.tel-search-select` combo (searchselect.js) —
   its flex sizing and the suggestions box's position/look are scoped under this wrapper
   class rather than the bare `.tel-search-select`/`.tel-search-suggestions` selectors, so
   nothing here touches how that combo looks on booking_form.html or the Who/invitee
   pickers, which still carry their own inline positioning. */
.bf-search-combo{position:relative;flex:1 1 220px;min-width:0}
.bf-search-combo .tel-search-suggestions{display:none;position:absolute;left:0;right:0;top:100%;
  margin-top:var(--space-2);background:var(--card);border:1px solid var(--line);border-radius:var(--radius-sm);
  box-shadow:var(--shadow-lg);max-height:260px;overflow:auto;z-index:20}
.bf-status{margin:0}
.bf-count{font-weight:400;opacity:.75}
.bf-card{padding:var(--space-4) 0}
.bf-job{font-weight:700;color:var(--orange-600);text-decoration:none;white-space:nowrap}
.bf-note{margin-top:var(--space-10)}
@media (max-width:600px){ .bf-tbl thead{display:none} }

/* ---- Collapsed "+ add" field groups (entity_form.html, opt-in via a form class's
   field_groups — see nexus2.forms.Styled) ---- */
.field-group{ margin:var(--space-14) 0 0; border:1px dashed var(--line); border-radius:11px;
  padding:var(--space-12) var(--space-14); background:#fafafa; }
.field-group>summary{ cursor:pointer; font-weight:600; font-size:var(--text-13); color:var(--slate-2); }
/* entity/venue forms' collapsed extras: the group's name left, "+ Add" hard right (15 Oct 2026). */
.field-group-add>summary{display:flex;align-items:center;gap:var(--space-8);list-style:none}
.field-group-add>summary::-webkit-details-marker{display:none}
.field-group-hint{ font-size:var(--text-12); color:var(--muted); margin:var(--space-8) 0 0; }
.field-group-hint a{ color:var(--orange-600); font-weight:600; text-decoration:none; }
.field-group-hint a:hover{ text-decoration:underline; }
.field-group .form-grid{ margin-top:var(--space-10); }

/* --- Annotate button show/hide (nav toggle) --- */
.annotate-hide-toggle .anno-show-lbl{ display:none; }
body.tel-anno-hidden .annotate-hide-toggle .anno-hide-lbl{ display:none; }
body.tel-anno-hidden .annotate-hide-toggle .anno-show-lbl{ display:inline; }
body.tel-anno-hidden .tel-anno-fab{ display:none !important; }

/* --- Dashboard KPI tiles: keep to two rows on tablet/phone --- */
@media (max-width:1180px){ .grid.kpis{ grid-template-columns:repeat(3,1fr); } }
@media (max-width:600px){ .grid.kpis{ grid-template-columns:repeat(2,1fr); } }

/* --- #65: dashboard money row (Income/Outgoings/Margin) stays 2-up + compact on
   phone instead of inheriting .cols-3's single-column stack — combined with the
   .kpi shrink above, this keeps the first 7 dashboard tiles from taking over the
   whole screen on mobile. Scoped to .dash-money (dashboard.html only) so the
   shared .cols-3 class keeps its normal look on financials/cost_report/resources. */
@media (max-width:600px){
  .dash-money{ grid-template-columns:1fr 1fr; gap:var(--space-10); }
  .dash-money .money-tile{ padding:var(--space-10) var(--space-12); }
  .dash-money .money-tile .big{ font-size:22px; }
}

/* --- Team-today card as a compact status table (Office/Remote/… columns) --- */
.wa-today-tbl{ width:100%; border-collapse:collapse; margin-top:var(--space-6); }
.wa-today-tbl th{ text-align:left; font-size:var(--text-11); font-weight:600; color:var(--slate-2); white-space:nowrap; vertical-align:bottom; }
.wa-today-tbl td{ vertical-align:top; border-top:1px solid var(--line); }
.wa-today-col{ display:flex; flex-wrap:wrap; gap:var(--space-4); }
.wa-today-tbl .wa-empty{ color:var(--slate-2); font-size:var(--text-12); }

/* ============================================================================
   MOBILE HARDENING (phones, <=700px) — 2026-07-13
   Two long-standing phone complaints, fixed at the source:
     1. Pages were wider than the screen, so the browser zoomed the whole page
        out to fit → tiny text + a jittery half-collapsed menu. Cause: long
        unbreakable text (emails, URLs, org names) and the odd wide element
        pushing the layout past the viewport.
     2. A page's top action-button row (booking detail, profiles, calendar
        filters) wrapped into 5-6 lines and shoved the real content off-screen.
   ============================================================================ */

/* --- 1. Never let the page pan sideways; keep text inside its box. ---------- */
@media (max-width:700px){
  /* overflow-x:clip stops the page itself from ever being wider than the phone
     (so no pinch-to-fit), WITHOUT creating a scroll container — sticky topbar &
     the off-canvas nav still work. Wide data tables/calendars keep their own
     inner overflow-x:auto scrollers, so nothing real is lost. */
  html,body{max-width:100%;overflow-x:clip}
  /* Long words / emails / URLs / names wrap onto the next line instead of
     spilling past the right edge. Doesn't touch white-space:nowrap table cells
     (those live in scrollable tables). */
  /* overflow-wrap (not word-break): breaks long emails/URLs when they'd overflow,
     but keeps each element's min-content intact — so table cells don't collapse to
     one-character-per-line the way word-break:break-word made them. */
  .content{overflow-wrap:break-word;overflow-x:clip}
  /* Media & embeds never exceed the column. */
  .content img,.content video,.content iframe,.content table{max-width:100%}
}

/* --- 2. Fold tall action-button clusters behind a compact toggle. ----------
   base.html tags known clusters (.detail-head/.profile .actions, #cat-toggles)
   with .m-collapsed + inserts a .m-collapse-btn before them. Everything here is
   phone-scoped, so desktop/tablet is untouched: the toggle stays hidden and the
   cluster shows exactly as it always has. */
.m-collapse-btn{display:none}
@media (max-width:700px){
  .m-collapse-btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--space-8);
    width:100%;margin:var(--space-4) 0;padding:var(--space-11) var(--space-15);border-radius:11px;border:1px solid var(--line);
    background:var(--card);color:var(--ink);font-family:inherit;font-weight:600;font-size:15px;cursor:pointer}
  .m-collapse-btn::after{content:'';width:8px;height:8px;border-right:2px solid currentColor;
    border-bottom:2px solid currentColor;transform:rotate(45deg);opacity:.5;
    margin-top:calc(-1 * var(--space-3));transition:transform .15s}
  .m-collapse-btn[aria-expanded=true]::after{transform:rotate(-135deg);margin-top:var(--space-3)}
  .m-collapsed{display:none !important}
  /* When an .actions cluster is expanded, stack its buttons full-width so each is
     an easy tap target instead of a cramped wrapping row. */
  .m-actions-open{display:flex;flex-direction:column;align-items:stretch;gap:var(--space-8);width:100%}
  .m-actions-open > a,.m-actions-open > button,.m-actions-open > form,
  .m-actions-open > .subscribe,.m-actions-open > details{width:100%}
  .m-actions-open > a.btn,.m-actions-open > button.btn,
  .m-actions-open .subscribe > summary,.m-actions-open form > button,
  .m-actions-open form > .btn{width:100%;justify-content:center}
}

/* --- 3. Record/log tables that stack into cards on phones. ------------------
   Multi-column tables (booking-form history, emails, edits log) were bleeding
   off the right edge — and once text-wrapping kicked in, a squeezed column
   rendered one character per line. On phones, mark such a table `.mtbl` and its
   rows become stacked cards: each cell on its own full-width line, readable, no
   sideways scroll. Inline width:/white-space:nowrap on the cells are overridden.
   Desktop keeps the normal table. */
@media (max-width:600px){
  table.mtbl,table.mtbl tbody,table.mtbl tr,table.mtbl td{display:block;width:auto}
  table.mtbl tr{border:1px solid var(--line);border-radius:12px;padding:var(--space-8) var(--space-12);
    margin:0 var(--space-12) var(--space-10);background:var(--card)}
  table.mtbl tr + tr{margin-top:0}
  table.mtbl td{width:auto !important;max-width:100% !important;
    white-space:normal !important;border:none !important;padding:var(--space-3) 0 !important;
    vertical-align:top}
  table.mtbl td:empty{display:none}          /* drop blank spacer cells */
}

/* ---------- Session accordion (shared) ------------------------------------
   ONE accordion, two surfaces: the booking detail page's "Services & delivery"
   list and the bookings list (`.svc` cards, _ml_card.html). The class contract
   is the detail page's, reused verbatim rather than reinvented:
     .svc-acc   a session panel (<details>);  .is-past  a delivered one
     summary    the header row;  .chev  the ▶ that rotates when open
     .svc-sec   the session EDITOR's own internal cards — never a session panel
   These rules were the detail page's inline <style>; they live here so the two
   surfaces can't drift apart.

   Governing rule (Greg, annotation #176): "past sessions don't need to be
   hidden, just collapsed accordions." Collapse only — nothing may become
   unreachable, and print re-opens the lot (see the @media print block below). */
.svc-acc>summary{cursor:pointer;list-style:none;display:flex;gap:var(--space-12);align-items:center;padding:var(--space-10) var(--space-14);font-size:var(--text-13);user-select:none}
.svc-acc>summary::-webkit-details-marker{display:none}
.svc-acc[open]>summary{border-bottom:1px solid var(--line)}
.svc-acc>summary .chev{margin-left:auto;transition:transform .15s;font-size:var(--text-11);color:var(--muted)}
.svc-acc[open]>summary .chev{transform:rotate(90deg)}
.svc-acc .cdot{width:9px;height:9px;border-radius:50%;flex:none;display:inline-block}
.cdot.st-green{background:var(--good)}.cdot.st-amber{background:var(--amber-400)}.cdot.st-red{background:var(--bad)}
/* A delivered session reads quieter, but stays a normal row you can open. */
.svc-acc.is-past>summary{opacity:.72}
.svc-acc.is-past>summary:hover{opacity:1}

/* --- Session header row (.sess-sum) — ONE header, both surfaces ------------
   Greg, 2026-09-13: "the bookings list page sessions are not the same as the
   bookings page sessions ... they should be though". So the booking page's session
   header moved here from that page's own <style> (where it was scoped on purpose
   while the list still had its own `.ml-head` row, now retired) and the list uses
   it too (_ml_card.html adds `.ml-sum` for the few list-only bits below).
     • Two lines while CLOSED (2026-09-07: "make each row two lines in height even
       when it's closed"); ONE row while open (2026-09-13), where line 2's `.muted`
       items (venue, times) and the team faces go because the open body shows them.
     • Sticky under the top bar (walkthrough 2026-09-09, Davina: "so you know that
       you're working within that session") — and under the booking page's
       shortcut strip where there is one (`--bk-strip-h`, set on `.bk-page`).
       Opaque, with the card's own radius on the top corners; on the BOTTOM corners
       too while closed, because a closed row is nothing but this summary and its
       square corners poked out past the rounded border (Greg, 2026-09-13).
     • z-index under the top bar's 20 — the top bar always wins.
   A flex item with no basis won't shrink below its content (min-width:auto), which
   starved the session title at tablet width (2026-09-07) — hence min-width:0. */
/* THREE slots on a grid (Greg, 2026-09-14): line 1, the `.sess-end` cluster (⧉ ✎ ▶), and
   line 2. The ⧉ ✎ ▶ cluster stays on the TOP line whether the row is open or closed, and the
   team faces ride on line 2 while closed — "the series of team member headshots should be in
   the second row and the duplicate/edit/chevron should remain in the top row". A flex column
   couldn't do that without the cluster living inside line 1, which would have put it in the
   MIDDLE of the one-row open header (before line 2's pills) instead of hard right.
     closed:  [ line 1 ........................ ][ ⧉ ✎ ▶ ]
              [ line 2 (times · venue · pills · faces) ]
     open:    [ line 1 ........ ][ line 2 pills ][ ⧉ ✎ ▶ ]   */
.svc-acc>summary.sess-sum{display:grid;grid-template-columns:minmax(0,1fr) auto;
  grid-template-areas:"l1 end" "l2 l2";column-gap:var(--space-12);row-gap:var(--space-4);align-items:center;
  position:sticky;top:calc(var(--header-h) + var(--bk-strip-h, 0px));z-index:12;background:var(--card);
  border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}
.sess-sum .sess-line1,.sess-sum .sess-line2{display:flex;align-items:center;gap:var(--space-12);min-width:0}
.sess-sum .sess-line1{grid-area:l1}
/* Line 2 may wrap at ANY width, not just on phones (2026-09-15): on a half-screen laptop
   window (~756px, Greg's usual) the times + status pills + faces don't fit even with the
   venue shrunk to nothing, and the no-wrap row pushed the whole page 2px sideways. With
   room to spare nothing wraps, so wide screens look exactly as before. */
.sess-sum .sess-line2{grid-area:l2;flex-wrap:wrap;row-gap:var(--space-4)}
.sess-sum .sess-end{grid-area:end;display:flex;align-items:center;gap:var(--space-12);justify-self:end}
.sess-sum .sess-line2 .faces{margin-left:auto}
/* Annotation #549 — the send-status pills sit to the right of the venue/status pills and
   push the team faces along with them; margin-left:auto on .faces itself only fires when
   it's the first thing after the wrapping pills (no emails to show on this row). */
.sess-sum .sess-line2 .ml-email-status{margin-left:auto;display:flex;align-items:center;gap:var(--space-6);flex-wrap:wrap}
.sess-sum .sess-line2 .ml-email-status+.faces{margin-left:0}
/* Annotation #549 (Greg, 6 Oct 2026) — "when the session is expanded, remove the pills
   from the header row... when it's collapsed, we should see only the pills... not the
   buttons at the bottom". The open card's own email buttons (.email-actions, in the body)
   already carry the same sent/not-sent colouring, so the header pills would just repeat it
   while expanded — hide them there and let the collapsed row (where .email-actions is
   native-hidden inside the closed <details>) carry on showing them. */
.svc-acc[open] .sess-sum .ml-email-status{display:none}
.svc-acc:not([open])>summary.sess-sum{border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius)}
.svc-acc[open]>summary.sess-sum{grid-template-columns:minmax(0,1fr) auto auto;grid-template-areas:"l1 l2 end"}
/* Greg, 20 Oct 2026: the stuck header "starts running into the curved edges above/below".
   TOP: once stuck, the header's rounded corners sat inside the card's straight side borders,
   so a square border corner and the scrolling body poked out around each curve. The header
   now draws the card's own outline itself (--acc-edge mirrors the card's border colour in
   each state: category edge, .sc-edge-orange, the hover ring and its focus-within undo —
   `border-color:inherit` can't do it, Chrome's <details> shadow wrapper breaks the
   inheritance and it fell back to the text colour), laid exactly over the card's border by the -1px
   margin. While it is stuck (stickyhead.js sets .is-stuck) its top corners go square, so it
   meets the card's straight sides with no notch. A first try painted page colour into the
   notches instead, but the card's shadow makes the page around it darker, so the patch
   showed, and its anti-aliased edge left a stray pixel on the corner (Greg, same day). Unstuck, the outline and the card's sit
   on the same pixels, so nothing changes. BOTTOM: sticky stops at the card's content box, so
   an open card keeps a radius of padding below its body (cancelled by the last child's
   negative margin, so the card is the same height) — the header now parks above the bottom
   curve instead of its square corners poking through it. */
.svc-acc{--acc-edge:var(--cat-edge,var(--line))}
.svc-acc.sc-edge-orange{--acc-edge:var(--orange)}
.svc-acc.card:hover{--acc-edge:var(--orange-600)}
.svc-acc.card:hover:focus-within{--acc-edge:var(--line)}
.svc-acc.card[class*="ml-cat-"]:hover:focus-within{--acc-edge:var(--cat-edge)}
.svc-acc>summary.sess-sum{border:1px solid var(--acc-edge);margin:-1px -1px 0}
.svc-acc:not([open])>summary.sess-sum{margin-bottom:-1px}
.svc-acc[open]>summary.sess-sum{border-bottom-color:var(--line)}
.svc-acc>summary.sess-sum.is-stuck{border-top-left-radius:0;border-top-right-radius:0}
.svc-acc[open]:has(>summary.sess-sum){padding-bottom:var(--radius)}
.svc-acc[open]:has(>summary.sess-sum)>:last-child{margin-bottom:calc(-1 * var(--radius))}
.svc-acc[open]>summary.sess-sum .sess-line2>.muted,
.svc-acc[open]>summary.sess-sum .faces{display:none}
/* The action pills in that header (_session_head_actions.html): ✎ + ⧉ while idle,
   Close + the trash while the row's editor is open (`data-editing`). Both states are
   stated positively. `.sess-pill` is only a size — the pills are `.btn ghost`. The
   trash keeps its distance from Close: a destructive control flush against the one
   people are reaching for is how the old bottom bar nearly ate a session. ✎ and ⧉ are
   both drawn icons from _icon.html, so they are the same size (Greg, 2026-09-13: the
   pencil was a text glyph and read smaller). */
.svc-acc .sess-head-act{display:inline-flex;align-items:center;gap:var(--space-6);flex:none;margin-left:var(--space-4)}
.svc-acc .sess-head-act form{display:inline-flex;margin:0}
.svc-acc .sess-pill{font-size:var(--text-12);padding:var(--space-3) var(--space-10);border-radius:999px;line-height:1.5}
.svc-acc .sess-head-act .btn.danger{margin-right:var(--space-8)}
.svc-acc .sess-act-edit{display:none}
.svc-acc[data-editing] .sess-act-edit{display:inline-flex}
.svc-acc[data-editing] .sess-act-idle{display:none}
/* .sess-edit-pill — moved here from booking_detail.html's own <style> block (2026-09-16)
   so the bookings list (_ml_card.html) shares the same toggle as the booking page. */
.svc-acc .sess-edit-pill{display:none}
.svc-acc[data-editing] .sess-edit-pill{display:inline-flex}
.svc-acc .sess-pill.sess-ico{padding:var(--space-3) var(--space-9);min-width:32px;justify-content:center;font-size:var(--text-13);line-height:1.3}
.svc-acc .sess-dup{display:inline-flex}
.svc-acc .sess-dup>summary{list-style:none}
.svc-acc .sess-dup .sc-pop{text-align:left;font-weight:400;white-space:normal;cursor:default;line-height:1.4}
.svc-acc .sess-dup form{display:block}
/* Bookings list only (.ml-sum): which job (pill + client, a link into it), times that
   stay visible when open (the list's card body has none), and the latest job note as a
   quiet third line while closed. `.svc` clips its overflow for its own rounded corners,
   which would stop this sticky header sticking — the header rounds itself instead. */
.svc-acc.svc{overflow:visible}
.ml-sum .ml-job{display:inline-flex;align-items:center;gap:var(--space-6);flex:0 1 auto;min-width:0;
  color:inherit;text-decoration:none;white-space:nowrap;overflow:hidden}
.ml-sum .ml-job b{overflow:hidden;text-overflow:ellipsis}
.ml-sum .ml-job:hover b{color:var(--orange-600);text-decoration:underline}
.ml-sum .ml-times{color:var(--muted)}
.ml-sum .ml-snote{grid-column:1/-1}
.svc-acc[open]>summary.ml-sum .ml-snote{display:none}
/* On the LIST a click on a CLOSED header row opens the card; a click on an OPEN one opens
   the job (Greg, 15 Oct 2026 — was: always the job, chevron only to fold, 2026-09-14;
   sessacc.js does the routing, keyed on `data-open-href`). So the name lights up on
   hover only while the row is open — that is when a click goes to the job. So the
   chevron gets a real, square target with a hover state, and hovering anywhere else on
   the row lights the client name the way hovering the name itself always did. */
.ml-sum .chev{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;
  border-radius:var(--radius-sm)}
.ml-sum .chev:hover{background:var(--bg);color:var(--ink)}
.svc-acc[open]>.ml-sum:hover:not(:has(.chev:hover,.sess-head-act:hover)) .ml-job b{color:var(--orange-600);text-decoration:underline}
/* A cancelled session keeps its strike-through on hover too (Jacob #378). */
.ml-sum .ml-job:hover b.sc-strike,.svc-acc[open]>.ml-sum:hover:not(:has(.chev:hover,.sess-head-act:hover)) .ml-job b.sc-strike{text-decoration:underline line-through}
/* --- the call sheet's reading order, on the office list ---------------------
   Greg, 2026-09-23: "on mobile, bookings list should read in the same style as call
   sheet", and on a laptop the same order with the same row height. Only the IDENTIFYING
   facts follow the engagement card — date, client, job number, course. The working detail
   a job row also carries (times, venue, status pills, team faces, ⧉ ✎ ▶) stays on its own
   line, because a call sheet card has none of it and squeezing it into that shape would
   cost the scan this page exists for.
     laptop:  WED 30 SEPT  Woodside Energy · Leadership Phase 2      ⧉ ✎ ▶
              09:00–16:00 · Bellevue Gold · HOLD · 👤👤👤
     phone:   WED 30 SEPT                                           ⧉ ✎ ▶
              Woodside Energy
              26-107B
              Leadership Phase 2 Stage 1
              09:00–16:00 · Bellevue Gold · HOLD · 👤👤👤
   `:not(.chg-inline)` keeps the yellow "date changed since the last snapshot" highlight
   readable — that rule paints its own background and must win on the colour. */
.sess-sum .sess-date:not(.chg-inline){font-family:var(--sans);font-size:var(--text-12);
  font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.ml-sum .ml-job b{font-size:var(--text-16)}
.ml-sum .sess-name{font-size:var(--text-13)}
/* Phone: an OPEN row keeps its two lines stacked — side by side they cut the date to
   "Mon 14 Sep 2" at 390px — and either line may wrap rather than squeeze. On the list
   the course drops to its own line so the client keeps its room (the 2026-07 "two
   pixels wide" bug was this same squeeze). */
@media(max-width:600px){
  .svc-acc[open]>summary.sess-sum{grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"l1 end" "l2 l2"}
  .sess-sum .sess-line1,.sess-sum .sess-line2{flex-wrap:wrap;row-gap:var(--space-4)}
  .sess-sum .sess-end{align-self:start}
  /* `.sc-ellip` is flex:1 with a zero basis, so on a phone the venue shrank to one letter
     ("P") beside the times instead of wrapping. A basis makes it take the next line. */
  .sess-sum .sess-venue{flex:1 1 10em}
  /* The engagement card's stack: date / client / job number / course, one per line.
     `display:contents` lets the job pill and the client out of the link that wraps them so
     they can take separate rows — the same trick the call sheet header uses on
     `.svc-head-link`; the link itself still works, its children are still inside it. */
  .ml-sum .ml-job{display:contents}
  .ml-sum .sess-line1{gap:var(--space-4) var(--space-8)}
  .ml-sum .sess-date{order:1;flex:0 0 auto}
  .ml-sum .ml-job b{order:2;flex:1 1 100%}
  .ml-sum .ml-job .job-pill{order:3;flex:0 0 auto}
  .ml-sum .sess-name{flex-basis:100%;order:4}
  /* With a row to itself, a long client or course should WRAP, not end in "…". Both carry
     ellipsis rules for the one-line laptop layout ( .ml-job b, .sc-ellip ), which cut
     "Royal Perth Bentley Group – Ed…" off mid-word on a phone. */
  .ml-sum .ml-job b,.ml-sum .sess-name{white-space:normal;overflow:visible;text-overflow:clip}
  .bk-strip .btn{height:32px;padding:0 var(--space-10);font-size:var(--text-12)}
}

/* --- Print ----------------------------------------------------------------
   A collapsed accordion MUST NOT print blank. The bookings list is carried on
   paper — that is the whole point of it — so print force-opens every session
   panel whatever is collapsed on screen. Scoped to the accordion classes so it
   can't spring open unrelated <details> (nav popovers, the edits log).
   ::details-content is the only CSS handle on a closed <details>'s contents;
   sessacc.js also opens them on beforeprint for engines without it. */
@media print{
  .svc-acc::details-content{content-visibility:visible!important;
    block-size:auto!important;opacity:1!important}
  .svc-acc>summary{border-bottom:1px solid var(--line)}
  .svc-acc>summary .chev{display:none}
  .svc-acc.is-past>summary{opacity:1}
  .svc-acc{break-inside:avoid}
}

/* "What's new" release log (settings/whats_new.html) — one accordion row per
   NEXUS2.md/ROADMAP.md section. .rl-body renders releaselog.render_markdown()'s
   output, so it needs its own light type styles (headings/lists/quote/code)
   since it's the only page in the app rendering hand-parsed Markdown bodies. */
.rl-entry{border-top:1px solid var(--line)}
.rl-entry:first-of-type{border-top:none}
.rl-entry>summary{list-style:none;cursor:pointer;padding:var(--space-14) var(--space-16);display:flex;
  align-items:center;justify-content:space-between;gap:var(--space-12);flex-wrap:wrap}
.rl-entry>summary::-webkit-details-marker{display:none}
.rl-entry>summary .chev{margin-left:auto;font-size:var(--text-11);color:var(--muted);
  transition:transform .15s;flex:none}
.rl-entry[open]>summary .chev{transform:rotate(90deg)}
.rl-summary-main{display:flex;align-items:center;gap:var(--space-10);min-width:0}
.rl-summary-meta{display:flex;align-items:center;gap:var(--space-10);flex:none}
.rl-body{padding:0 var(--space-16) var(--space-16);font-size:var(--text-14);line-height:1.55}
.rl-body p{margin:0 0 var(--space-10)}
.rl-body p:last-child{margin-bottom:0}
.rl-body h4{font-size:var(--text-14);margin:var(--space-14) 0 var(--space-6)}
.rl-body h4:first-child{margin-top:0}
.rl-body ul,.rl-body ol{margin:0 0 var(--space-10);padding-left:var(--space-22)}
.rl-body li{margin-bottom:var(--space-5)}
.rl-body li:last-child{margin-bottom:0}
.rl-body code{font-family:ui-monospace,monospace;font-size:var(--text-12);background:var(--bg-soft,#f7f7f7);
  padding:var(--space-1) var(--space-5);border-radius:5px}
.rl-quote{margin:0 0 var(--space-10);padding:var(--space-6) var(--space-12);border-left:3px solid var(--orange);
  color:var(--muted);font-style:italic}
.rl-quote:last-child{margin-bottom:0}

/* ---- What's-new "View" button + the tour glow it lands on ---------------- */
/* The per-line-item View button on the release log. Sits inline at the end of
   the bullet it belongs to, so it reads as "this item -> that place". */
.rl-view{display:inline-block;margin-left:var(--space-6);padding:var(--space-1) var(--space-8);
  border:1px solid var(--orange-600);border-radius:999px;
  font-size:var(--text-11);font-weight:600;line-height:1.7;
  color:var(--orange-600);text-decoration:none;white-space:nowrap;vertical-align:baseline}
.rl-view:hover{background:var(--orange-600);color:var(--white)}

/* The glow tour.js pulses around whatever the reader was sent to look at.
   Outline + box-shadow only: both paint OUTSIDE the box, so a highlighted
   element never shifts the layout of the page it's sitting in. */
@keyframes tour-glow-pulse{
  0%{box-shadow:0 0 0 0 rgba(241,102,34,.55),0 0 0 0 rgba(241,102,34,.28)}
  55%{box-shadow:0 0 0 6px rgba(241,102,34,0),0 0 22px 8px rgba(241,102,34,.30)}
  100%{box-shadow:0 0 0 0 rgba(241,102,34,0),0 0 0 0 rgba(241,102,34,0)}
}
.tour-glow{
  outline:2px solid var(--orange);
  outline-offset:3px;
  border-radius:var(--radius-sm);
  animation:tour-glow-pulse 1.7s ease-out 3;
}
/* Respect a reader who has asked for less motion: keep the outline (which is
   the actual "look here" signal) and drop the pulsing. */
@media (prefers-reduced-motion:reduce){
  .tour-glow{animation:none}
}

/* ============================================================
   RUN SHEETS — the five row kinds, plus the editor grid.

   TEL's own printed documents carry a "Background Colour Key" legend (found
   verbatim in 113 of the 270 corpus documents) classifying every row as
   facilitator point / activity / discussion / scene / break. That legend is
   the business's agreed vocabulary, so the NAMES are fixed. The COLOURS are
   not: the real files use 5 different greys and 4 different oranges for those
   same 5 meanings, so persisting the documents' inconsistency would bake in
   the mess. These tokens are the one answer.

   Each kind gets a pale FILL (the "background colour" the legend names, kept
   light enough that black body text on it clears AA comfortably) and a
   saturated EDGE used for the row's left bar, the legend swatch border and
   the editor's kind chip. Print-safe: all five fills survive greyscale as
   distinguishable tints, because these documents are printed in black and
   white as often as not.
   ============================================================ */
:root{
  /* Teaching point = white in the legend. A literal white fill would be
     invisible as a "kind", so the fill stays page-white and the EDGE carries
     the identity — the row still reads as unmarked, which is the intent. */
  --run-fac_point:#ffffff;      --run-fac_point-edge:#b9b4b0;
  --run-activity:#e6f4ea;       --run-activity-edge:#237a46;  /* = --good */
  --run-discussion:#e4eefb;     --run-discussion-edge:#1f5fa8;
  --run-scene:#fdeee5;          --run-scene-edge:#b44a22;     /* = --orange-600 */
  --run-break:#eeecea;          --run-break-edge:#6f6c6a;     /* = --muted */
}

/* --- legend (screen + print) ------------------------------------------- */
.run-legend{display:flex;flex-wrap:wrap;gap:var(--space-8) var(--space-14);
  align-items:center;margin:var(--space-10) 0}
.run-legend .lg{display:flex;align-items:center;gap:var(--space-6);
  font-size:12px;color:var(--ink)}
.run-legend .sw{width:16px;height:16px;border-radius:4px;flex:0 0 auto;
  border:2px solid var(--line)}
.run-legend .sw.k-fac_point{background:var(--run-fac_point);border-color:var(--run-fac_point-edge)}
.run-legend .sw.k-activity{background:var(--run-activity);border-color:var(--run-activity-edge)}
.run-legend .sw.k-discussion{background:var(--run-discussion);border-color:var(--run-discussion-edge)}
.run-legend .sw.k-scene{background:var(--run-scene);border-color:var(--run-scene-edge)}
.run-legend .sw.k-break{background:var(--run-break);border-color:var(--run-break-edge)}

/* --- the tally header -------------------------------------------------- */
.run-tally{display:flex;align-items:baseline;gap:var(--space-8);flex-wrap:wrap}
.run-tally .big{font-family:var(--display);font-size:34px;line-height:1;
  color:var(--ink);letter-spacing:.5px}
.run-tally .sub{color:var(--muted);font-size:13px}

/* --- editor rows ------------------------------------------------------- */
.run-rows{display:flex;flex-direction:column;gap:var(--space-8)}
.run-row{position:relative;border:1px solid var(--line);border-left:5px solid var(--run-fac_point-edge);
  border-radius:var(--radius-sm);background:var(--run-fac_point);
  padding:var(--space-10) var(--space-12)}
.run-row.k-fac_point{background:var(--run-fac_point);border-left-color:var(--run-fac_point-edge)}
.run-row.k-activity{background:var(--run-activity);border-left-color:var(--run-activity-edge)}
.run-row.k-discussion{background:var(--run-discussion);border-left-color:var(--run-discussion-edge)}
.run-row.k-scene{background:var(--run-scene);border-left-color:var(--run-scene-edge)}
.run-row.k-break{background:var(--run-break);border-left-color:var(--run-break-edge)}
/* Switched OFF: still visible and still editable — it keeps its place in the
   running order — but visibly not counting. Opacity alone would read as
   "disabled"; the dashed edge says "deliberately parked". */
.run-row.is-off{opacity:.55;border-left-style:dashed}
.run-row.is-off .run-cum{visibility:hidden}
/* "If time" (is_active but is_optional) — a lighter mark than Off's dashed+faded look,
   since the row is still very much on. Dotted, same as Greg asked for, and carried onto
   the printed run sheet / guide (see runsheet_pdf.css / .kind-if-time). */
.run-row.is-iftime{border-left-style:dotted}
.run-row.dragging{opacity:.4}
/* Hover strip between rows — reveals a "+" to insert a row at exactly that spot
   instead of at the bottom + drag up. height:0 with an absolutely-positioned button so
   it takes no extra vertical rhythm in the .run-rows gap — including on hover: this used
   to grow to var(--space-8) on :hover, which shifted every row below down a few pixels
   on every mouse pass (Greg). The button's own 20px circle already straddles the
   existing .run-rows gap without needing the strip itself to grow; only a deliberate
   click (below) grows it, to make room for the inline add-row form. */
.run-insert{position:relative;height:0}
.run-insert::before{content:"";position:absolute;left:0;right:0;top:0;height:1px;
  background:var(--orange);opacity:0;transition:opacity .12s}
.run-insert-btn{position:absolute;left:50%;top:0;transform:translate(-50%,-50%);
  width:20px;height:20px;border-radius:50%;border:1px solid var(--orange);background:var(--card);
  color:var(--orange);font-size:14px;font-weight:700;line-height:1;padding:0;cursor:pointer;
  display:flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;
  transition:opacity .12s}
.run-insert:hover::before,.run-insert-btn:focus{opacity:.5}
.run-insert:hover .run-insert-btn,.run-insert-btn:focus{opacity:1;pointer-events:auto}
/* .is-open: the deliberate, click-triggered expansion — holds the inline add-row form
   (search-the-library-or-type-a-title + Add/Cancel), same control the bottom "Add a
   row" card uses. Unlike the hover state above, this only ever changes on a click. */
.run-insert.is-open{height:auto;padding:var(--space-6) 0;z-index:1}
.run-insert.is-open::before{display:none}
.run-insert.is-open .run-insert-btn{display:none}
.run-insert-form{display:flex;gap:var(--space-6);align-items:center;background:var(--card);
  border:1px solid var(--orange);border-radius:8px;padding:var(--space-6) var(--space-8);
  box-shadow:0 2px 8px rgba(0,0,0,.08)}
.run-insert-form input{flex:1 1 200px;min-width:0;font-size:13px;padding:var(--space-6) var(--space-8);
  border:1px solid var(--line);border-radius:6px}
/* The "+" after the LAST row (Greg, 2026-09-15: "a plus button to appear after the final
   row so we can add a row to the bottom") — it replaced the separate "Add a row" card at the
   bottom of the page. Same button and the same inline form as the strips between rows,
   hidden until hover just like them (Greg, 2026-09-15: "should be hidden and exactly the
   same as the rest of the hover ones... shouldn't be any different"). Also the only way to
   add the FIRST row to an empty sheet. */
/* nowrap, not wrap (2026-09-22, Greg screenshot: the clock ran over onto its own row) —
   with wrap on, the browser decides whether an item fits BEFORE any shrinking happens,
   so once the trailing items' un-shrunk widths ran past the container, .run-clock (last
   in DOM order) dropped to a new line even though .run-topic had plenty of room left to
   give up (its min-width floor, 160px, was nowhere close). nowrap forces the browser to
   actually shrink flexible children to fit one line instead — see .run-topic and the
   two <select>s below, now allowed to share that squeeze instead of it landing entirely
   on whichever item happened to run out of room first. */
.run-rowhead{display:flex;align-items:center;gap:var(--space-8);flex-wrap:nowrap}
.run-drag{cursor:grab;color:var(--muted);font-size:15px;line-height:1;
  padding:0 var(--space-2);flex:0 0 18px;text-align:center;user-select:none}
/* Fixed width, not auto — .run-cum's own width varies with its text ("15m" vs "1h
   40m"), so whatever followed it ended up a different width on every row depending on
   how long THAT row's running total happened to be (Greg screenshot). Every box in
   this header is now fixed-width for the same reason (.run-topic included, below) —
   nothing left to grow into the gap an auto margin used to need. */
.run-cum{flex:0 0 54px;text-align:right;font-variant-numeric:tabular-nums;font-size:12px;
  color:var(--muted)}
/* The wall-clock time a row starts at, beside the running total (2026-09-18). Fixed
   width for the same reason .run-cum is, and `:empty{display:none}` so a sheet with no
   start time — or an OFF row, which shows no figures at all — reserves no space and
   looks exactly as it did before this column existed. Darker than .run-cum: it's the
   number you scan down the page while designing. */
.run-clock{flex:0 0 46px;text-align:right;font-variant-numeric:tabular-nums;font-size:12px;
  color:var(--slate);font-weight:600}
.run-clock:empty{display:none}
.run-mins{width:66px}
/* Blank minutes are easy to miss on a page this long — a thick red outline pulls the
   eye straight to the box (Greg). JS toggles .is-missing on load and on every edit;
   see runsheet_detail.html markMinutes(). */
.run-mins.is-missing{outline:3px solid var(--bad);outline-offset:-1px}
/* Fixed widths, not auto — a native <select> otherwise sizes itself to whichever
   option is CURRENTLY selected, so "Teaching point"/"Client/partner facilitator"
   made these two columns a different width on every single row (Greg screenshot).
   text-overflow only bites on the rare option longer than the box. */
/* flex-shrink 1, not 0 (2026-09-22) — a fixed WIDTH still stops these resizing to
   whatever option is currently selected (the whole reason they're fixed at all, see
   above), but they can now give up some of that width under real pressure, sharing the
   squeeze nowrap now demands with .run-topic instead of leaving it to absorb 100% of
   it alone. min-width is short enough to still read ("Teaching p…" / "External Fac…"),
   text-overflow covers the rest. */
.run-rowhead select[data-field="kind"]{flex:0 1 150px;min-width:80px;text-overflow:ellipsis}
.run-rowhead select[data-field="owner_role"]{flex:0 1 200px;min-width:100px;text-overflow:ellipsis}
/* The plain-text who beside the Who list (15 Oct 2026) — narrow, and the first thing to
   give way under the nowrap squeeze. */
.run-rowhead .run-owner-text{flex:0 1 110px;min-width:56px;font-size:var(--text-12)}
/* The run sheet header's credit line: Owner · Last edited · Edited by (15 Oct 2026). */
.rs-credit{display:flex;flex-wrap:wrap;gap:var(--space-4) var(--space-14);margin:var(--space-10) 0 0;
  font-size:var(--text-12);color:var(--muted)}
.rs-credit:empty{display:none}
/* Ends exactly where .run-body's left column (Topic/Content) ends below it, so the
   collapsed header and the expanded body read as the SAME two columns (Greg,
   2026-09-01). Both = (100% - .run-body's grid gap)/2, minus whatever sits to this
   box's left plus the gaps between: chev(26px)+gap(8px) alone, or
   drag(18px)+gap(8px)+chev(26px)+gap(8px) when a drag handle also precedes it (run
   sheet editor only — never a library card, never a read-only viewer, per .run-drag's
   own {% if u_can_edit_run_sheets %} gate). The general-sibling selector below picks
   the right one on its own — no template branching needed. If .run-rowtoggle's 26px,
   .run-drag's 18px, or --space-8 ever change, both numbers need recalculating by hand. */
.run-row .run-topic{flex:0 1 auto;width:calc(50% - 38px);min-width:160px}
.run-row .run-drag ~ .run-topic{width:calc(50% - 64px)}
/* The On/If time/Off pill — ALSO fixed-width for the same reason, replacing two
   independent-width checkbox+label pairs (annotation). Colour communicates state so the
   text itself can stay short and constant-width: on=default, if_time=dotted/muted (matches
   .run-row.is-iftime), off=struck through and faded (matches .run-row.is-off). */
.run-state-pill{flex:0 0 74px;text-align:center;font-size:12px;font-weight:600;
  padding:var(--space-6) 0;border-radius:999px;border:1px solid var(--line);
  background:var(--card);color:var(--ink);cursor:pointer}
.run-state-pill:hover{border-color:var(--orange)}
.run-state-pill:disabled{cursor:default}
.run-state-pill[data-state="if_time"]{border-style:dotted;color:var(--muted)}
.run-state-pill[data-state="off"]{opacity:.6;text-decoration:line-through;color:var(--muted)}
.run-body{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-8);
  margin-top:var(--space-8)}
.run-body label, .run-body .rf-label{display:block;font-size:11px;color:var(--muted);
  margin-bottom:var(--space-2)}
/* Annotation #290 — the header row's labels had no stacking rule of their own (they
   sit outside .run-body), so a bare browser <label> let the input crowd up right next
   to the label text instead of dropping to its own line. Force text-on-top,
   field-on-bottom like the row fields already do. */
.rs-header-row label{display:flex;flex-direction:column;gap:var(--space-2);
  font-size:11px;color:var(--muted)}
.run-body textarea{width:100%;min-height:62px;resize:vertical}
/* A row's two boxes side by side end level (Greg, 7 Oct 2026): Resources now carries its
   Handouts/Slides list and buttons underneath, so its box is one line shorter and the box
   beside it (Topic) stretches to the full height of the pair. Grid rows already share one
   height; this makes each cell's editable box fill what its cell has left. */
.run-row .run-body > .rf-label{display:flex;flex-direction:column}
.run-row .run-body > .rf-label > .run-rich-wrap{flex:1 1 auto;display:flex;flex-direction:column}
.run-row .run-body > .rf-label > .run-rich-wrap > .run-rich{flex:1 1 auto}
.run-row .run-body > .rf-res > .run-rich-wrap{flex:0 0 auto}
.run-row .run-body > .rf-res .run-rich{min-height:34px}
.run-opt{font-size:12px;color:var(--muted);display:flex;align-items:center;
  gap:var(--space-4)}
.run-flag{font-size:11px;padding:var(--space-1) var(--space-6);border-radius:999px;
  border:1px solid var(--run-break-edge);color:var(--slate-2)}
/* One column on phones: two side-by-side textareas at 390px are unusable, and
   the row's own horizontal scroll is what causes page-level overflow. */
@media (max-width:700px){
  .run-body{grid-template-columns:1fr}
  .run-cum{margin-left:0;width:100%}
  .run-mins{width:58px}
}
/* Annotation #183 — the run-sheet editor's inputs/selects/textareas were bare
   browser defaults, off-brand next to the rest of the site. Give them the same
   look as the site-wide .fld control (border/radius/padding/font/orange focus),
   but WITHOUT .fld's width:100%, which would break the row-head flex layout
   (.run-mins is 66px, .run-topic flexes). */
.run-row input:not([type=checkbox]),.run-row select,.run-row textarea,
#rsheader input,#rsheader select,#rsheader textarea,#rs-block{
  border:1px solid var(--line);border-radius:10px;
  padding:var(--space-9) var(--space-11);font-family:inherit;font-size:var(--text-14);
  background:var(--card);color:var(--ink)}
.run-row input:not([type=checkbox]):focus,.run-row select:focus,
.run-row textarea:focus,#rsheader input:focus,#rsheader select:focus,
#rsheader textarea:focus,#rs-block:focus{
  outline:none;border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-50)}

/* ---------- Dashboard tweaks (annotations #194 / #224 / #225, 2026-07-27) ----------
   Appended block, kept separate from the shared rules above so nothing else on the
   site shifts. `.dash-kpis` scopes the smaller stat tiles to the dashboard only —
   .kpi on its own (reports, actor/event detail) is untouched. */

/* #224/#225 — "these boxes can be smaller." Tighter padding + smaller number so the
   four dashboard stat tiles read as a compact strip instead of dominating the page. */
.dash-kpis.kpis .kpi{padding:var(--space-12) var(--space-14) var(--space-10)}
.dash-kpis.kpis .kpi .num{font-size:30px;margin-top:var(--space-4)}
.dash-kpis.kpis .kpi .num.cur{font-size:22px}
.dash-kpis.kpis .kpi .lab{font-size:11px}
.dash-kpis.kpis .kpi .sub{font-size:11px;margin-top:var(--space-2)}
.dash-kpis.kpis .kpi .accent{width:60px;height:60px;right:-12px;top:-12px}

/* "Team today" card — same request, one notch tighter than the default .card padding,
   and the table itself pulled in a little. */
.wa-today-card{padding:var(--space-10) var(--space-14)}
.wa-today-tbl th{padding:0 var(--space-8) var(--space-2) 0}
.wa-today-tbl td{padding:var(--space-6) var(--space-8) var(--space-2) 0}

/* #194 — "separate office staff from the rest so it's easy to see who's who." A small
   caps subheading above each office/other block within a "Team today" column
   (nexus2/views.py splits the people list; markup in dashboard.html). */
.wa-today-col-wrap{display:flex;flex-direction:column;gap:var(--space-4)}
.wa-today-sub{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;
  color:var(--muted)}
.wa-today-col-wrap .wa-today-col{max-width:none}

/* ---------- Run sheet / FAC guide readability (annotations #201/#202, 2026-07-27) ----------
   Appended block, kept separate from the shared rules above so nothing else on the site
   shifts. The rich-text boxes (Key points / Content / Facilitator notes / Overall objective /
   Learning objectives) were rendering at the 13.5-14px base — fine for a short label, too
   small for a box someone types a real talk-track into. This is the belt-and-braces copy of
   the same figure the run-sheet template also sets inline on .run-rich (that inline rule wins
   the cascade on this page; this one covers .run-rich anywhere else it gets reused, and lifts
   the plain textareas/inputs in the run-sheet body up to match so nothing reads smaller than
   the rich boxes beside it). */
.run-rich{font-size:var(--text-16);line-height:1.5}
.run-body textarea,.run-body input:not([type=checkbox]){font-size:var(--text-14)}
.run-rtb .rtb{font-size:var(--text-13)}

/* Rich email compose fields use the run-sheet editor's selection-only control idiom, but
   live here because nine separate send pages share them. */
.email-rich-wrap{border:1px solid var(--line);border-radius:8px;background:var(--card);position:relative}
.email-rtb{display:none;position:absolute;z-index:30;gap:var(--space-2);padding:var(--space-4);background:#27272a;border-radius:8px;box-shadow:0 4px 14px rgba(0,0,0,.3)}
.email-rtb.show{display:flex}
.email-rtb .rtb{font-size:12px;line-height:1;padding:var(--space-5) var(--space-9);border:none;background:transparent;border-radius:5px;cursor:pointer;color:var(--white)}
.email-rtb .rtb:hover{background:rgba(255,255,255,.15)}
.email-rtb .rtb mark{background:#fff59d;padding:0 var(--space-2);border-radius:2px}

/* Text-colour and highlight-colour pickers (Davina, 1 Oct 2026). richtoolbar.js adds them
   to EVERY selection bar (.run-rtb and .email-rtb); each swatch's own colour is palette
   data set by that script. The palette opens with .open (never [hidden] — a display rule
   would beat it). The bar wraps rather than running off a narrow field. */
.run-rtb,.email-rtb{flex-wrap:wrap;max-width:100%}
.rtc-wrap{position:relative;display:inline-flex}
.rtc{display:inline-flex;align-items:center;gap:var(--space-2);font-size:var(--text-12);line-height:1;padding:var(--space-5) var(--space-7);border:none;background:transparent;border-radius:var(--space-5);cursor:pointer;color:var(--card)}
.rtc:hover,.rtc[aria-expanded=true]{background:rgba(255,255,255,.15)}
.rtc:focus-visible,.rtc-sw:focus-visible{outline:2px solid var(--card);outline-offset:1px}
.rtc-glyph{font-weight:700;border-bottom:3px solid var(--orange)}
.rtc-glyph-hl{border-bottom:none;padding:0 var(--space-2);border-radius:2px;background:#fff59d;color:var(--ink)}
.rtc-caret{font-size:var(--text-10);opacity:.8}
.rtc-pop{display:none;position:absolute;top:100%;right:0;margin-top:var(--space-4);z-index:31;grid-template-columns:repeat(6,26px);gap:var(--space-4);padding:var(--space-6);background:#27272a;border-radius:var(--space-8);box-shadow:0 4px 14px rgba(0,0,0,.3)}
.rtc-wrap.open .rtc-pop{display:grid}
.rtc-pop.rtc-pop-left{right:auto;left:0}
.rtc-sw{width:26px;height:26px;padding:0;border:1px solid rgba(255,255,255,.55);border-radius:var(--space-5);cursor:pointer}
.rtc-sw:hover{transform:scale(1.1)}
@media (hover:none){.rtc-pop{grid-template-columns:repeat(6,34px)}.rtc-sw{width:34px;height:34px}.rtc{padding:var(--space-8) var(--space-9)}}
.rtc-none{background:linear-gradient(to top right,transparent 45%,var(--bad) 45%,var(--bad) 55%,transparent 55%),var(--card)}
.email-rich{min-height:216px;padding:var(--space-10) var(--space-12);font-family:inherit;font-size:var(--text-14);line-height:1.5;outline:none}
.email-rich:focus{box-shadow:inset 0 0 0 2px rgba(241,102,34,.35)}
.email-rich ul,.email-rich ol{margin:var(--space-2) 0 var(--space-2) var(--space-20);padding-left:var(--space-6)}
.email-rich ul{list-style:disc}.email-rich ol{list-style:decimal}.email-rich li{margin:var(--space-1) 0}.email-rich p{margin:0 0 var(--space-4)}

/* Email pill fields (To/Cc) — nexus2/static/nexus2/emailpills.js turns a plain
   comma-separated input into this. The box wraps to a new line rather than scrolling
   sideways, so a big cast's worth of addresses stays fully readable.

   Two colours mark WHO a pill is, at a glance, once the lookup has matched it: team (an
   Actor — blue) vs client contact (teal). A pill that never matched anyone (freeform typed
   text) keeps the original neutral grey — it's shown as the raw text typed, since there's
   no name to show. The email itself lives in the pill's title tooltip only (Greg,
   2026-09-08: "not their email in brackets... that's going to get too junked up") — hover
   a pill to confirm the address.

   Faces: the 2026-09-08 attempt at avatars here was dropped ("that was a bad idea"); on
   2026-09-13, once the 20px face-beside-name chip existed app-wide, Greg asked for the
   recipient pill to follow it ("the contact card within an email editor is not following
   the new small headshot pill style"). So: a 20px face for a team member the directory
   knows, nothing for anyone else. And the × stays ON the name's line — nowrap, never a
   second row ("the X for a contact in the email should be next to the name, not below"). */
.emlp-wrap{position:relative;width:100%}
.emlp-box{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-5);width:100%;
  min-height:38px;border:1px solid var(--line);border-radius:8px;padding:var(--space-5) var(--space-7);
  background:var(--card);cursor:text;box-sizing:border-box}
.emlp-box:focus-within{border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-50)}
/* `.chip.emlp-pill`: one recipient in the email box (a removable token). */
.chip.emlp-pill{--chip-bg:#eef2f6;--chip-fg:#3a4a58;border-width:0;flex-wrap:nowrap;gap:var(--space-5);
  padding:var(--space-2) var(--space-5) var(--space-2) var(--space-9);max-width:100%;white-space:nowrap}
.chip.emlp-pill:has(.emlp-hs){padding-left:var(--space-2)}
.emlp-hs{width:20px;height:20px;border-radius:50%;object-fit:cover;flex:none;background:var(--card);display:block}
/* `.team` is ALSO the session card's cast list (display:flex;flex-direction:column, above),
   and this pill carries `team` as its kind class — so without these three words the pill
   stacked face / name / × vertically. That was the "× below the name" Greg saw. */
.chip.emlp-pill.team{--chip-bg:#e6edfb;--chip-fg:#2a4a8a;display:inline-flex;flex-direction:row;margin:0}
.chip.emlp-pill.client{--chip-bg:#e2f5f1;--chip-fg:#1a6e63}
.chip.emlp-pill.bad{--chip-bg:#fdecec;--chip-fg:var(--bad)}
.emlp-pill-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.emlp-x{border:0;background:transparent;cursor:pointer;color:inherit;opacity:.55;
  font-size:15px;line-height:1;padding:0 var(--space-3);border-radius:50%}
.emlp-x:hover{opacity:1;background:rgba(0,0,0,.08)}
.emlp-input{flex:1 1 140px;min-width:140px;border:0;outline:0;font:inherit;
  font-size:var(--text-14);padding:var(--space-4) var(--space-2);background:transparent}
.emlp-pop{position:absolute;z-index:40;top:100%;left:0;margin-top:var(--space-4);min-width:260px;
  max-width:100%;max-height:260px;overflow:auto;background:var(--card);
  border:1px solid var(--line);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.14)}
.emlp-pop-item{padding:var(--space-7) var(--space-11);font-size:13px;cursor:pointer;display:flex;
  align-items:center;gap:var(--space-8)}
.emlp-pop-text{overflow:hidden}
.emlp-pop-item b{font-weight:600;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.emlp-pop-item small{color:var(--muted);font-size:11px;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.emlp-pop-item:hover,.emlp-pop-item.hi{background:var(--orange-50)}

/* ---------- Engagement status/money extras (2026-07-27) ----------
   The show-only-when-relevant blocks in _eng_form.html (reason for a "no", and the
   cancellation block with the two independent pay/charge %s). One appended block, kept
   separate so nothing else shifts. Visibility is driven by the `hidden` attribute in JS
   (telSyncStatusExtras) — this only styles the cancellation reminder note. */
.eng-status-extra{margin-top:var(--space-2);grid-column:1 / -1}
.eng-cancel-note{font-size:12px;line-height:1.4;color:var(--bad);background:color-mix(in srgb, var(--bad) 7%, transparent);
  border:1px solid color-mix(in srgb, var(--bad) 22%, transparent);border-radius:8px;padding:var(--space-7) var(--space-10);margin:var(--space-2) 0 var(--space-8);font-weight:600}

/* Per-session Invoicing / pay panel (nexus2 SessionPricing) on the booking session card. */
.sess-pricing{margin-top:var(--space-6)}
.sess-pricing .sp-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-8) var(--space-12)}
.sess-pricing label{font-size:11px;font-weight:600;color:var(--muted);display:block;margin-bottom:var(--space-2)}
.sess-pricing input[type=text],.sess-pricing input[type=number],.sess-pricing input:not([type]){width:100%;box-sizing:border-box;
  border:1px solid var(--line);border-radius:8px;padding:var(--space-7) var(--space-8);font-family:inherit;font-size:var(--text-14)}
/* #575 (Jacob, 2026-10-06): no up/down arrows on the invoicing/pay figures. */
.sess-pricing input[type=number]{-moz-appearance:textfield;appearance:textfield}
.sess-pricing input[type=number]::-webkit-outer-spin-button,
.sess-pricing input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
.sess-pricing .sp-full{grid-column:1 / -1}
.sess-pricing .sp-check{display:flex;gap:var(--space-7);align-items:center;font-size:13px;font-weight:600;color:var(--fg)}
.sess-pricing .sp-approve{margin-top:var(--space-8);padding:var(--space-8) var(--space-10);border-radius:8px;
  background:color-mix(in srgb, var(--good) 7%, transparent);border:1px solid color-mix(in srgb, var(--good) 22%, transparent)}
/* One session's row in the Financial tab's job-level invoicing table (2026-08-27). The
   session cards keep only a status pill; this is where the figures are typed. */
.sprow{padding:var(--space-8) 0;border-top:1px solid var(--line)}
.sprow:first-of-type{border-top:0;padding-top:var(--space-6)}
.sprow-head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-10);flex-wrap:wrap}
/* A session's invoicing row folds to its date + status pill (Greg, 2026-09-13). */
.sprow-acc>summary{list-style:none;cursor:pointer}
.sprow-acc>summary::-webkit-details-marker{display:none}
/* 20 Oct 2026 (Greg: "what's up with that blue selection box"): the browser's own focus
   ring drew round the whole row after a click or a save. Mouse focus shows nothing; keyboard
   focus gets the house orange ring. */
.sprow-acc>summary:focus:not(:focus-visible){outline:none}
.sprow-acc>summary:focus-visible{outline:2px solid var(--orange);outline-offset:2px;border-radius:var(--radius-sm)}
.sprow-acc>summary .chev{color:var(--muted);font-size:var(--text-11);transition:transform .15s}
.sprow-acc[open]>summary .chev{transform:rotate(90deg)}
.sprow-acc[open]>summary{margin-bottom:var(--space-4)}
.sess-pricing .sp-hourly{display:none}
.sess-pricing.is-hourly .sp-hourly{display:block}
.sess-pricing.is-hourly .sp-fixed{display:none}
/* "From cast hours": who it charges for, one tick per person (20 Oct 2026, casthours.js). */
.sess-pricing .sp-cast{border:1px solid var(--line);border-radius:8px;padding:var(--space-8) var(--space-10)}
.sess-pricing .sp-cast-head{font-size:var(--text-11);font-weight:600;color:var(--muted);margin-bottom:var(--space-4)}
.sess-pricing .sp-cast-list{list-style:none;margin:0;padding:0}
.sess-pricing .sp-cast-row{display:flex;align-items:baseline;gap:var(--space-8);flex-wrap:wrap;margin:0;
  padding:var(--space-3) 0;font-size:var(--text-13);font-weight:400;color:var(--fg);cursor:pointer}
.sess-pricing .sp-cast-row input{align-self:center}
.sess-pricing .sp-cast-name{font-weight:600}
.sess-pricing .sp-cast-hrs{margin-left:auto;font-variant-numeric:tabular-nums}
.sess-pricing .sp-cast-minline{margin-top:var(--space-4)}
/* The saved session's $ total on its folded row, beside the status pill (20 Oct 2026). */
.sprow-amt{font-size:var(--text-13);font-variant-numeric:tabular-nums}

/* ===== 2026-07-28 annotation sweep — appended block, late so it wins the cascade ===== */

/* #235 "these photos seem awfully small" — the Team-today faces on the dashboard were the
   generic 34px .avatar. They are the point of that card, so they get their own size here
   rather than changing .avatar everywhere. Columns widen to match so the same number of
   faces still fit per row. */
.wa-today-tbl .avatar{width:46px;height:46px;font-size:14px}
.wa-today-col{max-width:236px}
.wa-today-col-wrap{max-width:236px}
@media (max-width:700px){
  .wa-today-tbl .avatar{width:38px;height:38px;font-size:var(--text-13)}
  .wa-today-col,.wa-today-col-wrap{max-width:150px}
}

/* #216 "let the pill stretch big enough to see the details and times" — .wa-chip was
   clamped to max-width:130px with nowrap + ellipsis, so a real course title was always
   cut. On the PERSONAL week page (.wa-derived) the chips now wrap onto two lines and run
   the full column width. The team GRID keeps its clamp: that one is a dense matrix where
   130px is load-bearing. */
.wa-derived .wa-chip{max-width:100%;white-space:normal;overflow:visible;text-overflow:clip;
  line-height:1.35;align-items:flex-start;text-align:left}

/* ===== Info tips (#188) — see nexus2/static/nexus2/infotip.js ===================== */
/* The trigger: a little "i" in a circle sitting where a paragraph of grey help text
   used to. Sized off the surrounding text so it never dominates a field label. */
.infotip{display:inline-block;line-height:0;vertical-align:middle}
.infotip-btn{display:inline-flex;align-items:center;justify-content:center;
  width:15px;height:15px;padding:0;margin:0 0 var(--space-1) var(--space-5);border-radius:50%;
  border:1.3px solid var(--muted);background:transparent;color:var(--muted);
  font-family:inherit;font-size:0;font-weight:700;line-height:1;
  cursor:help;flex:0 0 auto;transition:color .12s,border-color .12s,background .12s}
/* Annotation #592 (Jacob, 6 Oct 2026) — "the 'i' in circles is uncentred. Fix across site."
   The button's own "i" TEXT was italic — the font's italic design shifts the dot further
   right than the stem, off the circle's true centre, and flex centring only centres the
   line box, not that lopsided ink. Drawing the glyph instead, as a `::before` shaped by
   `transform:skewX` on its OWN box, shears it around its own centre like any upright
   character, so it stays centred — same slanted look, this time balanced. The live "i" text
   is hidden (`font-size:0`, same trick the drawn chevrons use) rather than removed, so the
   dozen hand-copied infotip buttons across the templates (not just `_infotip.html`) are all
   fixed by this one rule, and the accessible name still comes from `aria-label`. */
/* Scoped off the reused-look triggers below (.pill / .person-btn / .person-nm / .tm-amt) —
   those show a count, a face or a figure, never the letter "i", and must not grow one. */
.infotip-btn:not(.pill):not(.person-btn):not(.person-nm):not(.tm-amt)::before{content:"i";
  display:inline-block;font-size:var(--text-11);transform:skewX(-12deg)}
.infotip-btn:hover,.infotip-btn:focus-visible,.infotip.is-open .infotip-btn{
  color:var(--white);background:var(--orange);border-color:var(--orange);outline:none}
/* A .pill reused as the infotip trigger (conflict/travel badges) keeps its own look —
   undo the "i in a circle" sizing instead of shrinking the pill to 15px. */
.pill.infotip-btn{all:unset;display:inline-flex;align-items:center;gap:var(--space-6);
  font-size:var(--text-11);font-weight:600;line-height:1.5;padding:var(--space-4) var(--space-9);
  border-radius:999px;white-space:nowrap;cursor:pointer}
/* The help text itself stays in the DOM (so it is still there for a screen reader and
   for the JS to copy) but never renders in the flow. */
.infotip-body{display:none}
/* The single global bubble. position:absolute + a body-level parent is deliberate: it
   escapes every .card, table and accordion that would otherwise clip it. */
.infotip-pop{position:absolute;z-index:9000;background:var(--ink);color:var(--white);
  border-radius:10px;padding:var(--space-9) var(--space-11);font-size:var(--text-13);line-height:1.45;font-weight:400;
  box-shadow:0 6px 24px rgba(0,0,0,.22);pointer-events:none;max-width:320px;
  opacity:0;transform:translateY(-4px);transition:opacity .15s ease,transform .15s ease}
/* #518/#519 (Greg, 5 Oct 2026) — every hover/tap popover in the app shares this one bubble
   (infotip.js), so a fade here is a fade everywhere: person cards, conflict pills, help
   "i"s. JS adds/removes `.is-visible`; `[hidden]` only lands once the fade-out finishes,
   so the box doesn't snap away before it's done animating. */
.infotip-pop.is-visible{opacity:1;transform:none;pointer-events:auto}
.infotip-pop[hidden]{display:none}
/* A plain `title=` shown in this bubble (infotip.js, 20 Oct 2026): its line breaks were
   real line breaks in the browser's own tooltip, so keep them. */
.infotip-pop.is-text .infotip-pop-body{white-space:pre-line}
@media (prefers-reduced-motion:reduce){.infotip-pop{transition:none}}
.infotip-pop a{color:#ffb37a;text-decoration:underline}
.infotip-pop b{color:var(--white)}
.infotip-arrow{position:absolute;top:-5px;width:10px;height:10px;margin-left:calc(-1 * var(--space-5));
  background:var(--ink);transform:rotate(45deg);border-radius:2px}
.infotip-pop.flip .infotip-arrow{top:auto;bottom:-5px}
@media (max-width:700px){
  /* Bigger tap target on a phone, where there is no hover at all. The glyph is drawn by
     ::before now (annotation #592) — grow that, not the button's own (hidden) font-size. */
  .infotip-btn{width:19px;height:19px}
  .infotip-btn:not(.pill):not(.person-btn):not(.person-nm):not(.tm-amt)::before{font-size:12px}
  .infotip-pop{font-size:var(--text-14)}
}

/* Arrangements tick list (#223) — job- and session-level. Reads as a row of chips rather
   than a stack of checkboxes: six of them stacked would dominate a session card. */
.logcheck{display:flex;flex-wrap:wrap;gap:var(--space-6);margin-top:var(--space-6)}
.logcheck-item{display:inline-flex;align-items:center;gap:var(--space-6);cursor:pointer;
  font-size:12px;font-weight:600;color:var(--muted);background:var(--paper,var(--white));
  border:1px solid var(--line);border-radius:999px;padding:var(--space-4) var(--space-10) var(--space-4) var(--space-8)}
.logcheck-item input{margin:0;width:13px;height:13px;flex:0 0 auto;accent-color:var(--good-2)}
.logcheck-item.on{color:var(--good-2);border-color:color-mix(in srgb,var(--good-2) 35%,transparent);
  background:color-mix(in srgb,var(--good-2) 8%,transparent)}
.logcheck-item input:disabled{cursor:default}
.logcheck-item:has(input:disabled){cursor:default;opacity:.75}
/* "show all arrangements" — the escape hatch for the items the job's travel terms say
   don't apply (2026-08-27). Reads as a quiet text link, not another chip. */
.logcheck-more>summary{cursor:pointer;list-style:none;font-size:var(--text-12);font-weight:600;
  color:var(--muted);margin-top:var(--space-7);width:max-content}
.logcheck-more>summary::-webkit-details-marker{display:none}
.logcheck-more>summary:hover{color:var(--orange-600)}
.logcheck-more[open]>summary{margin-bottom:var(--space-1)}

/* Logistics files card — grouped by the folder a file actually lives in (#186). */
.log-grp{border-top:1px solid var(--line);padding:var(--space-6) 0}
.log-grp:first-of-type{border-top:none}
.log-grp > summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:var(--space-8);
  flex-wrap:wrap;padding:var(--space-5) 0;font-size:var(--text-13)}
.log-grp > summary::-webkit-details-marker{display:none}
.log-grp > summary::before{content:'▸';opacity:.45;font-size:11px}
.log-grp[open] > summary::before{content:'▾'}
.log-grp-t{font-weight:600}

/* ---------- Run-sheet rows collapse to one line (annotation #200, 2026-07-27) ----------
   Appended block, kept separate from the run-sheet rules further up so nothing else on
   the site shifts. #200: "can these blocks collapse into the first row... that will be
   easier to move everything around. So when collapsed you see the title of the row."
   Collapsed IS the default (the class is on the server-rendered markup, so there is no
   expand-then-snap-shut flash on load). The summary line keeps the row's kind colour —
   the colour key is how these documents have always been read — plus its minutes, its
   topic and its running total, which is everything you need to drag it into place. */
.run-rowbar{display:flex;align-items:center;gap:var(--space-10);flex-wrap:wrap;
  margin-bottom:var(--space-10);padding-bottom:var(--space-8);border-bottom:1px solid var(--line)}

.run-rowtoggle{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:26px;height:26px;padding:0;border:1px solid transparent;border-radius:8px;
  background:transparent;color:var(--muted);cursor:pointer;line-height:1}
.run-rowtoggle:hover{background:rgba(0,0,0,.06);color:var(--ink)}
.run-rowtoggle:focus-visible{outline:none;border-color:var(--orange);
  box-shadow:0 0 0 3px var(--orange-50)}
.run-rowtoggle .chev{font-size:13px;transition:transform .12s ease}
.run-row:not(.is-collapsed) .run-rowtoggle .chev{transform:rotate(90deg)}
/* Screen-reader-only label for the chevron, scoped to this control so the two-letter
   class name isn't claimed site-wide. */
.run-rowtoggle .vh{position:absolute;width:1px;height:1px;margin:calc(-1 * var(--space-1));padding:0;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}

/* The per-row action strip (library flag / "Save to library" / "Remove row"). Its layout
   lives HERE rather than inline on the element: an inline `display:flex` outranks any
   stylesheet rule, so it would have survived the collapse below and left a floating strip
   under every closed row. */
.run-rowfoot{margin-top:var(--space-8);display:flex;gap:var(--space-8);
  align-items:center;flex-wrap:wrap}

/* The collapse itself. Everything below the summary line goes; the row shrinks to the
   height of one control, which is what makes a 37-row sheet draggable. */
.run-row.is-collapsed .run-body,
.run-row.is-collapsed .run-rowfoot,
/* History goes with them (2026-09-18, Greg: "that history link should only appear when
   the accordion is expanded... keep everything within one row when it's collapsed").
   Hidden here rather than moved into .run-body, because the History button and its panel
   are deliberately markup-with-no-<script> that has to survive being dropped into the
   page by innerHTML — see the long comment at the foot of _runsheet_row.html. */
.run-row.is-collapsed .run-history-btn,
.run-row.is-collapsed .run-history-panel{display:none}
.run-row.is-collapsed{padding-top:var(--space-6);padding-bottom:var(--space-6)}

/* A save the server REFUSED (e.g. Resources over its 300-character column). The row is
   force-opened and pinned open by the JS; this is where the reason is printed. */
.run-rowerr{margin-top:var(--space-6);padding:var(--space-6) var(--space-8);
  border-radius:var(--radius-sm);border:1px solid var(--bad);color:var(--bad);
  background:rgba(198,40,40,.06);font-size:12px}
.run-row[data-err]{box-shadow:0 0 0 2px rgba(198,40,40,.35)}

/* On a phone a collapsed row drops the secondary controls too, so the summary really is
   one line and the whole sheet fits on a screen you can drag on. They are all still in
   the DOM with their values — expanding the row brings them straight back. */
@media (max-width:700px){
  .run-row.is-collapsed .run-rowhead select,
  .run-row.is-collapsed .run-rowhead .run-owner-text,
  .run-row.is-collapsed .run-rowhead .run-opt{display:none}
  .run-row.is-collapsed .run-cum{margin-left:auto;width:auto}
  /* The row-wide rule gives Topic a 220px flex basis, which on a 390px screen forces the
     summary onto a second line. Collapsed, it may shrink — it is the only thing left
     that wants the space. */
  .run-row.is-collapsed .run-topic{flex:1 1 90px;min-width:0}
  /* Every px reclaimed here is a px of the topic you can actually read on a phone. */
  .run-row.is-collapsed .run-mins{width:46px;padding-left:var(--space-6);
    padding-right:var(--space-6);text-align:center}
  .run-row.is-collapsed .run-topic{padding-left:var(--space-8);padding-right:var(--space-8)}
}

/* ── Kit board tiles ───────────────────────────────────────────────────────────
   The office /kit/ page reuses _kit_tile.html, the same partial the wall iPad
   renders. The kiosk template is standalone and carries its own copy of these
   rules; this is the version for pages inside base.html. Kept deliberately
   finger-sized on both — the office page gets opened on a phone in the store
   room as often as on a desk. */
/* THE GAP BELONGS TO THE GROUP, not to the tile. It used to sit on `.tile` as a
   margin-bottom, and `.tile.kid` resets `margin` wholesale to indent itself — so a case with
   a laptop packed in it ended on a kid with no bottom margin and sat flush against the next
   case, while every other pair had a gap (Greg, 2026-07-30). A case plus its contents is ONE
   block on the board; spacing it as one is also what the kiosk already does. */
.grp{margin-bottom:var(--space-8)}
.grp:last-child{margin-bottom:0}
.tile{display:flex;align-items:center;width:100%;text-align:left;border:1px solid var(--line);
  background:var(--card);border-radius:var(--radius-10,10px);padding:var(--space-10) var(--space-12);
  font:inherit;cursor:pointer;min-height:52px}
.tile:hover{border-color:var(--orange)}
.tile:active{background:var(--wash)}
.tile b{font-size:var(--text-14);display:block;line-height:1.25}
.tile .meta{font-size:var(--text-12);color:var(--muted);margin-top:var(--space-2);display:block}
/* WHO HAS IT, AS A FACE (Greg, 2026-07-30) — the circle sets the tile's height and the two
   lines of text sit beside it. Mirrors the copy in kit_board.html; the kiosk is standalone
   and cannot share a stylesheet, so the two have to be kept in step by hand. */
.kit-av{width:42px;height:42px;border-radius:50%;overflow:hidden;margin-right:var(--space-11);
  flex:0 0 auto;background:var(--line);color:var(--muted);font-size:var(--text-13);
  font-weight:700;display:flex;align-items:center;justify-content:center}
.kit-av img{width:100%;height:100%;display:block;object-fit:cover}
/* Nobody assigned yet — an empty ring, not a broken photo. */
.kit-av.none{background:transparent;border:2px dashed var(--line)}
.kit-txt{flex:1 1 auto;min-width:0}
/* Something packed inside a case: indented on the left, but its RIGHT edge lines up with
   its case (Greg, 2026-07-29). `.tile` sets width:100%, so a left margin alone pushed the
   whole 100% across and the nested tile overhung its parent by exactly the indent —
   width:auto lets a block fill only what's left. */
.tile.kid{display:block;margin:var(--space-5) 0 0 var(--space-22);width:auto;min-height:0;
  padding:var(--space-8) var(--space-11);border-left:2px solid var(--line);
  border-radius:var(--radius-9,9px)}
.tile.kid b{font-size:var(--text-13);color:var(--muted)}
/* "then → Thu 6 Aug · IGO" — where this unit is promised NEXT. Quieter than the line above
   it: where it is now is the fact you read first. */
/* ONE LINE, not two (Greg, 2026-07-30). The chain is a glance — the case it moves to and
   the day — and anything longer belongs on the Kit runs page, so it clips rather than wraps. */
.tile .meta.next{color:var(--orange-600);font-size:var(--text-11,11px);margin-top:var(--space-1);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}
/* A REQUIREMENT THIS CASE IS STILL WAITING FOR. Dashed because there is nothing physical
   here yet — it deliberately does NOT look like a tile, since there is nothing to drag. */
.kit-slot{margin:var(--space-5) 0 0 var(--space-22);padding:var(--space-6) var(--space-11);
  border:1px dashed var(--line);border-radius:var(--radius-9,9px);color:var(--muted);
  font-size:var(--text-12)}
.kit-slot span{opacity:.8}
/* The delivery is TODAY and this is still on the shelf. Same rule mirrored into
   kit_board.html's inline CSS — the wall iPad cannot load this stylesheet, and a slot that
   goes red in the office but not on the wall is worse than one that never goes red. */
.kit-slot.late{border-color:var(--bad);border-style:solid;color:var(--bad)}
.kit-slot.late .hurry{opacity:1;font-weight:700}
/* A left edge in the status colour, so "ready" and "out" read at a glance from
   across the room rather than by reading the words. */
.tile.ready{border-left:4px solid var(--good)}
.tile.out{border-left:4px solid var(--warn)}

/* ── Kit runs: the forward plan ────────────────────────────────────────────────
   One row per unit, one column per day (nexus2/templates/nexus2/kit_runs.html). Narrow,
   dense columns on purpose: the whole point is seeing a month of movement at once, so a
   cell says only WHO, and the full session is in the title/link. */
.kit-grid{border-collapse:separate;border-spacing:0;font-size:var(--text-12)}
.kit-grid th,.kit-grid td{border-bottom:1px solid var(--line);padding:var(--space-4) var(--space-6);
  text-align:center;white-space:nowrap}
.kit-grid thead th{font-size:var(--text-11,11px);color:var(--muted);font-weight:600;
  text-transform:uppercase;letter-spacing:var(--caps-track);line-height:1.15}
.kit-grid thead th span{display:block;font-size:var(--text-10);opacity:.75}
/* The unit name has to stay readable while the dates scroll under it. */
.kit-grid .stick{position:sticky;left:0;background:var(--card);text-align:left;font-weight:600;
  min-width:150px;box-shadow:1px 0 0 0 var(--line)}
.kit-grid .stick .muted{display:block;font-weight:400;font-size:var(--text-11,11px)}
.kit-grid td.on{background:var(--wash)}
.kit-grid td.on a,.kit-grid td.on summary{color:var(--ink);text-decoration:none;font-weight:600}
/* PLAN vs REALITY, as two different-looking cells (Greg, 2026-08-27: "the item movements page
   is differing from the Case tracker main page… so what gives?"). Every filled cell used to
   look identical, so a promise and a fact were indistinguishable — and the board, which reads
   the physical truth, looked like it was contradicting this page. Outline = promised only;
   solid = the unit is genuinely in that delivery. Same source of truth either way
   (kitplan.assignment_state). */
.kit-grid td.packed{background:var(--good-tint);box-shadow:inset 0 0 0 1px var(--good)}
.kit-grid td.packed summary{font-weight:700}
/* The day has come and it is STILL on the shelf. Deliberately the same red weight as a clash:
   both are "go and do something now", not "read this later". Literal hex — the brand orange
   fails contrast on white and --bad is worse than this against small text. */
.kit-grid td.late{background:var(--bad-tint-2);box-shadow:inset 0 0 0 2px var(--bad)}
.kit-grid td.late a,.kit-grid td.late summary{color:var(--bad);font-weight:700;text-decoration:none}
/* Two jobs, one unit, one day — the thing this page exists to catch. */
.kit-grid td.clash{background:var(--bad-tint-2);box-shadow:inset 0 0 0 2px var(--bad)}
.kit-grid td.clash a,.kit-grid td.clash summary{color:var(--bad);font-weight:700;text-decoration:none}
.kit-grid .wk{background:#faf8f5}
.kit-grid td.on.wk{background:var(--wash)}
/* Today's column, so a month-wide grid has a "you are here". */
.kit-grid .istoday{box-shadow:inset 1px 0 0 0 var(--orange-600),inset -1px 0 0 0 var(--orange-600)}
.kit-grid thead th.istoday{color:var(--orange-600)}
.kit-grid .stick a{color:inherit;text-decoration:none;border-bottom:1px dotted var(--line)}
.kit-grid .stick a:hover{border-bottom-color:var(--orange-600)}
.kit-legend{display:flex;gap:var(--space-14);flex-wrap:wrap;font-size:var(--text-12);margin-top:var(--space-10);align-items:center}
.kit-legend i.sw{display:inline-block;width:13px;height:13px;border-radius:3px;margin-right:var(--space-5);
  vertical-align:-2px;background:var(--wash);box-shadow:inset 0 0 0 1px var(--line)}
.kit-legend i.sw.packed{background:var(--good-tint);box-shadow:inset 0 0 0 1px var(--good)}
.kit-legend i.sw.late{background:var(--bad-tint-2);box-shadow:inset 0 0 0 2px var(--bad)}
.kit-legend i.sw.clash{background:var(--bad-tint-2);box-shadow:inset 0 0 0 2px var(--bad)}

/* ── "Is it actually there?" — the one badge, on every surface ─────────────────
   The item page, the planner's today strip and the cell popovers all render this, from the
   same `state` string, so the words a person reads are identical wherever they read them. */
.kit-state{display:inline-block;font-size:11px;font-weight:700;padding:var(--space-2) var(--space-8);border-radius:999px;
  white-space:nowrap;line-height:1.5}
.kit-state.packed{background:var(--good-tint);color:#1c6b3a}
.kit-state.planned{background:var(--wash);color:#7a5a48}
.kit-state.unfilled{background:#f1efec;color:#6b6560}
.kit-state.late{background:var(--bad-tint-2);color:var(--bad)}
/* "Which deliveries is it going to?" — one card per job, tick-boxes inside. */
.kit-jobs{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:var(--space-10)}
.kit-job{border:1px solid var(--line);border-radius:10px;padding:var(--space-9) var(--space-11)}
.kit-job.own{box-shadow:inset 3px 0 0 0 var(--orange-600)}
/* One thing's path, oldest first. Done deliveries recede; today is the line you look for. */
.kit-path tr.was td{opacity:.6}
.kit-path tr.now td{background:var(--wash)}
.kit-path tr.now td:first-child{box-shadow:inset 3px 0 0 0 var(--orange-600)}
/* Click-to-edit popover on a filled cell (annotation #291) — a plain <details> so no JS is
   needed; the summary is the client name already shown, the panel holds the same
   reassign/drop controls as the "Still to sort" gaps table above. */
/* Two deliveries in one cell (a unit down for a morning AND an afternoon session) used to
   render as one run-on string — "CBH Group CBH Group". They stack. */
.kit-cell-edit{display:block;text-align:left;position:relative}
.kit-cell-edit + .kit-cell-edit{margin-top:var(--space-3);padding-top:var(--space-3);border-top:1px dotted var(--line)}
.kit-cell-edit summary{cursor:pointer;list-style:none}
.kit-cell-edit summary::-webkit-details-marker{display:none}
.kit-cell-pop{position:absolute;z-index:20;top:100%;left:0;margin-top:var(--space-4);width:220px;
  white-space:normal;background:var(--card);border:1px solid var(--line);border-radius:10px;
  box-shadow:0 4px 14px rgba(0,0,0,.12);padding:var(--space-10);font-weight:400}

/* ── Kit board: the shared gestures + action sheet ─────────────────────────────
   Greg, 2026-07-30: "everything that I can do in the case-tracker page, I should also be
   able to do in the /kit/ office-side page" — and, before that, "right now I can't drag
   anything onto the board in my view". The office page runs the SAME script as the wall
   iPad now (nexus2/templates/nexus2/_kit_js.html); these are the class names that script
   uses, in the app's own visual language. The kiosk keeps its own copy of the equivalent
   rules because it is a standalone page that cannot load this stylesheet. */
.tile{touch-action:pan-y}                 /* a swipe still scrolls; the drag is behind a hold */
.kit-lane{transition:background .12s,border-color .12s}
/* The column you're dragging over. */
.kit-lane.over{background:var(--wash);border-color:var(--orange)}
/* A case you're about to drop something INTO — a different thing from a lane highlight, and
   both can be under the pointer at once. */
.tile.drop{outline:3px solid var(--orange);outline-offset:2px;background:var(--wash)}
/* WHERE IT WILL LAND when reordering: a line above the tile, or along the bottom of the
   column for last. box-shadow, not a border — a border resizes the tile mid-drag and shoves
   the very thing you're aiming at out from under the cursor. */
.grp.ins{box-shadow:0 -3px 0 0 var(--orange)}
.kit-lane.tail{box-shadow:inset 0 -4px 0 0 var(--orange)}
.tile.arming{background:var(--wash);border-color:var(--orange)}
/* ⚠️ NAMESPACED, and it must stay that way. These were `.lifted` and `.ghost` for one
   deploy, and `.ghost` is ALREADY this app's secondary button (`class="btn ghost"`, used on
   nearly every page) — every one of them turned into a half-transparent tile floating over
   the screen, on booking pages that have nothing to do with the Case Tracker. A class name
   added to tel.css is global to the whole app: prefix anything page-specific with `kit-`. */
.kit-lifted{opacity:.35}
.kit-ghost{position:fixed;z-index:80;pointer-events:none;opacity:.92;transform:rotate(-1.5deg);
  box-shadow:0 10px 26px rgba(0,0,0,.22);border-radius:var(--radius-10,10px);background:var(--card);
  border:1px solid var(--orange);padding:var(--space-10) var(--space-12);min-width:180px}
.kit-ghost b{font-size:var(--text-14)}
/* The store room is ordinary tiles in a GRID, which does its own spacing with `gap` — the
   stacking margin the lanes want would double it. */
.kit-store .grp{margin:0}
/* The board itself: Ready / Out / Returned. Was a hard `repeat(3,1fr)` (inline style), which
   forced a ~756px half-screen window (Greg's normal size — memory half-screen-laptop-width)
   into sideways page scroll. auto-fit collapses empty tracks, so with exactly 3 lanes this
   still renders as three equal columns at 1440px — the same look as before — but drops to
   2, then 1 (stacked) as the window narrows, same pattern as .board.wrap above. */
.kit-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:var(--space-14);align-items:start}
/* LONG TERM — the fold at the foot of the Out lane. Greg, 2026-07-31: the cases that are
   out for eternity "are just hidden behind the long term drop down", rather than in a
   separate "Hidden from the board" list that implied they had left the board. Mirrors the
   wall iPad's `.lt` rules (kit_board.html), which cannot load this stylesheet. */
/* min-height, and it earns it: the fold is a DROP TARGET (Greg, 2026-07-31 — dragging a
   case down into it is the obvious way to park one), and a closed summary alone is a 20px
   strip to aim at with a whole hand. */
.kit-lt{margin-top:var(--space-11);border-top:1px dashed var(--line);padding-top:var(--space-9);min-height:46px}
/* Dragging a case over it. Deliberately louder than a lane highlight: this drop changes
   what the case IS on the board, not just which column it sits in. */
.kit-lt.over{background:var(--wash);border-top-color:var(--orange);
  box-shadow:inset 0 0 0 2px var(--orange);border-radius:var(--radius-10,10px)}
.kit-lt.over>summary{color:var(--orange-600)}
.kit-lt>summary{font-size:var(--text-12);text-transform:uppercase;letter-spacing:var(--caps-track);
  color:var(--muted);font-weight:600;cursor:pointer;padding:var(--space-4) var(--space-2);
  list-style:none}
.kit-lt>summary::-webkit-details-marker{display:none}
/* Drawn chevron (no typed ▸ ▾ — UX-RULES §1): right while shut, down while open. */
.kit-lt>summary:before{content:"";display:inline-block;width:5px;height:5px;margin:0 var(--space-6) var(--space-2) var(--space-2);
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(-45deg);transition:transform .15s}
.kit-lt[open]>summary:before{transform:rotate(45deg)}
.kit-lt .grp{margin-top:var(--space-8,var(--space-8));margin-bottom:0}
/* Parked, not amber: a long-term case can be sitting in any lane, and the fold lives under
   Out because that is where nearly all of them are, not because the colour would be true. */
.kit-lt .tile{border-left:4px solid var(--muted)}
/* THE ACTION SHEET. Slides up from the bottom on a phone in the store room and sits there
   on a desk; same shape as the kiosk's so the two surfaces feel like one tool. */
#kit-card{background:var(--card);color:var(--ink);border-radius:18px 18px 0 0;width:100%;
  max-width:620px;padding:var(--space-18) var(--space-18) var(--space-26);max-height:88vh;overflow:auto}
#kit-card h3{font-size:var(--text-18,18px);margin-bottom:var(--space-2)}
#kit-card .where{color:var(--muted);font-size:var(--text-13);margin-bottom:var(--space-12)}
.kit-act{display:block;width:100%;border:0;border-radius:12px;padding:var(--space-14);font:inherit;
  font-size:var(--text-15,15px);font-weight:600;margin-top:var(--space-10);cursor:pointer}
.kit-act.go{background:var(--orange-600);color:var(--white)}
.kit-act.ok{background:var(--good);color:var(--white)}
.kit-act.plain{background:#efe9e1;color:#171512}
/* Greg: "Edit what this case needs" reads as a label, not an action verb like the
   other plain buttons (Pack it in / Take out of X) — centered on its own, those stay
   left-aligned. */
.kit-act-center{text-align:center}
label.kit-pick{display:block;font-size:var(--text-12);color:var(--muted);
  margin-top:var(--space-12);font-weight:400}
label.kit-pick .fld{margin-top:var(--space-4)}
/* The picked job's dates, one tickable row each — a case goes out for one or several. */
.kit-dates-h{font-size:var(--text-12);color:var(--muted);margin-top:var(--space-10)}
label.kit-date{display:block;padding:var(--space-10) var(--space-12);margin-top:var(--space-4);
  border:1px solid var(--line);border-radius:10px;font-size:var(--text-15);color:var(--ink);cursor:pointer}
label.kit-date input{margin-right:var(--space-10);vertical-align:middle}
.kit-date-lead{color:var(--muted);margin-left:var(--space-8)}
/* Confirmation without a page-jump. The sheet's actions post in the background, so this is
   the only thing that says the server heard you. */
#kit-toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);background:#171512;
  color:#faf8f5;padding:var(--space-13) var(--space-20);border-radius:999px;font-size:var(--text-14);opacity:0;
  transition:opacity .2s;pointer-events:none;z-index:90}
#kit-toast.on{opacity:1}

/* ── Tick lists (CheckboxSelectMultiple) ──────────────────────────────────────
   Annotations #240/#241/#242. Django renders these as unclassed
   <div><label><input> text</label></div>, so they inherited `.fgroup label`
   (display:block, 600 weight) and the box sat on a different line from its
   words. One flex row per option fixes the alignment for every tick list on the
   site at once — the play form, booking contacts, skills, sensitivity tags. */
.chk-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));
  gap:var(--space-2) var(--space-12);margin-top:var(--space-4)}
.chk-grid label{display:flex;align-items:center;gap:var(--space-8);
  font-size:var(--text-13);font-weight:400;color:var(--ink);padding:var(--space-3) 0;
  cursor:pointer;line-height:1.25}
/* THE actual cause of #240. forms.Styled puts class="fld" on every widget, and
   `.fld{width:100%}` was making each checkbox 237px WIDE — an invisible input
   stretched across the column with its tick drawn at one end, which is why the
   box looked like it belonged to a different label. Undo `.fld` here; a tick box
   is the one control that must stay its intrinsic size. */
.chk-grid input[type=checkbox]{flex:0 0 auto;margin:0;width:auto;min-width:0;
  padding:0;border:0;border-radius:0;background:none;accent-color:var(--orange)}
/* Django wraps each option in its own div; it must not fight the grid. */
.chk-grid > div{display:contents}

/* A long tick list starts collapsed — see _field.html for the 12-option rule. */
.chk-box > summary{cursor:pointer;list-style:none;display:flex;align-items:center;
  gap:var(--space-8);font-size:var(--text-12);font-weight:600;color:var(--slate-2)}
.chk-box > summary::-webkit-details-marker{display:none}
.chk-box > summary::before{content:"";flex:none;width:5px;height:5px;margin:0 var(--space-2);color:var(--muted);
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(-45deg);transition:transform .15s}
.chk-box[open] > summary::before{transform:rotate(45deg)}
.chk-box .chk-count{color:var(--muted);font-weight:400}

/* A collapsed "+ Add X" behind a plain <details> (no JS, per its own convention) — the
   "+" flips to "−" once open, same idea as .chk-box/.log-grp above. Annotation #302:
   a static "+" read as wrong once the form underneath was already showing. */
.add-toggle > summary::-webkit-details-marker{display:none}
.add-toggle > summary::before{content:"+ "}
.add-toggle[open] > summary::before{content:"− "}

/* The suggestion card's single "something's wrong" entry point, collapsed behind a plain
   <details> (no JS, same convention as .add-toggle). It opens onto the two ways to act on
   that: a quick reject with an optional reason, or (via .reject-toggle-body's own link)
   asking Claude to fix it instead — one disclosure rather than a permanent reason field +
   Reject button + "Tell Claude" link all sitting in the row at once. */
.reject-toggle{display:inline-block}
.reject-toggle > summary{cursor:pointer;list-style:none;color:var(--bad);font-size:var(--text-12)}
.reject-toggle > summary::-webkit-details-marker{display:none}
.reject-toggle[open]{display:inline-flex;align-items:flex-start;gap:var(--space-8)}
.reject-toggle[open] > summary{color:var(--ink);padding-top:var(--space-4)}
.reject-toggle-body{display:flex;flex-direction:column;gap:var(--space-6);align-items:flex-start}
.reject-toggle-row{display:flex;gap:var(--space-8);align-items:center}
.reject-toggle input{border:1px solid var(--line);border-radius:var(--radius-sm);padding:var(--space-6) var(--space-8);font-family:inherit;font-size:var(--text-12);min-width:180px}
.reject-toggle-body>a{font-size:var(--text-12)}

/* ---- Address type-ahead (addrsuggest.js) --------------------------------------
   The list is appended to <body>, not next to the input, because the address field
   usually sits inside a panel with its own overflow which would clip it. That means it
   is positioned in page coordinates and must sit above everything else on the page. */
.addr-suggest{
  z-index:9999; background:var(--card); border:1px solid var(--line); border-radius:10px;
  box-shadow:0 8px 24px rgba(0,0,0,.13); overflow:hidden; max-height:270px;
  overflow-y:auto; font-size:13px;
}
.addr-suggest-row{ padding:var(--space-8) var(--space-11); cursor:pointer; border-bottom:1px solid var(--line); }
.addr-suggest-row:last-child{ border-bottom:0; }
.addr-suggest-row:hover,.addr-suggest-row.on{ background:var(--orange-50); }

/* ---- Messaging centre (/messages/) ---------------------------------------------
   Greg, 2026-07-30: "laid out like iMessage … the person's image and name and the
   conversation". Two panes on a desktop; ONE at a time on a phone, chosen by .cc-open
   (present only when a conversation is actually open) rather than by duplicating markup.

   THE TWO CHANNEL COLOURS ARE BOTH EXISTING TOKENS, on purpose — this page invents no
   palette. SMS = --orange-600 (the interactive brand orange; white on it measures 5.33,
   clears AA). Email = --slate-2 (#5b6770; white on it measures 5.49, clears AA). They are
   different HUES, not two shades of one, because "was that a text or an email?" has to be
   answerable at a glance and by someone who doesn't see colour the way you do — which is
   also why every bubble carries the word Text or Email as well as the colour.

   Inbound messages keep the channel colour as a left edge + label instead of a fill: their
   words are theirs, and a page where half the bubbles are solid brand orange stops reading
   as a conversation. */
.cc{display:grid;grid-template-columns:320px minmax(0,1fr);gap:var(--space-14);align-items:start}
.cc-list{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  overflow:hidden;max-height:calc(100vh - 190px);overflow-y:auto}
.cc-search{display:flex;gap:var(--space-6);padding:var(--space-10);border-bottom:1px solid var(--line);
  position:sticky;top:0;background:var(--card);z-index:2}
.cc-search input{flex:1;min-width:0;font-size:var(--text-13);padding:var(--space-8) var(--space-10);
  border:1px solid var(--line);border-radius:10px;background:var(--bg);font-family:var(--sans)}
.cc-alert{display:block;margin:var(--space-10);padding:var(--space-8) var(--space-10);
  border-radius:10px;background:var(--bad-50);color:var(--bad);font-size:var(--text-12);
  font-weight:600;text-decoration:none;cursor:pointer}
.cc-alert:hover{background:#fbd6d6}
/* The filtered view's banner is a plain div (nothing to click but its own "Show everyone"
   link), so it keeps .cc-alert's look without the pointer cursor the clickable version has. */
.cc-alert-filter{cursor:default}
.cc-alert-filter a{color:inherit;font-weight:700;text-decoration:underline;margin-left:var(--space-6)}
.cc-sub{padding:var(--space-10) var(--space-12) var(--space-4);font-size:var(--text-11);
  text-transform:uppercase;letter-spacing:var(--caps-track);color:var(--muted);font-weight:600}
.cc-item{display:flex;gap:var(--space-10);align-items:center;padding:var(--space-10) var(--space-12);
  border-bottom:1px solid var(--line-2);text-decoration:none;color:var(--ink)}
.cc-item:hover{background:var(--bg)}
.cc-item.on{background:var(--orange-50)}
.cc-item-body{flex:1;min-width:0;display:block}
.cc-item-top{display:flex;justify-content:space-between;gap:var(--space-8);align-items:baseline}
.cc-item-top b{font-size:var(--text-13)}
.cc-item-top small{color:var(--muted);font-size:var(--text-11);white-space:nowrap}
.cc-prev{display:block;font-size:var(--text-12);color:var(--muted);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis;margin-top:var(--space-2)}
.cc-chan{display:inline-block;font-size:var(--text-10);font-weight:700;text-transform:uppercase;
  letter-spacing:.04em;padding:0 var(--space-5);border-radius:5px;margin-right:var(--space-5);
  color:var(--white);background:var(--slate-2)}
.cc-chan.sms{background:var(--orange-600)}
.cc-chan.app{background:var(--purple)}
.cc-needs{background:var(--bad-50);color:var(--bad);font-size:var(--text-11);font-weight:700}
/* The dismiss "✕" next to the red pill (Greg, 2026-10-07) — mark a person's whole queue
   handled without opening their chat. A span, not a button (see _comms_row.html for why);
   sized for a thumb, not just a mouse. */
.cc-needs-wrap{display:inline-flex;align-items:center;gap:var(--space-5)}
.cc-dismiss{display:inline-flex;align-items:center;justify-content:center;width:22px;
  height:22px;border-radius:50%;color:var(--muted);font-size:var(--text-12);line-height:1;
  cursor:pointer}
.cc-dismiss:hover,.cc-dismiss:focus-visible{background:var(--bg);color:var(--bad)}
.cc-dismiss[aria-disabled="true"]{opacity:.4;pointer-events:none}

/* ---- The live half -------------------------------------------------------------
   Type-ahead results REPLACE the list rather than floating over it. .cc-list is a
   scrolling box with clipped overflow, so an absolutely-positioned dropdown inside it
   would be cut off at the box's edge — and "typing filters the list" is what a messaging
   app does anyway. Every hidden selector is a DIRECT child (>) because .cc-sub and
   .cc-item are reused INSIDE .cc-pop for the results themselves; an unqualified
   descendant selector here would hide the very thing it was opened to show. */
.cc-list.cc-searching > .cc-rows,
.cc-list.cc-searching > .cc-alert,
.cc-list.cc-searching > .cc-sub,
.cc-list.cc-searching > .cc-item{display:none}
.cc-pop .cc-sub{padding-top:var(--space-8)}
/* Keyboard highlight. Its own class rather than reusing .on, which means "this is the
   conversation you have open" — during a search those are two different rows. */
.cc-item.hi{background:var(--orange-50)}

/* A message that arrived while you were looking at the page announces itself and then
   stops: a 2.4s fade on the row, and a dot that stays until you open it. Nothing steals
   focus and nothing pops up — you are already reading, and being interrupted mid-sentence
   by your own inbox is the behaviour people turn off. Literal hex in the keyframe: custom
   properties are resolved but not interpolated between, so a var() here animates as a
   step rather than a fade. */
@keyframes cc-fresh-in{from{background:var(--orange-50)}to{background:transparent}}
.cc-item.cc-fresh{animation:cc-fresh-in 2.4s ease-out 1}
.cc-item.cc-unseen{background:var(--orange-50)}
.cc-item.cc-unseen .cc-item-top b::after{content:"";display:inline-block;width:7px;height:7px;
  border-radius:50%;background:var(--orange-600);margin-left:var(--space-6);vertical-align:middle}
/* Swapping a pane takes one round trip. Dimming it is enough to say "that click landed" —
   a spinner over half a second reads as slower than no spinner at all. */
.cc-pane.cc-loading{opacity:.55}

.cc-pane{background:var(--card);border:1px solid var(--line);border-radius:var(--radius);
  display:flex;flex-direction:column;min-height:420px;overflow:hidden}
.cc-head{display:flex;align-items:center;gap:var(--space-10);padding:var(--space-10) var(--space-14);
  border-bottom:1px solid var(--line)}
.cc-back{display:none;font-size:var(--text-20);text-decoration:none;color:var(--slate-2);
  width:34px;height:34px;align-items:center;justify-content:center;border-radius:9px;
  border:1px solid var(--line);flex:none}
.cc-who{display:flex;align-items:center;gap:var(--space-10);text-decoration:none;color:var(--ink)}
.cc-who-name{display:block;line-height:1.25}
.cc-who-name small{display:block;color:var(--muted);font-size:var(--text-11)}
.cc-thread{flex:1;overflow-y:auto;padding:var(--space-14);max-height:calc(100vh - 380px);
  min-height:240px;background:var(--bg)}
.cc-note{font-size:var(--text-11);color:var(--muted);background:var(--card);
  border:1px dashed var(--line);border-radius:10px;padding:var(--space-8) var(--space-10);
  margin-bottom:var(--space-12);line-height:1.45}
.cc-day{text-align:center;font-size:var(--text-11);color:var(--muted);font-weight:600;
  text-transform:uppercase;letter-spacing:var(--caps-track);margin:var(--space-16) 0 var(--space-8)}
.cc-row{display:flex;margin:var(--space-6) 0}
.cc-row.out{justify-content:flex-end}
.cbub{max-width:80%;min-width:0;border-radius:14px;padding:var(--space-8) var(--space-12);
  font-size:var(--text-13);line-height:1.45;overflow-wrap:anywhere}
.cbub.sms.out{background:var(--orange-600);color:var(--white);border-bottom-right-radius:5px}
.cbub.email.out{background:var(--slate-2);color:var(--white);border-bottom-right-radius:5px}
.cbub.sms.in{background:var(--card);color:var(--ink);border:1px solid var(--line);
  border-left:4px solid var(--orange-600);border-bottom-left-radius:5px}
.cbub.email.in{background:var(--card);color:var(--ink);border:1px solid var(--line);
  border-left:4px solid var(--slate-2);border-bottom-left-radius:5px}
/* The in-app chat (nexus2/appchat.py): purple, the colour of the bell it rings. */
.cbub.app.out{background:var(--purple);color:var(--white);border-bottom-right-radius:5px}
.cbub.app.in{background:var(--card);color:var(--ink);border:1px solid var(--line);
  border-left:4px solid var(--purple);border-bottom-left-radius:5px}
/* An app notice (the bell): quiet, so it reads as the app speaking, not a person. */
.cbub.notice.out{background:var(--purple-50);color:var(--ink);border:1px dashed var(--purple);border-bottom-right-radius:5px}
.cc-meta{font-size:var(--text-10);opacity:.75;margin-top:var(--space-4)}
/* WHO SENT IT, on outbound bubbles only. Its own row rather than a child of .cc-meta,
   because .cc-meta is 75% opacity and a faded headshot on an orange bubble is a smudge —
   the circle needs to be legible while the time beside it stays quiet. */
.cc-foot{display:flex;align-items:center;gap:var(--space-6);margin-top:var(--space-4)}
.cc-foot .cc-meta{margin-top:0;min-width:0}
.cc-by{flex:none;width:20px;height:20px;border-radius:50%;overflow:hidden;
  display:inline-flex;align-items:center;justify-content:center;line-height:1;
  font-size:8px;font-weight:800;letter-spacing:.01em;
  background:rgba(255,255,255,.28);color:var(--white)}
.cc-by img{width:100%;height:100%;object-fit:cover;display:block}
/* "Sent from the office, before we recorded who" — deliberately blank-looking. It must not
   be mistaken for TEL (which would claim the app sent something a person did) and it has no
   name to show, so it is a dot. */
.cc-by-unknown{font-size:var(--text-12);opacity:.8}
/* A message that never left sits on a pale grey bubble, where white-on-white would vanish. */
.cbub.undeliv .cc-by{background:var(--grey);color:var(--white)}
/* A message that never left must not read like one that did. Qualified with the channel
   classes so it BEATS .cbub.sms.out on specificity rather than relying on source order —
   a purpose-built rule losing a tie to a generic one has cost a day here before. */
.cbub.undeliv,.cbub.undeliv.sms.out,.cbub.undeliv.email.out,
.cbub.undeliv.sms.in,.cbub.undeliv.email.in,.cbub.undeliv.app.out,.cbub.undeliv.app.in{
  background:var(--line-2);color:var(--muted);border:1px dashed var(--grey)}
.cc-undeliv{font-size:var(--text-11);font-weight:700;color:var(--bad);margin-top:var(--space-4)}
/* Read receipts: whose face sits against the last message they've seen. Aligned to the same
   side as the message it belongs to, so the eye reads "this person got to HERE" rather than
   having to work out which bubble a floating row refers to. Pulled up tight under the
   bubble (negative margin) because it belongs TO that message, not between two of them. */
.cc-seen{display:flex;gap:var(--space-4);margin:calc(-1 * var(--space-2)) 0 var(--space-8)}
.cc-seen.out{justify-content:flex-end}
.cc-eye{width:18px;height:18px;border-radius:50%;overflow:hidden;flex:none;
  display:inline-flex;align-items:center;justify-content:center;line-height:1;
  font-size:8px;font-weight:800;background:var(--slate-2);color:var(--white);
  border:1.5px solid var(--bg)}
.cc-eye img{width:100%;height:100%;object-fit:cover;display:block}
.cc-handle{display:flex;align-items:center;gap:var(--space-8);margin:var(--space-4) 0 var(--space-10)}
.cc-handle .btn{font-size:var(--text-11);padding:var(--space-3) var(--space-9)}
.cc-empty,.cc-none{padding:var(--space-22);text-align:center;color:var(--muted);font-size:var(--text-13)}

/* Emails arrive folded: subject + one line. <details> does this with no JavaScript, so it
   keeps working inside a fragment the ajax rail swapped in (where an injected <script>
   would never have run). */
.cmail summary{cursor:pointer;list-style:none;display:block;font-size:var(--text-13)}
.cmail summary::-webkit-details-marker{display:none}
.cmail-tag{display:inline-block;font-size:var(--text-10);font-weight:700;text-transform:uppercase;
  letter-spacing:.04em;margin-right:var(--space-6);opacity:.85}
.cmail-peek{display:block;opacity:.8;font-size:var(--text-12);white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.cmail[open] .cmail-peek{display:none}
.cmail-body{margin-top:var(--space-8);padding-top:var(--space-8);font-size:var(--text-12);
  border-top:1px solid rgba(255,255,255,.35)}
.cbub.email.in .cmail-body{border-top-color:var(--line)}

.cc-compose{border-top:1px solid var(--line);padding:var(--space-10) var(--space-14);background:var(--card)}
.cc-toggle{display:flex;align-items:center;gap:var(--space-6);flex-wrap:wrap}
.cc-opt{display:inline-flex;align-items:center;gap:var(--space-5);font-size:var(--text-12);
  font-weight:600;padding:var(--space-4) var(--space-10);border:1px solid var(--line);
  border-radius:999px;cursor:pointer;background:var(--bg)}
.cc-opt.off{opacity:.45;cursor:not-allowed}
.cc-opt:has(input:checked){background:var(--orange-600);color:var(--white);border-color:var(--orange-600)}
.cc-opt-email:has(input:checked){background:var(--slate-2);border-color:var(--slate-2)}
.cc-opt-app:has(input:checked){background:var(--purple);border-color:var(--purple)}
.cc-dest{font-size:var(--text-11);color:var(--muted);margin-left:auto}
.cc-cant{font-size:var(--text-12);color:var(--bad);margin-top:var(--space-6);line-height:1.45}
.cc-cant.info{color:var(--muted)}
/* The subject box exists only for email. :has() on the checked radio does it in CSS, which
   means no per-page script and no state to keep in sync with the server's default. */
.cc-subject{display:none;margin-top:var(--space-8)}
.cc-compose:has(input[value="email"]:checked) .cc-subject{display:block}
.cc-subject input{width:100%;font-size:var(--text-13);padding:var(--space-8) var(--space-10);
  border:1px solid var(--line);border-radius:10px;font-family:var(--sans)}
.cc-write{display:flex;gap:var(--space-8);align-items:flex-end;margin-top:var(--space-8)}
.cc-write textarea{flex:1;min-width:0;font-family:var(--sans);font-size:var(--text-13);
  padding:var(--space-10);border:1px solid var(--line);border-radius:12px;resize:vertical}
/* The character count, texts only — shown/hidden by the same :has() trick as .cc-subject, so
   the Email toggle removes it with no script involved. Muted and small: going over the limit
   is ALLOWED (it just splits), so this is information, not a warning, and it never turns red.
   .split is only a weight change — enough to notice that the answer changed, not enough to
   read as "you have done something wrong". */
.cc-count{display:none;text-align:right;font-size:var(--text-11);color:var(--muted);
  margin-top:var(--space-6);font-variant-numeric:tabular-nums}
.cc-compose:has(input[value="sms"]:checked) .cc-count{display:block}
.cc-count.split{color:var(--slate-2);font-weight:600}
/* The sound toggle. Starts hidden and comms.js unhides it: with no JavaScript there is no
   poll, so there is no sound, so a control that turns it off would be a lie. Text as well as
   an icon — this is the one control someone in an open-plan office has to find in a hurry
   and be certain about, and a bare glyph is a guess. */
.cc-tools{display:flex;justify-content:flex-end;padding:var(--space-6) var(--space-10) 0}
.cc-sound{font-family:var(--sans);font-size:var(--text-11);font-weight:600;cursor:pointer;
  border:1px solid var(--line);border-radius:999px;background:var(--bg);color:var(--muted);
  padding:var(--space-3) var(--space-9)}
.cc-sound:hover{border-color:var(--grey);color:var(--ink)}
.cc-sound[aria-pressed="true"]{background:var(--orange-50);border-color:var(--orange-600);
  color:var(--orange-700)}

@media(max-width:900px){
  .cc{grid-template-columns:minmax(0,1fr)}
  .cc-list{max-height:none}
  .cc.cc-open .cc-list{display:none}
  .cc:not(.cc-open) .cc-pane{display:none}
  .cc-back{display:flex}
  .cc-thread{max-height:none;min-height:50vh}
  .cbub{max-width:88%}
}

/* ── The clients half of the messaging centre ─────────────────────────────────
   Greg, 2026-07-30: "the same chat box/inbox feature needs to apply to clients as well. on
   an individual level and at a whole organisation level." Everything below is ADDITIVE to
   the rules above — the team half's markup gained no classes, so it renders identically. */

/* TEAM / CLIENTS. Two real links (two URLs you can bookmark), styled as one control. Sits
   above the search box because it changes what the search box searches. */
.cc-tabs{display:flex;gap:var(--space-4);padding:var(--space-10) var(--space-10) 0}
.cc-tab{flex:1;text-align:center;font-size:var(--text-12);font-weight:700;text-decoration:none;
  padding:var(--space-6) var(--space-10);border:1px solid var(--line);border-radius:999px;
  color:var(--muted);background:var(--bg)}
.cc-tab:hover{border-color:var(--grey);color:var(--ink)}
.cc-tab.on{background:var(--orange-600);border-color:var(--orange-600);color:var(--white)}

/* An organisation has no face. Square-ish rather than a circle, so a client is never mistaken
   at a glance for a person in the same list — the shape carries the difference, not just the
   fact that one has initials where the other has a photograph. */
.headshot.hs-org{border-radius:10px;background:var(--slate-2)}
.headshot.hs-xs{width:22px;height:22px;font-size:10px;margin:0;flex:none}

/* THE PEOPLE AT AN ORGANISATION — the join between the two levels, and the answer to the
   read-only org thread. Scrolls sideways rather than wrapping to three rows: on a client with
   a dozen contacts the thread is what should own the vertical space. */
.cc-chips{display:flex;align-items:center;gap:var(--space-6);overflow-x:auto;
  padding:var(--space-8) var(--space-14);border-bottom:1px solid var(--line);
  background:var(--bg)}
.cc-chips-lab{font-size:var(--text-11);font-weight:600;color:var(--muted);flex:none;
  text-transform:uppercase;letter-spacing:var(--caps-track)}
/* `.chip.plain.cc-chip`: a person to pick; tighter left edge for the face. */
.cc-chip{gap:var(--space-5);flex:none;font-size:var(--text-12);font-weight:600;line-height:inherit;
  padding:var(--space-3) var(--space-9) var(--space-3) var(--space-4)}
.chip.cc-chip:hover{--chip-bd:var(--orange-600);--chip-fg:var(--orange-700)}
.cc-chip-name{white-space:nowrap}

/* WHICH PERSON AT THE CLIENT said it — org-level thread only, where the whole point is that
   each answer names who it was said to. Small and above the message, so it reads as a label
   on the bubble rather than part of what was written. */
.cc-party{font-size:var(--text-10);font-weight:700;text-transform:uppercase;
  letter-spacing:.04em;opacity:.75;margin-bottom:var(--space-4)}

/* ONE-CHANNEL COMPOSER. A client contact can only be emailed, so there is no radio to hang
   the :has() rules above off — the subject box and the counter are decided in the template and
   simply shown. Qualified with .cc-compose so it beats the display:none defaults on
   specificity rather than on document order (memory css-later-rule-wins-a-specificity-tie). */
.cc-compose.cc-one .cc-subject{display:block}
.cc-compose.cc-one .cc-count{display:block}
.cc-toggle.cc-solo{gap:var(--space-8)}

/* /me/messages/ — a team member's own chat with the office (views_mychat). The office pane's
   thread is capped to the window; here the page itself scrolls, newest at the bottom. */
/* No overflow:hidden: it would make this card the scroll box, and the reply box could not
   stick to the bottom of the SCREEN on a phone (below). */
.mychat{padding:0}
.mychat .mychat-thread{max-height:none;min-height:200px}
.mychat .mychat-note{margin:var(--space-8) 0 0}
/* Phones: the reply box is pinned to the bottom of the screen, like any chat app (Greg,
   4 Oct 2026). Sticky, not fixed, so it never covers the last message and still sits in the
   page flow on a tall screen. The note under it hides to keep the pinned box small. */
@media (max-width:700px){
  .mychat .cc-compose{position:sticky;bottom:0;z-index:5;
    padding-bottom:calc(var(--space-10) + env(safe-area-inset-bottom, 0px));
    box-shadow:0 -4px 12px rgba(0,0,0,.06)}
  .mychat .mychat-note{display:none}
}
/* The Lead chase: someone has not answered the check-in, so CALL them (appchat.people_to_call). */
.mychat-call{padding:var(--space-14);margin-bottom:var(--space-14);background:var(--bad-50);
  border:1px solid var(--bad)}
.mychat-call-head{font-weight:800;color:var(--bad);font-size:var(--text-16);margin-bottom:var(--space-8)}
.mychat-call-row{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:var(--space-8);padding:var(--space-8) 0;border-top:1px solid var(--line)}
.mychat-call-note{font-size:var(--text-12);margin-top:var(--space-8)}
/* A session's team chat (nexus2/teamchat.py). */
.teamchat-head{padding:var(--space-14);margin-bottom:var(--space-14)}
.teamchat-when{font-size:var(--text-12);margin-top:var(--space-4)}
.teamchat-closed{margin-top:var(--space-8);font-weight:700;color:var(--slate)}
.teamchat-start{padding:var(--space-14)}
.cbub.app.in.teamchat-office{border-left-color:var(--orange-600)}
.teamchat-row{display:flex;justify-content:space-between;align-items:center;gap:var(--space-10);
  padding:var(--space-12) var(--space-14);border-bottom:1px solid var(--line);color:var(--ink);
  text-decoration:none}
/* A Lead's "Message your team" box, above their chat. */
.mychat-team{padding:var(--space-14);margin-bottom:var(--space-14)}
.mychat-team-head{cursor:pointer;font-weight:700;font-size:var(--text-14)}
.mychat-team form{margin-top:var(--space-10)}
.mychat-team-who{font-size:var(--text-12);margin-top:var(--space-4)}
/* The Lead's face on a message from them: a white ring so it reads on a pale bubble. */
.cbub.in .cc-by-lead{background:var(--purple);color:var(--white)}
/* Quick replies: one tap fills the box (appchat.quick_replies). Finger-sized, in a row
   that wraps on a phone. */
.quick-replies{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--space-6);
  margin-top:var(--space-10)}
.quick-reply{font:inherit;font-size:var(--text-13);font-weight:600;color:var(--purple);
  background:var(--purple-50);border:1px solid var(--purple);border-radius:999px;
  padding:var(--space-6) var(--space-12);min-height:36px;cursor:pointer}
.quick-reply:hover,.quick-reply:focus-visible{background:var(--purple);color:var(--white)}

/* ── The floating chat bubble ──────────────────────────────────────────────────
   Greg, 2026-07-30: "a little message bubble/window in the bottom right corner the way
   facebook messenger appears."

   ⚠️ THE BOTTOM-RIGHT CORNER ALREADY HAD A TENANT. #scrolltop is a 42px circle at
   right:22px/bottom:22px, and the Annotate control owns bottom-LEFT. The launcher takes the
   corner and pushes #scrolltop up above it with a sibling selector — which is why
   {% comms_widget %} is written BEFORE that button in base.html. Two round controls in the
   same 42px of screen is a mis-tap, not a layout preference. */
.ccw{position:fixed;right:22px;bottom:22px;z-index:60}
.ccw ~ #scrolltop{bottom:78px}
@media(max-width:600px){ .ccw ~ #scrolltop{bottom:calc(72px + env(safe-area-inset-bottom, 0px))} }

/* Annotation #250 (Greg, on /kit/): "can this pill just be the symbol, not the word
   'messages'". A round icon button, not a pill — it sits over content on every page in the
   app, and the smallest thing that still reads as "messages" is the right size for that. The
   word survives as screen-reader-only text below: a button labelled only by an <svg>
   announces itself as "button". */
.ccw-fab{display:inline-flex;align-items:center;justify-content:center;cursor:pointer;
  width:46px;height:46px;color:var(--white);
  background:var(--ink);border:none;border-radius:50%;padding:0;
  box-shadow:var(--shadow-lg);position:relative}
.ccw-lbl{position:absolute;width:1px;height:1px;padding:0;margin:calc(-1 * var(--space-1));overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
.ccw-fab:hover{background:var(--orange-600)}
.ccw-fab.has-new{background:var(--orange-600)}
/* Annotation #795 (Greg): the open panel grows out of the bubble and takes its place, so the
   bubble hides while it is open and the panel sits on the corner, not above it. */
.ccw[data-open="1"] .ccw-fab{visibility:hidden}
.ccw[data-open="1"] .ccw-panel{bottom:0;transform-origin:100% 100%;animation:ccw-grow .16s ease-out}
@keyframes ccw-grow{from{transform:scale(.2);opacity:0}to{transform:scale(1);opacity:1}}
@media(prefers-reduced-motion:reduce){.ccw[data-open="1"] .ccw-panel{animation:none}}
/* The count, on the launcher as well as in the tab title: the bubble's whole job is to be
   noticeable from a page that is about something else. Hidden at zero — see comms.js.
   It is `.badge.ccw-badge` (red, corner-placed), defined under "Bits". */

.ccw-panel{position:absolute;right:0;bottom:56px;width:min(94vw,720px);
  background:var(--card);border:1px solid var(--line);border-radius:16px;
  box-shadow:var(--shadow-lg);overflow:hidden;display:flex;flex-direction:column}
/* ⚠️ `display:flex` ABOVE BEATS THE `hidden` ATTRIBUTE, and this rule is what puts it back.
   The browser's own `[hidden]{display:none}` is a plain element rule, so any class rule that
   sets `display` outranks it — the panel shipped permanently open, sitting over the bottom
   half of every page in the app and swallowing clicks on whatever was underneath.
   Playwright missed it because the assertion asked whether the `hidden` ATTRIBUTE was present
   (it was, and always had been) instead of whether the element was actually visible. Any
   `hidden` element this stylesheet gives a `display` to needs this line. */
.ccw-panel[hidden]{display:none}
.ccw-bar{display:flex;align-items:center;gap:var(--space-10);padding:var(--space-10) var(--space-14);
  border-bottom:1px solid var(--line);background:var(--bg);font-size:var(--text-13)}
.ccw-full{margin-left:auto;font-size:var(--text-11);font-weight:700;text-decoration:none;
  color:var(--muted)}
.ccw-full:hover{color:var(--orange-700)}
.ccw-x{background:none;border:none;cursor:pointer;font-size:var(--text-20);line-height:1;
  color:var(--muted);padding:0 var(--space-4)}
.ccw-x:hover{color:var(--ink)}

/* The panel hosts the SAME .cc grid as /messages/, just shorter and with its own scroll. Only
   the sizes are overridden — the layout, the rows, the bubbles and the composer are the page's
   own rules, which is what keeps the widget from becoming a second design.

   ⚠️ EVERY RULE HERE IS ABOUT HEIGHT, AND THE FIRST VERSION GOT IT WRONG IN A WAY THAT ONLY A
   SCREENSHOT CAUGHT. On /messages/ the pane is as tall as the viewport allows
   (`.cc-thread{max-height:calc(100vh - 380px)}`) and `.cc{align-items:start}` lets each column
   take its content height. Dropped into a 460px panel that produces a pane ~570px tall whose
   bottom — the composer — is simply clipped off by the panel's `overflow:hidden`. The page
   rendered, nothing errored, no overflow was reported, and there was just no message box.

   The fix is `align-items:stretch` + `height:100%` down the chain, and `min-height:0` so the
   thread is allowed to be SHORTER than its content and scroll. A flex child defaults to
   `min-height:auto`, which means "never shrink below your content" — that default is what was
   pushing the composer out. */
.ccw-cc{height:min(70vh,460px);border:none;border-radius:0;align-items:stretch;gap:0}
.ccw-cc .cc-list{max-height:none;height:100%;min-height:0;border:none;border-radius:0;
  border-right:1px solid var(--line)}
.ccw-cc .cc-pane{height:100%;min-height:0;border:none;border-radius:0}
.ccw-cc .cc-thread{max-height:none;min-height:0}
.ccw-cc .cc-search{padding:var(--space-8) var(--space-10)}
/* One pane at a time in a 366px-wide panel, same as the phone breakpoint does for the page. */
.ccw-cc .cc-chips{padding:var(--space-6) var(--space-10)}

/* On a phone the panel is nearly the whole width, so anchoring it to the launcher leaves 2px
   of margin on one side and 22px on the other. Pinned to both edges instead — it is a sheet at
   this size, not a popover. */
@media(max-width:600px){
  .ccw-panel{position:fixed;left:10px;right:10px;bottom:80px;width:auto}
}

/* Nothing floating belongs on paper — the same rule #scrolltop already follows. */
/* Greg, 2026-09-23 [06:38]: "right away on mobile view for office staff, we need to hide
   the message circle in the bottom right corner." On a laptop the bubble sits in dead space;
   on a phone it sits ON the content, and it is the one floating control whose panel takes
   the whole screen. The messages centre is still in the menu, so nothing is lost — the
   launcher just stops covering the page at the size where the page has no room to spare.
   Same 600px phone breakpoint .ccw-panel already uses above. */
@media(max-width:600px){.ccw{display:none}}

@media print{.ccw{display:none}}

/* ── Casting grid: the status in words, on demand ──────────────────────────────
   Roadmap item "casting cell labels visible without hovering". Every cell renders BOTH a
   symbol (.cs-ab) and the words (.cs-lb); this decides which is shown, and the toggle above
   the grid flips it per person.

   Default stays the symbol. The grid is a column per date and a row per person — it only fits
   on screen at symbol width, so words are a deliberate choice somebody makes for one sheet,
   not a change imposed on everyone scanning colour across forty dates. */
.cs-cell .cs-lb{display:none}
.cs-grid.cs-labels .cs-cell .cs-ab{display:none}
/* ⚠️ A FIXED WIDTH ON THE INNER BLOCK, not just a min-width on the cell. A table cell sizes
   itself to its content, so `white-space:normal` alone let a long label ("Held — University of
   NSW - AGSM") stretch its column to ~270px and pushed most of the dates off screen — in the
   one mode somebody turned on to read more, not less. Fixing the width here makes the text
   wrap instead of the column grow. Wrapping to two or three lines is the right trade: labels
   like "Work offer sent" and "Not available" differ at the END, so an ellipsis would leave
   exactly the wrong half. */
.cs-grid.cs-labels .cs-cell .cs-lb{display:block;font-size:var(--text-11);font-weight:700;
  line-height:1.2;white-space:normal;overflow-wrap:break-word;width:104px;margin:0 auto;
  padding:0 var(--space-2)}
.cs-grid.cs-labels .cs-cell{min-width:112px;height:auto;min-height:42px;
  padding:var(--space-5) 0}
/* The ✉ / ★ cells are bumped up to 17px for the symbol; that size on a sentence is shouting. */
.cs-grid.cs-labels .cs-cell.cs-offer{font-size:var(--text-13)}
.cs-labels-toggle[aria-pressed="true"]{background:var(--orange-50);border-color:var(--orange-600);
  color:var(--orange-700)}
/* A whole sentence, not a chip. `.cs-legend span{display:inline-flex}` above makes every child
   a flex item, which stacks this sentence's <b> into its own column once the row is tight. */
.cs-legend .cs-legend-note{display:block;flex:1 1 100%;margin-left:0;line-height:1.5}

/* Quick select — a mode, so it has to LOOK like one. Greg, 2026-07-31: working out a cast was
   "too many steps" because every click opened the editor panel. While this is on, a plain
   click is the tick, so the grid gets a pointer that says "I am a switch" rather than "I open
   something", and the toggle stays lit. */
.cs-quick-toggle[aria-pressed="true"]{background:var(--orange-50);border-color:var(--orange-600);
  color:var(--orange-700)}
.cs-grid.cs-quick .cs-cell{cursor:copy}
.cs-grid.cs-quick .cs-cell:hover{box-shadow:inset 0 0 0 2px var(--orange-600)}
/* `display:block` for the same reason as `.cs-legend-note` above, and it was missed here:
   without it this span keeps the legend's inline-flex, its <b> becomes a flex ITEM, and on a
   phone the sentence renders interleaved — "Quick select is on — a click   opens / selects or
   deselects   Shift+click the full editor". A whole sentence is a block, not a chip. */
/* `.cs-legend .cs-quick-hint`, not `.cs-quick-hint` — `.cs-legend span{display:inline-flex}`
   is a class PLUS an element, so it outranks a bare class and this rule was losing the tie it
   never had. Same fix, same reason, as `.cs-legend .cs-legend-note` above; without it the <b>
   becomes a flex ITEM and the sentence renders interleaved on a phone: "Quick select is on —
   a click   opens / selects or deselects   Shift+click the full editor". */
.cs-legend .cs-quick-hint{display:block;flex:1 1 100%;margin-left:0;line-height:1.5}

/* 1st choice / 2nd choice — the number in the corner of a picked cell. Greg, 2026-07-31:
   "selecting someone as option #1, then if you select someone else, the page already knows
   that they're option 2 (and shows #2)."

   Top-LEFT, because the auto-cast marker already owns the top-right (.cs-cell.auto::after) and
   two dots in one corner is a mystery rather than two facts. Small, high-contrast and
   tabular: it is scanned down a column, so the digits have to line up. */
.cs-pick{position:absolute;top:2px;left:3px;min-width:14px;height:14px;padding:0 var(--space-3);
  border-radius:7px;background:var(--ink);color:var(--white);font-size:var(--text-10);font-weight:800;
  line-height:14px;text-align:center;font-variant-numeric:tabular-nums;
  box-shadow:0 0 0 1.5px rgba(255,255,255,.85)}
.cs-grid.cs-labels .cs-pick{top:3px;left:4px}

/* ---------- Booking pages: DENSITY (2026-09-03, Greg) ---------- */
/* Greg, straight after the edge-rail + icon-rail deploy reclaimed 376px of width:
   "It definitely got us some real estate back on the screen, but now everything is just
   stretched wider — I'm not actually getting more information on the screen. It's all very
   vertically spaced and now there's space to put things next to each other and fit them in
   a bit better." Same complaint Jacob made in the client meeting: v2 shows LESS per screen
   than v1.

   Density here means SHOWING MORE AT ONCE, never hiding it more cleverly — nothing below
   adds an accordion or a "show more". It buys the space three ways:
     1. the 1320px .content cap is lifted for these two pages, so a 1920 monitor stops
        rendering identically to a 1440 one (before this, everything past ~1320px was pure
        margin);
     2. the booking form's field grid goes THREE columns once there is room;
     3. the gaps between things that belong together get tighter.

   Scope: `.bkedit` is the top-level wrapper on exactly two templates — booking_form.html
   and booking_detail.html (verified by grep) — so every rule below is scoped to it and
   reaches nothing else. `:has()` is used for the width because base.html is not this
   change's to edit; a browser without `:has()` simply keeps the 1320px cap, which is
   today's live behaviour, so the fallback is "no change" rather than "broken".

   LINE LENGTH: the width is deliberately NOT spent uniformly. A grid of short labelled
   inputs and a table of sessions genuinely want the extra width; a paragraph of prose gets
   HARDER to read as it widens. So every multi-line text control on these pages is capped at
   a readable measure (.bk-measure below) even where its grid cell is wider. */
/* 2026-09-04, Greg, in three steps that all landed on the same fix:
   1. "the edges of my booking page... traffic light and the width of the page" — an extra
      right-padding, but only inside the session editor's own grid.
   2. still misaligned against Logistics/Production/Financial, because `.bkpanel.on`
      (booking_detail.html) drew its OWN box — `width:calc(100vw - var(--nav-w))`, entirely
      outside `.content` — so nothing done to `.content` could ever have reached it. Patched
      by giving `.bkpanel.on` a matching --rail-clear reserve of its own.
   3. "the whole page should just stretch to that width, adaptable to the traffic light" —
      right diagnosis, wrong shape: TWO independently-computed boxes that happened to be
      patched to the same width can't be trusted to stay that way (`.bkedit` itself was
      ALSO independently reserving 20px for the same rail, found and removed the same day).
      One width, computed once: `.content` itself now full-bleeds edge-to-edge of the
      viewport (nav to the right edge, minus room for the rail), the same way `.bkpanel.on`
      used to on its own — see that rule (booking_detail.html) for the matching left-edge
      half of this, now just `width:100%` of this box, no escape hatch of its own left.
      Sessions, Logistics, Production, Financial — everything in `.bkedit` — is now simply a
      normal, unstyled-for-width child of ONE box, so they can't drift apart again. */
.content:has(> .bkedit){max-width:none;width:calc(100vw - var(--nav-w))}
/* The bookings list fills the page the same way (Greg, 2026-09-13: "they should also fill
   out the whole width of the page on bookings list"). .ml-toolbar is its first block. */
.content:has(> .ml-toolbar){max-width:none;width:calc(100vw - var(--nav-w))}
.content:has(.chk-rail){padding-right:calc(var(--content-pad) + var(--rail-clear))}
/* 2026-09-07: below tablet width the rail is display:none (still in the DOM, so the rule
   above keeps matching) — reclaim its 78px reserve. Placed AFTER the base rule on purpose:
   an identical-specificity override inside the @media(max-width:960px) block up near
   `.chk-rail{display:none}` loses the cascade to this later, unconditional rule — source
   order breaks the tie, not which one is behind a media query. */
@media(max-width:900px){.content:has(.chk-rail){padding-right:var(--content-pad)}}

/* Tighter vertical rhythm between things that belong together. The separation between
   genuinely different SECTIONS (the orange-ruled .bk-grp headings, the accordions) is kept —
   only the within-a-group spacing shrinks. */
.bkedit .form-grid{gap:var(--space-10) var(--space-18)}
.bkedit .fgroup.bk-grp{margin:var(--space-14) 0 var(--space-2);padding-top:var(--space-9)}
.bkedit .acc{margin-bottom:var(--space-10)}
.bkedit .acc>summary{padding:var(--space-11) var(--space-18)}
.bkedit .acc-body{padding:var(--space-2) var(--space-18) var(--space-14)}
/* The save bar is being laid out for its agreed END shape — one primary action plus the save
   STATUS as reassurance (see the long note above .bkedit-actions in booking_form.html). The
   status is the SECOND element now, not something trailing a Cancel button, so it needs room
   to say a whole sentence ("Not saved — Client needs fixing") instead of the 230px sliver it
   had when it was third in the row; Cancel is demoted to the far end. */
.bkedit-actions .save-state{max-width:340px;margin-left:var(--space-8)}

/* The floating save pill (savepill.js / _savepill.html, 2026-09-11). Fixed top-right under
   the header, icon + a few words, colour = state. Deliberately small: it is reassurance,
   not a toolbar. On a phone the words go and the icon carries it (title/aria-label keep
   the text). z-index sits under modals (1000+) and over cards. */
.tel-savepill{position:fixed;top:calc(var(--header-h) + 10px);right:18px;z-index:120;
  display:inline-flex;align-items:center;gap:var(--space-6);height:30px;padding:0 var(--space-11) 0 var(--space-9);
  border-radius:999px;border:1px solid var(--line);background:var(--card);color:var(--muted);
  font:600 12px/1 inherit;font-family:inherit;letter-spacing:.01em;cursor:pointer;
  box-shadow:0 2px 10px rgba(0,0,0,.10);transition:color .15s,border-color .15s,transform .15s}
.tel-savepill[hidden]{display:none}
.tel-savepill:hover{border-color:var(--ink);color:var(--ink)}
.tel-savepill:focus-visible{outline:2px solid var(--orange);outline-offset:2px}
.tel-savepill.is-pressed{transform:scale(.96)}
.tel-savepill .tel-savepill-ic{display:inline-flex;width:16px;height:16px}
.tel-savepill[data-state="saved"]{color:var(--good)}
.tel-savepill[data-state="dirty"]{color:#8a6a10;border-color:#e6c76a}
.tel-savepill[data-state="saving"]{color:#8a6a10}
.tel-savepill[data-state="attention"]{color:var(--white);background:var(--bad);border-color:var(--bad)}
.tel-savepill[data-state="attention"]:hover{color:var(--white);border-color:var(--bad)}
.tel-savepill-spin{animation:tel-savepill-spin 1s linear infinite}
@keyframes tel-savepill-spin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){.tel-savepill-spin{animation:none}}
/* Annotation #465 — clicking the save pill's "needs attention" scrolls to the problem;
   this makes it unmistakable once in view instead of a quiet, easy-to-miss scroll. */
.tel-attn-flash{animation:tel-attn-flash 2s ease;border-radius:var(--radius-sm)}
@keyframes tel-attn-flash{0%,100%{background:transparent}15%,55%{background:var(--bad-50)}}
@media (prefers-reduced-motion:reduce){.tel-attn-flash{animation:none;background:var(--bad-50)}}
@media (max-width:600px){.tel-savepill{right:12px;padding:0 var(--space-8)}
  .tel-savepill .tel-savepill-txt{display:none}
  .tel-savepill[data-state="attention"] .tel-savepill-txt{display:inline}
  /* Submit mode is a page's ONLY save/close — an icon alone is too cryptic for that. The
     phone rule further down (.tel-savepill .tel-savepill-lbl{display:none}, ~line 4126)
     hides the whole label wrapper, so re-show the WRAPPER, with enough specificity
     (attribute + 2 classes) to win wherever this sits in the file. */
  .tel-savepill[data-savepill-submit] .tel-savepill-txt{display:inline}
  .tel-savepill[data-savepill-submit] .tel-savepill-lbl{display:inline-grid}
  /* annotation #389 (Greg, 8 Oct 2026) — a phone otherwise only ever sees the icon, so a
     finished autosave looked like nothing happened. savepill.js adds this class for 2s
     right after a save completes, so the word briefly shows, then the icon-only rule
     above takes back over. */
  .tel-savepill.just-saved .tel-savepill-txt{display:inline}}

/* Marked-up diffs in a clash panel (nexus2/merge.py diff_html): what one side added is
   highlighted, what it removed is struck through, so two versions can be READ against each
   other instead of eyeballed. */
/* Generic clash panel + presence banner for saverail-wired forms (booking details,
   session editor — never-lose-typing plan, Phase 3). Same compare-and-choose shape as the
   run sheet's own panel, rendered by saverail.js at the top of the form. */
.tel-conflict{margin:0 0 var(--space-12);padding:var(--space-10) var(--space-14);border:1.5px solid var(--bad);
  border-radius:10px;background:#fbeeee;font-size:var(--text-14)}
.tel-conflict-msg{font-weight:600;color:var(--ink);margin-bottom:var(--space-6)}
.tel-conflict-field{margin-top:var(--space-8)}
.tel-conflict-label{display:block;font-size:11px;letter-spacing:var(--caps-track);text-transform:uppercase;color:var(--muted);font-weight:600;margin-bottom:var(--space-3)}
.tel-conflict-cmp{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-10)}
@media (max-width:700px){.tel-conflict-cmp{grid-template-columns:1fr}}
.tel-conflict-side{background:var(--card);border:1px solid var(--line);border-radius:8px;padding:var(--space-8) var(--space-10);white-space:pre-wrap;overflow-wrap:anywhere;font-size:13px;max-height:220px;overflow:auto}
.tel-conflict-sub{display:block;font-size:var(--text-12);color:var(--muted);margin-bottom:var(--space-3)}
.tel-conflict-actions{display:flex;gap:var(--space-8);margin-top:var(--space-10);flex-wrap:wrap}
.tel-presence{margin:0 0 var(--space-10);padding:var(--space-8) var(--space-12);border-radius:8px;background:#e3f0ff;color:#1a4b8a;
  font-size:13px;display:flex;align-items:center;gap:var(--space-10);flex-wrap:wrap}
.tel-presence[hidden]{display:none}
.tel-presence .btn{font-size:12px;padding:var(--space-4) var(--space-10)}
mark.diff-ins{background:#d8f0dd;color:inherit;border-radius:2px;padding:0 var(--space-1)}
del.diff-del{background:#f8dcdc;color:#7a1f1f;text-decoration:line-through;border-radius:2px;padding:0 var(--space-1)}
.bkedit-actions .bkedit-leave{margin-left:var(--space-8)}

/* A readable measure for anything that holds sentences rather than a value. Applied to the
   textareas on the booking form (see .bk-measure in booking_form.html) so widening the page
   widens the FIELD GRID, not the reading line. */
.bkedit .bk-measure,.bkedit textarea.fld{max-width:760px}

/* THREE columns for the booking form's own field grid once there is room for them.
   `.bk-fields` is a class added to that one grid in booking_form.html rather than a bare
   `.bkedit .form-grid`, on purpose: the session editor and several partials inside .bkedit
   carry their own .form-grid rows that this change does not own and must not reflow.
   Literal px in the media query and inside the block — CSS custom properties do not resolve
   in a media condition, and this file has been bitten by that before. */
/* Two-of-three cells, for the handful of blocks that genuinely need more than a third of a
   row — the Additional note and its tickboxes, the non-delivery pair. At two columns `span 2`
   is the full row (exactly what these had before); at three it is two thirds.
   GATED at 901px, matching the `@media(max-width:900px){.form-grid{grid-template-columns:1fr}}`
   rule above. `span 2` inside a genuinely ONE-column grid does not clamp — it creates an
   implicit second track, the `1fr` first column then collapses towards nothing, and every
   ordinary field on the page renders one word wide. Measured: it added ~980px of height to
   the phone layout before this gate was added. */
@media (min-width:901px){
  .bk-fields>.bk-span2{grid-column:span 2}
}
@media (min-width:1400px){
  .bk-fields{grid-template-columns:repeat(3,minmax(0,1fr))}
  /* #157's "put this under Cohort" pin was written when the grid was two columns wide and
     Cohort landed in column 2. Cohort is in a cell of its own now (booking_form.html wraps
     the field and its disclosure together), so the pin would only misplace it. */
  .bk-fields>.fgroup.under-col2{grid-column:auto}
}

/* ---------- One purchase-order area (2026-09-03, Greg) ---------- */
/* Greg: "you duplicated the purchase order and payment box and then added a purchase order
   section below, so that's very confusing." This morning's PO upload was wired in as a
   SECOND section under the finance block instead of being merged into it, so the page asked
   about purchase orders twice under two headings.
   They are one area now: `#fincard` (BookingFinance — the stored PO number and the payment
   tracking) and `#pocard` (the uploaded PO PDF and what was read out of it) sit side by side
   inside a single card, sharing one "Purchase order & payment" heading, with the PO document
   panel demoted to a sub-heading inside it.
   BOTH ids are kept exactly as they were: they are ajaxform-rail targets
   (data-ajax-target="#fincard" / "#pocard") and the rail replaces those nodes' contents on
   every save — deleting either id breaks saving. Only the wrapper around them is new.
   The PO NUMBER is still one input in one place (BookingFinance.po_number, in the finance
   block); the PO document panel's own po_number belongs to the PurchaseOrder row and writes
   THROUGH to it on confirm, which is unchanged. */
.po-merge{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-16)}
@media (min-width:1200px){
  .po-merge{grid-template-columns:minmax(0,1fr) minmax(0,1.2fr);gap:0 var(--space-26);align-items:start}
  .po-merge>#pocard{border-left:1px solid #e9e5e1;padding-left:var(--space-26)}
}

/* ---------- Upload box: ONE box that is the drop zone AND the file picker ---------- */
/* nexus2/static/nexus2/dropupload.js, loaded site-wide. Greg, 3 Sep 2026: "there should just be
   a way to drag and drop"; 12 Oct 2026: "surely these two fields anywhere they appear could
   merge into one" — as a style rule for every upload in the app. So every <input type=file>
   is turned into ONE dashed box (.dropzone-hint + .dropzone-pick): click it or press
   Enter/Space to choose, drop a file on it, and it shows the chosen names. The native input
   stays in the form, visually hidden (.dropzone-native), and keeps the keyboard focus; the box
   draws the ring (.is-focus). One ring only: hover is a tint, never a second orange border.
   .dropzone = any element that takes a drop (a whole panel, for the PO). */
.dropzone{position:relative;border-radius:var(--radius-sm);transition:background .12s}
.dropzone.is-dragover{background:var(--orange-50)}
.dropzone-hint{font-size:var(--text-12);color:var(--muted);display:flex;align-items:center;gap:var(--space-6);
  border:1px dashed var(--line);border-radius:var(--radius-sm);padding:var(--space-8) var(--space-11);
  margin-top:var(--space-8);background:var(--card)}
.dropzone-hint .ic{flex:none}
.dropzone-pick{cursor:pointer;min-height:40px;box-sizing:border-box}
.dropzone-pick:hover{background:var(--bg);color:var(--ink)}
.dropzone-pick:focus-visible,.dropzone-pick.is-focus{outline:2px solid var(--orange);outline-offset:2px}
.dropzone-pick.has-files{border-style:solid;color:var(--ink)}
.dropzone-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-2) var(--space-8);
  flex:1 1 auto;min-width:0}
.dropzone-names{font-weight:600;overflow-wrap:anywhere;min-width:0}
.dropzone-change{color:var(--orange-700);font-weight:600;margin-left:auto;white-space:nowrap}
.dropzone.is-dragover .dropzone-hint,.dropzone-hint.is-dragover{border-color:var(--orange);color:var(--orange-600)}
/* The native input: still in the form, still focusable, still `required` (a hidden-by-opacity
   input keeps the browser's "please select a file" bubble; display:none would lose it).
   !important because older templates still give file inputs an inline width/border. */
input.dropzone-native{position:absolute!important;width:1px!important;height:1px!important;
  padding:0!important;margin:0!important;border:0!important;opacity:0!important;overflow:hidden!important;
  min-width:0!important;pointer-events:none}
.dropzone-msg:empty{display:none}
.dropzone-msg{font-size:var(--text-12);margin-top:var(--space-6);color:var(--slate-2)}
/* Not white-on-orange anywhere here: brand orange fails WCAG AA for white text (3.15:1). */
.dropzone-msg.is-bad{color:var(--bad);font-weight:600}

/* ---------- Job Owner (showrunner) block: one line (2026-09-03, Greg) ---------- */
/* "That box is taking three lines up. That's a lot of visual space." It was a heading row, a
   name row, an assigned-by row and a cross-job link row — four lines to say who owns the job.
   One flex line now: label + owner + Change. The assign form drops onto its own line only
   when it is opened (.sr-form's full flex basis), so the closed state costs one line and the
   open state costs two. The explanation moved into the shared infotip; see the note at the
   top of _showrunner_block.html. */
.sr-line{display:flex;align-items:center;gap:var(--space-4) var(--space-10);flex-wrap:wrap;font-size:var(--text-14)}
.sr-ttl{font-size:14px;margin:0;flex:0 0 auto}
.sr-line .sr-btn{margin-left:auto}
.sr-form{flex:1 1 100%;margin-top:var(--space-6)}

/* ── Destructive controls: a red rubbish can, never the word "Delete" ────────
   Greg, 2026-09-03. ONE component (nexus2/templates/nexus2/_delbtn.html) so the
   glyph, the size, the red and the placement are identical on every surface.

   .delbtn        row-level delete in a list/table/card — ICON ONLY.
   .delbtn.unlink detaches a link, destroys no record — grey broken chain. A
                  different act must not look like the same act.
   .dellink       the ONE delete that ends a page (bottom of an edit form).
                  Keeps its words: it is not repeated, so the words are the
                  safety, and the bin makes it findable at a glance.

   Red = --bad #c62828, deliberately NOT the brand orange #F16622 (3.15:1, fails
   AA). #c62828 measures 5.62:1 on --card #fff, 5.12:1 on --bg #f6f4f2, 5.48:1 on
   the #fbfaf9 row-hover fill and 4.83:1 on its own #fbeaea hover fill — AA at
   every size on all four. Colour is never the only signal: the bin shape carries
   it for colour-blind users and the aria-label carries it for screen readers.

   32x32 minimum hit area — the office runs this on iPads and a 14px glyph is a
   mis-tap. Bigger target + the confirm() that is still on every one of these. */
.delbtn{-webkit-appearance:none;appearance:none;background:none;border:0;margin:0;padding:0;
  width:32px;height:32px;min-width:32px;min-height:32px;flex:0 0 auto;border-radius:8px;
  display:inline-flex;align-items:center;justify-content:center;line-height:0;
  vertical-align:middle;cursor:pointer;color:var(--bad);font-family:inherit;
  transition:background .12s ease,color .12s ease}
.delbtn svg.ic{width:16px;height:16px}
.delbtn:hover{background:var(--bad-tint);color:var(--bad-700)}
.delbtn:focus-visible{outline:2px solid var(--bad);outline-offset:1px;background:var(--bad-tint)}
.delbtn[disabled]{opacity:.4;cursor:default}
.delbtn.unlink{color:var(--slate-2)}
.delbtn.unlink:hover{background:var(--line-2);color:var(--ink)}
.delbtn.unlink:focus-visible{outline-color:var(--slate-2);background:var(--line-2)}
/* .delbtn.edit — same glyph button, neutral (not destructive). Pencil, not a bin. */
.delbtn.edit{color:var(--slate-2)}
.delbtn.edit:hover{background:var(--line-2);color:var(--ink)}
.delbtn.edit:focus-visible{outline-color:var(--slate-2);background:var(--line-2)}
.dellink{display:inline-flex;align-items:center;gap:var(--space-4);min-height:32px;
  padding:var(--space-2) var(--space-6);background:none;border:0;border-radius:8px;
  color:var(--bad);cursor:pointer;font-family:inherit;font-size:var(--text-13);font-weight:600;
  text-align:left;transition:background .12s ease,color .12s ease}
.dellink svg.ic{width:15px;height:15px}
.dellink:hover{background:var(--bad-tint);color:var(--bad-700)}
.dellink:focus-visible{outline:2px solid var(--bad);outline-offset:1px;background:var(--bad-tint)}
/* A delete sitting in a right-aligned actions cell should not stretch the row's
   other controls; keep it on the text baseline of a dense list. */
td>.delbtn,td>form>.delbtn{vertical-align:middle}
/* A bin sitting on top of a photograph can't rely on the page background for its
   contrast — give it a white disc to sit on so the red reads against any image. */
.delbtn.onphoto{background:rgba(255,255,255,.94);box-shadow:0 1px 4px rgba(0,0,0,.4)}
.delbtn.onphoto:hover{background:var(--card);color:var(--bad-700)}

/* ---------- Run sheet edit history (Kelly lost a long block of writing, twice — see
   RunSheetRowVersion's docstring in nexus2/models.py). Per-row History button/panel in
   _runsheet_row.html, and the fetched fragment _runsheet_row_history.html reuses these
   plus .run-rich (defined above) for the read-only content it shows per version. ---------- */
.run-history-btn{font-size:12px;margin-left:var(--space-6)}
.run-history-panel{margin-top:var(--space-8);border-top:1px solid var(--line);
  padding-top:var(--space-8)}
.run-history-panel[hidden]{display:none}
.run-hist-entry{border:1px solid var(--line);border-radius:var(--radius-sm,8px);
  padding:var(--space-8) var(--space-10);margin-bottom:var(--space-8);background:var(--card)}
.run-hist-entry:last-child{margin-bottom:0}
.run-hist-entry summary{cursor:pointer;font-size:var(--text-13);list-style:none}
.run-hist-entry summary::-webkit-details-marker{display:none}
.run-hist-entry summary::before{content:"";display:inline-block;width:5px;height:5px;margin:0 var(--space-6) var(--space-2) var(--space-2);
  color:var(--muted);border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(-45deg);transition:transform .15s}
.run-hist-entry[open] summary::before{transform:rotate(45deg)}
.run-hist-body{margin-top:var(--space-8);display:flex;flex-direction:column;gap:var(--space-6)}
.run-hist-body .rf-label{display:block;font-size:11px;color:var(--muted);
  margin-bottom:var(--space-2)}
.run-hist-body .run-rich{border:1px solid var(--line);border-radius:6px;padding:var(--space-6) var(--space-8);
  font-size:var(--text-14);background:var(--bg-2,#faf9f7)}

/* draft prompt (site-wide) — never-lose-typing Phase 2 (saverail.js's wireDraftBuffer()).
   Same look as runsheet_detail.html's own .rs-draft-prompt (that block is untouched; this
   is a COPY so every saverail-wired form gets the same "you have unsaved changes" bar,
   not just the run sheet). Keep the two in sync if the look ever changes. */
.rs-draft-prompt{margin-top:var(--space-6);padding:var(--space-8) var(--space-10);
  border-radius:var(--radius-sm);border:1px solid var(--orange);
  background:var(--orange-50);font-size:var(--text-13)}
.rs-draft-msg{color:var(--ink)}
.rs-draft-list{margin:var(--space-6) 0 0;padding-left:var(--space-18);font-size:var(--text-13)}
.rs-draft-list li+li{margin-top:var(--space-4)}
.rs-draft-kept{color:var(--ink)}
.rs-draft-actions{display:flex;gap:var(--space-8);margin-top:var(--space-8)}

/* "Save and continue" leave modal — saverail.js's askBeforeLeaving() (2e). Replaces the
   two confirm()-based lose-or-stay prompts (this file's own cross-form submit guard and
   boost.js's boosted-navigation guard) plus ajaxform.js's `reload` path, all of which now
   share this one dialog so there is exactly one "about to lose your typing" experience on
   the whole site. */
.tel-leave-modal{position:fixed;inset:0;z-index:900;display:flex;align-items:center;
  justify-content:center;background:rgba(20,18,16,.45);padding:var(--space-16,var(--space-16))}
.tel-leave-box{background:var(--card);border-radius:var(--radius-sm);
  border:1px solid var(--line);box-shadow:0 10px 40px rgba(0,0,0,.25);
  padding:var(--space-16,var(--space-16)) var(--space-16,var(--space-16));max-width:420px;width:100%}
.tel-leave-msg{color:var(--ink);font-size:var(--text-14);margin:0 0 var(--space-8) 0}
.tel-leave-status{color:var(--muted);font-size:var(--text-13);margin:0 0 var(--space-8) 0}
.tel-leave-status[hidden]{display:none}
.tel-leave-actions{display:flex;gap:var(--space-8);flex-wrap:wrap}

/* "Send an availability check?" — the session editor's Close pop-up (_avail_ask.html,
   2026-09-21). Deliberately the SAME box as the leave modal above it, plus the list of
   who would be emailed: one "a box is asking you something" look on the whole site. */
.avail-ask .avail-ask-list{list-style:none;margin:0 0 var(--space-10);padding:0;
  max-height:220px;overflow:auto}
.avail-ask .avail-ask-list li{display:flex;justify-content:space-between;gap:var(--space-8);
  padding:var(--space-4) 0;border-bottom:1px solid var(--line);font-size:var(--text-13)}
.avail-ask .avail-ask-list li:last-child{border-bottom:0}
.avail-ask .avail-ask-name{color:var(--ink);font-weight:600}
.avail-ask .avail-ask-email{color:var(--muted);font-size:var(--text-12);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---- RM-17 (2026-09-12): shared card partials — classes replacing inline styles ----
   _session_card.html / _booking_svc_card.html / _ml_card.html. `sc-` = shared card. Only
   DYNAMIC styles (a template variable in the value) and JS-toggled `display:none` stay
   inline in those files. Sizes snap to the type tokens (no more half-pixel font sizes). */
.pill.alert{background:var(--orange-600);color:var(--white);font-weight:700;gap:var(--space-5);padding:var(--space-3) var(--space-9);border-radius:999px}
.pill.holiday{color:var(--white);background:var(--pink)}
.pill.strong{font-weight:700}
.pill.tight{padding:0 var(--space-6)}
.btn.sm{font-size:var(--text-12);padding:var(--space-5) var(--space-14)}
.btn.xs{font-size:var(--text-11);padding:var(--space-2) var(--space-8)}
.btn.f12{font-size:var(--text-12)}
.btn.f13{font-size:var(--text-13)}
.job-badge.md{font-size:15px;padding:var(--space-2) var(--space-9)}
.job-badge.lg{font-size:17px;padding:var(--space-2) var(--space-10)}
.date-chip.fixed{flex:0 0 52px}
.date-chip .yr{font-size:var(--text-10);opacity:.75;margin-top:var(--space-1)}
.kv.top{align-items:flex-start}
.kv.mid{align-items:center}
.kv .k.auto{min-width:0}
.record-notice{display:flex;align-items:flex-start;gap:var(--space-10);background:var(--amber-50);border-top:1px solid var(--amber-600);border-bottom:1px solid var(--amber-600);padding:var(--space-10) var(--space-16)}
.record-notice .ic{font-size:16px;line-height:1.3}
.record-notice .txt{font-size:var(--text-13);line-height:1.4}
.sc-card{margin-bottom:var(--space-12)}
.sc-sub{margin-top:var(--space-10);border-top:1px dashed var(--line);padding-top:var(--space-10)}
.sc-sub.sc-noline{border-top:0;padding-top:0;margin-top:var(--space-4)}   /* first block in a column: no rule above it */
/* A section whose heading row is a <details> summary with an add control hard right
   ("+ Add" ↔ "− Close"), the form/box as the details' content at full column width, and the
   list AFTER the details so it never collapses — session notes + travel on the session card. */
.sc-addsec>summary{cursor:pointer}
.sc-addsec>summary::-webkit-details-marker{display:none}
/* THE "+ Add" button — every "+ Add" in the app uses exactly this (Greg, 2026-09-15: "any
   time there's a '+ add' it should be the exact same style of button"). Travel's size.
   Toggles carry two labels that flip on aria-expanded (addbutton.js) or on the <details>
   they head. See addbutton.js's header for the markup. */
/* margin-left:auto — + Add sits hard right of its section heading row, always (Greg, 15 Oct
   2026). text-transform/letter-spacing/font-family — inside an upper-case heading (.sec-title,
   the Rooms row on a venue) it still reads "+ Add". */
.btn.btn-add{font-size:var(--text-12);padding:var(--space-4) var(--space-9);background:transparent;
  border-color:transparent;color:var(--slate-2);white-space:nowrap;line-height:1.5;margin-left:auto;
  text-transform:none;letter-spacing:normal;font-family:var(--sans)}
/* line-height is set, not inherited: inside a heading (the run sheet's Handouts <h2>) the
   inherited value made it 24px tall vs 28px everywhere else. */
.btn.btn-add:hover{background:var(--bg);color:var(--ink)}
/* Two side by side (a run sheet row's + Handouts / + Slides): only the first takes the auto
   margin, so the pair sits together at the right. */
.btn.btn-add+.btn.btn-add{margin-left:0}
/* A btn-add-styled control that is NOT a section's add — the play page's "+ Client version"
   in the top bar's action group, the "Edit logistics" / "Edit info" / "Edit link" toggles —
   keeps its place among its neighbours. */
.btn.btn-add.btn-add-stay{margin-left:0}
/* A section with no heading row of its own: wrap the button in this to right-align it. */
.btn-add-row{display:flex;justify-content:flex-end;align-items:center;gap:var(--space-8)}
/* A <details> whose summary is the + Add, sitting in a flex row: push the whole thing right. */
.btn-add-details{margin-left:auto}
/* A <summary> button is a block flex box, so the auto margin above puts it hard right of
   its <details> too, open or shut. */
summary.btn-add{list-style:none;cursor:pointer;width:max-content;display:flex}
summary.btn-add::-webkit-details-marker{display:none}
.btn-add .btn-add-on{display:none}
.btn-add[aria-expanded="true"] .btn-add-off,
details[open]>summary.btn-add .btn-add-off,
details[open]>summary .btn-add .btn-add-off{display:none}
.btn-add[aria-expanded="true"] .btn-add-on,
details[open]>summary.btn-add .btn-add-on,
details[open]>summary .btn-add .btn-add-on{display:inline}
.sc-subhead{font-size:var(--text-11);color:var(--muted);text-transform:uppercase;letter-spacing:var(--caps-track);font-weight:600}
.sc-subhead.mb{margin-bottom:var(--space-5)}
.sc-sum{font-size:var(--text-13);color:var(--muted);font-weight:600}
.sc-sum{cursor:pointer}
.sc-row{display:flex;align-items:center;gap:var(--space-8);flex-wrap:wrap}
.sc-between{display:flex;align-items:center;justify-content:space-between;gap:var(--space-8)}
.sc-pills{display:flex;flex-wrap:wrap;gap:var(--space-6)}
.sc-flex1{flex:1;min-width:0}
.sc-rel{position:relative}
.sc-nomark{list-style:none}
.sc-nolink{text-decoration:none}
.sc-m0{margin:0}
.sc-pre{white-space:pre-wrap}
.sc-preline{white-space:pre-line}
.sc-italic{font-style:italic}
.sc-box{border:1px solid var(--line);border-radius:9px;padding:var(--space-10)}
.sc-input{border:1px solid var(--line);border-radius:8px;padding:var(--space-7) var(--space-9);font-family:inherit;font-size:var(--text-13)}
.sc-decline{flex-direction:column;gap:var(--space-8);margin-top:var(--space-8)}
.sc-declrow{display:flex;gap:var(--space-6);align-items:center;flex-wrap:wrap}
.sc-declrow input{flex:1;min-width:180px}
.sc-linkbtn{background:none;border:none;color:var(--bad);cursor:pointer;font-size:var(--text-13);font-family:inherit;font-weight:600}
.sc-warnbox{margin:0 0 var(--space-10);padding:var(--space-8) var(--space-12);border-radius:10px;font-size:var(--text-13);color:var(--amber-600);background:var(--amber-50)}
.sc-badbox{margin-bottom:var(--space-10);padding:var(--space-9) var(--space-12);border-radius:9px;background:var(--bad-50);border:1px solid var(--bad);font-size:var(--text-13);line-height:1.45}
.sc-badtext{color:var(--bad);font-weight:600}
.sc-legacynote{white-space:pre-wrap;font-size:var(--text-13);padding:var(--space-8) 0;border-bottom:1px solid var(--line)}
.sc-override{font-size:var(--text-12);margin-top:var(--space-2)}
.sc-ti-head{font-weight:600}
.sc-ti-head .muted{font-weight:400}
.sc-report{margin-top:var(--space-10);border:1px solid var(--line);border-radius:10px;padding:var(--space-11) var(--space-14);background:var(--bg)}
.sc-report-head{display:flex;justify-content:space-between;align-items:baseline;gap:var(--space-10);flex-wrap:wrap}
.sc-report-items{margin-top:var(--space-8);display:grid;gap:var(--space-7)}
.sc-report-q{font-size:var(--text-11);text-transform:uppercase;letter-spacing:.3px}
.sc-report-a{font-size:var(--text-14);white-space:pre-wrap}
.ml-snote{font-size:var(--text-12);margin-top:var(--space-3);color:var(--amber-600);white-space:pre-line;line-height:1.3}
/* spacing + size helpers — declared LAST so they win a specificity tie with the rules above */
.sc-mt2{margin-top:var(--space-2)}.sc-mt3{margin-top:var(--space-3)}.sc-mt5{margin-top:var(--space-5)}.sc-mt6{margin-top:var(--space-6)}.sc-mt8{margin-top:var(--space-8)}.sc-mt10{margin-top:var(--space-10)}
.sc-mb4{margin-bottom:var(--space-4)}.sc-mb8{margin-bottom:var(--space-8)}.sc-mb10{margin-bottom:var(--space-10)}.sc-mb12{margin-bottom:var(--space-12)}
.sc-ml6{margin-left:var(--space-6)}
.sc-11{font-size:var(--text-11)}.sc-12{font-size:var(--text-12)}.sc-13{font-size:var(--text-13)}.sc-14{font-size:var(--text-14)}

/* ‹ Period › stepper (pay period / financial year) — call sheet, actor profile, financials.
   Lived only in callsheet.html's inline <style> until 24 Sep 2026, so on the actor profile
   and Financials the two arrows fell onto their own lines above and below the year (Jacob,
   iPhone 15 Pro). One copy here, used by all three. */
.pay-nav{display:flex;align-items:center;gap:var(--space-6);min-width:0}
.pay-nav-mid{display:flex;align-items:baseline;gap:var(--space-8);min-width:0}
.pay-arrow{font-size:var(--text-18);line-height:1;padding:0 var(--space-6);font-weight:700;
  border:none;background:none;color:var(--muted);text-decoration:none}
.pay-arrow:hover{color:var(--orange-600)}
/* Actor profile Engagements row: what the session IS (its own name, or the job's title) and
   when it runs, so two same-day rows — "Core Team Meeting" 11:00 and "InTel's Team Meeting"
   12:30 — stop reading as one job printed twice. */
.eng-what{font-weight:600}
.eng-when{color:var(--muted);font-size:var(--text-12)}

/* ---- RM-17 part 2 (2026-09-12): client_detail.html + actor_detail.html off inline styles.
   Page-level helpers; `sc-` naming continues from the block above. ---- */
.card.pad-list{padding:var(--space-4) 0}
.card.pad-14{padding:var(--space-14)}
.card.pad-16{padding:var(--space-16)}
.card.pad-20{padding:var(--space-20)}
.card.pad-14-16{padding:var(--space-14) var(--space-16)}
.h3-14{font-size:var(--text-14);margin:0}
.pill.ink{background:var(--ink);color:var(--white)}
.pill.orange{background:var(--orange-600);color:var(--white)}
.pill[data-lvl=on]{background:var(--ink);color:var(--white)}
.pill[data-lvl=off]{opacity:.6}
.fin-strip-x{gap:var(--space-1);background:var(--line);border-radius:14px;overflow:hidden}
.sc-cardbg{background:var(--card)}
.sc-warnmsg{background:var(--amber-50);border:1px solid var(--amber-600);border-radius:12px;padding:var(--space-12) var(--space-14);margin-bottom:var(--space-12)}
.sc-ptr{cursor:pointer}
.sc-orange{color:var(--orange)}
.sc-bad{color:var(--bad)}
.sc-bold{font-weight:600}
.sc-normal{font-weight:400}
.sc-nowrap{white-space:nowrap}
.sc-wrap{white-space:normal}
.sc-block{display:block}
.sc-inline{display:inline;margin:0}
.sc-iflex{display:inline-flex}
.sc-iflex-c{display:inline-flex;align-items:center;gap:var(--space-6)}
.sc-vmid{vertical-align:middle}
.sc-right{text-align:right}
.sc-dim{opacity:.6}
.sc-em7{font-size:.7em}
.sc-w100{width:100%}
.sc-w220{max-width:220px}
.sc-grow150{flex:1;min-width:150px}
.sc-grow170{flex:1;min-width:170px}
.sc-grow220{flex:1;min-width:220px}
.sc-flexw140{flex:1;min-width:140px}
.sc-noshrink{flex-shrink:0}
.sc-inherit{color:inherit}
.sc-nocaps{text-transform:none;letter-spacing:0}
.sc-caps{font-size:var(--text-11);font-weight:700;text-transform:uppercase;letter-spacing:.03em}
.sc-fieldlbl{font-size:var(--text-12);font-weight:600;margin-bottom:var(--space-7)}
.sc-stagehead{margin-top:var(--space-14);padding-bottom:var(--space-4);border-bottom:2px solid var(--line);font-weight:700;font-size:var(--text-13)}
.sc-row.top{align-items:flex-start}
.sc-row.bottom{align-items:flex-end}
.sc-row.gap14{gap:var(--space-14)}
.sc-row.gap36{gap:36px}
.sc-rowb{display:flex;gap:var(--space-8);flex-wrap:wrap;align-items:baseline}
.sc-between.wrap{flex-wrap:wrap}
.sc-spread{display:flex;justify-content:space-between;gap:var(--space-10);flex-wrap:wrap}
.sc-spread.top{align-items:flex-start}
.sc-spread.base{align-items:baseline}
.sc-stack{margin-top:var(--space-12);display:grid;gap:var(--space-8);max-width:420px}
.sc-stack6{gap:var(--space-6);margin-top:var(--space-10);max-width:420px}
.sc-padx{padding:0 var(--space-14) var(--space-12)}
.sc-padx2{padding:0 var(--space-14) var(--space-14)}
.sc-pb8{padding:0 0 var(--space-8)}
.sc-pb10{padding:0 0 var(--space-10)}
.sc-rowlink{display:block;border-top:1px solid var(--line);padding:var(--space-8) 0;text-decoration:none;color:inherit}
.sc-line7{padding:var(--space-7) 0;border-bottom:1px solid var(--line)}
.sc-line8{padding:var(--space-8) 0;border-bottom:1px solid var(--line)}
.sc-line10{padding:var(--space-10) 0}
.sc-bb{border-bottom:1px solid var(--line)}
.sc-loc{border:1px solid var(--line);border-radius:12px;padding:var(--space-12) var(--space-14);margin-top:var(--space-10)}
.sc-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin:0 var(--space-3) var(--space-1) var(--space-6)}
.sc-dot.bad{background:var(--bad);margin:0 var(--space-6) 0 0}
.sc-linkbtn.orange{color:var(--orange);font-size:var(--text-12);padding:0;font-weight:400}
.sc-xbtn{background:none;border:none;color:inherit;cursor:pointer;font-size:var(--text-12);margin-left:var(--space-4);padding:0}
.sc-fnpanel{margin:0 0 var(--space-12) 150px;padding:var(--space-8) 0 var(--space-8) var(--space-12);border-left:2px solid var(--line)}
.sc-hsvar{width:120px;text-align:center}
.sc-hscircle{width:80px;height:80px;border-radius:50%;overflow:hidden;margin:0 auto var(--space-8);border:2px solid var(--orange);background:var(--bg);display:flex;align-items:center;justify-content:center}
.sc-cover{width:100%;height:100%;object-fit:cover}
.sc-mini{width:100%;border-collapse:collapse;font-size:var(--text-12);margin-bottom:var(--space-12)}
.sc-mini th,.sc-mini td{padding:var(--space-4)}
.sc-mini-h{text-align:left;color:var(--muted)}
.sc-mini-r{border-top:1px solid var(--line)}
.kv .k.sc-k150{min-width:150px}
.kv .k.sc-k220{min-width:220px}
.kv .k.sc-k220w{width:220px;flex:0 0 220px;word-break:break-word}
/* Profile HR cards (_profile_card.html): long labels like "Other organisations they work with"
   need the 220px on a laptop, but on a phone that leaves the value ~100px — halve it. */
@media (max-width:600px){ .kv .k.sc-k220w{width:130px;flex:0 0 130px} }
.sc-mt4{margin-top:var(--space-4)}.sc-mt12{margin-top:var(--space-12)}.sc-mt16{margin-top:var(--space-16)}
.sc-mb6{margin-bottom:var(--space-6)}.sc-mb16{margin-bottom:var(--space-16)}.sc-mb18{margin-bottom:var(--space-18)}.sc-mb20{margin-bottom:var(--space-20)}
.sc-ml4{margin-left:var(--space-4)}.sc-ml8{margin-left:var(--space-8)}
.sc-10{font-size:var(--text-10)}

/* ---- RM-17 part 3 (2026-09-12): booking_detail.html + _session_card_top_row.html off inline
   styles. Same `sc-` naming; only the status pill's live colour stays inline. ---- */
.card.pad-18{padding:var(--space-18)}
.h3-14b{font-size:var(--text-14);margin-bottom:var(--space-8)}
.btn.md{font-size:var(--text-13);padding:var(--space-6) var(--space-12)}
.btn.xsm{font-size:var(--text-12);padding:var(--space-4) var(--space-9)}
.sc-num{font-size:var(--text-13);font-weight:700;color:var(--slate-2)}
.sc-b7{font-weight:700}
.sc-strike{text-decoration:line-through}
.sc-cap{text-transform:capitalize}
.sc-breakall{word-break:break-all}
.sc-orange6{color:var(--orange-600)}
.sc-ellip{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.sc-scrollx{overflow-x:auto}
.sc-jstart{justify-content:flex-start}
.sc-jcenter{justify-content:center}
.sc-bt{border-top:1px solid var(--line)}
.sc-pt12{padding-top:var(--space-12)}
.sc-hr{border-top:1px solid var(--line);margin:var(--space-4) 0}
.sc-sect{padding-bottom:var(--space-12);border-bottom:1px solid var(--line);margin-bottom:var(--space-12)}
.sc-sub2{margin-top:var(--space-12);border-top:1px solid var(--line);padding-top:var(--space-10)}
.sc-edge-orange{border-color:var(--orange)}
.sc-unissued{margin-top:var(--space-8);padding:var(--space-12) var(--space-16);max-width:100%;overflow-x:auto}
.sc-grouplbl{font-size:var(--text-12);font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:var(--caps-track);margin-bottom:var(--space-8)}
.sc-cardlbl{font-size:var(--text-13);font-weight:600;margin-bottom:var(--space-8);display:flex;align-items:center;gap:var(--space-4)}
.sc-menu{max-width:280px;display:flex;flex-direction:column;gap:var(--space-6)}
.sc-dupform{padding:var(--space-8) var(--space-4) var(--space-2)}
.sc-checklbl{display:flex;align-items:center;gap:var(--space-8);font-size:var(--text-13);margin:var(--space-6) 0}
.sc-col3{margin-top:var(--space-6);display:flex;flex-direction:column;gap:var(--space-3)}
.sc-col5{margin-top:var(--space-10);display:flex;flex-direction:column;gap:var(--space-5)}
.sc-changed{font-size:var(--text-13);margin:calc(-1 * var(--space-2)) 0 var(--space-10);color:var(--amber-600)}
.sc-sigimg{max-width:280px;margin-top:var(--space-8);border:1px solid var(--line);border-radius:8px;background:var(--card)}
.sc-quote{white-space:pre-wrap;font-size:var(--text-13);margin-top:var(--space-6);padding:var(--space-8) var(--space-10);background:var(--bg);border-left:3px solid var(--line)}
.sc-quote.tall{max-height:340px;overflow:auto}
.sc-marksent{display:flex;gap:var(--space-4);margin:var(--space-6) 0 0}
.sc-input.slim{padding:var(--space-4) var(--space-7);font-size:var(--text-12)}
.sc-superseded{padding:var(--space-8) var(--space-14);border-top:1px solid var(--line)}
.sc-pop{position:absolute;right:0;top:calc(100% + 6px);z-index:20;width:270px;background:var(--card);border:1px solid var(--line);border-radius:11px;padding:var(--space-12);box-shadow:0 8px 24px rgba(0,0,0,.12)}
.facts.sc-autofit{margin-top:0;grid-template-columns:repeat(auto-fit,minmax(190px,1fr))}
/* the unissued-changes before/after table (no .tbl base) */
.sc-difftbl{border-collapse:collapse;font-size:var(--text-13)}
.sc-difftbl th{text-align:left;padding:var(--space-4) var(--space-14) var(--space-6);border-bottom:1px solid var(--line);white-space:nowrap}
.sc-difftbl td{padding:var(--space-5) var(--space-14);white-space:nowrap}
.sc-difftbl th:first-child,.sc-difftbl td:first-child{padding-left:0}
.sc-difftbl td:first-child{font-weight:600}
.sc-difftbl td.major{font-weight:700;background:var(--amber-50)}
.sc-difflist{margin:0;padding-left:var(--space-18);font-size:var(--text-13)}
/* .tbl cell-padding variants + cell widths */
.tbl.sc-td4 td{padding:var(--space-4) var(--space-8)}
.tbl.sc-td7 td{padding:var(--space-7) var(--space-8)}
.tbl td.sc-pad6-8{padding:var(--space-6) var(--space-8)}
.tbl td.sc-hug{white-space:nowrap;width:1%;padding-right:28px}
.sc-maxw460{max-width:460px}
.sc-minw230{min-width:230px}
.sc-w56{width:56px}.sc-w140{width:140px}.sc-w150{width:150px}.sc-w170{width:170px}
.sc-mt0{margin-top:0}.sc-mt7{margin-top:var(--space-7)}.sc-mt9{margin-top:var(--space-9)}.sc-mt14{margin-top:var(--space-14)}
.sc-mb0{margin-bottom:0}.sc-mb2{margin-bottom:var(--space-2)}.sc-mb14{margin-bottom:var(--space-14)}

/* ── Booking header, study H (Greg, 2026-09-13) ──────────────────────────────────────
   The booking page's identity lives in the sticky top bar: client on line one, the job
   pill + course on line two. The pill's colour is the whole job's state (nexus2/jobstate.py:
   Draft amber, Quote orange, Confirmed green, Cancelled red, Delivered grey) — the same
   .pill modifiers every other status uses. The page's sections sit on one long page and
   .bk-jump holds the shortcuts to them: inside the top bar from 1360px, in the sticky
   .bk-strip under it below that, where Edit and the menu join them on a phone.
   Literal px in the @media conditions — var() does not resolve there. */
.pill.job-pill{font-variant-numeric:tabular-nums;letter-spacing:.02em}
.pill.job-pill.is-cancelled{text-decoration:line-through}
.topbar .crumb .pill.job-pill{margin-right:var(--space-6);font-family:var(--display);font-size:var(--text-16);
  letter-spacing:.04em}
.bk-jump{display:flex;gap:var(--space-2);flex:none;font-size:var(--text-13);font-weight:600}
.bk-jump a{display:inline-flex;align-items:center;gap:var(--space-5);padding:var(--space-7) var(--space-9);
  border-radius:var(--radius-sm);color:var(--muted);text-decoration:none;white-space:nowrap}
.bk-jump a:hover{color:var(--ink)}
.bk-jump a.on{background:var(--orange-50);color:var(--orange-600)}
.bk-jump .bdg{background:var(--amber-50);color:var(--amber-600);border-radius:999px;
  padding:0 var(--space-6);font-size:var(--text-11);line-height:16px}
.bk-sp{flex:1 1 auto}
.bk-acts{display:flex;align-items:center;gap:var(--space-8);flex:none}
.bk-acts .sub-pop{right:0;left:auto}
.topbar:has(> .bk-jump) > div:not(.bkedit-actions){flex:0 1 auto;min-width:170px}
.bk-strip{display:none;align-items:center;gap:var(--space-8);position:sticky;top:var(--header-h);z-index:19;
  height:44px;margin:calc(-1 * var(--content-pad)) calc(-1 * var(--content-pad)) var(--space-16);
  padding:0 var(--content-pad);background:rgba(246,244,242,.94);backdrop-filter:saturate(1.4) blur(8px);
  border-bottom:1px solid var(--line)}
/* The fade marks "more this way" — it sits over padding the width of the fade, so a
   strip that fits shows its last shortcut in full (was a flat 90%, which greyed out half
   of "Case studies" on a half-screen laptop window even with room to spare). */
.bk-strip .bk-jump{flex:0 1 auto;margin-left:auto;overflow-x:auto;scrollbar-width:none;padding-right:var(--space-22);
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - var(--space-22)),transparent);
  mask-image:linear-gradient(90deg,#000 calc(100% - var(--space-22)),transparent)}
.bk-strip .bk-jump::-webkit-scrollbar{display:none}
.bk-strip .bk-acts{display:none}
.bk-page{--bk-strip-h:0px}
@media (max-width:1359px){
  .topbar > .bk-jump{display:none}
  .bk-strip{display:flex}
  .bk-page{--bk-strip-h:44px}
}
@media (max-width:600px){
  .topbar > .bk-acts{display:none}
  .bk-strip .bk-acts{display:flex}
}
/* The run sheet's sections (2026-09-15 — its three tabs became one long page with the same
   shortcuts, _runsheet_jump.html). A jumped-to section clears the sticky top bar, and the
   44px .bk-strip under it below 1360px. */
/* .jump-sec is the same thing for any other one-long-page screen (the actor job page,
   2026-09-24). Both now ride the global html scroll-padding-top (top of this file), which
   adds the strip's 44px wherever a .bk-strip is on the page. */
/* A session's team as small overlapping faces, the lead first with an orange ring
   ({% team_faces %}). Reuses the .person-hs circle so it matches every other face. */
.faces{display:inline-flex;align-items:center;flex:none}
.faces .person-hs{position:relative;vertical-align:middle;margin-left:calc(-1 * var(--space-7));box-shadow:0 0 0 2px var(--card)}
.faces .person-hs:first-child{margin-left:0}
.faces .person-hs.lead{box-shadow:0 0 0 2px var(--orange-600)}
/* A team member whose day-before check-in is red pulses on the closed card's faces
   (Greg, 20 Oct 2026) — same meaning as the pulsing status pill. The inner ring keeps its
   colour (white, or orange for the lead); the red ring grows outside it. */
.faces .person-hs.ci-urgent{z-index:10;--ring:var(--card);animation:face-pulse 1.6s ease-in-out infinite}
.faces .person-hs.lead.ci-urgent{--ring:var(--orange-600)}
@keyframes face-pulse{0%,100%{box-shadow:0 0 0 2px var(--ring),0 0 0 3px var(--bad)}
  60%{box-shadow:0 0 0 2px var(--ring),0 0 0 8px color-mix(in srgb,var(--bad) 0%,transparent)}}
@media (prefers-reduced-motion:reduce){.faces .person-hs.ci-urgent{animation:none;box-shadow:0 0 0 2px var(--ring),0 0 0 4px var(--bad)}}
/* #512 (Greg, 5 Oct 2026: "hand of cards") — without an explicit stack order, a face with
   no z-index of its own paints OVER an earlier sibling, so with 3+ faces the middle one
   ends up pinched between its two neighbours and mostly hidden. Each face now sits a
   little behind the one before it, like a fanned hand of cards — covers team_faces'
   own 6-face limit. */
.faces .person-hs:nth-child(1){z-index:9}
.faces .person-hs:nth-child(2){z-index:8}
.faces .person-hs:nth-child(3){z-index:7}
.faces .person-hs:nth-child(4){z-index:6}
.faces .person-hs:nth-child(5){z-index:5}
.faces .person-hs:nth-child(6){z-index:4}
.faces-more{margin-left:var(--space-5);font-size:var(--text-11);font-weight:600;color:var(--muted)}
/* A pulsing face rises above the fanned stack so its red ring is not hidden. */
.faces .person-hs.ci-urgent:nth-child(n){z-index:10}
/* Bookings list: ONE toolbar row instead of four (Greg, 2026-09-13) — undated-jobs pill,
   range picker, expand/collapse, Status board. Wraps on a narrow screen. */
.ml-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--space-8) var(--space-14);margin:0 0 var(--space-12)}
.ml-toolbar .ml-range{display:flex;align-items:center;gap:var(--space-6)}
.ml-toolbar .ml-range-lbl{font-size:var(--text-12)}
.ml-toolbar .ml-date{padding:var(--space-4) var(--space-6);font-size:var(--text-12);width:132px}
.ml-allctl{display:flex;align-items:center;gap:var(--space-6);font-size:var(--text-12);flex:0 0 auto}
/* "Show cancelled" switch (Greg + Davina, 15 Oct 2026) — mlcancelled.js hides a cancelled
   session once it's been seen; this is its toolbar label + the hidden-count it fills in. */
.ml-showcancelled{display:flex;align-items:center;gap:var(--space-6);font-size:var(--text-12);
  color:var(--muted);flex:0 0 auto;cursor:pointer}
.ml-showcancelled .fld-check{width:14px;height:14px}
.svc.ml-cancel-gone{display:none}
/* A team row's name line — face · name · role · LEAD · 🚗 Travel / ⚠ Conflict — on ONE line
   while it fits; when the pills run into the fee / status column they wrap to a hanging indent
   under the NAME, not under the face (Greg, 2026-09-13). The face is 24px + a 6px gap = 30px.
   Pills and bubbles reset the indent, or their own first line would inherit the -30px. */
.team .m .tm-line{padding-left:var(--space-30);text-indent:-30px}
/* The Lead or Solo actor (Greg, 20 Oct 2026): no LEAD / SOLO pill — a soft orange fill runs behind their face, name
   and role, so the eye finds the Lead without one more word on the row. No outline (Greg, same
   day: "let's not do an outline"). inline-block at the face's own 24px height so the fill hugs the
   headshot instead of sitting on the text's shorter line box, and rounded at the face's own 12px
   radius (Greg: "match the rounding and height of the circle headshot"). On a phone, where the
   name and role wrap, the fill keeps the row's hanging indent so the second line sits under the
   name, not under the face. The word is still there for
   a screen reader (.tm-lead-word). Booking page team row + bookings list card. */
.tm-lead{display:inline-block;line-height:24px;background:var(--orange-50);
  border-radius:12px;padding:0 var(--space-10) 0 var(--space-30);text-indent:-30px}
.tm-lead .person-hs{vertical-align:middle}
.tm-lead-word{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);
  clip-path:inset(50%);white-space:nowrap;border:0}
/* A long work address broke mid-word on a phone and stranded two characters on their own
   line ("poppin@theexperiencelab.co" / "m.au") — seen in the 23 Sept 2026 phone check of the
   actor call sheet. `anywhere` lets the break land at the @ or a dot instead of wherever the
   character count happens to run out, and only bites at widths where the line genuinely does
   not fit. Applies to the phone/email links on a team row, nothing else. */
.team .m .contact-link{overflow-wrap:anywhere}
.team .m .tm-line .pill,.team .m .tm-line .infotip,.team .m .tm-line .person{text-indent:0}
/* `.person` joined that list on 2026-09-21: `text-indent` INHERITS, so a face that isn't
   also an `.infotip` (the external facilitator's initials circle, which has no hover card
   to open) passed the -30px down into its own two letters and `overflow:hidden` ate them.
   Every {% person %} chip already matched via `.infotip`; this makes the hand-built one
   behave — and start at the same x — instead of looking like a broken avatar. */
/* Every other line under a team row's name — override time, decline reason/withdrawn date,
   juggle pill, cancellation pay, check-in badge — hangs under the NAME too, not the face
   (Greg, 2026-09-15: "any time an actor has text underneath ... it needs to be hanging
   indented under their name ... across the board"). Same 30px as the name line's own indent. */
.team .m .sc-flex1>div:not(.tm-line){padding-left:var(--space-30)}
/* Those 🚗 Travel / ⚠ Conflict pills are infotip triggers, so they picked up the "i" button's
   orange on hover / while open — and `.pill.infotip-btn{all:unset}` wiped their own red at
   rest. They keep the red pill look in every state (Greg, 2026-09-13: "don't need an orange
   highlight hover state"); the bubble opening is the feedback. */
.pill.bad.infotip-btn,.pill.bad.infotip-btn:hover,.pill.bad.infotip-btn:focus-visible,
.infotip.is-open .pill.bad.infotip-btn{color:var(--bad);background:var(--bad-50);border:0}
/* Same for every status class: an engagement status pill can be an infotip trigger too
   (the booking team row), and keeps its status colour in every state. */
.pill.ok.infotip-btn,.pill.ok.infotip-btn:hover,.pill.ok.infotip-btn:focus-visible,
.infotip.is-open .pill.ok.infotip-btn{color:var(--good);background:var(--good-50);border:0}
.pill.warn.infotip-btn,.pill.warn.infotip-btn:hover,.pill.warn.infotip-btn:focus-visible,
.infotip.is-open .pill.warn.infotip-btn{color:var(--amber-600);background:var(--amber-50);border:0}
.pill.info.infotip-btn,.pill.info.infotip-btn:hover,.pill.info.infotip-btn:focus-visible,
.infotip.is-open .pill.info.infotip-btn{color:var(--purple);background:var(--purple-50);border:0}
.pill.travel.infotip-btn,.pill.travel.infotip-btn:hover,.pill.travel.infotip-btn:focus-visible,
.infotip.is-open .pill.travel.infotip-btn{color:var(--blue-600);background:var(--blue-50);border:0}
.pill.muted.infotip-btn,.pill.muted.infotip-btn:hover,.pill.muted.infotip-btn:focus-visible,
.infotip.is-open .pill.muted.infotip-btn{color:var(--muted);background:var(--pill-muted-bg);border:0}
.pill.lead.infotip-btn,.pill.lead.infotip-btn:hover,.pill.lead.infotip-btn:focus-visible,
.infotip.is-open .pill.lead.infotip-btn{color:var(--orange-600);background:var(--orange-50);border:0}
.pill.infotip-btn:focus-visible{outline:2px solid var(--line);outline-offset:1px}
/* Session header while its editor is open: Briefing due · 🗑 · 💾 — no chevron, because
   folding the row doesn't save anything (Greg, 2026-09-13). */
.svc-acc[data-editing]>summary.sess-sum .chev{display:none}
/* The inline editor's autosave status line is hidden where the row header carries the save
   icon (Greg, 2026-09-13: "remove the autosave text from the bottom"). The element STAYS —
   saverail.js reads its data-url / data-form to autosave — it just isn't shown. */
.save-state.save-state-quiet:not([data-state="error"]){display:none}
/* The run sheet's three per-section status lines (Overview header, Room & Materials,
   Run order) say nothing while all is well — the top-right pill already reads Saved /
   Saving… / Unsaved for the whole page (Greg, 2026-09-15: "do all of the sections need
   'All changes saved' at the bottom now that it's saving in the top right pill?"). They
   reappear only for a problem (refused save, conflict, dropped connection), because
   that text says WHAT and WHERE, next to the rows it is about. The element STAYS in
   the DOM: saverail.js / the page script still write to it. */
.save-state-calm:not([data-state="error"]){display:none}
/* …and on the booking page that chip was the ONLY thing left in the editor's bottom bar (Save
   only shows for a brand-new session; Close + the trash live in the row header), so a bar with
   no button or link in it isn't drawn at all — no empty padded strip under the editor. */
#svc-edit-form .form-actions:not(:has(button, a)){display:none}
/* The inline session editor sits in the same card as the read-only session it replaces, so it
   keeps that card's side margins (Greg, 2026-09-13: "session edits form is not following the
   style guide's appropriate margins on the left and right"). The card body is 6px (the
   _detail_session_body.html wrapper) + 16px (.svc .svc-body) = 22px each side; the editor slot
   had only the 6px. :not(:empty) because that slot must stay literally empty when nothing is
   being edited (see _detail_session_body.html) — no padding, no gap. A brand-new session's
   editor (#sess-new) has no wrapper, so it carries the full 22px itself. */
.svc-acc .sess-edit-slot:not(:empty){padding:var(--space-6) var(--space-16) var(--space-14)}
.sess-new-body{padding:var(--space-12) var(--space-22) var(--space-20)}
/* Travel — actual bookings: ONE "+ Add" that drops a small menu of kinds (Greg, 2026-09-13). */
.tv-add{position:relative;flex:none}
.tv-add>summary{list-style:none}
.tv-add>summary::-webkit-details-marker{display:none}
.tv-add-menu{position:absolute;right:0;top:calc(100% + var(--space-4));z-index:30;min-width:140px;
  display:flex;flex-direction:column;gap:var(--space-2);padding:var(--space-4);
  background:var(--card);border:1px solid var(--line);border-radius:var(--radius-sm);box-shadow:var(--shadow)}
.tv-add-menu .btn{justify-content:flex-start;width:100%}
/* Edit page: its save controls sit in the top bar (Greg, 2026-09-13). Undo the sticky floating
   bar look they had under it, and keep the floating save pill where it was before that bar. */
.topbar > .bkedit-actions{position:static;flex:0 0 auto;min-width:max-content;display:flex;align-items:center;gap:var(--space-8);
  width:auto;margin:0;padding:0;background:none;border:0;box-shadow:none;border-radius:0}
.topbar > .bkedit-actions .save-state{max-width:200px;margin:0;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;font-size:var(--text-12)}
.topbar > .bkedit-actions .bkedit-leave{margin-left:0}
.topbar > .bkedit-actions .save-state{display:none}
@media (max-width:900px){ .topbar .bk-hide-narrow{display:none} }
@media (max-width:600px){ .topbar .bk-hide-phone,.topbar .bk-long{display:none} }
/* Edit page top bar: ONE save control (Greg, 2026-09-13: "the save button, the tickbox, and the
   little floating save pill ... can that pill go up into the top menu bar to replace the other
   two?"). The save pill sits in the bar instead of floating. It shows the state and is how you
   leave: pointed at, it reads "Save & close"; a click saves anything pending, then goes back to
   the booking (savepill.js data-savepill-close). Both labels share one grid cell, so the pill
   keeps its width on hover. The hidden status chip stays in the page — saverail.js reads it. */
.topbar .tel-savepill{position:static;flex:none;box-shadow:none;height:40px;
  padding:0 var(--space-12) 0 var(--space-10);font-size:var(--text-13)}
@media (max-width:600px){ .topbar .tel-savepill{height:36px} }
.tel-savepill .tel-savepill-lbl{display:inline-grid}
.tel-savepill .tel-savepill-lbl > span{grid-area:1/1;white-space:nowrap}
.tel-savepill .tel-savepill-go{visibility:hidden}
.tel-savepill:is([data-savepill-close],[data-savepill-submit]):not([data-state="attention"]):is(:hover,:focus-visible) .tel-savepill-txt{visibility:hidden}
.tel-savepill:is([data-savepill-close],[data-savepill-submit]):not([data-state="attention"]):is(:hover,:focus-visible) .tel-savepill-go{visibility:visible}
@media (max-width:600px){ .tel-savepill .tel-savepill-lbl{display:none}
  .tel-savepill[data-state="attention"] .tel-savepill-lbl{display:inline-grid}
  .tel-savepill.just-saved .tel-savepill-lbl{display:inline-grid} }
/* Phone: the title keeps no minimum width when the section shortcuts are hidden anyway — the
   170px floor made the top bar's contents wider than the bar at 390px. */
@media (max-width:600px){ .topbar:has(> .bk-jump) > div:not(.bkedit-actions){min-width:0} }
/* Booking page Production card: Topics / themes, folded, under the room details (Greg,
   2026-09-13 — it left the edit page with its Production section). */
.bk-topics > summary{cursor:pointer;list-style:none;font-size:var(--text-12);font-weight:600;color:var(--slate-2)}
.bk-topics > summary::-webkit-details-marker{display:none}
.bk-topics > summary::before{content:"+ "}
.bk-topics[open] > summary::before{content:"− "}
.bk-topics > div{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:var(--space-2) var(--space-12);
  margin-top:var(--space-8)}
.bk-topics label{display:flex;align-items:center;gap:var(--space-8);padding:var(--space-4) 0;font-size:var(--text-13);
  font-weight:400;cursor:pointer}
.bk-topics input[type=checkbox]{width:16px;height:16px;margin:0;accent-color:var(--orange);flex-shrink:0}

/* ---------- EAP (Foremind) — Greg, 2026-09-14 ----------
   .support-stack keeps an embedded Typeform (and anything stacked above it) one screen tall so
   the form fills what's left; two classes out-rank .full-bleed-frame's own height, phone rule
   included. Used by the Support/Report pages and the Actor/RolePlayer Reflection page.
   The green `.eap-strip` note that sat above the Support Form is gone (Greg, 2026-09-23) — that
   form's own cover screen already carries the EAP, and the EAP page is still in the menu.
   The rest is the /support/eap/ page — including .eap-actions, which that page still uses. */
.eap-actions{display:flex;flex-wrap:wrap;gap:var(--space-8)}
.eap-actions .btn{white-space:nowrap}
.support-stack{display:flex;flex-direction:column;width:100%;height:100vh;height:100dvh}
.support-stack .full-bleed-frame{flex:1 1 auto;height:auto;min-height:0}
@media(max-width:600px){.support-stack{height:calc(100dvh - 38px - 2 * var(--space-10) - var(--safe-top))}}
/* --safe-top above: the installed home-screen app (standalone, viewport-fit=cover) has a
   status bar/notch the browser doesn't reserve space for on its own — see the full-bleed
   topbar's own --safe-top padding below, which is what actually needs the room this frees
   up (30 Sept 2026: the menu button sat under the notch and nobody could reach it). Zero in
   a browser tab and on desktop, so this changes nothing anywhere else. */
.eap-page{max-width:880px}
.eap-hero{margin-bottom:var(--space-14)}
.eap-hero img{height:34px;width:auto;max-width:100%}
.eap-lede{font-size:var(--text-16);line-height:1.5;margin:0 0 var(--space-18);color:var(--ink)}
.eap-facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:var(--space-12);margin-bottom:var(--space-16)}
.eap-facts h3,.eap-section h3{margin:0 0 var(--space-6);font-size:var(--text-14)}
.eap-facts p{margin:0;color:var(--muted);font-size:var(--text-13);line-height:1.5}
.eap-section{margin-bottom:var(--space-16)}
.eap-steps{margin:var(--space-8) 0 var(--space-14);padding-left:var(--space-20)}
.eap-steps li{margin-bottom:var(--space-6)}
.eap-list{columns:2 220px;margin:var(--space-8) 0 var(--space-12);padding-left:var(--space-18)}
.eap-list li{margin-bottom:var(--space-3)}
.eap-note{margin:0;font-size:var(--text-13)}

/* ---------- Policies & Procedures (/support/policies/) — Greg, 2026-09-14 ---------- */
.doc-lede{font-size:var(--text-16);line-height:1.5;margin:0 0 var(--space-18);max-width:880px;color:var(--ink)}
.doc-list{display:flex;flex-direction:column;gap:var(--space-12);max-width:880px}
.doc-item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-12) var(--space-16)}
.doc-item .doc-body{flex:1 1 320px;min-width:0}
.doc-item h3{margin:0 0 var(--space-4);font-size:var(--text-16)}
.doc-item p{margin:0;color:var(--muted);font-size:var(--text-13);line-height:1.5}
.doc-meta{margin-top:var(--space-6);font-size:var(--text-12);color:var(--muted)}
.doc-foot{max-width:880px;margin:var(--space-16) 0 0;font-size:var(--text-13)}

/* ---------- Full-bleed pages (Support / Report It Typeforms — Greg, 2026-09-14) ----------
   A page opts in with {% block main_class %}full-bleed{% endblock %} (a class on .main, which
   sits inside the boost-swapped shell, so it can't outlive the page). No top bar, no content
   padding, no max-width: its .full-bleed-frame fills everything to the right of the menu.
   On a phone the menu only opens from the top bar's hamburger, so there the bar shrinks to
   just that button floating in the top-right corner — its unread pulse and count intact.

   RIGHT, not left (Greg, 2026-09-23: "the menu button on mobile view in the support form
   for actors is showing up on the left"). The hamburger moved to the right corner
   everywhere on 2026-09-22, but that was done with `order:9` against its siblings in the
   top bar — and on a full-bleed page every sibling is hidden, so a lone flex item has
   nothing to be ordered past and the bar itself was pinned `left:0`. Anchoring the bar to
   the right is what actually moves it here. */
.main.full-bleed .topbar{display:none}
.main.full-bleed .content{padding:0;max-width:none;flex:1 1 auto;display:flex;flex-direction:column}
.full-bleed-frame{flex:1 1 auto;width:100%;height:100vh;height:100dvh}
@media(max-width:600px){
  .main.full-bleed .topbar{display:flex;justify-content:flex-end;position:fixed;top:0;
    left:auto;right:0;z-index:20;height:auto;
    /* --safe-top on top only (30 Sept 2026): in the installed home-screen app this button
       sat right under the status bar/notch — reachable in a browser tab (--safe-top is 0
       there) but not in standalone, where nothing else pushes it clear. Same token every
       other topbar on the site already uses (tel.css var(--header-h), base.html's mobile
       .topbar padding) — this is the one topbar that had been left out because it's
       `position:fixed` instead of sitting in the document's normal flow. */
    padding:calc(var(--space-10) + var(--safe-top)) var(--space-10) var(--space-10);
    background:none;border:none;backdrop-filter:none}
  .main.full-bleed .topbar>*:not(.hamb){display:none !important}   /* !important: base.html shows #tel-back with an inline style */
  /* Greg, 2026-09-23: "can the support, report and reflections forms, once opened, take up
     the whole page (including the header bar) but still have the menu hamburger floating
     over the top right?" So the form starts at y=0 and runs the full height — the button
     is FIXED and sits over it rather than the page reserving a band for it, which is what
     the old padding-top and the shortened frame were doing.

     The one thing the button then needs is to survive whatever it lands on: a Typeform
     scrolls its own colours under it, so a boxless set of three dark lines can vanish.
     A white halo keeps it legible without giving it back the box Greg asked to remove. */
  .main.full-bleed .content{padding-top:0}
  .full-bleed-frame{height:100vh;height:100dvh}
  .main.full-bleed .hamb{filter:drop-shadow(0 0 3px rgba(255,255,255,.95))
    drop-shadow(0 1px 6px rgba(255,255,255,.75))}
}

/* Run sheet — lines of an uploaded Word document that didn't carry across (v2.89). Red and
   at the very top on purpose: Greg wants a list he can't miss and has to tick off. */
.import-leftovers{margin-bottom:var(--space-14);padding:var(--space-12) var(--space-14);border-radius:var(--radius);background:var(--bad-50);border:2px solid var(--bad)}
.import-leftovers.is-clear{background:var(--card);border:1px solid var(--line)}
.import-leftovers-head{font-size:var(--text-16);font-weight:700;color:var(--bad)}
.import-leftovers.is-clear .import-leftovers-head{font-size:var(--text-13);font-weight:600;color:var(--good)}
.import-leftovers-sub{margin-top:var(--space-4);font-size:var(--text-13);color:var(--ink)}
/* A grid, not a column: a messy document can leave 25+ lines, and one long column pushed the
   whole run sheet off the screen. Three across at desktop width (Greg's density rule), one on
   a phone. */
.import-leftovers-list{margin:var(--space-10) 0 0;padding:0;list-style:none;display:grid;grid-template-columns:repeat(auto-fill,minmax(min(100%,340px),1fr));gap:var(--space-6)}
.import-leftover-jump{white-space:nowrap}
.import-leftover{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-10);padding:var(--space-8) var(--space-10);background:var(--card);border:1px solid var(--line);border-radius:var(--radius-sm)}
.import-leftover-body{flex:1;min-width:0}
.import-leftover-where{font-size:var(--text-12);color:var(--muted)}
.import-leftover-text{margin-top:var(--space-2);font-size:var(--text-14);color:var(--ink);overflow-wrap:anywhere}
.import-leftovers-actions{margin-top:var(--space-10);display:flex;justify-content:flex-end}
.import-leftovers-done{margin-top:var(--space-10);font-size:var(--text-12);color:var(--muted)}
.import-leftovers-done summary{cursor:pointer}
.import-leftovers-done ul{margin:var(--space-6) 0 0;padding-left:var(--space-16)}
.import-leftovers-done li{margin:var(--space-2) 0}
.import-leftovers-badge{display:inline-flex;margin-top:var(--space-6);font-size:var(--text-12);text-decoration:none;
  white-space:normal;max-width:100%}  /* a sentence, not a tag — wraps inside its card (half-screen laptop, 2026-09-15) */
.run-row.is-flash{box-shadow:0 0 0 3px var(--orange)}

/* ── W1 2026-09-14 contacts ────────────────────────────────────────────────────────────
   The bookings-list card's Contacts block (_ml_card.html) and the contact typeahead's
   keyboard highlight (_contact_roles_forms.html). Every value here is a token; these are
   semantic classes, not more atoms for the sc-* ladder.

   Why its own block rather than reusing .team: a contact line is a NAME plus a variable
   number of pills plus one or two grey meta strings, all of which have to wrap on a phone
   without the name losing the pills that qualify it. .team .m is a single non-wrapping
   row built around a headshot, which is a different shape. */
.ml-contacts{display:flex;flex-direction:column;gap:var(--space-3);min-width:0}
.ml-contact{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-2) var(--space-6);
            font-size:var(--text-12);min-width:0;line-height:1.45}
.ml-cname{font-weight:600;color:var(--ink)}
/* Email and mobile are the least-scanned part of the line — grey, small, and allowed to
   break mid-string so a long address can never widen the column past the card. */
.ml-cmeta{color:var(--muted);font-size:var(--text-11);word-break:break-word}
.ml-crole{font-size:var(--text-10);color:var(--slate-2);background:var(--tab-idle)}
.ml-crole.primary{color:var(--orange-600);background:var(--orange-50)}
.ml-crole.onsite{color:var(--good);background:var(--good-50)}
/* Not one of ours — see the note in _ml_card.html for why this is a pill and not a role
   suffix like the cast rows carry. */
.ml-extfac{font-size:var(--text-10);color:var(--slate-2);background:var(--tab-idle);
           margin-left:var(--space-6)}
.ml-extfac-row{align-items:flex-start}
/* The ArrowDown/ArrowUp highlight in the contact typeahead. Keyed off aria-selected, the
   same attribute the screen reader reads, so the visible state and the announced state
   cannot drift apart. */
#role-contact-suggestions [role=option][aria-selected=true]{background:var(--orange-50);
  box-shadow:inset 3px 0 0 var(--orange)}
/* W2 2026-09-14 travel */
/* A flight number is upper-case on the ticket, in the airline's systems and in every lookup
   this app makes — the input shows it that way while it is being typed. The SAVE is what
   normalises the stored value (views._travel_fields_from_post); this is only the display
   half, so a paste of "qf123" never looks different from what will be stored. */
.tel-upper{text-transform:uppercase}
/* The "who's on this" pill on a travel row. `.pill` is white-space:nowrap, which ran a long
   name list straight off the edge of a narrow session card (Greg, job 26-030, 2026-09-14).
   This keeps the pill's shape and lets the names wrap instead. */
.tv-who{white-space:normal;align-items:flex-start;font-size:var(--text-11);
  padding:var(--space-2) var(--space-7);max-width:100%;overflow-wrap:anywhere}
/* A travel item that is only REQUESTED, shown to the crew. Deliberately quiet — it is a
   statement of fact beside the item, not an alarm; the office's own row uses `.pill warn`. */
.tv-requested{font-weight:600;color:var(--amber-600)}
/* ══════════════════════ W3 2026-09-14 booking page ══════════════════════
   Meeting of 2026-09-14. Everything below is new component CSS for that day's
   booking-page work; nothing above this line was changed. */

/* "Who prints" on the run sheet's Room & Materials tab [1:01:53]. Greg: "we will just
   always ask the client to print", so the three-way choice folds away behind the answer
   it almost always is. The <select> stays in the DOM — this only hides it — because the
   tab's autosave posts the whole room form and a missing control would blank the field. */

/* A cast member who is no longer coming — withdrawn / declined / unavailable / not
   comfortable / cancelled (booking.INACTIVE_ENGAGEMENT_STATUSES, decided in the view as
   `engagement.is_out`). Greg, 2026-09-14 [47:49][48:32]: keep the row, make it read as past
   tense. OPACITY, not greyscale and not a filter — a filter would flatten the headshot and
   drain the status pill's colour, and that colour is what says WHICH kind of no this was.
   The value is a token so the one number governs every surface that adopts this class. */
:root{--eng-out-opacity:.55}
.eng-out{opacity:var(--eng-out-opacity)}
/* Why they are out (and when), under the name: a hanging indent so it reads as a footnote
   to the person above it rather than another fact in the column [45:04][47:05]. */
.eng-reason{font-size:var(--text-12);margin-top:var(--space-2);padding-left:var(--space-10);text-indent:calc(-1 * var(--space-10))}

/* The crew job card's date chip [1:20:31]. It gained the weekday above the day number
   (_session_card.html), but the chip's type hierarchy only ever existed as `.row .date-chip`
   — and this chip lives in `.svc .svc-top`, not in a `.row`, so every line of it rendered at
   body size and the new weekday came out as loud as the date. These three lines give it the
   same hierarchy the chip has everywhere else: the day number big in the display face,
   weekday / month / year small, uppercase and muted around it. Scoped to `.svc` so it
   reaches this card and nothing else; the `.row` rules above still win inside a row, with
   identical values. No background — the chip sits on `.svc-top`'s own tint and does not need
   a second one. */
.svc .svc-top .date-chip{text-align:center;white-space:nowrap;align-self:flex-start}
.svc .svc-top .date-chip .d{font-family:var(--display);font-size:var(--text-20);line-height:1.1;color:var(--ink)}
.svc .svc-top .date-chip .w,.svc .svc-top .date-chip .m{font-size:var(--text-10);line-height:1.5;text-transform:uppercase;color:var(--muted);letter-spacing:.06em}
/* W4 2026-09-14 confirmation */
/* The "Attached: Handouts (N pages, 1 PDF)" note on the confirmation send page. The pack
   is attached automatically (Greg/Jacob 2026-09-14: the client always prints the
   handouts), so this STATES a fact rather than offering a choice — a quiet green tint,
   not the amber/orange warning stripes used elsewhere on this page for "read this before
   you send". */
.w4-attached{
  margin-top:var(--space-10);
  padding:var(--space-8) var(--space-12);
  border-left:3px solid var(--good);
  background:var(--good-50);
  border-radius:var(--radius-sm);
  font-size:var(--text-13);
}
.w4-attached-list{
  margin-top:var(--space-3);
  color:var(--muted);
  font-size:var(--text-12);
}
.w4-attached-missing{
  margin-top:var(--space-3);
  color:var(--bad);
  font-size:var(--text-12);
}

/* ══════════════ 2026-09-21 meeting — the session card's team list ══════════════
   Last fifteen minutes of the 21 Sept meeting. Greg asked "it's super busy … is there
   anything we can do to make it cleaner?" three times about this one list, so everything
   in this block is about the SAME column: nexus2/templates/nexus2/_booking_svc_card.html
   (.svc-col-team) and the row partial _booking_team_row.html. */

/* THREE COLUMNS [1:45:43] — who | status | money — "so all the wages and numbers are
   aligned". Aligned DOWN THE LIST, not just inside one row, which is why `.team` itself is
   the grid and each row is `subgrid`: three separate per-row grids would each size their own
   money column and the numbers would stagger again. The first declaration is the fallback an
   engine without subgrid keeps (rows align within themselves, columns still read left to
   right); `max-content` on the two right-hand columns means the widest status pill and the
   widest figure set the width, and nothing else can.
   Scoped to `.team-cols`, NOT `.team`: the same `.team .m` rows draw the actor call sheet
   (_session_card.html) and the bookings list (_ml_card.html), which have two cells, not
   three, and are not part of what was decided today. */
.team-cols{display:grid;grid-template-columns:minmax(0,1fr) max-content max-content;
  align-items:start;column-gap:var(--space-10);row-gap:var(--space-3)}
.team-cols>*{grid-column:1/-1;min-width:0}
.team-cols .m{grid-template-columns:minmax(0,1fr) max-content max-content;
  grid-template-columns:subgrid;display:grid;align-items:start;
  column-gap:var(--space-10);row-gap:var(--space-2);font-size:var(--text-13)}
/* Columns 1 and 2 read left-aligned; only the money is right-aligned, because a right edge
   is what makes a column of figures comparable at a glance. */
.team-cols .tm-who{grid-column:1;min-width:0}
.team-cols .tm-status{grid-column:2;justify-self:start;min-width:0}
.team-cols .tm-status .pill{white-space:normal}
.team-cols .tm-fee{grid-column:3;justify-self:end;text-align:right;white-space:nowrap;
  font-size:var(--text-13);font-weight:600}
/* Same hanging indent the name line has always had, now keyed off `.tm-who` (the row's own
   first cell) instead of the generic `.sc-flex1` it used to borrow. */
.team-cols .m .tm-who>div:not(.tm-line){padding-left:var(--space-30)}
/* The cancellation-pay to-do, under the status it belongs to. 2026-09-21 round 3: moved to
   a DIRECT CHILD of `.m`, spanning the full row (was nested inside `.tm-status`, whose
   `max-content` column grew to fit this line's full width and squeezed the `1fr` name
   column beside it to ~3px — real render, "Michelle Ri" then a squeeze). Same hanging
   indent as `.tm-line` so it still reads as a footnote under the person. */
.team-cols .m > .tm-cancelpay{grid-column:1/-1;padding-left:var(--space-30)}
.tm-cancelpay{font-size:var(--text-11);margin-top:var(--space-2);line-height:1.4}

/* The figure as an infotip trigger [1:45:00]. Same escape hatch `.pill.infotip-btn` uses:
   undo the "i in a circle" sizing rather than shrink the number to fit it. A dotted
   underline is the only mark it carries — it has to read as a number first. */
.tm-amt.infotip-btn{all:unset;display:inline;cursor:help;
  text-decoration:underline dotted var(--line-2);text-underline-offset:3px}
.infotip.is-open .tm-amt.infotip-btn{text-decoration-color:var(--orange)}

/* TWO COLUMNS, then ONE, as the column narrows — the same staging `.svc-3col` uses on the
   card around it, measured on the team column itself (`container-name:teamcol`) rather than
   on the viewport, because how wide this list is depends on which of the card's own three
   layouts is in play. At two columns the money drops under the status pill and keeps its
   right edge; at one, the name takes the whole width and the status and the money share the
   line underneath it — still hanging under the name, still with the figures against the
   right edge, because those are the two things the three-column shape was for. */
.svc-col-team{container-type:inline-size;container-name:teamcol}
@container teamcol (max-width:400px){
  .team-cols{grid-template-columns:minmax(0,1fr) max-content}
  .team-cols .m{grid-template-columns:minmax(0,1fr) max-content;
    grid-template-columns:subgrid}
  .team-cols .tm-who{grid-row:1/span 2}
  .team-cols .tm-status{grid-column:2;grid-row:1}
  .team-cols .tm-fee{grid-column:2;grid-row:2;justify-self:end}
}
@container teamcol (max-width:330px){
  .team-cols .tm-who{grid-column:1/-1;grid-row:1}
  .team-cols .tm-status{grid-column:1;grid-row:2;padding-left:var(--space-30)}
  .team-cols .tm-fee{grid-column:2;grid-row:2;justify-self:end}
}

/* "Not on this engagement" [1:26:05][1:27:14][1:23:37] — the whole out group behind ONE
   muted line. A native <details>, so it survives with JS off and prints open like every
   other one on this page. Quiet on purpose: it is the answer to a question nobody has asked
   yet, and it must never out-shout the live cast above it. */
.team-out{margin-top:var(--space-6)}
.team-out-sum{cursor:pointer;list-style:none;display:flex;align-items:center;
  gap:var(--space-6);font-size:var(--text-12);color:var(--muted);user-select:none;
  padding:var(--space-3) 0}
.team-out-sum::-webkit-details-marker{display:none}
.team-out-sum:hover{color:var(--ink)}
.team-out-list{margin-top:var(--space-6)}

/* The chevron that opens a session [07:44]. It was a `▶` glyph, and Greg read it as text:
   "it looked like the first bit of a word". Drawn instead of typed — two borders on a
   square, rotated — so it is unmistakably an arrow at any size and takes the row's own
   colour. The glyph stays in the markup (font-size:0 hides it) because the same `.chev`
   span is shared with half a dozen other accordions that are not part of this change.
   Rotation is the existing `[open] .chev{transform:rotate(90deg)}` rule, unchanged.
   Annotation #585 (Jacob, 6 Oct 2026) — "these > and < have a break in them": a uniform
   border-radius rounds EVERY corner of the little square, including the one where the
   right and bottom borders actually meet — that's the corner that becomes the arrow's
   point after rotation, so rounding it pulls the two border lines apart into a visible
   notch. Zero on that one corner only (bottom-right in the shorthand's TL/TR/BR/BL
   order) keeps the point a single sharp line; the other three keep their 1px softening. */
.svc-acc>summary.sess-sum .chev,.team-out-sum .chev{display:inline-flex;align-items:center;
  justify-content:center;font-size:0;flex:0 0 auto;vertical-align:middle}
.svc-acc>summary.sess-sum .chev::before,.team-out-sum .chev::before{content:"";
  width:8px;height:8px;margin-left:calc(-1 * var(--space-2));border-radius:1px 1px 0 1px;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:rotate(-45deg)}
/* Annotation #589 (Jacob, 6 Oct 2026) — the label used to start right after the chev, ~18px
   left of where the names above start reading. `.tm-line`'s hanging indent puts that text at
   24px (the headshot circle) + the row's own gap, so giving the chev the SAME 24px width
   lines the two up exactly, without duplicating the indent math here. */
.team-out-sum .chev{width:24px;transition:transform .15s}
.team-out[open]>.team-out-sum .chev{transform:rotate(90deg)}

/* ---------- Notes — ONE running list, everywhere (Greg, 21 Sept 2026 [01:26]–[03:12]) ----
   "my headshot circle, and then the note that I wrote — the bar underneath it of the edit
   and the delete and the tick for done and the date should disappear into a hover state, so
   it's just a running list of headshots to the left and note to the right."
   Markup: _note_row.html (every note kind) + _note_add.html (every add box).
   Modelled on .castface/.castface-actions above — same opacity reveal, same focus-within
   escape hatch for the keyboard — with one difference that matters: the strip is ABSOLUTE.
   A reserved-but-transparent bar would leave ~16px of dead air under every note and that is
   exactly the vertical space this change is meant to give back. On a touch screen there is
   no hover at all, so the @media (hover: none) block puts it back in the flow. */
.note-row{display:flex;gap:var(--space-8);align-items:flex-start;padding:var(--space-6) 0;position:relative}
.note-row+.note-row{border-top:1px solid var(--line-2)}
.note-row.is-dim{opacity:.55}
.note-row-face{flex:none;padding-top:var(--space-1)}
/* {% person %} draws face AND name; here the name belongs in the hover strip, so only the
   circle is kept. The face still carries the full person card on hover/tap. */
.note-row-face .person-nm{display:none}
.note-row-body{flex:1;min-width:0}
.note-row-text{white-space:pre-wrap;font-size:var(--text-13);line-height:1.45;overflow-wrap:anywhere}
.note-row-text.is-done{color:var(--muted);text-decoration:line-through}
.note-row-acts{position:absolute;top:var(--space-2);right:0;z-index:2;
  display:flex;gap:var(--space-8);align-items:center;max-width:100%;
  font-size:var(--text-11);color:var(--muted);
  background:var(--card);border:1px solid var(--line);border-radius:var(--radius-sm);
  padding:var(--space-2) var(--space-8);box-shadow:var(--shadow);
  opacity:0;transition:opacity .12s;pointer-events:none}
.note-row:hover .note-row-acts,.note-row:focus-within .note-row-acts{opacity:1;pointer-events:auto}
.note-row-acts form{display:inline;margin:0}
.note-row-meta{color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.note-act{background:none;border:0;padding:0;margin:0;font:inherit;font-size:var(--text-11);
  color:var(--orange-600);cursor:pointer;text-decoration:none;white-space:nowrap}
.note-act:hover{text-decoration:underline}
.note-row-acts .pill.note-icon{border:0;cursor:pointer}
/* No `display` here on purpose: the box is toggled by the `hidden` ATTRIBUTE
   (ajaxform.js `data-note-open`), and a class that set display would beat [hidden]. */
.note-edit{margin-top:var(--space-6)}
/* Annotation #793 (Greg): Edit opens the editor IN PLACE of the note's text, not below it. */
.note-row-body:has(>.note-edit:not([hidden])) > .note-row-text{display:none}
.note-row-body:has(>.note-edit:not([hidden])) > .note-edit{margin-top:0}
.note-edit-acts{display:flex;gap:var(--space-8);margin-top:var(--space-6)}
.note-reply-form{margin-top:var(--space-8);padding-left:var(--space-16)}
.note-below{margin-top:var(--space-8)}
.note-add{margin-top:var(--space-8)}
.note-add-row{display:flex;gap:var(--space-8);flex-wrap:wrap;margin-bottom:var(--space-6)}
.note-add-box{width:100%;border:1px solid var(--line);border-radius:10px;
  padding:var(--space-8);font-family:inherit;font-size:var(--text-13);resize:vertical}
/* Annotations #791/#792 (Greg): on the master list the add-a-note boxes are ONE line and grow as
   you type. field-sizing makes an empty box one line tall; older browsers keep two lines. */
.svc-acc .note-add-box,#job-noteform .note-add-box{field-sizing:content;min-height:0;resize:none}
.note-add-box:focus{outline:none;border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-50)}
/* A form with no submit control is not operable by everyone, and Enter-to-post is JS. The
   button stays in the DOM and out of sight — never `display:none`, which would take it out
   of the accessibility tree too. */
.note-sr{position:absolute;width:1px;height:1px;margin:calc(-1 * var(--space-1));padding:0;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}
@media (hover: none){
  .note-row-acts{position:static;opacity:1;pointer-events:auto;background:none;border:0;
    box-shadow:none;padding:0;margin-top:var(--space-3);flex-wrap:wrap}
  .note-row-meta{white-space:normal}
}
/* The face column takes a {% person %} chip when the author has an Actor and a plain
   initials circle when they don't (office logins usually don't). Same 24px either way. */
.note-row-face .headshot.hs-xs{width:24px;height:24px;font-size:var(--text-10)}
/* HR feed — replies sit under their entry, inside its own column */
.hr-replies{margin-top:var(--space-8);padding-left:var(--space-16);border-left:2px solid var(--line)}
.note-row.hr-reply{padding:var(--space-4) 0;border-top:0}
.note-row.hr-reply .note-row-text{font-size:var(--text-12)}

/* ============================================================
   .avail-* — THE ACTOR'S AVAILABILITY-ANSWER PAGE
   (availability_respond.html, 21 Sept 2026 meeting round 3)

   Greg: *"If it's multiple, they need one link to come to the app and respond to all of
   the notifications. And the notifications should have all of the respond options if
   they're not available and why."*

   A standalone page (no nav, no header) opened mostly on a phone from an emailed link, so
   it sets its own body padding and centres a single column. Everything else reaches for
   the components and tokens already here: .card .pad-18 .btn .fld .fld-check .muted.

   THE REVEAL IS PURE CSS. The four radios are direct children of .avail-row, ahead of both
   the button strip and the reason panel, so `~` can reach them. That is why there is no
   `:has()` here (and no JavaScript at all): an actor answering from a train on an old
   phone must not need a script to run before the page can be used.
   ============================================================ */
.avail-body{background:var(--bg);padding:var(--space-20) var(--space-16);
  padding-bottom:calc(var(--space-22) * 2);min-height:100vh}
/* avr- prefix: the older casting-grid .avail-wrap/.avail-form rules (removed 19 Oct 2026) hijacked this page (22 Sept 2026) */
.avr-wrap{max-width:640px;margin:0 auto}
.avail-logo{display:block;height:32px;margin:var(--space-2) auto var(--space-20)}
.avail-h1{font-size:var(--text-20);margin:0 0 var(--space-6)}
.avail-lead{font-size:var(--text-14);line-height:1.5;margin:0 0 var(--space-16)}
.avail-foot{margin-top:var(--space-14);margin-bottom:0}
.avail-sub{color:var(--muted);font-size:var(--text-12)}
.avail-list{margin-bottom:var(--space-14)}
.avail-empty-h{font-size:var(--text-16);margin:0 0 var(--space-10)}
.avail-row{padding:var(--space-12) 0;border-top:1px solid var(--line)}
.avail-row:first-of-type{border-top:0;padding-top:0}
/* The one-tap "no" landing notice — "Got it, you're marked Not free" — sits above the
   list, so it needs its own bottom margin rather than relying on .avail-said's own. */
.avail-notice{margin-bottom:var(--space-14)}
.avail-when{margin-bottom:var(--space-10);font-size:var(--text-14)}
.avail-when b{display:block}

/* The radios themselves are never seen — the labels are the buttons. Kept in the DOM and
   focusable (never display:none, which drops them out of the accessibility tree and the
   tab order with it), parked behind the strip they drive. */
.avail-radio{position:absolute;opacity:0;width:1px;height:1px;margin:0;pointer-events:none}
.avail-opts{display:flex;flex-wrap:wrap;gap:var(--space-8)}
.avail-opt{flex:1 1 46%;min-width:132px;display:flex;align-items:center;justify-content:center;
  padding:var(--space-12) var(--space-10);border:1px solid var(--line);border-radius:var(--radius-sm);
  background:var(--card);color:var(--ink);font-weight:600;font-size:var(--text-13);
  cursor:pointer;text-align:center;user-select:none}
.avail-opt:hover{border-color:var(--grey);box-shadow:var(--shadow)}
/* Selected state. A checked radio is a general sibling of .avail-opts, so each answer can
   paint only its own button. */
.avail-radio.r-free:checked~.avail-opts .avail-opt.o-free{background:var(--good);border-color:var(--good);color:var(--white)}
.avail-radio.r-not:checked~.avail-opts .avail-opt.o-not{background:var(--bad);border-color:var(--bad);color:var(--white)}
.avail-radio.r-am:checked~.avail-opts .avail-opt.o-am,
.avail-radio.r-pm:checked~.avail-opts .avail-opt.o-pm{background:var(--amber-50);border-color:var(--amber-600);color:var(--amber-600)}
.avail-radio.r-maybe:checked~.avail-opts .avail-opt.o-maybe{background:var(--amber-50);border-color:var(--amber-600);color:var(--amber-600)}
.avail-radio.r-ni:checked~.avail-opts .avail-opt.o-ni{background:var(--line-2);border-color:var(--grey);color:var(--slate)}
/* Keyboard: the invisible radio still takes focus, so the ring has to be drawn on its
   label or a keyboard user cannot see where they are. */
.avail-radio.r-free:focus-visible~.avail-opts .avail-opt.o-free,
.avail-radio.r-not:focus-visible~.avail-opts .avail-opt.o-not,
.avail-radio.r-am:focus-visible~.avail-opts .avail-opt.o-am,
.avail-radio.r-pm:focus-visible~.avail-opts .avail-opt.o-pm,
.avail-radio.r-maybe:focus-visible~.avail-opts .avail-opt.o-maybe,
.avail-radio.r-ni:focus-visible~.avail-opts .avail-opt.o-ni{border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-50)}

/* The "why not" panel — closed until Not free or Not interested is the answer. */
.avail-why{display:none;margin-top:var(--space-12);padding:var(--space-12);
  background:var(--bad-50);border-radius:var(--radius-sm)}
.avail-radio.r-not:checked~.avail-why,.avail-radio.r-ni:checked~.avail-why{display:block}
.avail-lbl{display:block;font-size:var(--text-12);font-weight:600;margin:var(--space-10) 0 var(--space-4)}
.avail-why>.avail-lbl:first-child{margin-top:0}
.avail-check{display:flex;align-items:flex-start;gap:var(--space-8);margin-top:var(--space-12);
  font-size:var(--text-13);cursor:pointer}
.avail-check .fld-check{flex:none;margin-top:var(--space-2)}
.avail-check .avail-sub{display:block}

/* One Send, full width on a phone — a multi-answer form is the submit case, not the
   save-pill case. */
.avail-send{width:100%;justify-content:center;padding:var(--space-14);font-size:var(--text-16)}

/* Thank-you state */
.avail-said{padding:var(--space-10) var(--space-12);border-radius:var(--radius-sm);
  margin-bottom:var(--space-8);font-size:var(--text-13);border-left:3px solid var(--line)}
.avail-said.ok{background:var(--good-50);border-left-color:var(--good)}
.avail-said.bad{background:var(--bad-50);border-left-color:var(--bad)}
.avail-said.part{background:var(--amber-50);border-left-color:var(--amber-600)}
.avail-answer{font-weight:600;margin-top:var(--space-3)}
/* Only the DATE is a line of its own; "You said: Available" must read as one sentence, so
   the <b> inside the sub-line stays inline (it inherits .avail-when b's display:block
   otherwise, and the answer drops under its own label). */
.avail-when .avail-sub b{display:inline}
/* This page is standalone (no base.html), so it never inherits the app's link colour —
   without this the "open this link again" and "Answer the rest" links render as plain
   black text and read as a full stop rather than something to tap. --orange-600 is the
   INTERACTIVE orange, the one that clears AA at this size. */
.avr-wrap a{color:var(--orange-600);font-weight:600}
/* The compose page's "Include job details" tick (casting_send_check.html). Sits between
   two uppercase field labels, so it needs its own vertical rhythm rather than a bare
   .fld-check floating in the form. */
.avail-details-tick{display:flex;align-items:flex-start;gap:var(--space-8);
  margin:var(--space-14) 0 0;font-size:var(--text-13);font-weight:600;cursor:pointer}
.avail-details-tick .fld-check{flex:none;margin-top:var(--space-2)}
.avail-details-tick .muted{font-weight:400}

/* ─────────────────────────────────────────────────────────────────────────
   "READY TO SEND" — the booking page's batch-send panel (2026-09-21 round 3).
   Greg: "How could Davina send off a bunch of availability checks or job offers at once —
   across several sessions from within the booking page, without opening the casting grid?"
   One card, one row per kind: a <details> carrying the count + who, and the send button.
   The row wraps at half-screen laptop width (~756px) rather than pushing the button off
   the edge — the counts are long sentences, not chips. */
/* Purple and pulsing like the unread bell (1 Oct 2026 meeting, Davina): something here is
   waiting on the office to press Send. It disappears once everything has gone. */
.sendpanel{border-left:4px solid var(--purple);
  background:color-mix(in srgb,var(--purple) 8%,var(--card));
  animation:telNotifPulse 1.8s ease-in-out infinite}
.sendpanel-title{color:var(--purple)}
@media (prefers-reduced-motion:reduce){.sendpanel{animation:none}}
.sendpanel-title{display:block;font-size:var(--text-14)}
.sendpanel-rows{display:flex;flex-direction:column;gap:var(--space-10);
  margin-top:var(--space-8)}
.sendpanel-row{display:flex;flex-wrap:wrap;align-items:flex-start;
  gap:var(--space-8) var(--space-14)}
.sendpanel-who{flex:1 1 340px;min-width:0;font-size:var(--text-13)}
.sendpanel-who>summary{cursor:pointer;color:var(--slate)}
.sendpanel-who>summary b{color:var(--ink)}
.sendpanel-list{list-style:none;margin:var(--space-6) 0 0;padding:0;
  font-size:var(--text-13)}
.sendpanel-list li{display:flex;flex-wrap:wrap;gap:var(--space-4) var(--space-8);
  padding:var(--space-3) 0;border-top:1px solid var(--line)}
.sendpanel-name{font-weight:600}
.sendpanel-when{color:var(--muted)}
/* The tick box sits ABOVE its own button, not beside it: it changes what that button
   sends, and reading it after pressing is too late. A FIXED column so the two Send
   buttons line up with each other — one has a tick box above it and one doesn't, and
   letting the column size itself put them at two different left edges. */
.sendpanel-act{flex:0 0 min(100%,300px);display:flex;flex-direction:column;
  align-items:flex-start;gap:var(--space-6);margin:0}
.sendpanel-check{display:flex;align-items:flex-start;gap:var(--space-6);
  font-size:var(--text-13);color:var(--slate);cursor:pointer}
.sendpanel-check .fld-check{flex:none;margin-top:var(--space-2)}

/* ─────────────────────────────────────────────────────────────────────────
   NOTIFICATIONS LIST (2026-09-22 phone audit). Was a <table class="tbl">
   whose right-hand action cell had white-space:nowrap, so that column kept
   its full width and squeezed the title column to ~10 characters on a
   phone — long titles wrapped one or two words per line down the screen.
   Notifications aren't tabular data anyway: stacked semantic rows instead.
   Phone-first order per row: unread dot + status pill, then the title full
   width, then detail/offer text, then the "…ago" meta line, then actions
   LAST in their own wrapping row (see .notif-actions .btn below for the
   40px tap floor). From 700px (this file's usual two-column cutoff) the
   row goes back to text-left/actions-right for office desktop use. */
/* Clips an unread row's tint (and the annotations card's own border) to the card's own
   rounded corners (annotation #703) — without this, a flat-cornered row background pokes
   past the card's curve on hover, when .card's border-color turns orange. */
.notif-card{padding:var(--space-4) 0;overflow:hidden}
.notif-list{display:flex;flex-direction:column}
.notif-row{display:flex;flex-direction:column;gap:var(--space-10);
  padding:var(--space-14) var(--space-16);border-top:1px solid var(--line-2)}
.notif-list .notif-row:first-child{border-top:none}
/* The annotations summary card (_notifications_list.html) sits above the live list. */
.notif-ann{border-bottom:1px solid var(--line-2)}
/* Reproduces the old inline background:rgba(124,77,255,.06) unread tint
   with the purple token instead of a raw colour. */
.notif-row.unread{background:color-mix(in srgb,var(--purple) 6%,var(--card))}
.notif-body{flex:1 1 auto;min-width:0}
/* The notice a phone-alert tap opened (/notifications/?n=<id>): an orange ring that fades,
   so the eye finds it in the list (Greg, 1 Oct 2026). */
.notif-focus{animation:notif-focus 3.5s ease-out 1}
@keyframes notif-focus{0%,55%{box-shadow:inset 0 0 0 2px var(--orange);background:var(--orange-50)}
  100%{box-shadow:none}}
@media (prefers-reduced-motion:reduce){.notif-focus{animation:none;box-shadow:inset 0 0 0 2px var(--orange)}}
.notif-top{display:flex;align-items:center;gap:var(--space-6)}
.notif-dot{width:8px;height:8px;border-radius:50%;background:var(--purple);flex:0 0 auto}
/* 10.5px in the old inline style — not a token; snapped to the nearest one. */
.notif-title{font-weight:700;font-size:var(--text-14);margin-top:var(--space-4)}
.notif-detail,.notif-offer{font-size:var(--text-12);line-height:1.5;margin-top:var(--space-4)}
.notif-offer div+div{margin-top:var(--space-2)}
.notif-meta{display:block;font-size:var(--text-12);margin-top:var(--space-4)}
.notif-actions{display:flex;flex-wrap:wrap;gap:var(--space-8)}
/* Phone: two-up, full-width-ish wrapping buttons with a real 40px thumb
   target — taller than .btn.xsm's own desktop padding gives it. */
/* Every action is an equal-width slot, main action on the RIGHT (Greg, 2026-09-25: Accept sits
   where Acknowledge does, same all the way down). Most buttons live inside their own <form>, so
   the slot is the direct child (form OR bare button/link) and the button fills it — sizing the
   .btn alone left Accept content-width inside its form while Decline stretched. */
.notif-actions{justify-content:flex-end}
.notif-actions>*,.notif-actions>.btn{flex:0 1 calc(50% - var(--space-4));min-width:0;margin:0;display:flex}
.notif-actions .btn{flex:1 1 auto;justify-content:center;min-height:40px}
@media(min-width:700px){
  .notif-row{flex-direction:row;align-items:flex-start;justify-content:space-between;gap:var(--space-16)}
  .notif-actions{flex-wrap:nowrap;flex:0 0 auto}
  .notif-actions>*,.notif-actions>.btn{flex:0 0 120px}
  .notif-actions .btn{min-height:0}
}

/* ─────────────────────────────────────────────────────────────────────────
   ACTOR CALL SHEET — `.csh` (2026-09-22 phone review). Prefix checked
   first: zero hits for `csh` anywhere in tel.css, the templates or the
   static tree before this block, so nothing here can silently restyle an
   older page (see memory `tel-css-prefix-already-taken`).

   Everything below the desktop bits is inside a @media (max-width:700px)
   block (two of them now — the stats bar has its own, right under the bar's
   laptop rules): office staff work on a laptop and the wide layout was fine,
   so nothing here may change at ≥700px.

   The phone card header is built by RE-FLOWING the shared session card
   (_session_card.html, which the actor JOB page also includes and which
   this task must not edit) rather than by changing its markup: the three
   wrapper elements go `display:contents`, so the date chip, the job
   number, the pills, the fee, the client name and the sub-line all become
   flex items of `.svc-top` itself and can be ordered independently. Scoped
   under `.csh` so the job page keeps exactly the layout it has today.
   ───────────────────────────────────────────────────────────────────── */
/* ~24px headshot beside the name in the top bar — the house size (memory
   `feedback-small-headshots-app-wide`), replacing the 90px one that used to
   open the page body. */
/* Greg, 2026-09-23: "in the header bar, can the actor's headshot take up the height of the
   two rows of text?" It could not, sitting where it does: the markup puts it INSIDE the h1
   (base.html's heading block), so its line box is that one row.

   So it is lifted out of the flow and centred against the whole title block — the h1 and
   the crumb together — with the text indented past it. `:has()` keeps this to the pages
   that actually carry a headshot up there; every other page's title block is untouched.
   44px is the measured height of the two rows (18px name + 12.5px crumb + their leading). */
.topbar > div:has(.csh-hs){position:relative;padding-left:56px}
.csh-hs{position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:44px;height:44px;margin:0;vertical-align:baseline}
/* The initials variant is a text box, not an image, so it needs its own line-height and a
   size that suits 44px rather than the 24px one `.person-hs.init` is built for. */
.person-hs.init.csh-hs{line-height:44px;font-size:var(--text-16)}
/* One row: range pill · ENGAGEMENTS n · TOTAL FEES $x. These are the laptop values;
   the phone budget is the block right below them. */
/* Greg, 2026-09-22 round 2: "I'm not crazy about these three items being in their own
   card… because it feels like we're losing width." The ROW is his own ask from this
   morning and survives untouched; only the packaging goes — no background, no border, no
   radius, and the horizontal padding drops so the figures line up with the card edges of
   the session list below instead of being inset from them. `margin-bottom` is the gap he
   also asked for: "there is no spacing between that card and the first work session block
   underneath it, the way there is between the work session blocks further down." */
.csh-bar{display:flex;align-items:center;flex-wrap:wrap;
  gap:var(--space-8) var(--space-22);margin:var(--space-12) 0 var(--space-16);
  padding:var(--space-2) 0;background:none;border:0}
/* Greg, 2026-09-23: "can time period, engagements and total fees be evenly distributed so
   they don't move around with each changing time period?" They were shrink-to-fit, so the
   whole row re-laid itself every time the period's WORD changed length — "Upcoming" and
   "Financial Year" pushed the money to a different place each time. Equal columns instead:
   the figures stay put and only their contents change. `min-width:0` so a long period label
   ellipsises inside its own column rather than stretching it. */
/* Greg, 23 Sept 2026 (second pass): "for the width of time period, engagements and total
   fees, can it be respectively 3FR, 2FR, 2FR?" Still fixed columns — the figures must not
   move as the period's word changes length, which was the morning's ask — but no longer
   equal ones: Time period holds the longest label ("Financial Year") and earns the extra
   room. A zero flex-basis makes these grow ratios behave exactly as 3fr/2fr/2fr would.
   When fees are hidden (someone who may not see money) it degrades to 3:2, which keeps
   Time period proportionally widest. */
.csh-stat{display:flex;flex-direction:column;gap:var(--space-2);min-width:0;flex:2 1 0}
.csh-bar .csh-stat:first-child{flex:3 1 0}
.csh-k{font-size:var(--text-11);text-transform:uppercase;letter-spacing:var(--caps-track);
  color:var(--muted);white-space:nowrap;font-weight:600}
.csh-v{font-size:var(--text-16);font-weight:600;white-space:nowrap}
/* Greg, 2026-09-22: those three on ONE row on a phone. At 390px the row has exactly
   328px of usable width, so this is budgeted rather than eyeballed:

     pill 143  +  ENGAGEMENTS 76  +  TOTAL FEES 78  +  two 10px gaps  =  317

   The phone type pass pushed it over that budget (`--text-13`, which the period pill
   uses, became 15px) and TOTAL FEES dropped to a second line. What gives ground here is
   only the packaging — the gutters, the gaps, the pill's padding and the small uppercase
   captions. The two FIGURES do not: `.csh-v` stays --text-16, because the money and the
   count are the reading matter, and the pill's font-size is deliberately untouched (a
   previous pass proved that widens the control and causes this very wrap). Everything
   is phone-only, so the laptop bar above is left pixel-for-pixel what it was. */
@media (max-width:700px){
  .csh-bar{gap:var(--space-8) var(--space-10);padding:var(--space-2) 0}
  /* --text-10 is the file's smallest LABEL step (`.csh-est` beside it is the same
     size), not a shrunk reading size. Buys ~8px on "ENGAGEMENTS", the longest one. */
  .csh-k{font-size:var(--text-10)}
  /* A `<select>` is as wide as its LONGEST option ("Financial Year"), never as wide as
     the one on show, so its padding is the part worth reclaiming. The right side stays
     clear of the chevron callsheet.html draws at `right:12px`. Two classes, so it beats
     that page's own `.period-select` (which loads after this file) without going near
     its font-size. */
  .csh-bar .period-select{padding-left:var(--space-10);padding-right:var(--space-22)}
}
/* Was font-size:var(--text-10) + two raw hexes inline on the page. */
.csh-est{font-size:var(--text-10);background:var(--orange-50);color:var(--orange-600)}
.csh-note{font-size:var(--text-12);margin-top:var(--space-6)}
.csh-acts{margin-left:auto;flex:0 0 auto;display:flex;align-items:center;gap:var(--space-8)}
/* The custom window, on the "Date range" period only. Greg, 2026-09-22 round 2: start date
   left, end date right, "next to each other on mobile in the same row" — so each takes half
   the row and neither may wrap onto its own line. There is no Apply button any more (each
   input submits on change), and no search box: that moved into the menu. */
.csh-range{display:flex;gap:var(--space-8);align-items:flex-end;margin-bottom:var(--space-16)}
.csh-rl{display:flex;flex-direction:column;gap:var(--space-3);flex:1 1 0;min-width:0}
.csh-range input{font-family:inherit;font-size:var(--text-14);width:100%;min-width:0;
  padding:var(--space-8) var(--space-11);border:1px solid var(--line);
  border-radius:var(--radius-sm);background:var(--card);color:var(--ink)}
/* Subscribe, demoted out of the header to the foot of the list. */
.csh-foot{display:flex;justify-content:center;margin-top:var(--space-20)}
/* Paper only — the top bar carries the name on screen and prints as nothing. */
.csh-print{display:none}
@media print{
  .csh-print{display:block;margin-bottom:var(--space-14)}
  .csh-print-name{font-size:var(--text-24);font-weight:700}
  .csh-foot,.csh-range{display:none!important}
}

/* ---- the engagement card header, at EVERY width -------------------- ----------------------
     Row 1  TUE 22 SEP 2026 ................................ $225 + SUPER
     Row 2  Client name (full width, nothing sharing it)
     Row 3  26-113  (+ LEAD / Offered)
     Row 4  course · session · your role
     then   the cancellation sentence, if there is one.            */
  .svc.sc-card .svc-top{display:flex;flex-wrap:wrap;align-items:center;
    gap:var(--space-4) var(--space-8)}
  .svc.sc-card .svc-top .svc-head,
  .svc.sc-card .svc-top .svc-head-link,
  .svc.sc-card .svc-top .svc-head-row{display:contents}
  /* Anything not placed below takes a line of its own, under the header. */
  .svc.sc-card .svc-top .svc-head>*,
  .svc.sc-card .svc-top .svc-head-link>*,
  .svc.sc-card .svc-top .svc-head-row>*{flex:1 1 100%;min-width:0;order:8}
  /* Row 1 — the date, flattened from a stacked block into one muted line.
     Keeps .date-chip.chg's yellow "date changed" highlight (that rule is
     !important and sets its own colours). */
  .svc.sc-card .svc-top .date-chip{order:1;flex:0 1 auto;display:flex;
    align-items:baseline;gap:var(--space-5);background:none;padding:0;
    text-align:left;white-space:nowrap}
  .svc.sc-card .svc-top .date-chip .d,
  .svc.sc-card .svc-top .date-chip .w,
  .svc.sc-card .svc-top .date-chip .m,
  .svc.sc-card .svc-top .date-chip .yr{font-family:var(--sans);
    font-size:var(--text-12);line-height:1.5;font-weight:700;
    letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
    opacity:1;margin:0}
  /* Row 1, right — the rate on a live job; the CANCELLED pill in the same
     slot on a cancelled one. */
  .svc.sc-card .svc-top .cs-fee{order:2;flex:0 0 auto;margin-left:auto;
    font-size:var(--text-18)}
  .svc.sc-card .svc-top .svc-head-row .pill.bad{order:2;flex:0 0 auto;
    margin-left:auto}
  /* Row 2 — the client. */
  .svc.sc-card .svc-top .svc-title{order:3;flex:1 1 100%;font-size:var(--text-16)}
  /* Row 3 — the job number (the slot Greg expected it in), plus any
     LEAD / Offered pill riding along with it. */
  .svc.sc-card .svc-top .job-badge{order:4;flex:0 0 auto;font-size:var(--text-13);
    color:var(--muted);padding:0;letter-spacing:.04em}
  .svc.sc-card .svc-top .svc-head-row .pill{order:4;flex:0 0 auto}
  /* Row 4 — course · session · your role. */
  .svc.sc-card .svc-top .svc-sub.muted{order:5;flex:1 1 100%}

  /* ---- a CANCELLED header: the struck fee is BACK in the corner, still one
     strikethrough, not three treatments ----
     22 Sep 2026: "these cancelled job headers look especially clunky" — so the
     struck ORIGINAL fee was hidden from the header altogether (the client name's
     own strikethrough was judged the clearer signal) and the cancellation
     sentence dropped out onto its own quiet line below, stated as a fact rather
     than shouted in bold red.

     23 Sep 2026, Greg: "on an actor's call sheet, the cancellation fee is
     actually missing from where the fee normally is in the top right corner of
     the card. So it shouldn't actually go anywhere." In the meeting itself
     [12:25]: "Is there a reason why we don't [show] the fees in the top right
     hand corner like the others?" and [12:38]: "you should probably say 36.75 +
     super in the top right hand corner on the cancelled one. Or at least the
     original fee with a strikethrough, and then the new fee."

     Both days are right at once, so the corner gets back ONE thing, not three:
     the struck original goes back into EXACTLY the slot a live job's fee sits in
     (same order:2, same flex:0 0 auto, same margin-left:auto as `.cs-fee` above)
     — just small and muted grey, no red, no bold. That is the "at least" floor
     Greg named.

     No stacking/collision logic was needed for the CANCELLED pill that also
     wants this slot: `.svc-head-row .pill.bad` (order:2 too) turns out to be
     permanently `display:none` already — `_session_card_css.html` (not owned by
     this pass) hides it whenever the card carries `.sc-st-cancelled`, "the band
     now says CANCELLED in red across the whole header, so the pill … is a third
     copy of the same word" — and `.sc-st-cancelled` is applied by the SAME
     `r.cancelled` flag that gates the pill in the template, so the two are never
     both true without the other. Checked live (DOM computed style, not just
     reasoning): `.pill.bad` really does compute to `display:none` on a
     job-cancelled card. So this corner is free for the fee with nothing to
     fight over.

     The PAYABLE figure Greg also asked for beside it is deliberately NOT split
     out here: views_actors.py hands this template `r.fee` as ONE
     already-finished sentence from fees.cancellation_line — "50% cancellation
     fee: $36.75", "Cancellation fee pending", "50% cancellation fee —
     cancellation pay to be confirmed by the office", "Released — no fee (booked
     on another job)" — never the number on its own, and this pass may only
     touch tel.css + this template, not views_actors.py. Slicing a dollar figure
     back out of that free-text sentence would mean parsing wording that is
     explicitly allowed to change shape per case (three of the four have NO
     number in them at all) — fragile, and it would break the day the wording
     does. So `r.fee` keeps rendering exactly where it already did: the calm
     line below the header (`.cs-fee-cancel` / `.cs-fee-released`, untouched by
     this pass) — the actor still reads the number, just not lifted into the
     corner on its own. A future pass that wants the literal split would add one
     more field in views_actors.py (the numeric `amount` cancellation_line()
     already returns, right beside `line`) rather than parse this string.

     `.cs-fee.strike` only ever renders when fees.cancellation_line gave a
     `fee_was` to print (views_actors sets fee_was and fee_str together), so
     un-hiding it can never show a struck dash with nothing behind it. */
  .svc.sc-card .svc-top .cs-fee.strike{order:2;flex:0 0 auto;margin-left:auto;
    font-size:var(--text-13);font-weight:400;color:var(--muted)}
  /* The amount rides directly after the struck original rather than taking its own auto
     margin, which would push the two to opposite ends of the row. */
  .svc.sc-card .svc-top .cs-fee.cs-fee-amt{order:2;flex:0 0 auto;margin-left:var(--space-6);
    font-size:var(--text-18)}
  .svc.sc-card .svc-top .cs-fee-cancel{order:6;flex:1 1 100%;text-align:left;
    font-weight:400;color:var(--slate);font-size:var(--text-14);
    margin-top:var(--space-2)}

@media (max-width:700px){
  /* ---- type size (Greg, 2026-09-22: "on mobile the font may be just a bit
     too small… too small and too light for some of our older actors", then
     "the colours are probably fine, it's just the size"). Colours are
     untouched; the reading matter steps UP one notch on the scale, scoped
     to this page so no shared component moves under any other page. Small
     LABELS (ENGAGEMENTS, TOTAL FEES) are not reading matter and stay; the row
     labels (Times, Venue, Address) are `.kv .k`, handled one line below. */
  .csh .svc-sub{font-size:var(--text-14)}
  .csh .svc .kv{font-size:var(--text-14)}
  .csh .svc .kv .k{font-size:var(--text-13)}
  .csh .team .m,.csh .team .m .role{font-size:var(--text-14)}
  .csh .contact-link small{font-size:var(--text-14)}
  .csh .cs-travel .ti{font-size:var(--text-14)}
  .csh .sc-13{font-size:var(--text-14)}
  .csh .csh-note{font-size:var(--text-14)}
}
/* Greg, 2026-09-22: "we can remove that dot next to confirmed." The dot is drawn
   by the shared `.pill .dot` component above, which many other pages use — so it
   is hidden HERE, on the call sheet's team lines only, rather than taken out of
   the component. Outside the phone query on purpose: the dot is just as redundant
   on a laptop and on the printed sheet. */
.csh .team .m .pill .dot{display:none}

/* ═════════════════════════════════════════════════════════════════════════
   THE PHONE PASS — 22 Sept 2026, app-wide (Greg, reviewing on his phone)

   Two sentences, two sections below:

   1. *"What if on mobile we didn't have to have everything within its own
      cards? Because it's essentially creating two levels of padding that's
      taking up horizontal width."*  A 390px screen was paying the gutter
      twice — `.content`'s 14px plus the card's own 16px — so a line of text
      started 30px in from each edge and had 330px to live in. Below the
      phone breakpoint a top-level card stops being an inset white box and
      becomes a full-bleed section: the page gutter moves INTO the card, one
      16px gutter total, ~28px of line width handed back.

   2. *"I think that font size is mobile view across the app… it's much
      smaller than if you were reading an article."* and, a minute earlier,
      *"actually the colours are probably fine, it's just the size."*  So the
      three type steps that carry READING MATTER move up; nothing else does,
      and no colour changes.

   Desktop and tablet are untouched: every rule here is inside a phone media
   query, and nothing below sets a property outside one.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── 1. Article-sized reading matter ──────────────────────────────────────
   Cut-off 700px, because that is the cut-off the app's OWN phone type rules
   already use: base.html's `@media (max-width:700px)` block (body 15.5px,
   fields 15px, `.muted` 13px) and the `.csh` call-sheet block above. A
   second, different number here would leave a band of widths reading at two
   sizes at once.

   Redefining the tokens is the whole change — there are ~235 `var(--text-1x)`
   declarations in this file and hunting them one by one would guarantee a
   miss. (Custom properties DO resolve inside a media query's BODY; what
   cannot be done is `@media(max-width:var(--bp-phone))` — a var in the media
   CONDITION — which is the warning at the top of this file.)

   Which steps move, and why:
     --text-14  14 → 16  the body size. Article-sized reading matter.
     --text-13  13 → 15  secondary reading matter (.kv values, .team lines,
                         .empty, table cells, .btn labels).
     --text-12  12 → 14  meta / sub lines (.bk .meta, .notif-detail,
                         .actor-card .sub, .kpi .sub).
   Which steps deliberately do NOT move:
     --text-10, --text-11  the small uppercase LABELS — TIMES, VENUE,
                         ENGAGEMENTS, `.tbl th` column heads, `.csh-k`,
                         `.pill`, `.badge`. Labels are not reading matter, and
                         Greg asked for size, not shouting.
     --text-16 and up    display sizes: headings, the job badge, `.kpi .num`,
                         the fee. Moving those blows up every layout that was
                         built around them.
   The hardcoded half-pixel sizes this file still carries (`.pill` 11.5px,
   `.hr-table` 12.5px) are labels/dense tables and are left exactly as they are.
   (`.cs-times span` 9.5px was a third; that whole block is gone — see the
   `.cs-times` note further up.) */
@media (max-width:700px){
  :root{
    --text-12:14px;
    --text-13:15px;
    --text-14:16px;
  }
  /* base.html's inline <style> sits AFTER this stylesheet, so its phone block
     wins every same-specificity tie. These three take one step of specificity
     more so the tokens above actually reach the page; nothing else in that
     block is contested (`.btn` stays at its 15px, which is what --text-13
     now resolves to anyway, so button rows do not grow). */
  html body{font-size:var(--text-14)}          /* was 15.5 px, a half-pixel size */
  html .muted,html small{font-size:var(--text-13)}   /* was 13px — the secondary line */
  /* What you TYPED is reading matter too — and at 16px iOS also stops zooming the
     page when a field takes focus. `select` is deliberately left out: the call
     sheet's period <select> is sized to share one row with the stats beside it. */
  html .fld,html input,html textarea{font-size:var(--text-14)}
  /* Four rules put a small uppercase LABEL on --text-12 instead of on the label
     steps (--text-10/-11) where the rest of them live. Those four must not move:
     at 14px the dashboard's KPI captions ("OUTGOINGS · NEXT 90D", "PROJECTED
     MARGIN") each broke onto a second line and made every tile taller. The
     literal 12px is deliberate and is the ONE place in this file where a raw
     size is right: inside this query `--text-12` means 14px by design, so the
     token cannot say "the size it used to be". */
  .kpi .lab,.money-tile .lab,.kit-lt>summary,.sc-grouplbl{font-size:12px}
}

/* ── 2. Flat, edge-to-edge sections ───────────────────────────────────────
   Cut-off 600px = `--bp-phone`, the file's phone tier: the width at which the
   nav already becomes the full-screen drawer and `--content-pad` already
   drops to 16px. Above it the inset card is correct — a tablet/laptop has the
   width to spend on it.

   Mechanics. The bleed is an INHERITED custom property set on `.content`, so
   two cases turn themselves off without having to be listed:
     * `.card *` → a card nested inside another card inherits 0 and keeps its
       box (flattening it would merge it into its parent, and it would lose
       its own padding against the parent's).
     * `.grid>*` → a card that is one TILE of a row of tiles (the dashboard
       KPIs, `.cols-2`, `.fin-strip`) inherits 0 and keeps its box; tiles side
       by side need an edge or they read as one block.
   Everything else — the section cards that ARE the page — bleeds to the screen
   edge and keeps its own padding as the single gutter.

   Everything is scoped under `.content`, the app shell's one content box, so a
   standalone page that never loads the shell — the availability answer page,
   the client sign-off page, the PDF templates — keeps the card it was designed
   with. On those pages the card IS the page, floating on the background, and
   there is no second gutter to win back.

   `#cal` rides along: FullCalendar's wrapper is not a `.card` but is built like
   one (same border, radius, shadow and padding), and it is the widest thing the
   app asks a phone to read.

   What is deliberately NOT changed:
     * the BORDER stays. At the screen edge a 1px `--line` hairline is
       invisible, and the top/bottom ones are exactly the "hairline between
       sections" this is meant to leave behind. More importantly the coloured
       left edges ride on it — `_ml_card.html`'s inline
       `border-left:4px solid <job state colour>`, `.sendpanel`'s purple,
       `.avail-said`'s status colours. Zeroing the border width would have
       taken the status colour off every session card on the phone.
     * modals, sheets, toasts and popovers. None of them is a `.card`
       (`.wa-modal-card`, `.tel-anno-dialog`, `.tel-leave-modal`, `.tel-toast`,
       `.ccw-panel`, `.dd-panel`, `.sub-pop` are their own components), so a
       raised object stays raised.
   `margin-left`/`margin-right`, not `margin-inline`: the wall iPad is iOS 12. */
@media (max-width:600px){
  /* base.html sets `.content{padding:16px 14px}` on a phone, so `--content-pad`
     (16px) stopped describing the real gutter — and the bleed below has to be
     the SAME number as the gutter or the card lands 2px off the edge. Re-assert
     it from the token; one step of specificity beats base.html's `.content`. */
  html .content{padding:var(--content-pad)}
  .content{--bleed:calc(-1 * var(--content-pad))}
  .content .card *,.content .grid>*,
  /* …and the same thing for the side-by-side layouts that are NOT `.grid`: two
     named ones, plus any wrapper that declares its own row/column layout in an
     inline style (the client-report page's `flex:1 1 560px` column is one).
     Found by measuring, not by guessing — `tools/`-style sweep over all 230
     GET-able pages at 390px, checking that every card with a negative margin
     really does land on BOTH screen edges. These three did not. */
  /* `.travel-cards` used to be listed here: its cards sat side by side, and a bled card
     inside a multi-column box overlaps its neighbour. The travel report folds to ONE
     column at this width now (22 Sept), so they bleed like every other section. */
  .content .stat-row>*,
  .content [style*="display:flex"]>*,.content [style*="display:grid"]>*,
  .content [style*="flex:"]>*,.content [style*="column-count"]>*{--bleed:0px}
  /* Full bleed + flat. The 0px fallback is the belt to that braces. */
  .content .card,#cal{margin-left:var(--bleed,0px);margin-right:var(--bleed,0px);
    border-radius:0;box-shadow:none}
  /* A card that kept its margins is still a box: give it its corners back, so
     nothing ends up half-flattened (square, shadowless, and still inset). */
  .content .card .card,.content .grid>.card,
  .content .stat-row>.card,
  .content [style*="display:flex"]>.card,.content [style*="display:grid"]>.card,
  .content [style*="flex:"]>.card,.content [style*="column-count"]>.card,
  /* An inline `margin:` SHORTHAND beats the bleed (three templates write one —
     expenses, play_detail, play_merge), so that card cannot reach the edge. It
     must not be left half-flattened: square and shadowless but still inset,
     floating on the page background with nothing to say where it starts. */
  .content .card[style*="margin:"]{
    margin-left:0;margin-right:0;border-radius:var(--radius-sm)}
}

/* ---------- Pick list ----------
   A list of things to choose ONE of, where each row is the choice — built for the
   Actor/RolePlayer Reflections session picker. A `.tbl` was the obvious reach and the wrong
   one: five columns of job facts can't fit a phone, so the row scrolled sideways and the
   button that actually does something sat off-screen. Here the whole row is the link, and
   on a phone the columns simply stack. */
.picklist .pick{display:grid;grid-template-columns:150px 92px minmax(0,1.2fr) minmax(0,1.4fr);
  gap:var(--space-14);align-items:baseline;padding:var(--space-12) var(--space-14);
  border-top:1px solid var(--line-2);color:var(--ink);text-decoration:none}
.picklist .pick:first-child{border-top:0}
.picklist .pick:hover{background:var(--orange-50)}
.picklist .pick-when{font-weight:600;white-space:nowrap}
.picklist .pick-job{color:var(--muted);white-space:nowrap}
.picklist .pick-sub{display:block;color:var(--muted);font-size:var(--text-12);margin-top:var(--space-2)}
@media (max-width:600px){
  /* Stacked: date and job share the first line, everything else falls under it. */
  .picklist .pick{grid-template-columns:auto 1fr;gap:var(--space-2) var(--space-10);
    padding:var(--space-12) var(--space-14)}
  .picklist .pick>*:nth-child(n+3){grid-column:1/-1}
}

/* ---------- Scripts page (actor, /me/scripts/) ----------
   One card per upcoming job that has something to read: the job (the whole head is the
   link to it), the script button, then who they are. Jobs with nothing attached are one
   line each in .scripts-waiting. Phone-first — an actor opens this on the train, and the
   first cut (Greg, 2026-09-24) was "absolutely horrendous on mobile". */
.scripts-job{margin-bottom:var(--space-12);overflow:hidden}
.scripts-job-head{display:block;position:relative;padding:var(--space-12) var(--space-16);
  padding-right:var(--space-22);color:var(--ink);text-decoration:none}
.scripts-job-head::after{content:"›";position:absolute;right:var(--space-14);top:var(--space-10);
  font-size:var(--text-20);color:var(--muted)}
.scripts-job-head:hover{background:var(--orange-50)}
.scripts-job-head h2{margin:0;font-size:var(--text-14);font-weight:700;line-height:1.3}
.scripts-job-when{font-size:var(--text-12);color:var(--muted);margin-top:var(--space-2);line-height:1.4}
.scripts-job-role{font-size:var(--text-12);margin-top:var(--space-2)}
.scripts-links{display:flex;flex-wrap:wrap;gap:var(--space-8);padding:0 var(--space-16) var(--space-12)}
.scripts-block{padding:var(--space-10) var(--space-16);border-top:1px solid var(--line-2)}
.scripts-label{font-size:var(--text-11);font-weight:600;letter-spacing:var(--caps-track);text-transform:uppercase;
  color:var(--muted);margin-bottom:var(--space-4)}
.scripts-char{padding:var(--space-6) 0}
.scripts-char+.scripts-char{border-top:1px solid var(--line-2)}
.scripts-char-name{font-weight:700;font-size:var(--text-13)}
.scripts-char-body{font-size:var(--text-13);white-space:pre-line;margin-top:var(--space-4)}
.scripts-char-wear{font-size:var(--text-12);color:var(--muted);margin-top:var(--space-4)}
.scripts-more>summary{cursor:pointer;font-size:var(--text-13);font-weight:700;color:var(--ink)}
.scripts-more>summary .muted{font-weight:400}
.scripts-waiting a{display:flex;justify-content:space-between;align-items:baseline;gap:var(--space-10);
  padding:var(--space-10) var(--space-16);border-top:1px solid var(--line-2);color:var(--ink);
  text-decoration:none;font-size:var(--text-13)}
.scripts-waiting a:first-child{border-top:0}
.scripts-waiting a:hover{background:var(--orange-50)}
.scripts-waiting .muted{flex:none;font-size:var(--text-12);text-align:right}
@media (max-width:600px){
  .scripts-links .btn{flex:1 1 100%;justify-content:center}
  .scripts-waiting a{flex-direction:column;gap:var(--space-2)}
  .scripts-waiting .muted{text-align:left}
}

/* ═════════════════════════════════════════════════════════════════════════
   ACTOR PHONE WALKTHROUGH — 24 Sept 2026 (call sheet, job page, crew chrome)
   ═════════════════════════════════════════════════════════════════════════ */

/* Team row on the actor call sheet / job page (_session_card.html). The same afternoon's
   two-line version (40px face, role on its own line) went back to ONE line that evening —
   Greg: "I liked it when the role was next to the name and the circles were small." So:
   the usual 24px face, then the name, then the role ("Actor as <character>", LEAD) on the
   SAME line. The row is a wrapping flex line hung 30px in (face 24 + gap 6), with the chip
   pulled back out to the edge, so anything that wraps lands under the NAME, never under the
   face. The role is ONE flex item: when it doesn't fit beside the name the whole of it drops
   to the next line — flex-wrap moves an item by its full width, so the role never breaks
   mid-way with half of it left beside the name. (Only a role longer than the whole line
   wraps inside itself, on its own line.) Phone and email are `.tm-contacts`, a full-width
   item under the name at the same indent. */
.team .m .tm-stack{display:flex;flex-wrap:wrap;align-items:baseline;column-gap:var(--space-6);
  row-gap:var(--space-1);padding-left:var(--space-30);text-indent:0}
.tm-stack>.person{margin-left:calc(-1 * var(--space-30))}
.tm-stack>.role{flex:0 1 auto;min-width:0}
.tm-stack>.role .pill{white-space:nowrap}
.tm-contacts{flex:0 0 100%;display:flex;flex-direction:column;align-items:flex-start;
  gap:var(--space-1);margin-top:var(--space-1)}
/* Icon and number on one centre line — the bare inline svg sat above the text's middle. */
.tm-stack .contact-link{display:inline-flex;align-items:center;gap:var(--space-5);line-height:1.3}
.tm-stack .contact-link svg.ic{width:14px;height:14px}
/* The coloured status band on the same card is a link to the job (_session_card.html) — it
   keeps the band's own look (colour from the status family rules), just becomes tappable. */
a.sc-st-link{display:block;text-decoration:none}
a.sc-st-link:focus-visible{outline:2px solid var(--orange);outline-offset:-2px}
/* A YouTube link in the actor job page's Resources or a character brief plays in place
   (actor_job.html): a full-width 16:9 player under the link, which stays for opening in
   the YouTube app. */
.yt-embed{display:block;width:100%;max-width:560px;aspect-ratio:16 / 9;height:auto;border:0;
  border-radius:var(--radius-sm);margin-top:var(--space-6);background:var(--ink)}
/* Web addresses in a character brief (`linkify`) — the site-wide `a{color:inherit}` left
   them grey and bare inside the muted brief, indistinguishable from the text. */
.cs-actor-notes{margin-top:var(--space-8);padding:var(--space-8) var(--space-12);border-left:3px solid var(--orange);background:var(--orange-50);font-size:var(--text-13);white-space:pre-line}
.cs-actor-notes b{display:block;margin-bottom:var(--space-4)}
.brief-text a{color:var(--orange-600);text-decoration:underline;overflow-wrap:anywhere}

/* Birthday banner on the call sheet: ONE line on a phone. It used to wrap to three because
   the emoji and the sentence were separate flex items allowed to wrap; the sentence now
   truncates with an ellipsis instead. */
.bday-banner{display:flex;align-items:center;gap:var(--space-8);min-width:0;
  margin-bottom:var(--space-12);padding:var(--space-9) var(--space-14);
  border-radius:var(--radius-sm);border:1px solid color-mix(in srgb,var(--pink) 28%,var(--card));
  border-left:4px solid var(--pink);background:color-mix(in srgb,var(--pink) 9%,var(--card));
  color:var(--pink);font-size:var(--text-14)}
.bday-banner-ic{flex:none;font-size:var(--text-18);line-height:1}
.bday-banner-txt{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}

/* The one-time "subscribe to your phone calendar" banner at the top of the call sheet
   (annotation #398) — same banner shape as .bday-banner just above, orange (a nudge to do
   something) instead of pink (a fact for today). */
.csh-sub-banner{display:flex;align-items:center;gap:var(--space-8);min-width:0;
  margin-bottom:var(--space-12);padding:var(--space-9) var(--space-14);
  border-radius:var(--radius-sm);border:1px solid color-mix(in srgb,var(--orange) 28%,var(--card));
  border-left:4px solid var(--orange);background:color-mix(in srgb,var(--orange) 9%,var(--card))}
.csh-sub-banner-link{flex:1 1 auto;min-width:0;display:flex;align-items:center;gap:var(--space-8);
  color:var(--ink);text-decoration:none;font-size:var(--text-14)}
.csh-sub-banner-link:hover{text-decoration:underline}
.csh-sub-banner-x{all:unset;flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:24px;height:24px;border-radius:var(--radius-sm);color:var(--muted);cursor:pointer}
.csh-sub-banner-x:hover,.csh-sub-banner-x:focus-visible{background:color-mix(in srgb,var(--orange) 16%,var(--card))}

/* Crew-only back arrow in the title bar (base.html) — every page but their call sheet, and
   it always goes TO the call sheet (a real link, not history.back(): an installed app
   opened from a notification has no history). Same bare 38px target as the menu button
   (base.html strips that one's border and fill), so the bar has one look at both ends. */
.tb-back{display:inline-flex;align-items:center;justify-content:center;flex:none;width:38px;height:38px;
  margin-left:calc(-1 * var(--space-8));border-radius:var(--radius-sm);color:var(--slate-2);
  text-decoration:none;-webkit-tap-highlight-color:transparent}
.tb-back:hover{color:var(--ink);background:var(--card)}
.tb-back svg.ic{width:22px;height:22px}
/* The installed-app "‹ Back" for everyone else (base.html, 1 Oct 2026): a <button> that
   starts hidden — the [hidden] rule has to be spelt out, or inline-flex above would win. */
.tb-back[hidden]{display:none}
.tb-back-hist{appearance:none;-webkit-appearance:none;border:0;background:none;cursor:pointer;
  width:auto;padding:0 var(--space-8) 0 var(--space-2);gap:var(--space-2);font:inherit;
  font-size:var(--text-14);font-weight:600}
.tb-back-hist svg.ic{width:20px;height:20px}

/* Past dates folded away behind a chevron, shut by default — the call sheet and the job page
   (phone walkthrough, 24 Sept 2026). The summary reads as the section heading it replaces;
   the cards inside keep their full width (no box round them). */
details.past-fold>summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:var(--space-8)}
details.past-fold>summary::-webkit-details-marker{display:none}
/* Drawn, not a › glyph: the display face renders › tiny and off the baseline. Points right
   while shut, down while open — the same two-border chevron as the call sheet's period picker. */
details.past-fold>summary .chev{flex:none;width:8px;height:8px;margin:0 var(--space-2);
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:rotate(-45deg);transition:transform .15s}
details.past-fold[open]>summary .chev{transform:rotate(45deg) translate(-2px,-2px)}
details.past-fold>summary .past-count{font-family:inherit;font-size:var(--text-13);letter-spacing:0;
  text-transform:none;color:var(--muted)}

/* Job materials "+ Add" box (booking page, 24 Sept 2026): label / link / file / session
   wrap two to a row in the narrow card instead of squashing four across. */
.matadd-fld{flex:1 1 160px;min-width:0}

/* ==== My Availability panel (_timeoff_panel.html / _timeoff_list.html) ====================
   Greg, 24 Sept 2026, on his phone as an actor: the add form was a strip of 11px labels and
   13px inputs wrapping anyhow. Now one field per row, the From/To pairs side by side, built
   on the site's own .fgroup label + .fld control (16px on a phone, so iOS doesn't zoom).
   Hosts: actor_timeoff.html and whereabouts_week.html (office My Availability). */
.toff-head{margin:0;font-size:var(--text-14)}
.toff-head .muted{font-weight:400;font-size:var(--text-12)}
.toff-list{display:flex;flex-direction:column;margin-top:var(--space-10)}
/* One row = text column (when / tags / note, all left-aligned) + action column, top-aligned. */
.toff-row{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:var(--space-12);
  align-items:start;font-size:var(--text-13);padding:var(--space-12) 0;border-bottom:1px solid var(--line)}
.toff-row:last-child{border-bottom:0}
.toff-main{display:flex;flex-direction:column;gap:var(--space-6);min-width:0}
.toff-when{font-weight:600;color:var(--ink)}
.toff-tags{display:flex;flex-wrap:wrap;gap:var(--space-6)}
.toff-note{color:var(--muted);line-height:1.45;overflow-wrap:anywhere}
.toff-acts{display:flex;align-items:center;gap:var(--space-4)}
.toff-kind{flex:none}
.toff-del{margin:0}
.toff-empty{margin:var(--space-8) 0 0}
.toff-form{display:flex;flex-direction:column;gap:var(--space-12);margin-top:var(--space-14)}
/* Two controls on one row at every width — a pair of dates fits a 390px phone. */
.toff-pair{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-10)}
.toff-pair>*{min-width:0}
/* .toff-pair / .fgroup set display, which beats the browser's [hidden] rule. */
.toff-form [hidden]{display:none}
/* iOS draws an empty date/time input with its own chrome and ignores width:100%; flatten it
   so the pair lines up with the selects and never pushes past the phone's edge. */
.toff-form input[type=date].fld,.toff-form input[data-time24].fld{-webkit-appearance:none;appearance:none;
  min-width:0;min-height:40px}
.toff-check{font-size:var(--text-13);color:var(--slate-2);cursor:pointer;padding:0}
.toff-actions{display:flex;justify-content:flex-start}
/* A wide desktop card would stretch a pair of dates across 1200px; keep the form a form. */
@media (min-width:601px){.toff-form{max-width:560px}}

/* Clients list + client page — Jacob/Greg meeting, 25 Sept 2026.
   .tbl-fit: a table column that hugs its content so the first (name) column takes the
   width — the Location / Bookings / Edit columns on the client list.
   .client-aka: a client's aliases after its name, as faint text, not a chip
   ("ASC  Australian Submarine Corporation"). */
.tbl th.tbl-fit,.tbl td.tbl-fit{width:1%;white-space:nowrap}
.client-aka{margin-left:var(--space-10);font-size:var(--text-12);color:var(--muted);font-weight:400}
/* Contacts tab: the active and the (collapsed) inactive contacts are two tables; a fixed
   layout with the same column shares makes the faded inactive names sit in exactly the same
   columns as the active ones, with no indent. */
.client-contacts{table-layout:fixed}
.client-contacts td{overflow-wrap:anywhere}
.client-contacts td:first-child{width:45%}
.client-contacts td:last-child{width:15%}
.client-inactive>summary{padding:var(--space-2) var(--space-14) var(--space-12)}
/* The PO stance pill, now at the top of the Financials & reports tab (was the page header). */
.client-po{margin-bottom:var(--space-12)}
/* Client edit form: the address search box, then suburb / state / postcode on one row. */
.client-addr-hint{font-size:var(--text-12);color:var(--muted)}

/* venues + search 2026-09-25 */
/* Venue page + venue edit form (25 Sept 2026 walkthrough): Parking & building access is
   its own block, the site-only fields sit behind "This is a site", and the edit form lays
   the address out as one type-ahead line with suburb / state / postcode on a row. */
.venue-form{padding:var(--space-22)}
.venue-sec{margin-top:var(--space-18)}
.venue-card{margin-bottom:var(--space-14)}
.venue-pre{white-space:pre-line}
.venue-site-fields{margin-top:var(--space-10)}
.venue-site-fields[hidden]{display:none}
.sec-title.venue-sub{margin-top:var(--space-16);font-size:var(--text-12)}
.venue-sub-hint{font-size:var(--text-12);margin:0 0 var(--space-8)}

/* ── Office Add/Edit actor page (actor_form.html, 25 Sept 2026) ─────────────────────────
   Availability as one row of Mon–Sun ticks per tier; the call-sheet name and login rows. */
.af-wrap{overflow-x:auto;grid-column:1/-1}
.af-days{width:100%;border-collapse:collapse;font-size:var(--text-13)}
.af-days th,.af-days td{padding:var(--space-6) var(--space-4);text-align:center;border-bottom:1px solid var(--line)}
.af-days th:first-child,.af-days td:first-child{text-align:left;padding-right:var(--space-10);min-width:120px}
.af-days th{font-size:var(--text-11);color:var(--muted);font-weight:600;text-transform:uppercase;letter-spacing:var(--caps-track)}
.af-days input{width:18px;height:18px;margin:0;accent-color:var(--orange)}
.af-days input:disabled{opacity:.5}
.af-na{color:var(--muted)}
.af-row{grid-column:1/-1;display:flex;align-items:center;gap:var(--space-10);flex-wrap:wrap;font-size:var(--text-14);color:var(--ink)}
.af-row .muted{font-size:var(--text-12)}
/* .field-group's own top margin (set for its usual stacked use) pushed it below
   centre when it sits inline in a flex row next to "Short name:" (#411, 2 Oct 2026). */
.af-row .field-group{margin-top:0}
.af-addr{grid-column:1/-1}

/* ---------- Tap the yellow → "Got it" (30 Sep 2026, chgack.js + _session_card.html) ----
   On the person's own card each yellow bit is a button (role/tabindex set by the script);
   tapping one shows the .chg-ack bar under that line. */
[data-chg-ack] .chg,[data-chg-ack] .chg-inline{cursor:pointer}
[data-chg-ack] [data-chg-bit]:focus-visible{outline:2px solid var(--ink);outline-offset:2px}
.chg-ack{display:flex;align-items:center;gap:var(--space-8);flex-wrap:wrap;
  margin:var(--space-6) 0;padding:var(--space-6) var(--space-10);border-radius:var(--radius-sm);
  background:var(--amber-50);border:1px solid var(--yellow);font-size:var(--text-13);color:var(--ink)}
.chg-ack[hidden]{display:none}
.chg-ack-msg{flex:1;min-width:0}
.chg-ack.is-done{background:var(--good-50);border-color:var(--good);color:var(--good)}
.chg-ack.is-err{background:var(--bad-50);border-color:var(--bad);color:var(--bad)}

/* ---------- My availability: the Save answer is obvious (30 Sep 2026) ----------
   .toff-saved = the sentence above the list; .toff-row-new = the row just saved, washed
   green for a few seconds. The chip beside the Save button is ajaxform's .save-state, set
   to 12px inline by that script — lifted here so a phone can read it. */
.toff-saved{margin:var(--space-10) 0 0;padding:var(--space-8) var(--space-10);
  border-radius:var(--radius-sm);background:var(--good-50);color:var(--good);
  font-size:var(--text-14);font-weight:600}
.toff-row-new{border-radius:var(--radius-sm);animation:toff-new 4s ease-out 1}
@keyframes toff-new{0%,60%{background:var(--good-50);box-shadow:0 0 0 2px var(--good)}
  100%{background:transparent;box-shadow:none}}
@media (prefers-reduced-motion:reduce){.toff-row-new{animation:none;background:var(--good-50)}}
.toff-actions{align-items:center;flex-wrap:wrap;gap:var(--space-8)}
.toff-actions .save-state{font-size:var(--text-14)!important;margin-left:0!important;text-align:left;color:var(--good)}
.toff-actions .save-state.dirty{color:var(--bad);font-weight:600}

/* ── Phone alerts + "How we reach you" (Notifications + My details, 30 Sept 2026) ──
   _phone_alerts.html, driven by phonealerts.js. [data-pa-show] blocks are shown one at a
   time by the script, keyed to this device's state. On Notifications the whole card can
   hide itself (`.pa-hiding` is the brief fade phonealerts.js applies before setting
   `hidden`, so it doesn't just vanish) once nothing needs the person's attention. */
.phone-alerts{margin-bottom:var(--space-16);padding:var(--space-14) var(--space-16)}
.phone-alerts.pa-hiding{opacity:0;transition:opacity .2s ease}
.phone-alerts h3{font-size:var(--text-14);margin:0 0 var(--space-4)}
.phone-alerts p{margin:0 0 var(--space-8);font-size:var(--text-13)}
.phone-alerts ol{margin:0 0 var(--space-8);padding-left:var(--space-20);font-size:var(--text-13)}
/* The step-by-step setup (1 Oct 2026): ONE block at a time, keyed to data-pa-state. */
.phone-alerts [data-pa-show]{display:none}
.phone-alerts[data-pa-state="loading"] [data-pa-show~="loading"],
.phone-alerts[data-pa-state="install"] [data-pa-show~="install"],
.phone-alerts[data-pa-state="desktop"] [data-pa-show~="desktop"],
.phone-alerts[data-pa-state="unsupported"] [data-pa-show~="unsupported"],
.phone-alerts[data-pa-state="allow"] [data-pa-show~="allow"],
.phone-alerts[data-pa-state="blocked"] [data-pa-show~="blocked"],
.phone-alerts[data-pa-state="test-ready"] [data-pa-show~="test-ready"],
.phone-alerts[data-pa-state="test-waiting"] [data-pa-show~="test-waiting"],
.phone-alerts[data-pa-state="test-done"] [data-pa-show~="test-done"],
.phone-alerts[data-pa-state="test-failed"] [data-pa-show~="test-failed"],
.phone-alerts[data-pa-state="reach"] [data-pa-show~="reach"],
.phone-alerts[data-pa-state="done"] [data-pa-show~="done"]{display:block}
/* This phone's own wording inside a step (data-pa-os on the card). */
.phone-alerts [data-pa-for]{display:none}
.phone-alerts[data-pa-os="iphone"] [data-pa-for~="iphone"],
.phone-alerts[data-pa-os="android"] [data-pa-for~="android"],
.phone-alerts[data-pa-os="other"] [data-pa-for~="other"]{display:block}
.pa-head{display:flex;align-items:baseline;justify-content:space-between;gap:var(--space-8)}
.pa-stepline{font-size:var(--text-12);color:var(--muted);font-weight:600}
.pa-stepline:empty{display:none}
.phone-alerts p.pa-title{font-size:var(--text-16);font-weight:700;margin:var(--space-6) 0 var(--space-6)}
.phone-alerts .pa-steps li{margin-bottom:var(--space-4)}
.phone-alerts p.pa-next{font-weight:600}
/* <button> doesn't inherit the page font by default — keep the card in one typeface. */
.phone-alerts button{font-family:inherit}
.pa-big{width:100%;justify-content:center;margin:var(--space-4) 0 var(--space-8)}
.phone-alerts p.pa-ok{color:var(--good);font-weight:700;font-size:var(--text-16)}
.pa-waiting{display:flex;align-items:center;gap:var(--space-10)}
.pa-spin{width:18px;height:18px;border-radius:50%;border:2px solid var(--line);
  border-top-color:var(--orange-600);animation:tel-spin .8s linear infinite;flex:none}
@media (prefers-reduced-motion:reduce){.pa-spin{animation-duration:2.4s}}
.pa-links{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);
  margin-top:var(--space-10);font-size:var(--text-12);color:var(--muted)}
.pa-link{background:none;border:0;padding:0;font:inherit;color:var(--slate);
  text-decoration:underline;cursor:pointer}
.pa-link:disabled{opacity:.5;cursor:default}
.pa-rec{color:var(--good);font-weight:600}
.pa-rec[hidden],.reach-option[hidden]{display:none}
.pa-skip{margin-top:var(--space-4)}
.phone-alerts-actions{display:flex;flex-wrap:wrap;gap:var(--space-8);align-items:center}
.phone-alerts-note{font-size:var(--text-12);color:var(--muted);min-height:var(--space-16)}
.phone-alerts-on{color:var(--good);font-weight:600}
.phone-alerts fieldset{border:0;margin:0;padding:0}
.phone-alerts legend{font-size:var(--text-13);font-weight:600;margin-bottom:var(--space-6)}
.phone-alerts .reach-backup{margin-top:var(--space-12)}
.reach{border-top:1px solid var(--line);margin-top:var(--space-12);padding-top:var(--space-12)}
.reach fieldset{border:0;margin:0;padding:0}
.reach legend{font-size:var(--text-13);font-weight:600;margin-bottom:var(--space-6)}
/* The second radio group of the reach ladder ("If you haven't answered…", 1 Oct 2026). */
.reach .reach-backup{margin-top:var(--space-12)}
/* Settings → "Reaching actors": three small fields side by side, stacking on a phone. */
.reach-settings{grid-template-columns:repeat(auto-fill,minmax(160px,220px));
  gap:var(--space-12);font-size:var(--text-12)}
.reach-option{display:flex;align-items:center;gap:var(--space-8);font-size:var(--text-13);
  padding:var(--space-4) 0;cursor:pointer}
.reach-option input[disabled] + span{color:var(--muted)}
/* The collapsed "Phone alerts are on · We reach you by: Both · Change" line (30 Sept 2026
   pm) — shown instead of the radios once a choice is on file; Change swaps them back.
   `display:flex` would otherwise beat the browser's own `[hidden]{display:none}` rule
   (an author rule always outranks a UA one), so it's restated below. */
.reach-summary{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;
  gap:var(--space-8);font-size:var(--text-13);color:var(--ink)}
.reach-summary[hidden]{display:none}
.reach-summary .btn{flex:0 0 auto}
/* "App icon" setting (_app_icon_setting.html, 1 Oct 2026): two tappable swatches of the
   actual artwork, Dark or Light. `.on` marks the saved choice; the orange border is the
   same "this one's picked" language as a selected pill elsewhere. */
.icon-choice-row{display:flex;gap:var(--space-16);flex-wrap:wrap}
.icon-choice{display:flex;flex-direction:column;align-items:center;gap:var(--space-6);
  border:2px solid var(--line);border-radius:var(--radius);padding:var(--space-10) var(--space-16);
  background:var(--card);cursor:pointer;font:inherit;font-size:var(--text-12);color:var(--ink)}
.icon-choice img{display:block;border-radius:var(--radius-sm)}
.icon-choice.on{border-color:var(--orange)}
.checkin-card{margin-bottom:var(--space-16);padding:var(--space-14) var(--space-16);
  border-left:4px solid var(--orange)}
.checkin-card h3{font-size:var(--text-14);margin:0 0 var(--space-6)}
.checkin-card ul{margin:0 0 var(--space-10);padding-left:var(--space-18);font-size:var(--text-13)}

/* Headshot crop box (headshotcrop.js, 1 Oct 2026) — any <input type=file data-headshot-crop>
   opens this on pick: drag/zoom the photo under a circle, with a dashed face oval + eye
   line so every face lands the same size (house style, tools/make_headshot_circles.py).
   Same "a box is asking you something" look as .tel-leave-modal. The guide is screen-only. */
.hscrop-modal{position:fixed;inset:0;z-index:10001;display:flex;align-items:center;
  justify-content:center;background:color-mix(in srgb,var(--ink) 55%,transparent);padding:var(--space-16)}
.hscrop-box{background:var(--card);border-radius:var(--radius-sm);border:1px solid var(--line);
  box-shadow:var(--shadow-lg);padding:var(--space-16);width:100%;max-width:400px;
  max-height:100%;overflow:auto;box-sizing:border-box}
.hscrop-title{margin:0 0 var(--space-10);font-size:var(--text-16)}
.hscrop-stage{position:relative;width:100%;aspect-ratio:1/1;overflow:hidden;
  border-radius:var(--radius-sm);background:var(--ink);cursor:grab;touch-action:none;
  user-select:none;-webkit-user-select:none;outline:none}
.hscrop-stage.is-dragging{cursor:grabbing}
.hscrop-stage:focus-visible{box-shadow:0 0 0 3px var(--orange)}
.hscrop-canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
.hscrop-mask{position:absolute;inset:0;border-radius:50%;pointer-events:none;
  border:2px solid var(--card);box-shadow:0 0 0 999px color-mix(in srgb,var(--ink) 55%,transparent)}
.hscrop-guide{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;overflow:visible}
.hscrop-guide ellipse,.hscrop-guide line{fill:none;vector-effect:non-scaling-stroke}
.hscrop-g-under ellipse,.hscrop-g-under line{stroke:color-mix(in srgb,var(--ink) 55%,transparent);stroke-width:3}
.hscrop-g-over ellipse,.hscrop-g-over line{stroke:color-mix(in srgb,var(--card) 85%,transparent);
  stroke-width:1.5;stroke-dasharray:5 4}
.hscrop-hint{margin:var(--space-8) 0 var(--space-10);font-size:var(--text-12);color:var(--muted)}
.hscrop-zoom{display:flex;align-items:center;gap:var(--space-10);font-size:var(--text-13);color:var(--ink)}
.hscrop-zoom input{flex:1;accent-color:var(--orange)}
.hscrop-actions{display:flex;justify-content:flex-end;gap:var(--space-8);margin-top:var(--space-14)}
.hscrop-preview{display:inline-flex;align-items:center;gap:var(--space-8);margin:var(--space-6) 0;
  font-size:var(--text-12);color:var(--muted)}
.hscrop-preview .headshot{margin:0;width:44px;height:44px}

/* ---- Casting sheet: Davina's 1 Oct 2026 meeting ------------------------------------------
   .cs-who-head   the grid's Member header with "+ Add date" beside it (opens #cs-adddates).
   .cs-adddates   the add-dates box, now ABOVE the grid instead of at the bottom of the page.
   .cs-foreign    "this sheet still has characters from another play" + Replace.
   .mdp-*         the multi-date calendar (multidatepicker.js). Selected = filled dark orange
                  (white on --orange fails AA); keyboard focus = an INK outline, so a ticked,
                  focused day never wears two orange rings. */
.cs-who-head{display:flex;align-items:center;justify-content:flex-start;gap:var(--space-12)}
/* Casting grid member cell: the times-cast count sits on the name line (Greg, 15 Oct 2026). */
.cs-who-line{display:flex;align-items:center;flex-wrap:wrap;gap:var(--space-4) var(--space-6)}
.cs-grid td.who .cs-who-line small{display:inline}
.cs-primary{color:var(--orange-600);font-size:var(--text-14);line-height:1}
.cs-adddates{padding:var(--space-14) var(--space-16);margin-bottom:var(--space-12)}
.cs-adddates-head{font-size:var(--text-14);margin-bottom:var(--space-10);display:flex;align-items:center;gap:var(--space-6)}
.cs-adddates-part{margin:0 0 var(--space-14)}
.cs-adddates-part:last-child{margin-bottom:0}
.cs-adddates-lead{font-size:var(--text-13);margin:0 0 var(--space-6)}
.cs-adddates-list{font-size:var(--text-12);margin:0 0 var(--space-8);padding-left:var(--space-18)}
.cs-adddates-row{display:flex;gap:var(--space-18);flex-wrap:wrap;align-items:flex-start}
.cs-adddates-side{display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-10);flex:1 1 220px;min-width:0}
.cs-adddates-fields{display:flex;gap:var(--space-10);flex-wrap:wrap}
.cs-adddates-lbl{display:flex;flex-direction:column;gap:var(--space-3);font-size:var(--text-12);color:var(--muted)}
.cs-adddates-lbl .fld{min-width:110px}
.cs-foreign{margin-bottom:var(--space-12);border-color:var(--amber-600);background:var(--amber-50);font-size:var(--text-13)}
.cs-foreign-line{margin:var(--space-6) 0 0}
.cs-foreign-form{margin:var(--space-10) 0 0}

.mdp{width:252px;max-width:100%;user-select:none}
.mdp-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--space-6)}
.mdp-title{font-weight:600;font-size:var(--text-13)}
.mdp-nav{background:none;border:1px solid var(--line);border-radius:var(--radius-sm);width:32px;height:32px;
  cursor:pointer;font-size:var(--text-16);line-height:1;color:var(--ink);font-family:inherit}
.mdp-nav:hover{background:var(--bg)}
.mdp-nav:focus-visible{outline:2px solid var(--ink);outline-offset:1px}
.mdp-grid{display:flex;flex-direction:column;gap:var(--space-2)}
.mdp-row{display:grid;grid-template-columns:repeat(7,1fr);gap:var(--space-2)}
.mdp-dow{text-align:center;font-size:var(--text-11);color:var(--muted);font-weight:600;padding:var(--space-2) 0}
.mdp-blank{display:block}
.mdp-day{width:100%;aspect-ratio:1;min-height:32px;border:1px solid transparent;border-radius:8px;background:none;
  cursor:pointer;font-size:var(--text-13);color:var(--ink);font-family:inherit;position:relative;padding:0}
.mdp-day:hover{background:var(--bg)}
.mdp-day:focus-visible{outline:2px solid var(--ink);outline-offset:1px}
.mdp-day.is-past{color:var(--muted)}
.mdp-day.is-today{font-weight:700;border-color:var(--line)}
.mdp-day.is-taken::after{content:"";position:absolute;left:50%;bottom:3px;width:4px;height:4px;margin-left:calc(-1 * var(--space-2));
  border-radius:50%;background:var(--muted)}
.mdp-day[aria-pressed="true"]{background:var(--orange-700);border-color:var(--orange-700);color:var(--white);font-weight:600}
.mdp-day[aria-pressed="true"].is-taken::after{background:var(--card)}
.mdp-chosen{display:flex;flex-wrap:wrap;gap:var(--space-5);min-height:26px;align-items:center}
.mdp-none{font-size:var(--text-12)}
/* `.chip.mdp-chip`: a chosen date, removable. */
.chip.mdp-chip{--chip-fg:var(--orange-700);border-width:0;gap:var(--space-3);
  padding:var(--space-2) var(--space-4) var(--space-2) var(--space-9);font-size:var(--text-12);font-weight:600;line-height:inherit}
.mdp-chip-x{background:none;border:none;cursor:pointer;color:inherit;font-size:var(--text-14);line-height:1;
  width:20px;height:20px;border-radius:50%;padding:0;font-family:inherit}
.mdp-chip-x:hover{background:var(--card)}
.mdp-chip-x:focus-visible{outline:2px solid var(--ink);outline-offset:0}

/* ---- "Offer sent Wed 1 Oct" under an Offered / Availability-check status (office only) ----
   .tm-askline   on the booking page's team row, under the status pill.
   .eng-askline  in the session editor's collapsed cast-row header, beside the role. */
.tm-askline{display:block;font-size:var(--text-11);color:var(--muted);margin-top:var(--space-2);max-width:130px;line-height:1.3}
.eng-askline{color:var(--muted);font-size:var(--text-11);white-space:nowrap}
/* The check-in on the status pill (Greg, 20 Oct 2026, option C — _status_pill_ci.html).
   The pill keeps its own colour when it is open (.subscribe[open]>summary turns text orange).
   RED pulses: someone must ring this person. A soft ring, not a flashing fill, so the
   words stay readable; reduced-motion gets a still ring instead. */
.tm-ci{display:inline-block}
.tm-ci>.sub-pop{width:280px}
.subscribe.tm-ci[open]>summary.pill.bad{color:var(--bad)}
.subscribe.tm-ci[open]>summary.pill.warn{color:var(--amber-600)}
.subscribe.tm-ci[open]>summary.pill.ok{color:var(--good)}
.subscribe.tm-ci[open]>summary.pill.muted{color:var(--muted)}
.tm-ci-say{margin:0 0 var(--space-8);font-size:var(--text-12);color:var(--slate-2);line-height:1.45}
.pill.tm-urgent{animation:tm-pulse 1.6s ease-in-out infinite}
@keyframes tm-pulse{0%,100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--bad) 55%,transparent)}
  60%{box-shadow:0 0 0 6px color-mix(in srgb,var(--bad) 0%,transparent)}}
@media (prefers-reduced-motion:reduce){.pill.tm-urgent{animation:none;box-shadow:0 0 0 2px var(--bad)}}
/* A draft-resync failure must point straight at the way out — the manual preview button
   pulses the same "needs attention" ring .pill.tm-urgent already uses (Greg, 20 Oct
   2026, after missing a plain error line: "pulse the update preview button"). */
.pulse-attn{animation:tm-pulse 1.6s ease-in-out infinite}
@media (prefers-reduced-motion:reduce){.pulse-attn{animation:none;box-shadow:0 0 0 2px var(--bad)}}
/* ── Casting sheet: batch availability check + remove a role (1 Oct 2026) ─────────────────
   Tick people / a role group / date columns, then one button opens the compose page
   (views_casting.casting_send_check_many). The bar sits above the grid, outside the ajax
   region. `.cs-tick` is the small "ask" tick in a date header. */
.cs-bulkbar{display:flex;align-items:center;gap:var(--space-12);flex-wrap:wrap;margin:var(--space-10) 0}
.cs-bulkbar .muted{font-size:var(--text-12)}
.btn.is-disabled{opacity:.5;cursor:not-allowed}
.cs-tick{display:inline-flex;align-items:center;gap:var(--space-4);font-size:var(--text-11);
  font-weight:600;color:var(--muted);cursor:pointer;margin:var(--space-2) 0}
.cs-tick .fld-check,.cs-grid td.who .cs-tick-input,.cs-grouphead-in .cs-tick-input{width:16px;height:16px;flex-shrink:0}
.cs-grouphead-in{display:flex;align-items:center;gap:var(--space-6);flex-wrap:wrap}
.cs-roleremove{margin:0 0 0 auto}
.cs-roleremove .btn{text-transform:none;letter-spacing:0}
.cs-otherplay{text-transform:none;letter-spacing:0;font-size:var(--text-11)}
/* Compose page: one row per recipient. */
.bc-people{display:flex;flex-direction:column;gap:var(--space-6);margin-top:var(--space-10)}
.bc-person{display:flex;align-items:flex-start;gap:var(--space-10);padding:var(--space-8) var(--space-10);
  border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--card)}
.bc-person.is-off{opacity:.6;background:var(--bg)}
.bc-person-main{flex:1;min-width:0}
.bc-person-main .muted{font-size:var(--text-12)}
.bc-dates{display:flex;flex-wrap:wrap;gap:var(--space-4);margin-top:var(--space-4)}
.bc-dates .pill{font-size:var(--text-11)}
/* Answer page: a date we already know about. */
.avail-known{padding:var(--space-10) 0;border-bottom:1px solid var(--line)}
.avail-known:last-child{border-bottom:0}
.bc-lbl{display:block;font-size:var(--text-12);font-weight:600;color:var(--muted);text-transform:uppercase;
  letter-spacing:var(--caps-track);margin:var(--space-14) 0 var(--space-4)}
.bc-actions{display:flex;align-items:center;gap:var(--space-10);flex-wrap:wrap;margin-top:var(--space-16)}

/* ---------- Venue maps, photos & files (Davina, 1 Oct 2026 — nexus2/venue_files.py) ----------
   .vf-grid/.vf-tile = the venue page's own card (_venue_files.html); .vf-strip/.vf-thumb =
   the same files on a call-sheet card (_session_card.html); .vf-res = a Resources link there. */
.vf-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:var(--space-10);margin-bottom:var(--space-12)}
.vf-tile{position:relative;min-width:0}
.vf-img{display:block;border-radius:var(--radius-sm);overflow:hidden;border:1px solid var(--line);aspect-ratio:4/3;background:var(--bg)}
.vf-img img{width:100%;height:100%;object-fit:cover;display:block}
.vf-pdf{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--space-4);color:var(--slate);font-size:var(--text-12);font-weight:600;text-decoration:none}
.vf-pdf svg{width:28px;height:28px}
.vf-del{position:absolute;top:var(--space-5);right:var(--space-5);margin:0}
.vf-capform{margin:var(--space-4) 0 0}
.vf-capfld{width:100%;font-size:var(--text-12);padding:var(--space-3) var(--space-6)}
.vf-capline{font-size:var(--text-12);color:var(--muted);margin-top:var(--space-3);overflow-wrap:anywhere}
.vf-upload{margin:0}
.vf-drop{padding:0}
.vf-drop-row{display:flex;gap:var(--space-8);flex-wrap:wrap}
.vf-capnew{flex:1;min-width:200px;max-width:360px}
.vf-strip{display:flex;flex-wrap:wrap;gap:var(--space-8)}
.vf-thumb{display:flex;flex-direction:column;width:96px;text-decoration:none;color:var(--ink)}
.vf-thumb img{width:96px;height:72px;object-fit:cover;border-radius:var(--radius-sm);border:1px solid var(--line);display:block}
.vf-cap{font-size:var(--text-11);color:var(--muted);margin-top:var(--space-2);line-height:1.25;overflow-wrap:anywhere}
.vf-doc{display:inline-flex;align-items:center;gap:var(--space-4);font-size:var(--text-13);font-weight:600}
.vf-res + .vf-res{margin-top:var(--space-5)}
.vf-res a{display:inline-flex;align-items:center;gap:var(--space-4);font-weight:600}
.venue-maplink{display:inline-flex;align-items:center;margin-left:var(--space-6);color:var(--muted);vertical-align:-2px}
.venue-maplink:hover{color:var(--orange-600)}
.venue-maplink svg{width:15px;height:15px}
.venue-client-src{font-size:var(--text-12);white-space:nowrap}

/* Photo viewer (lightbox.js, 7 Oct 2026): a dark overlay with the picture, its caption and
   next / back arrows. Above the app's own popovers (z-index 60). */
html.lb-open{overflow:hidden}
.lb{position:fixed;inset:0;z-index:200;background:rgba(0,0,0,.92);display:flex;align-items:center;justify-content:center;padding:var(--space-12)}
.lb[hidden],.lb [hidden]{display:none}
.lb-fig{margin:0;max-width:100%;max-height:100%;display:flex;flex-direction:column;align-items:center;gap:var(--space-10)}
.lb-img{max-width:100%;max-height:calc(100vh - 120px);object-fit:contain;border-radius:var(--radius-sm);display:block}
.lb-cap{color:var(--card);font-size:var(--text-14);text-align:center;max-width:36em;overflow-wrap:anywhere}
.lb-count{position:absolute;top:var(--space-14);left:50%;transform:translateX(-50%);color:var(--card);font-size:var(--text-13);opacity:.8}
.lb-btn{position:absolute;display:flex;align-items:center;justify-content:center;width:44px;height:44px;border:0;border-radius:50%;background:rgba(255,255,255,.16);color:var(--card);font-size:var(--text-32);line-height:1;cursor:pointer}
.lb-btn:hover,.lb-btn:focus-visible{background:rgba(255,255,255,.3)}
.lb-close{top:var(--space-10);right:var(--space-10)}
.lb-prev{left:var(--space-8);top:50%;transform:translateY(-50%)}
.lb-next{right:var(--space-8);top:50%;transform:translateY(-50%)}

/* Annotation thread (/annotations/<pk>/, 1 Oct 2026): the screen grab, the note, then the
   conversation with Claude. "dev" turns sit left with a purple edge (Claude), the person's
   own replies sit right on orange-50. Phone-first: one column, pictures never overflow. */
.ann-wrap{max-width:760px}
.ann-sec{padding:var(--space-14) var(--space-16)}
.ann-sec + .ann-sec{border-top:1px solid var(--line)}
.ann-meta{font-size:var(--text-12);color:var(--muted);overflow-wrap:anywhere}
.ann-note{font-size:var(--text-14);line-height:1.5;white-space:pre-line;overflow-wrap:anywhere;margin-top:var(--space-6)}
.ann-shot{display:block;max-width:100%;height:auto;border:1px solid var(--line);border-radius:var(--radius-sm);margin-top:var(--space-10)}
.ann-msgs{display:flex;flex-direction:column;gap:var(--space-10)}
.ann-msg{max-width:88%;border-radius:var(--radius-sm);padding:var(--space-10) var(--space-12);font-size:var(--text-13);line-height:1.5;overflow-wrap:anywhere}
.ann-msg.dev{align-self:flex-start;background:var(--card);border:1px solid var(--line);border-left:4px solid var(--purple)}
.ann-msg.dev.ask{border-left-color:var(--amber-600);background:var(--amber-50)}
.ann-msg.user{align-self:flex-end;background:var(--orange-50);border:1px solid var(--line)}
.ann-msg-who{font-size:var(--text-11);font-weight:700;color:var(--muted);margin-bottom:var(--space-4);display:flex;align-items:center;gap:var(--space-6)}
.ann-msg-who .avatar{width:18px;height:18px;font-size:var(--text-10);flex:0 0 auto}
.avatar-claude{background:var(--orange-600);color:var(--white)}
.avatar-claude svg{width:65%;height:65%}
.ann-msg-body{white-space:pre-line}
.ann-msg .ann-shot{margin-top:var(--space-8)}
.ann-reply textarea{min-height:90px;resize:vertical}
.ann-reply-row{display:flex;justify-content:flex-end;gap:var(--space-8);margin-top:var(--space-8)}
.ann-reply label{display:block}
/* Annotation #624 (Greg, phone): Send and Mark done & next are right-aligned at the foot of
   the pane — the same corner #scrolltop floats over once you've scrolled down, so the fixed
   circle sat on top of them. Reserve room below the last section on a phone so the circle
   has blank space to float over instead. */
@media(max-width:600px){
  .ann-pane .ann-sec:last-of-type{padding-bottom:calc(var(--space-16) + 64px)}
}
/* The annotations inbox (/annotations/, 5 Oct 2026; annotations.html + annotationinbox.js).
   Phone: the list only, a row is a link to the thread page. Desktop (900px+ — var() can't
   sit in @media): the list stays put on the left and the chosen conversation
   (_annotation_pane.html) loads on the right without a page reload. */
.ann-inbox{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--space-16);align-items:start}
.ann-inbox-pane{display:none;min-width:0}
.ann-inbox-list{padding:0;overflow:hidden}
.ann-inbox-tabs{margin-bottom:0;padding:0 var(--space-6);flex-wrap:wrap;overflow:visible}
.ann-inbox-full[hidden]{display:none}
.ann-inbox-tabs a{padding:var(--space-9) var(--space-8);font-size:var(--text-12)}
.ann-inbox-search{display:flex;gap:var(--space-8);padding:var(--space-10) var(--space-12);border-bottom:1px solid var(--line)}
.ann-inbox-search [data-ann-search]{flex:1 1 auto;min-width:0}
.ann-inbox-search [data-ann-who]{flex:0 0 auto;max-width:40%}
.ann-inbox-bar{display:flex;align-items:center;flex-wrap:wrap;gap:var(--space-6);margin-bottom:var(--space-10)}
.ann-inbox-keys{margin-left:auto;font-size:var(--text-11)}
.ann-pane.is-loading{opacity:.55}
.ann-row{display:flex;gap:var(--space-10);padding:var(--space-10) var(--space-14);border-top:1px solid var(--line-2);color:var(--ink);text-decoration:none}
.ann-row[hidden]{display:none}
.ann-row:hover{background:var(--bg)}
.ann-row.on{background:var(--orange-50);box-shadow:inset 3px 0 0 var(--orange)}
.ann-row-dot{flex:0 0 auto;width:var(--space-8);height:var(--space-8);border-radius:50%;margin-top:var(--space-5)}
.ann-row.unread .ann-row-dot{background:var(--purple)}
.ann-row-main{display:flex;flex-direction:column;gap:var(--space-3);flex:1 1 auto;min-width:0}
.ann-row-top,.ann-row-sub{display:flex;align-items:center;justify-content:space-between;gap:var(--space-8);min-width:0}
.ann-row-name{font-size:var(--text-13);font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ann-row.unread .ann-row-name{font-weight:700}
.ann-row-time{flex:0 0 auto;font-size:var(--text-11);color:var(--muted);white-space:nowrap}
.ann-row-who{font-size:var(--text-11);color:var(--muted);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ann-row-preview{font-size:var(--text-12);color:var(--muted);overflow:hidden;overflow-wrap:anywhere;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
@media (min-width:900px){
  .ann-inbox{grid-template-columns:minmax(280px,360px) minmax(0,1fr)}
  .ann-inbox-pane{display:block}
  .ann-inbox-list{position:sticky;top:calc(var(--header-h) + var(--space-16));display:flex;flex-direction:column;
    max-height:calc(100vh - var(--header-h) - var(--space-16) - var(--space-16))}
  .ann-inbox-rows{flex:1 1 auto;min-height:0;overflow-y:auto}
}
/* "Notes per week" trend card (annotations.html, 19 Oct 2026 advisor audit: "is the
   annotation rate going up or down?"). A bar pair per week; heights are data (a 0-100%
   step, carried as data-w, the Speed page's own pattern) so no inline style is needed. */
.ann-trend{display:flex;align-items:flex-end;gap:var(--space-8);height:80px;overflow-x:auto}
.ann-trend-wk{display:flex;flex-direction:column;align-items:center;gap:var(--space-4);flex:1 1 0;min-width:16px}
.ann-trend-pair{display:flex;align-items:flex-end;gap:2px;height:56px}
.ann-trend-bar{width:8px;min-height:2px;border-radius:2px 2px 0 0;background:var(--line-2)}
.ann-trend-bar.new{background:var(--orange)}
.ann-trend-bar.done{background:var(--good)}
.ann-trend-wk-lbl{font-size:var(--text-10);color:var(--muted);white-space:nowrap}
.ann-trend-bar[data-w="0"]{height:0%}.ann-trend-bar[data-w="5"]{height:5%}.ann-trend-bar[data-w="10"]{height:10%}.ann-trend-bar[data-w="15"]{height:15%}.ann-trend-bar[data-w="20"]{height:20%}.ann-trend-bar[data-w="25"]{height:25%}.ann-trend-bar[data-w="30"]{height:30%}.ann-trend-bar[data-w="35"]{height:35%}.ann-trend-bar[data-w="40"]{height:40%}.ann-trend-bar[data-w="45"]{height:45%}.ann-trend-bar[data-w="50"]{height:50%}.ann-trend-bar[data-w="55"]{height:55%}.ann-trend-bar[data-w="60"]{height:60%}.ann-trend-bar[data-w="65"]{height:65%}.ann-trend-bar[data-w="70"]{height:70%}.ann-trend-bar[data-w="75"]{height:75%}.ann-trend-bar[data-w="80"]{height:80%}.ann-trend-bar[data-w="85"]{height:85%}.ann-trend-bar[data-w="90"]{height:90%}.ann-trend-bar[data-w="95"]{height:95%}.ann-trend-bar[data-w="100"]{height:100%}
.ann-trend-key{display:flex;gap:var(--space-14);margin-top:var(--space-8);font-size:var(--text-12);color:var(--muted)}
.ann-trend-key i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:var(--space-4);vertical-align:-1px}
.ann-trend-key .new i{background:var(--orange)}
.ann-trend-key .done i{background:var(--good)}
.ann-trend-summary{margin-top:var(--space-6);font-size:var(--text-13);color:var(--muted)}
/* Rules Claude has learned (agent_rules.html, 3 Oct 2026): the line under each rule. */
.rule-meta{font-size:var(--text-12);color:var(--muted)}
/* SMS replies Claude read (/settings/reply-readings/, 8 Oct 2026): what it said, then one
   row of answer buttons that save in place. */
.reply-reading-said{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);margin-top:var(--space-8)}
.reply-reading-verdict{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6) var(--space-8);margin-top:var(--space-10)}
/* Who can see what (report_access.html, annotation 487): role and person tick matrices. */
.accmx-intro{display:flex;align-items:flex-start;gap:var(--space-14);flex-wrap:wrap}
.accmx-intro p{flex:1 1 320px;margin:0}
/* The table scrolls inside its own box (both ways), so the role/person names stay pinned at
   the top and the access names at the left — sticky can't pin to the page from inside an
   overflow box. */
.accmx-wrap{overflow:auto;max-height:calc(100vh - var(--header-h) - 48px);padding:0}
.accmx thead th{position:sticky;top:0;background:var(--card);z-index:2;box-shadow:inset 0 -1px 0 var(--line)}
.accmx th.accmx-col{text-align:center;min-width:96px;vertical-align:bottom;text-transform:none;letter-spacing:0;font-size:var(--text-12);color:var(--ink)}
.accmx td:first-child,.accmx th:first-child{position:sticky;left:0;background:var(--card);min-width:200px;z-index:1}
.accmx thead th:first-child{z-index:3}
.accmx-cb{width:18px;height:18px;accent-color:var(--good);cursor:pointer}
.accmx-sec td{background:var(--bg);font-weight:600;font-size:var(--text-12);text-transform:uppercase;letter-spacing:var(--caps-track);color:var(--muted);padding:var(--space-8) var(--space-14)}
.accmx-sec td:first-child{background:var(--bg)}
.accmx td.accmx-tick{text-align:center;vertical-align:middle}
.accmx-yes{color:var(--good);font-weight:700;font-size:var(--text-16)}
.accmx-no{color:var(--line)}
.accmx-label{font-weight:600}
.accmx-help{font-size:var(--text-12);color:var(--muted);margin:var(--space-2) 0 0}
.accmx-flag{display:block;font-weight:400;font-size:var(--text-11);color:var(--muted)}
.accmx-roles{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:var(--space-14)}
.accmx-role-name{margin:0;color:var(--orange-600);font-size:var(--text-16)}
.accmx-members{margin:var(--space-10) 0 0;padding-left:var(--space-18)}
.accmx-members li{margin:var(--space-2) 0}
.accmx-more{margin-top:var(--space-10)}
.accmx-rm{border:0;background:none;color:var(--muted);cursor:pointer;font-size:var(--text-16);line-height:1;padding:0 var(--space-4);margin-left:var(--space-2)}
.accmx-rm:hover,.accmx-rm:focus-visible{color:var(--bad)}
.accmx-add{margin-top:var(--space-12);border-top:1px solid var(--line-2);padding-top:var(--space-10)}
.accmx-addbox{margin-top:var(--space-8)}
.accmx-filter{width:100%}
.accmx-add-list{list-style:none;margin:var(--space-6) 0 0;padding:0;max-height:180px;overflow:auto;border:1px solid var(--line);border-radius:var(--radius-sm)}
.accmx-add-list li+li{border-top:1px solid var(--line-2)}
.accmx-add-item{display:block;width:100%;text-align:left;border:0;background:none;padding:var(--space-6) var(--space-10);cursor:pointer;font-size:var(--text-13);color:var(--ink)}
.accmx-add-item:hover,.accmx-add-item:focus-visible{background:var(--bg)}
.accmx-add-empty{margin:var(--space-6) 0 0}
.accmx-more summary{cursor:pointer;color:var(--orange-600);font-weight:600}
.accmx-notes{font-size:var(--text-13);margin-top:var(--space-14)}

/* ── Play page: Briefing documents card (PlayBriefing, 2026-10-05) ── */
.playbrief{margin-bottom:var(--space-12)}
.playbrief-h{margin:0;font-size:var(--text-14)}
.playbrief-add{margin-top:var(--space-10);display:grid;gap:var(--space-8);max-width:420px}
.playbrief-add[hidden]{display:none}
.playbrief-file{display:block;font-size:var(--text-12);color:var(--muted)}
.playbrief-tag{white-space:nowrap}

/* ---- Speed page (Settings → Speed, nexus2/views_speed.py, 5 Oct 2026) -------------------
   Three-colour "where the time went" bar: database / outside services / Python. Segment
   widths are data (a percentage per row), carried as data-w="0..100" in 5% steps. */
.speed-bar{display:flex;height:8px;border-radius:999px;overflow:hidden;background:var(--line-2);min-width:110px}
.speed-bar .db{background:var(--purple)}
.speed-bar .api{background:var(--orange)}
.speed-bar .py{background:var(--slate)}
.speed-bar span{flex:0 0 auto;width:0}
/* Width ladder in 5% steps (the `speed_step` filter snaps the value) — a data-driven width
   without an inline style attribute. */
.speed-bar [data-w="0"]{width:0%}.speed-bar [data-w="5"]{width:5%}.speed-bar [data-w="10"]{width:10%}.speed-bar [data-w="15"]{width:15%}.speed-bar [data-w="20"]{width:20%}.speed-bar [data-w="25"]{width:25%}.speed-bar [data-w="30"]{width:30%}.speed-bar [data-w="35"]{width:35%}.speed-bar [data-w="40"]{width:40%}.speed-bar [data-w="45"]{width:45%}.speed-bar [data-w="50"]{width:50%}.speed-bar [data-w="55"]{width:55%}.speed-bar [data-w="60"]{width:60%}.speed-bar [data-w="65"]{width:65%}.speed-bar [data-w="70"]{width:70%}.speed-bar [data-w="75"]{width:75%}.speed-bar [data-w="80"]{width:80%}.speed-bar [data-w="85"]{width:85%}.speed-bar [data-w="90"]{width:90%}.speed-bar [data-w="95"]{width:95%}.speed-bar [data-w="100"]{width:100%}
.speed-note-pad{padding:0 var(--space-14) var(--space-12)}
.speed-note-pad-top{padding:var(--space-8) var(--space-14) var(--space-12)}
.speed-key{display:flex;gap:var(--space-14);font-size:var(--text-12);color:var(--muted);margin-top:var(--space-6)}
.speed-key i{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:var(--space-4);vertical-align:-1px}
.speed-key .db i{background:var(--purple)}.speed-key .api i{background:var(--orange)}.speed-key .py i{background:var(--slate)}
.speed-chart{height:260px;position:relative}
.speed-note{font-size:var(--text-13);color:var(--muted);line-height:1.5;margin-top:var(--space-8)}
.speed-grid2{grid-template-columns:1fr 1fr}
@media (max-width:760px){.speed-grid2{grid-template-columns:1fr}.kpis.speed-kpis{grid-template-columns:1fr 1fr}}

/* ---- Server health (Settings → Server health, nexus2/views_health.py, 19 Oct 2026) --------
   Shares the Speed page's chart box, bar and width ladder; these add the disk fill colours,
   the "busy" number colour, the connection-test row and the masked query list. */
.speed-bar .disk{background:var(--slate)}
.speed-bar .disk.full{background:var(--bad)}
.kpi .num.health-busy{color:var(--bad)}
.card-head.health-head{flex-wrap:wrap;gap:var(--space-4) var(--space-12)}
.card-head.health-head h3{white-space:nowrap}
.health-ping{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-12);padding:var(--space-14) var(--space-16)}
.health-ping-out{font-size:var(--text-14);color:var(--ink)}
.health-forecast{font-size:var(--text-14);font-weight:600;color:var(--ink);margin-top:var(--space-12)}
.health-busy-row>summary{cursor:pointer;list-style:none;display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);
  padding:var(--space-12) var(--space-16);border-bottom:1px solid var(--line);font-size:var(--text-13)}
.health-busy-row>summary::-webkit-details-marker{display:none}
.health-q{list-style:none;margin:0;padding:var(--space-8) var(--space-16) var(--space-12);background:var(--surface-2);border-bottom:1px solid var(--line)}
.health-q li{padding:var(--space-6) 0;font-size:var(--text-12);color:var(--muted)}
.health-q code{display:block;font-family:ui-monospace,monospace;font-size:var(--text-12);color:var(--ink);white-space:pre-wrap;word-break:break-word;margin-top:var(--space-2)}
/* Live (20 Oct 2026): the topbar's LIVE indicator — a green dot that pulses while readings
   arrive, amber when the last one is over 10 s old or the tab is hidden, red when contact is
   lost (the script sets is-stale / is-paused / is-lost) — the rolling chart and the traffic line. */
.health-live{display:inline-flex;align-items:center;gap:var(--space-6);flex:0 0 auto;padding:var(--space-4) var(--space-10);
  border:1px solid var(--line);border-radius:999px;background:var(--card);font-size:var(--text-12);color:var(--muted);white-space:nowrap}
.health-live-dot{position:relative;width:8px;height:8px;border-radius:50%;background:var(--good);flex:0 0 auto}
.health-live-dot::after{content:"";position:absolute;inset:0;border-radius:50%;background:var(--good);animation:health-live-pulse 1.6s ease-out infinite}
@keyframes health-live-pulse{0%{transform:scale(1);opacity:.7}100%{transform:scale(2.6);opacity:0}}
.health-live-word{font-weight:700;letter-spacing:var(--caps-track);text-transform:uppercase;color:var(--good)}
.health-live.is-stale .health-live-dot,.health-live.is-paused .health-live-dot{background:var(--amber-600)}
.health-live.is-stale .health-live-word,.health-live.is-paused .health-live-word{color:var(--amber-600)}
.health-live.is-lost .health-live-dot{background:var(--bad)}
.health-live.is-lost .health-live-word,.health-live.is-lost .health-live-age{color:var(--bad)}
.health-live.is-stale .health-live-dot::after,.health-live.is-paused .health-live-dot::after,.health-live.is-lost .health-live-dot::after{animation:none;opacity:0}
@media (prefers-reduced-motion:reduce){.health-live-dot::after{animation:none;opacity:0}}
@media (max-width:600px){.health-live{padding:var(--space-2) var(--space-8)}}
.speed-chart.health-live-chart{height:220px}
.health-traffic{font-size:var(--text-14);color:var(--ink);margin-top:var(--space-12)}
.health-slow{color:var(--bad);font-weight:600}
.health-history-head{font-size:var(--text-16);margin:var(--space-12) 0 0;display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-4) var(--space-10)}
.health-history-head .muted{font-size:var(--text-13);font-weight:400}

/* ---- Inline-style sweep, 6 Oct 2026 (SMS log, What's new, Speed) -------------------------
   Semantic classes for things several pages do: a card that holds only a table, a strip of
   status pills, a folded rules note, a muted second line under a cell, a metadata line. */
.card.pad-tbl{padding:var(--space-4) 0}
.status-strip{display:flex;flex-wrap:wrap;gap:var(--space-10);align-items:center}
.rules-note{font-size:var(--text-13);margin-top:var(--space-10);line-height:1.5}
.rules-note summary{cursor:pointer}
.rules-note>div{margin-top:var(--space-4)}
.sub-line{font-size:var(--text-11);margin-top:var(--space-4)}
.meta{font-size:var(--text-12);color:var(--muted)}
.cell-13{font-size:var(--text-13)}

/* ---- Cross-job casting matrix (client_casting_matrix.html, 6 Oct 2026) --------------------
   One client's current jobs as one sheet: Character | (Part) | Stream/job | Person, then a
   column per session date with the header text turned on its side so many weeks fit. The
   Character, Part and Stream labels are drawn ONCE per group with a rowspan (merged down the
   rows they cover); a heavier rule opens each character group, a lighter one each stream.
   The name columns stay put while the dates scroll (desktop only — on a phone they would fill
   the screen). Green = confirmed, amber = any other on-the-job status, red "!" = the same
   person on two rows that day. The last row of a stream is "To Be Cast": grey, italic, "?"
   circle (same convention as _booking_team_row.html) with a per-date count of open slots —
   "nobody yet", a different state from amber "someone, not confirmed". */
.xm-intro{margin-bottom:var(--space-12)}
.xm-intro .legend{margin:var(--space-8) 0 0}
.xm-key{display:inline-block;width:12px;height:12px;border-radius:3px}
.xm-key.ok{background:var(--good)}
.xm-key.pending{background:var(--warn)}
.xm-clash-note{margin-top:var(--space-8);color:var(--bad);font-weight:600;font-size:var(--text-13)}
.xm-open-note{margin-top:var(--space-8);color:var(--slate);font-weight:600;font-size:var(--text-13)}
.xm-wrap{overflow:auto;max-height:calc(100vh - var(--header-h) - 160px);background:var(--card);
  border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow)}
.xm{border-collapse:separate;border-spacing:0;width:max-content;font-size:var(--text-13)}
.xm th,.xm td{border-bottom:1px solid var(--line-2);border-right:1px solid var(--line-2);padding:var(--space-6) var(--space-8)}
.xm thead th{position:sticky;top:0;z-index:2;background:var(--card);color:var(--muted);font-size:var(--text-11);
  text-transform:uppercase;letter-spacing:var(--caps-track);text-align:left;vertical-align:bottom;border-bottom:1px solid var(--line);font-weight:600}
.xm th.xm-date{text-transform:none;letter-spacing:0;padding:var(--space-8) var(--space-2);width:30px;min-width:30px;text-align:center}
.xm th.xm-date span{writing-mode:vertical-rl;transform:rotate(180deg);white-space:nowrap;display:inline-block;color:var(--slate)}
.xm th.xm-date.today span{color:var(--orange-600);font-weight:700}
.xm th.xm-date.wk-start,.xm td.xm-cell.wk-start{border-left:2px solid var(--line)}
.xm .xm-char,.xm .xm-part,.xm .xm-job,.xm .xm-who{position:sticky;background:var(--card);z-index:1;vertical-align:top}
.xm thead .xm-char,.xm thead .xm-part,.xm thead .xm-job,.xm thead .xm-who{z-index:3}
.xm .xm-char{left:0;width:170px;min-width:170px;max-width:170px;font-weight:600}
.xm .xm-job{left:170px;width:190px;min-width:190px;max-width:190px}
.xm .xm-who{left:360px;width:160px;min-width:160px;max-width:160px;border-right:1px solid var(--line)}
.xm .xm-job small{display:block;color:var(--muted);font-size:var(--text-11)}
.xm .xm-job small.xm-full{color:var(--good);font-style:italic}
/* With a Part column: narrower name columns so the sticky block stays about the same width. */
.xm.has-parts .xm-char{width:150px;min-width:150px;max-width:150px}
.xm .xm-part{left:150px;width:60px;min-width:60px;max-width:60px;white-space:nowrap;color:var(--slate)}
.xm.has-parts .xm-job{left:210px;width:180px;min-width:180px;max-width:180px}
.xm.has-parts .xm-who{left:390px;width:150px;min-width:150px;max-width:150px}
/* Group rules: rowspan cells sit in the group's first row, so the rule lands on them too. */
.xm tbody tr.xm-gstart>td{border-top:2px solid var(--line)}
.xm tbody tr:first-child>td{border-top:0}
.xm tbody tr.xm-sstart>td{border-top:1px solid var(--line)}
/* A character can span 80+ rows; its label rides down with the scroll, just under the
   rotated date header (~90px tall), until the group ends. */
.xm .xm-char .xm-glabel{display:block;position:sticky;top:100px}
.xm tr.xm-open .xm-who .person{display:inline-flex;align-items:center;gap:var(--space-6)}
.xm tr.xm-open .xm-who .person-hs{vertical-align:middle}
.xm td.xm-cell.tbc{background:var(--bg)}
.xm-tbc-n{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 var(--space-4);
  border-radius:9px;background:var(--tab-idle);color:var(--muted);box-shadow:0 0 0 1px var(--grey);
  font-size:var(--text-11);font-weight:700;font-style:italic;line-height:1}
.xm td a{color:inherit;text-decoration:none}
.xm td a:hover{color:var(--orange-600)}
.xm td.xm-cell{padding:0;width:30px;min-width:30px;text-align:center;vertical-align:middle;position:relative;font-weight:700}
.xm td.xm-cell.ok{background:var(--good);color:var(--white)}
.xm td.xm-cell.pending{background:var(--warn);color:var(--ink)}
.xm td.xm-cell.clash{box-shadow:inset 0 0 0 2px var(--bad)}
.xm-flag{display:inline-flex;align-items:center;justify-content:center;width:14px;height:14px;border-radius:50%;
  background:var(--bad);color:var(--white);font-size:var(--text-10);font-weight:700;line-height:1}
.xm td.xm-cell .xm-flag{position:absolute;top:1px;right:1px}
.xm tbody tr:hover td{border-bottom-color:var(--line)}
@media (max-width:760px){
  .xm .xm-char,.xm .xm-part,.xm .xm-job,.xm .xm-who{position:static}
  .xm thead .xm-char,.xm thead .xm-part,.xm thead .xm-job,.xm thead .xm-who{position:sticky}
}
@media print{
  .xm-wrap{max-height:none;overflow:visible;box-shadow:none}
  .xm td.xm-cell.ok,.xm td.xm-cell.pending,.xm td.xm-cell.tbc,.xm-tbc-n,.xm-flag,.xm-key{-webkit-print-color-adjust:exact;print-color-adjust:exact}
}

/* "Something's off? Tell the office" on the call-time check-in (7 Oct 2026). */
.checkin-problem{margin-top:var(--space-12)}
.checkin-problem textarea{width:100%;margin:var(--space-8) 0}

/* "Tell Claude what to do differently" on the daily + cumulative client report pages, and
   the rules page it feeds (nexus2/report_rules.py, 8 Oct 2026). */
.rrule{margin-top:var(--space-14)}
.rrule-head{font-size:var(--text-13);font-weight:600;color:var(--muted);text-transform:uppercase;
  letter-spacing:.04em;margin-bottom:var(--space-8)}
.rrule-form textarea{margin-bottom:var(--space-8)}
.rrule-row{display:flex;gap:var(--space-8);align-items:center;flex-wrap:wrap}
.rrule-row select{width:auto;flex:1 1 140px}
.rrule-list{margin-top:var(--space-12);font-size:var(--text-12)}
.rrule-item{border-top:1px solid var(--line);padding:var(--space-6) 0}
.rrule-kind{color:var(--muted);font-size:var(--text-11)}

/* Cumulative feedback report editor (8 Oct 2026): office-only flags. A delivery with no
   facilitator head-count is tinted red with a one-line chase note; the client PDF never shows
   either. .cum-flag spaces the red notes (stale numbers, missing head-counts) in the form. */
.cum-flag{margin-top:var(--space-12)}
.cum-gap{background:var(--bad-50);box-shadow:inset 3px 0 0 var(--bad)}
.cum-gap > *{padding-left:var(--space-8)}
.cum-gap-note{color:var(--bad);font-weight:600;font-size:var(--text-12)}
/* Cumulative report list: one row = the report link + (drafts only) a Delete button. */
.cum-row{display:flex;align-items:center;gap:var(--space-10);border-top:1px solid var(--line);margin-top:var(--space-10)}
.cum-row-link{display:block;flex:1;min-width:0;padding:var(--space-10) 0;text-decoration:none;color:inherit}

/* Cumulative report — the "build a report" page and the job picker (8 Oct 2026). Step 1 (client,
   dates, "Show the jobs") is always on screen; step 2 is one ticked row per job. .cnew-job is
   reused, compact, in the report page's "Jobs in this report" card. An unticked job fades so
   what is left out reads at a glance. */
.cnew{max-width:780px}
.cnew-step{margin-bottom:var(--space-14)}
.cnew-h{font-size:var(--text-16);font-weight:700;margin-bottom:var(--space-4)}
.cnew-scope{display:flex;gap:var(--space-12);flex-wrap:wrap;align-items:flex-end}
.cnew-f{flex:1 1 150px;min-width:0}
.cnew-f.wide{flex:2 1 240px}
.cnew-scope .btn{white-space:nowrap}
.cnew-tickbar{display:flex;gap:var(--space-8);align-items:center;flex-wrap:wrap;margin:var(--space-8) 0}
.cnew-total{font-size:var(--text-12)}
.cnew-jobs{border-top:1px solid var(--line)}
.cnew-job{display:flex;gap:var(--space-10);align-items:flex-start;padding:var(--space-8) 0;
  border-bottom:1px solid var(--line);cursor:pointer;font-size:var(--text-13)}
.cnew-job input{margin-top:var(--space-3);flex:none}
.cnew-job-body{display:flex;flex-direction:column;gap:var(--space-2);min-width:0}
.cnew-job-meta{color:var(--muted);font-size:var(--text-12)}
.cnew-job:has(input:not(:checked)){opacity:.55}
.cnew-jobs.compact .cnew-job{padding:var(--space-6) 0;font-size:var(--text-12)}
.cnew-check{display:block;font-size:var(--text-13);padding:var(--space-2) 0}
.cnew-go{margin-top:var(--space-18);display:flex;gap:var(--space-8);align-items:center;flex-wrap:wrap}
.cnew-go .muted,.cnew-empty{font-size:var(--text-12)}
.cnew-side{margin-top:var(--space-14)}

/* Questions participants ask a character (CharacterQuestion, Oct 2026). Actor side: the
   engagement page's Resources (_actor_char_questions.html) + the Scripts page. Office side:
   the character page card (_character_questions.html). */
.cq-card{margin-bottom:var(--space-14)}
.cq-block+.cq-block{border-top:1px solid var(--line);margin-top:var(--space-12);padding-top:var(--space-12)}
.cq-head{font-weight:700;font-size:var(--text-14)}
.cq-hint{margin:var(--space-2) 0 var(--space-6);font-size:var(--text-12);color:var(--muted)}
.cq-list{margin:0;padding-left:var(--space-18);font-size:var(--text-13);line-height:1.45}
.cq-list li+li{margin-top:var(--space-4)}
.cq-more>summary{cursor:pointer;font-size:var(--text-12);color:var(--muted);margin-top:var(--space-6)}
.cq-add{display:grid;gap:var(--space-6);margin-top:var(--space-10);max-width:520px}
.cq-add[hidden]{display:none}
.cq-label{font-size:var(--text-12);font-weight:600}
.cq-sent{font-size:var(--text-12);color:var(--good)}
.cq-scripts{margin-top:var(--space-6)}
.cq-scripts .cq-hint{margin-top:var(--space-4)}
.cq-office{margin-top:var(--space-14)}
.cq-title{margin:0;font-size:var(--text-14)}
.cq-sub{font-size:var(--text-11);font-weight:600;letter-spacing:var(--caps-track);text-transform:uppercase;
  color:var(--muted);margin:var(--space-14) 0 var(--space-4)}
.cq-row{display:flex;gap:var(--space-10);align-items:flex-start;padding:var(--space-6) 0;
  border-bottom:1px solid var(--line-2);font-size:var(--text-13)}
.cq-text{flex:1;min-width:0}
.cq-text-edit{width:100%;box-sizing:border-box;font-size:var(--text-13);padding:var(--space-4) var(--space-6)}
.cq-meta{font-size:var(--text-11);color:var(--muted);margin-top:var(--space-2)}
.cq-acts{display:flex;gap:var(--space-6);flex:none}
.cq-icon{display:inline-flex;align-items:center;gap:var(--space-4)}
.cq-icon .ic{width:15px;height:15px}
.cq-none{font-size:var(--text-12);padding:var(--space-4) 0}
/* Screen-reader-only label beside each icon button, scoped here so the two-letter class
   name isn't claimed site-wide (same technique as .run-rowtoggle .vh / .cqf-vh). */
.cq-vh{position:absolute;width:1px;height:1px;margin:calc(-1 * var(--space-1));padding:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
.cq-removed{margin-top:var(--space-12)}
.cq-removed>summary{cursor:pointer;font-size:var(--text-12)}
.cq-pick{border:0;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:var(--space-4) var(--space-14)}
.cq-pick legend{padding:0;margin-bottom:var(--space-4);width:100%}
.cq-pick-item{display:inline-flex;align-items:center;gap:var(--space-6);font-size:var(--text-13);cursor:pointer}

/* Call sheet traffic lights (Jacob, 8 Oct 2026) — Flight / Food / Parking / Accommodation
   on every session card. Green = provided, amber = being arranged, red = we know it is NOT
   provided, grey = not known / not applicable. The word sits under each icon so a phone
   (no hover) and a colour-blind reader both get the answer; "no" also strikes the word.
   Logic: nexus2/callsheet_icons.py. */
.cs-lights{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-10);margin-top:var(--space-8)}
.cs-light{display:flex;flex-direction:column;align-items:center;gap:var(--space-2);min-width:44px;
  font-size:var(--text-10);font-weight:700;letter-spacing:.02em;text-transform:uppercase;line-height:1.2}
.cs-light .cs-light-ic{display:flex;align-items:center;justify-content:center;width:34px;height:34px;
  border-radius:999px;border:2px solid currentColor}
.cs-light .cs-light-ic svg.ic{width:18px;height:18px}
.cs-light.is-yes{color:var(--good)}
.cs-light.is-yes .cs-light-ic{background:var(--good-50)}
.cs-light.is-pending{color:var(--amber-600)}
.cs-light.is-pending .cs-light-ic{background:var(--amber-50)}
.cs-light.is-no{color:var(--bad)}
.cs-light.is-no .cs-light-ic{background:var(--bad-50)}
.cs-light.is-no .cs-light-word{text-decoration:line-through}
.cs-light.is-unknown{color:var(--grey);opacity:.55}
.cs-light.is-unknown .cs-light-ic{border-style:dashed}
.cs-light .cs-light-state{position:absolute;width:1px;height:1px;margin:calc(-1 * var(--space-1));padding:0;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* ── Meeting form (8 Oct 2026 rebuild — _meeting_form_inner.html, _dt_field.html) ── */
.mtg-card{padding:var(--space-16);max-width:640px}
.mtg-note{margin-bottom:var(--space-12)}
.msg.info{background:var(--bg);color:var(--ink);border:1px solid var(--line)}
.mtg-hint{font-weight:400}
.mtg-where{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8) var(--space-18)}
.mtg-where select{max-width:300px}
.mtg-teams{font-size:var(--text-13);color:var(--orange-700)}
.mtg-more{grid-column:1/-1;border-top:1px solid var(--line);padding-top:var(--space-10)}
.mtg-more>summary{cursor:pointer;font-size:var(--text-13);font-weight:600;color:var(--orange-700)}
.mtg-rec{margin-top:var(--space-10);display:grid;gap:var(--space-10);
  grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}
.mtg-rec-wide{grid-column:1/-1}
.mtg-off{display:none}
.mtg-email-hint{font-size:var(--text-12);color:var(--muted);margin-top:var(--space-3)}
.mtg-quickadd{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);margin-top:var(--space-8)}
.mtg-quickadd input[type=text]{flex:1;min-width:140px;max-width:240px}
/* .mtg-quickadd sets display:flex AFTER .mtg-off and wins the tie, so the hidden
   "Save as a contact" row showed on every empty form (20 Oct 2026). */
.mtg-quickadd.mtg-off{display:none}
.mtg-cancel{margin-top:var(--space-18);padding-top:var(--space-14);border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;gap:var(--space-8);align-items:center}
.mtg-cancel-head{flex-basis:100%;font-size:var(--text-13);font-weight:600;color:var(--muted)}
.mtg-cancel select{width:auto}
.mtg-cancel input[type=text]{flex:1;min-width:200px}
.mtg-summary{display:grid;grid-template-columns:auto 1fr;gap:var(--space-8) var(--space-16);
  font-size:var(--text-14)}
.mtg-summary dt{color:var(--muted);font-weight:600}
.mtg-summary dd{margin:0;white-space:pre-line}
/* RSVP (20 Oct 2026, meeting_form.html): the invitee's Accept / Decline strip and the
   organiser's list of answers. */
.mtg-rsvp{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8)}
.mtg-rsvp-head{flex-basis:100%;font-size:var(--text-14)}
.mtg-rsvp form{display:inline}
.mtg-rsvp .btn[disabled]{cursor:default}
.mtg-answers{display:flex;flex-wrap:wrap;gap:var(--space-6) var(--space-14);font-size:var(--text-13);
  margin-bottom:var(--space-12)}
.mtg-answers-head{flex-basis:100%;color:var(--muted);font-weight:600}
.mtg-answer{display:inline-flex;align-items:center;gap:var(--space-6)}
.dt-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8)}
.dt-row .dt-pair-date{max-width:180px}
.dt-row .dt-time{width:auto;min-width:110px}
.dt-dur{font-size:var(--text-13)}
.dt-dur.dt-bad{color:var(--bad);font-weight:600}
/* The calendar's "+ Meeting" button (8 Oct 2026) — beside the view menu. A phone keeps the
   toolbar for the range and views only; "+ New meeting" is in the view menu there. */
.fc .fc-newMeeting-button{white-space:nowrap;font-size:var(--text-13);font-weight:600;
  background:var(--orange-700)!important;border-color:var(--orange-700)!important;color:var(--white)!important;
  margin-right:var(--space-6)!important}
.fc .fc-newMeeting-button:hover{background:var(--orange-600)!important}
@media (max-width:700px){.fc .fc-newMeeting-button{display:none!important}}
/* Session editor — "Parking for the team / catering" prompt under the venue (Greg, 8 Oct
   2026). Template: _svc_logistics_prompt.html; script in _session_editor.html. */
.svc-logi{margin:var(--space-6) 0 var(--space-10);display:flex;flex-direction:column;gap:var(--space-6)}
.svc-logi[hidden]{display:none}
.svc-logi-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);font-size:var(--text-13)}
.svc-logi-row[hidden],.svc-logi-acts[hidden],.svc-logi .btn[hidden]{display:none}
.svc-logi-msg{color:var(--muted)}
.svc-logi-msg.is-gap{color:var(--amber-600);font-weight:600;background:var(--amber-50);
  padding:var(--space-3) var(--space-8);border-radius:var(--radius-sm)}
.svc-logi-acts{display:inline-flex;flex-wrap:wrap;align-items:center;gap:var(--space-4)}
.svc-logi-lbl{color:var(--muted);font-size:var(--text-12);margin-left:var(--space-4)}
/* "Questions you were asked" — the no-sign-in form (character_questions_form.html). A
   standalone phone-first page. Its background is a token on purpose: this is not a login or
   error page, so the literal-hex rule for those does not apply. */
.cqf-body{background:var(--bg);margin:0;padding:var(--space-20) var(--space-14) var(--space-22);min-height:100vh;
  font-family:'Poppins',Arial,Helvetica,sans-serif;color:var(--ink)}
.cqf-wrap{max-width:560px;margin:0 auto}
.cqf-logo{display:block;height:34px;margin:var(--space-2) auto var(--space-20)}
.cqf-h1{font-size:var(--text-24);margin:0 0 var(--space-4);line-height:1.2}
.cqf-lead{font-size:var(--text-14);color:var(--muted);margin:0 0 var(--space-16);line-height:1.5}
.cqf-msg{font-size:var(--text-14);padding:var(--space-10) var(--space-12);border-radius:var(--radius-sm);margin:0 0 var(--space-14)}
.cqf-msg.ok{background:var(--good-50);color:var(--good)}
.cqf-msg.bad{background:var(--bad-50);color:var(--bad)}
.cqf-form{display:grid;gap:var(--space-10);background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius);padding:var(--space-16)}
.cqf-played{margin:0;font-size:var(--text-16)}
.cqf-change>summary{cursor:pointer;font-size:var(--text-13);color:var(--orange-700)}
.cqf-pick{margin-top:var(--space-8)}
.cqf-label{font-size:var(--text-14);font-weight:600}
.cqf-text{width:100%;box-sizing:border-box;font-size:var(--text-16);min-height:9em}
.cqf-go{justify-content:center;padding:var(--space-12) var(--space-16);font-size:var(--text-16)}
.cqf-already{margin-top:var(--space-14);background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius);padding:var(--space-10) var(--space-14)}
.cqf-already>summary{cursor:pointer;font-size:var(--text-14);font-weight:600}
.cqf-already .cq-list{margin-top:var(--space-8)}
.cqf-foot{font-size:var(--text-12);color:var(--muted);margin:var(--space-16) 0 0;line-height:1.5}

/* The Lead's bundled review page (character_questions_digest.html, 15 Oct 2026) — same
   standalone shell as the form above, one card per character, one row per question. Every
   row defaults to Keep (the radio below), so the page IS "approve all" the moment the one
   Save button is pressed with nothing touched. */
.cqf-group{margin:0 0 var(--space-14);background:var(--card);border:1px solid var(--line);
  border-radius:var(--radius);padding:var(--space-14)}
.cqf-group-head{font-size:var(--text-15);font-weight:600;margin:0 0 var(--space-10)}
.cqf-group-head .muted{font-weight:400}
.cqf-qrow{padding:var(--space-10) 0;border-top:1px solid var(--line)}
.cqf-group .cqf-qrow:first-of-type{border-top:none;padding-top:0}
.cqf-qtext{width:100%;box-sizing:border-box;font-size:var(--text-14);min-height:3em;
  margin:0 0 var(--space-6)}
.cqf-qactions{display:flex;gap:var(--space-10);font-size:var(--text-13);color:var(--slate-2)}
.cqf-act{display:flex;align-items:center;gap:var(--space-4);cursor:pointer;
  min-height:40px;padding:0 var(--space-10);border:1px solid var(--line);
  border-radius:var(--radius-sm);box-sizing:border-box}
.cqf-act .ic{width:18px;height:18px;flex:none}
.cqf-act input{margin:0}
.cqf-act-remove:has(input:checked){border-color:var(--bad);color:var(--bad);background:var(--bad-50)}
.cqf-act-edit{color:var(--muted)}
/* Screen-reader-only label beside each icon, scoped here so the two-letter class name
   isn't claimed site-wide (same technique as .run-rowtoggle .vh). */
.cqf-vh{position:absolute;width:1px;height:1px;margin:calc(-1 * var(--space-1));padding:0;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
.cq-change{margin-top:var(--space-4)}
.cq-change>summary{cursor:pointer;font-size:var(--text-12);color:var(--orange-700)}
/* Call sheet: "Typical questions for <character>", inside the Resources row's .vf-res
   stack, right under the script (_session_card.html). */
.cq-typical{font-size:var(--text-13)}
.cq-typical>summary{cursor:pointer;font-weight:600}
.cq-typical .cq-list{margin-top:var(--space-6)}
/* Job close-out (closeout.html + _closeout_row.html, meeting 12 Oct 2026): past sessions
   waiting for approval. */
.closeout-lead{font-size:var(--text-13);margin:0 0 var(--space-12);line-height:1.5}
/* Greg, 12 Oct 2026 (#767): the table fills the screen right of the menu, like the bookings list. */
.content:has(> .closeout-lead){max-width:none;width:calc(100vw - var(--nav-w))}
.closeout-card{overflow-x:auto}
.closeout-sum{padding:var(--space-10) var(--space-14) 0}
.closeout-tbl tr.is-cancelled td{background:var(--bad-50)}
.closeout-tbl tr.is-approved td{opacity:.55}
.closeout-approve{display:flex;align-items:center;gap:var(--space-8);margin:0;white-space:nowrap}
.closeout-older{margin-top:var(--space-16);font-size:var(--text-12)}
.closeout-older>summary{cursor:pointer;color:var(--muted)}
.closeout-older-form{display:flex;align-items:center;gap:var(--space-8);flex-wrap:wrap;margin-top:var(--space-8)}
.closeout-older-form .fld{width:auto}
/* Each session is its own <tbody> (_closeout_row.html): a summary row, then a cast detail
   row opened by the actors button (closeout.js). `.is-open`, not [hidden]: on a phone
   `table.mtbl tr{display:block}` would beat the hidden attribute. */
.closeout-lbl{display:none}
.closeout-blocked{display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-4)}
.closeout-cast-btn{background:none;border:none;padding:0;font:inherit;color:var(--orange-700);cursor:pointer;white-space:nowrap;text-align:left}
.closeout-cast-btn:hover{text-decoration:underline}
.closeout-tbl tr.closeout-detail{display:none}
.closeout-tbl tr.closeout-detail.is-open{display:table-row}
.closeout-tbl tr.closeout-detail td,.closeout-tbl tr.closeout-detail:hover td{background:var(--bg);padding-top:var(--space-8)}
.closeout-people{display:flex;flex-direction:column;gap:var(--space-4);font-size:var(--text-13)}
.closeout-person{display:grid;grid-template-columns:minmax(10em,1.4fr) minmax(9em,1fr) 4.5em minmax(8em,1fr) minmax(12em,1.6fr);
  gap:var(--space-10);align-items:center;margin:0;padding:var(--space-4) 0;border-bottom:1px solid var(--line-2)}
.closeout-person-head{border-bottom:1px solid var(--line);text-transform:uppercase;letter-spacing:.05em;font-size:var(--text-11)}
.closeout-person.is-missing .closeout-pname{color:var(--bad);font-weight:600}
.closeout-person.is-dna .closeout-pname,.closeout-person.is-dna .closeout-phours{color:var(--muted)}
.closeout-ptimes,.closeout-pfee,.closeout-pact{display:flex;align-items:center;gap:var(--space-6);flex-wrap:wrap}
.closeout-ptimes{flex-wrap:nowrap}
.closeout-time{width:4.5em}
.closeout-fee{width:6.5em}
.closeout-pdna{display:inline-flex;align-items:center;gap:var(--space-4);white-space:nowrap}
.closeout-open{display:inline-block;margin-top:var(--space-10);color:var(--orange-700)}
.closeout-money{margin-top:var(--space-4)}
.closeout-stale{margin:0 0 var(--space-8);padding:var(--space-8) var(--space-12);border-radius:var(--radius-sm);
  background:var(--amber-50);color:var(--amber-600);font-size:var(--text-13);font-weight:600}
@media (max-width:600px){
  .closeout-tbl thead{display:none}
  .closeout-lbl{display:inline;float:left;color:var(--muted);font-size:var(--text-12)}
  table.mtbl.closeout-tbl tr.closeout-detail{display:none}
  table.mtbl.closeout-tbl tr.closeout-detail.is-open{display:block}
  .closeout-person{grid-template-columns:1fr 1fr}
  .closeout-person-head{display:none}
  .closeout-pname,.closeout-pact{grid-column:1 / -1}
}

/* Xero invoices page (/xero/invoices/): "Send to Xero as draft" (12 Oct 2026). The confirm
   is a <details> that opens a one-line form under the button — no JS confirm(), no extra
   page. .xsend-bar = one invoice's Xero status row; .xsend-all = the "Send all ready" bar. */
.xsend-bar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6) var(--space-12);
  padding:var(--space-6) var(--space-14);font-size:var(--text-12);border-top:1px solid var(--line)}
.xsend-all{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8) var(--space-12);
  margin:0 0 var(--space-16)}
.xsend{display:inline-block}
.xsend>summary{list-style:none;cursor:pointer}
.xsend>summary::-webkit-details-marker{display:none}
.xsend-confirm{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);
  margin-top:var(--space-6);padding:var(--space-8) var(--space-10);background:var(--amber-50);
  border-radius:var(--radius-sm);font-size:var(--text-12)}
.xsend-h{font-size:var(--text-16);margin:var(--space-22) 0 var(--space-4)}
.xsend-lines{margin:0;padding:var(--space-6) var(--space-14) var(--space-10) var(--space-22);
  font-size:var(--text-13)}
/* Link a Nexus client to a Xero contact (same page). Native <datalist> type-ahead. */
.xlink-form{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6) var(--space-8)}
.xlink-label{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);font-size:var(--text-12)}
.xlink-label input{min-width:220px;padding:var(--space-4) var(--space-8);border:1px solid var(--line);
  border-radius:var(--radius-sm);font-size:var(--text-13)}
.xlink-change{cursor:pointer;color:var(--orange-700);list-style:none}
.xlink-change::-webkit-details-marker{display:none}

/* Payroll (payroll.html, payroll_statement.html, my_pay_statements.html, _payroll_rows.html,
   _pay_statement.html — 12 Oct 2026). Approved pay -> one "In Room" line per person on the
   DRAFT Xero pay run, and each person's itemised pay statement. */
.prl-head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:var(--space-8) var(--space-16);margin:0 0 var(--space-14)}
.prl-xero{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6)}
.prl-alert{margin-bottom:var(--space-16);border-left:3px solid var(--amber-600)}
.prl-lead{font-size:var(--text-13);margin:0 0 var(--space-12);line-height:1.5}
.prl-tblwrap{overflow-x:auto}
.prl-tbl{font-size:var(--text-13)}
.prl-tbl td,.prl-tbl th{padding:var(--space-6) var(--space-8)}
.prl-why{color:var(--bad)}
.prl-sum{padding:var(--space-10) var(--space-14);border-bottom:1px solid var(--line)}
.prl-person{border-bottom:1px solid var(--line-2)}
.prl-person:last-child{border-bottom:0}
.prl-person summary{display:flex;align-items:center;gap:var(--space-10);padding:var(--space-10) var(--space-14);
  cursor:pointer;list-style:none}
.prl-person summary::-webkit-details-marker{display:none}
.prl-tick{width:var(--space-18);flex:none}
.prl-name{font-weight:600}
.prl-state{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);flex:1;min-width:0}
.prl-total{font-weight:700;white-space:nowrap}
.prl-body{padding:0 var(--space-14) var(--space-14)}
.prl-adj{margin:var(--space-6) 0;font-size:var(--text-13)}
.prl-confirm{margin:0 0 var(--space-20)}
.prl-confirm summary{list-style:none;display:inline-flex}
.prl-confirm summary::-webkit-details-marker{display:none}
.prl-confirm[open] summary{margin-bottom:var(--space-10)}
.prl-links{list-style:none;margin:0;padding:0}
.prl-links li{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6) var(--space-12);
  padding:var(--space-8) 0;border-bottom:1px solid var(--line-2)}
.prl-links li:last-child{border-bottom:0}
.prl-linkform{display:flex;gap:var(--space-6);align-items:center;flex-wrap:wrap}
.prl-linkform .fld{min-width:240px}
.prl-statement .pay-tbl{font-size:var(--text-13)}
.prl-st-head{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;
  gap:var(--space-6);margin-bottom:var(--space-8)}
.prl-st-total td{font-weight:700;border-top:2px solid var(--line)}
.prl-st-office{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);margin:0 0 var(--space-6)}
@media (max-width:600px){
  .prl-linkform .fld{min-width:0;flex:1}
  .prl-person summary{flex-wrap:wrap}
  .pay-tbl td.prl-hours{order:5;flex:0 0 100%;margin-top:var(--space-2);color:var(--muted);text-align:left}
  .prl-st-foot,.prl-st-foot tr{display:flex;width:100%;justify-content:space-between}
  .prl-st-foot tr{border-top:2px solid var(--line)}
  .prl-st-foot td{display:block;padding:var(--space-8) 0 0;border:0}
}
/* Payroll (12 Oct 2026): "Pay statements" beside the pay-period stepper, own call sheet only. */
.csh-paylink{font-size:var(--text-13);font-weight:600;color:var(--orange-700);white-space:nowrap}
/* Weekend / evening / public-holiday loadings (12 Oct 2026): Settings → Loadings
   (loadings_settings.html, _loadings_holidays.html), the Loading box on a session's
   invoicing row, the loading pill + hints on Job close-out, the loading line on the
   Xero invoices page. Maths: nexus2/loadings.py. */
.loadings-card{margin-bottom:var(--space-16)}
.loadings-head{padding:0 0 var(--space-10);margin-bottom:var(--space-10);gap:var(--space-10);flex-wrap:wrap}
.loadings-lead{font-size:var(--text-13);line-height:1.5;margin:0 0 var(--space-10);max-width:72ch}
ul.loadings-lead{padding-left:var(--space-18)}
.loadings-tblwrap{overflow-x:auto}
.loadings-tbl td{vertical-align:middle}
.loadings-num{width:6.5em;text-align:right}
.loadings-add{margin:0 0 var(--space-12)}
.loadings-addform{display:flex;flex-wrap:wrap;align-items:end;gap:var(--space-10);margin-top:var(--space-10)}
.loadings-addform label{display:flex;flex-direction:column;gap:var(--space-4);font-size:var(--text-12);font-weight:600;color:var(--muted)}
.loadings-addform .fld{width:auto}
.loadings-year{margin-top:var(--space-12)}
.loadings-year h4{font-size:var(--text-13);margin:0 0 var(--space-4)}
.loadings-hols td{padding:var(--space-6) var(--space-10);vertical-align:middle}
/* Named hourly rates (Settings → Loadings → Actor rates, 13 Oct 2026). */
.hrate-tbl{margin-bottom:var(--space-12)}
.hrate-name{width:16em}
.hrate-order{width:4em;text-align:right}
.hrate-off .hrate-name{color:var(--muted);text-decoration:line-through}
/* The fee box's rate pick list (session editor, 13 Oct 2026). Built on .addr-suggest (the
   list hangs off <body>, positioned by the script); wider than the 76px fee box. */
.fee-rate-list{min-width:15rem}
.fee-rate-row{display:flex;justify-content:space-between;gap:var(--space-12)}
.fee-rate-amt{color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}
.sess-pricing .sp-loading-total{align-self:end}
.closeout-loading{margin-top:var(--space-4)}
.closeout-undermin{display:block;color:var(--bad);font-size:var(--text-11);font-weight:600;white-space:normal}
.closeout-loadhint{flex-basis:100%;color:var(--muted);font-size:var(--text-11)}
.xi-tbl tr.xi-loading td{border-top:0;padding-top:0;color:var(--muted)}
/* "Bill to" picker: the client's linked Xero contacts, one radio per line. */
.xlink-list{display:flex;flex-direction:column;gap:var(--space-4);margin:0;
  padding:var(--space-6) var(--space-10);border:1px solid var(--line);border-radius:var(--radius-sm);
  font-size:var(--text-13)}
.xlink-list legend{font-size:var(--text-12);font-weight:600;padding:0 var(--space-4)}
/* "Create in Xero" — a new contact, prefilled from Nexus, corrected before it is made. */
.xcreate-form{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));
  gap:var(--space-6) var(--space-10);margin-top:var(--space-6);padding:var(--space-8) var(--space-10);
  border:1px solid var(--line);border-radius:var(--radius-sm)}
.xcreate-warn{grid-column:1/-1;margin:0;padding:var(--space-6) var(--space-8);
  background:var(--amber-50);color:var(--amber-600);border-radius:var(--radius-sm);font-size:var(--text-12)}
/* Job files card on the booking page (nexus2.JobFile, 12 Oct 2026): files a client sent,
   uploaded onto the job. .jobfile = one row (name, Print ×N pill, size · who, when, bin);
   .jobfiles-form = the upload box, which is also a .dropzone. */
.jobfile{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-4) var(--space-8);
  font-size:var(--text-13);padding:var(--space-4) 0}
.jobfile-name{flex:1 1 220px;min-width:0;color:var(--orange-700);text-decoration:none;
  overflow-wrap:anywhere}
.jobfile-name:hover{text-decoration:underline}
.jobfile-meta{font-size:var(--text-12);white-space:nowrap}
/* Pill, size · who and the bin stay together, so the bin never drops onto a line alone. */
.jobfile-side{display:inline-flex;align-items:center;gap:var(--space-8);margin-left:auto}
.jobfile-del{display:inline;margin:0}
.jobfile-note{flex:1 1 100%;font-size:var(--text-12);color:var(--slate);padding-left:var(--space-22)}
.jobfiles-form{margin:var(--space-8) 0;padding:var(--space-8);border:1px solid var(--line);
  border-radius:var(--radius-sm)}
.jobfiles-file{width:100%}
.jobfiles-fields{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);
  margin-top:var(--space-8)}
.jobfiles-fields .fld{flex:1 1 180px;min-width:0}
.jobfiles-fields .fld.jobfiles-copies{flex:0 0 72px}
.jobfiles-print{display:inline-flex;align-items:center;gap:var(--space-4);font-size:var(--text-13);
  white-space:nowrap}
.jobfiles-due{font-size:var(--text-12);margin-top:var(--space-6)}

/* ---------- Contacts & roles row (_contact_role_row_inner.html) ----------
   Swept from 17 inline style= attributes. The .5px sizes stay as written (see the type-scale
   note above: .5 variants are real, not rounded onto the whole-pixel tokens). */
.crr-name{font-weight:600}
.crr-sub{font-size:var(--text-12)}
.crr-opt{display:inline-flex;align-items:center;gap:var(--space-8);font-size:var(--text-13);cursor:pointer}
.crr-opt.crr-primary{margin-bottom:var(--space-8)}
.crr-opt.crr-primary.crr-locked{cursor:default}
.crr-opt.crr-cc{margin-top:var(--space-8)}
.crr-opt.crr-role{gap:var(--space-5);white-space:nowrap}
.crr-tick{margin:0}
.crr-role-pick{display:flex;flex-wrap:wrap;gap:var(--space-5) var(--space-14);align-items:center}
.btn.crr-add-btn{font-size:var(--text-12);padding:var(--space-3) var(--space-9);white-space:nowrap}
.crr-held{margin-top:var(--space-6);display:flex;flex-wrap:wrap;gap:var(--space-5)}
.crr-unlink{margin-top:var(--space-6)}

/* ---------- Who played which character (play_character_history.html, Oct 2026) ----------
   Back-fills Engagement.character on older jobs, so the casting sheet's "Never played"
   pill stops flagging long-serving actors. */
.chist-intro{margin:0 0 var(--space-8);font-size:var(--text-14)}
.chist-sub{margin:0 0 var(--space-14);font-size:var(--text-13)}
.chist-section{margin:var(--space-16) 0 var(--space-8);font-size:var(--text-14)}
.card.chist-tablecard{padding:0;overflow:hidden}
.chist-table{width:100%;border-collapse:collapse}
.chist-num{text-align:right;white-space:nowrap}
.card.chist-card{padding:var(--space-14) var(--space-16);margin-bottom:var(--space-14)}
.chist-who{min-width:0}
/* Per-play page: one compact row per person (Review / Dismiss / Not in this play). */
.chist-people{list-style:none;margin:0;padding:0}
.chist-person{display:flex;align-items:center;justify-content:space-between;gap:var(--space-10);
  flex-wrap:wrap;padding:var(--space-10) var(--space-16);border-bottom:1px solid var(--line)}
.chist-person:last-child{border-bottom:0}
.chist-btns{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);margin:0}
.chist-dismissed{margin-top:var(--space-16);font-size:var(--text-13)}
.chist-dismissed summary{cursor:pointer}
.chist-dismissed .chist-people{margin-top:var(--space-8)}
.chist-dismissed .chist-person{padding:var(--space-6) 0}
/* The per-person pop-up (_chist_person.html in a .castdlg <dialog>, chistdlg.js). */
.castdlg.chistdlg{width:min(600px,calc(100vw - 32px))}
.chist-none{font-size:var(--text-13);margin:var(--space-10) 0 0}
.chist-error{color:var(--bad);font-size:var(--text-13);margin:var(--space-8) 0 0}
.chist-error:empty{display:none}
.chist-pop-foot{margin-top:var(--space-14)}
.chist-recorded{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);
  margin-top:var(--space-8);font-size:var(--text-13)}
.chist-jobs{list-style:none;margin:var(--space-10) 0 0;padding:0;border-top:1px solid var(--line)}
.chist-jobs li{display:flex;align-items:center;justify-content:space-between;gap:var(--space-8);
  padding:var(--space-6) 0;border-bottom:1px solid var(--line)}
.chist-job{display:flex;align-items:center;gap:var(--space-8);flex:1;min-width:0;margin:0;
  cursor:pointer;font-size:var(--text-13)}
.chist-date{white-space:nowrap;color:var(--muted);min-width:84px;flex:none}
.chist-job-main{min-width:0}
.chist-open{font-size:var(--text-12);white-space:nowrap}
.chist-actions{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-8);margin-top:var(--space-10)}
.chist-actions .fld{flex:0 1 260px;min-width:0}
/* The casting sheet / character form "Never played" pill, as a link to this page. */
a.chist-pill-link{text-decoration:none;color:inherit}
a.chist-pill-link:hover .pill{text-decoration:underline}

/* Delegation for one session (runsheet_session_who.html, 15 Oct 2026 run sheets
   meeting; called "Who's doing what" until Greg renamed it the same day).
   Opened from a QR on paper, so phone first: one column, big tap targets. The
   chips are real checkboxes (keyboard: Tab to one, Space to tick), drawn as pills. */
.who-intro{padding:var(--space-14) var(--space-16);margin-bottom:var(--space-12)}
.who-title{font-size:var(--text-16);margin:0 0 var(--space-4)}
.who-list{display:flex;flex-direction:column}
.who-row{display:flex;flex-direction:column;gap:var(--space-6)}
.who-row-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--space-4) var(--space-8)}
.who-clock{font-weight:700;font-variant-numeric:tabular-nums}
.who-topic{font-size:var(--text-14)}
.who-techs{display:flex;flex-wrap:wrap;gap:var(--space-6)}
.who-pick fieldset{border:0;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:var(--space-6)}
.who-legend{font-size:var(--text-12);color:var(--muted)}
.who-chip{position:relative;display:inline-flex;cursor:pointer}
.who-chip input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
/* `.who-chip > .chip.plain` — a person to tick (real checkbox before it); `.chip.on.who-chip-ro`
   the read-only copy. These only add the 36px tap height and the tick mark. */
.who-chip .chip,.who-chip-ro{min-height:36px;padding:var(--space-4) var(--space-12);gap:0;line-height:inherit}
.who-chip input:checked + .chip{--chip-bg:var(--orange-50);--chip-bd:var(--orange-600);
  --chip-fg:var(--orange-700);font-weight:700}
.who-chip input:checked + .chip::before{content:"";display:inline-block;width:4px;height:8px;margin:0 var(--space-6) var(--space-2) var(--space-2);
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg)}
.who-chip input:focus-visible + .chip{outline:2px solid var(--orange-600);outline-offset:2px}
.chip.who-chip-ro{min-height:0;--chip-bd:var(--orange-600)}
/* Character rows follow the casting (read-only), "Needs someone" after a copy, and the
   shortcuts card at the top (copy / replace / "It ran as planned"). */
.who-cast{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6)}
.who-cast-label{font-size:var(--text-12);color:var(--muted)}
.who-chip-cast{--chip-bg:var(--bg);--chip-bd:var(--line-2);--chip-fg:var(--ink);
  padding:var(--space-4) var(--space-12);border-style:dashed;line-height:inherit}
.who-needs{display:inline-flex;align-self:flex-start;align-items:center;gap:var(--space-4);
  padding:var(--space-2) var(--space-10);border-radius:999px;font-size:var(--text-12);
  font-weight:700;color:var(--amber-600);background:var(--amber-50)}
.who-tools{padding:var(--space-12) var(--space-16);margin-bottom:var(--space-12);
  display:flex;flex-direction:column;gap:var(--space-8)}
.who-tools-head{font-size:var(--text-13);font-weight:700;margin:0}
.who-tool{border-top:1px solid var(--line);padding-top:var(--space-8)}
.who-tool>summary{cursor:pointer;font-weight:600;font-size:var(--text-13);min-height:32px;
  display:flex;align-items:center}
.who-tool-form{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-8);
  margin-top:var(--space-8)}
.who-tool-form label{display:flex;flex-direction:column;gap:var(--space-2);
  font-size:var(--text-12);color:var(--muted);flex:1 1 200px}
.who-tool-form .who-tool-check,.who-tool-form .who-tool-radio{flex-direction:row;
  align-items:center;gap:var(--space-6);flex:0 0 auto;color:var(--ink);font-size:var(--text-13)}
.who-tool-radios{display:flex;flex-wrap:wrap;gap:var(--space-4) var(--space-12);border:0;
  margin:0;padding:0;flex:1 1 100%}
.who-tool-radios legend{font-size:var(--text-12);color:var(--muted);padding:0;
  margin-bottom:var(--space-2)}
.who-confirmed{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6) var(--space-10);
  border-top:1px solid var(--line);padding-top:var(--space-8);font-size:var(--text-13)}
/* The builder's "Delegation ▾" session list, and the Skills tab's run count. */
.who-pick-pop{display:flex;flex-direction:column;gap:var(--space-2)}
.skill-runs{font-size:var(--text-12);color:var(--muted);margin-top:var(--space-4)}
/* "Characters matched to <main play>" on a client version that shares the main play's
   casting (play_detail, nexus2/castgroup.py, 15 Oct 2026). */
.card.charmatch{margin-bottom:var(--space-12)}
.charmatch h3{margin:0;font-size:var(--text-14)}
.charmatch .list-head{margin-bottom:var(--space-8)}
.charmatch-row{display:flex;align-items:center;gap:var(--space-8);flex-wrap:wrap;padding:var(--space-6) 0;border-top:1px solid var(--line)}
.charmatch-row:first-child{border-top:0}
.charmatch-name{flex:0 0 180px;font-weight:600}
.charmatch-row .fld{flex:1 1 220px;max-width:340px}
.charmatch-tag{font-size:var(--text-12)}
.charmatch-hint{margin:var(--space-8) 0 0;font-size:var(--text-12)}

/* Delegation rows, compact (Greg, 15 Oct 2026: "formatting is terrible" on the first pass of a sibling page):
   part details left, ticks right, one line per part on a wide screen. The legend stays for screen readers only. */
.who-legend{position:absolute;width:1px;height:1px;margin:calc(-1 * var(--space-1));padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.who-list{gap:var(--space-6)}
.who-row-info{display:flex;flex-direction:column;gap:var(--space-4);min-width:0}
.who-chip .chip,.chip.who-chip-ro{min-height:32px}
@media (min-width:900px){
  .who-row{display:grid;grid-template-columns:minmax(0,2fr) minmax(0,3fr);align-items:center;gap:var(--space-6) var(--space-16)}
  .who-row>*{grid-column:2}
  .who-row>.who-row-info{grid-column:1;grid-row:1 / span 3}
}
/* Earlier sessions page (15 Oct 2026): one row per session — fixed date column, one meta line, reflections below. */
.earlier-list{list-style:none;margin:0;padding:0}
.earlier-item{display:grid;grid-template-columns:120px minmax(0,1fr);gap:var(--space-12);padding:var(--space-12) var(--space-16);border-top:1px solid var(--line)}
.earlier-item:first-child{border-top:0}
.earlier-date{font-weight:600;font-size:var(--text-13);color:var(--ink)}
.earlier-time{display:block;font-weight:400;color:var(--muted);font-size:var(--text-12)}
.earlier-body{min-width:0}
.earlier-meta{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-4) var(--space-12);font-size:var(--text-13)}
.earlier-lead{color:var(--muted)}
.earlier-snap{font-size:var(--text-12)}
.earlier-context{color:var(--muted);font-size:var(--text-12);margin-top:var(--space-4)}
.earlier-none{color:var(--muted);font-size:var(--text-12);margin-top:var(--space-4)}
.earlier-refl{margin:var(--space-8) 0 0;display:grid;grid-template-columns:max-content minmax(0,1fr);gap:var(--space-4) var(--space-12);font-size:var(--text-13)}
.earlier-refl dt{color:var(--muted);font-size:var(--text-12)}
.earlier-refl dd{margin:0;overflow-wrap:anywhere}
@media (max-width:600px){.earlier-item{grid-template-columns:1fr;gap:var(--space-4)}.earlier-time{display:inline;margin-left:var(--space-6)}.earlier-refl{grid-template-columns:1fr}}

/* ===== Phase C2 (18 Oct 2026, docs/UX-RULES.md §5) — time presets, live validation, pill on phones =====
   §5.6 — a box whose text is not a time (time24.js), or a session time out of order
   (timepresets.js: "Ends before it starts", "Call is after the start time"). Red edge plus a
   visible line of text; aria-invalid carries the same fact to a screen reader. */
.fld.fld-bad,.fld-bad{border-color:var(--bad)}
.fld.fld-bad:focus{border-color:var(--bad);box-shadow:0 0 0 3px var(--bad-50)}
.tp-warn{margin-top:var(--space-4);font-size:var(--text-12);font-weight:600;color:var(--bad)}
.tp-warn[hidden]{display:none}
/* §5.5 — the "End: +1 h · 3 h block · Half day · Full day" chips under the session's
   Access / Call / Start / End row. Real <button type="button">s: Tab reaches them, Enter
   and Space press them. */
.tp-chips{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-6);margin:var(--space-2) 0 var(--space-14)}
.tp-chips-lead{font-size:var(--text-12);color:var(--muted);font-weight:600}
/* `.chip.plain.tp-chip`: a preset time to press; slate words, 28px tall. */
.tp-chip{position:relative;min-height:28px;padding:0 var(--space-10);
  font:inherit;font-size:var(--text-12);font-weight:600;cursor:pointer;white-space:nowrap}
.chip.tp-chip{--chip-fg:var(--slate)}
.chip.tp-chip:hover{--chip-bd:var(--orange);--chip-fg:var(--orange-700)}
.tp-chip:focus-visible{outline:2px solid var(--orange);outline-offset:2px}
/* §5.4 — 44px tap targets on a phone, through a transparent pad so the chips keep their size. */
@media (max-width:600px){
  .tp-chip::before{content:"";position:absolute;left:0;right:0;top:calc(50% - 22px);bottom:calc(50% - 22px)}
  .tp-chips{gap:var(--space-8)}
}
/* §5.1 — the save pill on a phone keeps ONE word ("Saved", "Unsaved", "Saving…", "Save") instead
   of going icon-only; only the time / "& close" (.tel-savepill-more, savepill.js) drops. The
   "Save & close" hover twin is hidden there: a phone has no hover, and a sticky :hover after a
   tap must not blank the word. A transparent pad gives the pill a 44px hit area. */
@media (max-width:600px){
  .tel-savepill .tel-savepill-txt,.tel-savepill[data-state] .tel-savepill-txt{display:inline}
  .tel-savepill .tel-savepill-lbl,.tel-savepill[data-state] .tel-savepill-lbl{display:inline-grid}
  .tel-savepill .tel-savepill-more{display:none}
  .tel-savepill .tel-savepill-lbl .tel-savepill-go{display:none}
  .tel-savepill:is([data-savepill-close],[data-savepill-submit]):not([data-state="attention"]):is(:hover,:focus-visible) .tel-savepill-txt{visibility:visible}
  .topbar .tel-savepill{position:relative;top:auto;right:auto}
  .tel-savepill::before{content:"";position:absolute;left:calc(-1 * var(--space-2));right:calc(-1 * var(--space-2));top:calc(50% - 22px);bottom:calc(50% - 22px)}
}
/* ===== end Phase C2 ===== */
/* ---------- Phase C1 icon contexts (18 Oct 2026, docs/UX-RULES.md §1) ----------
   The last emoji and typed marks became _icon.html glyphs or drawn shapes. Each rule below
   sizes an icon to the place a typed glyph used to sit, so nothing reflows. */
.cchev>svg.ic{width:1em;height:1em;display:block;transform:rotate(-90deg)}
/* Progress ticks: drawn, not typed. The mark follows the row's `.ok` class; booking_form's
   JS empties .tk and toggles `.ok`, and font-size:0 hides any stray text. */
.tick .tk{display:inline-block;position:relative;width:9px;height:9px;margin-right:var(--space-4);
  font-size:0;vertical-align:-1px;flex:none}
.tick .tk::before,.tick .tk::after{content:"";position:absolute;left:50%;top:0;width:2px;height:100%;
  margin-left:calc(-1 * var(--space-1));background:currentColor;border-radius:1px;transform:rotate(45deg)}
.tick .tk::after{transform:rotate(-45deg)}
.tick.ok .tk::after{display:none}
.tick.ok .tk::before{left:3px;top:-1px;width:3px;height:8px;margin:0;background:none;border-radius:0;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor}
.cs-ab svg.ic{width:15px;height:15px;vertical-align:middle}
.cs-primary svg.ic{width:13px;height:13px;vertical-align:-1px}
.cs-covbadge .pinch svg.ic{width:11px;height:11px;display:block}
.sent svg.ic,.sent-marker svg.ic{width:12px;height:12px;vertical-align:-1px}
.eng-warn svg.ic{width:14px;height:14px;vertical-align:-2px}
.record-notice .ic svg.ic{width:16px;height:16px;display:block}
.chip-x svg.ic,.sc-xbtn svg.ic,.cc-dismiss svg.ic{width:12px;height:12px;display:block}
.accmx-yes svg.ic{width:15px;height:15px;vertical-align:-2px}
.xm td.xm-cell svg.ic{width:13px;height:13px;vertical-align:middle}
.alert-grp .row .ic svg.ic{width:15px;height:15px}
.bday-banner-ic svg.ic{width:18px;height:18px;display:block}
.sp-toggle svg.ic{width:11px;height:11px;display:block;transform:rotate(-90deg);transition:transform .15s}
.sp-toggle[aria-expanded="true"] svg.ic{transform:none}
.closeout-cast-btn[aria-expanded="true"] .dchev svg.ic{transform:rotate(180deg)}
/* Shopping list tick: an empty ring to tick, the check once bought. */
.shop-tick:not(.on)::before{content:"";display:inline-block;width:16px;height:16px;border:2px solid currentColor;
  border-radius:50%;vertical-align:middle}
.shop-tick svg.ic{width:20px;height:20px;vertical-align:middle}
/* The box to tick by hand on a packing list (was a typed box). Paper copy: booking/static/runsheet_pdf.css. */
.m-box{display:inline-block;width:11px;height:11px;border:1.5px solid var(--slate);border-radius:2px;vertical-align:middle}
/* FullCalendar's view button: the chevron after "Week" (calviewmenu.js keeps only the word). */
.fc-viewMenu-button::after{content:"";display:inline-block;width:5px;height:5px;margin:0 0 var(--space-3) var(--space-6);
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;transform:rotate(45deg)}
/* The big "done" mark on a thank-you page (was a typed tick emoji). */
.done-mark{color:var(--good);line-height:1}
.done-mark svg.ic{width:40px;height:40px}

/* Card stacks (UX-RULES §3.4): put cards in a .stack and they get ONE gap between them,
   never a per-page margin. Added 2026-10-19; templates adopt it as they are touched. */
.stack>*+*{margin-top:var(--space-16)}
/* ── UX audit fixes, 19 Oct 2026 (board, booking edit, whereabouts, meetings, calendar) ──
   Appended as one block so the parallel tel.css rewrite does not clash with it. */
/* Booking edit crumb: the job's dates sit next to the job pill (when comes first). */
.topbar .crumb .bk-crumb-dates{margin-right:var(--space-6);font-weight:600;color:var(--ink);white-space:nowrap;
  font-variant-numeric:tabular-nums}
/* The compact "Sections" menu stands in for .bk-jump wherever that row hides (below 1360px). */
.bk-jump-sel{display:none;flex:none;max-width:120px;font-size:var(--text-13);font-weight:600;
  padding:var(--space-6) var(--space-8);border:1px solid var(--line);border-radius:var(--radius-sm);
  background:var(--card);color:var(--ink);min-height:36px}
@media (max-width:1359px){ .topbar > .bk-jump-sel{display:block} }
/* A phone's top bar cannot also fit the dates and the menu (a 76px crumb), so there both
   move into a sticky strip under it (.bkedit-strip, booking_form.html). Above 600px that
   strip stays hidden and must not add its 44px to the jump offset. */
.bk-strip.bkedit-strip{display:none}
.bkedit-strip .bk-jump-sel{display:block;margin-left:auto;min-height:36px}
.bkedit-strip-dates{font-weight:600;font-size:var(--text-14);color:var(--ink);white-space:nowrap;
  font-variant-numeric:tabular-nums;overflow:hidden;text-overflow:ellipsis}
@media (min-width:601px) and (max-width:1359px){
  html:has(.bkedit-strip){scroll-padding-top:calc(var(--header-h) + var(--space-16))}
}
@media (max-width:600px){
  .bk-strip.bkedit-strip{display:flex}
  .topbar > .bk-jump-sel,.topbar .crumb .bk-crumb-dates{display:none}
}
/* Calendar day popover: no Save button — every change autosaves; this line says so. */
.wa-pop .wa-pop-state{min-height:16px;margin-top:var(--space-6);font-size:var(--text-11)}
.wa-pop .wa-pop-state:empty::before{content:"Changes save on their own"}
/* Meeting "When" row: the 30 min / 1 h / 2 h chips sit on the line, after the length. */
.dt-row .dt-chips{margin:0}
/* Calendar filter chips on a LIGHT colour (data-light, set by the wants_dark_text filter from
   the colour's luminance): white text failed AA on them (Office 2.0:1, Travel 1.7:1), so the
   words go --ink, on and off. The border and the dot still carry the colour. */
.cat-chip[data-light],.cat-chip.on[data-light]{--chip-fg:var(--ink);color:var(--ink)}

/* ===== Phone-width fixes + field captions (19 Oct 2026) =====
   1. At 390px a collapsed session row on the booking edit page put its pills, Duplicate and
      the chevron past the right edge (html/body clip, so they were simply cut off). The
      summary may wrap onto a second line on a phone. The job-file row on the booking page
      did the same with its size/who line and bin.
   2. `.fld-cap` — a small caption that stays above a dense-row field (Role, Character, Fee
      on a team row; the play document boxes). A placeholder alone disappears once a value
      is typed (UX-RULES 4.4). */
@media (max-width:600px){
  .sess-row>summary{flex-wrap:wrap;row-gap:var(--space-4)}
  .jobfile-side{flex-wrap:wrap;min-width:0;max-width:100%}
  .jobfile-meta{white-space:normal}
}
.fld-cap{display:block;margin-bottom:var(--space-2);font-size:var(--text-11);font-weight:600;
  letter-spacing:var(--caps-track);text-transform:uppercase;color:var(--muted);line-height:1.2;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:default}
.eng-head-fields{align-items:flex-end}
.eng-head-fee .fld-cap{text-align:right}

/* Teams meeting for a session (_teams_panel.html, 19 Oct 2026) */
.teams-panel .teams-line{display:block;margin-bottom:var(--space-6)}
.teams-panel .teams-stale{color:var(--amber-600)}
.teams-panel .teams-actions{display:flex;flex-wrap:wrap;gap:var(--space-8)}
.teams-panel .teams-make{display:flex;flex-direction:column;align-items:flex-start;gap:var(--space-4)}
.teams-panel .teams-make .fld{width:100%;max-width:320px}
.teams-panel .teams-invited{text-decoration:underline dotted;cursor:help}

/* Client site forms on the JOB page's Logistics section (_booking_site_forms.html; Davina,
   20 Oct 2026 — the travel report's card until then, which now keeps one line pointing
   here): each form with its "Used on this job" tick and "Use an edited copy" (a <details>
   holding one drop box), then the job answers, the session ticks, the travel questions and
   one ZIP of every person's pre-filled forms. */
.client-forms-moved{font-size:var(--text-13);margin-top:var(--space-10)}
.client-forms-tick{display:inline-flex;align-items:center;gap:var(--space-6);font-size:var(--text-13);min-height:44px}
.sfj-add{margin-bottom:var(--space-12)}
.sfj-note{font-size:var(--text-12)}
.sfj-item{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-4) var(--space-12);padding:var(--space-6) 0;border-top:1px solid var(--line)}
.sfj-item:first-child{border-top:0}
.sfj-text{display:flex;flex-direction:column;min-width:0;padding-top:var(--space-12);font-size:var(--text-13)}
.sfj-used{flex:0 0 auto}
.sfj-dl{margin-top:var(--space-12);padding-top:var(--space-12);border-top:1px solid var(--line)}
.sfj-group{font-size:var(--text-12);font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:var(--space-12) 0 var(--space-6)}
.sfj-dl .sfj-group:first-of-type{margin-top:0}
.sfj-ticks{display:flex;flex-wrap:wrap;gap:0 var(--space-18)}
.sfj-ticksec{flex:0 0 100%;font-size:var(--text-12);margin-top:var(--space-8)}
.client-forms-edited>summary{font-size:var(--text-12);cursor:pointer;list-style:none;min-height:32px;display:flex;align-items:center}
.client-forms-edited>summary::-webkit-details-marker{display:none}
.client-forms-edited[open]>summary{margin-bottom:var(--space-6)}
.client-forms-foot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-10);margin-top:var(--space-14)}
.client-forms-foot .fld{width:auto;max-width:260px}
.client-forms-note{font-size:var(--text-12);margin-top:var(--space-8)}

/* Client site forms: the client page's "Site forms" card (client_detail.html), the all-forms
   fallback list (client_forms_list.html) and the review page (client_form_detail.html): one
   row per box on the client's form, a drop-down of what goes in it, and a text input that
   shows only for "Ask once per job" / "Same text on every copy" (:has on the selected
   option, so no script is needed). A tick box's "Ticked by default" shows the same way. */
.csf-list{display:flex;flex-direction:column}
.csf-item{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-4) var(--space-10);padding:var(--space-8) 0;border-top:1px solid var(--line);font-size:var(--text-13)}
.csf-item:first-child{border-top:0}
.csf-item .pill{margin-left:auto}
.csf-hint{font-size:var(--text-12);margin:var(--space-8) 0 var(--space-12)}
.csf-name{font-weight:600}
.csf-head-foot{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-10) var(--space-18)}
.csf-head-foot .csf-test{margin-left:auto}
.csf-replace{margin-top:var(--space-12)}
.csf-replace>summary{font-size:var(--text-13);cursor:pointer;list-style:none;min-height:44px;display:flex;align-items:center}
.csf-replace>summary::-webkit-details-marker{display:none}
.csf-head{margin-top:var(--space-16)}
.csf-sec{margin-top:var(--space-16)}
.csf-tbl td{vertical-align:middle}
.csf-tbl .csf-label{width:34%;font-weight:600}
.csf-tbl .fld{max-width:320px}
.csf-row .csf-text{display:none}
.csf-row:has(option[value="job"]:checked) .csf-text,
.csf-row:has(option[value="fixed"]:checked) .csf-text{display:block}
.csf-row .csf-tickdef{display:none}
.csf-row:has(option[value="job"]:checked) .csf-tickdef{display:inline-flex}
/* "Ask each person (new detail)…" shows its "What to ask" box (20 Oct 2026). */
.csf-row .csf-ask{display:none;font-size:var(--text-12);color:var(--muted)}
.csf-row:has(option[value="detail:__new__"]:checked) .csf-ask{display:flex;flex-direction:column;gap:var(--space-4)}
/* Flat PDF form "Positions" card (client_form_detail.html, 20 Oct 2026): each page image in
   an inline SVG (viewBox width 1000) with a numbered marker per box, placed by SVG x/y and
   transform attributes; the across / down inputs beside it move the markers. */
.csf-pos-page{display:grid;grid-template-columns:minmax(0,3fr) minmax(0,2fr);gap:var(--space-16);align-items:start;margin-top:var(--space-12)}
.csf-pos-page h4{grid-column:1/-1;margin:0}
.csf-pos-svg{display:block;width:100%;height:auto;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--card)}
.csf-mk-box{fill:none;stroke:var(--orange-700);stroke-width:2}
.csf-mk-dot{fill:var(--orange-700)}
.csf-mk-num{fill:var(--card);font-size:var(--text-16);font-weight:700}
.csf-pos-tbl td{vertical-align:middle}
.csf-pos-tbl .fld{max-width:96px}
.csf-num{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;margin-right:var(--space-6);border-radius:var(--radius-sm);background:var(--orange-50);color:var(--orange-700);font-size:var(--text-12);font-weight:700}
@media (max-width:900px){.csf-pos-page{grid-template-columns:1fr}}
/* Job card: who has not given a detail a ticked form asks for (_booking_site_forms.html). */
.sfj-missing{font-size:var(--text-12);margin-top:var(--space-4)}
/* My details: the "Details for client sites" intro line. */
.md-sec-intro{font-size:var(--text-13);margin:0 0 var(--space-8)}
@media (max-width:600px){
  .csf-tbl thead{display:none}
  .csf-tbl tr,.csf-tbl td{display:block}
  .csf-tbl tr{border-bottom:1px solid var(--line);padding:var(--space-8) 0}
  .csf-tbl td{border-bottom:0;padding-top:var(--space-4);padding-bottom:var(--space-4)}
  .csf-tbl .csf-label{width:auto}
  .csf-tbl .fld{max-width:none}
}
/* Server health: five "Right now" cards (People using the app now joined the four, 20 Oct
   2026). Wide screens only; the .grid.kpis 3- and 2-column steps below 1181px still apply. */
@media (min-width:1181px){.grid.kpis.health-kpis{grid-template-columns:repeat(5,1fr)}}
/* Team birthdays report (team_birthdays.html, 20 Oct 2026): the window + print bar, the
   month rows in the list, the no-address warning. */
.bdrep-bar{display:flex;align-items:flex-end;gap:var(--space-12);flex-wrap:wrap;padding:var(--space-12) var(--space-14);margin-bottom:var(--space-12)}
.bdrep-bar .fgroup{margin:0}
.bdrep-print{display:flex;gap:var(--space-8);margin-left:auto;flex-wrap:wrap}
.bdrep-sum{font-size:var(--text-13);margin:0 0 var(--space-12)}
.bdrep-warn{padding:var(--space-12) var(--space-14);margin-bottom:var(--space-12);font-size:var(--text-13)}
.bdrep-month td{font-size:var(--text-11);font-weight:600;text-transform:uppercase;letter-spacing:var(--caps-track);color:var(--muted);background:var(--surface-2)}
.bdrep-day{white-space:nowrap;font-variant-numeric:tabular-nums}

/* ---------------------------------------------------------------------------------------------
   Marketing dashboard (/marketing/, 22 Oct 2026). Scoped to .mkt. The app has no dark theme,
   so the dashboard carries its own: the light tokens below, and under prefers-color-scheme:
   dark the .mkt container redefines the shared tokens (--card, --ink, --line ...) so every
   tel.css component inside it follows. Chart colours are --mkt-* tokens; marketing.js reads
   them with getComputedStyle (var() does not resolve on <canvas>). Hex lives only here, in
   :root token definitions.
   --------------------------------------------------------------------------------------------- */
:root{
  --mkt-s1:#F16622; --mkt-s2:#1d5fa6; --mkt-s3:#237a46; --mkt-s4:#5b3fd6; --mkt-s5:#b8477a;
  --mkt-s6:#8a5a00; --mkt-prev:#888686; --mkt-note:#5b6770; --mkt-grid:#eeeae7;
  --mkt-alert-bg:#fff6e0; --mkt-alert-fg:#6b4600; --mkt-alert-bd:#f0c25a;
  /* dark versions */
  --mkt-d-bg:#161514; --mkt-d-card:#211f1e; --mkt-d-surface:#2a2826; --mkt-d-ink:#f1eeec;
  --mkt-d-muted:#b3aeaa; --mkt-d-line:#3a3735; --mkt-d-grid:#2f2c2a; --mkt-d-orange:#ff9a62;
  --mkt-d-orange-50:#3a2418; --mkt-d-good:#5cc48a; --mkt-d-good-50:#173323; --mkt-d-bad:#ff7b7b;
  --mkt-d-bad-50:#3a1c1c; --mkt-d-amber:#e8b54a; --mkt-d-amber-50:#33290f; --mkt-d-pill:#2f2c2a;
  --mkt-d-s1:#ff8a4c; --mkt-d-s2:#6aa8ec; --mkt-d-s3:#5cc48a; --mkt-d-s4:#a593ff;
  --mkt-d-s5:#e47fae; --mkt-d-s6:#e0b44a; --mkt-d-prev:#8f8a86; --mkt-d-note:#c9c4bf;
  --mkt-d-alert-bg:#33290f; --mkt-d-alert-fg:#f3d68f; --mkt-d-alert-bd:#7a5d16;
}
.mkt{min-width:0}
.mkt>*+*{margin-top:var(--space-16)}
.mkt-status{display:flex;flex-wrap:wrap;gap:var(--space-6) var(--space-16);font-size:var(--text-12);color:var(--muted)}
.mkt-src{display:inline-flex;align-items:center;gap:var(--space-6)}
.mkt-src-stale{color:var(--ink)}
.mkt-alert{display:flex;align-items:flex-start;gap:var(--space-8);padding:var(--space-10) var(--space-14);
  border:1px solid var(--mkt-alert-bd);background:var(--mkt-alert-bg);color:var(--mkt-alert-fg);
  border-radius:var(--radius-sm);font-size:var(--text-13);font-weight:600}
.mkt-alert svg.ic{width:16px;height:16px;flex:0 0 auto;margin-top:var(--space-2)}
.mkt>.mkt-alert+.mkt-alert{margin-top:var(--space-8)}
.mkt-checks{display:flex;flex-wrap:wrap;gap:var(--space-4) var(--space-14);font-size:var(--text-12)}
.mkt-checks:empty{display:none}
.mkt-range{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-10) var(--space-16)}
.mkt-tabs{margin-bottom:0;border-bottom:0}
.mkt-custom{display:flex;flex-wrap:wrap;align-items:flex-end;gap:var(--space-8)}
.mkt-custom-f{display:flex;flex-direction:column}
.mkt-custom-f .fld{width:auto;min-width:0;padding:var(--space-6) var(--space-8);font-size:var(--text-13)}
.mkt-compare{cursor:pointer;text-decoration:none}
.mkt-compare svg.ic{width:12px;height:12px}
.mkt-period{font-size:var(--text-12)}
.mkt>.mkt-period{margin-top:var(--space-6)}
.mkt-kpis{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:var(--space-12)}
.mkt-kpi{padding:var(--space-14) var(--space-16) var(--space-10);display:flex;flex-direction:column;min-width:0}
.mkt-kpi-lab{font-size:var(--text-12);font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:var(--caps-track);display:flex;align-items:center;gap:var(--space-4)}
.mkt-kpi-num{font-family:var(--display);font-size:var(--text-40);line-height:1;margin-top:var(--space-8);color:var(--ink);white-space:nowrap}
.mkt-kpi-num.mkt-missing{color:var(--muted)}
.mkt-kpi-sub{font-size:var(--text-12);color:var(--muted);margin-top:var(--space-4)}
.mkt-chg{font-weight:700;color:var(--ink)}
.mkt-chg.up{color:var(--good)}
.mkt-chg.down{color:var(--bad)}
.mkt-spark{height:36px;margin-top:auto;padding-top:var(--space-8)}
.mkt-spark canvas{width:100%;height:100%}
.mkt-charts{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,440px),1fr));gap:var(--space-16)}
.mkt-two{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,380px),1fr));gap:var(--space-16)}
.mkt-tables{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,520px),1fr));gap:var(--space-16)}
.mkt-card{min-width:0}
.mkt-card .card-head{flex-wrap:wrap;gap:var(--space-4) var(--space-10)}
.mkt-card .card-head h3{display:flex;align-items:center;gap:var(--space-4)}
.mkt-chart{position:relative;height:260px;padding:var(--space-12) var(--space-14)}
.mkt-chart .empty{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}
.mkt-funnel{padding:var(--space-14) var(--space-16)}
.mkt-fstep+.mkt-fstep{margin-top:var(--space-14)}
.mkt-fhead{display:flex;justify-content:space-between;gap:var(--space-10);font-size:var(--text-13)}
.mkt-fbar{height:12px;border-radius:999px;background:var(--line-2);overflow:hidden;margin-top:var(--space-6)}
.mkt-fbar span{display:block;height:100%;width:0;background:var(--orange);border-radius:999px}
.mkt-fbar [data-w="5"]{width:5%}.mkt-fbar [data-w="10"]{width:10%}.mkt-fbar [data-w="15"]{width:15%}
.mkt-fbar [data-w="20"]{width:20%}.mkt-fbar [data-w="25"]{width:25%}.mkt-fbar [data-w="30"]{width:30%}
.mkt-fbar [data-w="35"]{width:35%}.mkt-fbar [data-w="40"]{width:40%}.mkt-fbar [data-w="45"]{width:45%}
.mkt-fbar [data-w="50"]{width:50%}.mkt-fbar [data-w="55"]{width:55%}.mkt-fbar [data-w="60"]{width:60%}
.mkt-fbar [data-w="65"]{width:65%}.mkt-fbar [data-w="70"]{width:70%}.mkt-fbar [data-w="75"]{width:75%}
.mkt-fbar [data-w="80"]{width:80%}.mkt-fbar [data-w="85"]{width:85%}.mkt-fbar [data-w="90"]{width:90%}
.mkt-fbar [data-w="95"]{width:95%}.mkt-fbar [data-w="100"]{width:100%}
.mkt-fdrop{font-size:var(--text-12);margin-top:var(--space-4)}
.mkt-opps{padding:var(--space-12) var(--space-16)}
.mkt-opp+.mkt-opp{margin-top:var(--space-14);padding-top:var(--space-12);border-top:1px solid var(--line)}
.mkt-opp-h{font-size:var(--text-12);font-weight:600;text-transform:uppercase;letter-spacing:var(--caps-track);color:var(--muted);margin-bottom:var(--space-6)}
.mkt-opp-row{display:flex;flex-direction:column;font-size:var(--text-13);padding:var(--space-4) 0}
.mkt-opp .muted{font-size:var(--text-12)}
.mkt-key{overflow-wrap:anywhere;word-break:break-word}
.mkt-tbar{display:flex;flex-wrap:wrap;gap:var(--space-8);align-items:center;padding:var(--space-10) var(--space-14) 0}
.mkt-search{flex:1 1 180px;width:auto;padding:var(--space-6) var(--space-10);font-size:var(--text-13)}
.mkt-brand{display:flex;gap:var(--space-4)}
.mkt-brand .chip{cursor:pointer;font-family:inherit}
.mkt-tblwrap{max-height:440px;overflow:auto;margin-top:var(--space-8)}
.mkt-tbl{font-size:var(--text-13)}
.mkt-tbl th{position:sticky;top:0;background:var(--card);z-index:1}
.mkt-tbl td,.mkt-tbl th{padding:var(--space-8) var(--space-10)}
.mkt-tbl .mkt-n{text-align:right;white-space:nowrap;font-variant-numeric:tabular-nums}
.mkt-tbl th.sorted-asc::after{content:" \2191"}
.mkt-tbl th.sorted-desc::after{content:" \2193"}
.mkt-row{cursor:pointer}
.mkt-row:focus-visible td{outline:none;background:var(--surface-3)}
.mkt-row.on td{background:var(--orange-50)}
.mkt-drill .card-head .btn{margin-left:auto}
/* Google Ads card (through GA4): words-only until linked, then six small figures + chart + table */
.mkt-ads-body{padding:var(--space-12) var(--space-16);font-size:var(--text-13)}
.mkt-ads-body p{margin:0}
.mkt-ads-body p+p,.mkt-ads-body p+ol{margin-top:var(--space-8)}
.mkt-ads-steps{margin:0;padding-left:var(--space-20)}
.mkt-ads-steps li+li{margin-top:var(--space-4)}
.mkt-ads-figs{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:var(--space-12) var(--space-16);padding:var(--space-12) var(--space-16) 0}
.mkt-afig{min-width:0}
.mkt-afig-num{font-family:var(--display);font-size:var(--text-24);line-height:1.1;margin-top:var(--space-6);color:var(--ink);white-space:nowrap}
.mkt-afig-num.mkt-missing{color:var(--muted)}
.mkt-note-form{flex-wrap:wrap;gap:var(--space-8);align-items:flex-end;padding:var(--space-12) var(--space-16) 0}
.mkt-note-d{display:flex;flex-direction:column;flex:0 0 auto}
.mkt-note-t{display:flex;flex-direction:column;flex:1 1 220px}
.mkt-notes{list-style:none;margin:0;padding:var(--space-6) var(--space-16) var(--space-12)}
.mkt-note{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--space-10);padding:var(--space-8) 0;border-bottom:1px solid var(--line);font-size:var(--text-13)}
.mkt-note:last-child{border-bottom:0}
.mkt-note-date{font-variant-numeric:tabular-nums;margin-right:var(--space-6)}
.mkt-note-text{min-width:0;overflow-wrap:anywhere}
@media (max-width:600px){
  .mkt-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}
  .mkt-kpi-num{font-size:var(--text-32)}
  .mkt-chart{height:220px;padding:var(--space-8)}
  .mkt-tbl td,.mkt-tbl th{padding:var(--space-6) var(--space-6)}
}
@media (prefers-color-scheme:dark){
  .mkt{color-scheme:dark;background:var(--mkt-d-bg);color:var(--mkt-d-ink);
    border-radius:var(--radius);padding:var(--space-16);
    --card:var(--mkt-d-card);--ink:var(--mkt-d-ink);--muted:var(--mkt-d-muted);
    --line:var(--mkt-d-line);--line-2:var(--mkt-d-surface);--surface-2:var(--mkt-d-surface);
    --surface-3:var(--mkt-d-surface);--bg:var(--mkt-d-bg);--slate-2:var(--mkt-d-muted);
    --orange-600:var(--mkt-d-orange);--orange-700:var(--mkt-d-orange);--orange-50:var(--mkt-d-orange-50);
    --good:var(--mkt-d-good);--good-50:var(--mkt-d-good-50);--bad:var(--mkt-d-bad);
    --bad-50:var(--mkt-d-bad-50);--amber-600:var(--mkt-d-amber);--amber-50:var(--mkt-d-amber-50);
    --pill-muted-bg:var(--mkt-d-pill);--shadow:none;
    --mkt-s1:var(--mkt-d-s1);--mkt-s2:var(--mkt-d-s2);--mkt-s3:var(--mkt-d-s3);
    --mkt-s4:var(--mkt-d-s4);--mkt-s5:var(--mkt-d-s5);--mkt-s6:var(--mkt-d-s6);
    --mkt-prev:var(--mkt-d-prev);--mkt-note:var(--mkt-d-note);--mkt-grid:var(--mkt-d-grid);
    --mkt-alert-bg:var(--mkt-d-alert-bg);--mkt-alert-fg:var(--mkt-d-alert-fg);
    --mkt-alert-bd:var(--mkt-d-alert-bd)}
  .mkt .tabs a{color:var(--mkt-d-muted)}
  .mkt .tabs a.on{color:var(--mkt-d-orange)}
}
