/* ============================================================================
   G4 CONSOLE — Boeing brand theme
   Single source of truth for the whole console. No webfonts, no CDN, no remote
   assets: every colour, gradient, texture and animation below is CSS only, so
   the page renders identically on an air-gapped show floor.

   Brand palette (exact hex from the owner's brand sheet):
     #1A409F  Boeing blue   — primary actions, headings, active state
     #0D7CB1  cyan-blue     — secondary accent, links, focus, data
     #394957  slate         — dark neutral surface
     #FDFDFE  near-white    — light ground / ink on dark

   Reading note: #1A409F and #0D7CB1 are dark-ish. Used as FILLS under white
   text they are excellent (9.2:1 and 4.6:1). Used as TEXT on a dark ground
   they would fail contrast, so the tokens --brand-ink / --cyan-ink hold
   lightened tints of the same hues for type and hairlines. Brand hexes stay
   exact wherever they are the actual painted colour.
   ========================================================================== */

/* ---------------------------------------------------------------- 1. TOKENS */
:root{
  /* --- brand, exact ------------------------------------------------------ */
  --boeing-blue:#1A409F;
  --boeing-cyan:#0D7CB1;
  --boeing-slate:#394957;
  --boeing-white:#FDFDFE;

  /* --- brand, lightened for type on dark grounds ------------------------- */
  --brand-ink:#7C9BE8;          /* Boeing blue, legible as text */
  --cyan-ink:#4FB3E8;           /* cyan-blue, legible as text  */
  --cyan-soft:rgba(13,124,177,.16);
  --blue-soft:rgba(26,64,159,.20);

  /* --- ground: deep navy derived from Boeing blue, cooled with slate ----- */
  --ground:#070C18;
  --ground-2:#0A1120;
  --panel:#0F182A;
  --panel-2:#141F33;
  --panel-3:#1B2942;
  --raise:#22314B;

  /* --- hairlines --------------------------------------------------------- */
  --line:#22314B;
  --line-soft:#1A2740;
  --line-strong:#2E4166;

  /* --- ink --------------------------------------------------------------- */
  --ink:#FDFDFE;
  --ink-2:#C6D2E4;
  --muted:#8C9CB5;
  --faint:#7A8AA5;   /* measured 5.6:1 on the ground — AA at the 11px sizes it is used at */

  /* --- semantic ---------------------------------------------------------- */
  --ok:#1FA97A;      --ok-ink:#4FD9A6;   --ok-soft:rgba(31,169,122,.15);
  --warn:#C98A18;    --warn-ink:#F2C263; --warn-soft:rgba(201,138,24,.16);
  --bad:#C7443E;     --bad-ink:#FF8A82;  --bad-soft:rgba(199,68,62,.16);
  --info-soft:rgba(13,124,177,.15);

  /* --- geometry ---------------------------------------------------------- */
  --r-xs:6px; --r-sm:9px; --r:12px; --r-lg:16px; --r-xl:22px; --r-pill:999px;

  /* --- elevation --------------------------------------------------------- */
  --sh-1:0 1px 2px rgba(0,0,0,.40);
  --sh-2:0 6px 18px rgba(0,0,0,.34);
  --sh-3:0 18px 44px rgba(0,0,0,.48);
  --sh-4:0 34px 90px rgba(0,0,0,.62);
  --glow-cyan:0 0 0 1px rgba(13,124,177,.35), 0 8px 30px rgba(13,124,177,.22);
  --focus:0 0 0 3px rgba(13,124,177,.55);

  /* --- type -------------------------------------------------------------- */
  --font:-apple-system,BlinkMacSystemFont,"Segoe UI Variable Text","Segoe UI",
         Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;
  --mono:ui-monospace,"SF Mono","Cascadia Mono",Consolas,"Roboto Mono",monospace;

  --t-11:.688rem; --t-12:.75rem;  --t-13:.812rem; --t-14:.875rem;
  --t-15:.938rem; --t-16:1rem;    --t-18:1.125rem;--t-22:1.375rem;
  --t-28:1.75rem; --t-36:2.25rem;

  /* --- motion ------------------------------------------------------------ */
  --ease:cubic-bezier(.22,.61,.36,1);
  --fast:.14s var(--ease);
  --med:.26s var(--ease);
}

/* ------------------------------------------------------------ 2. BASE/RESET */
/* box-sizing is border-box for EVERYTHING, including pseudo-elements: padding
   is then paid for out of the declared width instead of being added on top of
   it, which is the single most common way a phone layout ends up 30px wider
   than the screen. */
*,*::before,*::after{box-sizing:border-box}

html{
  -webkit-text-size-adjust:100%;   /* iOS must not inflate type on rotate */
  text-size-adjust:100%;
}
body{
  margin:0;
  min-height:100vh;
  min-height:100dvh;               /* mobile chrome shrinks/grows: follow it */
  background:var(--ground);
  color:var(--ink);
  font-family:var(--font);
  font-size:var(--t-14);
  line-height:1.5;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overscroll-behavior-y:none;      /* no rubber-band under the fixed tab bar */
}

/* Sideways-scroll backstop, applied AFTER the real overflow sources were fixed
   (fluid grids, card-ified tables, wrapping strings — see §15). `clip` rather
   than `hidden` on purpose: `hidden` turns the element into a scroll container,
   which would sever the sticky top bar from the viewport's scrollport. `clip`
   crops without ever becoming a scroller, so the header keeps sticking. */
html,body{overflow-x:clip}
@supports not (overflow-x:clip){
  body{overflow-x:hidden}
}
h1,h2,h3,h4{margin:0;font-weight:650;letter-spacing:-.015em;line-height:1.22}
p{margin:0 0 10px}
a{color:var(--cyan-ink);text-decoration:none}
a:hover{text-decoration:underline}
img{max-width:100%;display:block}
[hidden]{display:none !important}
::selection{background:rgba(13,124,177,.38)}

/* Touch behaviour. `manipulation` tells the browser this element will never be
   double-tap-zoomed, which removes the legacy 300ms click delay; the tap flash
   is replaced by the :active states the components already define. */
button,a,label,summary,input,select,textarea,[role="button"],.navlink,.seg button{
  touch-action:manipulation;
  -webkit-tap-highlight-color:rgba(13,124,177,.16);
}

/* Long unbroken strings — asset URIs, MAC addresses, audit targets, hostnames —
   are the other classic source of sideways scroll. `anywhere` (not break-word)
   also lets these boxes report a 1-character min-content width, so a table or
   flex row can actually shrink to the phone instead of forcing the page wide. */
.mono,.t2,.feed-body .l1,.empty p,.toast .tx,.tile .v,.who-name,
td,th,.pagehead h1,.card-h h2,.card-h h3,.health-row .nm{overflow-wrap:anywhere}

/* One focus treatment everywhere — visible, on-brand, never removed.
   No border-radius here on purpose: the ring must follow each element's own
   shape (pill buttons stay pills) rather than squaring it off. */
:focus-visible{outline:none;box-shadow:var(--focus)}
/* Keep a real outline for forced-colors / Windows high-contrast users, where
   box-shadows are not painted at all. */
@media (forced-colors:active){
  :focus-visible{outline:3px solid Highlight;outline-offset:2px}
}

