/* F&H STAC Dashboard - extracted from index.html 2026-07-28.
   Must stay beside index.html: url("stac-logo.png") is relative. */

  :root{
    --navy-0:#060815; --navy-1:#0A0E25; --navy-2:#111942; --navy-line:#161F51;
    --bg-glow:#172157;
    --surface-hi:#131B47; --surface:#0F1639; --surface-2:#121A44; --surface-3:#0B102A;
    /* Tone tokens, added 1 Aug 2026. There were none, so every status color in the
       product was a raw hex typed from memory and "warning orange" existed in three
       different shades. -ink is the readable text tone on that background. */
    --ok:#0F7A3D; --ok-ink:#7FD3A2; --ok-wash:rgba(15,122,61,.13);
    --warn:#8A5A00; --warn-ink:#E0A94A; --warn-wash:rgba(138,90,0,.15);
    --bad:#B3541E; --bad-ink:#F0A181; --bad-wash:rgba(179,84,30,.12);
    --stop:#8A1C1C; --stop-ink:#FF9B8A; --stop-wash:rgba(138,28,28,.14);
    --scrim:rgba(2,4,14,.74);
    --bar-1:rgba(18,30,72,.62); --bar-2:rgba(4,8,30,.5); --ctl-bg:rgba(123,63,184,.16); --ctl-line:rgba(144,90,207,.42);
    --action-1:#37086D; --action-2:#27064C; --action-3:#190431;
    --purple-dark:#400A7E; --purple:#5F0EBD; --purple-mid:#630FC4; --purple-bright:#9342F1;
    /* Steel/metal sheen — operator (dark) views only. Darker stops lifted a touch
       for a lighter overall gradient (client dashboards use plain .pv-title). */
    --metal-hi:#ffffff; --metal-1:#eef1f5; --metal-2:#d4d8de; --metal-3:#b0b6bf; --metal-4:#8a909b;
    --text:#dfe3ee; --text-dim:#8b93ad; --radius:16px;
    --live:#3fd0a0; --soon:#7e88a6; --recess:#0B1029;
  }
  :root[data-theme="light"]{
    --navy-0:#F3F4FA; --navy-1:#F3F4FA; --navy-2:#ffffff; --navy-line:#E6E8F1;
    --bg-glow:#F1E7FD;
    --surface-hi:#F9FAFD; --surface:#F3F4F9; --surface-2:#F1F3FA; --surface-3:#F4F6FD;
    /* Same tones, darkened so they stay legible on a light background. */
    --ok:#0F7A3D; --ok-ink:#0B5C2E; --ok-wash:rgba(15,122,61,.10);
    --warn:#8A5A00; --warn-ink:#6E4800; --warn-wash:rgba(138,90,0,.11);
    --bad:#B3541E; --bad-ink:#8F3F13; --bad-wash:rgba(179,84,30,.10);
    --stop:#8A1C1C; --stop-ink:#7A1414; --stop-wash:rgba(138,28,28,.10);
    --recess:#F4F6FD;
    --scrim:rgba(20,22,45,.42);
    --bar-1:#F5F6FB; --bar-2:#F8F9FE; --ctl-bg:#F3EAFD; --ctl-line:#E5D2FB;
    --action-1:#F5F6FB; --action-2:#F6F5FC; --action-3:#F1EFFA;
    --purple-dark:#560DAB; --purple:#7914ED; --purple-mid:#8C36F0; --purple-bright:#7914ED;
    --metal-hi:#ffffff; --metal-1:#16181F; --metal-2:#3A3E4D; --metal-3:#565C6E; --metal-4:#8A90A2;
    /* #69728A measured 4.11-4.45:1 on the light surfaces, just under the 4.5 AA
       floor. #656D84 is the same hue four points darker - visually identical,
       clears 4.65:1 on the worst of them. Revert this one value to undo. */
    --text:#16181F; --text-dim:#656D84;
    --live:#13A06A; --soon:#8A90A2;
  }
  *{box-sizing:border-box}
  /* Without this, Chrome for Android "font boosts" text in any block it thinks is
     wider than the viewport - which is how the Module Sandbox came out at roughly
     4x size on 1 Aug. capa-tool, interconnect and osha-301 already carry the guard;
     the dashboard was the only page without it. */
  html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
  html,body{margin:0;padding:0;overflow-x:hidden}
  body{font-family:'Inter',system-ui,sans-serif;color:var(--text);
    background-color:var(--navy-0);
    background-image:var(--suite-bg, radial-gradient(120% 80% at 50% -10%, var(--bg-glow) 0%, var(--navy-2) 28%, var(--navy-1) 60%, var(--navy-0) 100%));
    background-attachment:fixed;background-repeat:no-repeat;background-size:cover;
    min-height:100vh;-webkit-font-smoothing:antialiased;overflow-x:hidden}
  /* The emblem PNG carries its own near-black backing disc: #010915 across 46% of
     the file, which is darker than every stop in the dark gradient. Under a plain
     opacity that disc SUBTRACTS light, so the watermark read as a soft smudge
     instead of a mark (measured 3 Aug 2026). `screen` never darkens, so the disc
     becomes a no-op and only the silver wordmark and the purple ring paint. The
     opacity rises because .05 was compensating for the blot. The light theme keeps
     the original treatment, further down. */
  body::before{content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
    background-image:url("stac-logo.png");
    background-repeat:no-repeat;background-position:50% 46%;background-size:min(46vw,440px) auto;
    opacity:.14;mix-blend-mode:screen}
  /* Keep the F&H emblem watermark off white-labeled client dashboards */
  body.client-view::before,body.role-client::before{background-image:none}
  /* soft-light, not overlay: against a backdrop this dark, overlay resolves toward
     multiply, so the grain could only ever darken and it added to the muddiness. */
  body::after{content:"";position:fixed;inset:0;pointer-events:none;z-index:0;opacity:.04;mix-blend-mode:soft-light;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")}
  .wrap{position:relative;z-index:1;max-width:1180px;margin:0 auto;padding:28px 22px 80px}

  /* THE SHEEN IS AN OPERATOR SIGNAL, NOT A FLOURISH. TD-20, September 4, 2026.
     It has always been described that way in the token comment at the top of this file,
     "operator (dark) views only", and it was true only by accident: the elements that
     carry .metal happen to be on builder screens. The rule below makes it a rule. A
     client surface never wears it, whatever element ends up carrying the class, which is
     what makes it something a person can rely on. Written down in DESIGN.md so the next
     lane does not tidy it away as an unused override. */
  .metal{background:linear-gradient(180deg,var(--metal-hi) 0%,var(--metal-1) 16%,var(--metal-3) 42%,var(--metal-4) 52%,var(--metal-3) 60%,var(--metal-1) 84%,var(--metal-2) 100%);
    -webkit-background-clip:text;background-clip:text;color:transparent;
    filter:drop-shadow(0 1px 0 rgba(0,0,0,.6)) drop-shadow(0 -1px 0 rgba(255,255,255,.15));letter-spacing:.02em}

  header.bar{display:flex;align-items:center;gap:16px;padding:15px 20px;border-radius:var(--radius);
    background:var(--surface-2);border:1px solid var(--navy-line);position:relative;
    box-shadow:0 1px 0 rgba(255,255,255,.03) inset,0 12px 30px -26px rgba(0,0,0,.85)}
  /* THE ACCENT RAIL, AND THE CHIP UNDER IT. TD-20, September 4, 2026.
     Three signals move together so that a person can tell F&H's own console from a
     client's suite without reading anything: the wordmark sheen (further down this
     file, and it is an operator signal, not decoration), this rail, and the chip.

     THE RAIL IS F&H'S OWN RAMP AND READS NO CLIENT TOKEN, and the first draft of it did
     the opposite. Reading the client brand token with the F&H ramp as its fallback looked
     right: violet on F&H's screen, the client's color on a client's.
     verify_td19_brand.js said no, on a check TD-19 wrote
     the day before, "the builder console is styled from its own ramp, so a client token
     cannot reach it". The check is right and the draft was wrong. This bar is the
     OPERATOR's chrome: `body.role-client > .wrap` is display:none further down this file,
     so a client signed in to their own suite never sees it, and the one place it does
     appear over a client's suite is the share view, where tinting F&H's own chrome with
     the client's color is the exact bug TD-19 was written to stop.
     So the rail is violet because this chrome is F&H's, and it says so. A client's
     surfaces carry the client's color through the shell, head to foot, which is the other
     half of the same statement. Checked against Basin Ridge's copper (#B8531D) as well as
     F&H's violet, which is why the second client exists in the suites.

     THE CHIP IS NOT HERE, AND THAT IS A DECISION, NOT AN OMISSION. The work order asked
     for an ADMIN / CLIENT SUITE chip in this bar and said not to half-apply it. It
     cannot be applied here: `body.role-client > .wrap` is display:none further down this
     file, so a client signed in to their own suite never sees this bar at all, and in the
     share view fh-shell.css offsets it behind the shell's own top bar. A chip in this bar
     would read ADMIN on the only screen that shows it and would never be seen on either
     of the screens it is meant to tell apart. It needs the shell's header, which is
     fh-shell.js and twenty-two module pages, and which cannot yet tell an operator in the
     sandbox from a client on their own phone. Written up as its own short lane in the
     TD-20 status file. */
  header.bar::before{content:"";position:absolute;left:0;right:0;top:0;height:3px;
    border-radius:var(--radius) var(--radius) 0 0;pointer-events:none;
    background:linear-gradient(90deg,var(--purple),var(--purple-bright))}

  /* ─── WHOSE SCREEN IS THIS: THE FOURTH SIGNAL, AND THE ONLY ONE THAT IS A WORD ──
     TD-21, September 4, 2026. DESIGN.md has listed three signals since TD-20 and said
     in as many words that the fourth was not built. All three of the others are
     COLORS: the palette, the accent rail above, and the metal sheen. A client's brand
     is a color too, so sooner or later one of them is violet and all three agree with
     each other about the wrong answer. A palette cannot reproduce a word. A client's
     suite can be violet from top to bottom and still never say ADMIN.

     WHY IT IS TWO ELEMENTS AND NOT ONE. TD-20 looked at putting it in this bar and
     stopped, correctly: `body.role-client > .wrap` is display:none further down this
     file, so a chip here would read ADMIN on the only screen that shows it and
     would never appear on either of the two screens it exists to tell apart. The
     answer is a chip on EACH header. #fhSigOperator is in header.bar, which is F&H's
     own chrome. #fhSigClient is in .cd-brandbar, which is the client suite's own
     header and is the one surface BOTH a signed-in client and an operator previewing
     that client can see. On that screen you are looking at a client's suite whoever
     you are, so the word is the same for both. The chip names the SURFACE, not the
     viewer, and that is what makes it checkable.

     AND THE OPERATOR CHIP IS BLANKED BY A RULE, NOT BY AN ACCIDENT. It would already
     be invisible on a client's screen, because .wrap is hidden. That is a fact about
     a different rule. TD-20 recorded exactly this reasoning about the metal sheen: it
     was an operator signal by accident, because the elements carrying the class
     happened to sit on builder screens, until a rule made it one. So both client
     classes blank this chip explicitly, and it stays true whatever ends up carrying
     the class.

     THE WORD IS ADMIN, AND IT IS COPY, NOT WIRING. TD-22, September 4, 2026. TD-21
     shipped this chip reading OPERATOR; Bryant read it on his own console and asked for
     Admin instead. Only the text a person reads changed. The id #fhSigOperator and the
     class .fh-sig-op are what this stylesheet, the two blanking rules below and two
     suites all look for, so they did not move: renaming them would buy nothing and
     would break every one of those. The CLIENT SUITE chip was left alone, because it
     was not asked for and it reads correctly.

     WHAT IS STILL NOT COVERED, said out loud so nobody reads this as finished: the
     twenty-two module pages. Their header is built by fh-shell.js and it cannot yet
     tell an operator previewing a module from a client on their own phone. That is
     its own short lane, named in DESIGN.md and in the TD-21 status file.
     ─────────────────────────────────────────────────────────────────────── */
  .fh-sig{flex:0 0 auto;font-family:'Archivo',sans-serif;font-weight:800;font-size:9px;
    letter-spacing:.16em;text-transform:uppercase;border-radius:6px;padding:5px 9px;
    white-space:nowrap;align-self:center}
  .fh-sig-op{color:#fff;background:linear-gradient(160deg,var(--purple-bright),var(--purple-dark));
    box-shadow:0 4px 10px -4px rgba(123,63,184,.6)}
  /* The client chip is deliberately QUIET. It is a label on the surface you are
     already looking at, not a warning, and a loud one on every client screen would be
     read past inside a week. The operator chip is the loud one, because it is the one
     carrying the claim. */
  .fh-sig-cl{color:var(--text-dim);background:var(--surface-3);border:1px solid var(--navy-line)}
  body.role-client #fhSigOperator,
  body.client-view #fhSigOperator{display:none!important}
  .tools{display:flex;align-items:center;gap:11px;flex:0 0 auto}
  .iconbtn{flex:0 0 auto;width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border-radius:11px;border:1px solid var(--navy-line);background:var(--surface-3);color:var(--text-dim);cursor:pointer;transition:.2s}
  .iconbtn:hover{border-color:var(--purple);color:var(--purple-bright)}
  .iconbtn svg{width:18px;height:18px}
  /* A ROUNDED SQUARE, AND NEVER A CIRCLE. (Bryant, August 25, 2026.)

     This was `border-radius:50%` with `object-fit:cover`, which is the worst pair available for a
     mark: cover CROPS to fill the box, and the circle then masks off the corners of what is left.
     A wordmark loses its ends and a square badge loses four bites. Every other logo slot in the
     product was already a rounded square with `contain` (.cd-logoslot 13px, .preview-logo 12px,
     the PIN gate 14px), so this was also the odd one out.

     `contain` keeps the whole mark and letterboxes it. The 4px inset stops a trimmed logo from
     colliding with the corner radius, and the inner radius is the outer minus that inset so the
     two curves stay concentric rather than one riding inside the other.

     The dead space between the mark and this border is dealt with in the IMAGE, by
     trimLogoPadding() in dashboard.js. No amount of CSS can crop padding that is baked into the
     file: to the browser an empty border is part of the picture. */
  .seal{flex:0 0 auto;width:50px;height:50px;border-radius:14px;display:grid;place-items:center;
    padding:4px;box-sizing:border-box;overflow:hidden;
    background:#f7f5f2;box-shadow:0 0 0 1px var(--navy-line)}   /* fixed light backing, see .cd-logoslot */
  .seal img{width:100%;height:100%;border-radius:10px;display:block;object-fit:contain}
  @keyframes pulse{0%,100%{box-shadow:0 0 0 1px var(--purple-dark),0 0 26px -8px var(--purple-bright)}50%{box-shadow:0 0 0 1px var(--purple),0 0 44px -2px var(--purple-bright)}}
  .titles{flex:1 1 auto;min-width:0}
  .wordmark{font-family:'Archivo',sans-serif;font-weight:800;font-size:clamp(20px,2.4vw,26px);line-height:1;letter-spacing:.06em;color:var(--text)}
  .subtitle{font-family:'Archivo',sans-serif;font-weight:600;font-size:10.5px;letter-spacing:.16em;color:var(--text-dim);text-transform:uppercase;margin-top:4px}
  .clock{flex:0 0 auto;text-align:right;font-family:'Archivo',sans-serif;border-left:1px solid var(--navy-line);padding-left:13px}
  .clock .t{font-size:18px;font-weight:700;color:var(--text);line-height:1}
  .clock .d{font-size:10px;letter-spacing:.15em;color:var(--text-dim);text-transform:uppercase;margin-top:3px}
  .navlink{flex:0 0 auto;height:40px;display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-family:'Archivo',sans-serif;font-weight:600;font-size:11.5px;letter-spacing:.04em;text-transform:uppercase;color:var(--text);background:var(--surface-3);border:1px solid var(--navy-line);border-radius:11px;padding:0 14px;transition:.2s;text-decoration:none}
  .navlink:hover{border-color:var(--purple);color:var(--purple-bright)}
  .navlink svg{width:16px;height:16px;color:var(--purple-bright)}
  /* .clientpick and its four rules were retired by TD-21 on September 4, 2026, with
     the #clientPick markup they styled. That control was the header client picker,
     and it had been display:none since the rule further down this file reading
     "focused on the assembly -> dashboard flow". The client is the navigation now:
     see .op-spine. */

  .sec-head{display:flex;align-items:center;gap:16px;margin:42px 4px 20px}
  .sec-head h2{font-family:'Archivo',sans-serif;font-weight:700;font-size:15px;letter-spacing:.32em;text-transform:uppercase;margin:0;color:var(--text-dim)}
  .sec-head .rule{flex:1;height:1px;background:linear-gradient(90deg,var(--purple),transparent)}

  .featured{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}
  .action{position:relative;cursor:pointer;border:0;text-align:left;color:var(--text);padding:24px 24px 22px;border-radius:var(--radius);overflow:hidden;
    background:linear-gradient(155deg,var(--action-1) 0%,var(--action-2) 48%,var(--action-3) 100%);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.12),inset 0 0 0 1px rgba(144,90,207,.4),0 24px 48px -28px #000;
    transition:transform .18s ease,box-shadow .25s ease;font-family:'Inter',sans-serif}
  .action::after{content:"";position:absolute;inset:0;background:radial-gradient(120% 120% at 0% 0%,rgba(144,90,207,.25),transparent 55%);pointer-events:none}
  .action:hover{transform:translateY(-3px);box-shadow:inset 0 1px 0 rgba(255,255,255,.18),inset 0 0 0 1px rgba(144,90,207,.8),0 0 40px -8px var(--purple-bright),0 28px 56px -30px #000}
  .action:active{transform:translateY(0) scale(.995)}
  .action .ico{width:42px;height:42px;color:var(--metal-1);margin-bottom:12px;filter:drop-shadow(0 2px 2px rgba(0,0,0,.5))}
  .action h3{font-family:'Archivo',sans-serif;font-weight:800;font-size:21px;letter-spacing:.03em;margin:0 0 6px}
  .action p{margin:0;font-size:12.5px;color:var(--text-dim);line-height:1.5}
  .action .go{position:absolute;top:22px;right:22px;width:30px;height:30px;border-radius:50%;display:grid;place-items:center;background:rgba(144,90,207,.18);border:1px solid rgba(144,90,207,.5);color:var(--metal-1);transition:.2s}
  .action:hover .go{background:var(--purple);border-color:var(--purple-bright)}

  .statgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
  .tile{position:relative;cursor:pointer;border:0;color:var(--text);text-align:center;padding:24px 14px 18px;border-radius:var(--radius);overflow:hidden;
    background:linear-gradient(180deg,var(--surface-hi) 0%,var(--surface) 55%,var(--surface-3) 100%);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.07),inset 0 0 0 1px rgba(120,140,200,.10),0 16px 34px -26px #000;
    transition:transform .16s ease,box-shadow .22s ease;font-family:'Inter',sans-serif}
  .tile:hover{transform:translateY(-3px);box-shadow:inset 0 1px 0 rgba(255,255,255,.12),inset 0 0 0 1px rgba(144,90,207,.6),0 0 30px -10px var(--purple-bright),0 18px 40px -26px #000}
  .tile:active{transform:translateY(0) scale(.99)}
  .tile .ring{width:54px;height:54px;border-radius:50%;margin:0 auto 12px;display:grid;place-items:center;
    background:radial-gradient(circle at 50% 38%,rgba(123,63,184,.30),rgba(10,19,52,.2));
    box-shadow:0 0 0 1px var(--purple-dark),inset 0 0 0 1px rgba(255,255,255,.06);transition:.22s}
  .tile:hover .ring{box-shadow:0 0 0 1px var(--purple),0 0 22px -6px var(--purple-bright),inset 0 0 0 1px rgba(255,255,255,.08)}
  .tile .ico{width:26px;height:26px;color:var(--metal-1)}
  .tile .label{font-family:'Archivo',sans-serif;font-weight:700;font-size:13.5px;letter-spacing:.03em;color:var(--text)}
  .tile .sub{font-size:10.5px;color:var(--text-dim);margin-top:3px}
  .badge{position:absolute;top:11px;right:11px;font-family:'Archivo',sans-serif;font-weight:700;font-size:9px;letter-spacing:.12em;text-transform:uppercase;padding:3px 7px;border-radius:20px;border:1px solid}
  .badge.live{color:var(--live);border-color:rgba(63,208,160,.4);background:rgba(63,208,160,.08)}
  .badge.ai{color:var(--purple-bright);border-color:rgba(144,90,207,.5);background:rgba(144,90,207,.12)}
  .badge.soon{color:var(--soon);border-color:rgba(126,136,166,.35);background:rgba(126,136,166,.08)}
  .badge.beta{color:#5ca8ff;border-color:rgba(92,168,255,.45);background:rgba(92,168,255,.1)}

  footer{margin-top:50px;text-align:center;color:var(--text-dim);font-size:11px;letter-spacing:.22em;text-transform:uppercase;font-family:'Archivo',sans-serif}

  .overlay{position:fixed;top:0;left:0;right:0;bottom:0;width:100%;height:100%;z-index:150;display:none;align-items:flex-start;justify-content:center;padding:40px 18px;background:var(--scrim,rgba(6,8,20,.6));backdrop-filter:blur(7px);overflow-y:auto}
  .overlay.open{display:flex!important;animation:fade .25s ease}
  /* The share popup is opened from inside the client dashboard (.pageview is z-index 200),
     so it must stack above that page — otherwise it opens hidden behind it. */
  #shareModal{z-index:250}
  /* preview modal opens from inside the wizard (.pageview z-200) — keep it on top */
  #previewModal{z-index:260}
  #paletteCompareModal{z-index:261}
  input[type="password"]::-ms-reveal,input[type="password"]::-ms-clear{display:none!important}
  #previewModal .modal.preview-lg{max-width:min(1180px,95vw);height:88vh;display:flex;flex-direction:column;overflow:hidden}
  #previewModal .modal.preview-lg h3{flex:none}
  #previewModal .modal-body{flex:1;min-height:0;padding:0}
  #previewModal #brandPreviewBody{height:100%}
  #previewModal .bp-frame{width:100%;height:100%;border:0;background:#fff;display:block}
  @keyframes fade{from{opacity:0}to{opacity:1}}
  .modal{width:100%;max-width:760px;border-radius:20px;background:var(--surface-2);border:1px solid var(--navy-line);
    box-shadow:0 0 0 1px rgba(144,90,207,.25),0 40px 90px -40px #000;animation:rise .3s cubic-bezier(.2,.8,.2,1)}
  @keyframes rise{from{transform:translateY(18px);opacity:0}to{transform:translateY(0);opacity:1}}
  .modal-head{display:flex;align-items:center;gap:14px;padding:22px 24px;border-bottom:1px solid var(--navy-line)}
  .modal-head h3{font-family:'Archivo',sans-serif;font-weight:800;font-size:20px;letter-spacing:.04em;margin:0;flex:1}
  .modal-head .x{cursor:pointer;border:1px solid var(--navy-line);background:transparent;color:var(--text-dim);width:34px;height:34px;border-radius:9px;font-size:18px;line-height:1;transition:.2s}
  /* The drawer close button matched no .x rule at all, so it came out 27x21. */
  .as-drawer .x{cursor:pointer;border:1px solid var(--navy-line);background:transparent;color:var(--text-dim);width:44px;height:44px;border-radius:9px;font-size:20px;line-height:1}
  .modal-head .x:hover{color:var(--text);border-color:var(--purple)}
  .modal-body{padding:24px}
  .field{margin-bottom:20px}
  .field label{display:block;font-family:'Archivo',sans-serif;font-weight:600;font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--text-dim);margin-bottom:8px}
  .field input[type=text],.field textarea{width:100%;padding:12px 14px;border-radius:10px;border:1px solid var(--navy-line);background:var(--recess);color:var(--text);font-family:'Inter',sans-serif;font-size:14px;outline:none;transition:.2s;resize:vertical}
  .field input[type=text]:focus,.field textarea:focus{border-color:var(--purple);box-shadow:0 0 0 3px rgba(123,63,184,.2)}
  .swatches{display:flex;gap:14px;flex-wrap:wrap}
  .swatch{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--text-dim)}
  .swatch input[type=color]{width:42px;height:42px;border:1px solid var(--navy-line);border-radius:10px;background:none;cursor:pointer;padding:2px}
  .drop{border:1.5px dashed rgba(144,90,207,.5);border-radius:12px;padding:24px;text-align:center;cursor:pointer;transition:.2s;color:var(--text-dim);font-size:13px}
  .drop:hover,.drop.hot{border-color:var(--purple-bright);background:rgba(123,63,184,.08);color:var(--text)}
  .drop b{color:var(--metal-1)}
  .preview-logo{margin-top:14px;display:flex;align-items:center;gap:14px}
  .preview-logo img{width:64px;height:64px;border-radius:12px;object-fit:contain;background:var(--surface-3);border:1px solid var(--navy-line)}
  .gallery{display:grid;grid-template-columns:repeat(auto-fill,minmax(84px,1fr));gap:10px;margin-top:12px}
  .gallery .cell{position:relative;aspect-ratio:1;border-radius:10px;overflow:hidden;border:1px solid var(--navy-line)}
  .gallery .cell img{width:100%;height:100%;object-fit:cover}
  .gallery .cell button{position:absolute;top:4px;right:4px;width:20px;height:20px;border-radius:50%;border:0;background:rgba(2,4,14,.8);color:#fff;cursor:pointer;font-size:11px;line-height:1}
  .btnrow{display:flex;gap:12px;justify-content:flex-end;margin-top:8px;flex-wrap:wrap}
  .btn{font-family:'Archivo',sans-serif;font-weight:700;letter-spacing:.05em;cursor:pointer;border-radius:11px;padding:12px 20px;font-size:14px;border:1px solid var(--navy-line);background:var(--surface);color:var(--text);transition:.2s}
  .btn:hover{border-color:var(--purple)}
  .btn.primary{background:linear-gradient(180deg,var(--purple),var(--purple-mid));border:1px solid var(--purple-bright);color:#fff;box-shadow:0 0 24px -8px var(--purple-bright)}
  .btn.primary:hover{filter:brightness(1.1)}
  .btn:disabled{opacity:.5;cursor:not-allowed}
  /* client-card action menu: inline row on desktop, compact dropdown on mobile */
  .cardmenu{position:relative;display:flex;justify-content:flex-end}
  .cardmenu-toggle{display:none;width:42px;height:38px;align-items:center;justify-content:center;border-radius:10px;border:1px solid var(--navy-line);background:var(--surface-3);color:var(--text);cursor:pointer;font-size:22px;line-height:1;padding:0}
  /* Six actions live on this row (View Dashboard, Info & Branding, Module Manager,
     Notifications, Features, Delete). Without flex-wrap they cannot break to a second
     line, so they shrink below their own labels: the text wraps inside each button,
     the heights go uneven, and Delete runs past the card's right edge. Wrapping the ROW
     and refusing to wrap the LABEL is what keeps a button a button. Added 2026-09-02. */
  .cardmenu-list{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}
  .cardmenu-list .btn{white-space:nowrap;padding:10px 15px;font-size:13px}
  @media(max-width:560px){
    .cardmenu-toggle{display:inline-flex}
    .cardmenu-list{display:none}
    /* Inline-flow menu on mobile: expands within the card and pushes content down, so it
       is never clipped by the modal's scroll area (the old absolute dropdown got cut off). */
    .cardmenu.open{flex-wrap:wrap;width:100%}
    .cardmenu.open .cardmenu-list{display:flex;position:static;flex-direction:column;gap:6px;width:100%;margin-top:8px;background:var(--surface-hi);border:1px solid var(--navy-line);border-radius:12px;padding:8px;box-shadow:0 18px 44px -14px rgba(0,0,0,.6)}
    .cardmenu-list .btn{width:100%;text-align:left}
  }
  .btn .spin{display:inline-block;width:13px;height:13px;border:2px solid rgba(255,255,255,.35);border-top-color:#fff;border-radius:50%;animation:spin .7s linear infinite;vertical-align:-2px;margin-right:7px}
  @keyframes spin{to{transform:rotate(360deg)}}
  .hint{font-size:11px;color:var(--text-dim);margin-top:8px;line-height:1.5}
  .toast{position:fixed;bottom:26px;left:50%;transform:translateX(-50%) translateY(20px);z-index:80;
    background:#16181F;border:1px solid var(--purple-bright);color:#fff;
    padding:13px 22px;border-radius:12px;font-size:13px;font-family:'Archivo',sans-serif;letter-spacing:.04em;
    box-shadow:0 0 30px -8px var(--purple-bright);opacity:0;pointer-events:none;transition:.3s;max-width:90vw;text-align:center}
  .toast.show{opacity:1;transform:translateX(-50%) translateY(0)}

  /* Template library */
  .tabs{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:18px}
  .tab{cursor:pointer;border:1px solid var(--navy-line);background:var(--recess);color:var(--text-dim);font-family:'Archivo',sans-serif;font-weight:600;font-size:12px;letter-spacing:.08em;padding:8px 14px;border-radius:20px;transition:.2s}
  .tab.active,.tab:hover{border-color:var(--purple);color:var(--text);background:rgba(123,63,184,.12)}
  .tpl-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:12px;max-height:54vh;overflow-y:auto;padding-right:4px}
  .tpl-card{cursor:pointer;border:1px solid var(--navy-line);border-radius:12px;padding:15px 16px;background:linear-gradient(180deg,var(--surface-2),var(--surface-3));transition:.18s;text-align:left}
  .tpl-card:hover{border-color:var(--purple);transform:translateY(-2px);box-shadow:0 0 24px -10px var(--purple-bright)}
  .tpl-card .nm{font-family:'Archivo',sans-serif;font-weight:700;font-size:14.5px;color:var(--metal-1);margin-bottom:4px}
  .tpl-card .ds{font-size:11.5px;color:var(--text-dim);line-height:1.45}
  .tpl-card .ct{display:inline-block;margin-top:9px;font-family:'Archivo',sans-serif;font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--purple-bright);border:1px solid rgba(144,90,207,.4);border-radius:14px;padding:2px 8px}
  .ai-tag{display:inline-flex;align-items:center;gap:5px;font-family:'Archivo',sans-serif;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--purple-bright)}

  /* Chat */
  .chat-log{height:46vh;min-height:280px;overflow-y:auto;display:flex;flex-direction:column;gap:12px;padding:4px 4px 8px}
  .msg{max-width:84%;padding:11px 14px;border-radius:14px;font-size:13.5px;line-height:1.55;white-space:pre-wrap;word-wrap:break-word}
  .msg.user{align-self:flex-end;background:linear-gradient(180deg,var(--purple),var(--purple-mid));color:#fff;border-bottom-right-radius:4px}
  .msg.bot{align-self:flex-start;background:var(--surface-2);border:1px solid var(--navy-line);color:var(--text);border-bottom-left-radius:4px}
  .msg.bot.think{color:var(--text-dim);font-style:italic}
  .dots span{display:inline-block;width:6px;height:6px;margin:0 2px;border-radius:50%;background:var(--purple-bright);animation:blink 1.2s infinite}
  .dots span:nth-child(2){animation-delay:.2s}.dots span:nth-child(3){animation-delay:.4s}
  @keyframes blink{0%,80%,100%{opacity:.3}40%{opacity:1}}
  .chat-input{display:flex;gap:10px;margin-top:14px}
  .chat-input input{flex:1;padding:13px 15px;border-radius:12px;border:1px solid var(--navy-line);background:var(--recess);color:var(--text);font-family:'Inter',sans-serif;font-size:14px;outline:none}
  .chat-input input:focus{border-color:var(--purple)}
  .chips{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px}
  .chip{cursor:pointer;font-size:11.5px;color:var(--text-dim);border:1px solid var(--navy-line);background:var(--recess);border-radius:18px;padding:6px 12px;transition:.2s}
  .chip:hover{border-color:var(--purple);color:var(--text)}

  /* Incident module */
  .form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px}
  .form-grid .field{margin-bottom:0}
  .full{grid-column:1/-1}
  .field select{width:100%;padding:12px 14px;border-radius:10px;border:1px solid var(--navy-line);background:var(--recess);color:var(--text);font-family:'Inter',sans-serif;font-size:14px;outline:none;transition:.2s;-webkit-appearance:none;appearance:none;cursor:pointer}
  .field select:focus{border-color:var(--purple);box-shadow:0 0 0 3px rgba(123,63,184,.2)}
  .analysis{margin-top:20px;padding-top:20px;border-top:1px solid var(--navy-line);display:none}
  .analysis.show{display:block;animation:fade .3s ease}
  .sev{display:inline-block;font-family:'Archivo',sans-serif;font-weight:700;font-size:12px;letter-spacing:.12em;text-transform:uppercase;padding:5px 14px;border-radius:20px;border:1px solid}
  .sev.low{color:var(--live);border-color:rgba(63,208,160,.45);background:rgba(63,208,160,.1)}
  .sev.medium{color:#f0c14b;border-color:rgba(240,193,75,.45);background:rgba(240,193,75,.1)}
  .sev.high{color:#ff8a5c;border-color:rgba(255,138,92,.5);background:rgba(255,138,92,.12)}
  .sev.critical{color:#ff5d76;border-color:rgba(255,93,118,.55);background:rgba(255,93,118,.14)}
  .cards{display:grid;gap:10px}
  .acard{border:1px solid var(--navy-line);border-left:3px solid var(--purple);border-radius:10px;padding:12px 14px;background:var(--surface-2)}
  .acard .at{font-family:'Archivo',sans-serif;font-weight:700;font-size:14px;color:var(--metal-1)}
  .acard .am{font-size:12px;color:var(--text-dim);margin-top:4px}
  .acard .ap{display:inline-block;margin-top:8px;font-family:'Archivo',sans-serif;font-size:9px;letter-spacing:.1em;text-transform:uppercase;padding:2px 8px;border-radius:14px;border:1px solid rgba(144,90,207,.4);color:var(--purple-bright)}
  .acard .ap.High{color:#ff5d76;border-color:rgba(255,93,118,.5)}
  .acard .ap.Medium{color:#f0c14b;border-color:rgba(240,193,75,.45)}
  .recent{display:flex;flex-direction:column;gap:8px}
  /* Sync & Storage. paintStorage() joins its four sentences with \n and writes them
     as textContent, so the panel MUST keep the line breaks - pre-line. The .recent
     class was the obvious place to put it and would have run all four together into
     one paragraph. */
  .storagepanel{white-space:pre-line;font-size:13px;line-height:1.65;color:var(--text);
    border:1px solid var(--navy-line);border-radius:10px;padding:11px 13px;background:var(--recess)}
  .recent .ri{display:flex;justify-content:space-between;align-items:center;gap:10px;font-size:12px;border:1px solid var(--navy-line);border-radius:9px;padding:9px 12px;background:var(--recess)}
  .recent .ri .meta{color:var(--text-dim)}
  .recent .empty{font-size:12px;color:var(--text-dim);font-style:italic}

  /* Academy / Oversight / Knowledge / Agent */
  .question{border:1px solid var(--navy-line);border-radius:12px;padding:14px 16px;margin-bottom:12px;background:var(--surface)}
  .qtext{font-family:'Archivo',sans-serif;font-weight:600;font-size:14px;margin-bottom:10px}
  .opt{display:block;font-size:13px;color:var(--text);padding:7px 0;cursor:pointer}
  .opt input{margin-right:8px}
  .logitem{display:flex;justify-content:space-between;align-items:center;gap:12px;border:1px solid var(--navy-line);border-radius:10px;padding:10px 13px;margin-bottom:8px;background:var(--recess)}
  .logitem .lmain{display:flex;flex-direction:column;gap:2px;min-width:0}
  .logitem .lk{font-family:'Archivo',sans-serif;font-weight:700;font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:var(--purple-bright)}
  .logitem .ls{font-size:12.5px;color:var(--text)}
  .logitem .lt{font-size:10.5px;color:var(--text-dim)}
  .logitem .lstat{display:flex;align-items:center;gap:8px;flex:0 0 auto}
  .st{font-family:'Archivo',sans-serif;font-size:10px;letter-spacing:.1em;text-transform:uppercase;padding:3px 9px;border-radius:14px;border:1px solid}
  .st.pending{color:#f0c14b;border-color:rgba(240,193,75,.4)}
  .st.reviewed{color:#5ca8ff;border-color:rgba(92,168,255,.4)}
  .st.approved{color:var(--live);border-color:rgba(63,208,160,.45)}
  .ktype{font-family:'Archivo',sans-serif;font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--purple-bright);border:1px solid rgba(144,90,207,.4);border-radius:14px;padding:2px 8px;margin-left:6px}
  .agentbox{margin-top:18px;padding-top:18px;border-top:1px solid var(--navy-line);display:none}
  .agentbox.show{display:block;animation:fade .3s ease}

  /* Soon-tier modules */
  .cal{display:grid;grid-template-columns:repeat(7,1fr);gap:5px}
  .cal .dow{font-family:'Archivo',sans-serif;font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--text-dim);text-align:center;padding:4px 0}
  .cal .cell{aspect-ratio:1;border:1px solid var(--navy-line);border-radius:8px;padding:4px;font-size:11px;background:var(--recess);position:relative;overflow:hidden;display:flex;flex-direction:column;gap:2px}
  .cal .cell.empty{background:transparent;border:none}
  .cal .cell .dn{color:var(--text-dim);font-size:10px}
  .cal .cell.today{border-color:var(--purple-bright);box-shadow:inset 0 0 0 1px var(--purple-bright)}
  .cal .dot{font-size:8px;line-height:1.25;color:var(--text);background:rgba(123,63,184,.35);border-radius:4px;padding:1px 3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .cal .dot.due{background:rgba(255,138,92,.35)}
  .cal .dot.train{background:rgba(92,168,255,.35)}
  .calnav{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
  .calnav .mlabel{font-family:'Archivo',sans-serif;font-weight:700;font-size:16px;letter-spacing:.04em}
  .gauge-wrap{display:flex;align-items:center;gap:22px;flex-wrap:wrap;justify-content:center;margin:6px 0 18px}
  .gauge{position:relative;width:160px;height:90px}
  .gauge svg{width:160px;height:90px;overflow:visible}
  .gauge .gv{position:absolute;bottom:0;left:0;right:0;text-align:center;font-family:'Archivo',sans-serif;font-weight:800;font-size:30px}
  .gband{font-family:'Archivo',sans-serif;font-weight:700;letter-spacing:.1em;text-transform:uppercase;font-size:13px;padding:5px 14px;border-radius:20px;border:1px solid}
  .gband.low{color:var(--live);border-color:rgba(63,208,160,.45)}
  .gband.moderate{color:#f0c14b;border-color:rgba(240,193,75,.45)}
  .gband.elevated{color:#ff8a5c;border-color:rgba(255,138,92,.5)}
  .gband.high{color:#ff5d76;border-color:rgba(255,93,118,.55)}
  .scale{display:flex;flex-direction:column;gap:10px;margin:10px 0}
  .scale .srow{display:flex;align-items:center;gap:12px}
  .scale .slabel{flex:1;font-size:13px}
  .scale input[type=range]{flex:1;accent-color:var(--purple)}
  .scale .sval{width:26px;text-align:center;font-family:'Archivo',sans-serif;font-weight:700;color:var(--purple-bright)}
  .visiondrop{border:1.5px dashed rgba(144,90,207,.5);border-radius:12px;padding:30px;text-align:center;cursor:pointer;transition:.2s;color:var(--text-dim)}
  .visiondrop:hover,.visiondrop.hot{border-color:var(--purple-bright);background:rgba(123,63,184,.08);color:var(--text)}
  .visionprev{margin-top:14px;border-radius:12px;max-width:100%;max-height:280px;display:block;border:1px solid var(--navy-line)}
  .finding{border:1px solid var(--navy-line);border-left:3px solid var(--purple);border-radius:10px;padding:11px 13px;margin-bottom:8px;background:var(--surface-2)}
  .finding .ft{font-family:'Archivo',sans-serif;font-weight:700;font-size:13.5px;color:var(--metal-1)}
  .finding .fd{font-size:12px;color:var(--text-dim);margin-top:3px}

  @media(max-width:980px){.featured{grid-template-columns:1fr}.statgrid{grid-template-columns:repeat(3,1fr)}}
  @media(max-width:680px){.statgrid{grid-template-columns:repeat(2,1fr)}.tpl-grid{grid-template-columns:1fr}.seal{width:46px;height:46px}.clock{display:none}.form-grid{grid-template-columns:1fr}}
  /* ─── Suite Builder ───────────────────────────────────────────────── */
  .sb-meta{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:14px 16px;border-radius:12px;background:var(--surface-3);border:1px solid var(--navy-line);margin-bottom:18px}
  .sb-meta .label{font-family:'Archivo',sans-serif;font-size:10px;letter-spacing:.2em;color:var(--text-dim);text-transform:uppercase;margin-bottom:4px}
  .sb-meta .name{font-family:'Archivo',sans-serif;font-weight:700;font-size:18px;color:var(--text)}
  .sb-meta .count{font-family:'Archivo',sans-serif;font-size:10.5px;font-weight:600;letter-spacing:.14em;text-transform:uppercase;color:var(--purple-bright);padding:7px 11px;border-radius:9px;background:var(--ctl-bg);border:1px solid var(--ctl-line);white-space:nowrap}
  .sb-section{margin-bottom:22px}
  .sb-section h4{font-family:'Archivo',sans-serif;font-weight:700;font-size:11px;letter-spacing:.28em;text-transform:uppercase;color:var(--text-dim);margin:0 0 10px;display:flex;align-items:center;gap:12px}
  .sb-section h4::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--navy-line),transparent)}
  .sb-list{display:grid;grid-template-columns:1fr;gap:8px}
  .sb-row{display:flex;align-items:center;gap:14px;padding:13px 14px;border-radius:11px;background:var(--surface-3);border:1px solid var(--navy-line);transition:border-color .2s}
  .sb-row:hover{border-color:var(--purple-mid)}
  .sb-row .info{flex:1 1 auto;min-width:0}
  .sb-row .name{font-family:'Archivo',sans-serif;font-weight:600;font-size:13.5px;color:var(--text);letter-spacing:.02em;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
  .sb-row .sub{font-size:11px;color:var(--text-dim);margin-top:3px}
  .sb-row .pill{display:inline-block;font-family:'Archivo',sans-serif;font-size:9px;font-weight:600;letter-spacing:.18em;text-transform:uppercase;color:var(--soon);border:1px solid var(--soon);padding:2px 8px;border-radius:6px}
  /* "Custom" on a Module Manager row. Deliberately quieter than the purple "In suite"
     chip beside it: this one says what a row IS, not what state it is in, so it must not
     read as a second status. Both colors are tokens, so it follows the theme. */
  .mm-cust-tag{display:inline-block;font-family:'Archivo',sans-serif;font-size:9px;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--text-dim);border:1px solid var(--navy-line);padding:2px 7px;border-radius:6px;white-space:nowrap}
  .sb-toggle{flex:0 0 auto;width:44px;height:24px;border-radius:14px;background:var(--surface);border:1px solid var(--navy-line);position:relative;cursor:pointer;transition:background .25s,border-color .25s,box-shadow .25s}
  .sb-toggle::after{content:"";position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:linear-gradient(180deg,var(--metal-1),var(--metal-3));transition:left .25s,background .25s;box-shadow:0 1px 2px rgba(0,0,0,.4)}
  .sb-toggle.on{background:linear-gradient(180deg,var(--purple-dark),var(--purple));border-color:var(--purple-bright);box-shadow:0 0 12px -2px var(--purple-bright)}
  .sb-toggle.on::after{left:22px;background:linear-gradient(180deg,var(--metal-hi),var(--metal-1))}
  /* 44px wide but only 24px tall, so a thumb misses it vertically. ::after is already
     the knob, so the hit area is ::before. Safe as an overlay rather than a real
     min-height because .sb-row is about 60px tall with an 8px gap, which leaves 24px
     of clearance between one row's 44px area and the next. */
  .sb-toggle::before{content:"";position:absolute;left:0;right:0;top:50%;transform:translateY(-50%);height:44px}
  .sb-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:18px;padding-top:18px;border-top:1px solid var(--navy-line)}
  .sb-actions button{font-family:'Archivo',sans-serif;font-weight:600;font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;padding:11px 18px;border-radius:11px;cursor:pointer;transition:.2s;color:var(--text)}
  .sb-actions .save{background:linear-gradient(180deg,var(--purple),var(--purple-dark));color:var(--metal-hi);border:1px solid var(--purple-bright);box-shadow:0 0 14px -3px var(--purple-bright)}
  .sb-actions .save:hover{filter:brightness(1.1)}
  .sb-actions .reset{background:var(--surface-3);border:1px solid var(--navy-line)}
  .sb-actions .reset:hover{border-color:var(--purple)}

  /* ─── Operator Console ─────────────────────────────────────────────── */
  .op-console{margin-top:30px}
  /* THE GREETING ABOVE THE CONSOLE. TD-22, September 4, 2026. It sits above the shell
     rather than inside the stage head, because the stage head is about the client that
     is selected and this sentence is about the person reading the screen: putting it
     there would make it repaint on every client switch and read as a fact about the
     client. It is the F&H violet, not a client token, for the reason the accent rail
     above is: this chrome is F&H's own and a client's color must never reach it.
     EMPTY IS A REAL STATE. When no usable name is stored the element still carries the
     bare greeting, and if fh-home.js did not load it carries nothing at all and simply
     takes no room, which is why there is no min-height here. */
  .op-greet{font-family:'Archivo',sans-serif;font-weight:800;font-size:22px;
    letter-spacing:-.005em;color:var(--text);margin:0 0 16px}
  .op-greet:empty{display:none}
  /* .op-bar, .op-bar-label, .op-bar-pick, .op-bar-pick select, .op-bar-pick
     select:focus, .op-newbtn and .op-newbtn:hover were retired by TD-21 on
     September 4, 2026 with the "Active Client Suite" bar they styled. That bar held
     the SECOND client picker on this screen, and it was the one that did not filter:
     renderClientSelect() ran clientsList through isOfferableClient() and
     renderOpConsole() did not, so the sandbox tenant and the invented local-stac row
     were both selectable from the control in the middle of the screen. It had been
     display:none the whole time, which is why nobody hit it. Both pickers are gone
     and the spine carries the guard once, in one place. */
  .op-perf{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:24px}
  .op-card{padding:18px 16px;border-radius:14px;background:var(--surface-3);border:1px solid var(--navy-line);position:relative;overflow:hidden}
  .op-card .k{font-family:'Archivo',sans-serif;font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--text-dim);margin-bottom:12px}
  .op-card .v{font-family:'Archivo',sans-serif;font-weight:800;font-size:29px;line-height:1;color:var(--text)}
  .op-card .v.dim{color:var(--metal-4);font-size:24px}
  .op-card .note{font-size:10px;color:var(--text-dim);margin-top:9px;letter-spacing:.04em}
  .op-card .spark{position:absolute;right:13px;top:15px;color:var(--purple-bright);opacity:.7}
  .op-card .spark svg{width:18px;height:18px}
  /* ─── THE CLIENT-FIRST CONSOLE (TD-21, September 4, 2026) ──────────────

     WHAT WAS HERE. .op-actions and eleven .op-action* rules: a three-then-two grid
     of five big cards, their 44px violet icon squares, the live fact line under each
     title that TD-20 took off, and the hover that moved a card 2px and lit its
     border. All retired with the tile grid, at Bryant's direction, after he chose
     direction D from three mockups. Nothing here was wrong. The screen it painted
     was organized around verbs while the work is organized around nouns.

     THE ICON SQUARE ARGUMENT SURVIVES, because .op-act-ico is the same construction
     at a smaller size and the reasoning is still what decides it. In the LIGHT theme
     the square is a violet gradient with a white glyph, lit along its top edge, on a
     violet-tinted shadow: the same shape as the client home's tile ring, so the two
     screens read as one product. In the DARK theme the card is itself a violet slab,
     so a violet square on it measured 3.43:1 on September 4, over the icon floor and
     under anything you would call legible; there the square is glass instead, a white
     wash with a lit top edge and a white glyph. TD-20 then took the background off
     the dark rule, because fh-theme.css sets --fh-chip-bg to a measured 22% of
     --fh-on-brand and this selector was more specific, so it was quietly replacing a
     measured value with one that had already been measured and rejected.
     Same three sentences, same two rules, new class name.

     THE VIOLET IS F&H'S OWN and is written from the F&H ramp, because this is the
     builder's console. The only client color on this screen is the per-suite accent
     on a spine avatar, and that is written by dashboard.js from the roster row's
     accent_color, never from a token and never in this file. verify_td19_brand.js
     fails if this file ever reads a client brand token at all, and it should. Note
     that the check is a plain substring search, so writing the token's own name in
     this comment would fail it: the sentence you are reading was drafted with the
     name in it and turned the suite red on the first run. Same shape as the note in
     CLAUDE.md about writing an escape into prose about escapes.

     THE HOVER RULE. DESIGN.md: a hover changes something that is already there, it
     never draws an edge from nothing. So .op-act carries a resting edge and a
     resting shadow and hover deepens both. A spine row carries no edge at any time
     and hover changes its background only, which is the same rule kept rather than
     dodged: there is no edge to draw, so none can blink.
     ──────────────────────────────────────────────────────────────────────── */

  .op-shell{display:grid;grid-template-columns:250px minmax(0,1fr);gap:16px;align-items:start}

  /* THE SPINE: who you work on. */
  .op-spine{background:var(--surface-2);border:1px solid var(--navy-line);border-radius:var(--radius);
    padding:9px 8px 12px;box-shadow:0 12px 30px -28px rgba(0,0,0,.85)}
  .op-spine-sec{font-family:'Archivo',sans-serif;font-weight:700;font-size:9.5px;letter-spacing:.2em;
    text-transform:uppercase;color:var(--text-dim);padding:13px 11px 7px;margin:0}
  .op-row{display:flex;align-items:center;gap:11px;width:100%;min-height:44px;padding:7px 11px;
    border:0;border-radius:12px;background:transparent;color:var(--text);font:inherit;text-align:left;
    cursor:pointer;margin-bottom:2px;transition:background .15s}
  .op-row:hover{background:var(--surface-3)}
  .op-row-t{font-family:'Archivo',sans-serif;font-weight:700;font-size:13.5px;letter-spacing:.01em;
    flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  /* A ROUNDED SQUARE, NEVER A CIRCLE. Standing house rule, restated September 2026.
     AND FLAT, WHICH IS THE CONTRAST SWEEP'S DOING. It carried the icon-square recipe,
     a gradient into rgba(0,0,0,.28) with an inset light along the top, and
     verify_td19_contrast_dom.js scored the initials against the WORST pixel in the box,
     which was that light line: 1.23:1 on Basin Ridge's copper against a floor of 4.5.
     A recipe written for a box with a glyph in it is not a recipe for a box with a WORD
     in it. dashboard.js paints the fill and the ink from brand-apply.js, so no color of
     any kind is declared here. */
  .op-av{flex:0 0 auto;width:28px;height:28px;border-radius:9px;display:grid;place-items:center;
    font-family:'Archivo',sans-serif;font-weight:800;font-size:10px;letter-spacing:.02em}
  /* The selected suite wears an inset rail in its OWN accent and a wash of it. Both
     are set inline by dashboard.js from clients.accent_color, so this rule declares
     the shape and no color at all. */
  .op-row.on .op-row-t{font-weight:800}
  .op-row:focus-visible{outline:2px solid var(--purple-bright);outline-offset:-2px}

  /* THE SANDBOX. Hatched, gray, and captioned, so it can never be mistaken for a
     client suite at a glance. dashboard.js says why it does not become the active
     client. */
  .op-row-sand .op-av{background-image:repeating-linear-gradient(45deg,#8990A3,#8990A3 4px,#6F768A 4px,#6F768A 8px);
    background-color:#6F768A}
  .op-row-sand .op-row-t{color:var(--text-dim)}
  /* --text, not --text-dim. At 9px this is small text and takes the 4.5 floor, and the
     dim tone on the light surface measured 4.22:1. Nine pixels of monospace is already
     quiet enough without spending contrast on it. */
  .op-row-note{font-family:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
    font-size:9px;letter-spacing:.06em;text-transform:uppercase;color:var(--text);
    border:1px solid var(--navy-line);border-radius:5px;padding:2px 5px;flex:0 0 auto}
  /* Add a client suite: a dashed square, which reads as an empty slot rather than as
     somebody's initials. */
  /* The + is a WORD to the sweep, not a glyph, so it takes the 4.5 floor: --text-dim on
     --surface-3 measured 2.03:1 in the light theme and 2.00:1 in the dark. The dashed
     edge is what makes this read as an empty slot; the mark inside it does not have to
     be faint as well. */
  .op-row-add .op-av{background:var(--surface-3);border:1px dashed var(--text-dim);
    color:var(--text);box-shadow:none;font-size:15px;font-weight:400;line-height:1}
  .op-row-add .op-row-t{color:var(--text-dim);font-weight:600}
  /* The F&H group: F&H's own work, drawn in outline rather than in a fill, so the
     eye reads the filled squares above as the suites and these as something else. */
  .op-row-fh .op-av{background:var(--surface-3);border:1px solid var(--navy-line);
    color:var(--text-dim);box-shadow:none}
  .op-row-fh .op-av svg{width:15px;height:15px;stroke:currentColor;fill:none;stroke-width:1.7;
    stroke-linecap:round;stroke-linejoin:round}
  .op-row-fh .op-row-t{font-weight:600;font-size:13px;color:var(--text-dim)}
  .op-spine-empty{font-size:12px;line-height:1.5;color:var(--text-dim);padding:6px 11px 10px}

  /* THE STAGE: what you can do to whoever is selected. */
  .op-stage{min-width:0}
  .op-stagehead{background:var(--surface-2);border:1px solid var(--navy-line);border-radius:var(--radius);
    padding:18px 20px;margin-bottom:14px;box-shadow:0 12px 30px -28px rgba(0,0,0,.85)}
  .op-stagehead-r{display:flex;align-items:center;gap:14px;flex-wrap:wrap}
  /* Flat, for the reason written against .op-av above. */
  .op-av-lg{flex:0 0 auto;width:44px;height:44px;border-radius:13px;display:grid;place-items:center;
    font-family:'Archivo',sans-serif;font-weight:800;font-size:14px}
  .op-stagehead h2{font-family:'Archivo',sans-serif;font-weight:800;font-size:24px;letter-spacing:-.005em;
    margin:0;color:var(--text);min-width:0;overflow-wrap:anywhere}
  .op-state{font-family:'Archivo',sans-serif;font-weight:700;font-size:9.5px;letter-spacing:.16em;
    text-transform:uppercase;border-radius:6px;padding:5px 9px;flex:0 0 auto}
  .op-state-live{color:var(--ok-ink);background:var(--ok-wash);border:1px solid var(--ok-wash)}
  .op-state-part{color:var(--warn-ink);background:var(--warn-wash);border:1px solid var(--warn-wash)}
  .op-stagesub{font-family:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
    font-size:11.5px;line-height:1.6;color:var(--text-dim);margin:9px 0 0;padding-left:58px;
    overflow-wrap:anywhere}
  .op-stagesub b{color:var(--text);font-weight:500}

  .op-acts{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px}
  .op-act{display:flex;align-items:center;gap:13px;text-align:left;width:100%;min-height:64px;
    padding:13px 15px;border-radius:14px;background:linear-gradient(150deg,var(--action-1),var(--action-2) 55%,var(--action-3));
    border:1px solid var(--navy-line);cursor:pointer;position:relative;
    box-shadow:0 1px 2px rgba(0,0,0,.16),0 10px 22px -18px rgba(0,0,0,.7);
    transition:transform .15s,border-color .15s,box-shadow .15s}
  .op-act:hover{border-color:var(--purple-bright);transform:translateY(-2px);
    box-shadow:0 1px 2px rgba(0,0,0,.2),0 14px 26px -16px rgba(123,63,184,.55)}
  .op-act:active{transform:translateY(1px)}
  .op-act:focus-visible{outline:2px solid var(--purple-bright);outline-offset:2px}
  .op-act[disabled]{opacity:.45;cursor:not-allowed;transform:none}
  .op-act-ico{flex:0 0 auto;width:36px;height:36px;border-radius:11px;display:grid;place-items:center;
    background:linear-gradient(155deg,var(--purple-bright),var(--purple-dark));
    border:0;color:#fff;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 6px 14px -5px rgba(123,63,184,.55)}
  .op-act-ico svg{width:19px;height:19px}
  :root:not([data-theme="light"]) .op-act-ico{
    box-shadow:inset 0 1px 0 rgba(255,255,255,.35),inset 0 0 0 1px rgba(255,255,255,.14),0 6px 14px -6px rgba(0,0,0,.55)}
  .op-act-t{font-family:'Archivo',sans-serif;font-weight:700;font-size:14.5px;letter-spacing:.01em;
    color:var(--text);flex:1 1 auto;min-width:0}
  .op-act-go{flex:0 0 auto;color:var(--text-dim);display:grid;place-items:center;transition:.15s}
  .op-act-go svg{width:15px;height:15px}
  .op-act:hover .op-act-go{color:var(--purple-bright);transform:translateX(3px)}

  /* TODAY. Not a tile and never was: its whole content is its line, and it is hidden
     until it has something the stage does not already say. */
  .op-today{display:flex;align-items:center;gap:13px;text-align:left;width:100%;margin-top:11px;
    padding:13px 15px;border-radius:14px;cursor:default;
    background:linear-gradient(155deg,rgba(123,63,184,.16),rgba(123,63,184,.05));
    border:1px solid rgba(123,63,184,.35)}
  .op-today[hidden]{display:none}
  .op-today-ico{flex:0 0 auto;width:36px;height:36px;border-radius:11px;display:grid;place-items:center;
    background:linear-gradient(155deg,var(--purple-bright),var(--purple-dark));color:#fff;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.35),0 6px 14px -5px rgba(123,63,184,.55)}
  .op-today-ico svg{width:19px;height:19px}
  .op-today-txt{flex:1 1 auto;min-width:0}
  .op-today-h{display:block;font-family:'Archivo',sans-serif;font-weight:700;font-size:14.5px;
    letter-spacing:.01em;color:var(--text)}
  /* --text, not --text-dim, and this is a defect TD-21 inherited rather than wrote.
     The Today card has been a violet wash with a --text-dim line on it since TD-19, and
     the contrast sweep never saw it: the card is hidden unless it has something to say,
     and the sweep loaded the console with an empty roster, so it never had anything to
     say. Seeding a roster made it appear and it measured 3.18:1 in the light theme and
     4.41:1 in the dark, both under the 4.5 floor. A card that only appears when it has
     news is exactly the card whose text has to be readable. */
  .op-today-fact{display:block;font-family:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
    font-size:11px;line-height:1.45;color:var(--text);margin-top:3px;letter-spacing:.01em}
  .op-today-fact:empty{display:none}

  /* THE STAC SEAL. It never went anywhere: dashboard.css has painted the emblem
     behind this console the whole time, from the body watermark at the top of this
     file. TD-19 added a second, drawn one here and then took it out again: two
     watermarks on one screen is not a restored watermark, it is clutter. */

  /* ─── Assembly Line wizard (installer) ─────────────────────────────── */
  .modal.wizard{max-width:660px}
  .wiz-head{display:flex;align-items:center;justify-content:space-between;padding:18px 22px;border-bottom:1px solid var(--navy-line)}
  .wiz-title{display:flex;align-items:center;gap:12px}
  .wiz-title svg{width:22px;height:22px;color:var(--purple-bright)}
  .wiz-progress{display:flex;gap:5px;padding:15px 22px;border-bottom:1px solid var(--navy-line)}
  .wiz-pip{flex:1 1 auto;height:5px;border-radius:3px;background:var(--surface);transition:background .3s,box-shadow .3s}
  .wiz-pip.done{background:var(--purple)}
  .wiz-pip.active{background:linear-gradient(90deg,var(--purple),var(--purple-bright));box-shadow:0 0 10px -2px var(--purple-bright)}
  .wiz-body{padding:26px 24px;min-height:300px;max-height:56vh;overflow-y:auto}
  .wiz-station-title{font-family:'Archivo',sans-serif;font-weight:800;font-size:22px;color:var(--text);margin:0 0 7px;letter-spacing:.01em}
  .wiz-station-sub{font-size:13px;color:var(--text-dim);margin:0 0 22px;line-height:1.55}
  /* Modules start unselected, so give the operator a one-click way to take the lot. */
  .wiz-pickbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:-12px 0 4px}
  .wiz-pickcount{font-size:12px;font-weight:700;color:var(--text-dim);letter-spacing:.02em}
  /* A real min-height, not an invisible overlay: .wiz-pickbar wraps, so on a narrow
     phone two of these land about 20px apart and two 44px overlays would overlap,
     the lower one stealing the upper one's press. */
  .wiz-picklink{display:inline-flex;align-items:center;background:none;border:0;padding:4px 2px;min-height:44px;font:700 12px/1 inherit;color:var(--purple-bright);cursor:pointer;text-decoration:underline;text-underline-offset:3px}
  .wiz-picklink:hover{color:var(--text)}
  .wiz-field{margin-bottom:16px}
  .wiz-field label{display:block;font-family:'Archivo',sans-serif;font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--text-dim);margin-bottom:7px}
  /* Logo option checkboxes: sentence-case and wrapping. Must NOT inherit the uppercase,
     letter-spaced field-caption style above/below (that overflowed narrow panels on PC + mobile). */
  .wiz-field label.optchk,.field label.optchk{display:flex;align-items:flex-start;gap:8px;font-family:inherit;font-weight:500;font-size:12.5px;line-height:1.4;letter-spacing:normal;text-transform:none;color:var(--text);margin:10px 0 0;cursor:pointer;text-align:left}
  .wiz-field label.optchk>input,.field label.optchk>input{flex:none;width:16px;height:16px;min-width:16px;margin:2px 0 0;padding:0;accent-color:var(--purple-bright)}
  .wiz-field label.optchk>span,.field label.optchk>span{flex:1;min-width:0}
  .wiz-field input{width:100%;font-family:'Inter',sans-serif;font-size:14px;color:var(--text);background:var(--surface-3);border:1px solid var(--navy-line);border-radius:10px;padding:12px 14px}
  .wiz-field input:focus{outline:none;border-color:var(--purple-bright)}
  .wiz-field input.wiz-invalid{border-color:#e5484d;box-shadow:0 0 0 1px rgba(229,72,77,.35)}
  .wiz-req{color:#e5484d;font-weight:700}
  .wiz-reqnote{font-size:12px;color:var(--text-dim);margin:-4px 0 4px}
  .wiz-err{margin:0 24px;padding:11px 14px;border-radius:10px;background:rgba(229,72,77,.12);border:1px solid rgba(229,72,77,.55);color:#e5484d;font-size:13px;font-weight:600;display:flex;align-items:center;gap:8px}
  .wiz-err::before{content:"!";flex:none;width:18px;height:18px;border-radius:50%;background:#e5484d;color:#fff;font-size:12px;display:inline-flex;align-items:center;justify-content:center}
  .wiz-color-row{display:flex;gap:12px}
  .wiz-color-row .wiz-field{flex:1}
  .wiz-color-row input[type=color]{height:46px;padding:4px;cursor:pointer}
  .wiz-welcome{text-align:center;padding:24px 10px}
  .wiz-welcome img{width:92px;height:92px;margin:0 auto 22px;display:block;object-fit:contain;filter:drop-shadow(0 8px 18px rgba(30,27,75,.20))}
  .wiz-welcome h2{font-family:'Archivo',sans-serif;font-weight:800;font-size:24px;color:var(--text);margin:0 0 10px}
  .wiz-welcome p{font-size:13.5px;color:var(--text-dim);line-height:1.6;max-width:420px;margin:0 auto}
  .wiz-review-group{margin-bottom:18px}
  .wiz-review-group h4{font-family:'Archivo',sans-serif;font-size:11px;letter-spacing:.2em;text-transform:uppercase;color:var(--purple-bright);margin:0 0 8px}
  .wiz-review-item{display:flex;justify-content:space-between;gap:14px;padding:8px 0;border-bottom:1px solid var(--navy-line);font-size:13px}
  .wiz-review-item .lab{color:var(--text-dim)}
  .wiz-review-item .val{color:var(--text);font-weight:600;text-align:right}
  .wiz-installing{text-align:center;padding:46px 20px}
  .wiz-installing h2{font-family:'Archivo',sans-serif;font-weight:800;font-size:22px;color:var(--text);margin:0 0 6px}
  .wiz-installing p{font-size:13px;color:var(--text-dim);margin:0}
  .wiz-install-bar{height:8px;border-radius:5px;background:var(--surface);overflow:hidden;margin:26px auto;max-width:360px}
  .wiz-install-fill{height:100%;width:0;background:linear-gradient(90deg,var(--purple),var(--purple-bright));transition:width .25s;border-radius:5px}
  .wiz-foot{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:16px 22px;border-top:1px solid var(--navy-line)}
  .wiz-step-label{font-family:'Archivo',sans-serif;font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--text-dim)}
  .wiz-back,.wiz-next{font-family:'Archivo',sans-serif;font-weight:600;font-size:12px;letter-spacing:.08em;text-transform:uppercase;padding:11px 20px;border-radius:11px;cursor:pointer;transition:.2s}
  .wiz-back{background:var(--surface-3);border:1px solid var(--navy-line);color:var(--text)}
  .wiz-back:hover{border-color:var(--purple)}
  .wiz-back:disabled{opacity:.35;cursor:not-allowed}
  .wiz-next{background:linear-gradient(180deg,var(--purple),var(--purple-dark));color:var(--metal-hi);border:1px solid var(--purple-bright);box-shadow:0 0 14px -4px var(--purple-bright)}
  .wiz-next:hover{filter:brightness(1.12)}
  .wiz-next.deploy{background:linear-gradient(180deg,var(--live),#0d8f5e);border-color:var(--live);box-shadow:0 0 16px -4px var(--live);color:#fff}
  @media(max-width:780px){.op-perf{grid-template-columns:repeat(2,1fr)}}

  /* ─── THE PHONE (TD-21, September 4, 2026) ─────────────────────────
     "This is the screen Bryant opens in a truck." A left rail is a desktop shape, so
     below 820 the spine stops being a rail and becomes a client switcher across the
     top: the same rows, in a strip that scrolls sideways, with the stage taking the
     full width underneath. The selected suite keeps its accent, but as a rail along
     the BOTTOM of the chip rather than its left edge, because a 3px line down the
     left of a chip in a horizontal strip reads as a divider between two chips rather
     than as a state. dashboard.js sets that inline, from the same accent_color.
     The section headings go. Two words of chrome across the top of a 390px screen
     cost more than they say, and the order still carries the grouping: the F&H
     entries are the ones after the dashed Add chip.
     Not a follow-up. A console that only works at 1280 is half a console.
     ─────────────────────────────────────────────────────────────────────── */
  @media(max-width:820px){
    .op-shell{grid-template-columns:minmax(0,1fr);gap:12px}
    .op-spine{display:flex;gap:7px;overflow-x:auto;overflow-y:hidden;padding:9px;
      scrollbar-width:thin;-webkit-overflow-scrolling:touch}
    .op-spine-sec{display:none}
    .op-spine-empty{white-space:normal;min-width:220px;padding:6px 4px}
    .op-row{width:auto;flex:0 0 auto;margin-bottom:0;min-height:48px;padding:7px 12px;
      border-radius:12px;background:var(--surface-3)}
    .op-row-t{max-width:132px}
    .op-row-note{display:none}
    .op-acts{grid-template-columns:minmax(0,1fr)}
    .op-stagehead{padding:15px 16px}
    .op-stagehead h2{font-size:20px}
    .op-stagesub{padding-left:0;margin-top:11px}
  }

  /* ─── Mobile / tablet responsive layer ─────────────────────────────── */
  *{-webkit-tap-highlight-color:rgba(123,63,184,.16)}
  @media(max-width:820px){
    .wrap{padding:16px 13px 60px;max-width:100%;overflow-x:hidden}
    /* Header wraps. The client picker that used to take its own full-width row
       here was deleted by TD-21 with the rest of the picker. */
    header.bar{flex-wrap:wrap;gap:11px;padding:13px 14px}
    .titles{flex:1 1 auto;min-width:0}
    .wordmark{font-size:20px}
    .clock{display:none}
    .tools{flex:1 1 100%;flex-wrap:wrap;gap:9px;padding-top:12px;border-top:1px solid var(--navy-line)}
    .tools #btnOps{flex:1 1 100%;justify-content:center}
    .tools #btnSignOut{flex:1 1 0;justify-content:center}
    .tools #btnTheme{flex:0 0 auto}
    .navlink{height:46px;font-size:12px}
    .iconbtn{width:46px;height:46px}
    .iconbtn svg{width:20px;height:20px}
    /* Operator console stacks */
    .op-console{margin-top:20px}
    .op-greet{font-size:19px;margin-bottom:12px}
    /* Full-screen modals — standard mobile app pattern */
    /* 1 Aug 2026: was align-items:stretch, which pinned the modal to exactly 100vh in a
       fixed-height flex container. Anything taller spilled past its own background and
       sat on the overlay scrim — the two-tone panel that hid the build stamp. */
    .overlay{padding:0;align-items:flex-start}
    .modal{max-width:none;width:100%;min-height:100vh;border-radius:0;border-left:0;border-right:0}
    .modal-head{padding:15px 16px;position:sticky;top:0;z-index:3;background:var(--surface-2)}
    .modal-head h3{font-size:17px}
    .modal-head .x{width:40px;height:40px;font-size:20px}
    .modal-body{padding:17px}
    /* Wizard fills the screen; Back/Next stay reachable */
    .modal.wizard{display:flex;flex-direction:column;min-height:100vh}
    .wiz-head{position:sticky;top:0;z-index:3;background:var(--surface-2)}
    .wiz-body{max-height:none;flex:1 1 auto}
    .wiz-foot{position:sticky;bottom:0;background:var(--surface-3);box-shadow:0 -8px 20px -16px #000}
    .wiz-station-title{font-size:19px}
    .wiz-back,.wiz-next{padding:13px 18px}
    /* 16px form controls stop iOS auto-zoom on focus */
    .field input[type=text],.field textarea,.wiz-field input,input,select,textarea{font-size:16px}
    /* Inline-styled grids in module renders collapse (attribute selectors
       reach inline styles that media-query class rules cannot) */
    [style*="grid-template-columns:repeat(4"]{grid-template-columns:repeat(2,1fr)!important}
    [style*="grid-template-columns:repeat(3"]{grid-template-columns:repeat(2,1fr)!important}
    .statgrid{grid-template-columns:repeat(2,1fr)!important}
    img,svg,canvas,video{max-width:100%}
    table{display:block;max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}
  }
  @media(max-width:520px){
    .wordmark{font-size:18px;letter-spacing:.04em}
    .subtitle{font-size:9.5px;letter-spacing:.12em}
    .op-perf{grid-template-columns:repeat(2,1fr)}
    .statgrid{grid-template-columns:1fr!important}
    [style*="grid-template-columns:repeat(2"]{grid-template-columns:1fr!important}
    .wiz-color-row{flex-direction:column;gap:0}
    .cal{gap:3px}
    .modal-body{padding:14px}
  }

  /* ===== Light theme = STAC Field design system (matches the modules) ===== */
  :root[data-theme="light"] body{
    /* Light mode wash — brand-adaptive. Corner glows + a gentle bottom wash over a
       tinted base, all mixed from --purple so EVERY client suite's light mode tints
       to their OWN brand color instead of a fixed F&H lavender. The first (plain)
       declaration is the fallback for browsers without color-mix; the second wins
       where color-mix is supported. */
    background:
      linear-gradient(180deg,#F4F1FD 0%,#EAE4F8 54%,#DFD8F1 100%) fixed;
    background:
      radial-gradient(1100px 620px at 112% -12%, color-mix(in srgb, var(--purple) 26%, transparent) 0%, transparent 62%),
      radial-gradient(940px 560px at -14% 2%, color-mix(in srgb, var(--purple) 18%, transparent) 0%, transparent 60%),
      radial-gradient(1240px 820px at 50% 118%, color-mix(in srgb, var(--purple-mid, var(--purple)) 17%, transparent) 0%, transparent 62%),
      linear-gradient(180deg,
        color-mix(in srgb, var(--purple) 3%, #fff) 0%,
        color-mix(in srgb, var(--purple) 11%, #fff) 54%,
        color-mix(in srgb, var(--purple) 20%, #fff) 100%) fixed;
  }
  /* The emblem's dark disc is an asset ON a light page, so plain opacity is right
     there. screen would wash it out to nothing; overlay still lightens correctly
     on a light backdrop. Both revert here. */
  :root[data-theme="light"] body::before{opacity:.05;mix-blend-mode:normal}
  :root[data-theme="light"] body::after{mix-blend-mode:overlay}
  :root[data-theme="light"] .metal{
    background:none;-webkit-background-clip:initial;background-clip:initial;
    -webkit-text-fill-color:var(--text);color:var(--text);filter:none;
  }
  /* And a client surface never wears the sheen, in either theme. Same neutralizer as the
     light-theme one above, on the class that says whose screen this is. */
  body.role-client .metal,body.client-view .metal{
    background:none;-webkit-background-clip:initial;background-clip:initial;
    -webkit-text-fill-color:var(--text);color:var(--text);filter:none;
  }
  :root[data-theme="light"] header.bar,
  :root[data-theme="light"] .action,
  :root[data-theme="light"] .tile,
  :root[data-theme="light"] .op-card,
  :root[data-theme="light"] .op-act{
    box-shadow:0 1px 2px rgba(20,22,45,.05),0 4px 14px rgba(20,22,45,.05);
  }
  :root[data-theme="light"] .modal{ box-shadow:0 10px 34px rgba(20,22,45,.14); }
  #brandModal .modal{ background:var(--surface); }
  :root[data-theme="light"] .action,
  :root[data-theme="light"] .tile{ border:1px solid var(--navy-line); }
  :root[data-theme="light"] .action .ico,
  :root[data-theme="light"] .tile .ico{ color:var(--purple); filter:none; }

  /* Light mode: subtle brand-tinted fills so cards & buttons read gently colored
     instead of flat white. Derived from --purple, so each client suite tints to its
     OWN brand automatically (white-label friendly). Falls back to the plain white
     surface on browsers without color-mix. */
  :root[data-theme="light"] .op-act,
  :root[data-theme="light"] .op-card,
  :root[data-theme="light"] .action,
  :root[data-theme="light"] .tile,
  :root[data-theme="light"] .cd-tile{
    background:linear-gradient(155deg, color-mix(in srgb, var(--purple) 8%, #fff) 0%, color-mix(in srgb, var(--purple) 3%, #fff) 100%);
    border-color:color-mix(in srgb, var(--purple) 22%, var(--navy-line));
  }
  :root[data-theme="light"] .op-act:hover,
  :root[data-theme="light"] .tile:hover,
  :root[data-theme="light"] .cd-tile:hover{
    background:linear-gradient(155deg, color-mix(in srgb, var(--purple) 13%, #fff) 0%, color-mix(in srgb, var(--purple) 5%, #fff) 100%);
  }
  :root[data-theme="light"] .btn:not(.primary){
    background:color-mix(in srgb, var(--purple) 6%, #fff);
    border-color:color-mix(in srgb, var(--purple) 20%, var(--navy-line));
  }
  :root[data-theme="light"] .sb-toggle::after{ background:linear-gradient(180deg,#fff,#E6E8F1); box-shadow:0 1px 2px rgba(20,22,45,.18); }
  :root[data-theme="light"] .sb-toggle.on::after{ background:#fff; }

  /* ─── Suite Assembly + Client Dashboard (full-screen pages) ───────────── */
  /* Client list hidden for now — focused on the assembly → dashboard flow. */
  #clientPick{display:none!important}
  /* THE FOUR STAT CARDS ARE STILL HIDDEN, AND THAT IS NOT A DECISION TD-21 MADE.
     This line arrived under "Client list hidden for now - focused on the assembly ->
     dashboard flow" and it hid three things: both client pickers and these four
     cards. The pickers are deleted. The cards are Compliance Health, At-Risk Items,
     Open Actions and Active Modules, all four of them facts about the ACTIVE client,
     so TD-21 moved #opPerf onto the stage, which is the surface they describe, and
     left them switched off. Switching them on is a product change nobody asked for
     and it would need its own contrast pass over four new surfaces. It is written up
     in the TD-21 status file as a thing for Bryant to decide. */
  .op-perf{display:none!important}

  .pageview{position:fixed;top:0;left:0;right:0;bottom:0;width:100%;height:100%;z-index:200;display:none;overflow-y:auto;
    background-color:#0b0d1c;
    background-image:radial-gradient(120% 80% at 50% -10%, var(--bg-glow,rgba(108,61,214,.28)) 0%, var(--navy-2,#15172e) 28%, var(--navy-1,#0f1020) 60%, var(--navy-0,#0a0a14) 100%)}
  #cdView{background-image:var(--suite-bg, radial-gradient(120% 80% at 50% -10%, var(--bg-glow,rgba(108,61,214,.28)) 0%, var(--navy-2,#15172e) 28%, var(--navy-1,#0f1020) 60%, var(--navy-0,#0a0a14) 100%))}
  .pageview.show{display:block!important}
  .pv-bar{position:sticky;top:0;z-index:5;display:flex;align-items:center;justify-content:space-between;gap:14px;
    padding:15px 26px;background:var(--bar-1);backdrop-filter:blur(12px);border-bottom:1px solid var(--navy-line)}
  .pv-bar-l{display:flex;align-items:center;gap:16px;min-width:0}
  /* iOS notch / home-indicator handling (only adds space on notched devices; 0 elsewhere). */
  @supports (padding: max(0px)){
    .pv-bar{ padding-top: max(15px, env(safe-area-inset-top)); }
    #cdView{ padding-top: env(safe-area-inset-top); }
    .pageview{ padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); }
  }
  .pv-back{display:inline-flex;align-items:center;gap:7px;font-family:'Archivo',sans-serif;font-weight:600;font-size:12px;letter-spacing:.05em;
    padding:9px 14px;border-radius:10px;border:1px solid var(--navy-line);background:var(--surface-3);color:var(--text);cursor:pointer;transition:.15s;white-space:nowrap}
  .pv-back:hover{border-color:var(--purple);color:var(--purple-bright)}
  /* Discreet in-app navigation (operator): back / forward (when available) / contextual close */
  .fhnav{display:none;align-items:center;gap:2px;padding:3px;border-radius:999px;background:var(--ctl-bg);border:1px solid var(--navy-line);flex:0 0 auto}
  .fhnav.show{display:inline-flex}
  .fhnav button{appearance:none;-webkit-appearance:none;background:none;border:0;cursor:pointer;color:var(--text-dim);height:28px;width:28px;padding:0;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:background .15s,color .15s,opacity .15s}
  .fhnav .fhnav-close{width:auto;padding:0 12px 0 9px;font:700 10px/1 'Archivo',sans-serif;letter-spacing:.07em;text-transform:uppercase}
  .fhnav button:hover:not(:disabled){background:var(--ctl-line);color:var(--text)}
  .fhnav button:disabled{opacity:.32;cursor:default}
  .fhnav .fh-hide{display:none!important}
  .fhnav svg{width:16px;height:16px;stroke:currentColor;fill:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}
  /* Website Engagement panel */
  #engagementModal .modal{max-width:920px}
  .eng-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px;flex-wrap:wrap}
  .eng-sub{font-size:12px;color:var(--text-dim);letter-spacing:.02em}
  .eng-range{display:inline-flex;gap:4px;background:var(--surface-3);border:1px solid var(--navy-line);border-radius:999px;padding:3px}
  .eng-rbtn{border:0;background:none;color:var(--text-dim);font:700 11px/1 'Archivo',sans-serif;letter-spacing:.05em;padding:7px 13px;border-radius:999px;cursor:pointer;transition:.15s}
  .eng-rbtn.is-on{background:linear-gradient(180deg,var(--purple),var(--purple-dark));color:var(--metal-hi)}
  .eng-kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:6px}
  .eng-kpi{background:var(--surface-3);border:1px solid var(--navy-line);border-radius:14px;padding:14px 16px}
  .eng-kpi-v{font-family:'Archivo',sans-serif;font-weight:800;font-size:25px;color:var(--metal-1);line-height:1}
  .eng-kpi-l{font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--text-dim);margin-top:7px;font-weight:700}
  .eng-kpi-s{font-size:11px;margin-top:6px;color:var(--text-dim)}
  .eng-trend{font-weight:700}
  .eng-trend.up{color:var(--live)} .eng-trend.down{color:#e0785f}
  .eng-h{font:700 11px/1 'Archivo',sans-serif;letter-spacing:.1em;text-transform:uppercase;color:var(--text-dim);margin:20px 0 10px}
  .eng-chart{display:flex;align-items:flex-end;gap:2px;height:130px;padding:8px 0}
  .eng-bar{flex:1 1 0;min-width:0;background:linear-gradient(180deg,var(--purple-bright),var(--purple));border-radius:3px 3px 0 0;min-height:2px;opacity:.85;transition:opacity .15s}
  .eng-bar:hover{opacity:1}
  .eng-axis{display:flex;justify-content:space-between;font-size:10.5px;color:var(--text-dim);margin-top:6px;border-top:1px solid var(--navy-line);padding-top:6px}
  .eng-cols{display:grid;grid-template-columns:1fr 1fr;gap:24px}
  @media(max-width:640px){ .eng-kpis{grid-template-columns:1fr 1fr} .eng-cols{grid-template-columns:1fr} }
  .eng-row{display:flex;align-items:center;gap:10px;margin:9px 0;font-size:12.5px}
  .eng-row .nm{width:100px;flex:none;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .eng-track{flex:1;height:8px;background:var(--surface-3);border-radius:999px;overflow:hidden}
  .eng-fill{height:100%;background:linear-gradient(90deg,var(--purple),var(--purple-bright));border-radius:999px}
  .eng-row .ct{width:44px;flex:none;text-align:right;color:var(--text-dim);font-variant-numeric:tabular-nums}
  #clockT,#clockD{font-variant-numeric:tabular-nums}   /* fixed-width digits so the header clock doesn't shift as time ticks */
  .eng-empty{padding:38px 20px;text-align:center;color:var(--text-dim);font-size:13.5px;line-height:1.7}
  .pv-title{font-family:'Archivo',sans-serif;font-weight:800;font-size:18px;color:var(--text);letter-spacing:.01em;line-height:1.2}
  .pv-sub{font-size:11.5px;color:var(--text-dim);line-height:1.4;margin-top:2px}
  .pv-wrap{max-width:1180px;margin:0 auto;padding:28px 26px 90px}
  .pv-publish{font-family:'Archivo',sans-serif;font-weight:700;font-size:12px;letter-spacing:.07em;text-transform:uppercase;
    padding:12px 22px;border-radius:11px;cursor:pointer;transition:.2s;white-space:nowrap;
    background:linear-gradient(180deg,var(--live),#0d8f5e);border:1px solid var(--live);color:#fff;box-shadow:0 0 16px -4px var(--live)}
  .pv-publish:hover{filter:brightness(1.1)}
  .pv-publish:disabled{background:var(--surface-3);border-color:var(--navy-line);color:var(--text-dim);box-shadow:none;cursor:not-allowed;filter:none}

  /* ---- one collapsible group header, used by three screens ----------------------
     The wizard's Modules station, the Suite Assembly page and the Module Manager all
     draw the same eleven module types. This is that header, declared once (31 Jul
     2026, open item b). The wizard carried it as inline styles until then; three
     copies of a style is three chances for one screen to quietly stop matching.
     grpBtn() in dashboard.js writes this markup - change them together. */
  .grp-btn{width:100%;text-align:left;display:flex;flex-direction:column;gap:3px;padding:14px 16px;border-radius:12px;cursor:pointer;font:inherit;background:var(--surface-3);border:1.5px solid var(--navy-line)}
  .grp-btn.has{border-color:var(--purple-bright);box-shadow:0 0 0 3px rgba(124,58,237,.15)}
  .grp-btn:hover{border-color:var(--purple-mid,var(--purple-bright))}
  .grp-top{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:14px;font-weight:700;color:var(--text)}
  .grp-meta{display:flex;align-items:center;gap:8px;flex:none}
  .grp-count{font-size:11px;font-weight:700;padding:3px 9px;border-radius:99px;color:var(--text-dim);background:var(--surface-2)}
  .grp-count.on{color:#fff;background:var(--purple-bright)}
  .grp-caret{color:var(--text-dim);font-size:11px}
  .grp-sub{font-size:12px;color:var(--text-dim);line-height:1.4}
  .grp-note{padding:14px 2px;font-size:12.5px;color:var(--text-dim)}

  /* Collapsed groups sit close together; the gap used to be 30px because each area
     was a whole open section. */
  .as-areas{display:flex;flex-direction:column;gap:10px}
  .as-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:15px;margin-top:12px}
  /* Tie the open cards to the heading they belong to. Without it a single card sits a
     third of the way across an otherwise empty row and reads as unrelated. */
  .as-area.open .as-grid{margin-left:6px;padding-left:14px;border-left:2px solid var(--ctl-line,rgba(144,90,207,.4))}
  .as-card{position:relative;border:1px solid var(--navy-line);border-radius:15px;background:var(--surface);padding:18px;cursor:pointer;
    transition:border-color .15s,box-shadow .15s,transform .12s;display:flex;flex-direction:column;gap:11px;min-height:172px}
  .as-card:hover{border-color:var(--purple);box-shadow:0 10px 28px -16px var(--purple-bright);transform:translateY(-2px)}
  .as-card.on{border-color:var(--purple-bright);box-shadow:0 0 0 1px var(--purple-bright),0 10px 28px -16px var(--purple-bright)}
  .as-incl-tag{position:absolute;top:14px;right:15px;font-family:'Archivo',sans-serif;font-weight:700;font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--live);display:none}
  .as-card.on .as-incl-tag{display:block}
  .as-card-top{display:flex;align-items:flex-start;gap:12px;padding-right:54px}
  .as-cico{flex:none;width:40px;height:40px;display:grid;place-items:center;border-radius:11px;background:linear-gradient(135deg,var(--purple),var(--purple-mid,var(--purple-bright)));color:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.28)}
  .as-cico svg{width:22px;height:22px}
  .as-cname{font-family:'Archivo',sans-serif;font-weight:700;font-size:14.5px;color:var(--text);line-height:1.25}
  .as-cdesc{font-size:12px;color:var(--text-dim);line-height:1.5;flex:1}
  .as-card-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:auto;padding-top:11px;border-top:1px solid var(--navy-line)}
  .as-incl{display:inline-flex;align-items:center;gap:9px;font-family:'Archivo',sans-serif;font-size:11px;letter-spacing:.05em;color:var(--text-dim)}
  .as-config{font-family:'Archivo',sans-serif;font-weight:600;font-size:11px;letter-spacing:.04em;color:var(--purple-bright);background:none;border:0;cursor:pointer;padding:5px 2px}
  .as-config:hover{text-decoration:underline}

  .as-switch{width:40px;height:22px;border-radius:11px;background:var(--surface-3);border:1px solid var(--navy-line);position:relative;transition:.2s;flex:none;cursor:pointer}
  .as-switch::after{content:"";position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--metal-3);transition:.2s}
  .as-switch.on{background:linear-gradient(90deg,var(--purple),var(--purple-bright));border-color:var(--purple-bright)}
  .as-switch.on::after{left:20px;background:#fff}

  .as-drawer-scrim{position:fixed;top:0;left:0;right:0;bottom:0;width:100%;height:100%;z-index:210;background:var(--scrim,rgba(6,8,20,.6));display:none}
  .as-drawer-scrim.show{display:block}
  .as-drawer{position:fixed;top:0;right:0;bottom:0;width:430px;max-width:94vw;z-index:220;background:var(--surface);
    border-left:1px solid var(--navy-line);transform:translateX(100%);transition:transform .25s;display:flex;flex-direction:column;box-shadow:-20px 0 50px -24px rgba(0,0,0,.5)}
  .as-drawer.show{transform:none}
  .as-dr-head{display:flex;align-items:center;gap:12px;padding:18px 20px;border-bottom:1px solid var(--navy-line)}
  .as-dr-body{flex:1;overflow-y:auto;padding:20px}
  .as-dr-section{font-family:'Archivo',sans-serif;font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--text-dim);margin:22px 0 12px}
  .as-fld{margin-bottom:15px}
  .as-fld label{display:block;font-family:'Archivo',sans-serif;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--text-dim);margin-bottom:7px}
  .as-fld input,.as-fld textarea{width:100%;font-family:'Inter',sans-serif;font-size:16px;color:var(--text);background:var(--surface-3);border:1px solid var(--navy-line);border-radius:9px;padding:10px 12px;resize:vertical}
  .as-fld input:focus,.as-fld textarea:focus{outline:none;border-color:var(--purple-bright)}
  .as-toggle-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:13px 0;border-bottom:1px solid var(--navy-line);font-size:13px;color:var(--text)}
  .as-preview-btn{width:100%;font-family:'Archivo',sans-serif;font-weight:600;font-size:12.5px;letter-spacing:.05em;padding:12px;border-radius:10px;cursor:pointer;transition:.15s;
    background:var(--surface-3);border:1px solid var(--ctl-line);color:var(--purple-bright)}
  .as-preview-btn:hover{border-color:var(--purple-bright);background:var(--ctl-bg)}
  .as-dr-foot{padding:16px 20px;border-top:1px solid var(--navy-line)}
  .as-include-btn{width:100%;font-family:'Archivo',sans-serif;font-weight:700;font-size:12px;letter-spacing:.07em;text-transform:uppercase;padding:13px;border-radius:11px;cursor:pointer;transition:.2s}
  .as-include-btn.add{background:linear-gradient(180deg,var(--purple),var(--purple-dark));border:1px solid var(--purple-bright);color:var(--metal-hi);box-shadow:0 0 14px -4px var(--purple-bright)}
  .as-include-btn.rm{background:var(--surface-3);border:1px solid var(--navy-line);color:var(--text)}
  .as-include-btn:hover{filter:brightness(1.08)}

  /* Client-facing dashboard */
  .cd-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:15px;margin-bottom:22px}
  .cd-tile{text-align:left;border:1px solid var(--navy-line);border-radius:15px;background:var(--surface);padding:18px;cursor:pointer;
    transition:border-color .15s,box-shadow .15s,transform .12s;display:flex;flex-direction:column;gap:10px;min-height:160px;font:inherit}
  .cd-tile:hover{border-color:var(--purple);box-shadow:0 10px 28px -16px var(--purple-bright);transform:translateY(-2px)}
  .cd-name{font-family:'Archivo',sans-serif;font-weight:700;font-size:15px;color:var(--text);line-height:1.25}
  .cd-desc{font-size:12px;color:var(--text-dim);line-height:1.5;flex:1}
  .cd-usage{display:flex;align-items:center;justify-content:space-between;margin-top:auto;padding-top:11px;border-top:1px solid var(--navy-line);font-family:'Archivo',sans-serif;font-size:12px;color:var(--text-dim)}
  /* 'Open' link: black on the light dashboard, but a light on-brand accent in dark
     mode so it doesn't vanish. Mixed from --purple-bright and forced light, with
     a lavender fallback for browsers without color-mix. */
  .cd-go{color:#E7E0F5;color:color-mix(in srgb, var(--purple-bright) 48%, #fff);font-weight:700}
  :root[data-theme="light"] .cd-go{color:#000}
  .cd-empty{border:1px dashed var(--navy-line);border-radius:15px;padding:40px;text-align:center;color:var(--text-dim);font-size:13px;margin-bottom:22px}
  .cd-exec{width:100%;text-align:left;display:flex;align-items:center;justify-content:space-between;gap:16px;font:inherit;cursor:pointer;
    border:1px solid var(--ctl-line);border-radius:16px;padding:22px 24px;transition:.15s;
    background:linear-gradient(120deg, var(--action-2), var(--action-1))}
  .cd-exec:hover{border-color:var(--purple-bright);box-shadow:0 12px 32px -18px var(--purple-bright)}
  .cd-exec-l{display:flex;align-items:center;gap:16px}
  .cd-exec-ico{flex:none;width:48px;height:48px;display:grid;place-items:center;border-radius:13px;background:linear-gradient(135deg,var(--purple),var(--purple-mid,var(--purple-bright)));color:#fff;box-shadow:inset 0 1px 0 rgba(255,255,255,.28)}
  .cd-exec-ico svg{width:26px;height:26px}
  .cd-exec-h{font-family:'Archivo',sans-serif;font-weight:800;font-size:17px;color:var(--text)}
  .cd-exec-s{font-size:12.5px;color:var(--text-dim);line-height:1.5;margin-top:3px;max-width:560px}
  /* client dashboard brand header + responsive logo slot (auto-fits any logo shape) */
  .cd-brandbar{display:flex;align-items:center;gap:18px;padding:16px 20px;margin-bottom:22px;border:1px solid var(--navy-line);border-radius:16px;background:var(--surface)}
  /* THE CLIENT'S MARK KEEPS ITS OWN BACKING IN BOTH THEMES. 30 Aug 2026, at Bryant's
     instruction. This tile used to follow the theme, so the same logo sat on a pale panel in
     light and a dark one at night, and a mark drawn on its own light ground looked wrong in
     one of them however the tile was tuned. A fixed warm off-white ends that: the logo renders
     identically in both modes and there is nothing left to tune per theme. */
  .cd-logoslot{flex:0 0 auto;display:flex;align-items:center;justify-content:center;min-width:56px;max-width:300px;height:66px;padding:6px 12px;border-radius:13px;background:#f7f5f2}
  .cd-logoslot img{max-height:54px;max-width:276px;width:auto;height:auto;object-fit:contain;display:block}
  .cd-logoslot.empty{display:none}
  .cd-brandname{font-family:'Archivo',sans-serif;font-weight:800;font-size:20px;color:var(--text);line-height:1.1}
  .cd-brandtag{font-size:12px;letter-spacing:.04em;color:var(--text-dim);margin-top:3px}
  /* Added 27 Aug 2026. The action group carried its layout as inline style, which no media
     query can override, and .cd-brandbar had NO responsive rule anywhere in this file. At
     360px, a Samsung S25's CSS width, that left #cdSignOut 99px past the right edge with 0px
     of it on screen: a client's only way to end a session was unreachable on a phone.
     Measured, not guessed. verify_signout_onscreen.js pins it at 360 and 390. */
  .cd-brandactions{margin-left:auto;display:flex;align-items:center;gap:12px;flex:0 0 auto}
  .cd-brandtext{flex:1 1 auto;min-width:0}
  /* assembly-line client branding bar */
  .as-brandbar{display:flex;align-items:center;gap:16px;flex-wrap:wrap;padding:15px 16px;margin-bottom:20px;border:1px dashed var(--navy-line);border-radius:14px;background:var(--surface)}
  .as-logodrop{flex:1 1 260px;display:flex;align-items:center;gap:13px;min-height:56px;padding:10px 14px;border:1px solid var(--navy-line);border-radius:12px;background:var(--ctl-bg,rgba(144,90,207,.05));cursor:pointer;transition:border-color .2s}
  .as-logodrop.hot{border-color:var(--purple-bright)}
  .as-logodrop img{max-height:46px;max-width:180px;object-fit:contain}
  .as-logohint{font-size:12.5px;color:var(--text-dim)}
  .as-swatches{display:flex;gap:8px}
  .as-swatch{width:30px;height:30px;border-radius:8px;border:1px solid rgba(255,255,255,.18)}
  /* send-to-client: share button, modal rows, and client-view mode */
  .cd-bar-actions{display:flex;gap:10px;align-items:center}
  .cd-share{display:inline-flex;align-items:center;gap:7px;font-family:'Archivo',sans-serif;font-weight:700;font-size:12px;letter-spacing:.05em;padding:9px 16px;border-radius:10px;border:1px solid var(--purple-bright);background:linear-gradient(180deg,var(--purple),var(--purple-mid));color:#fff;cursor:pointer;transition:.2s}
  .cd-share:hover{filter:brightness(1.08)}
  .cd-share:disabled{opacity:.6;cursor:default}
  .share-link-row{display:flex;gap:8px;align-items:stretch}
  .share-link-row input{flex:1}
  .share-actions{display:flex;gap:10px;margin-top:18px}
  .share-actions .btn{flex:1}
  body.client-view #cdBack, body.client-view #cdHome, body.client-view #cdShare, body.client-view #cdReset{display:none!important}
  body.client-view #cdView .pv-bar{display:none!important}
  /* End-client experience (signed-in client): hide the operator-oriented header
     title/subtitle in the client dashboard bar. Keep the bar itself (Sign out).
     Operator preview is unaffected. */
  body.role-client #cdView .pv-title, body.role-client #cdView .pv-sub{display:none!important}

  /* ─── role-based chrome: a signed-in client_user sees ONLY their dashboard ─── */
  /* Hide every builder-only control so a client can never reach operator tools. */
  body.role-client .op-console,
  body.role-client #cdBack,
  body.role-client #cdHome,
  body.role-client #cdReset,
  body.role-client #cdShare{display:none!important}
  /* This comment used to say "keep #cdView .pv-bar visible, it holds the client's Sign out
     button". It was never true: body.client-view hides that bar whole, two rules up, with
     !important, and both classes are set together on a signed-in client. So the button was in
     the DOM and never on screen, and the comment is what made that look intentional for weeks.
     Sign out now lives in the brand bar beside the signed-in name, which no client rule hides,
     and verify_client_signout.js is what keeps it there. */
  /* The builder shell (header + console) is hidden for clients; only #cdView shows. */
  body.role-client > .wrap{display:none!important}
  /* Sign-out chip in the header (builders) + inside the client dashboard bar. */
  .signout-btn{display:inline-flex;align-items:center;gap:6px;font-family:'Archivo',sans-serif;font-weight:600;font-size:12px;letter-spacing:.04em;
    padding:9px 14px;border-radius:10px;border:1px solid var(--navy-line);background:var(--surface-3);color:var(--text);cursor:pointer;transition:.15s;white-space:nowrap}
  .signout-btn:hover{border-color:var(--purple);color:var(--purple-bright)}

  /* ─── full-screen auth login overlay (Supabase Auth gate) ─── */
  #authGate{position:fixed;inset:0;z-index:4000;display:none;align-items:center;justify-content:center;padding:24px;
    background-color:#0a0a14;
    background-image:radial-gradient(120% 80% at 50% -10%, rgba(108,61,214,.30) 0%, #15172e 28%, #0f1020 60%, #0a0a14 100%)}
  #authGate.show{display:flex}
  .auth-card{width:100%;max-width:380px;background:var(--surface);border:1px solid var(--navy-line);border-radius:18px;padding:32px 28px;text-align:center;box-shadow:0 24px 60px -20px rgba(0,0,0,.7)}
  .auth-card .auth-logo{width:58px;height:58px;object-fit:contain;border-radius:14px;margin:0 auto 16px;display:block}
  .auth-card h2{font-family:'Archivo',sans-serif;font-weight:800;font-size:21px;color:var(--text);margin:0 0 4px}
  .auth-card .auth-sub{font-size:13px;color:var(--text-dim);margin:0 0 20px}
  .auth-card input{width:100%;box-sizing:border-box;background:var(--ctl-bg,#11183a);border:1px solid var(--navy-line);border-radius:10px;padding:12px 13px;color:var(--text);font:inherit;font-size:14px;margin-bottom:11px}
  .auth-card input:focus{outline:none;border-color:var(--purple-bright)}
  #authErr{display:none;color:var(--danger,#ff5d76);font-size:13px;margin-bottom:11px;text-align:left}
  #authGo{width:100%;margin-top:4px}
  .auth-card .auth-alt{margin-top:14px;font-size:13px}
  .auth-card .auth-alt a{color:var(--purple-bright,#b79ad8);text-decoration:none;cursor:pointer}
  .auth-card .auth-alt a:hover{text-decoration:underline}
  .auth-card .auth-hint{margin-top:12px;font-size:11.5px;line-height:1.5;color:var(--text-dim)}
  .auth-card .auth-msg{display:none;font-size:13px;margin-bottom:11px;text-align:left;line-height:1.45}
  .auth-card .auth-msg.ok{color:var(--ok,#46d39a);display:block}
  .auth-card .auth-msg.bad{color:var(--danger,#ff5d76);display:block}
  .auth-card .auth-req{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--text-dim);margin:2px 0 13px;text-align:left}
  .auth-card .auth-req .dot{width:14px;height:14px;border-radius:50%;border:1.5px solid var(--text-dim);flex:0 0 auto;position:relative;transition:.15s}
  .auth-card .auth-req.ok{color:var(--ok,#46d39a)}
  .auth-card .auth-req.ok .dot{background:var(--ok,#46d39a);border-color:var(--ok,#46d39a)}
  .auth-card .auth-req.ok .dot:after{content:"";position:absolute;left:4px;top:1px;width:4px;height:7px;border:solid #fff;border-width:0 2px 2px 0;transform:rotate(45deg)}

  @media(max-width:900px){
    .as-grid,.cd-grid{grid-template-columns:repeat(2,1fr)}
    .pv-wrap{padding:22px 16px 80px}
    .pv-bar{padding:13px 16px}
  }
  @media(max-width:560px){
    .as-grid,.cd-grid{grid-template-columns:1fr}
    .pv-title{font-size:16px}
    .cd-exec{flex-direction:column;align-items:flex-start;gap:12px}
    /* the brand bar wraps rather than pushing its controls off the right edge */
    .cd-brandbar{flex-wrap:wrap;gap:12px;padding:14px 16px}
    .cd-brandtext{flex:1 1 140px}
    .cd-brandactions{margin-left:0;width:100%;justify-content:flex-end;border-top:1px solid var(--navy-line);padding-top:12px}
  }

  /* ---- portrait ----
     .as-grip, .as-dragclone and .as-area.as-drop were removed 31 Jul 2026 with
     drag-to-reclassify. A module's type now comes from its own row, so a per-browser
     drag would have made this console disagree with everything else. */
  .as-card-top{align-items:center}
  @media(max-width:900px){ .as-grid{grid-template-columns:repeat(2,1fr)} }
  @media(max-width:640px){
    .as-grid{grid-template-columns:1fr}
    /* 20px of indent is a lot of a 360px screen; keep the accent, drop most of the gap */
    .as-area.open .as-grid{margin-left:0;padding-left:9px}
    .as-drawer{width:100vw;max-width:100vw}
    /* stack the page header so buttons never overlap the title on phones */
    .pv-bar{flex-direction:column;align-items:stretch;gap:10px}
    .pv-bar-l{flex-direction:column;align-items:flex-start;gap:8px;min-width:0}
    .pv-bar-l>div{min-width:0}
    .pv-sub{white-space:normal}
    .cd-bar-actions{width:100%;flex-wrap:wrap}
    .cd-bar-actions .cd-share,.cd-bar-actions .pv-back{flex:1 1 auto;justify-content:center}
    .swatches{gap:10px}
    .as-swatch{width:34px;height:34px}
  }

  /* ===== Mobile finishing — portrait + landscape, no side-scroll ===== */
  img{max-width:100%}
  @media(max-width:820px){
    /* .op-bar-pick was the fourth selector here and went with the picker. */
    .rowbtns,.cd-bar-actions,.tabs{flex-wrap:wrap}
  }
  @media (orientation:landscape) and (max-height:600px){
    .wrap{padding-top:12px;padding-bottom:46px}
    header.bar{padding:10px 16px}
    .op-console{margin-top:14px}
    .op-perf{margin-bottom:16px}
    .pv-wrap{padding-top:14px;padding-bottom:44px}
  }

  /* ------------------------------------------------------------------ *
   * iOS auto-zoom guard.  Added 5 Aug 2026 after tests/verify_taps.js.  *
   * ------------------------------------------------------------------ *
     Safari on iPhone zooms the whole page in when you tap a text field whose
     font-size is under 16px, and it does not zoom back out. On the sign-in card
     that means the first tap on the app throws the layout sideways. It is 16px
     exactly, not "about 16": 15.9px zooms.

     Only phones are touched. The query needs BOTH a narrow screen and a coarse
     pointer, so a 1200px desktop window keeps the 14px controls it has always
     had and nothing above this line changes visually. A desktop browser
     narrowed to 600px also keeps 14px, because a mouse is not coarse.

     16px is a floor, not a size: the rules above stay as written, and these
     only raise the ones that fall under it. */
  @media (max-width:820px) and (pointer:coarse){
    .auth-card input,
    .field input[type=text], .field input[type=email], .field input[type=password],
    .field input[type=number], .field input[type=tel], .field input[type=date],
    .field textarea, .field select,
    .wiz-field input, .wiz-field textarea, .wiz-field select,
    .chat-input input{ font-size:16px }
  }

  /* The New Suite Setup logo beat. Replaced the Welcome station on 5 Aug 2026: the
     station explained the wizard to the one person who already knows it and charged a
     keypress for it. The splash keeps the "something is starting" beat and costs nothing.
     prefers-reduced-motion gets the mark with no movement, not no mark. */
  @keyframes fhSplashIn{ from{ opacity:0; transform:scale(.88) } to{ opacity:1; transform:none } }
  @media (prefers-reduced-motion:reduce){
    .wiz-splash img{ animation:none !important }
    .wiz-splash{ transition:none !important }
  }

/* Build stamp, bottom right. Sits BELOW the update chip (z 2147483001) and the offline
   badge (bottom left) so it can never cover either: those are things to act on, this is
   a thing to read when somebody asks "what have you got?". pointer-events:none so it can
   never take a tap meant for the app underneath it. */
#fhBuildChip{
  position:fixed; right:8px;
  bottom:calc(env(safe-area-inset-bottom, 0px) + 3px);
  z-index:2147482000; pointer-events:none; user-select:none;
  font:500 9.5px/1 "Archivo",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  letter-spacing:.06em; color:var(--text-dim,#8a93a6); opacity:.45;
  padding:2px 5px; border-radius:4px; background:transparent;
}
#fhBuildChip:empty{ display:none; }
@media print{ #fhBuildChip{ display:none; } }