/* thin, on-brand scrollbars (WebKit) + Firefox */
*{scrollbar-width:thin;scrollbar-color:var(--line-strong) transparent}
::-webkit-scrollbar{width:10px;height:10px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--line-strong);border-radius:99px;border:3px solid transparent;background-clip:content-box}
::-webkit-scrollbar-thumb:hover{background:#3B5480;background-clip:content-box;border:3px solid transparent}

.skip-link{
  position:fixed;left:12px;top:-60px;z-index:200;
  background:var(--boeing-blue);color:#fff;padding:10px 16px;border-radius:var(--r-sm);
  font-weight:650;transition:top var(--fast);
}
.skip-link:focus{top:12px}

/* ------------------------------------------------------------- 3. BACKDROP */
/* Aerospace ground: a faint navigation grid, two slow-drifting light masses
   and a hairline horizon. All fixed, all behind everything, all cheap. */
.backdrop{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.backdrop::before{                    /* survey grid */
  content:"";position:absolute;inset:-2px;
  background-image:
    linear-gradient(rgba(56,84,140,.16) 1px,transparent 1px),
    linear-gradient(90deg,rgba(56,84,140,.16) 1px,transparent 1px);
  background-size:64px 64px;
  mask-image:radial-gradient(120% 90% at 50% 0%,#000 12%,transparent 76%);
  -webkit-mask-image:radial-gradient(120% 90% at 50% 0%,#000 12%,transparent 76%);
  opacity:.5;
}
.backdrop::after{                     /* light masses */
  content:"";position:absolute;inset:-25%;
  background:
    radial-gradient(46% 40% at 22% 12%,rgba(26,64,159,.42),transparent 68%),
    radial-gradient(40% 36% at 82% 22%,rgba(13,124,177,.24),transparent 70%),
    radial-gradient(60% 50% at 50% 108%,rgba(57,73,87,.40),transparent 72%);
  filter:blur(6px);
  animation:drift 44s var(--ease) infinite alternate;
}
@keyframes drift{
  0%  {transform:translate3d(0,0,0)      scale(1)}
  50% {transform:translate3d(-2.2%,1.6%,0) scale(1.05)}
  100%{transform:translate3d(1.8%,-1.4%,0) scale(1.02)}
}
.horizon{                             /* hairline sweep under the top bar */
  position:fixed;left:0;right:0;top:0;height:1px;z-index:1;pointer-events:none;
  background:linear-gradient(90deg,transparent,rgba(13,124,177,.55) 28%,rgba(26,64,159,.65) 62%,transparent);
}

/* ------------------------------------------------------------- 4. THE SHELL */
.shell{
  position:relative;z-index:2;display:flex;flex-direction:column;
  min-height:100vh;min-height:100dvh;
}

.topbar{
  position:sticky;top:0;z-index:40;
  background:linear-gradient(180deg,rgba(10,17,32,.94),rgba(10,17,32,.80));
  backdrop-filter:saturate(150%) blur(14px);
  -webkit-backdrop-filter:saturate(150%) blur(14px);
  border-bottom:1px solid var(--line);
  padding-top:env(safe-area-inset-top,0px);   /* clear of a notch / status bar */
}
.topbar-in{
  max-width:1520px;margin:0 auto;
  padding:12px max(22px,env(safe-area-inset-left,0px)) 12px max(22px,env(safe-area-inset-left,0px));
  display:flex;align-items:center;gap:18px;flex-wrap:nowrap;
}

/* The mark is WHITE artwork on a transparent ground, so it sits directly on the
   navy — no plate, no filter, no recolouring. (It used to be Boeing blue, which
   is why this rule once painted a near-white badge behind it.) */
.lockup{display:flex;align-items:center;gap:14px;min-width:0}
.logo-mark{display:grid;place-items:center;flex:none}
.logo-mark img{height:22px;width:auto}
.wordmark{min-width:0}
.wordmark .p{
  font-size:var(--t-15);font-weight:700;letter-spacing:.012em;line-height:1.1;
  white-space:nowrap;
}
.wordmark .s{
  font-size:var(--t-11);color:var(--muted);letter-spacing:.22em;
  text-transform:uppercase;margin-top:3px;white-space:nowrap;
}
.divider-v{width:1px;height:34px;background:var(--line);flex:none}

/* Tabs never scroll. If the bar runs short they wrap - a scrollbar on a tab
   bar hides tabs, and a hidden tab is a feature nobody finds. 2026-09-18. */
/* One row, always. Below 1400px the labels give way to the icons that
   already ride in the markup; below 768px the phone tab bar takes over. */
.mainnav{display:flex;flex-wrap:nowrap;gap:2px;flex:1 1 320px;min-width:0}
.mainnav::-webkit-scrollbar{height:0}
.navlink{
  appearance:none;border:1px solid transparent;background:transparent;
  color:var(--muted);font:inherit;font-size:var(--t-13);font-weight:600;
  padding:9px 12px;border-radius:var(--r-sm);cursor:pointer;white-space:nowrap;
  transition:color var(--fast),background var(--fast),border-color var(--fast);
  position:relative;
}
/* Glyph + short label ride along in the markup and stay switched off until the
   phone tab bar needs them. Children never take the click, so the delegated
   `closest('.navlink')` handler in app.js always sees the button. */
.navlink>*{pointer-events:none}
.navlink .ni,.navlink .ns{display:none}
.navlink .ni{
  width:21px;height:21px;flex:none;fill:none;stroke:currentColor;
  stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round;
}
.navlink:hover{color:var(--ink-2);background:rgba(34,49,75,.55)}
.navlink[aria-current="page"]{
  color:var(--ink);background:var(--panel-2);border-color:var(--line-strong);
}
.navlink[aria-current="page"]::after{
  content:"";position:absolute;left:15px;right:15px;bottom:-1px;height:2px;border-radius:2px;
  background:linear-gradient(90deg,var(--boeing-blue),var(--boeing-cyan));
}

.topbar-right{display:flex;align-items:center;gap:12px;margin-left:auto}
/* The show in focus - one control, outside the tabs. 2026-09-18. */
/* "?" tips. One mechanism for every header, section and parameter: hover or
   focus the circle and the explanation appears. 2026-09-18 review. */
.tip{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;
  border-radius:50%;border:1px solid var(--line-strong);color:var(--ink-2);font-size:11px;
  font-weight:700;margin-left:6px;cursor:help;position:relative;vertical-align:middle;
  line-height:1;flex:0 0 auto;font-family:inherit;background:transparent}
.tip:hover,.tip:focus{color:var(--ink);border-color:var(--ink-2);outline:none}
/* The explanation floats on top of everything, placed inside the window by app.js
   (tipShow) - drawn under its "?" by CSS it ran off the page (2026-09-27: "the "?"
   tips are getting cut off"). */
.tipfloat{position:fixed;left:0;top:0;z-index:1000;width:max-content;min-width:180px;
  max-width:min(320px,calc(100vw - 16px));padding:8px 10px;border-radius:8px;background:var(--panel-2);
  color:var(--ink);border:1px solid var(--line);box-shadow:0 8px 24px rgba(0,0,0,.35);
  font-size:var(--t-11);font-weight:400;line-height:1.45;text-align:left;white-space:normal;
  opacity:0;pointer-events:none;transition:opacity .12s}
.tipfloat.is-on{opacity:1}
/* ▶ preview (2026-09-27): a round play button before each asset, and its popup. */
.btn-play{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;flex:0 0 auto;
  border-radius:50%;border:1px solid var(--line-strong);background:transparent;color:var(--ink);
  cursor:pointer;font-size:11px;padding:0;margin-right:8px;vertical-align:middle}
.btn-play:hover,.btn-play:focus{border-color:var(--ink-2);outline:none}
.apv-stage{display:flex;justify-content:center;background:#000;border-radius:10px;overflow:hidden}
.apv-media{display:block;max-width:100%;max-height:68vh}
.apv-audio{width:100%}
.apv-bar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-bottom:10px}
.apv-page,.apv-zoom{font-variant-numeric:tabular-nums;color:var(--ink-2);font-size:var(--t-11)}
.apv-doc{overflow:auto;max-height:70vh;background:var(--panel-2);border-radius:10px;text-align:center;padding:8px}
.apv-doc canvas{display:inline-block;box-shadow:0 2px 12px rgba(0,0,0,.35)}
/* A client's logo or background on a checkerboard, so a transparent logo shows (2026-09-27). */
.apv-checker{background:repeating-conic-gradient(#cfcfcf 0% 25%,#f4f4f4 0% 50%) 50% / 20px 20px}
/* The ▶ beside a client's Logo and Background link (2026-09-27). */
.brandlink-in{display:flex;align-items:center;gap:8px}
.brandlink-in input{flex:1 1 auto;min-width:0}
.brandlink-in .btn-play{margin-right:0}
/* The slices in a device's popup on Shows (2026-09-27). */
.slice-edit input{width:100%;min-width:64px}
/* What a device reports about itself, shown not typed (2026-09-27). */
.factval{padding:8px 0;font-variant-numeric:tabular-nums}
h1 .tip,h2 .tip{font-size:11px}
.swatch{display:inline-block;width:18px;height:18px;border-radius:5px;border:1px solid var(--line-strong);margin-right:4px;vertical-align:middle}
.showpick{display:flex;align-items:center;gap:8px;min-width:0}
.showpick-k{font-size:var(--t-12);color:var(--ink-3);letter-spacing:.04em;text-transform:uppercase;white-space:nowrap}
.showpick select{max-width:220px;padding:6px 28px 6px 10px;font-weight:600}
@media (max-width:640px){.showpick-k{display:none}.showpick select{max-width:150px}}
.livechip{
  display:inline-flex;align-items:center;gap:7px;
  font-size:var(--t-11);color:var(--muted);
  background:var(--panel);border:1px solid var(--line);
  padding:6px 11px;border-radius:var(--r-pill);white-space:nowrap;
}
.livedot{
  width:7px;height:7px;border-radius:99px;background:var(--ok);flex:none;
  box-shadow:0 0 0 0 rgba(31,169,122,.55);animation:ping 2.4s infinite;
}
.livechip.stale .livedot{background:var(--warn);animation:none}
@keyframes ping{
  0%{box-shadow:0 0 0 0 rgba(31,169,122,.5)}
  70%{box-shadow:0 0 0 7px rgba(31,169,122,0)}
  100%{box-shadow:0 0 0 0 rgba(31,169,122,0)}
}
.who{display:flex;align-items:center;gap:10px;min-width:0}
.avatar{
  width:32px;height:32px;border-radius:99px;flex:none;display:grid;place-items:center;
  font-size:var(--t-12);font-weight:750;color:#fff;letter-spacing:.02em;
  background:linear-gradient(140deg,var(--boeing-blue),var(--boeing-cyan));
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.14);
}
.avatar[title]{cursor:default}
.roomblock{padding:6px 2px 4px}
.roomblock+.roomblock{border-top:1px solid var(--line-soft);margin-top:6px;padding-top:10px}
.roomhead{display:flex;align-items:center;gap:6px;padding:2px 2px 8px}
.btn-icon{padding:7px;width:32px;height:32px;justify-content:center}
/* Between a wide desk and a phone: ten tabs still fit on one row as icons. */
@media (max-width:1400px){
  .navlink{padding:9px 10px}
  .navlink .nt{display:none}
  .navlink .ni{display:block;width:18px;height:18px}
  .showpick-k{display:none}
}

main{
  flex:1;width:100%;max-width:1520px;margin:0 auto;min-width:0;
  padding:24px max(22px,env(safe-area-inset-right,0px)) 72px max(22px,env(safe-area-inset-left,0px));
}

.pagehead{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;flex-wrap:wrap;margin-bottom:20px}
.pagehead h1{font-size:var(--t-22);letter-spacing:-.02em}
.pagehead .sub{color:var(--muted);font-size:var(--t-13);margin-top:4px}
.pagehead .acts{display:flex;gap:9px;flex-wrap:wrap}

.foot{
  border-top:1px solid var(--line-soft);color:var(--faint);font-size:var(--t-11);
  padding:16px max(22px,env(safe-area-inset-right,0px)) 16px max(22px,env(safe-area-inset-left,0px));
  max-width:1520px;margin:0 auto;width:100%;
  display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap;
  letter-spacing:.06em;text-transform:uppercase;
}

/* ------------------------------------------------------ 5. CARDS AND TILES */
.card{
  background:linear-gradient(180deg,rgba(20,31,51,.86),rgba(15,24,42,.92));
  border:1px solid var(--line);border-radius:var(--r-lg);
  box-shadow:var(--sh-2);
  position:relative;overflow:hidden;
}
.card::before{                        /* top hairline catch-light */
  content:"";position:absolute;left:0;right:0;top:0;height:1px;
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.10),transparent);
}
.card-h{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:15px 18px;border-bottom:1px solid var(--line-soft);
}
.card-h h2,.card-h h3{
  font-size:var(--t-11);text-transform:uppercase;letter-spacing:.15em;
  color:var(--muted);font-weight:700;
}
.card-b{padding:18px}
.card-b.flush{padding:0}
.card-f{padding:12px 18px;border-top:1px solid var(--line-soft);color:var(--muted);font-size:var(--t-12)}

.grid{display:grid;gap:16px}
.g-4{grid-template-columns:repeat(4,minmax(0,1fr))}
.g-3{grid-template-columns:repeat(3,minmax(0,1fr))}
.g-2{grid-template-columns:repeat(2,minmax(0,1fr))}
.g-2-1{grid-template-columns:minmax(0,1.55fr) minmax(0,1fr)}
.span-2{grid-column:span 2}

/* stat tile */
.tile{
  background:linear-gradient(165deg,rgba(27,41,66,.85),rgba(15,24,42,.95));
  border:1px solid var(--line);border-radius:var(--r-lg);
  padding:17px 18px 16px;position:relative;overflow:hidden;
  box-shadow:var(--sh-2);
  transition:border-color var(--med),transform var(--med);
}
/* No hover lift on a tile: nothing happens when you click one, and a thing
   that lights up is a thing that promises to do something. 2026-09-18. */
.tile::after{                          /* corner light, brand gradient */
  content:"";position:absolute;right:-38px;top:-46px;width:130px;height:130px;border-radius:99px;
  background:radial-gradient(circle,rgba(13,124,177,.26),transparent 66%);
  pointer-events:none;
}
.tile .k{
  font-size:var(--t-11);text-transform:uppercase;letter-spacing:.14em;
  color:var(--muted);font-weight:700;display:flex;align-items:center;gap:8px;
}
.tile .k svg{width:15px;height:15px;flex:none;stroke:var(--cyan-ink);fill:none;stroke-width:1.7;opacity:.9}
.search svg,.empty .ico svg,.expander svg{stroke-linecap:round;stroke-linejoin:round}
.tile .v{
  font-size:var(--t-36);font-weight:700;letter-spacing:-.03em;line-height:1.05;
  margin-top:10px;font-variant-numeric:tabular-nums;
}
.tile .v small{font-size:var(--t-18);color:var(--muted);font-weight:600;letter-spacing:0}
.tile .m{font-size:var(--t-12);color:var(--muted);margin-top:6px}
.tile .spark{display:flex;align-items:flex-end;gap:3px;height:26px;margin-top:12px}
.tile .spark i{
  flex:1;border-radius:2px 2px 0 0;min-height:2px;
  background:linear-gradient(180deg,var(--boeing-cyan),rgba(13,124,177,.18));
}

/* meter bar */
.meter{height:6px;border-radius:99px;background:var(--panel-3);overflow:hidden;margin-top:12px}
.meter i{display:block;height:100%;border-radius:99px;background:linear-gradient(90deg,var(--boeing-blue),var(--boeing-cyan));transition:width .6s var(--ease)}
.meter.warn i{background:linear-gradient(90deg,#8a6a12,var(--warn))}
.meter.bad  i{background:linear-gradient(90deg,#7d2b27,var(--bad))}

/* -------------------------------------------------------------- 6. BUTTONS */
.btn{
  appearance:none;font:inherit;font-size:var(--t-13);font-weight:650;
  display:inline-flex;align-items:center;justify-content:center;gap:8px;
  padding:10px 16px;border-radius:var(--r-sm);cursor:pointer;white-space:nowrap;
  border:1px solid var(--line-strong);
  background:linear-gradient(180deg,var(--panel-3),var(--panel-2));
  color:var(--ink);
  transition:border-color var(--fast),background var(--fast),transform var(--fast),box-shadow var(--fast);
  box-shadow:var(--sh-1);
}
.btn:hover:not(:disabled){border-color:var(--boeing-cyan);background:linear-gradient(180deg,#20304c,#182642)}
.btn:active:not(:disabled){transform:translateY(1px)}
.btn:disabled{opacity:.42;cursor:not-allowed;box-shadow:none}
.btn-primary{
  border-color:transparent;color:#fff;
  background:linear-gradient(135deg,var(--boeing-blue) 0%,#1656B8 52%,var(--boeing-cyan) 100%);
  box-shadow:0 4px 16px rgba(26,64,159,.42), inset 0 1px 0 rgba(255,255,255,.18);
}
.btn-primary:hover:not(:disabled){
  background:linear-gradient(135deg,#1F4CB8 0%,#1866CE 52%,#1290CC 100%);
  border-color:transparent;box-shadow:0 6px 22px rgba(13,124,177,.46), inset 0 1px 0 rgba(255,255,255,.22);
}
.btn-ghost{background:transparent;border-color:var(--line);color:var(--ink-2)}
.btn-ghost:hover:not(:disabled){background:var(--panel-2);border-color:var(--line-strong)}
.btn-danger{background:transparent;border-color:rgba(199,68,62,.5);color:var(--bad-ink)}
.btn-danger:hover:not(:disabled){background:rgba(199,68,62,.14);border-color:var(--bad)}
.btn-danger-solid{background:linear-gradient(180deg,#C7443E,#9E3630);border-color:transparent;color:#fff}
.btn-sm{padding:6px 11px;font-size:var(--t-12);border-radius:var(--r-xs)}
.btn-lg{padding:13px 22px;font-size:var(--t-15);border-radius:var(--r)}
.btn-block{width:100%}
.btn-icon{padding:7px;width:32px;height:32px}
.btn .spin{width:14px;height:14px;flex:none}

/* segmented filter chips */
.seg{display:inline-flex;background:var(--panel);border:1px solid var(--line);border-radius:var(--r-sm);padding:3px;gap:2px}
.seg button{
  appearance:none;border:0;background:transparent;color:var(--muted);font:inherit;
  font-size:var(--t-12);font-weight:600;padding:6px 13px;border-radius:var(--r-xs);cursor:pointer;
  transition:background var(--fast),color var(--fast);white-space:nowrap;
}
.seg button:hover{color:var(--ink-2)}
.seg button[aria-pressed="true"]{background:var(--boeing-blue);color:#fff;box-shadow:var(--sh-1)}

/* ---------------------------------------------------------------- 7. FORMS */
.field{margin-bottom:14px}
label,.lbl{
  display:block;font-size:var(--t-12);font-weight:650;color:var(--ink-2);
  margin-bottom:6px;letter-spacing:.01em;
}
.hint{font-size:var(--t-12);color:var(--muted);margin-top:6px}
input[type=text],input[type=password],input[type=search],input[type=email],
input[type=number],input[type=url],select,textarea{
  width:100%;font:inherit;font-size:var(--t-14);color:var(--ink);
  background:var(--ground-2);
  border:1px solid var(--line-strong);border-radius:var(--r-sm);
  padding:10px 12px;
  transition:border-color var(--fast),box-shadow var(--fast),background var(--fast);
}
input::placeholder,textarea::placeholder{color:var(--faint)}
input:hover:not(:disabled),select:hover:not(:disabled),textarea:hover:not(:disabled){border-color:#38507A}
input:focus,select:focus,textarea:focus{
  outline:none;border-color:var(--boeing-cyan);box-shadow:var(--focus);background:#0C1524;
}
input:disabled,select:disabled,textarea:disabled{opacity:.5;cursor:not-allowed}
input[aria-invalid="true"]{border-color:var(--bad);box-shadow:0 0 0 3px rgba(199,68,62,.25)}
textarea{min-height:84px;resize:vertical;line-height:1.5}
select{
  appearance:none;cursor:pointer;padding-right:34px;
  background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),
                   linear-gradient(135deg,var(--muted) 50%,transparent 50%);
  background-position:calc(100% - 17px) 50%,calc(100% - 12px) 50%;
  background-size:5px 5px,5px 5px;background-repeat:no-repeat;
}
input[type=checkbox],input[type=radio]{
  width:16px;height:16px;accent-color:var(--boeing-cyan);margin:0;cursor:pointer;
}
.check{display:flex;align-items:center;gap:9px;font-size:var(--t-13);color:var(--ink-2);cursor:pointer}
.check label{margin:0;cursor:pointer;font-weight:500}

.search{position:relative}
.search input{padding-left:36px}
.search svg{
  position:absolute;left:11px;top:50%;transform:translateY(-50%);
  width:15px;height:15px;stroke:var(--faint);fill:none;stroke-width:2;pointer-events:none;
}
.formrow{display:grid;gap:14px;grid-template-columns:repeat(2,minmax(0,1fr))}
.formrow-3{display:grid;gap:14px;grid-template-columns:repeat(3,minmax(0,1fr))}
.err-inline{
  color:var(--bad-ink);font-size:var(--t-12);margin-top:6px;display:flex;gap:6px;align-items:flex-start;
}
.toolbar{
  display:flex;gap:10px;align-items:center;flex-wrap:wrap;
  padding:13px 16px;background:var(--panel);border:1px solid var(--line);
  border-radius:var(--r-lg);margin-bottom:16px;
}
.toolbar .grow{flex:1 1 220px;min-width:170px}
.toolbar .spacer{flex:1}
/* Filter controls. The widths live here rather than in inline style attributes
   so a media query can actually beat them — an inline width is unreachable
   from a stylesheet without !important, and that is how toolbars end up wider
   than the phone. */
.toolbar .tb-field{flex:0 1 auto;min-width:0}
.toolbar .tb-field select,.toolbar .tb-field input{width:100%;min-width:0}
.toolbar .tb-field.w-sm{flex-basis:132px}
.toolbar .tb-field.w-md{flex-basis:152px}

/* --------------------------------------------------------------- 8. TABLES */
.tablewrap{
  border:1px solid var(--line);border-radius:var(--r-lg);overflow:auto;
  background:var(--panel);box-shadow:var(--sh-2);max-width:100%;
}
table{width:100%;border-collapse:separate;border-spacing:0;font-size:var(--t-13)}
thead th{
  position:sticky;top:0;z-index:2;
  text-align:left;font-size:var(--t-11);font-weight:700;
  text-transform:uppercase;letter-spacing:.11em;color:var(--muted);
  padding:12px 15px;white-space:nowrap;
  background:linear-gradient(180deg,#16223a,#121c30);
  border-bottom:1px solid var(--line-strong);
}
tbody td{
  padding:12px 15px;border-bottom:1px solid var(--line-soft);vertical-align:middle;
  font-variant-numeric:tabular-nums;
}
tbody tr:last-child td{border-bottom:0}
tbody tr{transition:background var(--fast)}
tbody tr:hover{background:rgba(34,49,75,.42)}
tbody tr.is-open{background:rgba(26,64,159,.14)}
td .t1{font-weight:650;color:var(--ink);font-variant-numeric:normal}
td .t2{font-size:var(--t-12);color:var(--muted);margin-top:2px;font-variant-numeric:normal}
.num{text-align:right}
.nowrap{white-space:nowrap}
.mono{font-family:var(--mono);font-size:var(--t-12);color:var(--ink-2);letter-spacing:-.01em}
.rowacts{display:flex;gap:6px;justify-content:flex-end}
/* Direct child only: as a descendant rule this stripped the padding from every
   cell of the room tables nested inside the expander. 2026-09-20. */
.subrow>td{background:rgba(7,12,24,.55);padding:0;border-bottom:1px solid var(--line-soft)}
.subwrap{padding:14px 18px 16px}

/* ---------------------------------------------------------- 9. PILLS/BADGES */
.pill{
  display:inline-flex;align-items:center;gap:6px;
  font-size:var(--t-11);font-weight:700;letter-spacing:.07em;text-transform:uppercase;
  padding:4px 10px;border-radius:var(--r-pill);white-space:nowrap;
  border:1px solid transparent;
}
.pill i{width:6px;height:6px;border-radius:99px;background:currentColor;flex:none}
.pill.ok  {background:var(--ok-soft);  color:var(--ok-ink);  border-color:rgba(31,169,122,.34)}
.pill.warn{background:var(--warn-soft);color:var(--warn-ink);border-color:rgba(201,138,24,.34)}
.pill.bad {background:var(--bad-soft); color:var(--bad-ink); border-color:rgba(199,68,62,.36)}
.pill.info{background:var(--info-soft);color:var(--cyan-ink);border-color:rgba(13,124,177,.36)}
.pill.brand{background:var(--blue-soft);color:var(--brand-ink);border-color:rgba(26,64,159,.45)}
.pill.mute{background:rgba(57,73,87,.30);color:var(--muted);border-color:var(--line-strong)}
.pill.ok i{box-shadow:0 0 0 3px rgba(31,169,122,.18)}
.tag{
  display:inline-block;font-size:var(--t-11);color:var(--ink-2);
  background:var(--panel-3);border:1px solid var(--line);border-radius:var(--r-xs);
  padding:2px 8px;font-weight:600;
}

/* health strip on the dashboard */
.health{display:flex;flex-direction:column}
.health-row{
  display:flex;align-items:center;gap:12px;padding:11px 18px;
  border-bottom:1px solid var(--line-soft);
}
.health-row:last-child{border-bottom:0}
.health-row .nm{flex:1;min-width:0}
.health-row .nm b{display:block;font-size:var(--t-13);font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.health-row .nm span{font-size:var(--t-11);color:var(--muted)}
.status-bar{width:3px;align-self:stretch;border-radius:99px;flex:none}
.status-bar.ok{background:var(--ok)} .status-bar.warn{background:var(--warn)} .status-bar.bad{background:var(--bad)}

/* activity feed */
.feed{max-height:420px;overflow:auto}
.feed-item{
  display:flex;gap:12px;padding:12px 18px;border-bottom:1px solid var(--line-soft);
  animation:fadein .3s var(--ease);
}
.feed-item:last-child{border-bottom:0}
.feed-dot{width:8px;height:8px;border-radius:99px;margin-top:6px;flex:none;background:var(--cyan-ink)}
.feed-dot.warn{background:var(--warn)} .feed-dot.bad{background:var(--bad)} .feed-dot.ok{background:var(--ok)}
.feed-body{min-width:0;flex:1}
.feed-body .l1{font-size:var(--t-13);color:var(--ink);word-break:break-word}
.feed-body .l2{font-size:var(--t-11);color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}
@keyframes fadein{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}

/* --------------------------------------------------------------- 10. STATES */
.empty{
  padding:56px 26px;text-align:center;color:var(--muted);
}
.empty .ico{
  width:52px;height:52px;margin:0 auto 14px;border-radius:var(--r);
  display:grid;place-items:center;background:var(--panel-2);border:1px solid var(--line);
}
.empty .ico svg{width:24px;height:24px;stroke:var(--cyan-ink);fill:none;stroke-width:1.6}
.empty h3{font-size:var(--t-15);color:var(--ink);margin-bottom:6px}
.empty p{font-size:var(--t-13);max-width:400px;margin:0 auto 14px}

.sk{
  position:relative;overflow:hidden;border-radius:var(--r-xs);
  background:linear-gradient(90deg,var(--panel-2) 0%,var(--panel-3) 50%,var(--panel-2) 100%);
  background-size:220% 100%;animation:shimmer 1.35s linear infinite;
}
@keyframes shimmer{from{background-position:120% 0}to{background-position:-120% 0}}
.sk-line{height:11px;margin:9px 0}
.sk-tile{height:118px;border-radius:var(--r-lg)}
.sk-row{height:16px}
.sk-table{padding:6px 15px}
.sk-table .r{display:flex;gap:14px;padding:11px 0;border-bottom:1px solid var(--line-soft)}
.sk-table .r>span{height:12px;border-radius:4px}

.spin{
  width:16px;height:16px;border-radius:99px;display:inline-block;
  border:2px solid rgba(255,255,255,.22);border-top-color:#fff;
  animation:rot .7s linear infinite;
}
@keyframes rot{to{transform:rotate(360deg)}}
.loading-bar{
  position:fixed;left:0;top:0;height:2px;z-index:120;
  background:linear-gradient(90deg,var(--boeing-blue),var(--boeing-cyan));
  width:0;opacity:0;transition:width .3s var(--ease),opacity .3s
}
.loading-bar.on{opacity:1}

/* --------------------------------------------------------------- 11. TOASTS */
.toasts{
  position:fixed;z-index:150;
  right:max(20px,env(safe-area-inset-right,0px));
  bottom:calc(20px + env(safe-area-inset-bottom,0px));
  display:flex;flex-direction:column;gap:10px;align-items:flex-end;pointer-events:none;
  max-width:min(400px,calc(100% - 40px));
}
.toast{
  pointer-events:auto;display:flex;gap:11px;align-items:flex-start;
  background:linear-gradient(180deg,#182642,#111c31);
  border:1px solid var(--line-strong);border-left:3px solid var(--boeing-cyan);
  border-radius:var(--r);padding:12px 15px;box-shadow:var(--sh-3);
  font-size:var(--t-13);animation:toastin .3s var(--ease);
  max-width:100%;
}
.toast.ok{border-left-color:var(--ok)}
.toast.bad{border-left-color:var(--bad)}
/* The error notice (2026-10-06): red, small, clickable - it opens the Activity log on errors. */
.toast.err-pop{cursor:pointer;border-color:var(--bad);border-left-width:4px;background:linear-gradient(180deg,#3a1418,#2a0f12)}
.toast.err-pop .tx b{color:#ff8b8b}
/* An error row in the Activity log, in red text (his "shown in the activity log in red text"). */
tr.is-error td, tr.is-error .t1, tr.is-error .t2{color:var(--bad)}
.toast.warn{border-left-color:var(--warn)}
/* A hint or a progress note - stated, so every kind toast() takes has a look (2026-10-07). */
.toast.info{border-left-color:var(--boeing-cyan)}
.toast .tx{min-width:0;word-break:break-word}
.toast .tx b{display:block;margin-bottom:2px}
.toast .x{
  margin-left:auto;background:none;border:0;color:var(--faint);cursor:pointer;
  font-size:var(--t-16);line-height:1;padding:0 2px;
}
.toast .x:hover{color:var(--ink)}
.toast.out{animation:toastout .22s var(--ease) forwards}
@keyframes toastin{from{opacity:0;transform:translateY(12px) scale(.97)}to{opacity:1;transform:none}}
@keyframes toastout{to{opacity:0;transform:translateX(18px)}}

/* ---------------------------------------------------------------- 12. MODAL */
.modal-bg{
  position:fixed;inset:0;z-index:100;display:grid;place-items:center;padding:22px;
  background:rgba(4,7,14,.72);backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);
  animation:fadein .18s var(--ease);
}
.modal{
  width:100%;max-width:600px;max-height:88vh;overflow:auto;
  background:linear-gradient(180deg,#152039,#0E1728);
  border:1px solid var(--line-strong);border-radius:var(--r-xl);
  box-shadow:var(--sh-4);animation:modalin .24s var(--ease);
}
.modal.wide{max-width:840px}
.modal.narrow{max-width:440px}
@keyframes modalin{from{opacity:0;transform:translateY(14px) scale(.985)}to{opacity:1;transform:none}}
/* Phones get the same dialog as a bottom sheet — it rises from the thumb, not
   from the middle of the glass. Wired up in §15. */
@keyframes sheetin{from{opacity:0;transform:translateY(30px)}to{opacity:1;transform:none}}
.modal-h{
  display:flex;align-items:flex-start;justify-content:space-between;gap:14px;
  padding:20px 22px 14px;border-bottom:1px solid var(--line-soft);
}
.modal-h h2{font-size:var(--t-18)}
.modal-h .sub{color:var(--muted);font-size:var(--t-12);margin-top:4px}
.modal-b{padding:20px 22px}
.modal-f{
  padding:16px 22px;border-top:1px solid var(--line-soft);
  display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap;
  position:sticky;bottom:0;background:linear-gradient(180deg,rgba(14,23,40,.8),#0E1728);
}
.modal .x{
  background:none;border:0;color:var(--faint);cursor:pointer;font-size:1.4rem;line-height:1;padding:0 4px;
}
.modal .x:hover{color:var(--ink)}
.danger-note{
  background:var(--bad-soft);border:1px solid rgba(199,68,62,.35);
  border-radius:var(--r-sm);padding:12px 14px;font-size:var(--t-13);color:#FFC3BE;
}

/* ---------------------------------------------------------------- 13. LOGIN */
.login{
  position:relative;z-index:2;
  min-height:100vh;min-height:100dvh;
  display:grid;place-items:center;
  padding:34px max(20px,env(safe-area-inset-right,0px))
          max(34px,env(safe-area-inset-bottom,0px)) max(20px,env(safe-area-inset-left,0px));
}
.login-sky{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.login-sky .glow{
  position:absolute;border-radius:99px;filter:blur(70px);opacity:.55;
}
.login-sky .g1{width:640px;height:640px;left:-14%;top:-22%;background:rgba(26,64,159,.55);animation:float1 26s var(--ease) infinite alternate}
.login-sky .g2{width:560px;height:560px;right:-12%;top:8%;background:rgba(13,124,177,.38);animation:float2 32s var(--ease) infinite alternate}
.login-sky .g3{width:720px;height:420px;left:26%;bottom:-24%;background:rgba(57,73,87,.55);animation:float1 38s var(--ease) infinite alternate-reverse}
@keyframes float1{to{transform:translate3d(7%,5%,0) scale(1.10)}}
@keyframes float2{to{transform:translate3d(-6%,7%,0) scale(1.08)}}
.login-sky .stars{
  position:absolute;inset:0;opacity:.85;
  background:
    radial-gradient(1.4px 1.4px at 12% 18%,rgba(253,253,254,.55),transparent),
    radial-gradient(1.2px 1.2px at 78% 12%,rgba(79,179,232,.60),transparent),
    radial-gradient(1.1px 1.1px at 34% 74%,rgba(253,253,254,.40),transparent),
    radial-gradient(1.3px 1.3px at 63% 56%,rgba(124,155,232,.50),transparent),
    radial-gradient(1px 1px at 88% 78%,rgba(253,253,254,.35),transparent),
    radial-gradient(1.2px 1.2px at 22% 90%,rgba(79,179,232,.40),transparent),
    radial-gradient(1px 1px at 52% 30%,rgba(253,253,254,.30),transparent),
    radial-gradient(1px 1px at 8% 56%,rgba(253,253,254,.28),transparent);
  animation:twinkle 7s ease-in-out infinite alternate;
}
@keyframes twinkle{from{opacity:.45}to{opacity:.95}}
.login-sky .grid{
  position:absolute;inset:0;display:block;
  background-image:
    linear-gradient(rgba(79,179,232,.10) 1px,transparent 1px),
    linear-gradient(90deg,rgba(79,179,232,.10) 1px,transparent 1px);
  background-size:78px 78px;
  mask-image:radial-gradient(90% 70% at 50% 50%,#000,transparent 72%);
  -webkit-mask-image:radial-gradient(90% 70% at 50% 50%,#000,transparent 72%);
}
.login-sky .arc{                       /* slow contrail sweeping the sky */
  position:absolute;left:-30%;top:34%;width:160%;height:2px;
  background:linear-gradient(90deg,transparent,rgba(79,179,232,.0) 18%,rgba(79,179,232,.55) 50%,rgba(255,255,255,.0) 82%,transparent);
  transform:rotate(-11deg);opacity:.5;animation:sweep 19s linear infinite;
}
@keyframes sweep{
  0%{transform:rotate(-11deg) translateX(-24%);opacity:0}
  12%{opacity:.55}
  88%{opacity:.4}
  100%{transform:rotate(-11deg) translateX(24%);opacity:0}
}

.login-inner{position:relative;z-index:2;width:100%;max-width:412px;text-align:center}
/* No plate. The mark is white on transparent, so it belongs straight on the
   sky — a badge behind it would only cut a bright rectangle out of the night. */
.login-lockup{
  display:inline-grid;place-items:center;margin-bottom:22px;max-width:100%;
}
.login-lockup img{height:40px;width:auto;max-width:100%}
.login-title{font-size:var(--t-22);font-weight:700;letter-spacing:-.02em}
.login-sub{
  color:var(--muted);font-size:var(--t-11);letter-spacing:.28em;text-transform:uppercase;margin-top:7px;
}
.login-card{
  margin-top:24px;text-align:left;
  background:linear-gradient(180deg,rgba(21,32,57,.92),rgba(12,20,36,.96));
  border:1px solid var(--line-strong);border-radius:var(--r-xl);
  padding:26px 26px 24px;box-shadow:var(--sh-4);
  backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
}
.login-card.shake{animation:shake .4s var(--ease)}
@keyframes shake{
  0%,100%{transform:translateX(0)}
  18%{transform:translateX(-8px)} 38%{transform:translateX(7px)}
  58%{transform:translateX(-5px)} 78%{transform:translateX(3px)}
}
.login-err{
  display:flex;gap:9px;align-items:flex-start;
  background:var(--bad-soft);border:1px solid rgba(199,68,62,.4);
  color:#FFC3BE;border-radius:var(--r-sm);padding:11px 13px;
  font-size:var(--t-13);margin-bottom:16px;animation:fadein .2s var(--ease);
}
.login-foot{
  margin-top:20px;color:var(--faint);font-size:var(--t-11);
  letter-spacing:.14em;text-transform:uppercase;
}

/* ------------------------------------------------------------ 14. UTILITIES */
.muted{color:var(--muted)}
.faint{color:var(--faint)}
.small{font-size:var(--t-12)}
.tiny{font-size:var(--t-11)}
.b{font-weight:650}
.center{text-align:center}
.right{text-align:right}
.row{display:flex;align-items:center;gap:10px}
.row.wrap{flex-wrap:wrap}
.col{display:flex;flex-direction:column;gap:10px}
.stack>*+*{margin-top:14px}
.mt-0{margin-top:0}.mt-8{margin-top:8px}.mt-16{margin-top:16px}.mt-24{margin-top:24px}
.mb-0{margin-bottom:0}.mb-8{margin-bottom:8px}.mb-16{margin-bottom:16px}
.hr{height:1px;background:var(--line-soft);border:0;margin:18px 0}
.hr-brand{
  height:1px;border:0;margin:0;
  background:linear-gradient(90deg,transparent,var(--boeing-blue) 22%,var(--boeing-cyan) 62%,transparent);
  opacity:.7;
}
.pagination{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:12px 16px;flex-wrap:wrap;color:var(--muted);font-size:var(--t-12);
}
.pagination .pg{display:flex;gap:7px;align-items:center}
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0;
}
.expander{
  background:none;border:0;color:var(--muted);cursor:pointer;padding:4px;
  display:inline-flex;align-items:center;justify-content:center;
  transition:transform var(--fast),color var(--fast);
}
.expander:hover{color:var(--cyan-ink)}
.expander[aria-expanded="true"]{transform:rotate(90deg);color:var(--cyan-ink)}
.expander svg{width:14px;height:14px;stroke:currentColor;fill:none;stroke-width:2.2}

/* ---------------------------------------------------------- 15. RESPONSIVE */
/* Read this section top-down: desktop → large tablet → tablet → PHONE → small
   phone. The phone block is the biggest one on purpose — a phone held in
   portrait is the primary device for this console, and the rule it has to obey
   is absolute: the PAGE never scrolls sideways. Wide things scroll inside their
   own box or stop being wide. */

/* Large tablet / small laptop. */
@media (max-width:1240px){
  .g-4{grid-template-columns:repeat(2,minmax(0,1fr))}
  .g-2-1{grid-template-columns:minmax(0,1fr)}
}

/* Tablet. */
@media (max-width:980px){
  .g-3,.g-2{grid-template-columns:minmax(0,1fr)}
  .span-2{grid-column:auto}
  main{padding:20px max(16px,env(safe-area-inset-right,0px)) 64px max(16px,env(safe-area-inset-left,0px))}
  .topbar-in{padding-top:10px;padding-bottom:10px;gap:12px}
  .wordmark .s{display:none}
  .who-name{max-width:130px}
}

/* ════════════════════════════════════════════ PHONES — the primary device ══ */
@media (max-width:767.98px){

  /* ---------------------------------------------------------- page frame -- */
  main{
    padding:16px max(14px,env(safe-area-inset-right,0px)) 18px max(14px,env(safe-area-inset-left,0px));
  }
  .grid{gap:12px}
  .pagehead{margin-bottom:15px;gap:12px;align-items:stretch}
  .pagehead h1{font-size:var(--t-18)}
  .pagehead .sub{font-size:var(--t-12)}
  .pagehead .acts{width:100%;gap:8px}
  .pagehead .acts>.btn{flex:1 1 auto}

  /* The bottom tab bar floats over the page, so the last thing on it needs
     room to clear the bar AND the iPhone home indicator. */
  .foot{
    padding:14px max(14px,env(safe-area-inset-left,0px))
            calc(80px + env(safe-area-inset-bottom,0px));
    font-size:10px;letter-spacing:.05em;
  }

  /* ------------------------------------------------------------- top bar -- */
  /* backdrop-filter is switched OFF here deliberately. A filtered element
     becomes the containing block for its position:fixed descendants, which
     would pin the tab bar inside this 56px-tall header instead of to the
     viewport. Same reason you cannot hang a picture on a wall that is itself
     hanging on a hook — so the header gets a solid paint instead of a blur. */
  .topbar{
    backdrop-filter:none;-webkit-backdrop-filter:none;
    background:linear-gradient(180deg,#0A1120 0%,rgba(10,17,32,.97) 100%);
  }
  .topbar-in{
    padding:9px max(14px,env(safe-area-inset-right,0px)) 9px max(14px,env(safe-area-inset-left,0px));
    gap:10px;flex-wrap:nowrap;
  }
  /* The lockup is the only elastic item: it truncates rather than shoving the
     identity cluster off the right edge. */
  .lockup{flex:1 1 auto;min-width:0;overflow:hidden;gap:9px}
  .logo-mark img{height:20px}
  .wordmark{display:block;overflow:hidden}
  .wordmark .p{
    display:block;font-size:var(--t-13);
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  }
  .divider-v{display:none}
  .topbar-right{flex:none;gap:8px}
  .who-txt{display:none}
  .avatar{width:34px;height:34px}
  #signout{width:44px;height:44px;min-height:44px;padding:0;flex:none}
  #signout .btn-t{display:none}
  #signout svg{width:17px;height:17px}
  .livechip{padding:6px 10px;gap:6px}

  /* ------------------------------------------------ nav → bottom tab bar -- */
  /* Thumb-reachable, always visible, and the active section is unmistakable.
     Same six buttons as the desktop header — one nav element, two skins. */
  .mainnav{
    position:fixed;left:0;right:0;bottom:0;z-index:5;
    display:grid;grid-auto-flow:column;grid-auto-columns:1fr;
    gap:2px;flex:none;order:0;overflow:visible;
    padding:6px max(6px,env(safe-area-inset-left,0px))
            calc(6px + env(safe-area-inset-bottom,0px));
    background:linear-gradient(180deg,rgba(10,17,32,.97) 0%,#070C18 72%);
    border-top:1px solid var(--line);
    box-shadow:0 -12px 34px rgba(0,0,0,.5);
  }
  .navlink{
    display:flex;flex-direction:column;align-items:center;justify-content:center;
    gap:4px;min-height:52px;padding:6px 2px;
    font-size:10px;font-weight:650;line-height:1;letter-spacing:.01em;
    border-radius:var(--r-sm);
  }
  .navlink .ni{display:block}
  .navlink .ns{display:block}
  .navlink .nt{display:none}
  .navlink:hover{background:transparent}
  .navlink[aria-current="page"]{
    color:var(--ink);background:rgba(26,64,159,.32);border-color:transparent;
  }
  .navlink[aria-current="page"] .ni{stroke:var(--cyan-ink)}
  .navlink[aria-current="page"]::after{
    left:26%;right:26%;top:-6px;bottom:auto;height:3px;
  }

  /* -------------------------------------------------------------- tiles -- */
  /* Two up. A phone-wide column of giant cards buries the fourth stat below
     the fold for no gain. */
  .g-4{grid-template-columns:repeat(2,minmax(0,1fr))}
  .tile{padding:13px 13px 12px;border-radius:var(--r)}
  .tile::after{right:-30px;top:-38px;width:100px;height:100px}
  .tile .k{font-size:10px;letter-spacing:.1em;gap:6px}
  .tile .k svg{width:13px;height:13px}
  .tile .v{font-size:1.5rem;margin-top:8px}
  .tile .v small{font-size:var(--t-13)}
  .tile .m{font-size:var(--t-11);margin-top:5px}
  .tile .spark{height:20px;margin-top:9px}
  .meter{margin-top:9px}

  /* -------------------------------------------------------------- cards -- */
  .card{border-radius:var(--r)}
  .card-h{padding:13px 14px;flex-wrap:wrap;gap:8px}
  .card-b{padding:14px}
  .card-f{padding:11px 14px}
  .empty{padding:40px 18px}
  .empty p{font-size:var(--t-12)}
  .feed{max-height:none}
  .feed-item{padding:11px 14px}

  /* device health: names and pills wrap instead of clipping or pushing wide */
  .health-row{flex-wrap:wrap;gap:9px 10px;padding:11px 14px}
  .health-row .nm{flex:1 1 55%;min-width:0}
  .health-row .nm b{white-space:normal;overflow:visible;text-overflow:clip;line-height:1.3}
  .health-row .pill{flex:none;margin-left:auto}
  .status-bar{min-height:34px}

  /* ────────────────────────── TABLES BECOME STACKED CARDS ───────────────── */
  /* This is the change that kills the sideways scrolling. A six-column audit
     table cannot be made narrow — so on a phone it stops being a table. Each
     row becomes its own card: a headline, then labelled field rows, then the
     actions. Like a spreadsheet row turned into an index card. The real table
     comes straight back at 768px (see the end of this block). */
  .tw-cards{
    border:0;background:transparent;box-shadow:none;border-radius:0;overflow:visible;
  }
  .tw-cards>table,.tw-cards tbody{display:block;width:100%}
  .tw-cards thead{display:none}

  .tw-cards tbody tr{
    display:flex;flex-direction:column;position:relative;overflow:hidden;
    background:linear-gradient(180deg,rgba(20,31,51,.88),rgba(15,24,42,.94));
    border:1px solid var(--line);border-radius:var(--r);
    box-shadow:var(--sh-1);margin:0 0 10px;
  }
  .tw-cards tbody tr:last-child{margin-bottom:0}
  .tw-cards tbody tr:hover{background:linear-gradient(180deg,rgba(20,31,51,.88),rgba(15,24,42,.94))}
  .tw-cards tbody tr.is-open{border-color:var(--line-strong);background:rgba(26,64,159,.16)}

  /* a field row: label parked on the left, value filling the rest */
  .tw-cards tbody td{
    display:block;width:100%;min-height:40px;
    padding:9px 14px 9px 40%;position:relative;
    border-bottom:1px solid var(--line-soft);
    text-align:left;white-space:normal;overflow-wrap:anywhere;
    font-variant-numeric:normal;
  }
  .tw-cards tbody td::before{
    content:attr(data-label);
    position:absolute;left:14px;top:11px;width:calc(40% - 24px);
    font-size:var(--t-11);font-weight:700;letter-spacing:.08em;line-height:1.2;
    text-transform:uppercase;color:var(--muted);
  }
  .tw-cards tbody tr>td:last-child{border-bottom:0}

  /* the card's headline — full width, no label, always first */
  .tw-cards tbody td.cell-title{
    order:-1;padding:12px 50px 11px 14px;
    background:rgba(26,64,159,.14);border-bottom:1px solid var(--line);
  }
  .tw-cards tbody td.cell-title::before{content:none}
  .tw-cards tbody td.cell-title .t1{font-size:var(--t-15);line-height:1.3}

  /* the expander is pinned to the card's corner instead of owning a row */
  .tw-cards tbody td.cell-x{
    position:absolute;right:3px;top:3px;z-index:1;
    width:44px;min-height:0;padding:0;border:0;background:none;
  }
  .tw-cards tbody td.cell-x::before{content:none}

  /* row actions become a full-width button bar at the foot of the card */
  .tw-cards tbody td.cell-acts{
    order:9;padding:11px 14px;border-bottom:0;background:rgba(7,12,24,.45);
  }
  .tw-cards tbody td.cell-acts::before{content:none}
  .tw-cards td.cell-acts .rowacts{justify-content:stretch;gap:8px}
  .tw-cards td.cell-acts .rowacts>.btn{flex:1 1 0;min-width:0;font-size:var(--t-13)}

  /* an expanded show's display list is a nested card stack, not a nested box */
  .tw-cards tbody tr.subrow{
    display:block;background:none;border:0;box-shadow:none;border-radius:0;
    overflow:visible;margin:-4px 0 10px;
  }
  .tw-cards tbody tr.subrow>td{
    display:block;padding:0;border:0;min-height:0;background:none;
  }
  .tw-cards tbody tr.subrow>td::before{content:none}
  .subwrap{padding:0}
  .subwrap .muted.small{padding:2px 2px 6px !important}

  /* paging sits under the stack as its own strip */
  .tw-cards .pagination{
    background:var(--panel);border:1px solid var(--line);border-radius:var(--r);
    margin-top:10px;padding:12px 14px;gap:10px;
  }
  .tw-cards .pagination .pg{flex-wrap:wrap;gap:8px;width:100%}
  .tw-cards .pagination .pg>select{flex:1 1 100%;width:100% !important}
  .tw-cards .pagination .pg>.btn{flex:1 1 40%}

  /* skeletons keep their boxed look while loading */
  .sk-table{padding:4px 12px}
  .sk-table .r{gap:10px}

  /* ------------------------------------------------------------ toolbar -- */
  .toolbar{
    flex-direction:column;align-items:stretch;gap:9px;
    padding:12px;border-radius:var(--r);margin-bottom:14px;
  }
  .toolbar .grow,.toolbar .tb-field{flex:1 1 auto;width:100%;min-width:0}
  .toolbar .tb-field.w-sm,.toolbar .tb-field.w-md{flex-basis:auto}
  .toolbar .spacer{display:none}
  .toolbar>.muted.small{text-align:center}
  .toolbar>.btn{width:100%}
  .seg{display:grid;grid-auto-flow:column;grid-auto-columns:1fr;width:100%}
  .seg button{padding:11px 6px;min-height:44px}

  /* -------------------------------------------------- forms and controls -- */
  /* 16px is not a taste call: iOS Safari zooms the whole page when a focused
     input is smaller, and that zoom is what leaves the user scrolled sideways
     with no way back. */
  input[type=text],input[type=password],input[type=search],input[type=email],
  input[type=number],input[type=url],select,textarea{
    font-size:16px;padding:12px 13px;min-height:46px;
    -webkit-appearance:none;appearance:none;border-radius:var(--r-sm);
  }
  select{padding-right:38px;background-position:calc(100% - 18px) 50%,calc(100% - 13px) 50%}
  textarea{min-height:96px}
  .search input{padding-left:40px}
  .search svg{left:13px;width:16px;height:16px}
  .field{margin-bottom:15px}
  .formrow,.formrow-3{grid-template-columns:minmax(0,1fr)}
  .check{min-height:44px}
  input[type=checkbox],input[type=radio]{width:20px;height:20px}

  /* every control clears the 44px thumb target */
  .btn{min-height:44px;padding:11px 16px}
  .btn-sm{min-height:44px;padding:10px 14px;font-size:var(--t-12)}
  .btn-lg{min-height:50px}
  .btn-icon{width:44px;height:44px;padding:0}
  .expander{width:44px;height:44px;padding:0}
  .expander svg{width:16px;height:16px}
  .modal .x,.toast .x{
    width:44px;height:44px;flex:none;
    display:inline-flex;align-items:center;justify-content:center;
  }

  /* ----------------------------------------------- modal → bottom sheet -- */
  .modal-bg{padding:0;align-items:flex-end;justify-items:center}
  .modal,.modal.wide,.modal.narrow{
    width:100%;max-width:100%;
    max-height:92vh;max-height:92dvh;
    border-radius:var(--r-xl) var(--r-xl) 0 0;border-bottom:0;
    animation:sheetin .26s var(--ease);
  }
  .modal-h{padding:18px 16px 13px}
  .modal-h h2{font-size:var(--t-16)}
  .modal-b{padding:16px}
  .modal-f{
    padding:13px 16px calc(13px + env(safe-area-inset-bottom,0px));
    flex-direction:column-reverse;align-items:stretch;gap:9px;
  }
  .modal-f>.btn{width:100%;min-height:48px}

  /* ------------------------------------------------------------- toasts -- */
  .toasts{
    left:max(12px,env(safe-area-inset-left,0px));
    right:max(12px,env(safe-area-inset-right,0px));
    bottom:calc(76px + env(safe-area-inset-bottom,0px));
    max-width:none;align-items:stretch;
  }

  /* -------------------------------------------------------------- login -- */
  .login{
    padding:26px max(16px,env(safe-area-inset-right,0px))
            calc(26px + env(safe-area-inset-bottom,0px)) max(16px,env(safe-area-inset-left,0px));
    align-items:center;
  }
  .login-lockup{margin-bottom:16px}
  .login-lockup img{height:34px}
  .login-sub{letter-spacing:.2em;font-size:10px}
  .login-card{margin-top:20px;padding:20px 17px;border-radius:var(--r-lg)}
  .login-foot{margin-top:18px;letter-spacing:.1em;font-size:10px}
  .login-sky .g1{width:min(560px,150vw);height:min(560px,150vw)}
  .login-sky .g2{width:min(460px,130vw);height:min(460px,130vw)}
  .login-sky .g3{width:min(600px,160vw);height:min(360px,90vw)}
}

/* Narrow phones (≤ 414px covers 360 / 375 / 390 / 393 / 414). Every pixel of
   the top bar has to be earned, so the live chip drops to its dot — the title
   attribute and its colour still say everything it said before. */
@media (max-width:479.98px){
  #livetext{display:none}
  .livechip{padding:0;width:30px;height:30px;justify-content:center;flex:none}
  .tw-cards tbody td{padding-left:43%}
  .tw-cards tbody td::before{width:calc(43% - 22px)}
  .pagehead .acts>.btn{flex:1 1 100%}
  .g-4{gap:10px}
}

/* Landscape tablets: keep the density high, trim vertical padding. */
@media (min-height:0) and (max-height:820px) and (min-width:900px){
  .topbar-in{padding-top:9px;padding-bottom:9px}
  main{padding-top:18px}
  .feed{max-height:330px}
}

/* Phone in landscape: short viewport, so the sheet must not eat the screen. */
@media (max-width:900px) and (max-height:520px) and (orientation:landscape){
  .modal,.modal.wide,.modal.narrow{max-height:100dvh;border-radius:0}
  .modal-f{flex-direction:row;justify-content:flex-end}
  .modal-f>.btn{width:auto}
  .login{padding-top:18px;padding-bottom:18px}
  .login-lockup{margin-bottom:10px}
  .login-card{margin-top:14px}
}

/* --------------------------------------------------- 16. REDUCED MOTION/HC */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;animation-iteration-count:1 !important;
    transition-duration:.001ms !important;scroll-behavior:auto !important;
  }
  /* The Hold to Sync fill is a timer, not decoration (2026-09-27, his words: "can we
     also have the sync buttons fill up left to right when we have to hold"). With
     Windows' animation effects off, the rule above made it jump to full at once. */
  [data-hold].is-holding::after{transition-duration:var(--hold-ms,3000ms) !important}
}
@media (prefers-contrast:more){
  :root{--line:#3A5480;--line-soft:#2C3F60;--muted:#A9B8CE;--faint:#8496AF}
}
@media print{
  .topbar,.toasts,.backdrop,.foot,.toolbar{display:none !important}
  body{background:#fff;color:#000}
  .card,.tablewrap{border-color:#bbb;box-shadow:none;background:#fff}
}

/* ============================================================= 17. PREVIS ==
   A live MOCK of the four screens the booth actually puts in front of a
   visitor. Nothing plays. Every frame is a scale drawing of a real device, so
   the interaction model — press a button on the tablet, watch the TV answer —
   can be proved to a client with no hardware, no content and no network.

   The layout contract, in the owner's words: the programming UI on the LEFT,
   the previs stack TOP TO BOTTOM on the RIGHT, so nothing ever has to be
   scrolled sideways to be seen. On a phone the two panels simply stack.

   Colour note: the palette below is only a FALLBACK. /api/previs ships the
   client's own colours and the view writes them onto .previs as inline custom
   properties, so re-branding the mock for the next client is a server change,
   not a stylesheet fork.
   ========================================================================= */

.previs{
  --pv-bg:#0b1e33;
  --pv-surface:#12293f;
  --pv-accent:#1b6ec2;
  --pv-text:#e8eef6;
  --pv-muted:#9fb3c8;
  --pv-busy:#d64545;
  --pv-free:#3fa45b;
}

/* ---------------------------------------------------------- the two panels */

.previs-left{min-width:0;display:flex;flex-direction:column;gap:14px}

/* 2026-09-24, his words: "previz needs to be under the floor plan and above
   the programming ui, not off to the side." The previz is a full-width band
   under the floor plan, Programming below it. */
.previs-stack{display:flex;flex-direction:column;gap:18px}
.previs-view{min-width:0;display:flex;flex-direction:column;gap:12px}

/* The previs column sticks to the top of the viewport and scrolls INSIDE
   itself. A long left panel then cannot push the mock off screen — the whole
   point of the two-panel arrangement is that the client keeps seeing the
   screens while the operator keeps programming. */


.previs-hint{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  font-size:var(--t-11);color:var(--faint);
  letter-spacing:.06em;text-transform:uppercase;font-weight:700;
}
.previs-hint .dot{width:5px;height:5px;border-radius:99px;background:var(--cyan-ink);flex:none}

/* ------------------------------------------------------ left panel widgets */

.pv-ts{display:flex;align-items:center;gap:7px;flex:0 0 auto}
.pv-ts input{width:88px;min-width:0;text-align:center;font-variant-numeric:tabular-nums}
.pv-pick{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(190px,1fr));
  gap:6px;max-height:210px;overflow:auto;padding:10px;
  border:1px solid var(--line-strong);border-radius:var(--r-sm);background:var(--ground-2);
}
.pv-pick label{
  display:flex;align-items:flex-start;gap:9px;margin:0;padding:6px;
  border-radius:var(--r-xs);font-weight:500;font-size:var(--t-12);
  color:var(--ink-2);cursor:pointer;min-height:38px;
}
.pv-pick label:hover{background:var(--panel-2)}
.pv-pick input{margin-top:2px;flex:none}
.pv-pick .s{display:block;color:var(--muted);font-size:var(--t-11);margin-top:1px}

/* ------------------------------------------------------------- responsive */
/* One column below a laptop: the previs stack drops under the programming
   panel and stops being sticky, because a sticky pane taller than a phone is
   just a pane you cannot scroll. */
@media (max-width:767.98px){
  .previs-left{gap:12px}
  .pv-ts{width:100%}
  .pv-ts input{flex:1 1 auto;width:auto}
  .pv-pick{grid-template-columns:minmax(0,1fr);max-height:230px}
  .pv-pick label{min-height:44px}
}

/* Seven tabs have to fit a 360px phone without a single pixel of sideways
   scroll, so the labels get their own ellipsis rather than pushing the bar
   wide. Nothing about the six existing tabs changes above this width. */
@media (max-width:479.98px){
  .mainnav{gap:1px}
  .navlink{padding:6px 1px;font-size:9.5px}
  .navlink .ni{width:19px;height:19px}
  .navlink .ns{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
}

/* ═══════════════════════════════════════════════════════════════════════════
   §20  SHOSYNC v2 — devices, branding, looks, staging, help
   ═════════════════════════════════════════════════════════════════════════
   Everything below was added when the console stopped being a read-only view
   of the fleet and started managing it. It reuses the existing tokens without
   exception: no new colour is invented here, so a theme change upstream still
   reaches every one of these components.
   ═══════════════════════════════════════════════════════════════════════════ */

/* -- small shared pieces --------------------------------------------------- */

/* A tag that carries weight. `hot` is the media master and anything else that
   is singular on the floor — there is only ever one, and it should read that
   way at a glance. */
.tag-hot{background:rgba(254,120,0,.16);color:#FFB169;border-color:rgba(254,120,0,.42)}
.tag-ok {background:var(--ok-soft); color:var(--ok-ink); border-color:rgba(31,169,122,.34)}
.tag-bad{background:var(--bad-soft);color:var(--bad-ink);border-color:rgba(199,68,62,.36)}

/* The push button. Not `danger` — a push is not a mistake — but not a plain
   primary either, because it moves gigabytes and erases disks. */
.btn-hot{
  background:linear-gradient(180deg,#E06A00,#C25A00);
  border-color:rgba(254,120,0,.55);color:#fff;
}
.btn-hot:hover:not(:disabled){background:linear-gradient(180deg,#F07500,#D06200)}

/* A whole table row that navigates. The cursor and the hover lift are the only
   affordance a row gets; buttons inside it still win the click. */
.rowlink{cursor:pointer}
.rowlink:hover{background:var(--panel-2)}
.rowlink .cell-acts{cursor:default}

.backlink{
  display:inline-block;margin-bottom:6px;color:var(--muted);
  font-size:var(--t-13);text-decoration:none;
}
.backlink:hover{color:var(--cyan-ink)}

.mt-8{margin-top:8px}

/* confirmAction() escapes its body, so a multi-line message arrives as text
   with real newlines in it. Honour them rather than running the lines together. */
.danger-note{white-space:pre-line}

/* A checkbox that reads as a control rather than as a stray box. */
.chk{display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-size:var(--t-13)}
.chk input[type="checkbox"]{
  width:17px;height:17px;flex:none;accent-color:var(--boeing-cyan);cursor:pointer;
}
.chk input:disabled{cursor:not-allowed;opacity:.5}
.chk-row{
  display:flex;width:100%;padding:9px 11px;border:1px solid var(--line-soft);
  border-radius:var(--r-sm);background:var(--panel);margin-bottom:6px;
}
.chk-row:hover{border-color:var(--line-strong);background:var(--panel-2)}
.checklist{max-height:320px;overflow:auto;padding-right:4px}

/* -- device rows in dialogs ------------------------------------------------ */

.devrow{
  display:flex;align-items:center;gap:12px;padding:10px 12px;margin-bottom:6px;
  background:var(--panel);border:1px solid var(--line-soft);border-radius:var(--r-sm);
}
.dr-main{flex:1 1 auto;min-width:0;display:flex;flex-wrap:wrap;align-items:center;gap:7px}
.dr-main b{font-size:var(--t-14)}
.dr-main .muted{width:100%}

/* -- branding editor ------------------------------------------------------- */

.colorgrid{
  display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
  margin-top:12px;
}
/* A slot that is switched off is dimmed, NOT emptied. The colour stays on the
   screen and in the database, so turning it back on restores what was there
   instead of making somebody pick it again. */


/* The live preview. It is deliberately a slab of the real thing — heading,
   sub-heading and two buttons — because a row of colour swatches tells you
   nothing about whether the text will be readable on the photo. */
.brandpreview{
  margin-top:16px;border-radius:var(--r);border:1px solid var(--line);
  padding:26px 22px;min-height:150px;display:flex;flex-direction:column;
  justify-content:center;gap:8px;background-size:cover;background-position:center;
}
.bp-head{font-size:var(--t-28);font-weight:700;line-height:1.1}
.bp-sub{font-size:var(--t-15)}
.bp-btns{display:flex;gap:9px;margin-top:12px;flex-wrap:wrap}
.bp-btn{
  padding:8px 18px;border-radius:var(--r-pill);color:#fff;
  font-size:var(--t-13);font-weight:600;
}

/* -- push / staging -------------------------------------------------------- */



/* The wipe warning. Loud on purpose: this is the box that stands between an
   operator and erasing a show that is still running. */
.warnbox{
  background:var(--bad-soft);border:1px solid rgba(199,68,62,.42);
  border-radius:var(--r);padding:14px 16px;color:var(--ink-2);
}
.warnbox b{color:var(--bad-ink)}
.warnbox ul{margin:9px 0 0;padding-left:20px}
.warnbox li{margin-bottom:4px}
.warnbox p{margin:10px 0 0;font-size:var(--t-13);color:var(--muted)}
.okbox{
  background:var(--ok-soft);border:1px solid rgba(31,169,122,.34);
  border-radius:var(--r);padding:14px 16px;color:var(--ink-2);font-size:var(--t-13);
}

/* -- help ------------------------------------------------------------------ */

.helpnav{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:16px}
.helpnav a{
  padding:7px 13px;border-radius:var(--r-pill);border:1px solid var(--line);
  background:var(--panel);color:var(--ink-2);font-size:var(--t-13);text-decoration:none;
}
.helpnav a:hover{border-color:var(--line-strong);color:var(--ink)}

.helpsec{scroll-margin-top:96px}
.helpsec h2{font-size:var(--t-22);margin:0 0 4px}
.helpsec .lede{color:var(--muted);font-size:var(--t-14);margin:0 0 14px}

/* Numbered steps. The number is the point — an operator following this on a
   show floor is counting, not reading. */
.steps{counter-reset:step;list-style:none;margin:0;padding:0}
.steps>li{
  counter-increment:step;position:relative;padding:0 0 18px 44px;
  border-left:2px solid var(--line);margin-left:13px;
}
.steps>li:last-child{border-left-color:transparent;padding-bottom:0}
.steps>li::before{
  content:counter(step);position:absolute;left:-14px;top:-2px;
  width:26px;height:26px;border-radius:50%;display:flex;align-items:center;
  justify-content:center;background:var(--panel-3);border:1px solid var(--line-strong);
  color:var(--ink);font-size:var(--t-12);font-weight:700;
}
.steps>li b{display:block;margin-bottom:3px;font-size:var(--t-14)}
.steps>li p{margin:0;color:var(--muted);font-size:var(--t-13);line-height:1.55}

/* The admin half of Help. Marked, not hidden behind a different page, so an
   admin reads one document rather than two. */
.adminonly{
  border-left:3px solid rgba(254,120,0,.5);padding-left:14px;
  background:linear-gradient(90deg,rgba(254,120,0,.05),transparent);
}
.faq{border-top:1px solid var(--line-soft);padding:12px 0}
.faq summary{cursor:pointer;font-weight:600;font-size:var(--t-14);color:var(--ink)}
.faq summary::marker{color:var(--cyan-ink)}
.faq p{margin:9px 0 0;color:var(--muted);font-size:var(--t-13);line-height:1.6}

/* -- the tablet scrub bar -------------------------------------------------- */

.scrubwrap{display:flex;align-items:center;gap:10px;width:100%;margin-top:8px}
.scrubwrap .sc-t{
  font-family:var(--mono);font-size:var(--t-12);color:var(--muted);
  flex:none;min-width:42px;text-align:center;
}
/* A finger, not a mouse. 30px of vertical target on a rail that only draws 6px
   — the same trick every phone media player uses. */
.scrub{
  flex:1 1 auto;-webkit-appearance:none;appearance:none;height:30px;
  background:none;cursor:pointer;
}
.scrub::-webkit-slider-runnable-track{
  height:6px;border-radius:99px;background:var(--panel-3);
}
.scrub::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;width:20px;height:20px;margin-top:-7px;
  border-radius:50%;background:var(--boeing-cyan);border:2px solid var(--ink);
  box-shadow:var(--sh-1);
}
.scrub::-moz-range-track{height:6px;border-radius:99px;background:var(--panel-3)}
.scrub::-moz-range-thumb{
  width:20px;height:20px;border-radius:50%;background:var(--boeing-cyan);
  border:2px solid var(--ink);
}
.scrub:disabled{opacity:.4;cursor:not-allowed}

/* -- phone ----------------------------------------------------------------- */

@media (max-width:767.98px){
  .colorgrid{grid-template-columns:minmax(0,1fr)}
  .bp-head{font-size:var(--t-22)}
  .devrow{flex-wrap:wrap}
  .steps>li{padding-left:38px}
}

/* ═══════════════════════════════════════════════════════════════════════════
   §21  ANALYTICS, ENGAGEMENT, TAGS
   ═══════════════════════════════════════════════════════════════════════════ */

.anah{
  margin:0 0 8px;font-size:var(--t-13);text-transform:uppercase;
  letter-spacing:.09em;color:var(--muted);font-weight:700;
}

/* A second metric under a number, where the number alone is ambiguous:
   "11" means nothing until it says "23%" underneath. */
.sub2{display:block;font-size:10px;color:var(--muted);margin-top:1px}

/* -- the eyeball ----------------------------------------------------------- */
.eyebtn{
  display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;padding:0;border-radius:var(--r-xs);
  background:none;border:1px solid transparent;color:var(--muted);cursor:pointer;
  transition:color var(--fast),border-color var(--fast),background var(--fast);
}
.eyebtn svg{width:17px;height:17px}
.eyebtn:hover{color:var(--ink);border-color:var(--line-strong);background:var(--panel-2)}
/* Hidden is the LOUD state on purpose. A row quietly missing from a client
   report is a mistake waiting to happen; it should be obvious it is suppressed. */
.eyebtn.is-off{color:var(--warn-ink)}
.eyebtn.is-off:hover{color:var(--warn-ink);border-color:rgba(201,138,24,.45)}
.is-hidden-row{opacity:.5}
.is-hidden-row .cell-title .t1::after{
  content:'hidden';margin-left:8px;font-size:9px;letter-spacing:.08em;
  text-transform:uppercase;padding:2px 6px;border-radius:var(--r-xs);
  background:var(--warn-soft);color:var(--warn-ink);
}

/* -- sample-data banner ---------------------------------------------------- */

/* -- inline proportion bar ------------------------------------------------- */
.minimeter{
  display:block;width:74px;height:6px;border-radius:99px;
  background:var(--panel-3);overflow:hidden;
}
.minimeter i{display:block;height:100%;background:var(--boeing-cyan);border-radius:99px}

/* -- room x media grid ----------------------------------------------------- */
.rmgrid{display:grid;gap:12px;grid-template-columns:repeat(auto-fit,minmax(270px,1fr))}
.rmgroup{
  background:var(--panel-2);border:1px solid var(--line-soft);
  border-radius:var(--r-sm);padding:12px;
}
.rmhead{
  font-size:var(--t-13);font-weight:700;color:var(--ink);
  margin-bottom:8px;padding-bottom:7px;border-bottom:1px solid var(--line-soft);
}
.rmrow{
  display:grid;grid-template-columns:1fr auto auto 74px;gap:9px;
  align-items:center;padding:4px 0;font-size:var(--t-12);
}
.rm-n{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--ink-2)}
.rm-x{font-family:var(--mono);color:var(--cyan-ink)}
.rm-w{font-family:var(--mono);color:var(--muted);font-size:10px}

/* -- charts ---------------------------------------------------------------- */
.chartgrid{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(330px,1fr))}
.chartcard .card-b{padding:16px 18px 18px}
.chartwrap{display:flex;align-items:center;gap:18px;flex-wrap:wrap}
.donut{flex:none}
.donut path{transition:opacity var(--fast);cursor:default}
.donut path:hover{opacity:.82}
.dn-c1{
  fill:var(--ink);font-size:20px;font-weight:700;text-anchor:middle;
  font-family:var(--font);
}
.dn-c2{
  fill:var(--muted);font-size:10px;text-anchor:middle;font-family:var(--font);
  text-transform:uppercase;letter-spacing:.09em;
}
.legend{flex:1 1 150px;min-width:0;display:flex;flex-direction:column;gap:5px}
.lg-row{display:flex;align-items:center;gap:8px;font-size:var(--t-12)}
.lg-dot{width:9px;height:9px;border-radius:3px;flex:none}
.lg-lbl{
  flex:1 1 auto;min-width:0;color:var(--ink-2);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.lg-val{font-family:var(--mono);color:var(--muted);flex:none}
.chart-empty{
  padding:26px;text-align:center;color:var(--faint);font-size:var(--t-13);
  border:1px dashed var(--line);border-radius:var(--r);width:100%;
}

.barchart{display:flex;flex-direction:column;gap:9px}
.bc-row{display:grid;grid-template-columns:82px 1fr 46px;gap:11px;align-items:center}
.bc-lbl{font-size:var(--t-12);color:var(--ink-2);text-align:right}
.bc-track{height:19px;border-radius:var(--r-xs);background:var(--panel-3);overflow:hidden}
.bc-track i{display:block;height:100%;border-radius:var(--r-xs);transition:width var(--med)}
.bc-val{font-family:var(--mono);font-size:var(--t-12);color:var(--muted)}

/* -- insights -------------------------------------------------------------- */
.insight{
  display:flex;gap:11px;padding:11px 0;font-size:var(--t-13);
  line-height:1.55;color:var(--ink-2);border-top:1px solid var(--line-soft);
}
.insight:first-child{border-top:0;padding-top:0}
.ins-dot{
  width:8px;height:8px;border-radius:50%;flex:none;margin-top:6px;
  background:var(--cyan-ink);
}
.insight.warn .ins-dot{background:var(--warn-ink)}
.insight.ok   .ins-dot{background:var(--ok-ink)}
.bad-ink{color:var(--bad-ink)}

/* -- asset thumbnails (2026-09-23): pulled from Dropbox when the asset is
      added, kept in its show's folder; an empty frame keeps the rows lined up */
.asset-cell{display:flex;gap:12px;align-items:flex-start}
.asset-cell-text{min-width:0}
.asset-thumb{flex:none;display:block;width:96px;height:54px;border-radius:6px;
  object-fit:cover;background:var(--panel-3)}
.asset-thumb-empty{border:1px dashed var(--line)}
.thumb-edit{display:flex;gap:12px;align-items:center;flex-wrap:wrap}

/* -- tags ------------------------------------------------------------------ */
.chip{
  display:inline-flex;align-items:center;gap:5px;
  padding:2px 8px;margin:2px 4px 2px 0;border-radius:var(--r-pill);
  border:1px solid var(--line-strong);background:var(--panel-2);
  font-size:11px;color:var(--ink-2);
}
.chip-on{background:var(--panel-3);color:var(--ink)}
.chip-x{
  background:none;border:0;color:var(--muted);cursor:pointer;padding:0 0 0 2px;
  font-size:14px;line-height:1;
}
.chip-x:hover{color:var(--bad-ink)}

.tagbox{
  border:1px solid var(--line);border-radius:var(--r-sm);
  background:var(--panel);padding:10px;
}
.tb-chips{min-height:26px;margin-bottom:8px}
.tb-input{display:flex;gap:8px;position:relative}
.tb-input input{flex:1 1 auto;min-width:0}

/* The suggestion list. Absolutely positioned so it floats over whatever is
   under it rather than shoving the dialog's buttons down the screen. */
.tb-menu{
  position:absolute;top:100%;left:0;right:0;z-index:30;margin-top:5px;
  background:var(--panel-2);border:1px solid var(--line-strong);
  border-radius:var(--r-sm);box-shadow:var(--sh-3);
  max-height:230px;overflow:auto;padding:4px;
}
.tb-opt{
  display:flex;align-items:center;gap:9px;width:100%;
  padding:8px 10px;border:0;border-radius:var(--r-xs);
  background:none;color:var(--ink-2);font:inherit;font-size:var(--t-13);
  text-align:left;cursor:pointer;
}
.tb-opt:hover,.tb-opt.on{background:var(--panel-3);color:var(--ink)}
.tb-uses{margin-left:auto;font-family:var(--mono);font-size:10px;color:var(--faint)}
.tb-new{color:var(--cyan-ink);font-style:italic}

/* -- phone ----------------------------------------------------------------- */
@media (max-width:767.98px){
  .chartgrid{grid-template-columns:minmax(0,1fr)}
  .chartwrap{justify-content:center}
  .rmgrid{grid-template-columns:minmax(0,1fr)}
  .bc-row{grid-template-columns:66px 1fr 40px;gap:8px}
}

/* Nine tabs now. Below 560px the labels have to give ground before the bar
   does, so the short labels shrink rather than the row scrolling sideways. */
@media (max-width:559.98px){
  .navlink{padding:6px 1px;font-size:9px}
  .navlink .ni{width:18px;height:18px}
}

/* ===================================================================
   SHOW DOCUMENT + FLEET
   Added with those screens. Every class below was being used by app.js
   before it existed here, which is why the door-sign editor rendered as
   unstyled boxes: an invented class is a silent no-op, and the page
   still "works" while looking broken.
   =================================================================== */

.pad{padding:16px 18px}

/* Two columns that collapse on a narrow screen. The door sign is edited on
   a phone in a corridor as often as at a desk. */
.grid2{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px}

/* A warning that must not be mistaken for a card of content. */
.bad-card{border-color:var(--bad,#8a2b2b)}
.bad-card ul{margin:8px 0 0;padding-left:20px}
.bad-card li{margin:3px 0}

/* A deck: one named state of a display. */
.deck{margin-bottom:16px;padding:0 0 8px}
.deck-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:14px 18px;border-bottom:1px solid var(--line-soft)}
.deck-head h3{margin:0;font-size:var(--t-15,15px)}

/* A layer: a region of the output. A room player has one; the wall has one
   per slice. */
.layer{padding:12px 18px 6px}
.layer + .layer{border-top:1px dashed var(--line-soft)}
.layer-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  margin-bottom:8px}
.layer-head .btn{margin-left:auto}
/* Edit slice, Remove slice and Add slot sit together at the right
   (2026-09-23) - each .btn above would otherwise push itself away. */
.layer-head .layer-acts{margin-left:auto;display:flex;gap:6px;flex-wrap:wrap}
.layer-head .layer-acts .btn{margin-left:0}

/* 2026-09-24: the Edit/Remove pair on each media row, a deck with no slices
   yet, and the "or" rule in Add slice. (The deck's UID is never shown - "that
   is internal info".) */
.deck-head .btn + .btn{margin-left:0}
.deck-empty{padding:12px 18px 4px;margin:0}
.layer td.nowrap .btn + .btn{margin-left:6px}
.row.gap8{gap:8px}
.row.gap8 select{flex:1 1 auto;min-width:0}
.divider-label{display:flex;align-items:center;gap:10px;margin:18px 0 14px;
  color:var(--muted);font-size:var(--t-12,12px);text-transform:uppercase;letter-spacing:.05em}
.divider-label::before,.divider-label::after{content:"";flex:1;height:1px;background:var(--line-soft)}

/* Shows: with Reset beside Delete (2026-09-24) the buttons take more room, and
   between the card layout and a wide screen the show name was squeezed to a
   letter per line. It keeps a readable width; the table scrolls instead. */
@media (min-width:768px){
  #shows-body td.cell-title{min-width:170px}
}

/* Add takeover (2026-09-24): rooms with checkboxes; a ticked room opens with
   "All" and every screen ticked, each screen with the deck it plays. */
.tk-room{border:1px solid var(--line-soft);border-radius:10px;padding:8px 12px;margin:0 0 8px}
.tk-room.is-on{border-color:var(--line-strong);background:rgba(26,64,159,.10)}
.tk-room-h{margin:0}
.tk-disps{display:flex;flex-direction:column;gap:6px;margin:8px 0 4px 26px}
.tk-all{font-weight:600;margin:0}
.tk-disp{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.tk-disp .check{margin:0;flex:1 1 220px}
.tk-disp select{width:auto;min-width:200px;flex:0 1 260px}

/* A director's takeover buttons, and the decks on one (2026-09-24). */
.dbtn-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:8px 0;border-bottom:1px dashed var(--line-soft)}
.dbtn-row:last-child{border-bottom:0}
.dbtn-main{flex:1 1 220px;min-width:0}
.dbtn-row .layer-acts{display:flex;gap:6px;flex-wrap:wrap}

/* The screens a control iPad drives, grouped by room (2026-09-23). */
.ctlroom{margin:0 0 12px}
.ctlroom-h{font-size:var(--t-12,12px);font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  color:var(--ink-3);margin:0 0 6px}
.ctlroom .check{margin:5px 0}

/* Show checks on Activity (2026-09-23): one block per kind of finding. */
.checks-block{border:1px solid var(--line);border-radius:10px;margin-top:10px}
.checks-block ul{margin:8px 0 0;padding-left:20px}
.checks-block li{margin:3px 0}

/* The display picker can hold more entries than fit on one line. */
.seg.wrap{flex-wrap:wrap;margin-bottom:14px}

/* The slot table's controls sit inside cells and must not stretch them. */
.layer table select,.layer table input[type=text]{min-width:120px}
.layer table input.num{max-width:120px;text-align:right}

/* Branding, per company (2026-09-20). A colour plus what it is for - a bare
   swatch says nothing about whether it is the heading or the border. */
.brandchip{display:inline-flex;align-items:center;gap:7px;padding:5px 10px 5px 6px;
  border:1px solid var(--line);border-radius:999px;background:var(--panel-2)}
.brandchip i{width:18px;height:18px;border-radius:50%;border:1px solid var(--line);display:block}
.brandchip b{font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--ink-2)}
.brandchip.is-off{opacity:.45}
.brandchip.is-off i{filter:grayscale(1)}
/* A colour, what it is for, and whether it is used - one line, because three
   equal columns wrapped the third onto a row of its own and read as a bug. */
.brandrow{grid-template-columns:96px minmax(0,1fr) 110px;align-items:end}
.brandtextrow{grid-template-columns:minmax(0,1fr) 110px;align-items:end}
/* A logo or background link, its Scaling and its Use it (2026-09-25). */
.brandlinkrow{grid-template-columns:minmax(0,1fr) 130px 110px;align-items:end;margin-bottom:14px}
@media (max-width:760px){.brandlinkrow{grid-template-columns:minmax(0,1fr)}}
.brandrow input[type=color]{width:100%;height:38px;padding:3px;border-radius:8px;
  border:1px solid var(--line);background:var(--panel-2);cursor:pointer}
@media (max-width:760px){.brandrow,.brandtextrow{grid-template-columns:minmax(0,1fr)}}

/* A client's card head: who on the left, the buttons on the right (2026-09-25). */
.client-h,.client-acts{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
.client-acts{justify-content:flex-end}
/* A client's media on the Clients page, and picking it (2026-09-25). */
.client-media{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;margin:4px 0 0}
.client-clip{display:flex;flex-direction:column;gap:5px;font-size:12.5px;min-width:0}
.client-clip>span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.client-thumb{width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:8px;display:block}
.client-pick{display:flex;flex-direction:column;gap:6px;max-height:55vh;overflow:auto}
.client-pickrow{display:flex;align-items:center;gap:10px;margin:0}
.client-pickrow .client-thumb{width:72px;flex:none}
/* A takeover's client (2026-09-25): it fills every screen the takeover ticks. */
.tk-client-note{margin:4px 0 0}
.tk-new{color:var(--brand-ink,#6aa8ff);font-weight:600}

/* Drag and drop (2026-09-20). A device onto a room, a room onto a show. The
   target has to say it is the target, or a drag is a guess. */
.roomhead .grip{cursor:grab;opacity:.5;margin-right:8px;user-select:none}
.roomhead:active .grip{cursor:grabbing}
tr[data-drag] td:first-child{cursor:grab}
.is-dragging{opacity:.45}
.is-drop-over{outline:2px dashed var(--brand-ink);outline-offset:-2px;
  background:rgba(124,155,232,.12)}

/* The floor plan (2026-09-22). A picture with spaces drawn on it: a room is a
   soft outline, a display is a small solid one, and the one being previewed is
   lit. Shapes are positioned in per-cent, which is what makes the same plan
   right on a phone and on a projector. */
.planempty{padding:14px 2px;display:flex;flex-direction:column;gap:12px;align-items:flex-start}
/* WHITE under the drawing, always: a CAD export with a transparent
   background and black linework vanished on the dark theme in GhostNet
   before the same fix. 2026-09-23. */
/* Zoom and hand (2026-09-23): the view is a window onto the drawing; the
   drawing and its shapes scale and move together inside it. */
.planzoom{display:flex;align-items:center;gap:6px;margin:0 0 8px}
.planzoom-v{min-width:44px;text-align:center;font-size:12px;font-weight:600;color:var(--ink-2)}
.planview{position:relative;overflow:hidden;border-radius:12px;max-width:100%;width:fit-content}
.planview.is-hand,.planview.is-hand *{cursor:grab !important}
.planview.is-grabbing,.planview.is-grabbing *{cursor:grabbing !important}
.planwrap{position:relative;display:inline-block;max-width:100%;border:1px solid var(--line);
  border-radius:12px;overflow:hidden;background:#ffffff;touch-action:none;user-select:none}
.planwrap.is-nav .planshape{cursor:pointer}
/* On Build the plan is the way to pick what the previz shows, so it stays
   short enough for the previz to be on screen with it (2026-09-23: at full
   size it pushed the previz below the fold). The wrap shrinks with the
   picture, so the spaces stay on their rooms. */
.planwrap.is-nav img{max-height:300px;width:auto}
.planwrap.is-drawing{cursor:crosshair}
.planwrap img{display:block;max-width:100%;height:auto}
.planlayer{position:absolute;inset:0}
.planshape{position:absolute;border-radius:8px;cursor:pointer;transition:background .12s,border-color .12s}
/* Every label: one line, pinned in its shape's top-left corner, scaled by
   app.js (planFitLabel) until it fits inside the room or device - his words,
   2026-09-23: "nested in the top left corner justified ... auto scale to fit
   and not exceed the device or room bounds". Padding and corners are in em so
   they shrink with the text. */
.planshape>span{position:absolute;left:2px;top:2px;font-size:11px;font-weight:600;letter-spacing:.02em;
  line-height:1.15;white-space:normal;overflow-wrap:normal;word-break:normal;
  background:rgba(8,14,26,.72);color:#fff;
  padding:.12em .4em;border-radius:.4em;pointer-events:none}
/* Horizontal only, never vertical: his words, 2026-09-23: "titles need to
   only be horizontal, never vertical. if we need 2 -4 lines to make it fit,
   so be it." A label wraps to at most 4 lines (at spaces, then after _ or -,
   then anywhere) and app.js shrinks it until it fits the box - keeping at
   least three characters to a line, so a short name never stacks a letter
   per line (planFitLabel). */
.planshape.is-room{border:2px solid var(--brand-ink);background:rgba(124,155,232,.10)}
.planshape.is-display{border:2px solid var(--ok-ink,#3BA55D);background:rgba(59,165,93,.18);border-radius:6px}
.planshape:hover{background:rgba(124,155,232,.24)}
.planshape.is-on{box-shadow:0 0 0 3px rgba(124,155,232,.55);background:rgba(124,155,232,.30)}
.planrubber{position:absolute;border:2px dashed var(--brand-ink);
  background:rgba(124,155,232,.18);border-radius:8px;pointer-events:none}

/* Previz scopes (2026-09-22). One tile per device for "This room" and "Whole
   booth" - twelve detailed mocks is a wall nobody reads, and what he needs at
   a glance is which screen is showing what. */
.seg{display:inline-flex;border:1px solid var(--line);border-radius:10px;overflow:hidden}
.segbtn{background:transparent;border:0;color:var(--ink-2);font:inherit;font-size:12px;
  font-weight:600;padding:7px 12px;cursor:pointer}
.segbtn+.segbtn{border-left:1px solid var(--line)}
.segbtn.is-on{background:var(--brand-ink);color:#fff}
.pv-scopename{margin-left:auto;font-weight:600;color:var(--ink-2)}
.pvgrid{display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(190px,1fr))}
.pvtile{display:flex;flex-direction:column;gap:8px;padding:10px;background:var(--panel-2);
  border:1px solid var(--line);border-radius:12px;cursor:pointer;text-align:left;
  font:inherit;color:inherit}
.pvtile:hover{border-color:var(--brand-ink)}
.pvtile.is-on{box-shadow:0 0 0 2px var(--brand-ink)}
.pvtile-h{font-weight:600;font-size:13px}
.pvtile-screen{display:flex;align-items:center;justify-content:center;min-height:86px;
  border-radius:8px;background:#05080f;border:1px solid var(--line);padding:8px}
.pvtile-screen>span{font-size:11px;color:#cfe0ff;text-align:center;line-height:1.35}
.pvtile-f{font-size:11px;color:var(--ink-2)}
.planshape{touch-action:none}
.planhandle{position:absolute;right:-6px;bottom:-6px;width:14px;height:14px;border-radius:4px;
  background:var(--brand-ink);border:2px solid #fff;cursor:nwse-resize}
.planmsg{font-size:12.5px;margin:0 0 8px;min-height:0}
.planmsg:empty{display:none}
.planmsg.is-ok{color:var(--ok-ink,#3BA55D)}
.planmsg.is-warn{color:var(--warn-ink,#E8A93B)}

/* ---- the previz, 2026-09-23: what each device outputs ---------------------
   Drawn from the show document the device is sent. The screen keeps the
   device's own shape (aspect-ratio from its resolution) and the words inside
   scale with it (container units), so a tile and the big frame read alike. */
.pvl{display:flex;flex-direction:column;gap:8px;min-width:0}
.pvl-big{padding:12px;border:1px solid var(--line);border-radius:14px;background:var(--panel-2)}
.pvl-h{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;font-size:13px;min-width:0}
.pvl-h>span{font-size:11px;color:var(--ink-2)}
.pvl-f{display:flex;flex-direction:column;gap:2px;font-size:11.5px;color:var(--ink-2)}
.pvl-f>span{color:var(--faint)}
.pvl-bezel{display:block;padding:6px;border-radius:12px;background:#000;border:1px solid #2a2f3a}
.pvl-screen{display:block;position:relative;margin:0 auto;background:#0d0d10;border-radius:6px;
  overflow:hidden;color:#e8e8ec;line-height:1.3;container-type:inline-size}
.pvl-layer{position:absolute;display:flex;flex-direction:column;gap:2px;padding:4px 6px;
  box-sizing:border-box;border:1px solid rgba(90,165,255,.6);background:rgba(27,110,194,.2);overflow:hidden}
/* The clip's picture in its slice, scaled the way it plays (2026-09-24). The
   caption sits over it on a dark tab so the words stay readable. */
.pvl-layer.has-shot{background:#000}
.pvl-shot{position:absolute;left:50%;top:50%;overflow:hidden;pointer-events:none}
.pvl-shot img{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);display:block;max-width:none;max-height:none}
/* The same padding with or without a picture, so every caption on a screen is
   fitted to the same room (2026-09-25: "why are they not all uniform"). */
.pvl-cap{position:relative;z-index:1;display:flex;flex-direction:column;gap:2px;align-self:flex-start;max-width:100%;
  padding:2px 5px;border-radius:4px}
.pvl-layer.has-shot .pvl-cap{background:rgba(0,0,0,.62)}
.pvl-n{font-size:max(8px,min(3cqw,11px));color:#9fb3c8;text-transform:uppercase;letter-spacing:.05em}
/* A caption breaks between words only - never inside one (2026-09-25). */
.pvl-media{font-size:max(9px,min(4.6cqw,17px));color:#fff;font-weight:700;overflow-wrap:normal;word-break:normal}
/* A control iPad's remote, drawn like G4Remote (2026-09-25): background, logo,
   a grid of picture buttons with the label under each, the playing ring. */
.pvl-remote{position:absolute;inset:0;display:flex;flex-direction:column;gap:2.4cqw;padding:3cqw;overflow:hidden}
.pvl-remote-logo{display:flex;justify-content:center;height:9cqw;flex:none}
/* A takeover's door iPad shows its client's logo over the sign (2026-09-25). */
.pvl-door-logo{display:flex;justify-content:center;height:14cqw;margin-bottom:2cqw}
.pvl-door-logo .pvl-door-logoimg{height:100%;width:auto;object-fit:contain;border-radius:0}
/* The picture of the clip a remote button plays, in its dialog (2026-09-25). */
.rb-shot{margin:-4px 0 12px}
.rb-shot .rb-thumb{width:160px;aspect-ratio:16/9;object-fit:cover;border-radius:8px;display:block}
.pvl-remote-logo .pvl-remote-logoimg{height:100%;width:auto;object-fit:contain;border-radius:0}
.pvl-remote-grid{display:grid;gap:2.4cqw;align-content:start}
.pvl-remote-btn{display:flex;flex-direction:column;gap:1cqw;border-radius:1.6cqw;overflow:hidden;background:#111;padding:.6cqw}
.pvl-remote-btn .pvl-remote-thumb{display:block;width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:1.2cqw}
.pvl-remote-label{font-size:max(7px,min(2.4cqw,15px));font-weight:700;color:#fff;text-align:center;line-height:1.2;
  overflow-wrap:normal;word-break:normal;padding:0 .6cqw .6cqw}
.rbtn-row{align-items:center;gap:10px}
.rbtn-row .rbtn-thumb{width:64px;height:36px;object-fit:cover;border-radius:6px;flex:none}
.pvl-then{font-size:max(8px,min(3.2cqw,12px));color:#cfe0ff}
.pvl-note{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:4px;padding:8%;text-align:center}
.pvl-note>b{font-size:max(9px,min(5cqw,15px))}
.pvl-note>span{font-size:max(8px,min(3.6cqw,12px));color:#9a9aa6}
.pvl-app{position:absolute;inset:0;display:flex;flex-direction:column;gap:5px;padding:4%;overflow:auto;
  font-size:max(8px,min(3.4cqw,12px))}
.pvl-app-h{display:flex;align-items:baseline;gap:6px;font-weight:700;font-size:max(9px,min(4.4cqw,15px))}
.pvl-app-h>span{font-weight:400;font-size:max(8px,min(3cqw,11px));color:#8b8b96}
.pvl-card{display:flex;flex-direction:column;gap:4px;background:#17171c;border:1px solid #26262e;
  border-radius:8px;padding:5px 6px}
.pvl-row{display:flex;flex-wrap:wrap;gap:4px;align-items:center}
.pvl-pill{font-size:max(7px,min(2.8cqw,10px));padding:2px 6px;border-radius:999px;background:#22222a;color:#9a9aa6}
.pvl-pill.is-ok{background:#14421f;color:#77dd8f}
.pvl-pill.is-bad{background:#4a1c1c;color:#ff9d9d}
.pvl-chip{font-family:inherit;font-weight:600;font-size:max(8px,min(3.2cqw,12px));line-height:1;
  padding:5px 8px;border-radius:7px;border:1px solid #33333d;background:#22222a;color:#e8e8ec}
.pvl-chip.is-on{background:#2f6fd0;border-color:#2f6fd0;color:#fff}
button.pvl-chip{cursor:pointer}
button.pvl-chip:hover{border-color:#2f6fd0}
.pvl-lbl{font-size:max(7px,min(2.6cqw,10px));color:#8b8b96;text-transform:uppercase;letter-spacing:.06em}
.pvl-dim{color:#8b8b96}
/* A control iPad's picture buttons for the screens it drives (2026-09-23). */
.pvl-sub{display:block;font-weight:600;font-size:max(7px,min(2.8cqw,11px));margin-top:2px}
.pvl-tiles{display:grid;grid-template-columns:repeat(auto-fill,minmax(max(44px,16cqw),1fr));gap:4px}
.pvl-tile{display:flex;flex-direction:column;gap:2px;min-width:0;font-size:max(7px,min(2.4cqw,10px))}
.pvl-tile>span{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pvl-tile .asset-thumb{width:100%;height:auto;aspect-ratio:16/9;border-radius:4px;object-fit:cover}
.pvl-door{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:3px;padding:8%;text-align:center;box-sizing:border-box;
  border:max(3px,1.6cqw) solid var(--pv-free,#3fa45b);background:#0b1e33}
.pvl-door.is-busy{border-color:var(--pv-busy,#d64545)}
.pvl-door-name{font-size:max(11px,min(9cqw,30px));font-weight:800;color:#fff;overflow-wrap:anywhere}
.pvl-door-l{font-size:max(8px,min(5cqw,16px));color:#cfe0ff}
.pvl-door-t{font-size:max(8px,min(4.4cqw,14px));color:#9fb3c8}
.pvl-door-s{margin-top:4%;font-size:max(7px,min(3.4cqw,11px));letter-spacing:.08em;
  text-transform:uppercase;color:#9fb3c8}
.pvtile.pvl{padding:10px}
.pvroom{display:flex;flex-direction:column;gap:8px}
.pvroom+.pvroom{margin-top:8px}
.pvroom-h{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px}
.pvroom-h>span{font-size:11px;font-weight:400;color:var(--ink-2)}
/* The whole booth as one multiviewer (2026-09-25): the rooms share one
   screen-high grid, each room has a border with its devices inside, and every
   screen fits its cell - no scrolling. */
.pvmv{display:grid;gap:10px;height:calc(100vh - 230px);min-height:420px;grid-auto-rows:minmax(0,1fr)}
.pvmv-room{display:flex;flex-direction:column;gap:6px;min-width:0;min-height:0;padding:8px;
  border:2px solid var(--line);border-radius:12px;background:var(--panel)}
.pvmv-h{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px;white-space:nowrap;overflow:hidden}
.pvmv-h>span{font-size:11px;font-weight:400;color:var(--ink-2)}
.pvmv-devs{flex:1;min-height:0;display:grid;gap:6px;grid-auto-rows:minmax(0,1fr)}
.pvtile.is-mv{padding:6px;gap:4px;min-height:0;min-width:0;overflow:hidden}
.pvtile.is-mv .pvl-h{font-size:12px;flex-wrap:nowrap;white-space:nowrap;overflow:hidden}
.pvtile.is-mv .pvl-bezel{flex:1;min-height:0;padding:3px;display:flex;align-items:center;
  justify-content:center;container-type:size}
.pvtile.is-mv .pvl-screen{margin:0}
.pvtile.is-mv .pvl-f{display:block;font-size:10.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pvtile.is-mv .pvl-f>span{display:none}
.syncbar .card-b{align-items:center}
/* The floor plan, nested in its show (2026-09-23). */
.showplan{margin:4px 0 14px;padding:12px;border:1px solid var(--line);border-radius:12px;background:var(--panel)}
.showplan-h{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:10px}
/* HOLD TO SYNC (2026-09-26: "a press and hold for 3s ... for all push or sync
   buttons"): the button fills while it is held and fires when full. */
[data-hold]{position:relative;overflow:hidden;isolation:isolate}
[data-hold]::after{content:'';position:absolute;inset:0;background:rgba(255,255,255,.30);transform:scaleX(0);
  transform-origin:left center;pointer-events:none;z-index:-1}
[data-hold].is-holding::after{transform:scaleX(1);transition:transform var(--hold-ms,3000ms) linear}
/* THE DIRECTOR iPAD, 1:1 WITH THE iPAD (2026-10-07, his words: "the director ipad is not 1:1 with the website.
   the website looks like shit for the director. the ipad looks right"). Drawn at the iPad's own points with
   the iPad's own sizes (ipad/ShoSync/DirectorView.swift: 24 padding, 20 between widgets, .title 28 semibold
   names, .title3 20 controls 56 tall, 20-radius widgets, 12-radius controls, an 18 health dot, the 64-point
   six-tap strip at the bottom) and scaled as one picture to its frame (app.js dsimFitAll). Two columns. */
.dsim-fit{position:relative;display:block;overflow:hidden;margin:0 auto}
.dsim{position:absolute;left:0;top:0;transform-origin:0 0;display:flex;flex-direction:column;background:#141414;
  color:#fff;font:20px/1.25 -apple-system,"SF Pro Text","SF Pro Display",system-ui,"Segoe UI",sans-serif;
  overflow:hidden;text-align:left;-webkit-font-smoothing:antialiased}
.dsim-scroll{flex:1;min-height:0;overflow:auto;padding:24px 24px 88px}
.dsim-cols{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px;align-items:start}
.dsim-col{display:flex;flex-direction:column;gap:20px;min-height:150px}
.dsim-slot{display:flex}
.dsim-w{flex:1;min-width:0;background:rgba(255,255,255,.06);border:0;border-radius:20px;padding:20px;display:flex;
  flex-direction:column;gap:16px;color:inherit;font:inherit;text-align:left}
.dsim-h{display:flex;align-items:center;gap:10px}
.dsim-h b{font-size:28px;font-weight:600;line-height:1.2;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.dsim-dot{width:18px;height:18px;border-radius:50%;background:#8E8E93;flex:none}
.dsim-dot.is-ok{background:#30D158}
.dsim-dot.is-bad{background:#FF453A}
.dsim-dot.is-wait{animation:dsimPulse 1.2s ease-in-out infinite}
@keyframes dsimPulse{0%,100%{opacity:1}50%{opacity:.25}}
.dsim-row{display:flex;gap:12px;align-items:center}
.dsim-row select{flex:1 1 0;min-width:0;height:56px;font:inherit;font-size:20px;color:#fff;padding:0 44px 0 16px;
  border:0;border-radius:12px;background:rgba(142,142,147,.25) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='2.4'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E") right 16px center / 20px 20px no-repeat;
  -webkit-appearance:none;appearance:none}
.dsim-row select option{color:#000}
.dsim-btn{position:relative;isolation:isolate;overflow:hidden;height:56px;border:0;border-radius:12px;padding:0 18px;
  font:inherit;font-size:20px;background:rgba(10,132,255,.18);color:#0A84FF;cursor:pointer;flex:none}
.dsim-btn:disabled{opacity:.4;cursor:default}
.dsim-plus{width:60px;padding:0;font-size:26px;font-weight:600}
.dsim-sync{min-width:130px;background:rgba(142,142,147,.25);color:#fff;font-weight:600}
.dsim-sync.is-waiting{background:rgba(10,132,255,.45);box-shadow:0 0 12px rgba(10,132,255,.9)}
.dsim-sync.is-holding::after{background:rgba(48,209,88,.85)}
.dsim-occ{height:56px;width:100%;border:0;border-radius:12px;font:inherit;font-size:20px;font-weight:600;color:#fff;
  cursor:pointer}
.dsim-occ.is-vacant{background:#34C759}
.dsim-occ.is-occupied{background:#FF3B30}
.dsim-left{font-size:13px;color:rgba(235,235,245,.6);font-variant-numeric:tabular-nums}
.dsim-left:empty{display:none}
.dsim-left.is-warn{color:#FFD60A}
.dsim-left.is-over{color:#FF453A;font-weight:700}
.dsim-why{font-size:13px;color:#FF453A}
.dsim-note{min-height:150px;gap:12px;cursor:pointer}
.dsim-note .dsim-h svg{width:26px;height:26px;flex:none}
.dsim-sub{font-size:20px;color:rgba(235,235,245,.6)}
.dsim-slot[draggable]{cursor:grab}
.dsim-slot[draggable]:active{cursor:grabbing}
/* 2026-10-09: Notes pinned on the right at full height (app.js dsimHome), every other widget collapsible, a calculator. */
.dsim-slot{position:relative}
.dsim-slot.is-pinned{flex:1;min-height:0}
.dsim-slot.is-pinned .dsim-note{flex:1}
.dsim-col[data-dsim-col="right"]{align-self:start}
.dsim-fold{position:absolute;top:14px;right:14px;z-index:2;width:44px;height:44px;border:0;border-radius:12px;
  background:rgba(142,142,147,.25);color:#fff;font:inherit;font-size:22px;line-height:44px;cursor:pointer}
.dsim-slot:not(.is-pinned) .dsim-h{padding-right:56px}
.dsim-slot.is-folded .dsim-w > :not(.dsim-h){display:none}
.dsim-calc{cursor:default}
.dsim-calc-out{font-size:34px;font-weight:600;text-align:right;padding:10px 14px;border-radius:12px;background:rgba(0,0,0,.35);
  font-variant-numeric:tabular-nums;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dsim-calc-keys{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}
.dsim-ck{width:100%;padding:0;background:rgba(142,142,147,.25);color:#fff}
.dsim-ck.is-wide{grid-column:span 2}
.dsim-corner{position:absolute;width:64px;height:64px;z-index:5;background:transparent;border:0;padding:0;cursor:default}
.dsim-corner.c0{left:0;top:0}.dsim-corner.c1{right:0;top:0}.dsim-corner.c2{left:0;bottom:0}.dsim-corner.c3{right:0;bottom:0}
/* Its frames: the Director card on Build (as wide as the card), and a previz cell (.is-fill: fits both ways,
   centred, letterboxed in black like every other screen). */
.dsim-panel{position:relative;border-radius:14px;overflow:hidden}
.dsim-panel.is-fill{display:flex;align-items:center;justify-content:center;background:#000;border-radius:0}
/* The timer border on a screen, and a door's Occupied / Vacant border. */
.dsim-tb,.dsim-door{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:6;overflow:visible}
.dsim-tb path,.dsim-door rect{fill:none}
.dsim-tb.is-flash path{animation:dsimFlash var(--flash,1s) steps(1,end) infinite}
@keyframes dsimFlash{0%{opacity:1}50%{opacity:.06}}
/* Its popups: the stopwatch, the notifications, the notepad, the options. */
.dsw-row{display:grid;grid-template-columns:1fr auto;gap:8px 12px;align-items:center;padding:12px 0;border-bottom:1px solid var(--line)}
.dsw-row:last-child{border-bottom:0}
.dsw-left{font-size:22px;font-weight:700;font-variant-numeric:tabular-nums}
.dsw-left.is-warn{color:#FFD60A}.dsw-left.is-over{color:#FF6B6B}
.dsw-ctl{display:flex;flex-wrap:wrap;gap:8px;align-items:center;grid-column:1/-1}
.dsw-ctl input[type=range]{flex:1;min-width:140px}
.dnote-area{width:100%;min-height:260px;font:14px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
.dn-item{padding:10px 12px;border:1px solid var(--line);border-radius:10px;margin-bottom:8px;background:var(--panel-2)}
.dn-item.is-bad{border-color:#E5484D}
.dn-item small{color:var(--muted);display:block;margin-top:2px}
.dn-item .btn{margin-top:8px}
/* The takeover editor's room sections (2026-09-26). */
.tk-sect{margin-top:12px;border:1px solid var(--line);border-radius:10px;padding:10px 12px;background:var(--panel)}
.tk-sect>summary{cursor:pointer;font-weight:600}
.tk-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px;margin-top:10px}
.tk-grid .field{margin:0}
.tk-style{display:grid;grid-template-columns:84px repeat(auto-fit,minmax(78px,1fr));gap:6px 8px;align-items:end;margin-top:10px;font-size:12.5px}
.tk-style label{display:flex;flex-direction:column;gap:3px}
.tk-style label.check{flex-direction:row;align-items:center}
.tk-style input,.tk-style select{min-width:0}
.tk-slices{display:flex;flex-wrap:wrap;gap:4px 14px;margin:6px 0 2px 26px;font-size:12.5px}
.tk-newclient{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:8px;margin-top:8px;padding:10px;
  border:1px dashed var(--line-strong);border-radius:10px}
/* A client's two fonts; remote help; the master popup. */
.fontslot{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--line)}
.fontslot:last-child{border-bottom:0}
.fontslot .grow{flex:1;min-width:0}
.live-img{display:block;width:100%;border-radius:10px;border:1px solid var(--line);background:#000;min-height:220px;object-fit:contain}
.fetch-row{display:grid;grid-template-columns:1fr auto;gap:8px;align-items:center;padding:10px 0;border-bottom:1px solid var(--line)}
.fetch-row:last-child{border-bottom:0}
.fetch-row .row{gap:6px}
.colour-a{display:flex;align-items:center;gap:8px}
.colour-a input[type=range]{flex:1;min-width:90px}


/* ═════════ CONTROL (2026-10-06) - the red Live switch, and a control iPad as the real one ═════════ */
.ctl-live{display:flex;align-items:center;gap:16px;padding:14px 18px;border-radius:14px;margin-top:4px;
  border:2px solid rgba(199,68,62,.55);background:rgba(199,68,62,.08);color:var(--bad-ink)}
.ctl-live.is-on{background:linear-gradient(180deg,#C7443E,#9E3630);border-color:#FF8A82;color:#fff;
  box-shadow:0 0 0 3px rgba(199,68,62,.35),0 0 24px rgba(199,68,62,.45)}
.ctl-live-words b{font-size:var(--t-16,16px);letter-spacing:.02em}
.ctl-switch{position:relative;flex:0 0 auto;width:62px;height:34px;border-radius:999px;cursor:pointer;
  border:2px solid rgba(255,138,130,.7);background:rgba(0,0,0,.35);padding:0}
.ctl-knob{position:absolute;top:3px;left:3px;width:24px;height:24px;border-radius:50%;background:#FF8A82;
  transition:left .15s ease}
.ctl-live.is-on .ctl-switch{background:#fff;border-color:#fff}
.ctl-live.is-on .ctl-knob{left:31px;background:#C7443E}
.ctl-ipad{max-width:980px;margin:0 auto;padding:18px;border-radius:34px;background:#0b0b0d;
  border:1px solid rgba(255,255,255,.12);box-shadow:0 10px 40px rgba(0,0,0,.45)}
.ctl-screen{border-radius:18px;background:#000;min-height:300px;padding:16px;display:flex;flex-direction:column;gap:14px}
/* The Control iPad's top bar, deck row and clip grid rules went on 2026-10-07: the page draws those
   with the iPad's own measurements, inline (renderControl / controlPaint), and none of the classes was
   ever set - ctl-clip is a data-act, ctl-reach an id. */
