/* dashboard.css — the dashboard rebuild's component kit (plan §3.2, appendix-
   visual-language §2/§5/§6). Loaded AFTER styles.css and BEFORE the app.html
   inline <style> block, so legacy inline rules keep final say while views
   migrate here one at a time. Everything in this file reads design tokens —
   zero hex literals outside the hex-allowlist block at the bottom.

   Spacing scale (literal values per house style, no spacing tokens):
   4 (chip innards) · 6 · 8 · 10 · 12 (grid gaps) · 14 · 18 · 22 · 26.
   Radii: var(--radius) 10 / var(--radius-lg) 16 / var(--radius-pill).

   Type scale: 26/700 stat number · 22/700 view header · 17/600 card heading
   (serif where editorial) · 15 inputs · 14/600 component titles · 13.5-13 body
   · 12.5-12 secondary · 11.5-11/600 uppercase micro-labels (.06em) · 10.5
   timestamps. tabular-nums on every numeric. */

/* ── 2.1 Stat tile + stat row ──────────────────────────────────────────────
   One component, two densities. Markup:
   div.stat-tile[tabindex=-1] > span.st-l + div.st-v > span.st-n
     + span.st-badge(.up|.down) + svg.st-spark
   Loading = .skel.skel-tile placeholder; empty value = "—"; never holo. */
.stat-tile{background:var(--bg-surface);border:none;border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:14px 16px;}
.stat-tile .st-l{display:block;font-size:11px;text-transform:uppercase;
  letter-spacing:.06em;color:var(--text-secondary);font-weight:600;}
.stat-tile .st-v{display:flex;align-items:center;gap:8px;margin-top:2px;min-width:0;}
.stat-tile .st-n{font-size:26px;font-weight:700;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;color:var(--text-primary);}
.st-badge{display:inline-flex;align-items:center;font-size:12px;font-weight:600;
  padding:3px 10px;border-radius:var(--radius-pill);letter-spacing:-.01em;
  font-variant-numeric:tabular-nums;background:rgba(120,120,128,.14);color:var(--text-secondary);}
.st-badge.up{background:var(--success-soft);color:var(--success);}
.st-badge.down{background:var(--danger-soft);color:var(--danger);}
/* Sparklines draw in currentColor; the tile keeps them quiet. */
.stat-tile .st-spark{color:var(--text-tertiary);flex:none;margin-left:auto;}

/* Compact strip directly under a .view-header; chips can act as
   click-to-scroll anchors. */
.stat-row{display:flex;gap:10px;overflow-x:auto;padding:14px 24px 0;align-items:stretch;}
.stat-row .stat-tile{flex:none;}
.stat-tile.compact{padding:8px 12px;}
.stat-tile.compact .st-n{font-size:17px;}
.stat-tile.compact .st-spark{display:none;}

/* ── 2.2 Chart shell — generic twin of .lead-graph (which stays put for the
   three bespoke renderers). New panels use these classes so they don't ship
   lead- semantics. ─────────────────────────────────────────────────────── */
.chart-shell{background:var(--bg-surface);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:15px 17px 13px;margin-bottom:14px;min-height:120px;}
.chart-shell-head{display:flex;align-items:flex-start;justify-content:space-between;
  gap:10px;flex-wrap:wrap;margin-bottom:10px;}
.chart-shell-title{font-size:14.5px;font-weight:600;letter-spacing:-.01em;}
.chart-shell-sub{font-size:12px;color:var(--text-secondary);margin-top:2px;
  font-variant-numeric:tabular-nums;}
.chart-shell>svg{display:block;width:100%;height:auto;}
.chart-shell .empty-note{padding:14px 2px;}
/* The honesty-footnote slot (rehearsal flags, "spend only", cohort maturity). */
.chart-shell .chart-note,.chart-shell .roas-note{font-size:11.5px;color:var(--text-secondary);margin-top:8px;}
.chart-shell.empty{display:flex;align-items:center;justify-content:center;}

/* ── 2.3 Compact entity card — .studio-card surface + a 3-column grid.
   Left: optional .avatar-ios; middle: .t name + .meta; right: .acts with ONE
   overflow "…" button instead of a per-row button wall. ──────────────────── */
.ent-card{background:var(--bg-surface);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:12px 15px;font-size:13.5px;
  display:grid;grid-template-columns:auto 1fr auto;gap:4px 12px;align-items:center;
  transition:transform .12s;}
.ent-card:hover{transform:translateY(-1px);}
.ent-card .t{font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px;flex-wrap:wrap;min-width:0;}
.ent-card .meta{font-size:12px;color:var(--text-secondary);word-break:break-word;}
.ent-card .acts{display:flex;align-items:center;gap:6px;justify-self:end;}
/* Selection is a USER act — orange, never holo (plan §3.1). */
.ent-card.selected{box-shadow:0 0 0 2px var(--accent-bright),var(--shadow-card);}

/* ── 2.4 Sheet — the forms-out-of-page pattern. Right-side panel for all
   edit/detail flows; .modal stays only for ≤6-field creates. Open/close is a
   class toggle only, so section markup moved into a sheet at authoring time
   keeps every boot-time getElementById/listener alive (static ownership).
   A closed sheet is visibility:hidden, not merely translated off-screen: an
   off-screen panel still takes Tab focus, and settingsView owns ten of them.
   The visibility flip is delayed by the slide duration on close and immediate
   on open, so the animation still reads. ─────────────────────────────────── */
.sheet{position:fixed;top:0;right:0;bottom:0;width:min(480px,100vw);
  background:var(--bg-surface);border-left:1px solid var(--border);
  box-shadow:-24px 0 60px rgba(0,0,0,.16);transform:translateX(102%);
  visibility:hidden;
  transition:transform .28s cubic-bezier(.2,.8,.2,1),visibility 0s linear .28s;
  z-index:40;display:flex;flex-direction:column;}
.sheet.open{transform:none;visibility:visible;
  transition:transform .28s cubic-bezier(.2,.8,.2,1),visibility 0s;}
.sheet.wide{width:min(640px,100vw);}
.sheet-head{flex:none;position:sticky;top:0;display:flex;align-items:center;
  justify-content:space-between;gap:10px;padding:14px 22px;
  border-bottom:1px solid var(--border);background:var(--bg-surface);}
.sheet-title{font-size:17px;font-weight:600;letter-spacing:-.01em;}
.sheet-close{width:44px;height:44px;flex:none;display:inline-flex;align-items:center;
  justify-content:center;border:none;background:transparent;border-radius:50%;
  font-size:19px;color:var(--text-secondary);cursor:pointer;}
.sheet-close:hover{background:rgba(120,120,128,.12);color:var(--text-primary);}
.sheet-body{flex:1;overflow-y:auto;padding:18px 22px;}
.sheet-body .field{margin-bottom:14px;}
.sheet-foot{flex:none;display:flex;justify-content:flex-end;gap:10px;padding:12px 22px;
  border-top:1px solid var(--border);background:var(--bg-surface);}
/* Stepped variant (ads campaign editor): a .sheet-steps strip under the head
   holds a .seg acting as the step indicator — one step per screen. */
.sheet.stepped .sheet-steps{flex:none;padding:12px 22px 0;display:flex;}
.sheet.stepped .sheet-steps .seg{width:100%;}
.sheet.stepped .sheet-steps .seg button{flex:1;}
/* Shared backdrop host — clone of .modal-backdrop one layer below sheets. */
.sheet-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.32);
  backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);display:none;z-index:35;}
.sheet-backdrop.open{display:block;}
@media (max-width:760px){.sheet,.sheet.wide{width:100vw;}}

/* ── 2.5 Segmented control — .seg (app.html) is canonical; .seg-ios
   (styles.css) is deprecated and aliased here so both meet the same target
   sizes while markup migrates view by view. ─────────────────────────────── */
.seg,.seg-ios{min-height:32px;align-items:center;}

/* ── 2.6 Empty state hero — generalizes the agent empty state. CTA is an
   orange .btn.primary wired to an EXISTING modal/sheet; optional .agent-chip
   sample rows reuse that class as-is. Never holo. ───────────────────────── */
.empty-hero{background:var(--bg-surface);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);text-align:center;padding:44px 24px;
  margin:24px auto;max-width:520px;}
.empty-hero .eh-mark{font-size:26px;color:var(--accent);margin-bottom:14px;}
.empty-hero .eh-title{font-size:17px;font-weight:600;margin-bottom:6px;letter-spacing:-.01em;}
.empty-hero .eh-sub{font-size:13px;color:var(--text-secondary);line-height:1.55;
  margin:0 auto 16px;max-width:380px;}
.empty-hero .agent-chips{margin-top:14px;justify-content:center;}

/* ── 2.7 Skeleton — first paint only: loaders seed placeholders before fetch
   and replace on render; never during live updates the user is watching. ── */
.skel{border-radius:8px;background:linear-gradient(90deg,var(--bg-muted) 25%,
  rgba(120,120,128,.10) 50%,var(--bg-muted) 75%);background-size:200% 100%;
  animation:skel 1.1s linear infinite;}
@keyframes skel{to{background-position:-200% 0;}}
.skel.skel-line{height:12px;}
.skel.skel-tile{height:72px;border-radius:var(--radius-lg);}
.skel.skel-card{height:120px;border-radius:var(--radius-lg);}

/* ── 2.10 Card — the shadcn Card, ported by hand into vanilla CSS on the
   tokens, exactly the way §2.8's Alert came over: no build step, no npm
   dependency, no /components/ui directory, no React.

   NUMBERING HERE IS CHRONOLOGICAL, NOT POSITIONAL. §2.9 Donut already lives
   near the bottom of this file; these three sections are numbered in the
   order they were written and placed where they disturb the least. Read the
   numbers as dates, not as coordinates.

   Markup:
   div.card(.card-sm|.card-lg)(.card-wide)(.card-interactive)(.selected)
     > div.card-head > div.card-title + div.card-desc
     + div.card-body
     + div.card-foot
   Everything after .card is optional; a card whose whole content is one
   paragraph writes .card-body and skips the head. A trailing control goes in
   .card-foot — the head stacks its two children rather than laying them out
   as a row, because a title and its description are one block of prose.

   THE BORDER IS THE POINT. It is the one declaration here that is not
   decoration. In dark mode --bg-surface sits on --bg-canvas at 1.08:1, and
   --shadow-card collapses to a single 0 1px 2px rgba(0,0,0,.35) drop that a
   near-black canvas swallows whole. Fill and shadow together therefore give a
   card NO EDGE AT ALL in the dark theme: what you get is one continuous dark
   field with text floating on it. The hairline is what draws the box —
   --border measures 1.23:1 against --bg-surface and 1.34:1 against
   --bg-canvas, so it is the only thing in the component carrying a visible
   boundary. It is not 1px of polish; remove it and the dark theme loses the
   card. (Light mode is the same shape of problem for a different reason:
   white on --bg-canvas is 1.12:1, but the light --shadow-card has a soft 16px
   ambient that does most of the work there. So the border is belt-and-braces
   in light and load-bearing in dark, and one rule covers both.)

   .card SUPERSEDES NOTHING RETROACTIVELY. .chart-shell, .bento-panel,
   .set-card, .stat-tile and .ent-card are each pinned by exact-count or
   exact-string assertions in test/dashboard-foundation.js, and not one of
   them is touched, re-based or aliased here. .card is what NEW work wears;
   the five incumbents keep their own surfaces until something deliberately
   migrates them, one at a time, with their assertions rewritten in the same
   commit as the markup.

   NO TRANSFORM, EVER — not on .card, not on its hover, and no filter,
   perspective or will-change either. A transformed ancestor becomes the
   containing block for any descendant's fixed-attachment background (CSS
   Transforms L1), which re-anchors that gradient to the transformed box.
   home.html documents the exact bug: .pcard's 3px hover lift was a
   translateY, and it re-anchored the price card's viewport-fixed heat ring to
   the card's own padding box at the precise moment the pointer was on it —
   the hot spot landed hundreds of pixels off-card and the ring went flat red
   whenever anyone hovered it, i.e. the only time anyone looks. That lift is
   `top` now, which creates no containing block. So is this one.

   Selection is a USER act, so it is --accent-bright and never holo — the same
   call .ent-card.selected made, spelled the same way. ────────────────────── */
.card{position:relative;display:flex;flex-direction:column;min-width:0;
  background:var(--bg-surface);border:1px solid var(--border);
  border-radius:var(--radius-lg);box-shadow:var(--shadow-card);padding:16px 18px;}
/* [hidden] has to out-specify the display:flex above, exactly as .alert
   [hidden] and .nav-badge already do it. */
.card[hidden]{display:none;}
.card-head{display:flex;flex-direction:column;gap:4px;margin-bottom:12px;min-width:0;}
.card-title{font-size:17px;font-weight:600;letter-spacing:-.01em;color:var(--text-primary);}
/* 58ch is the measure, not the box: a description that runs the full width of
   a .card-wide panel is a line nobody finishes. */
.card-desc{font-size:13px;line-height:1.55;color:var(--text-secondary);max-width:58ch;}
.card-body{min-width:0;}
/* margin-top:auto pins the foot to the bottom of the card, which is what makes
   a row of equal-height grid cards line their actions up. */
.card-foot{margin-top:auto;padding-top:12px;display:flex;align-items:center;
  gap:10px;flex-wrap:wrap;}

/* sizes — two classes deep so they win the tie against .card whatever the
   source order, the way §2.8's .alert.sm/.lg do. */
.card.card-sm{padding:12px 14px;}
.card.card-sm .card-head{margin-bottom:8px;}
.card.card-sm .card-title{font-size:14px;}
.card.card-lg{padding:24px;}
.card.card-lg .card-head{margin-bottom:14px;}
.card.card-lg .card-title{font-size:24px;letter-spacing:-.02em;}

/* layout + states */
.card-wide{grid-column:1/-1;}
.card-interactive{top:0;cursor:pointer;
  transition:top .14s,border-color .14s,box-shadow .14s;}
.card-interactive:hover{top:-2px;border-color:var(--accent-line);}
.card-interactive:focus-visible{outline:2px solid var(--accent-bright);outline-offset:2px;}
.card.selected{border-color:var(--accent-bright);
  box-shadow:0 0 0 2px var(--accent-bright),var(--shadow-card);}

/* A phone step-down, because .card-lg's 24px is 13% of a 375px viewport: 48px
   of a 375px screen would go to gutters before a single word is set. */
@media (max-width:480px){
  .card{padding:14px 15px;}
  .card.card-lg{padding:16px 15px;}
  .card.card-lg .card-title{font-size:20px;}
}

/* ── 2.11 Segmented control, modifiers — the base .seg rule is NOT here. It
   lives further down beside its .seg-ios alias, and that position is
   load-bearing: the coarse-pointer 44px override only wins because it is
   declared ahead of the base button rule at equal specificity, and a test
   pins that ordering by source index. Nothing in this section moves it.
   Every modifier below is two classes deep or a child combinator, so each
   wins on specificity rather than on position and is free to sit up here.

   .seg-fill     equal-width triggers. Three rules in this file hand-rolled
                 this already — the stepped sheet's step indicator, the
                 conversations filter and the contact-detail tabs — beside the
                 mobile shell's .m-seg--two grid twin. Those keep their own
                 copies until their markup gains the class; .seg-fill is what
                 the next one writes instead of a fourth.
   .seg-scroll   scrolls rather than compresses. The CRM's section nav is
                 seven triggers — Overview, People, Deals, Schedule, Comms,
                 Capture, Automations — and at 375px seven of anything either
                 wraps to three lines or squeezes every label past legibility.
                 A scroller keeps each trigger at its real width and its real
                 touch target; the scrollbar is hidden because a pill visibly
                 overflowing its container is already its own affordance.
   :focus-visible  the ring. These groups are driven by a roving tabindex, so
                 exactly ONE button in the group is tabbable and the arrow
                 keys move that tab stop — which makes the focus ring the only
                 thing on screen saying which button it currently is. Inset,
                 because the pill is 2px of padding around 2px gaps: an outset
                 ring would be drawn across the neighbouring trigger.
   .seg-panel    the panes a .seg switches between. [hidden] is display:none
                 from the UA sheet and loses to any author display, so a pane
                 that is itself a flex or grid container needs this the way
                 .alert[hidden] does.

   NOT ported: the source's h-10. shadcn sizes this control at 40px. Here it
   is 32px on a fine pointer (§2.5) and 44px on a coarse one (the touch-target
   block further down), and 40px is worse than both — too tall for a dense
   toolbar, too short to be a touch target. Two honest numbers beat one
   compromise that is neither. ────────────────────────────────────────────── */
.seg.seg-fill{display:flex;width:100%;}
.seg.seg-fill>button{flex:1 1 0;min-width:0;}
.seg.seg-scroll{display:flex;max-width:100%;flex-wrap:nowrap;overflow-x:auto;
  scrollbar-width:none;-ms-overflow-style:none;}
.seg.seg-scroll::-webkit-scrollbar{width:0;height:0;}
.seg.seg-scroll>button{flex:none;white-space:nowrap;}
.seg>button:focus-visible{outline:2px solid var(--accent-bright);outline-offset:-2px;}
.seg-panel[hidden]{display:none;}

/* ── 2.12 Border beam — ported from BorderBeam, and deliberately not by the
   route BorderBeam takes.

   THE MECHANISM IS A CONIC GRADIENT, NOT offset-path. The source walks a
   small gradient blob around offset-path:rect(…), i.e. offset-path taking a
   <basic-shape>. MDN's browser-compat-data for that subfeature
   (css/properties/offset-path.json, key basic_shape): Chrome 116, Edge
   mirror, Firefox 122, Safari 18 — and Safari 18 shipped September 2024. The
   conic build's one exotic dependency is @property, without which a custom
   property typed <angle> is an uninterpolable token: Chrome 85, Edge mirror,
   Firefox 128, Safari 16.4. So the so-called fallback is the older and wider
   mechanism in three of the four — 31 Chrome/Edge versions and roughly a year
   and a half of Safari. Firefox is the single engine where offset-path landed
   first (122 in Jan 2024 against 128 in Jul 2024), a six-month gap that
   closed two years ago. There is no browser today where offset-path buys a
   user anything, so this ships as ONE implementation with no @supports fork:
   a fork whose two arms are never both exercised is a fork that quietly loses
   an arm.

   THE RING is the house's own two-layer mask trick rather than a second
   element: fill the whole box, mask OUT the content box, and what survives is
   the padding ring. It ships three times in home.html already — the Alice
   cards' pointer beam, the price card's heat ring, the glass button's rim —
   and this is the fourth copy of the same six lines.
   The mask gradient is written linear-gradient(black 0 0) and not a hex,
   because this file is hex-scanned AND THE SCANNER READS COMMENTS. It is also
   the more honest spelling: only the mask's ALPHA is ever sampled, so the
   keyword is not a colour decision at all — any fully opaque value paints the
   identical mask in both themes. Read it as the number one.

   COLOUR is the holo pair swept end to end, --holo into --holo-cyan, which is
   the same two-stop idea as the source's colorFrom/colorTo. --holo-cyan is
   the middle stop of --holo-grad and DOES NOT EXIST as a token yet — that
   file belongs to another agent this wave — so it is written with a var()
   fallback to --holo. Today that paints a single-hue sweep; the day the token
   lands the two-stop ramp appears with no edit here.

   MEANING IS NARROW AND NORMATIVE. The beam says exactly one thing: a
   long-running server job is regenerating THIS card's contents right now. Not
   "loading" (that is .skel), not "a decision is waiting on you" (that is the
   holo rail and the holo badge), not "new". Which means:
     · AT MOST ONE beamed card per view, ever. A second is not twice the
       signal, it is no signal.
     · APPLIED ONLY BY JS toggling .beam, and removed when the job reports
       done. It is NEVER authored into markup — markup cannot know whether a
       job is running, so a hand-written .beam is a claim that never switches
       off.
     · NEVER co-occurring with .holo-pulse or a holo inset rail on the same
       surface. Two holo motions on one card read as a fault, not a state.
     · BANNED OUTRIGHT in the five zero-holo views — settings, agency, crm,
       files and visibility. Those budgets are asserted by exact rule lists
       further down this file and by markup slices in the foundation suite;
       the beam is holo, and holo does not get an exemption here.
   The sweep stops for reduced-motion users in the kill block below, which is
   the whole reason this section sits ABOVE it: the two rules are equal
   specificity and source order is the entire argument. Stopped, --beam-angle
   rests at its registered initial value and the ring shows one static arc —
   a truthful "something is happening" with the movement removed rather than
   the state. ─────────────────────────────────────────────────────────────── */
@property --beam-angle{syntax:"<angle>";inherits:false;initial-value:0deg;}
@keyframes beamSpin{to{--beam-angle:360deg;}}
.beam{position:relative;isolation:isolate;}
.beam::after{content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  border-radius:inherit;padding:1px;
  background:conic-gradient(from var(--beam-angle) at 50% 50%,transparent 0turn,
    var(--holo) .05turn,var(--holo-cyan,var(--holo)) .11turn,transparent .19turn,
    transparent 1turn);
  -webkit-mask:linear-gradient(black 0 0) content-box,linear-gradient(black 0 0);
  -webkit-mask-composite:xor;
  mask:linear-gradient(black 0 0) content-box,linear-gradient(black 0 0);
  mask-composite:exclude;
  animation:beamSpin 4.5s linear infinite;}

/* ── 2.8 Alert — the boxed message, ported from a React/Tailwind
   component the way everything else in this kit was: by hand, into vanilla CSS
   on the tokens. No build step, no npm dependency, no /components/ui, and the
   six glyphs are drawn as SVG path data in app.js rather than pulled in with
   lucide-react. The source ships variant × appearance × size as three
   independent axes plus a dismiss button; all four crossed over, minus two
   cells that could not be told the truth here:

     · There is NO `info` variant. The source paints info violet, and the
       obvious blue is already spoken for: --holo is a RESERVED scarcity tier
       meaning "the machine is acting right now", with per-view budgets
       asserted by exact rule list in test/dashboard-foundation.js. Every
       surface in this portal that plays the info role — the flow activity
       banner, the owner insights strip — is already built on --accent-soft
       or the neutral --bg-muted, so `info` maps onto `primary` and `mono` and
       no third hue enters a deliberately small palette. The info GLYPH still
       ships (message-square-warning, on `primary`); only the colour is gone.
     · There is NO `stroke` appearance. In the source it resolves to the same
       two declarations as `outline` — a border and a transparent fill — so
       porting it would have shipped a class that looks like a choice and is
       not one. `outline` is that appearance, under its honest name.

   Colour is the ONLY thing that changes per variant, and which token step you
   get is decided by what sits on top of the fill, exactly as the ramp note in
   styles.css sets out:
     solid    the variant's fill under --on-accent (white in both themes)
     outline  no fill, the -line step as the border, the -text step as text
     light    the -soft tint, the -line border, the -text step as text
   `secondary` and `mono` are the two neutrals and have no tint step to be the
   light version OF, so for those two solid and light differ by the hairline
   alone. `mono` inverts a PAIR rather than picking a colour: --text-primary
   and --bg-surface flip together, so the emphatic neutral reads near-black on
   white in light mode and near-white on near-black in dark without this file
   writing a body.dark rule (which it is not allowed to write anyway).

   MEASURED, because the ramp note in styles.css asks for it: white on
   --accent-deep is 4.52:1, on --danger 4.67:1, on --warning 4.93:1 — all AA
   for body text. White on --success is 4.39:1, which MISSES the 4.5:1 bar.
   --success has never carried text in this product (it is a progress fill and
   a glyph colour on --success-soft) and it does not start here:
   .alert.success.light puts --success-text on --success-soft and is what a
   green alert should use. The solid cell is kept, spelled out and measured
   rather than quietly shipped as if it passed — the --accent-bright
   precedent.
   The two NEUTRAL variants ride --text-secondary, which is 3.39:1 on
   --bg-muted. That is not a decision made here: it is what de-emphasised
   copy has measured everywhere in this app since the palette landed
   (.of-strip, .of-run-line, .cdb-issue, .stat-tile .st-l are all the same
   pair). The component inherits the house treatment rather than quietly
   forking a second, darker secondary that only alerts would use. Reach for
   .mono when the sentence has to be read rather than glanced at — that is
   the whole difference between the two neutrals, and it measures 16:1.

   Markup:
   div.alert(.secondary|.primary|.destructive|.success|.warning|.mono)
       (.solid|.outline|.light)(.sm|.md|.lg)[role=alert|status]
     > svg.alert-icon
     + div.alert-content > div.alert-title + div.alert-description
     + div.alert-toolbar
     + button.alert-close[type=button][aria-label]
   Everything after the icon is optional; an alert whose whole body is one
   sentence writes that sentence into .alert-description and skips the title.
   Bare .alert is secondary + solid + md, so the base rule states those three
   defaults once and every named class below re-states its own cell. Built by
   alertFill()/alertNode() in app.js so the markup and the glyphs live in one
   place. Never holo. ────────────────────────────────────── */
.alert{display:flex;align-items:flex-start;gap:10px;padding:12px 14px;
  border:1px solid transparent;border-radius:var(--radius);font-size:13px;
  line-height:1.5;background:var(--bg-muted);color:var(--text-secondary);}
/* [hidden] has to out-specify the display:flex above, the way .nav-badge and
   .lf-progress already do it — half the alerts in this app are toggled by
   the attribute and the other half by .style.display, and both must work. */
.alert[hidden]{display:none;}

/* solid — the fill carries the text. Naming `.solid` is optional (it is the
   default); the second selector on each line is what makes writing it safe. */
.alert.secondary,.alert.solid.secondary{background:var(--bg-muted);border-color:transparent;color:var(--text-secondary);}
.alert.primary,.alert.solid.primary{background:var(--accent-deep);border-color:var(--accent-deep);color:var(--on-accent);}
.alert.destructive,.alert.solid.destructive{background:var(--danger);border-color:var(--danger);color:var(--on-accent);}
.alert.success,.alert.solid.success{background:var(--success);border-color:var(--success);color:var(--on-accent);}
.alert.warning,.alert.solid.warning{background:var(--warning);border-color:var(--warning);color:var(--on-accent);}
.alert.mono,.alert.solid.mono{background:var(--text-primary);border-color:var(--text-primary);color:var(--bg-surface);}

/* outline — no fill; the -line step draws the edge, the -text step the text. */
.alert.outline{background:transparent;border-color:var(--border);color:var(--text-secondary);}
.alert.outline.primary{border-color:var(--accent-line);color:var(--accent-text);}
.alert.outline.destructive{border-color:var(--danger-line);color:var(--danger-text);}
.alert.outline.success{border-color:var(--success-line);color:var(--success-text);}
.alert.outline.warning{border-color:var(--warning-line);color:var(--warning-text);}
.alert.outline.mono{border-color:var(--sep);color:var(--text-primary);}

/* light — the -soft tint under the -text step. The workhorse: every alert
   this portal actually puts on screen is one of these six. */
.alert.light{background:var(--bg-muted);border-color:var(--border);color:var(--text-secondary);}
.alert.light.primary{background:var(--accent-soft);border-color:var(--accent-line);color:var(--accent-text-soft);}
.alert.light.destructive{background:var(--danger-soft);border-color:var(--danger-line);color:var(--danger-text);}
.alert.light.success{background:var(--success-soft);border-color:var(--success-line);color:var(--success-text);}
.alert.light.warning{background:var(--warning-soft);border-color:var(--warning-line);color:var(--warning-text);}
.alert.light.mono{background:var(--bg-muted);border-color:var(--sep);color:var(--text-primary);}

/* sizes — the source's radius/padding/icon/gap triple, on the house scale.
   .md restates the base so all three are spellable; .sm and .lg follow it and
   win the tie. */
.alert.md{gap:10px;padding:12px 14px;font-size:13px;border-radius:var(--radius);}
.alert.sm{gap:8px;padding:8px 12px;font-size:12.5px;}
.alert.lg{gap:12px;padding:14px 18px;font-size:14px;border-radius:var(--radius-lg);}

/* slots. The icon inherits the box's colour rather than owning one, so a
   variant is one class and never a glyph edit. */
.alert-icon{flex:none;width:18px;height:18px;margin-top:1px;fill:none;
  stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
.alert.sm .alert-icon{width:16px;height:16px;margin-top:0;}
.alert.lg .alert-icon{width:22px;height:22px;}
.alert-content{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:4px;}
.alert-title{font-size:14px;font-weight:600;letter-spacing:-.01em;}
.alert.sm .alert-title{font-size:13px;}
.alert.lg .alert-title{font-size:15px;}
.alert-description{word-break:break-word;}
.alert-toolbar{flex:none;margin-left:auto;display:flex;align-items:center;gap:6px;flex-wrap:wrap;}

/* Dismissal is NET-NEW: nothing in this portal is dismissible today, because
   every alert here is shown and hidden by the loader that owns it, and an
   alert a poller re-renders would silently un-dismiss itself. So the control
   ships complete and opted into by nobody yet — it is for the alerts that
   nothing re-renders, and the first one that wants it passes dismissible:true.
   It is a real <button type=button> with its listener attached in JS: this app
   runs under a CSP that forbids inline handlers, and a <span> would be
   unreachable by keyboard. The hover wash is color-mix on currentColor rather
   than a token so ONE rule covers all six variants in both themes without
   inventing a fill; the 44px coarse-pointer target comes from the shared
   block further down, next to .btn's. */
.alert-close{flex:none;margin-left:4px;width:26px;height:26px;padding:0;
  display:inline-flex;align-items:center;justify-content:center;border:none;
  border-radius:var(--radius-pill);background:transparent;color:inherit;
  opacity:.65;cursor:pointer;transition:opacity .12s,background .12s;}
.alert-close:hover{opacity:1;background:color-mix(in srgb,currentColor 14%,transparent);}
.alert-close:focus-visible{opacity:1;outline:2px solid var(--accent-bright);outline-offset:2px;}
.alert-close .alert-x{width:14px;height:14px;fill:none;stroke:currentColor;
  stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}

/* Two spans in the owner console already answered to the bare class name
   before this component existed: the pass-telemetry strip's holo segment
   (.of-strip .alert, pinned by exact string in the A7 holo list) and the
   insights banner's failure-rate segment (pinned by className in the A7 DOM
   test). Both are inline TEXT inside a strip, not boxes, and neither name is
   free to move. So the two strips un-box their own descendant here, once,
   rather than the kit ducking the collision under a second root name — the
   component is called .alert because that is what it is. Colour is NOT reset:
   both owning rules are .of-* descendant selectors and out-rank .alert. */
.of-strip .alert,.of-insights .alert{display:inline;padding:0;border:0;
  border-radius:0;background:none;font-size:inherit;gap:0;}
/* ── Holo motion — dots/rings pulse while the machine is acting; edges get a
   dash march (owned by the flow canvas when it lands in A3/B1). The A1
   shimmer/pulse surfaces are zeroed in a second reduced-motion block after
   the view sections below. ─────────────────────────────────────────────── */
@keyframes holoPulse{50%{opacity:.55;}}
.holo-pulse{animation:holoPulse 2.2s ease-in-out infinite;}
@media (prefers-reduced-motion:reduce){
  .skel,.holo-pulse{animation:none;}
  .beam::after{animation:none;}
  .card-interactive{transition-duration:.01ms;}
}

/* ── Chart helper support (charts.js draws structure; colors live here). ── */
.spark{display:block;}
.hbar{display:block;}
.hbar-track{fill:rgba(120,120,128,.14);}
.donut{display:block;}
.donut-track{stroke:rgba(120,120,128,.14);fill:none;}

/* ── Shell: sidebar nav badge slots (plan §3.3). Studio's pending count is
   holo (a decision blocking the machine); the Ads drafts count is neutral.
   Slots ship hidden; later waves set textContent + remove [hidden]. ─────── */
.nav-badge{display:inline-flex;align-items:center;justify-content:center;
  min-width:18px;height:18px;padding:0 5px;margin-left:6px;
  border-radius:var(--radius-pill);font-size:10.5px;font-weight:700;
  font-variant-numeric:tabular-nums;background:rgba(120,120,128,.16);
  color:var(--text-secondary);vertical-align:middle;}
.nav-badge.holo{background:var(--holo-soft);color:var(--holo);}
.nav-item.active .nav-badge{background:rgba(255,255,255,.22);color:inherit;}
.nav-badge[hidden]{display:none;}

/* ── Shell: the channel thread-list belongs to the Channels view only —
   switchView toggles body.view-chat (plan §3.3). When it's hidden the nav
   takes the slack so the footer stays pinned to the sidebar's bottom. ────── */
#chatNav{display:none;}
body.view-chat #chatNav{display:block;}
body:not(.view-chat) .sidebar-nav{flex:1;}

/* ── Shell: off-canvas drawer ≤760px (not an icon rail — #chatNav's text
   labels can't compress). ≥761px the sidebar is byte-identical to today. ── */
.nav-toggle{display:none;position:fixed;top:12px;left:12px;z-index:28;
  width:44px;height:44px;border-radius:12px;border:1px solid var(--border);
  background:var(--bg-surface);box-shadow:var(--shadow-card);font-size:18px;
  color:var(--text-primary);align-items:center;justify-content:center;padding:0;}
.drawer-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.32);
  z-index:29;opacity:0;pointer-events:none;transition:opacity .25s;}
@media (max-width:760px){
  .nav-toggle{display:inline-flex;}
  .sidebar{position:fixed;inset:0 auto 0 0;z-index:30;transform:translateX(-102%);
    transition:transform .25s;}
  .sidebar.open{transform:none;}
  .drawer-backdrop{display:block;}
  .drawer-backdrop.open{opacity:1;pointer-events:auto;}
  /* Clear the fixed hamburger (44px + 12px either side). .content prefix
     out-specifies the inline block's later `.view-header{padding:…}`. */
  .content .view-header{padding-left:68px;}
}

/* ── Touch targets ≥44px on coarse pointers (appendix §5). The .sidebar-nav
   prefix out-specifies the inline block's later `.nav-item{padding:…}`. ──── */
@media (pointer:coarse){
  .btn{min-height:44px;}
  .sidebar-nav .nav-item{padding:12px 14px;}
  .seg button,.seg-ios button{min-height:44px;padding:10px 15px;}
  .alert-close{min-height:44px;min-width:44px;}
}

/* ═════════════════════════════════════════════════════════════════════════
   A1 — the agent/chat/files/visibility view sections, evacuated from the
   app.html inline block as those views were rebuilt (plan §3.2's evacuation
   rule), plus the A1 additions. Rule order inside each section preserves the
   inline block's order; the block's later ".msg compacting" overrides are
   merged into their base rules here (same computed result, one rule). The
   deliberate remainders still inline: conversations' .msg.* variants (A2)
   and .msg.mine .bubble, whose intentional white-on-orange hex must stay out
   of this hex-scanned file.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── Overflow "…" menu — kit addendum debuting in A1 (chat header, files
   rows): ONE right-aligned trigger replacing per-row/-header button walls.
   Open/close is a class toggle driven by a single delegated listener pair in
   app.js (setupOverflowMenus) — CSP-safe, zero inline handlers. ─────────── */
.ovf{position:relative;display:inline-flex;}
.ovf-btn{min-width:34px;}
.ovf-menu{position:absolute;top:calc(100% + 6px);right:0;min-width:180px;z-index:24;
  background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;
  box-shadow:0 12px 32px rgba(0,0,0,.16);padding:5px;display:none;flex-direction:column;gap:1px;}
.ovf.open .ovf-menu{display:flex;}
/* Flips upward when the trigger sits near the viewport bottom (JS toggles). */
.ovf.up .ovf-menu{top:auto;bottom:calc(100% + 6px);}
.ovf-item{display:block;width:100%;text-align:left;border:none;background:transparent;
  border-radius:8px;padding:8px 11px;font-size:13px;font-weight:500;color:var(--text-primary);
  cursor:pointer;font-family:inherit;text-decoration:none;white-space:nowrap;}
.ovf-item:hover{background:var(--bg-muted);}
.ovf-item.danger{color:var(--danger);}
.view-header .ovf{margin-left:auto;}

/* ── agentView — one centred column; serif = the agent's voice, sans = the
   user's. Holo debuts here, and only here twice: the PENDING batch (a human
   decision blocking the machine) and the Thinking… shimmer (the machine
   acting right now). .decided reverts to the neutral border as before. ──── */
.agent-shell{flex:1;display:flex;flex-direction:column;min-height:0;background:transparent;}
/* The §3.3 stat row. Compact tiles and a 720px column so the numbers sit over
   the conversation rather than beside it — this console keeps its one-column
   voice, it just stops being the only view that opens with an input. */
.agent-stats{max-width:720px;margin:0 auto;width:100%;padding:14px 24px 0;}
.agent-scroll{flex:1;overflow-y:auto;padding:34px 24px 10px;}
.agent-thread{max-width:720px;margin:0 auto;display:flex;flex-direction:column;gap:22px;}

/* The empty state rides the kit's .empty-hero card; these scoped rules keep
   the agent's serif voice inside it. (.agent-chips/.agent-chip are reused by
   the kit as-is.) */
.agent-empty{margin-top:34px;}
.agent-empty .eh-title{font-family:var(--font-serif);font-size:24px;font-weight:500;}
.agent-empty .eh-sub{font-size:14px;max-width:440px;}
.agent-chips{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;}
.agent-chip{font-size:12.5px;padding:9px 15px;border:1px solid var(--border);border-radius:20px;background:var(--bg-surface);color:var(--text-primary);text-align:left;}
.agent-chip:hover{border-color:var(--accent-bright);background:var(--accent-soft);}

.agent-msg{display:flex;flex-direction:column;gap:6px;}
.agent-msg .who{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;font-weight:600;color:var(--text-secondary);}
.agent-msg.assistant .who{color:var(--accent);}
.agent-msg .body{font-size:14.5px;line-height:1.65;white-space:pre-wrap;color:var(--text-primary);}
.agent-msg.assistant .body{font-family:var(--font-serif);font-size:15px;}
.agent-msg.user .body{background:var(--bg-surface);border:1px solid var(--border);border-radius:8px;padding:11px 14px;}
/* Thinking… — the machine acting right now (allowed holo use). The sweep is
   --holo-soft, the designated tint behind holo text; --holo-grad never
   carries or backs text (plan §3.1). */
.agent-msg.pending .body{color:var(--holo);font-style:italic;border-radius:6px;
  background:linear-gradient(100deg,transparent 30%,var(--holo-soft) 50%,transparent 70%);
  background-size:200% 100%;background-repeat:no-repeat;
  animation:holoShimmer 1.8s linear infinite;}
@keyframes holoShimmer{from{background-position:180% 0;}to{background-position:-80% 0;}}

/* Proposal batch — the approval gate. Undecided = holo border/head (the
   canonical blocking-decision surface); .decided reverts to neutral and the
   title returns to the brand orange, exactly the pre-rebuild decided look. */
.agent-batch{border:1px solid var(--holo);border-radius:8px;overflow:hidden;background:var(--bg-surface);}
.agent-batch-head{padding:11px 14px;background:var(--holo-soft);display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;}
.agent-batch-head .ttl{font-size:13px;font-weight:600;color:var(--holo);}
.agent-batch-head .acts{display:flex;gap:7px;}
.agent-batch-list{padding:12px 14px;display:flex;flex-direction:column;gap:10px;max-height:400px;overflow-y:auto;}
.agent-camp{border:1px solid var(--border);border-radius:10px;padding:11px 13px;}
.agent-camp .nm{font-size:13.5px;font-weight:600;margin-bottom:3px;}
.agent-camp .meta{font-size:11.5px;color:var(--text-secondary);margin-bottom:7px;}
.agent-camp .cp{font-size:12.5px;white-space:pre-wrap;line-height:1.5;color:var(--text-primary);margin:0;}
.agent-batch-note{padding:9px 14px;border-top:1px solid var(--border);font-size:11.5px;color:var(--text-secondary);}
.agent-batch.decided{border-color:var(--border);opacity:.72;}
.agent-batch.decided .agent-batch-head{background:var(--bg-muted);}
.agent-batch.decided .agent-batch-head .ttl{color:var(--accent-text-soft);}

.agent-composer-wrap{border-top:1px solid var(--border);background:var(--bg-surface);padding:14px 24px 16px;}
.agent-composer{max-width:720px;margin:0 auto;display:flex;gap:9px;align-items:flex-end;}
.agent-composer textarea{flex:1;resize:none;max-height:180px;font-size:14px;line-height:1.5;padding:11px 13px;}
.agent-foot{max-width:720px;margin:8px auto 0;display:flex;justify-content:space-between;gap:12px;font-size:11px;color:var(--text-secondary);}
.linkish{background:none;border:none;color:var(--accent-text);text-decoration:underline;font-size:11px;padding:0;}

/* Ambient strip above the composer: neutral cross-view "waiting on you"
   chips aggregated from already-loaded endpoints. Never holo — this view's
   two holo instances are the pending batch and the shimmer, and a third
   would mean one is wrong (plan §3.1 budget). */
.agent-ambient{max-width:720px;margin:0 auto 9px;display:flex;gap:8px;flex-wrap:wrap;}
.agent-ambient[hidden]{display:none;}
.amb-chip{font-size:12px;font-weight:600;padding:5px 12px;border-radius:var(--radius-pill);
  border:1px solid var(--border);background:var(--bg-surface);color:var(--text-secondary);
  cursor:pointer;font-family:inherit;transition:color .15s,border-color .15s;}
.amb-chip:hover{color:var(--accent-text);border-color:var(--accent-bright);}
.amb-chip[hidden]{display:none;}

/* ── chatView + the sidebar channel list (#chatNav) ─────────────────────── */
/* The §3.3 stat row sits above BOTH states of this view — the empty hero and
   an open channel — because those are the only numbers that stay true in
   both. The per-channel header lives inside #chatActive and follows it. */
.chat-stats{flex:none;}
.messages{flex:1;overflow-y:auto;padding:20px 24px;display:flex;flex-direction:column;gap:14px;}
.msg{max-width:66%;margin-bottom:7px;}
.msg .bubble{background:var(--bg-surface);border:1px solid var(--border);border-radius:9px;padding:8px 11px;font-size:13px;line-height:1.42;white-space:pre-wrap;}
.msg .author{font-size:11.5px;font-weight:700;color:var(--accent-text);margin-bottom:3px;}
.msg .time{font-size:10.5px;color:var(--text-secondary);margin-top:3px;}
.msg.mine{align-self:flex-end;}
.msg.mine .author{display:none;}
.msg.system .bubble{background:var(--bg-muted);color:var(--text-secondary);font-style:italic;font-size:12px;padding:6px 10px;}
.msg.assistant .bubble{background:var(--accent-soft);border-color:var(--accent);}
.msg.assistant .author{color:var(--accent);}
.msg .used-files{font-size:10.5px;color:var(--text-secondary);margin-top:4px;}
/* Channel thinking indicator — the machine acting right now (allowed holo). */
.agent-thinking{padding:0 24px 8px;font-size:12px;color:var(--holo);font-style:italic;animation:holoPulse 2.2s ease-in-out infinite;}
.message-form{display:flex;gap:8px;padding:11px 18px;border-top:1px solid var(--border);background:var(--bg-surface);}
.message-form input{flex:1;}
.no-thread{flex:1;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);font-size:14px;text-align:center;padding:24px;}

/* Pending agent-proposal cards — a human decision blocking the machine: the
   card rail is holo (only pending proposals ever render in this bar). */
.proposals-bar{padding:10px 24px;border-bottom:1px solid var(--border);background:var(--holo-soft);display:none;flex-direction:column;gap:8px;}
.proposal-card{display:flex;align-items:center;gap:10px;background:var(--bg-surface);border:1px solid var(--border);border-left:3px solid var(--holo);border-radius:10px;padding:9px 12px;font-size:12.5px;}
.proposal-card .txt{flex:1;}
.proposal-card .txt .lbl{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--accent);font-weight:600;}

/* Sidebar channel thread list (shown only beside the Channels view — A0's
   body.view-chat scoping above). */
.thread-list{flex:1;overflow-y:auto;padding:0 10px 10px;}
.group-label{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-secondary);font-weight:600;margin:14px 8px 6px;display:flex;justify-content:space-between;align-items:center;}
.group-label .add{color:var(--accent-text);font-size:15px;font-weight:700;cursor:pointer;line-height:1;}
.thread-item{display:flex;align-items:center;gap:7px;width:100%;text-align:left;padding:8px 11px;border:none;background:transparent;border-radius:5px;font-size:13px;color:var(--text-primary);margin-bottom:2px;}
.thread-item:hover{background:var(--bg-muted);}
.thread-item.active{background:var(--accent-soft);color:var(--accent-text-soft);font-weight:600;}
.thread-item .nm{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
/* Unread — flat --ios-blue by rule (the SMS precedent): unread is neither
   machine-acting nor decision-blocking, so it never gets holo (plan §3.1). */
.thread-item .dot{width:8px;height:8px;border-radius:50%;background:var(--ios-blue);flex:none;margin-left:auto;}
.empty-note{font-size:12px;color:var(--text-secondary);padding:4px 9px;}

/* ── filesView (the .files-toolbar/.files-table primitives also still serve
   adsView until its own wave, A6) ───────────────────────────────────────── */
.files-toolbar{display:flex;gap:10px;padding:16px 24px;border-bottom:1px solid var(--border);background:var(--bg-surface);align-items:center;}
.files-toolbar input[type=text]{max-width:320px;}
/* Files' own search box breathes wider now that Search/Clear are gone. */
#filesView .files-toolbar input[type=text]{max-width:420px;}
#filesView .files-toolbar .btn.primary{margin-left:auto;}
.files-stats{padding-bottom:2px;}
/* The dropzone collapses to a one-line hint (click = browse); the real drop
   surface is the full-bleed overlay below. With an empty library it grows
   into the hero card — the dropzone IS the empty state. */
.dropzone{margin:14px 24px;border:1.5px dashed var(--border);border-radius:8px;padding:10px 14px;text-align:center;color:var(--text-secondary);font-size:12.5px;cursor:pointer;transition:border-color .15s,color .15s;}
.dropzone:hover,.dropzone:focus-visible{border-color:var(--accent-bright);color:var(--accent-text);outline:none;}
.dropzone.hero{margin:34px auto;max-width:520px;padding:44px 24px;font-size:14px;border-radius:var(--radius-lg);background:var(--bg-surface);box-shadow:var(--shadow-card);}
.drop-overlay{position:fixed;inset:0;z-index:60;display:none;align-items:center;justify-content:center;background:rgba(0,0,0,.32);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);}
.drop-overlay.open{display:flex;}
.drop-overlay .dov-card{background:var(--bg-surface);border:2px dashed var(--accent-bright);border-radius:var(--radius-lg);padding:38px 54px;font-size:15px;font-weight:600;color:var(--accent-text);box-shadow:var(--shadow-card);pointer-events:none;}
.files-list{flex:1;overflow-y:auto;padding:0 24px 24px;}
.files-list[hidden]{display:none;}
table.files-table{width:100%;border-collapse:collapse;font-size:13px;}
table.files-table th{text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-secondary);padding:8px 10px;border-bottom:1px solid var(--border);}
table.files-table td{padding:9px 10px;border-bottom:1px solid var(--border);vertical-align:middle;}
.search-results{padding:16px 24px;border-bottom:1px solid var(--border);background:var(--bg-muted);}
.search-results[hidden]{display:none;}
.search-result{background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;padding:10px 13px;margin-bottom:8px;font-size:12.5px;}
.search-result .fn{font-weight:700;color:var(--accent-text);margin-bottom:3px;}
.search-result mark{background:var(--accent-soft);color:var(--accent-text-soft);padding:0 1px;}
/* Expandable AI analysis-summary rows (kept), re-padded for .data-table. */
.analysis-row td{padding:0 16px 12px;border-bottom:.5px solid var(--sep);}
.analysis-box{background:var(--bg-muted);border:1px solid var(--border);border-radius:10px;padding:10px 13px;font-size:12.5px;color:var(--text-secondary);white-space:pre-wrap;}
.file-summary-toggle{background:none;border:none;color:var(--accent-text);font-size:11.5px;text-decoration:underline;padding:0;margin-left:8px;}
/* Deliberate widget-exposure marker on a row (opt-in is an admin act). */
.pill.widget{background:var(--accent-soft);color:var(--accent-text-soft);}
.files-data .acts-cell{text-align:right;white-space:nowrap;}
/* .data-table ships overflow:hidden for its rounded corners, which would clip
   the row menus — the files table lets them escape and re-rounds on cells. */
.files-data{overflow:visible;}
.files-data thead th:first-child{border-top-left-radius:var(--radius-lg);}
.files-data thead th:last-child{border-top-right-radius:var(--radius-lg);}
.files-data tbody tr:last-child td:first-child{border-bottom-left-radius:var(--radius-lg);}
.files-data tbody tr:last-child td:last-child{border-bottom-right-radius:var(--radius-lg);}

/* ── The FileCard grid (2026-08-28) ─────────────────────────────────────────
   A second layout over the same list, chosen with #filesLayoutSeg and
   remembered in localStorage('loggiaFilesLayout'). Table is the default and
   nothing here paints until someone asks for Cards.

   ZERO HOLO, like the rest of this view: holo means "the machine is acting
   right now" and a file at rest is not that. The one colour a card carries is
   its FAMILY, and the six --ft-* tokens are declared in styles.css — this
   file is hex-scanned and may not spell a colour that differs by theme. They
   are fills that carry --on-accent (white text on a ground dark enough for
   it), so they appear as `background` beside `color:var(--on-accent)` and
   never as a text colour. ─────────────────────────────────────────────────*/

/* The toolbar's free space belongs to the toggle now. Two `margin-left:auto`
   in one flex line SPLIT the free space between them rather than stacking, so
   adding one to the seg while the button kept its own would have parked the
   toggle halfway across the bar. One auto margin, on the search box, pushes
   everything after it to the right edge — and it keeps doing that in the
   empty state, where the seg is display:none and there is nothing between the
   box and the button. Both rules are deliberate later-wins overrides of the
   identical-specificity pair above, which pre-date the toggle. */
#filesView .files-toolbar input[type=text]{margin-right:auto;}
#filesView .files-toolbar .btn.primary{margin-left:0;}
/* The .seg base rule is display:inline-flex — an author declaration, so the UA
   stylesheet's [hidden]{display:none} loses to it, the same trap #mtNewWrap
   documents further down this file. The toggle needs its own rule to actually
   vanish while the library is empty. */
#filesLayoutSeg[hidden]{display:none;}

.files-cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));
  gap:14px;padding-top:2px;}
.files-cards[hidden]{display:none;}

.file-card{background:var(--bg-surface);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:10px 10px 11px;display:flex;
  flex-direction:column;gap:7px;
  /* NOT overflow:hidden. The row menus needed .files-data{overflow:visible} to
     escape the table's rounded clip; the grid has the same problem in another
     shape, and a card that clipped would swallow the .ovf-menu of every tile
     in the last column. Only the thumb clips, and it holds nothing that has to
     escape. */
  overflow:visible;}

.fc-thumb{position:relative;height:118px;border-radius:10px;overflow:hidden;
  background:var(--bg-muted);border:1px solid var(--border);
  display:flex;align-items:center;justify-content:center;padding:14px 16px 24px;}
/* The coloured format banner: the family's fill, with the token that fill is
   built to carry. Six rules and no seventh — a deck is a document (see
   FILE_FORMATS in app.js). --text-tertiary is the base so a card whose family
   class somehow failed to land is still a legible banner rather than a
   transparent one. */
.fc-banner{position:absolute;left:0;right:0;bottom:0;padding:2px 9px;
  font-size:10.5px;font-weight:700;letter-spacing:.06em;
  color:var(--on-accent);background:var(--text-tertiary);}
.file-card.ft-doc .fc-banner{background:var(--ft-doc);}
.file-card.ft-sheet .fc-banner{background:var(--ft-sheet);}
.file-card.ft-image .fc-banner{background:var(--ft-image);}
.file-card.ft-video .fc-banner{background:var(--ft-video);}
.file-card.ft-archive .fc-banner{background:var(--ft-archive);}
.file-card.ft-code .fc-banner{background:var(--ft-code);}

.fc-name{font-size:13px;font-weight:600;line-height:1.3;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fc-meta{font-size:11.5px;color:var(--text-secondary);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.fc-foot{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
.fc-pills{display:flex;align-items:center;gap:5px;flex-wrap:wrap;min-width:0;}
.fc-acts{margin-left:auto;}

/* ── The per-format wireframe ───────────────────────────────────────────────
   Static by design, and deliberately NOT built on .skel: that class is the
   kit's shimmer and §2.7 documents it as first-paint only, so a grid of them
   would be a strobe rather than a hint. One neutral ink for every piece,
   declared once as a local custom property — a grey with an alpha channel
   rather than a token, because it has to sit on --bg-muted in both themes,
   which is exactly what the .seg and .skel neutrals already do. */
.fc-skel{--fc-ink:rgba(120,120,128,.34);
  display:flex;align-items:center;justify-content:center;gap:6px;
  width:100%;height:100%;pointer-events:none;}
.fc-skel .fc-stack{display:flex;flex-direction:column;gap:5px;flex:1;min-width:0;}
.fc-bar{height:5px;border-radius:3px;background:var(--fc-ink);}
.fc-w1{width:100%;} .fc-w2{width:72%;} .fc-w3{width:88%;}
/* The glyphs themselves are text nodes built in app.js — never `content:` —
   so they can be read out, selected and copied like any other character. */
.fc-glyph{font-family:var(--font-serif);font-size:26px;line-height:1;font-weight:600;
  color:var(--fc-ink);flex:none;}
/* lines — prose */
.fc-lines{flex-direction:column;align-items:stretch;gap:6px;}
/* grid — a spreadsheet */
.fc-grid{display:grid;grid-template-columns:repeat(3,1fr);grid-auto-rows:11px;
  gap:5px;align-content:center;align-items:stretch;}
.fc-cell{border-radius:2px;background:var(--fc-ink);}
/* cols — delimited text: columns, not a lattice */
.fc-cols{align-items:stretch;gap:10px;}
.fc-cols .fc-col{display:flex;flex-direction:column;gap:5px;flex:1;justify-content:center;}
.fc-cols .fc-cell{height:6px;}
/* md / angle / brace — a glyph beside a short stack; the glyph characters
   themselves are text nodes built in app.js. */
.fc-md,.fc-angle,.fc-brace{align-items:center;}
/* zip — offset teeth */
.fc-zip{flex-direction:column;align-items:center;gap:4px;}
.fc-tooth{width:34px;height:6px;border-radius:2px;background:var(--fc-ink);
  transform:translateX(-7px);}
.fc-tooth.alt{transform:translateX(7px);}
/* swatch — an image, standing in as blocks of tone */
.fc-swatch{display:grid;grid-template-columns:1fr 1fr;grid-auto-rows:22px;gap:5px;
  align-content:center;align-items:stretch;}
.fc-sw{border-radius:3px;background:var(--fc-ink);}
.fc-sw.fc-s2{background:rgba(120,120,128,.2);}
.fc-sw.fc-s3{background:rgba(120,120,128,.24);}
.fc-sw.fc-s4{background:rgba(120,120,128,.46);}
/* play — a SHAPE, not a glyph: the triangle is a border, so no `content:`
   here either. It is cut out in --bg-muted, the thumb's own ground. */
.fc-play{flex-direction:column;gap:9px;}
.fc-disc{width:36px;height:36px;border-radius:50%;background:var(--fc-ink);
  display:flex;align-items:center;justify-content:center;}
.fc-tri{width:0;height:0;margin-left:3px;
  border-top:7px solid transparent;border-bottom:7px solid transparent;
  border-left:11px solid var(--bg-muted);}
.fc-scrub{width:62%;height:4px;border-radius:2px;background:var(--fc-ink);}

/* 375px: minmax(300px,1fr) does not survive a phone — one 300px track inside
   the view's 24px gutters overflows the line, so below 420px the grid drops to
   a single column and the thumb gives back a little height with it. */
@media (max-width:420px){
  .files-cards{grid-template-columns:1fr;gap:12px;}
  .fc-thumb{height:104px;}
  /* Three controls on one 375px line is a truncated search box beside a
     two-line button (measured, not assumed). The box takes its own row and
     the toggle shares the next one with Upload, which keeps the right edge. */
  .files-toolbar{flex-wrap:wrap;}
  #filesView .files-toolbar input[type=text]{flex:1 1 100%;max-width:none;margin-right:0;}
  #filesView .files-toolbar .btn.primary{margin-left:auto;}
}

/* ── visibilityView — zero holo by design; score coloring stays
   success/amber/danger. The new .vis-body wrapper finally makes the view
   scroll (it never had an internal scroller; html,body{overflow:hidden}
   clipped everything below the fold). ───────────────────────────────────── */
.vis-stats{padding-bottom:2px;}
.vis-stats .st-n.up{color:var(--success);}
.vis-stats .st-n.down{color:var(--danger);}
.vis-body{flex:1;overflow-y:auto;min-height:0;padding:16px 24px 28px;}
.vis-intro{background:var(--bg-surface);border-radius:16px;box-shadow:var(--shadow-card);padding:15px 18px;margin-bottom:12px;max-width:760px;}
.vis-intro-t{font-family:var(--font-serif);font-size:18px;font-weight:700;letter-spacing:-.01em;margin-bottom:5px;}
.vis-intro-p{font-size:13.5px;line-height:1.55;color:var(--text-secondary);margin:0;}
.vis-fine{display:block;font-size:12px;color:var(--text-tertiary);margin-top:7px;}
.vis-scan-bar{display:flex;gap:9px;max-width:520px;margin-bottom:8px;}
.vis-scan-bar input{flex:1;padding:11px 14px;border:1px solid var(--border);border-radius:11px;font-size:14px;font-family:inherit;background:var(--bg-surface);color:var(--text-primary);}
.vis-scan-bar input:focus{outline:none;border-color:var(--accent-bright);}
/* #visError is .alert.destructive.light.sm now (kit §2.8); what is left
   here is the two things the component does not own — where it sits and how
   wide it gets. Its colour moved from --danger to --danger-text with the
   port, which is the step that flips: --danger on --danger-soft measures
   3.2:1 on the dark canvas, the exact failure mobile.css documents at line
   87. */
.vis-err{max-width:520px;margin-bottom:12px;}
.vis-loading{display:flex;align-items:center;gap:10px;font-size:13.5px;color:var(--text-secondary);padding:14px 0;}
.vis-spin{width:16px;height:16px;border:2px solid var(--border);border-top-color:var(--accent-bright);border-radius:50%;animation:visspin .7s linear infinite;}
@keyframes visspin{to{transform:rotate(360deg);}}
.vis-scores{display:flex;gap:26px;align-items:center;flex-wrap:wrap;background:var(--bg-surface);border-radius:18px;box-shadow:var(--shadow-card);padding:20px 24px;margin:14px 0;max-width:760px;}
.vis-ring-wrap{position:relative;width:120px;height:120px;flex:none;}
.vis-ring{width:120px;height:120px;transform:rotate(-90deg);}
.vr-track{fill:none;stroke:rgba(120,120,128,.14);stroke-width:9;}
/* 327 ≈ 2πr for r=52. The dash offset is set from JS to draw the arc. */
.vr-fill{fill:none;stroke:var(--accent-bright);stroke-width:9;stroke-linecap:round;stroke-dasharray:327;stroke-dashoffset:327;transition:stroke-dashoffset .8s cubic-bezier(.2,.8,.2,1),stroke .4s;}
.vis-ring-c{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;}
.vis-ring-n{font-size:32px;font-weight:700;letter-spacing:-.03em;font-variant-numeric:tabular-nums;}
.vis-ring-l{font-size:10.5px;text-transform:uppercase;letter-spacing:.07em;color:var(--text-tertiary);}
.vis-bars{flex:1;min-width:260px;}
.vis-bar-row{display:flex;align-items:center;gap:11px;padding:6px 0;font-size:13px;}
.vis-bar-row .vbl{width:88px;flex:none;font-weight:500;}
.vis-bar-row .vbt{flex:1;height:7px;border-radius:99px;background:rgba(120,120,128,.14);overflow:hidden;}
.vis-bar-row .vbf{display:block;height:100%;width:0;border-radius:99px;background:linear-gradient(90deg,var(--heat-red),var(--heat-orange),var(--heat-yellow));transition:width .8s cubic-bezier(.2,.8,.2,1);}
.vis-bar-row .vbn{width:34px;text-align:right;font-variant-numeric:tabular-nums;color:var(--text-secondary);font-size:12.5px;}
.vis-scanned{font-size:11.5px;color:var(--text-tertiary);margin-top:9px;}
.vis-delta{font-size:11.5px;font-weight:600;margin-left:6px;}
.vis-delta.up{color:var(--success);} .vis-delta.down{color:var(--danger);}
/* Findings — 2-col at the ≥900px enhancement tier; pass items compress to a
   single line while fails keep their detail + Fix line. */
.vis-checks{display:grid;grid-template-columns:1fr;gap:8px;max-width:960px;margin-bottom:16px;}
.vis-check{background:var(--bg-surface);border-radius:13px;box-shadow:var(--shadow-card);padding:12px 15px;display:flex;gap:12px;align-items:flex-start;}
.vis-check .vc-icon{width:21px;height:21px;border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700;margin-top:1px;}
.vis-check.pass .vc-icon{background:var(--success-soft);color:var(--success);margin-top:0;}
.vis-check.fail .vc-icon{background:var(--danger-soft);color:var(--danger);}
.vis-check.pass{align-items:center;padding:9px 15px;}
.vis-check .vc-body{min-width:0;}
.vis-check .vc-l{font-size:13.5px;font-weight:600;}
.vis-check .vc-d{font-size:12.5px;color:var(--text-secondary);margin-top:2px;line-height:1.45;}
.vis-check .vc-f{font-size:12.5px;color:var(--accent-text);margin-top:5px;line-height:1.45;}
.vis-check .vc-area{margin-left:auto;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-tertiary);flex:none;padding-top:3px;}
.vis-check.pass .vc-area{padding-top:0;}
@media (min-width:900px){.vis-checks{grid-template-columns:1fr 1fr;}}
.vis-fix-cta{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:16px;max-width:760px;}
.vis-fix-block{background:var(--bg-surface);border-radius:16px;box-shadow:var(--shadow-card);padding:16px 18px;margin-bottom:12px;max-width:760px;}
.vis-fix-h{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:14px;font-weight:700;letter-spacing:-.01em;margin-bottom:4px;}
.vis-fix-p{font-size:12.5px;color:var(--text-secondary);margin:0 0 10px;}
.vis-code{background:var(--bg-muted);border-radius:10px;padding:13px 15px;font-size:11.5px;line-height:1.55;overflow-x:auto;white-space:pre-wrap;word-break:break-word;margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--text-primary);}
.vis-faq{display:flex;flex-direction:column;gap:11px;}
.vis-faq .vq{font-size:13.5px;font-weight:600;}
.vis-faq .va{font-size:13px;color:var(--text-secondary);line-height:1.5;margin-top:3px;}
.vis-steps{margin:0;padding-left:19px;font-size:13.5px;line-height:1.65;color:var(--text-secondary);}
/* History — Charts.sparkline first (in the kit's chart shell), the raw rows
   collapsed behind a native <details>. */
.vis-history{max-width:760px;}
.vis-spark{color:var(--accent-bright);}
.vis-spark svg{display:block;width:100%;max-width:640px;height:auto;}
.vis-hist-details{margin-top:10px;}
.vis-hist-details summary{cursor:pointer;font-size:12.5px;font-weight:600;color:var(--text-secondary);list-style:none;}
.vis-hist-details summary::-webkit-details-marker{display:none;}
.vis-hist-details summary::after{content:' ▸';font-size:10px;}
.vis-hist-details[open] summary::after{content:' ▾';}
.vis-hist-details[open] summary{margin-bottom:8px;}
.vis-hrow{display:flex;align-items:center;gap:12px;padding:9px 13px;background:var(--bg-surface);border-radius:11px;box-shadow:var(--shadow-card);margin-bottom:6px;font-size:13px;}
.vis-hrow .hs{font-weight:700;font-variant-numeric:tabular-nums;width:32px;}
.vis-hrow .hu{color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.vis-hrow .hd{margin-left:auto;font-size:11.5px;color:var(--text-tertiary);flex:none;}

/* ── A1 motion, zeroed for reduced-motion users (the shimmer text stays holo,
   the movement stops). Sits after the rules it silences. ────────────────── */
@media (prefers-reduced-motion:reduce){
  .agent-msg.pending .body{animation:none;background:none;}
  .agent-thinking{animation:none;}
}

/* ═════════════════════════════════════════════════════════════════════════
   A2 — conversationsView, evacuated from the app.html inline block as the
   view was rebuilt. Master-detail with the shared stat row on top. The
   "Needs you" chip + its per-row pill twin are this view's single holo
   signal (a customer waiting on a human = a decision blocking the machine,
   plan §3.1); AI-active rows keep the orange-soft status pill, closed stays
   neutral, and the unread dot is flat --ios-blue per the SMS precedent.
   The .msg.customer/.ai variants land here beside chatView's .msg base; the
   white-on-orange .msg.mine/.msg.agent pair joined them in A5, once
   styles.css had an --on-accent token to say "white" without a hex literal.
   ═════════════════════════════════════════════════════════════════════════ */
.conv-stats{padding-bottom:12px;}
/* Tiles double as filter shortcuts (same dispatch as the seg). */
.conv-stats .stat-tile{cursor:pointer;}
/* Holo only while nonzero — JS toggles .waiting; a zero blocks nothing. */
.conv-stats .st-n.waiting{color:var(--holo);}
.conv-shell{flex:1;display:flex;min-height:0;border-top:1px solid var(--border);}
.conv-list-pane{width:300px;flex:none;border-right:1px solid var(--border);display:flex;flex-direction:column;background:var(--bg-surface);}
.conv-filter{padding:10px 12px;border-bottom:1px solid var(--border);}
.conv-filter .seg{display:flex;width:100%;}
.conv-filter .seg button{flex:1;padding:6px 8px;font-size:12px;}
.conv-list{flex:1;overflow-y:auto;}
.conv-item{display:block;width:100%;text-align:left;padding:11px 16px;border:none;border-bottom:1px solid var(--border);background:transparent;}
.conv-item:hover{background:var(--bg-muted);}
.conv-item.active{background:var(--accent-soft);}
.conv-item .conv-name{font-size:13px;display:flex;gap:8px;align-items:center;}
.conv-item .conv-name .nm{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600;}
/* Unread — flat --ios-blue by rule: neither machine-acting nor blocking. */
.conv-item .dot{width:8px;height:8px;border-radius:50%;background:var(--ios-blue);flex:none;}
.conv-item .conv-preview{font-size:12px;color:var(--text-secondary);margin-top:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.conv-item .conv-time{font-size:10.5px;color:var(--text-secondary);margin-top:2px;font-variant-numeric:tabular-nums;}
/* The blocking-decision pill — holo text on the designated holo-soft tint
   (AA-checked pair, the nav-badge.holo precedent). */
.pill.conv-needs{background:var(--holo-soft);color:var(--holo);}
.conv-main-pane{flex:1;display:flex;flex-direction:column;min-width:0;}
.conv-back{display:none;}
/* Conversation-thread bubble variants (the .msg base lives with chatView
   above; these must follow it in file order to win their cascade ties). */
.msg.customer{align-self:flex-start;}
.msg.customer .bubble{background:var(--bg-surface);border:1px solid var(--border);}
.msg.ai{align-self:flex-end;}
.msg.ai .bubble{background:var(--accent-soft);border-color:var(--accent);}
.msg.agent{align-self:flex-end;}
/* The white-on-orange pair, finally out of the inline block (A5): it was the
   one A1/A2 remainder because its literal white could not enter this
   hex-scanned file (the scan reads comments too, so none is written here).
   --on-accent (styles.css) is that white as a token — white in both themes by
   design, exactly like .btn.primary. Both selectors stay combined, and they
   sit after .msg.ai .bubble so the equal-specificity order is unchanged. */
.msg.mine .bubble,.msg.agent .bubble{background:var(--accent-deep);color:var(--on-accent);border-color:var(--accent-deep);}
/* ≤760px: single-pane push nav — the list runs full-width; opening a thread
   slides the main pane over the whole view (the sheet slide, one class on
   .conv-shell toggled by openConversation/Back — no socket, no re-render).
   z-index 26 keeps it under the fixed hamburger (28) and drawer (29/30). */
@media (max-width:760px){
  .conv-list-pane{width:100%;border-right:none;}
  .conv-main-pane{position:fixed;inset:0;z-index:26;background:var(--bg-canvas);
    transform:translateX(102%);transition:transform .28s cubic-bezier(.2,.8,.2,1);}
  .conv-shell.thread-open .conv-main-pane{transform:none;}
  .conv-back{display:inline-flex;}
}
@media (prefers-reduced-motion:reduce){
  .conv-main-pane{transition:none;}
}

/* ═════════════════════════════════════════════════════════════════════════
   A3 — autopilotView + the flow-canvas vocabulary, born here exactly so the
   full canvas (B1) and the CRM Workflows tab (A6) inherit proven CSS
   (appendix-visual-language §2.9). Class names are generic from day one —
   nothing ap-* about the canvas itself. The autopilot funnel is the first
   consumer: .flow-node stage cards over one absolutely-positioned SVG layer
   of bezier .flow-edge connectors. An edge whose stage automation is ON runs
   the holo-grad dash march (machine autonomously acting — plan §3.1's
   allowed list); .flow-canvas.paused (apPaused OR the org all-stop) turns
   every live signal --danger, because paused must never look alive. State
   classes shipped here are the ones with consumers today (.on/.live/.paused);
   B1 adds .running/.selected/.locked when the editor exists to use them.
   ═════════════════════════════════════════════════════════════════════════ */
.flow-canvas{position:relative;display:flex;gap:26px 30px;padding:16px 24px 4px;
  flex-wrap:wrap;align-items:stretch;}
.flow-canvas .flow-edges{position:absolute;inset:0;width:100%;height:100%;
  pointer-events:none;overflow:visible;}
/* Node card — .studio-card surface + a reserved left state rail. Clickable
   stages render as <button>, so the resets keep both tags identical. */
.flow-node{position:relative;z-index:1;flex:1;min-width:150px;text-align:left;
  background:var(--bg-surface);border:none;border-radius:14px;
  box-shadow:var(--shadow-card);padding:13px 15px;
  border-left:3px solid transparent;font-family:inherit;color:var(--text-primary);}
button.flow-node{cursor:pointer;transition:transform .12s;}
button.flow-node:hover{transform:translateY(-1px);}
.fn-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:4px;}
.fn-kind{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;
  font-weight:600;color:var(--text-tertiary);}
.fn-dot{width:8px;height:8px;border-radius:50%;background:var(--border);flex:none;}
.fn-dot.live{background:var(--holo);}
.fn-n{font-size:26px;font-weight:700;letter-spacing:-.02em;font-variant-numeric:tabular-nums;}
.fn-title{font-size:14px;font-weight:600;letter-spacing:-.01em;}
.fn-sub{font-size:12px;color:var(--text-secondary);margin-top:2px;}
/* Enabled is a USER act — the rail is brand orange; only the dot/edge (the
   machine acting) get holo. */
/* SWEPT to --accent-bright by the 2026-08-03 ramp. Nothing sits on the 3px
   rail: every label lives inside the card's 13px/15px padding, on
   --bg-surface. And the comparison that carries the meaning is ON vs OFF --
   OFF is this same 3px slot with a TRANSPARENT border, i.e. the card's own
   surface showing through -- so the test is bright on --bg-surface, 3.00:1,
   at the 3:1 graphical bar. (5.80:1 on the dark surface.) The rail's other
   neighbour, the page canvas behind .flow-canvas, is 2.69:1, but that edge
   is drawn by --shadow-card, not by this rail.
   Side effect, deliberate: --accent-deep is the one white-label token
   (app.js WHITE_LABEL_TOKENS), so this rail stops repainting per tenant --
   which is the right side of that line for a state indicator that has to
   stay visible whatever hex an agency picked.
   Two graphical uses below are NOT swept; each states its own measurement. */
.flow-node.on{border-left-color:var(--accent-bright);}
/* Edges: gray at rest, holo-grad + dash march + slow pulse while live. The
   2px ceiling is the --holo-grad stroke limit (plan §3.1).
   --fe-live-stroke is set on each edge layer by Charts.holoStroke and names
   the gradient THAT canvas minted: SVG ids are document-global and views are
   hidden rather than removed, so one fixed gradient id written here would send
   every canvas to whichever def comes first in the document. The fallback
   is a solid holo stroke — an unsized canvas degrades to a flat colour, never
   to nothing. Declaring the stroke through a custom property (rather than an
   inline style) is what keeps the .paused override below able to win. */
.flow-edge .fe-path{stroke:var(--border);}
.flow-edge.live .fe-path{stroke:var(--fe-live-stroke,var(--holo));stroke-width:2;stroke-dasharray:7 4;
  animation:flowDash 1.4s linear infinite,holoPulse 2.8s ease-in-out infinite;}
@keyframes flowDash{to{stroke-dashoffset:-22;}}
.fe-label{fill:var(--text-secondary);font-size:11px;font-weight:600;
  font-variant-numeric:tabular-nums;font-family:inherit;}
.flow-arrow{fill:var(--border);}
.flow-arrow-live{fill:var(--holo);}
/* Paused/all-stop: one class on the canvas flips every live signal to
   danger — a paused machine must never look alive. Live edges keep their
   dashes (interrupted, not resting) but stop moving. */
.flow-canvas.paused .flow-edge.live .fe-path{stroke:var(--danger);animation:none;}
.flow-canvas.paused .fn-dot.live{background:var(--danger);animation:none;}
.flow-canvas.paused .flow-arrow-live{fill:var(--danger);}
.flow-canvas.paused .flow-node.on{border-left-color:var(--danger);}
/* First-paint skeletons keep the canvas's card footprint. */
.flow-skel{flex:1;min-width:150px;}

/* ── autopilotView chrome (evacuated from the app.html inline block, order
   preserved; the old arrow-chain funnel classes died with the rebuild). ─── */
.ap-stats{padding-bottom:2px;}
.ap-body{flex:1;overflow-y:auto;min-height:0;}
/* #apStatus holo dot — the machine is autonomously acting right now. */
.meta.ap-live::before{content:'';display:inline-block;width:7px;height:7px;
  border-radius:50%;background:var(--holo);margin-right:6px;vertical-align:1px;
  animation:holoPulse 2.2s ease-in-out infinite;}
.ap-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:14px;padding:14px 24px;}
.ap-card{background:var(--bg-surface);border-radius:16px;box-shadow:var(--shadow-card);padding:18px 20px;}
/* Stage-click landing flash — orange (a user act), fading to rest. */
.ap-card.flash{animation:apCardFlash .9s ease-out 1;}
@keyframes apCardFlash{0%{box-shadow:0 0 0 3px var(--accent-bright),var(--shadow-card);}
  100%{box-shadow:var(--shadow-card);}}
.ap-h{font-family:var(--font-serif);font-size:17px;font-weight:700;letter-spacing:-.01em;margin-bottom:6px;}
.ap-h-sub{margin-top:20px;}
.ap-p{font-size:13px;color:var(--text-secondary);line-height:1.55;margin-bottom:12px;}
.ap-toggle{display:flex;align-items:center;gap:9px;font-size:13.5px;padding:7px 0;cursor:pointer;}
.ap-toggle input{width:16px;height:16px;accent-color:var(--accent-bright);}
.ap-toggle.ap-kill{margin-top:10px;padding-top:10px;border-top:1px solid var(--sep);color:var(--danger);font-weight:600;}
.ap-toggle.ap-kill input{accent-color:var(--danger);}
.ap-lbl{display:block;font-size:12px;font-weight:600;color:var(--text-secondary);margin:12px 0 5px;}
.ap-req{font-weight:400;color:var(--text-tertiary);}
#autopilotView textarea,#autopilotView input[type=text],#autopilotView select{
  width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:10px;font-size:13.5px;
  font-family:inherit;background:var(--bg-surface);color:var(--text-primary);}
#autopilotView textarea:focus,#autopilotView input:focus,#autopilotView select:focus{outline:none;border-color:var(--accent-bright);}
.ap-note{font-size:11.5px;color:var(--text-tertiary);line-height:1.5;margin-top:8px;}
.ap-hours{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--text-secondary);flex-wrap:wrap;}
.ap-hours input{width:56px;padding:6px 8px;border:1px solid var(--border);border-radius:8px;font-family:inherit;font-size:13px;}
/* Toggle-first collapses (native details — the .vis-hist-details idiom). */
.ap-details{margin:4px 0 6px;}
.ap-details>summary{cursor:pointer;font-size:12.5px;font-weight:600;color:var(--text-secondary);
  list-style:none;padding:3px 0;}
.ap-details>summary::-webkit-details-marker{display:none;}
.ap-details>summary::after{content:' ▸';font-size:10px;}
.ap-details[open]>summary::after{content:' ▾';}
.ap-details>summary:hover{color:var(--accent-text);}
.ap-save-row{display:flex;align-items:center;gap:10px;padding:0 24px 28px;max-width:900px;}
/* ≤760px: vertical document flow — the SVG layer rests and edges collapse to
   short connectors between stacked cards (the canvas's mobile rule). */
@media (max-width:760px){
  .flow-canvas{flex-direction:column;gap:0;}
  .flow-canvas .flow-edges{display:none;}
  .flow-node{min-width:0;}
  .flow-node+.flow-node{margin-top:16px;}
  .flow-node+.flow-node::before{content:'';position:absolute;top:-16px;left:50%;
    margin-left:-1px;width:2px;height:16px;background:var(--border);}
  .flow-skel+.flow-skel{margin-top:16px;}
}
/* A3 motion, zeroed for reduced-motion users (the colors stay, the movement
   stops; the .fn-dot pulse rides .holo-pulse, killed in the kit block). */
@media (prefers-reduced-motion:reduce){
  .flow-edge.live .fe-path{animation:none;}
  .meta.ap-live::before{animation:none;}
  .ap-card.flash{animation:none;}
  button.flow-node{transition:none;}
}

/* ═════════════════════════════════════════════════════════════════════════
   B1 — the full flow canvas (Ad Program view + the owner editor layer),
   built ON the A3 .flow-node/.flow-edge vocabulary above. The .fv-canvas
   variant switches the canvas from A3's flex funnel to lane-snap absolute
   placement (x = column slot, y = measured stack) inside one pan/zoom
   transform layer. State classes promised by A3 land here with their real
   consumers: .selected (focus-panel selection — a USER act, so the outline
   is --ios-blue, never holo, plan §3.1), .locked (read-only affordance),
   .running (the machine executing THIS node right now — holo, ≤2px stroke;
   fed by B3 run rows through the shared renderer's state map).
   ═════════════════════════════════════════════════════════════════════════ */
/* The §3.3 stat row, above the map: the numbers come from the same two
   responses the canvas is drawn from, so nothing here waits on a second
   fetch and nothing here can disagree with the picture below it. */
.fv-stats{flex:none;}
.fv-shell{flex:1;display:flex;min-height:0;}
.flow-wrap{flex:1;position:relative;overflow:hidden;min-width:0;cursor:grab;
  background-image:radial-gradient(var(--sep) 1px,transparent 1px);
  background-size:22px 22px;}
.flow-wrap.panning{cursor:grabbing;}
/* The transform layer: block flow, explicit width/height set from geometry. */
.fv-canvas{display:block;gap:0;padding:0;flex-wrap:nowrap;transform-origin:0 0;
  will-change:transform;}
.fv-canvas .fv-node{position:absolute;width:240px;flex:none;min-width:0;
  transition:top .28s cubic-bezier(.2,.8,.2,1),left .28s cubic-bezier(.2,.8,.2,1);}
.flow-node.selected{outline:2px solid var(--ios-blue);outline-offset:1px;}
.flow-node.running{box-shadow:0 0 0 1.5px var(--holo),var(--shadow-card);}
.flow-node.locked{cursor:default;}
/* Run-playback states (owner executions canvas): a node the run FAILED on
   reads danger and stays clickable through to its step excerpt; a node the
   run never reached fades rather than lying about having run. Neither is
   holo — a finished verdict is not the machine acting. */
.flow-node.failed{border-left-color:var(--danger);
  box-shadow:0 0 0 1px var(--danger),var(--shadow-card);}
.flow-node.pending{opacity:.5;}
/* Readiness pills (§7's "test on save"): a step whose CONFIG cannot run yet.
   Danger, not holo — the machine is not waiting on a decision, the document
   is simply incomplete, and publish will refuse it. */
.fv-badge.needs-setup{background:var(--danger-soft);color:var(--danger);}
.flow-node.needs-setup{border-left-color:var(--danger);}
.fv-issues{font-size:11.5px;color:var(--danger);margin-top:5px;line-height:1.4;}
.fv-lock{font-size:10px;opacity:.7;}
.fv-lane{position:absolute;top:0;}
.fv-lane-t{font-size:11px;font-weight:700;text-transform:uppercase;
  letter-spacing:.07em;color:var(--text-tertiary);}
.fv-lane-n{font-size:10.5px;color:var(--text-tertiary);margin-top:2px;line-height:1.35;}
.fv-lane-inline{position:static;margin:14px 2px 4px;}
.fv-badges{display:flex;flex-wrap:wrap;gap:4px;margin-top:7px;}
.fv-badge{font-size:10.5px;font-weight:600;padding:2px 8px;border-radius:var(--radius-pill);
  background:var(--bg-muted);color:var(--text-secondary);font-variant-numeric:tabular-nums;}
/* Draft-differs-from-published chip (§7 version-lock) — a publish decision
   is blocking the machine, the one holo use this surface owns. */
.fv-badge.holo-stale{background:var(--holo-soft);color:var(--holo);}
.fv-note{font-size:11.5px;color:var(--text-tertiary);margin-top:6px;font-style:italic;line-height:1.4;}
.fv-state-pill{margin-top:7px;display:inline-block;}
.fv-skel{display:inline-block;width:240px;height:110px;margin:44px 0 0 12px;}
.fv-zoom{position:absolute;right:14px;bottom:14px;display:flex;gap:6px;z-index:3;}
.fv-zoom button{width:34px;height:34px;border-radius:10px;border:1px solid var(--border);
  background:var(--bg-surface);color:var(--text-primary);font-size:15px;cursor:pointer;
  box-shadow:var(--shadow-card);}
.fv-zoom button:hover{color:var(--accent-text);border-color:var(--accent-bright);}
.fv-paused-note{color:var(--danger);}

/* Activity banner — real automation_log counts; informational, so brand
   orange, never holo. It is .alert.primary.light.md now (kit §2.8): orange
   tint, orange text, the message-square-warning glyph — the `info` role,
   which this palette spells in the accent because --holo is not available
   for it. What is left below is the three things the component does not own:
   where the banner sits, that its segments wrap, and that they centre on
   each other rather than hanging off the icon's top edge. The radius
   tightens from --radius-lg to the component's --radius and the type goes
   12.5 to 13 with the port; that is what adopting a component costs, and it
   buys the same box as the owner-side insights banner. */
/* .fv-activity.alert, not .fv-activity: .alert.md is a two-class selector
   and would otherwise win the gap this row actually wants (6 down, 12
   across — a wrapping strip needs two axes, and a component that knows
   nothing about wrapping only ships one). Same tie, same fix, in the owner
   insights banner. */
.fv-activity.alert{margin:12px 24px 0;flex-wrap:wrap;gap:6px 12px;align-items:center;}
.fv-activity[hidden]{display:none;}
/* The sentence slot belongs to the component now, so .fva-line is RETIRED
   (test/dashboard-foundation.js asserts it stays retired, the way .aud-group
   and friends are). All it carried was a weight and a colour: the colour is
   inherited from .alert.light.primary — same token, said once — and the
   weight is the one thing the description does not know. The chips keep
   their own colour: they sit ON the tint, not in it. */
.fv-activity .alert-description{font-weight:600;}
.fva-chips{display:flex;flex-wrap:wrap;gap:5px;}
.fva-chip{font-size:11px;padding:2px 9px;border-radius:var(--radius-pill);
  background:var(--bg-surface);color:var(--text-secondary);}

/* Focus panel — docked beside the canvas ≥761px (n8n adoption #7: the
   diagram stays visible), full-screen sheet ≤760px. */
.flow-panel{width:min(380px,42vw);flex:none;border-left:1px solid var(--border);
  background:var(--bg-surface);display:flex;flex-direction:column;min-height:0;}
.flow-panel[hidden]{display:none;}
.fvp-head{flex:none;display:flex;align-items:center;justify-content:space-between;
  gap:10px;padding:12px 16px;border-bottom:1px solid var(--border);}
.fvp-title{font-size:15px;font-weight:600;letter-spacing:-.01em;}
.fvp-body{flex:1;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:12px;}
.fvp-sub{font-size:12.5px;color:var(--text-secondary);}
.fvp-field{display:flex;flex-direction:column;gap:5px;font-size:13px;}
.fvp-field.check{flex-direction:row;align-items:center;gap:8px;}
.fvp-field.check input{width:16px;height:16px;accent-color:var(--accent-bright);}
.fvp-field.ro input,.fvp-field.ro textarea,.fvp-field.ro select{opacity:.65;}
.fvp-lab{font-size:11.5px;font-weight:600;color:var(--text-secondary);
  text-transform:uppercase;letter-spacing:.05em;}
.fvp-help{font-size:11.5px;color:var(--text-tertiary);line-height:1.45;}
.fvp-warn{color:var(--danger);}
.fvp-slots{display:flex;flex-wrap:wrap;gap:5px;}
.fvp-slot{font-size:11px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  padding:3px 8px;border:1px solid var(--border);border-radius:var(--radius-pill);
  background:var(--bg-muted);color:var(--text-secondary);cursor:pointer;}
.fvp-slot:hover{color:var(--accent-text-soft);border-color:var(--accent-bright);}
.fvp-actions{display:flex;gap:8px;}
.fvp-attach{display:flex;flex-direction:column;gap:8px;align-items:flex-start;}
.fvp-foot{flex:none;padding:12px 16px;border-top:1px solid var(--border);
  display:flex;justify-content:flex-end;align-items:center;gap:8px;}
#flowView input[type=text],#flowView input[type=number],#flowView textarea,#flowView select{
  width:100%;padding:9px 11px;border:1px solid var(--border);border-radius:10px;
  font-size:13px;font-family:inherit;background:var(--bg-surface);color:var(--text-primary);}
#flowView input:focus,#flowView textarea:focus,#flowView select:focus{outline:none;border-color:var(--accent-bright);}

/* Owner editor layer (FlowEditor.mount — B3 hosts it in ownerView). */
.fv-editor-bar{display:flex;align-items:center;gap:10px;margin-bottom:12px;}
.fv-editor-name{font-weight:600;font-size:14px;}
.fv-editor-lanes{display:flex;gap:18px;align-items:flex-start;overflow-x:auto;padding-bottom:8px;}
.fv-editor-lane{width:250px;flex:none;display:flex;flex-direction:column;gap:10px;}
.fv-edit-node{position:static;width:100%;cursor:pointer;}
.fv-edit-acts{display:flex;gap:2px;}
.fv-edit-act{border:none;background:transparent;font-size:11px;color:var(--text-secondary);
  cursor:pointer;padding:2px 4px;border-radius:6px;font-family:inherit;}
.fv-edit-act:hover{background:var(--bg-muted);color:var(--accent-text-soft);}
.fv-lane-plus,.fv-edge-plus{border:1.5px dashed var(--border);background:transparent;
  border-radius:10px;padding:8px 10px;font-size:12px;color:var(--text-secondary);
  cursor:pointer;text-align:left;font-family:inherit;}
.fv-lane-plus:hover,.fv-edge-plus:hover{border-color:var(--accent-bright);color:var(--accent-text);}
.fv-edge-plus{font-size:11px;padding:5px 8px;margin-left:14px;}
.fv-palette{width:100%;background:var(--bg-surface);border:1px solid var(--border);
  border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.16);padding:8px;
  display:flex;flex-direction:column;gap:6px;}
.fv-pal-list{max-height:240px;overflow-y:auto;display:flex;flex-direction:column;gap:2px;}
.fv-pal-row{display:flex;gap:8px;align-items:center;border:none;background:transparent;
  border-radius:8px;padding:7px 9px;font-size:13px;cursor:pointer;text-align:left;
  color:var(--text-primary);font-family:inherit;}
.fv-pal-row:hover{background:var(--bg-muted);}
.fv-pal-lab{font-weight:500;}
.fv-editor-config{margin-top:14px;background:var(--bg-surface);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:14px 16px;display:flex;flex-direction:column;
  gap:12px;max-width:560px;}
/* Lane annotations (§5.1): the editable twin of the .fv-lane strip the client
   canvas draws. Two borderless inputs that read as the heading they become —
   an owner writing the lane title should see the lane title, not a form. */
.fv-lane-edit{position:static;display:flex;flex-direction:column;gap:2px;padding-bottom:2px;}
.fv-lane-edit input{border:none;border-bottom:1px dashed transparent;border-radius:0;
  padding:2px 0;background:transparent;}
.fv-lane-edit input:hover{border-bottom-color:var(--border);}
.fv-lane-edit input:focus{outline:none;border-bottom-color:var(--accent-bright);}
.fv-lane-title-in{font-size:11px;font-weight:700;text-transform:uppercase;
  letter-spacing:.06em;color:var(--text-secondary);}
.fv-lane-note-in{font-size:10.5px;color:var(--text-tertiary);}
/* Pin sample data + execute step (§5.1, owner-only). A dry run is a USER act
   and a finished verdict is not the machine acting — zero holo here. */
.fv-dry-block{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--sep);padding-top:12px;}
.fv-dry{border:1px solid var(--border);border-left:3px solid var(--text-tertiary);
  border-radius:10px;padding:9px 11px;background:var(--bg-muted);display:flex;
  flex-direction:column;gap:5px;}
.fv-dry.failed{border-left-color:var(--danger);}
.fv-dry-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.fv-dry-sum{font-size:12.5px;color:var(--text-primary);white-space:pre-wrap;word-break:break-word;}
.fv-dry-out{font:12px/1.5 ui-monospace,Consolas,monospace;color:var(--text-secondary);
  white-space:pre-wrap;word-break:break-word;max-height:180px;overflow-y:auto;}

/* ≤760px: the vertical document flow (plan §5.1 — read-only structure, but
   editable-node taps open the panel as a full-screen sheet). A3's stacked-
   card connector rules above already draw the between-card rules. */
@media (max-width:760px){
  .fv-shell{display:block;overflow-y:auto;}
  .flow-wrap{overflow:visible;cursor:default;background-image:none;}
  .fv-canvas{padding:14px 16px 28px;}
  .fv-canvas .fv-node{position:static;width:100%;}
  .fv-zoom{display:none;}
  .flow-panel{position:fixed;inset:0;width:100vw;z-index:40;border-left:none;}
}
@media (prefers-reduced-motion:reduce){
  .fv-canvas .fv-node{transition:none;}
}

/* ═════════════════════════════════════════════════════════════════════════
   A4 — settingsView. The nineteen stacked sections became a ten-card grid
   over static-ownership sheets, so everything the old single column styled is
   evacuated here, and the new work is only two things: what a summary card
   looks like, and what a section looks like once it lives inside .sheet-body.
   ZERO holo in this whole section by design (appendix-visual-language §4):
   nothing in Settings is the machine acting right now, nor a decision
   blocking it. Health is --success, failure and the emergency stop are
   --danger, everything else is neutral.
   Two appendix §6 dark-mode fixes land at the source here rather than as
   body.dark overrides: .pill.anomalous and .pill.status-pending.
   ═════════════════════════════════════════════════════════════════════════ */

/* Shared column primitive — studioView, agencyView and ownerView still lay
   themselves out with .settings-body, so it stays generic and travels intact.
   Only settingsView opts out of the 640px measure: its content is a grid. */
.settings-body{flex:1;overflow-y:auto;padding:26px 30px;max-width:640px;}
.settings-body h3{font-size:16px;margin:22px 0 10px;}
.settings-body h3:first-child{margin-top:0;}
.settings-hint{font-size:12.5px;color:var(--text-secondary);margin-bottom:10px;line-height:1.55;max-width:620px;}
.muted-inline{font-size:12px;color:var(--text-secondary);margin-left:auto;}

/* ── the card grid ───────────────────────────────────────────────────────── */
.set-stats{padding-bottom:2px;}
.settings-body.set-body{max-width:none;padding:16px 24px 28px;}
.set-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px;
  align-items:start;}
.set-card{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-card);
  padding:15px 17px;display:flex;flex-direction:column;gap:6px;align-items:flex-start;
  min-height:132px;}
.set-card.set-wide{grid-column:1/-1;}
.sc-head{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;}
.sc-t{font-size:14px;font-weight:600;letter-spacing:-.01em;}
.sc-pill{flex:none;}
.sc-pill[hidden]{display:none;}
.sc-v{font-size:13.5px;color:var(--text-primary);line-height:1.45;word-break:break-word;}
.sc-sub{font-size:12px;color:var(--text-secondary);line-height:1.5;}
.sc-edit{margin-top:auto;}
/* Mini-meters (Charts.hbar): the spend-vs-cap bar and the promoted usage bar.
   The remainder segment is transparent so the rounded track shows through. */
.sc-meter{width:100%;max-width:260px;margin-top:2px;}
.sc-meter svg{display:block;width:100%;height:auto;}
/* Graphical, but NOT swept to --accent-bright -- the .lf-uncov reasoning, with
   its own numbers. This fill's job is to be told apart from the track it runs
   in, and .set-meter-rest is transparent, so that comparison is against
   .hbar-track: rgba(120,120,128,.14) composited over the white card. Bright is
   2.54:1 there, under the 3:1 bar; --accent-deep is 3.83:1. The .warn step
   (--accent) is 3.07:1, so sweeping would drop the DEFAULT state below the
   floor its own warning state clears. Contrast wins over the default
   direction. */
.set-meter-used{fill:var(--accent-deep);}
.set-meter-used.warn{fill:var(--accent);}
.set-meter-used.over{fill:var(--danger);}
.set-meter-rest{fill:transparent;}
/* The emergency stop renders on the grid face, never behind a sheet — a
   safety switch that takes two clicks is a broken safety switch. */
.set-stop .sc-v{font-size:14.5px;font-weight:600;}
.set-stop .sc-sub{max-width:640px;}
.set-stop.stopped{box-shadow:0 0 0 2px var(--danger),var(--shadow-card);}
.set-stop.stopped .sc-v{color:var(--danger);}

/* ── inside the sheets: the moved sections, styled where they now live ───── */
#settingsView .sheet-body h3{font-size:15px;font-weight:600;margin:20px 0 10px;letter-spacing:-.01em;}
/* Direct child only: an h3 nested in a gated wrapper (#adminSecuritySection)
   is not the top of the sheet and must keep its separation. */
#settingsView .sheet-body>h3:first-child{margin-top:0;}
#settingsView .sheet-body h4{font-size:13.5px;font-weight:600;margin:18px 0 6px;}
.invite-box{display:flex;align-items:center;gap:10px;background:var(--bg-muted);border:1px solid var(--border);border-radius:10px;padding:12px 14px;}
.invite-box code{font-size:15px;font-weight:700;letter-spacing:.03em;}
.members-list{display:flex;flex-direction:column;gap:6px;margin-top:6px;}
.member-row{display:flex;justify-content:space-between;align-items:center;background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;padding:8px 12px;font-size:13px;gap:10px;}
.member-row .grow{flex:1;min-width:0;}
.member-mail{color:var(--text-secondary);}
.member-with-ai-limit{flex-wrap:wrap;}
.member-ai-limit{flex-basis:100%;border-top:1px solid var(--border);padding-top:8px;}
.member-ai-limit p{margin:0 0 8px;}
.member-ai-controls{display:flex;align-items:end;flex-wrap:wrap;gap:8px;}
.member-ai-controls label{display:grid;gap:4px;font-size:12px;}
.member-ai-controls input{width:180px;max-width:100%;padding:7px 9px;}
.member-ai-controls .settings-hint{flex-basis:100%;}
.member-seen{display:block;color:var(--text-secondary);font-size:11.5px;}
.pill.inactive{background:var(--bg-muted);color:var(--text-secondary);}
/* §6.4 fixed at the source: the old pair was a light-only hex on hex. */
.pill.anomalous{background:var(--danger-soft);color:var(--danger);}
.dlp-list{display:flex;flex-direction:column;gap:6px;}
.dlp-row{display:flex;gap:8px;align-items:center;}
.dlp-row input{flex:1;}
.check-lg{width:18px;height:18px;}
.backup-codes{background:var(--bg-muted);border:1px solid var(--border);border-radius:10px;
  padding:12px 14px;font-size:13px;line-height:1.8;}
.set-audit-scroll{max-height:360px;overflow-y:auto;}
.set-audit-scroll .nowrap{white-space:nowrap;}

/* Sending domain */
.dns-table{border:1px solid var(--border);border-radius:10px;overflow:hidden;margin-top:6px;}
.dns-row{display:grid;grid-template-columns:70px 1fr 1.4fr;gap:10px;padding:8px 11px;font-size:11.5px;border-bottom:1px solid var(--border);align-items:start;}
.dns-row:last-child{border-bottom:none;}
.dns-row.head{background:var(--bg-muted);font-weight:600;text-transform:uppercase;letter-spacing:.05em;font-size:10.5px;}
.dns-row code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;word-break:break-all;font-size:11px;}
.pill.status-verified{background:var(--success-soft);color:var(--success);}
/* §6.5, settings half: fixed at the source (the old fill was a light-only
   cream literal). Its adsView twin .pill.status-paused was fixed the same way
   when A6a rebuilt that view — see the A6a section below. */
.pill.status-pending{background:var(--accent-soft);color:var(--accent);}

/* AI usage meter */
.usage-box{border:1px solid var(--border);border-radius:8px;padding:14px 16px;background:var(--bg-surface);max-width:560px;}
.usage-top{display:flex;justify-content:space-between;align-items:baseline;gap:12px;margin-bottom:9px;font-size:13px;}
.usage-nums{color:var(--text-secondary);font-size:12.5px;}
.usage-bar{height:7px;border-radius:4px;background:var(--bg-muted);overflow:hidden;}
/* Not pinned, but held on the deep step with .set-meter-used above: both are
   meters in this same view, and one bright bar beside one deep bar would read
   as a state difference that does not exist. */
.usage-fill{height:100%;width:0;background:var(--accent-deep);transition:width .3s;}
.usage-fill.warn{background:var(--accent);}
.usage-fill.over{background:var(--danger);}
.usage-note{font-size:11.5px;color:var(--text-secondary);margin-top:9px;line-height:1.5;}
.usage-breakdown{margin-top:11px;display:flex;flex-direction:column;gap:4px;}
.usage-row{display:flex;justify-content:space-between;font-size:12px;color:var(--text-secondary);border-top:1px solid var(--border);padding-top:5px;}

/* Widget embed snippet box */
.code-box{background:var(--bg-muted);border:1px solid var(--border);border-radius:10px;padding:12px 14px;font-size:12px;font-family:monospace;white-space:pre-wrap;word-break:break-all;}

/* Call log + transcript. The old inline background read var(--bg-subtle,…),
   a token that does not exist — so dark mode fell through to a light hex. */
.call-row{justify-content:flex-start;text-align:left;}
.voice-transcript{margin-top:12px;padding:12px;border:1px solid var(--border);
  border-radius:8px;background:var(--bg-muted);}
.vt-title{font-weight:600;margin-bottom:8px;font-size:13px;}
.vt-line{font-size:12.5px;margin-bottom:4px;}

/* ≤760px: the grid collapses to one column and sheets go full-screen (the
   kit's own rule); the stop card is already full-width. */
@media (max-width:760px){
  .set-grid{grid-template-columns:1fr;}
  .settings-body.set-body{padding:14px 16px 24px;}
}
@media (prefers-reduced-motion:reduce){
  .sheet{transition:none;}
  .usage-fill{transition:none;}
}

/* ═════════════════════════════════════════════════════════════════════════
   A4b — the glass account card, and the settings screen re-skinned onto it.

   THIS IS A PORT (2026-08-28). The source was `GlassAccountSettingsCard`: a
   React component on shadcn/ui + Tailwind, animated with framer-motion and
   iconed with lucide-react. None of that stack exists in this repo and none
   of it was added — the frontend is hand-written vanilla ES over one
   stylesheet, served straight out of public/ with NO build step, under a
   strict CSP (no inline <script>, no inline handlers, no innerHTML carrying
   data). Same treatment the owner bento and the pipeline donut already got:
   the DESIGN crosses over, the dependencies do not.

   WHAT CROSSED OVER
     · one 24px-radius outer card: 1px hairline at 60%, a translucent fill
       over backdrop-filter:blur(24px), 32px padding rising to 48px at ≥640px,
       and a top-left→bottom-right wash that fades IN on hover of the card
     · the header row — eyebrow pill (uppercase, .28em tracking, bordered,
       faint fill), a 24px h1 going 30px at ≥640px, one muted line, and a
       rounded-full badge whose border, fill and label all take the accent on
       hover
     · the responsive grid: one column, 2fr/3fr at ≥1024px, 32px gutter
     · sub-panels sharing ONE look — 16px radius, the same 60% hairline, a
       fill one step off the card, its own blur, 24px padding, a small
       semibold heading over a tiny muted sub-line
     · pill buttons: outline (60% hairline, faint fill, muted label that goes
       accent on hover) and exactly ONE filled, which lifts 4px into a long
       soft coloured shadow
     · the entrance — opacity 0→1 and y 18px→0 over 450ms on the original's
       ease [0.16, 1, 0.3, 1]. The component called useReducedMotion() and
       skipped it; the reduced-motion block at the foot of this section is
       that, spelled in CSS
     · the check glyph: lucide `Check` (M20 6 9 17l-5-5) — five path commands
       hand-written into app.html instead of a 400-icon dependency

   WHAT DID NOT, AND WHY
     · Tailwind's arbitrary colour values. Every colour below is a token, so
       the card is right in dark mode with no second rule — and that is not
       taste: this file is hex-scanned and may not hold a single body.dark
       override (test/dashboard-foundation.js enforces both). Translucency
       needs an alpha channel and a per-theme base, which cannot be spelled
       here at all, so the eight --glass-* tokens live in styles.css beside the
       rest of the theme flip and are only READ from here. The accent is
       Loggia's own orange ramp throughout — no new palette, and deliberately
       no holo, which this view has zero of by design (see A4 above).
     · framer-motion. One CSS keyframe does the one thing the card animated.
     · lucide-react, and every other npm dependency.
     · the demo's invented CONTENT — "Workspace Pro", "$24 per user / month",
       "billed yearly", "Unlimited projects", "alex.parker@example.com". Loggia
       is ONE plan at $799/mo flat, month to month, no seats and no percentage
       of ad spend (billing.js: `operator` is the only plan sold), and
       "unlimited" is a word this product does not get to use
       (test/website-build.js). Every field is re-pointed at data the settings
       loaders had already fetched — renderAccount* in app.js, no new endpoint
       and no second fetch for a number a panel already had.
     · a "Cancel subscription" button. There is no cancel endpoint: Stripe's
       own portal is where a subscription is cancelled, and that portal is
       already behind "Payment & invoices". A second button that claimed to
       cancel and silently opened the same portal would be a lie about
       consequence.

   SCOPE. The re-skin half is scoped to #settingsView throughout. .stat-tile,
   .set-card, .sc-*, .invite-box, .usage-box and .member-row are all shared
   vocabulary — agencyView draws three cards of its own with them (A5 below) —
   and re-skinning one screen is not a licence to repaint another.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── the card ────────────────────────────────────────────────────────────── */
.ga-card{position:relative;margin-bottom:14px;padding:32px;
  border:1px solid var(--glass-line);border-radius:24px;
  background:var(--glass-card);
  backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);
  animation:ga-in .45s cubic-bezier(.16,1,.3,1) both;}
/* The wash lives on its own layer so the hover fade is ONE opacity
   transition and never touches the card's own translucent fill. */
.ga-card::before{content:"";position:absolute;inset:0;pointer-events:none;
  border-radius:inherit;opacity:0;transition:opacity .5s ease;
  background-image:linear-gradient(135deg,var(--glass-wash-a) 0%,transparent 48%,var(--glass-wash-b) 100%);}
.ga-card:hover::before{opacity:1;}
.ga-card>*{position:relative;}
@keyframes ga-in{from{opacity:0;transform:translateY(18px);}to{opacity:1;transform:none;}}

.ga-head{display:flex;align-items:flex-start;justify-content:space-between;gap:18px;flex-wrap:wrap;}
.ga-head-main{min-width:0;}
/* .28em of tracking is added AFTER the last glyph too, so the left padding
   carries that much extra or the label sits visibly off-centre in its pill. */
.ga-eyebrow{display:inline-flex;align-items:center;padding:4px 8px 4px 12px;
  border:1px solid var(--glass-line);border-radius:var(--radius-pill);
  background:var(--glass-tint);font-size:10px;font-weight:600;
  text-transform:uppercase;letter-spacing:.28em;color:var(--text-secondary);}
.ga-title{font-size:24px;font-weight:600;letter-spacing:-.022em;
  margin:14px 0 0;color:var(--text-primary);word-break:break-word;}
.ga-desc{margin:7px 0 0;font-size:13.5px;line-height:1.55;
  color:var(--text-secondary);max-width:58ch;}
.ga-badge{flex:none;display:inline-flex;align-items:center;padding:6px 15px;
  border:1px solid var(--glass-line);border-radius:var(--radius-pill);
  background:var(--glass-tint);font-size:12px;font-weight:600;letter-spacing:-.01em;
  color:var(--text-secondary);transition:border-color .18s,background .18s,color .18s;}
.ga-card:hover .ga-badge{border-color:var(--accent-bright);
  background:var(--accent-soft);color:var(--accent-text-soft);}

.ga-grid{display:grid;grid-template-columns:1fr;gap:32px;margin-top:28px;}
.ga-col{display:flex;flex-direction:column;gap:20px;min-width:0;}

/* ── one sub-panel look, four sub-panels ─────────────────────────────────── */
.ga-panel{padding:24px;min-width:0;
  border:1px solid var(--glass-line);border-radius:16px;
  background:var(--glass-panel);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);}
.ga-panel-h{font-size:14px;font-weight:600;letter-spacing:-.01em;color:var(--text-primary);}
.ga-panel-sub{margin-top:3px;font-size:11.5px;line-height:1.55;color:var(--text-secondary);}

/* Security: label/value rows on the same hairline as everything else. */
.ga-kv{display:flex;align-items:baseline;justify-content:space-between;gap:12px;
  margin-top:12px;padding-top:11px;border-top:1px solid var(--glass-line);font-size:13px;}
.ga-k{flex:none;color:var(--text-secondary);}
.ga-v{min-width:0;text-align:right;color:var(--text-primary);word-break:break-word;}

/* The Switch. A real <input type=checkbox> styled into an iOS track — the
   shell's .ms-switch idiom without the :has(), because there the track is a
   separate element and here the input IS the track, so :checked styles it
   directly and no selector-support caveat applies. */
.ga-sw-row{display:flex;align-items:center;gap:12px;margin:0;padding:12px 0;
  border-top:1px solid var(--glass-line);cursor:pointer;}
.ga-sw-row:first-of-type{margin-top:10px;}
.ga-sw-main{flex:1;min-width:0;}
.ga-sw-l{display:block;font-size:13px;font-weight:500;color:var(--text-primary);}
.ga-sw-s{display:block;font-size:11.5px;color:var(--text-secondary);}
.ga-sw{appearance:none;-webkit-appearance:none;position:relative;flex:none;margin:0;
  width:44px;height:26px;border:none;border-radius:var(--radius-pill);
  background:rgba(120,120,128,.28);cursor:pointer;transition:background .18s;}
.ga-sw::after{content:"";position:absolute;top:3px;left:3px;width:20px;height:20px;
  border-radius:50%;background:var(--on-accent);box-shadow:0 1px 3px rgba(0,0,0,.28);
  transition:transform .18s;}
.ga-sw:checked{background:var(--accent-bright);}
.ga-sw:checked::after{transform:translateX(18px);}
.ga-sw:disabled{opacity:.45;cursor:not-allowed;}
.ga-sw:focus-visible{outline:2px solid var(--accent-bright);outline-offset:2px;}
/* Kept in its own rule: an engine without :has() drops the whole selector
   list a rule is declared with, and this one is cosmetic. */
.ga-sw-row:has(.ga-sw:disabled){cursor:default;}

/* Plan: the price line, then the terms with their check chips. */
.ga-plan-line{display:flex;align-items:baseline;justify-content:space-between;
  gap:12px;flex-wrap:wrap;margin-top:16px;}
.ga-plan-name{font-size:17px;font-weight:600;letter-spacing:-.015em;color:var(--text-primary);}
.ga-plan-price{font-size:15px;font-weight:600;color:var(--text-secondary);
  font-variant-numeric:tabular-nums;}
.ga-feats{list-style:none;margin:14px 0 0;padding:0;display:flex;flex-direction:column;gap:9px;}
.ga-feats li{display:flex;align-items:flex-start;gap:9px;font-size:12.5px;
  line-height:1.5;color:var(--text-secondary);}
.ga-check{flex:none;width:18px;height:18px;margin-top:1px;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--accent-soft);color:var(--accent-text-soft);}
.ga-check svg{display:block;}

/* ── pill buttons: many outline, exactly one filled ──────────────────────── */
.ga-acts{display:flex;flex-wrap:wrap;gap:9px;margin-top:18px;}
.ga-btn{display:inline-flex;align-items:center;justify-content:center;
  padding:9px 18px;border:1px solid transparent;border-radius:var(--radius-pill);
  font-family:inherit;font-size:12.5px;font-weight:600;letter-spacing:-.01em;
  cursor:pointer;background:none;
  transition:transform .18s,box-shadow .18s,color .15s,border-color .15s,background .15s;}
.ga-btn.outline{border-color:var(--glass-line);background:var(--glass-tint);color:var(--text-secondary);}
.ga-btn.outline:hover{border-color:var(--accent-bright);color:var(--accent-text);}
.ga-btn.fill{background:var(--accent-deep);color:var(--on-accent);}
.ga-btn.fill:hover{transform:translateY(-4px);box-shadow:0 18px 34px -14px var(--glass-lift);}
.ga-btn:focus-visible{outline:2px solid var(--accent-bright);outline-offset:2px;}
.ga-btn:disabled{opacity:.45;cursor:not-allowed;transform:none;box-shadow:none;}
.ga-btn[hidden]{display:none;}

@media (min-width:640px){
  .ga-card{padding:48px;}
  .ga-title{font-size:30px;}
}
@media (min-width:1024px){
  .ga-grid{grid-template-columns:2fr 3fr;}
}
/* The one deviation from the source's spacing: 32px of padding either side of
   a 375px phone leaves the panels ~40 characters wide inside the shell, which
   is where the copy starts breaking mid-word. */
@media (max-width:480px){
  .ga-card{padding:22px 18px;border-radius:20px;}
  .ga-grid{gap:18px;margin-top:22px;}
}

/* ── the rest of the screen, onto the same vocabulary ─────────────────────
   Three moves, everywhere: the 60% hairline instead of a shadow, a
   translucent fill over a blur, and a pill wherever a control was a control.
   Every selector is #settingsView-scoped (see SCOPE, above). */
#settingsView .set-stats .stat-tile,
#settingsView .set-card{border:1px solid var(--glass-line);background:var(--glass-panel);
  backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);box-shadow:none;}
#settingsView .set-card{padding:22px 24px;gap:7px;transition:border-color .18s;}
#settingsView .set-card:hover{border-color:var(--accent-bright);}
/* The emergency stop is the exception it always was. It does not light up
   orange on hover — an accent hover reads as "pick me", and this is the one
   card on the screen you should touch only on purpose — and its stopped ring
   is restated here because the id-scoped rules above outrank A4's .set-stop. */
#settingsView .set-stop:hover{border-color:var(--glass-line);}
#settingsView .set-stop.stopped{border-color:var(--danger);box-shadow:0 0 0 1px var(--danger);}
#settingsView .sc-edit{padding:7px 15px;border-radius:var(--radius-pill);
  border:1px solid var(--glass-line);background:var(--glass-tint);color:var(--text-secondary);}
#settingsView .sc-edit:hover{border-color:var(--accent-bright);background:var(--glass-tint);color:var(--accent-text);}
#settingsView #allStopBtn{padding:8px 17px;border-radius:var(--radius-pill);}

/* The sheets are frosted rather than see-through: they hold forms, and a form
   you can read the page through is a form you cannot read. */
#settingsView .sheet{background:var(--glass-sheet);border-left:1px solid var(--glass-line);
  backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);}
#settingsView .sheet-head{background:transparent;border-bottom:1px solid var(--glass-line);}
#settingsView .sheet-body .usage-box,
#settingsView .sheet-body .invite-box,
#settingsView .sheet-body .member-row,
#settingsView .sheet-body .code-box,
#settingsView .sheet-body .backup-codes,
#settingsView .sheet-body .dns-table,
#settingsView .sheet-body .voice-transcript,
#settingsView .sheet-body .data-table{border:1px solid var(--glass-line);border-radius:16px;
  background:var(--glass-panel);backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
  box-shadow:none;}
#settingsView .sheet-body .btn{border-radius:var(--radius-pill);}
#settingsView .sheet-body .btn.outline{border-color:var(--glass-line);
  background:var(--glass-tint);color:var(--text-secondary);}
#settingsView .sheet-body .btn.outline:hover{border-color:var(--accent-bright);color:var(--accent-text);}
/* :not([type=checkbox]) because a checkbox DOES paint its background box in
   Blink, and tinting it turns the control into a grey square. */
#settingsView .sheet-body input:not([type=checkbox]),
#settingsView .sheet-body textarea,
#settingsView .sheet-body select{border-radius:12px;border-color:var(--glass-line);
  background:var(--glass-tint);}

/* useReducedMotion(), spelled in CSS: the entrance is skipped ENTIRELY (not
   shortened), and every hover transition the port introduced goes with it. */
@media (prefers-reduced-motion:reduce){
  .ga-card{animation:none;}
  .ga-card::before,.ga-badge,.ga-btn,.ga-sw,.ga-sw::after,
  #settingsView .set-card{transition:none;}
  .ga-btn.fill:hover{transform:none;}
}

/* ═════════════════════════════════════════════════════════════════════════
   A5 — studioView + agencyView. Studio is reordered by consequence: the
   approval queue first (the one thing here that blocks the machine), then
   what exists (tools + keys as .ent-cards with one "…" menu each), then the
   catalog as tiles, then receipts. The two authoring forms — the tool builder
   and the key mint — moved once, at authoring time, into static-ownership
   .sheets, so every studio.js getElementById still resolves.
   Holo budget, both views: exactly ONE instance — the pending-approvals count
   (and its twin on the sidebar Studio badge, styled by the kit's
   .nav-badge.holo). Agency gets none: nothing there is machine-acting or
   decision-blocking, and holo tokens are platform-owned, deliberately outside
   the white-label override path (app.js WHITE_LABEL_TOKENS).
   Agency reuses A4's .set-grid/.set-card/.sc-* card-face vocabulary as-is —
   one component, two consumers, no parallel classes.
   Appendix §6 item 6 lands here at the source: .pill.danger's light-only
   brick-red literal becomes the danger token pair.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── studio chrome, evacuated from the app.html inline block (order preserved,
   .pill.danger retokenized) ─────────────────────────────────────────────── */
.studio-row{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:8px 0;}
.studio-check{display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--text-secondary);white-space:nowrap;}
.studio-check input{width:auto;}
.studio-form{display:flex;flex-direction:column;gap:8px;margin-bottom:10px;}
.studio-config{display:flex;flex-direction:column;gap:6px;}
.studio-config:empty{display:none;}
.studio-config label{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--text-secondary);}
.studio-config label span{flex:none;min-width:180px;}
.studio-config input[type="text"],.studio-config input[type="number"]{flex:1;}
.studio-config input[type="checkbox"]{width:auto;}
.studio-list{display:flex;flex-direction:column;gap:8px;margin-top:10px;}
.studio-card{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-card);padding:12px 15px;font-size:13.5px;}
.studio-card .t{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap;}
.studio-card .name{font-weight:600;}
.studio-card .meta{color:var(--text-secondary);font-size:12px;margin-top:3px;word-break:break-word;}
.studio-card .args{font-family:ui-monospace,Menlo,monospace;font-size:12px;background:var(--bg-muted);border-radius:8px;padding:7px 9px;margin-top:7px;white-space:pre-wrap;word-break:break-word;}
/* Reveal-once key box: shown for one paste, never re-openable. */
.studio-key{background:var(--success-soft);color:var(--success);border-radius:var(--radius);padding:10px 13px;font-family:ui-monospace,Menlo,monospace;font-size:13px;margin:8px 0;word-break:break-all;user-select:all;}
.studio-card .pills{display:flex;gap:6px;align-items:center;flex-wrap:wrap;}
.pill.draft{background:rgba(120,120,128,.14);color:var(--text-secondary);}
.pill.published{background:var(--success-soft);color:var(--success);}
.pill.deprecated{background:rgba(120,120,128,.14);color:var(--text-tertiary);text-decoration:line-through;}
.pill.approval{background:var(--accent-soft);color:var(--accent-text-soft);}
/* §6.6 fixed at the source: the old pair was a light-only hex on rgba. */
.pill.danger{background:var(--danger-soft);color:var(--danger);}
.outreach-edit{width:100%;box-sizing:border-box;font-family:inherit;font-size:13px;border-radius:8px;padding:7px 9px;margin-top:7px;min-height:64px;resize:vertical;}
.outreach-rcpt{font-size:12.5px;margin-top:6px;display:flex;gap:6px;align-items:center;flex-wrap:wrap;}
.studio-batchbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;padding:8px 12px;background:var(--bg-muted);border-radius:var(--radius);}

/* ── the rebuilt studio surface ─────────────────────────────────────────── */
.studio-stats{padding-bottom:2px;}
/* The ONE holo instance in this view: a queue with something in it is a human
   decision blocking the machine. Zero blocks nothing, so JS drops the class. */
.studio-stats .st-n.waiting{color:var(--holo);}
.settings-body.studio-body,.settings-body.agency-body{max-width:none;padding:16px 24px 28px;}
.studio-sec{margin-bottom:26px;}
.studio-sec:last-child{margin-bottom:0;}
.studio-sec-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:6px;}
.studio-sec-head h3{margin:0;font-size:16px;}
.studio-sec-head .btn{margin-left:auto;}
/* The verify readout keeps its place beside the button, not after it. */
.studio-sec-head #studioVerifyOut{margin-bottom:0;}
.studio-scopes{display:flex;flex-direction:column;gap:6px;}
/* Card grid for tools + keys: the kit's .ent-card, one "…" menu per row
   instead of the old wall of buttons. */
.ent-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:12px;align-items:start;margin-top:10px;}
.ent-grid .ent-card{grid-template-columns:1fr auto;}
.ent-grid .ent-card:hover{transform:none;}
.ent-grid .ent-card .meta{grid-column:1/-1;}
.ent-grid .ent-card .pills{grid-column:1/-1;display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-top:2px;}
.ent-grid .ent-card .acts{align-self:start;}
/* Catalog tiles drive the hidden <select> (the #adPlatformInput precedent):
   the tile sets value + dispatches change, so studioTemplateChanged is
   untouched. Installed tiles are disabled, exactly like the old options. */
.cat-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(168px,1fr));gap:8px;margin:6px 0 10px;}
/* The #studioTemplateTiles prefix is an id, so it out-specifies the base
   .plat-tile rules regardless of order. (Those rules moved out of the
   app.html inline block into the A6 section below when adsView was rebuilt;
   the prefix was originally required because the inline block loaded after
   this file, and it stays because these are genuine per-consumer overrides.) */
#studioTemplateTiles .cat-tile{align-items:flex-start;text-align:left;gap:7px;padding:11px 12px;}
#studioTemplateTiles .cat-tile .plat-lbl{font-size:12.5px;line-height:1.3;}
#studioTemplateTiles .cat-tile:disabled{opacity:.62;cursor:default;}
#studioTemplateTiles .cat-tile:disabled:hover{border-color:var(--border);}
.plat-badge.installed{background:var(--success-soft);color:var(--success);}
.plat-badge.setup{background:var(--accent-soft);color:var(--accent-text-soft);}
/* A verdict the chain itself failed reads danger, never neutral. */
.studio-stats .st-n.bad{color:var(--danger);}

/* ── agencyView: three cards + two sheets, table kept. Zero holo. ───────── */
.agency-stats{padding-bottom:2px;}
.agency-body h3{margin-top:26px;}
/* The pre-agency create form keeps the old reading measure — the grid is what
   wanted the full width, not a two-field form. */
#agencySetup{max-width:640px;}
.agency-portal{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12.5px;word-break:break-all;}
.agency-body .data-table{margin-top:10px;}

@media (max-width:760px){
  .settings-body.studio-body,.settings-body.agency-body{padding:14px 16px 24px;}
  .ent-grid{grid-template-columns:1fr;}
}

/* ═════════════════════════════════════════════════════════════════════════
   A6a — adsView, evacuated from the app.html inline block as the view was
   rebuilt. Exactly three holo instances, each named in plan §3.1's allowed
   list: the creative-review panel while a photo waits for the owner's OK, the
   "Generating… N%" line while a GPU is working, and the intraday alert card
   while an alert is live TODAY. The ROAS verdict is deliberately NOT holo —
   green owns healthy, red owns failure, and a verdict is a fact about the
   past. The AdPreview platform mockups stay in the inline block: their hexes
   are the documented light-rendering exemption (appendix §6 item 9).
   ═════════════════════════════════════════════════════════════════════════ */

/* THE NEWER CARD SURFACE, ADOPTED BY ADS (owner, 2026-08-29).
   "Revamp anything that hasn't been updated in Ads to the new style."

   Done as a SCOPED RESTYLE rather than a markup migration, deliberately. The
   panels in this view carry element ids that test/dashboard-ids.js pins and
   that app.js reads by name; swapping every .chart-shell for a .card would
   rewrite roughly three hundred lines of markup in order to change a border.
   So Ads adopts the newer surface where it stands.

   What actually differs between the two surfaces: the newer one draws a
   hairline border and sits on slightly roomier padding. The shadow, the
   radius and every colour are the same tokens either way — which is why this
   is two declarations rather than a theme.

   HONEST LIMIT, worth saying out loud: Ads now LEADS this migration rather
   than completing it. CRM, Settings and the Owner console still wear the
   older shell, so the two surfaces coexist until those follow in their own
   pass. That is a visible half-step taken on purpose, not an oversight. */
#adsView .chart-shell{
  border:1px solid var(--border);
  padding:16px 18px;
}

/* One window control for the whole view, parked at the end of the header. */
.view-header .seg.ads-window{margin-left:auto;}
.ads-stats{padding-bottom:2px;}
/* Sparkline slot inside a tile: quiet, right-aligned, currentColor-driven. */
.stat-tile .st-spark-host{margin-left:auto;color:var(--text-tertiary);display:inline-flex;}
.stat-tile .st-spark-host svg{display:block;}
/* ROAS verdict, from the SERVER's kill/scale thresholds. The [hidden] guard
   is load-bearing: .pill sets display:inline-flex, which would otherwise beat
   the UA's [hidden]{display:none} and show an empty pill with no spend. */
.ads-verdict{font-size:11.5px;font-weight:600;}
.ads-verdict[hidden]{display:none;}
.ads-verdict.good{background:var(--success-soft);color:var(--success);}
.ads-verdict.ok{background:var(--accent-soft);color:var(--accent-text-soft);}
.ads-verdict.bad{background:var(--danger-soft);color:var(--danger);}
.ads-verdict.none{background:rgba(120,120,128,.14);color:var(--text-secondary);}
/* The view's own scroller — without it the panels below the fold are
   unreachable under html,body{overflow:hidden} (the A1 .vis-body lesson). */
.ads-body{flex:1;overflow-y:auto;min-height:0;padding:16px 24px 28px;}

/* ── Goal composer as the hero bar ──────────────────────────────────────── */
.ads-hero{background:var(--bg-surface);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:15px 17px;margin-bottom:14px;}
.ads-hero .ah-row{display:flex;gap:10px;flex-wrap:wrap;align-items:center;}
.ads-hero .ah-row input{flex:1;min-width:220px;margin:0;}
.ads-hero .ah-chips{justify-content:flex-start;margin-top:10px;}
/* The permanent disclaimer paragraph, collapsed to an info affordance. */
.ads-fine{margin-top:10px;}
.ads-fine summary{cursor:pointer;font-size:12px;color:var(--text-tertiary);list-style:none;}
.ads-fine summary::-webkit-details-marker{display:none;}
.ads-fine summary::after{content:' ▸';font-size:9px;}
.ads-fine[open] summary::after{content:' ▾';}
.ads-fine p{font-size:12.5px;color:var(--text-secondary);line-height:1.55;margin:6px 0 0;max-width:620px;}

/* ── Intraday watch (holo while an alert is live today) ─────────────────── */
.ads-alerts.live{box-shadow:inset 3px 0 0 var(--holo),var(--shadow-card);}
.alert-row{display:flex;gap:9px;align-items:baseline;flex-wrap:wrap;
  padding:8px 0;border-top:1px solid var(--sep);font-size:13px;}
.alert-row:first-child{border-top:none;}
.alert-row .ar-name{font-weight:600;}
.alert-row .ar-meta{font-size:12px;color:var(--text-secondary);
  font-variant-numeric:tabular-nums;flex:1 1 220px;}
/* The CARD carries the holo rail (one instance, a decision waiting); the rows
   themselves are danger, because red owns failure — and money going out with
   nothing coming back is a failure, not a status. */
.pill.alert-now{background:var(--danger-soft);color:var(--danger);}

/* ── Creative review — the view's blocking decision, so: holo rail + badge ─ */
.creative-panel{box-shadow:inset 3px 0 0 var(--holo),var(--shadow-card);}
.creative-panel .holo-badge{display:inline-flex;align-items:center;font-size:11px;
  font-weight:600;padding:3px 10px;border-radius:var(--radius-pill);
  background:var(--holo-soft);color:var(--holo);white-space:nowrap;}
.cr-card{display:flex;gap:16px;flex-wrap:wrap;align-items:flex-start;
  padding:12px 0;border-top:1px solid var(--border);}
.cr-side{flex:1;min-width:240px;max-width:420px;}
.cr-title{font-weight:600;margin-bottom:2px;}
.cr-prompt-l{margin-top:8px;font-weight:600;}
.cr-prompt{font-size:12px;color:var(--text-secondary);white-space:pre-wrap;
  word-break:break-word;max-height:120px;overflow:auto;border:1px solid var(--border);
  border-radius:8px;padding:8px;margin-top:4px;}
.cr-prior{margin-top:6px;}
.cr-status{margin-top:8px;}
/* The machine is working right now — the second sanctioned holo use here. */
.cr-status.generating{color:var(--holo);font-weight:600;}
.cr-acts{display:flex;gap:8px;flex-wrap:wrap;margin-top:10px;}

/* ── Campaign rows ──────────────────────────────────────────────────────── */
.ads-sec{margin-bottom:14px;}
.ads-sec-head{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:8px;}
.ads-sec-head h3{margin:0;font-size:16px;}
.ads-sec-head .settings-hint{margin:0;}
.ads-list{margin-top:0;}
.ads-list .ent-card{grid-template-columns:auto 1fr auto;}
.ads-list .ent-card:hover{transform:none;}
.ads-list .ent-card .meta{grid-column:2/-1;}
.ad-card .ad-plat{color:var(--text-secondary);align-self:start;}
.ads-list .empty-note{padding:10px 2px;}

/* ── Brand library tiles ────────────────────────────────────────────────── */
.library-grid{display:flex;flex-wrap:wrap;gap:10px;}
.lib-tile{width:110px;}
.lib-tile img{width:110px;height:80px;object-fit:cover;border-radius:8px;display:block;}
.lib-tile.rejected img{opacity:.4;}
.lib-badge{font-size:11px;margin-top:3px;}
.lib-cap{font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}

/* ── Status pills, moved here from the inline block. Item 5 of the dark
   checklist (.pill.status-paused's hardcoded cream fill) is fixed AT THE
   SOURCE: the token pair flips with the theme, so no body.dark override is
   needed. Shared with the customer-chat rows — hence the kit. ──────────── */
.pill.status-draft{background:var(--bg-muted);color:var(--text-secondary);}
.pill.status-active{background:var(--success-soft);color:var(--success);}
.pill.status-paused{background:var(--accent-soft);color:var(--accent);}
.pill.status-completed{background:var(--accent-soft);color:var(--accent-text-soft);}
.pill.status-ai{background:var(--accent-soft);color:var(--accent-text-soft);}
.pill.status-human{background:var(--success-soft);color:var(--success);}
.pill.status-closed{background:var(--bg-muted);color:var(--text-secondary);}

/* ── The campaign editor: four steps in one sheet ───────────────────────── */
.ad-step{display:none;}
.ad-step.on{display:block;}
/* Back/Next left, Cancel/Save right, in one foot row. */
.sheet.stepped .sheet-foot #adStepNext{margin-right:auto;}
.ad-pair{display:flex;gap:10px;}
.ad-pair>div{flex:1;min-width:0;}
.ad-image-row{display:flex;gap:8px;align-items:center;}
.ad-image-row input[type=file]{flex:1;min-width:0;}
/* Form primitives the editor shares with the rest of the app. */
.field-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:5px;}
.field-head label{margin-bottom:0;}
.ai-hint{font-size:11.5px;color:var(--text-secondary);margin-top:5px;}
/* Two modifiers so the editor carries no inline style attributes at all. */
.ai-hint.inline{display:inline;margin:0;}
.ai-hint.flush{margin-top:0;}

/* Audience chip input — the text field stays the source of truth; these only
   ever rewrite it. Replaces six <details> groups and forty checkboxes. */
.chip-set{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;}
.chip-set[hidden]{display:none;}
.chip-suggest{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:10px;}
.cs-group{flex-basis:100%;font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--text-tertiary);font-weight:600;margin-top:6px;}
.chip{display:inline-flex;align-items:center;gap:5px;font-size:12px;padding:5px 11px;
  border:1px solid var(--border);border-radius:var(--radius-pill);background:var(--bg-surface);
  color:var(--text-primary);font-family:inherit;line-height:1.35;transition:border-color .12s,background .12s;}
.chip:hover{border-color:var(--accent-bright);color:var(--accent-text);}
/* Chosen = orange: adding an audience is a user act, never machine state. */
.chip.on{background:var(--accent-soft);border-color:var(--accent-bright);
  color:var(--accent-text-soft);font-weight:600;}
.chip .cx{font-size:13px;line-height:1;opacity:.7;}

/* Live platform previews (chrome only — the mockup inside stays light). */
.ad-preview{margin-top:12px;border:1px solid var(--border);border-radius:12px;overflow:hidden;}
.ad-preview-head{display:flex;align-items:center;justify-content:space-between;padding:9px 13px;background:var(--bg-muted);border-bottom:1px solid var(--border);}
.ad-preview-cap{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--text-tertiary);}
.ad-preview-plat{font-size:12px;font-weight:600;color:var(--text-secondary);}
.ad-preview-body{padding:16px;background:var(--bg-muted);display:flex;justify-content:center;}

/* Platform picker — brand-icon tiles driving the hidden <select>. Also the
   surface the studio catalog tiles extend (.cat-tile). */
.plat-picker{display:grid;grid-template-columns:repeat(7,1fr);gap:7px;margin-top:4px;}
@media (max-width:560px){.plat-picker{grid-template-columns:repeat(4,1fr);}}
.plat-tile{display:flex;flex-direction:column;align-items:center;gap:6px;padding:11px 4px 8px;background:var(--bg-surface);border:1.5px solid var(--border);border-radius:12px;position:relative;color:var(--text-primary);transition:border-color .12s,background .12s,transform .06s;}
.plat-tile:hover{border-color:var(--text-tertiary);}
.plat-tile:active{transform:scale(.96);}
.plat-tile.active{border-color:var(--accent-bright);background:var(--accent-soft);box-shadow:0 0 0 3px var(--accent-soft);}
.plat-ic{height:22px;display:flex;align-items:center;justify-content:center;}
.plat-ic svg{display:block;}
.plat-lbl{font-size:11px;font-weight:600;letter-spacing:-.01em;line-height:1;}
.plat-badge{font-size:8.5px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;padding:1.5px 5px;border-radius:999px;line-height:1.3;}
.plat-badge.live{background:var(--success-soft);color:var(--success);}
.plat-badge.soon{background:rgba(120,120,128,.16);color:var(--text-secondary);}

/* AI draft review: variants land here for comparison, never straight into the
   saved record. Hidden until a generation returns. */
.ad-review{display:none;margin-top:10px;border:1px solid var(--accent-bright);border-radius:10px;overflow:hidden;}
.ad-review.open{display:block;}
.ad-review-head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 11px;background:var(--accent-soft);flex-wrap:wrap;}
.ad-review-head .ttl{font-size:12.5px;font-weight:600;color:var(--accent-text-soft);}
.ad-review-head .acts{display:flex;gap:6px;}
.ad-review-list{padding:10px 11px;display:flex;flex-direction:column;gap:9px;max-height:340px;overflow-y:auto;}
.ad-variant{border:1px solid var(--border);border-radius:10px;padding:10px 12px;background:var(--bg-surface);}
.ad-variant.selected{border-color:var(--accent-bright);background:var(--accent-soft);}
.ad-variant-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;}
.ad-variant-head .lbl{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--accent);font-weight:600;}
.ad-variant-body{font-size:12.5px;color:var(--text-primary);white-space:pre-wrap;line-height:1.5;font-family:var(--font-sans);margin:0;}

@media (max-width:760px){
  .ads-body{padding:14px 16px 24px;}
  .ads-list .ent-card{grid-template-columns:auto 1fr;}
  .ads-list .ent-card .acts{grid-row:1;justify-self:end;}
}

/* ═════════════════════════════════════════════════════════════════════════
   A6b — crmView, the app's largest surface. Eleven tabs became seven
   segments, Overview became a dashboard, and everything the old view styled
   inline (the jobs tracker, the calendar, the SMS pane, the source bars, the
   canonical .seg itself) evacuated here with it.

   Holo budget: ZERO. Nothing in the CRM is the machine acting on its own or a
   decision blocking it — automations run deterministically on rules the user
   wrote, and the Automations canvas is deliberately static in v1 (no
   crm_workflow_runs list endpoint exists, so a pulse here would be a
   decoration pretending to be telemetry). Red owns overdue, green owns won,
   orange owns everything the user initiates.

   Four appendix §6 dark bugs are fixed AT THE SOURCE in this section rather
   than as body.dark overrides: the .job done-state greens (§6.2), the .cal-ev
   text colours and .cal-legend dots (§6.3), and the phantom --surface-2/--bg
   fallbacks (§6.1), which are gone because the board columns and cards are
   classes on real tokens now. §6.7's .cdb-bulk white-on-white is fixed in
   styles.css, where the rule lives.
   ═════════════════════════════════════════════════════════════════════════ */

/* ── Segmented control, finally out of the inline block. It is the kit's
   canonical control (§2.5) and belongs here beside its own alias — and only
   here does the coarse-pointer 44px override above actually win, which it
   could not while a later inline `.seg button{padding:6px 15px}` outranked
   it at equal specificity. ─────────────────────────────────────────────── */
.seg{display:inline-flex;background:rgba(120,120,128,.12);border-radius:9px;padding:2px;gap:2px;}
.seg button{border:none;background:transparent;padding:6px 15px;border-radius:7px;
  font-size:13px;font-weight:600;color:var(--text-primary);font-family:inherit;cursor:pointer;}
.seg button.on{background:var(--bg-surface);box-shadow:0 1px 3px rgba(0,0,0,.12);}

/* ── Section nav + the view's own scroller ──────────────────────────────── */
.view-header .seg.crm-nav{flex-wrap:wrap;}
.crm-body{flex:1;overflow-y:auto;min-height:0;padding:16px 24px 28px;}
.crm-panel-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:12px;}
.crm-panel-t{font-size:13px;font-weight:600;color:var(--text-secondary);}
.crm-panel-meta{font-size:12.5px;color:var(--text-secondary);}
.crm-panel-head .btn.primary{margin-left:auto;}
.crm-panel-head .seg.crm-window{margin-left:auto;}
.crm-hint-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;}
.crm-hint{font-size:12.5px;color:var(--text-secondary);max-width:600px;}
.crm-hint-row .btn,.crm-hint-row .crm-panel-meta{margin-left:auto;white-space:nowrap;}
.crm-sec-head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin:22px 0 10px;}
.crm-sec-head:first-child{margin-top:0;}
.crm-sec-h{margin:0;font-size:16px;}
.crm-sec-head .btn{margin-left:auto;}
.crm-lbl{display:block;font-size:12px;font-weight:600;color:var(--text-secondary);margin:12px 0 5px;}
.crm-check label{display:flex;align-items:center;gap:6px;font-weight:400;}
/* Form rows shared by the field/action/step builders (the old inline
   cssText). */
.crm-row{display:flex;gap:6px;align-items:center;margin-bottom:6px;}
.crm-row .grow{flex:1;min-width:0;}
.crm-row .narrow{width:96px;flex:none;}
.crm-row .push-right{margin-left:auto;}
.crm-inline-check{font-size:12px;display:flex;align-items:center;gap:3px;white-space:nowrap;}

/* ── Overview ───────────────────────────────────────────────────────────── */
.stat-row.crm-stats{padding:0 0 14px;}
.crm-stats .stat-tile{min-width:150px;}
.crm-st-sub{display:block;font-size:11.5px;color:var(--text-secondary);margin-top:2px;}
.crm-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:14px;align-items:start;}
.crm-grid .chart-shell{margin-bottom:0;}
.crm-grid .crm-wide{grid-column:1/-1;}
/* Pipeline bar: five stage fills, cycled — a palette, not a semantic scale,
   so it reads as one chart rather than five verdicts. */
/* Stage 0 SWEPT to --accent-bright. It is a bare SVG fill with nothing on it
   (segment identity is read off the legend's names and values, never off
   colour alone), and its only meaningful adjacency is the white .chart-shell
   it sits in: 3.00:1. Its neighbour stage-1 was 1.25:1 against it and is now
   1.21:1 -- both far under any bar, unchanged in kind, and exactly why the
   legend carries the text.
   The stronger reason is white-label: --accent-deep is the one token app.js
   repaints per tenant (WHITE_LABEL_TOKENS), so on the deep step ONE member of
   a five-colour palette changes hue per agency and can land on top of a
   sibling -- an amber-branded tenant got stage 0 ~ stage 2. A palette has to
   be deterministic; the four other steps already are. */
.crm-stage-0{fill:var(--accent-bright);}
.crm-stage-1{fill:var(--accent);}
.crm-stage-2{fill:var(--heat-amber);}
.crm-stage-3{fill:var(--heat-yellow);}
.crm-stage-4{fill:var(--ios-blue);}
.crm-legend{margin-top:10px;display:flex;flex-direction:column;gap:5px;}
.crm-legend-row{display:flex;align-items:center;gap:9px;font-size:12.5px;}
.crm-swatch{width:9px;height:9px;border-radius:3px;flex:none;}
/* The swatch is a DOM element, so the same class has to paint background too. */
/* Moves in lockstep with the .crm-stage-0 fill above -- a legend that does not
   match its chart is worse than any brightness question. 9px square on the
   white shell, text beside it and never on it: 3.00:1. */
.crm-swatch.crm-stage-0{background:var(--accent-bright);}
.crm-swatch.crm-stage-1{background:var(--accent);}
.crm-swatch.crm-stage-2{background:var(--heat-amber);}
.crm-swatch.crm-stage-3{background:var(--heat-yellow);}
.crm-swatch.crm-stage-4{background:var(--ios-blue);}
.crm-legend-n{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.crm-legend-v{color:var(--text-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;}
/* Tasks, checkable right where you read them. */
.crm-task{display:flex;align-items:center;gap:9px;padding:7px 0;font-size:13.5px;cursor:pointer;
  border-top:1px solid var(--sep);}
.crm-task:first-child{border-top:none;}
.task-check{width:16px;height:16px;accent-color:var(--accent-bright);flex:none;cursor:pointer;}
.crm-task-t{flex:1;min-width:0;}
.crm-task-due{font-size:11.5px;color:var(--text-tertiary);white-space:nowrap;}
.crm-task-due.overdue{color:var(--danger);font-weight:600;}
/* Agenda strip — appointments and dated tasks, merged and sorted. */
.crm-agenda .agenda-row{display:flex;align-items:center;gap:9px;padding:7px 0;font-size:13px;
  border-top:1px solid var(--sep);}
.crm-agenda .agenda-row:first-child{border-top:none;}
.agenda-what{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.agenda-when{font-size:11.5px;color:var(--text-secondary);white-space:nowrap;}
.crm-contact-row{display:flex;align-items:center;gap:9px;width:100%;text-align:left;padding:8px 0;
  border:none;border-top:1px solid var(--sep);background:transparent;font-family:inherit;
  font-size:13.5px;color:var(--text-primary);cursor:pointer;}
.crm-contact-row:first-child{border-top:none;}
.crm-contact-row:hover .crm-contact-n{color:var(--accent-text);}
.crm-contact-n{font-weight:600;}
.crm-contact-c{color:var(--text-secondary);font-size:12.5px;flex:1;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.crm-contact-t{margin-left:auto;font-size:11px;color:var(--text-tertiary);white-space:nowrap;}

/* ── People: the one contact list ───────────────────────────────────────── */
.cdb-search{flex:1;min-width:200px;padding:7px 11px;border:1px solid var(--border);
  border-radius:8px;background:var(--bg-surface);color:var(--text-primary);
  font-family:inherit;font-size:13px;}
.cdb-search:focus{outline:none;border-color:var(--accent-bright);}
.cdb-list .ent-card{grid-template-columns:auto 1fr auto;}
.cdb-list .ent-card:hover{transform:none;}
.cdb-list .ent-card .meta{grid-column:2/-1;}
.cdb-open{border:none;background:transparent;padding:0;font-family:inherit;text-align:left;
  color:var(--text-primary);cursor:pointer;}
.cdb-open:hover{color:var(--accent-text);}
.cdb-empty{font-size:13px;color:var(--text-secondary);padding:18px 2px;}

/* ── Deals: board columns on real tokens ────────────────────────────────────
   These used to be inline cssText reading var(--surface-2, …) and
   var(--bg, …) — two token names that never existed anywhere in the app — so
   dark mode fell through to the light literals written as their fallbacks and
   the board rendered pale columns on a dark canvas. */
.crm-board{display:flex;gap:12px;overflow-x:auto;padding:4px 2px 14px;align-items:flex-start;}
.crm-col{min-width:240px;max-width:260px;flex:0 0 auto;background:var(--bg-muted);
  border:1px solid var(--border);border-radius:var(--radius);padding:8px;}
.crm-col-head{display:flex;align-items:baseline;gap:7px;margin:2px 4px 9px;}
.crm-col-t{font-weight:600;font-size:12.5px;flex:1;min-width:0;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.crm-col-n{font-size:11.5px;font-weight:700;color:var(--text-secondary);
  font-variant-numeric:tabular-nums;background:rgba(120,120,128,.16);
  border-radius:var(--radius-pill);padding:1px 7px;}
.crm-col-v{font-size:11.5px;color:var(--text-secondary);font-variant-numeric:tabular-nums;}
.crm-col-empty{font-size:11.5px;color:var(--text-tertiary);padding:6px 4px;}
.crm-deal{background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;
  padding:9px 10px;margin-bottom:8px;}
.crm-deal-head{display:flex;align-items:flex-start;gap:6px;}
.crm-deal-t{flex:1;min-width:0;font-weight:600;font-size:13px;}
.crm-deal-m{font-size:11.5px;color:var(--text-secondary);margin:2px 0 7px;}
.crm-deal-stage{width:100%;font-size:12px;padding:5px 8px;border:1px solid var(--border);
  border-radius:8px;background:var(--bg-surface);color:var(--text-primary);font-family:inherit;}
.crm-deal-stage:focus{outline:none;border-color:var(--accent-bright);}

/* ── Jobs tracker, evacuated from the inline block. §6.2 fixed at the source:
   the done-state greens are the --success token in both themes now. ─────── */
.job-list{display:flex;flex-direction:column;gap:10px;}
.job{background:var(--bg-surface);border-radius:16px;box-shadow:var(--shadow-card);padding:15px 17px;
  display:grid;grid-template-columns:1fr auto;gap:4px 16px;align-items:center;transition:transform .12s;}
.job:hover{transform:translateY(-1px);}
.job .jt{font-size:15px;font-weight:600;letter-spacing:-.01em;}
.job .jm{font-size:12.5px;color:var(--text-secondary);margin-top:2px;display:flex;gap:10px;flex-wrap:wrap;align-items:center;}
.job .jm-late{color:var(--danger);font-weight:600;}
.job .jv{font-size:17px;font-weight:700;letter-spacing:-.02em;text-align:right;white-space:nowrap;}
.job .jbar{grid-column:1/-1;display:flex;align-items:center;gap:11px;margin-top:9px;}
.job .jtrack{flex:1;height:8px;border-radius:99px;background:rgba(120,120,128,.14);overflow:hidden;cursor:pointer;position:relative;}
.job .jfill{height:100%;border-radius:99px;background:linear-gradient(90deg,var(--heat-red),var(--heat-orange),var(--heat-yellow));transition:width .35s cubic-bezier(.3,.7,.3,1);}
.job.done .jfill{background:var(--success);}
.job .jpct{font-size:12.5px;font-weight:600;width:40px;text-align:right;font-variant-numeric:tabular-nums;}
.job .jtick{width:26px;height:26px;border-radius:50%;border:1.5px solid var(--border);background:transparent;
  display:flex;align-items:center;justify-content:center;font-size:13px;color:transparent;flex:none;transition:all .18s;cursor:pointer;}
.job.done .jtick{background:var(--success);border-color:var(--success);color:var(--on-accent);}
.job .jstep{background:none;border:none;color:var(--ios-blue);font-size:12px;font-weight:600;padding:2px 4px;cursor:pointer;}
.pill.won{background:var(--success-soft);color:var(--success);}
.pill.lost{background:var(--danger-soft);color:var(--danger);}
.pill.open{background:rgba(10,132,255,.12);color:var(--ios-blue);}

/* ── Schedule: tasks ────────────────────────────────────────────────────── */
#crmTasksList{display:flex;flex-direction:column;gap:8px;}
.task-row{grid-template-columns:auto 1fr auto;}
.task-row:hover{transform:none;}
.task-row .meta{grid-column:2/-1;}
.task-row.done{opacity:.55;}
.task-row.done .t{text-decoration:line-through;}

/* ── Calendar, evacuated. §6.3 fixed at the source: the event text and the
   legend dots are token pairs, matching the .pill.open precedent (the same
   --ios-blue on the same .12 tint), so no body.dark override is needed. ─── */
.cal-bar{display:flex;align-items:center;gap:8px;margin-bottom:14px;flex-wrap:wrap;}
.cal-label{font-weight:600;font-size:15px;margin-left:6px;}
.cal-legend{display:flex;gap:14px;font-size:11.5px;color:var(--text-secondary);align-items:center;margin-left:auto;}
.cal-legend .dot{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:5px;}
.dot.appt{background:var(--ios-blue);}
.dot.task{background:var(--heat-amber);}
.dot.job{background:var(--success);}
.cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:6px;}
.cal-grid .dow{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-tertiary);
  text-align:center;padding-bottom:4px;font-weight:600;}
.cal-cell{background:var(--bg-surface);border-radius:12px;min-height:92px;padding:8px 9px;
  box-shadow:var(--shadow-card);display:flex;flex-direction:column;gap:4px;}
.cal-cell.dim{opacity:.42;}
/* Today stays brand orange — a date is not a machine state (plan §3.1). */
/* Graphical, but NOT swept to --accent-bright. The ring is a 2px spread
   OUTSIDE the cell's box, into the grid's 6px gap -- so where it paints, a
   non-today cell shows --bg-canvas. Today-vs-not-today is therefore orange on
   the grouped-gray canvas: bright is 2.69:1 there, under the 3:1 bar, where
   --accent-deep is 4.05:1. Contrast with .flow-node.on, which IS swept: that
   rail's off-state is the card's own surface, so it clears at 3.00:1. Two
   answers, because the surface behind the off-state differs.
   The numeral below is text and already takes the text step, on the white
   cell -- so nothing here is a text-contrast question. */
.cal-cell.today{box-shadow:0 0 0 2px var(--accent-deep),var(--shadow-card);}
.cal-cell .dnum{font-size:12.5px;font-weight:600;color:var(--text-secondary);}
.cal-cell.today .dnum{color:var(--accent-text);}
.cal-ev{font-size:11px;border-radius:6px;padding:3px 6px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  background:rgba(10,132,255,.12);color:var(--ios-blue);}
.cal-ev.task{background:rgba(255,159,10,.15);color:var(--heat-amber);}
.cal-ev.job{background:rgba(48,209,88,.15);color:var(--success);}
.cal-ev.done{opacity:.5;text-decoration:line-through;}
.cal-ev.cal-more{background:transparent;color:var(--text-tertiary);}
.cal-week .cal-cell{min-height:200px;}

/* ── Comms: templates/sequences as cards, sends as a compact log ─────────── */
#crmTemplatesList.ent-grid .ent-card,#crmSequencesList.ent-grid .ent-card,
#crmBookingPagesList.ent-grid .ent-card,#crmFormsList.ent-grid .ent-card{grid-template-columns:1fr auto;}
.ent-grid .ent-card .acts{align-self:start;}
/* A sequence IS a little flow — the card shows it as one: delay chip, then
   what gets sent, joined by a rail down the left. */
.seq-preview{grid-column:1/-1;margin-top:8px;border-left:2px solid var(--border);padding-left:11px;
  display:flex;flex-direction:column;gap:6px;}
.seq-step{display:flex;gap:8px;align-items:baseline;font-size:12px;position:relative;}
.seq-step::before{content:'';position:absolute;left:-16px;top:5px;width:7px;height:7px;
  border-radius:50%;background:var(--border);}
.seq-wait{flex:none;font-weight:600;color:var(--text-tertiary);text-transform:uppercase;
  letter-spacing:.05em;font-size:10px;}
.seq-subject{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-secondary);}
.seq-step-row{border:1px solid var(--border);border-radius:10px;padding:9px 10px;margin-bottom:8px;}
.seq-step-row .seq-subject-in,.seq-step-row .seq-body{width:100%;margin-bottom:6px;}
.seq-step-row .seq-body{margin-bottom:0;}
.crm-log{display:flex;flex-direction:column;}
.log-row{display:flex;align-items:baseline;gap:10px;padding:7px 2px;font-size:12.5px;
  border-top:1px solid var(--sep);}
.log-row:first-child{border-top:none;}
.log-who{font-weight:600;flex:none;max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.log-what{flex:1;min-width:0;color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.log-when{font-size:11px;color:var(--text-tertiary);white-space:nowrap;}
.crm-link{grid-column:1/-1;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11.5px;
  color:var(--text-tertiary);word-break:break-all;}
/* The two plain (non-grid) card lists: name + menu on one line, meta under. */
#crmAppointmentsList{display:flex;flex-direction:column;gap:8px;}
#crmAppointmentsList .ent-card,.contact-tab .ent-card{grid-template-columns:1fr auto;}
#crmAppointmentsList .ent-card .meta,.contact-tab .ent-card .meta{grid-column:1/-1;}
#crmAppointmentsList .ent-card:hover{transform:none;}
.crm-embed{grid-column:1/-1;margin-top:6px;}
.crm-embed summary{cursor:pointer;font-size:12px;color:var(--text-secondary);list-style:none;}
.crm-embed summary::-webkit-details-marker{display:none;}
.crm-embed summary::after{content:' ▸';font-size:9px;}
.crm-embed[open] summary::after{content:' ▾';}
.crm-embed textarea{width:100%;margin-top:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11.5px;padding:7px 9px;}

/* ── Messages (SMS), evacuated from the inline block ────────────────────── */
.sms-shell{display:flex;gap:14px;height:min(560px,64vh);}
.sms-list{width:270px;flex:none;overflow-y:auto;display:flex;flex-direction:column;gap:7px;}
.sms-thread{background:var(--bg-surface);border-radius:13px;padding:11px 13px;box-shadow:var(--shadow-card);
  cursor:pointer;border:1.5px solid transparent;transition:border-color .15s;}
.sms-thread.on{border-color:var(--ios-blue);}
.sms-thread .n{font-size:14px;font-weight:600;display:flex;align-items:center;gap:6px;}
.sms-thread .p{font-size:12px;color:var(--text-secondary);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
/* Unread stays flat --ios-blue by rule — neither machine-acting nor blocking. */
.sms-thread .unread{width:8px;height:8px;border-radius:50%;background:var(--ios-blue);flex:none;}
.sms-main{flex:1;background:var(--bg-surface);border-radius:16px;box-shadow:var(--shadow-card);
  display:flex;flex-direction:column;min-width:0;overflow:hidden;}
.sms-head{padding:13px 16px;border-bottom:.5px solid var(--sep);display:flex;align-items:baseline;gap:9px;}
.sms-body{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:8px;}
.sms-active{display:none;flex:1;flex-direction:column;min-height:0;}
.bub{max-width:74%;padding:9px 13px;border-radius:18px;font-size:14px;line-height:1.4;word-wrap:break-word;}
.bub.out{align-self:flex-end;background:var(--ios-blue);color:var(--on-accent);border-bottom-right-radius:5px;}
.bub.in{align-self:flex-start;background:rgba(120,120,128,.14);color:var(--text-primary);border-bottom-left-radius:5px;}
.bub .t{display:block;font-size:10.5px;opacity:.65;margin-top:3px;}
.sms-form{display:flex;gap:8px;padding:12px 14px;border-top:.5px solid var(--sep);}
.sms-form input{flex:1;margin:0;}
/* #smsNotice is .alert.warning.light.md now (kit §2.8), and it is the
   whole reason the warning tokens exist. This rule used to invent
   rgba(255,214,10,.12) inside a 1px rgba(255,214,10,.35): a theme-frozen iOS
   amber with no token behind it, no dark-mode step, nothing else on the page
   using that hue, and 2.1:1 on white — so nothing could safely be written
   ON it, which is unfortunate for a box whose entire job is a sentence. All
   that survives is the margin, because a component does not own where it
   sits. */
.notice-inline{margin-bottom:12px;}

/* ── Lead source bars, evacuated. Kept exactly as they were — this is the
   one chart in the CRM that already told the truth (share AND conversion). */
.src-row{display:flex;align-items:center;gap:10px;padding:7px 0;font-size:13px;}
.src-row .nm{width:92px;flex:none;font-weight:500;text-transform:capitalize;}
.src-row .track{flex:1;height:7px;border-radius:99px;background:rgba(120,120,128,.14);overflow:hidden;}
.src-row .fill{height:100%;border-radius:99px;background:linear-gradient(90deg,var(--heat-red),var(--heat-orange),var(--heat-yellow));}
.src-row .pct{width:46px;text-align:right;font-variant-numeric:tabular-nums;color:var(--text-secondary);font-size:12.5px;}
.src-row .conv{width:74px;text-align:right;font-size:11.5px;color:var(--success);}
.src-note{font-size:11.5px;color:var(--text-tertiary);margin-top:8px;}

/* ── Automations: the flow vocabulary at card scale ─────────────────────── */
.crm-wf{background:var(--bg-surface);border-radius:var(--radius-lg);box-shadow:var(--shadow-card);
  padding:13px 15px;margin-bottom:12px;}
.crm-wf-head{display:flex;align-items:center;gap:9px;flex-wrap:wrap;margin-bottom:4px;}
.crm-wf-t{font-size:14px;font-weight:600;}
.crm-wf-n{font-size:11.5px;color:var(--text-tertiary);font-variant-numeric:tabular-nums;}
.crm-wf-head .ovf{margin-left:auto;}
/* FlowStatic's host: the A3 canvas, sized to its own content and scrolled
   sideways rather than panned — there is nothing here to explore. */
.fs-canvas{position:relative;display:block;padding:8px 2px 2px;overflow-x:auto;overflow-y:hidden;}
.fs-canvas .fs-node{position:absolute;text-align:left;}
.fs-canvas .flow-edges{position:absolute;inset:0;pointer-events:none;overflow:visible;}
.fs-node .fn-title{font-size:13px;}
.fs-node.locked{cursor:default;}

/* ── Contact detail sheet ───────────────────────────────────────────────── */
.contact-meta{font-size:13px;color:var(--text-secondary);}
.cm-line{display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
.cm-bits{color:var(--text-secondary);}
.cm-attr{margin-top:8px;font-size:12.5px;display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
.cm-src{color:var(--text-primary);text-transform:capitalize;}
.cm-convos{margin-top:10px;font-size:12.5px;color:var(--text-secondary);}
.contact-acts{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:14px 0;}
.contact-ai{white-space:pre-wrap;background:var(--bg-muted);border:1px solid var(--border);
  border-radius:10px;padding:11px 13px;margin-bottom:12px;font-size:13px;}
.contact-ai[hidden]{display:none;}
.seg.contact-tabs{display:flex;width:100%;margin-bottom:12px;}
.contact-tabs button{flex:1;}
.contact-tab .ent-card{margin-bottom:8px;}
.contact-tab .ent-card:hover{transform:none;}
.act-row{display:flex;gap:12px;align-items:flex-start;padding:8px 0;border-top:1px solid var(--sep);}
.act-row:first-child{border-top:none;}
.act-when{min-width:150px;flex:none;color:var(--text-secondary);font-size:11.5px;}
.act-body{flex:1;min-width:0;font-size:13px;white-space:pre-wrap;word-break:break-word;}

@media (max-width:760px){
  .crm-body{padding:14px 16px 24px;}
  .crm-grid{grid-template-columns:1fr;}
  .sms-shell{flex-direction:column;height:auto;}
  .sms-list{width:100%;max-height:200px;}
  .sms-main{min-height:60vh;}
  .cdb-list .ent-card{grid-template-columns:auto 1fr;}
  /* FlowStatic falls back to the stacked document flow the A3 media query
     already draws connectors for, so the absolute placement must release. */
  .fs-canvas{overflow:visible;}
  .fs-canvas .fs-node{position:static;width:100%;}
}

/* ── Dark-mode parity fixes (appendix-visual-language §6 checklist). Every
   item is now fixed AT THE SOURCE — in the section of this file that owns the
   rule, or in styles.css where the rule lives — so this block records where
   each one went rather than carrying a body.dark override:

   §6.1 phantom --surface-2/--bg — GONE with the A6b board rebuild: the
        columns and cards are .crm-col/.crm-deal on real tokens, and the
        contact sheet's summary box is .contact-ai. No fallback survives, so
        the compatibility shim that used to define those two names is gone
        too — nothing in the app reads them.
   §6.2 .job done-state greens → --success, A6b section above.
   §6.3 .cal-ev text + .cal-legend dots → token pairs, A6b section above.
   §6.4 .pill.anomalous → danger tokens, A4 section above.
   §6.5 .pill.status-pending (A4) and .pill.status-paused (A6a) → --accent-soft.
   §6.6 .pill.danger → danger tokens, A5 section above.
   §6.7 .cdb-bulk white-on-white → the bar is --accent-deep with --on-accent
        text in styles.css, where the rule lives: a selection action bar is a
        user act, and orange is what the user's own actions look like here. */

/* ═════════════════════════════════════════════════════════════════════════
   leadfinderView — the owner's prospecting bench. Nothing here forks a
   primitive: the states picker is the shared .chip/.chip-set, both tables are
   .data-table, and the section chrome is .settings-body, exactly as agencyView
   uses them. Only the bits with no existing rule are written below.

   ZERO holo, deliberately. A search the owner just pressed Run on is progress
   they asked for, not the machine acting on its own — so the live readout is
   --accent, and settled verdicts are success/danger like everywhere else.

   The one colour decision worth naming: an area code with no purchase on
   record reads ORANGE, not red. It is not a failure, it is a gap in a ledger
   the owner keeps by hand — and orange is what "your move" looks like across
   this product. Green is reserved for a row the owner has actually recorded.
   ═════════════════════════════════════════════════════════════════════════ */
.lf-stats{padding-bottom:2px;}
.settings-body.lf-body{max-width:none;padding:16px 24px 28px;}
.lf-body h3{margin-top:26px;}

/* Trade + volume sit on one line; both keep the shared field/label chrome. */
.lf-form{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end;max-width:640px;}
.lf-f-trade{flex:1 1 260px;margin-bottom:0;}
.lf-f-limit{flex:0 0 150px;margin-bottom:0;}
.lf-form+.field{margin-top:16px;}

/* 51 two-letter chips: the shared .chip-set wrap, capped so the picker never
   pushes the Run button below the fold on a short window. */
.lf-states{max-height:172px;overflow-y:auto;padding:2px;}
.lf-states .chip{min-width:44px;justify-content:center;font-variant-numeric:tabular-nums;}
.lf-state-acts{display:flex;align-items:center;gap:10px;margin-top:9px;flex-wrap:wrap;}
.lf-run-row{display:flex;align-items:center;gap:12px;margin-top:18px;flex-wrap:wrap;}

/* Live run readout. Accent-tinted because a run is the owner's own action in
   flight; it settles into the same counts it was showing while it worked. */
.lf-progress{margin-top:14px;background:var(--bg-surface);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:14px 16px;max-width:640px;}
.lf-progress[hidden]{display:none;}
.lf-p-head{display:flex;align-items:center;gap:10px;}
.lf-p-title{font-size:14px;font-weight:600;letter-spacing:-.01em;}
.lf-p-head .pill{margin-left:auto;flex:none;}
.lf-p-counts{display:flex;flex-wrap:wrap;gap:6px 18px;margin-top:10px;font-size:12.5px;
  color:var(--text-secondary);}
.lf-p-c b{font-size:15px;font-weight:700;color:var(--text-primary);
  font-variant-numeric:tabular-nums;margin-right:4px;}
/* #lfProgressError is .alert.destructive.light.sm now (kit §2.8) — the
   same --danger to --danger-text move as .vis-err, for the same reason. The
   [hidden] rule stays because it is how the poller shows and hides it; the
   kit's .alert[hidden] would cover it anyway, and a view-local rule that
   says so out loud costs one line. */
.lf-p-err{margin-top:10px;}
.lf-p-err[hidden]{display:none;}

/* Run status. queued/running are the owner's request still moving (accent);
   done is a fact about the past (green); failed is red. */
.pill.lf-queued{background:var(--bg-muted);color:var(--text-secondary);}
.pill.lf-running{background:var(--accent-soft);color:var(--accent-text-soft);}
.pill.lf-done{background:var(--success-soft);color:var(--success);}
.pill.lf-failed{background:var(--danger-soft);color:var(--danger);}

.lf-body .data-table{margin-top:10px;}
.lf-n{font-variant-numeric:tabular-nums;}
.lf-npa{font-variant-numeric:tabular-nums;font-weight:600;letter-spacing:.01em;}
.lf-when,.lf-states-cell{color:var(--text-secondary);font-size:13px;}
.lf-act{text-align:right;white-space:nowrap;}
.lf-row-err{margin-top:4px;font-size:11.5px;color:var(--danger);line-height:1.45;
  white-space:normal;max-width:220px;}
.lf-row-note{margin-top:3px;font-size:11px;color:var(--text-tertiary);line-height:1.4;}

/* The ledger note is the sentence that has to survive being skimmed. */
.lf-ledger-note{margin-top:-2px;}

/* Registry state. Covered = recorded by the owner; not covered = a gap in
   their own record, which is a prompt, not an error. */
.pill.lf-covered{background:var(--success-soft);color:var(--success);}
.pill.lf-uncovered{background:var(--accent-soft);color:var(--accent-text-soft);}
.lf-areas tbody tr.lf-uncov{background:var(--accent-soft);}
.lf-areas tbody tr.lf-uncov:hover{background:var(--bg-muted);}
/* Graphical, but NOT swept to --accent-bright: this rail is inset into a row
   whose background is --accent-soft, and bright on that tint is 2.57:1 --
   under the 3:1 bar for graphics. The deep step is 3.87:1 there. The ramp
   exists to keep contrast, so contrast wins over the default direction. */
.lf-areas tbody tr.lf-uncov td:first-child{box-shadow:inset 3px 0 0 var(--accent-deep);}

/* In-table controls: the global input/select rule is page-sized (100% wide,
   11px padding) and would blow the row height out. */
#leadfinderView .lf-tier,#leadfinderView .lf-exp{width:auto;padding:6px 9px;font-size:13px;
  border-radius:8px;}
#leadfinderView .lf-tier{min-width:170px;}
#leadfinderView .lf-exp:disabled{opacity:.45;}

@media (max-width:760px){
  .settings-body.lf-body{padding:14px 16px 24px;}
  .lf-f-limit{flex:1 1 120px;}
  /* Eight and seven columns of mostly numbers: these two reflow badly and
     scroll well — the .owner-orgs precedent. */
  .lf-runs,.lf-areas{display:block;overflow-x:auto;white-space:nowrap;}
}

/* ═════════════════════════════════════════════════════════════════════════
   A7 + B5 — ownerView. The one view that crosses tenant boundaries, and the
   only one no customer ever sees. Four things landed here:

     1. the four platform totals, from an innerHTML tile grid to the kit's
        .stat-row / .stat-tile (the shared "open with numbers" pattern);
     2. the pipeline <pre> blocks, from prose in a monospace box to .flow-node
        mini-funnels drawn by FlowStatic from the STRUCT the endpoint now
        composes — with the byte-identical SMS text kept behind a <details>,
        because the text is the record the owner's phone receives;
     3. the worker key table, from a <table> to fleet cards carrying the holo
        liveness dot, queue stat chips, the reveal-once mint box (choreography
        untouched) and the model picker in the reserved Hardware slot;
     4. the Flows tab's .of-* rules, evacuated from app.html's inline block
        where B3 parked them with a note naming this wave.

   HOLO BUDGET — four rules, each on §3.1's allowed list, each meaning "the
   machine is acting right now" or "a decision is blocking it", and each
   bounded so at most ~2 can be on screen together: the worker liveness dot
   (only for an ONLINE key — one PC, one dot), the running-count chip (only
   while nonzero), the Flows stale chip (a publish decision, one per stale
   flow), and the pass-telemetry strip's worker-offline-with-queued-work
   segment (ONE segment — its evidence line stays neutral). The dot and that
   segment are mutually exclusive by construction: one says the worker is
   here, the other that it is not. Deliberately NOT holo, though it looks the
   part: the "Waiting on Studio worker" run pill — see .pill.of-parked below.
   Everything healthy is --success, everything broken is --danger, everything
   else is neutral.
   ═════════════════════════════════════════════════════════════════════════ */

.owner-stats{padding-bottom:2px;}
.owner-body{flex:1;overflow-y:auto;max-width:1000px;}

/* ── B6 category boxes — the console's seven <details> ──────────────────────
   Markup:
   details.oc-cat[open] > summary > span.oc-t + span.oc-n
                        + div.oc-body

   The seven <h3> headings this replaces are gone, and with them the
   .owner-body h3 rules; there is no other <h3> in this view.

   Chrome stays deliberately quiet. Collapsed, the console is a table of
   contents the owner reads top to bottom, so a summary row is a hairline, a
   name and a number — not a card, which would make seven closed boxes look
   like seven panels and undo the consolidation. The marker is a chevron drawn
   from two borders rather than the native ▸: the platform glyph is a
   different shape on every OS and on one of them a filled triangle the size
   of a bullet, and this file already draws it by hand twice (.pipe-raw,
   .wk-hw details). Rotation, not two glyphs, so open and closed are one
   element that turns. */
.oc-cat{border-top:1px solid var(--sep);}
.oc-cat:first-of-type{border-top:none;}
.oc-cat>summary{display:flex;align-items:center;gap:10px;cursor:pointer;list-style:none;
  padding:13px 2px;font-size:14.5px;font-weight:600;letter-spacing:-.01em;
  color:var(--text-primary);}
.oc-cat>summary::-webkit-details-marker{display:none;}
.oc-cat>summary:hover{color:var(--accent-text);}
.oc-cat>summary::before{content:"";flex:none;width:6px;height:6px;margin-left:3px;
  border-right:1.5px solid var(--text-tertiary);border-bottom:1.5px solid var(--text-tertiary);
  transform:rotate(-45deg);transition:transform .16s;}
.oc-cat[open]>summary::before{transform:rotate(45deg);}
/* --text-primary, not the --text-secondary every other metadata line in this
   file takes. Measured: secondary lands at 3.46:1 on --bg-canvas, which is the
   house register for copy you glance past — and this is the opposite of that.
   It is the ONE thing a fully collapsed console shows, the whole reason the
   boxes are allowed to be closed. Smaller and lighter than the category name
   beside it, so it still reads as data rather than a second heading. */
.oc-n{margin-left:auto;font-size:12px;font-weight:600;color:var(--text-primary);
  font-variant-numeric:tabular-nums;}
.oc-body{padding:2px 0 18px;}
.oc-seg{margin-bottom:10px;}

/* The two fleet panels over the pipeline list. .dash-split's proportions on
   purpose — a wide chart beside a ring is exactly the pair the home dashboard
   puts in that shape, and this console is being spoken to in that vocabulary. */
.oc-split{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(0,1fr);
  gap:12px;margin-bottom:12px;}
@media (max-width:1000px){.oc-split{grid-template-columns:minmax(0,1fr);}}
.oc-split .chart-shell{margin:0;}
.oc-ringbody{display:flex;flex-direction:column;justify-content:center;flex:1;}

/* Orgs table: the plan-select contract is untouched; the first column is a
   health glyph derived from lastActivityAt recency — green inside a week,
   neutral inside a month, danger beyond (or never).

   B6 took ten columns to six. Agent, both <select>s and the file and message
   counts moved into the row's own <details>, which is the only place in this
   table that may wrap: the ≤760px rule below puts the whole thing on
   white-space:nowrap so it scrolls sideways rather than reflowing, and a
   disclosure full of label/value pairs has to opt back out. */
.owner-orgs{margin-bottom:4px;}
/* Top, not the .data-table default of middle. An open disclosure makes its row
   five lines tall, and centring floats the tier pill, the member count and the
   menu into the middle of a gap — beside nothing. Aligned to the top they stay
   beside the org NAME, which is the row they belong to. Closed rows are one
   line tall in every cell, so this changes nothing for them. */
.owner-orgs tbody td{vertical-align:top;}
.owner-orgs .plan-select{width:auto;padding:5px 8px;font-size:12.5px;}
.owner-orgs td.oo-health{width:26px;padding-right:0;}
.oo-det>summary{display:flex;align-items:center;gap:7px;cursor:pointer;list-style:none;
  flex-wrap:wrap;min-width:0;}
.oo-det>summary::-webkit-details-marker{display:none;}
.oo-det>summary::after{content:'▸';font-size:10px;color:var(--text-secondary);}
.oo-det[open]>summary::after{content:'▾';}
.oo-name{font-weight:600;}
.oo-det>summary:hover .oo-name{color:var(--accent-text);}
.oo-more{display:flex;flex-direction:column;gap:6px;margin-top:9px;padding:9px 0 2px;
  border-top:1px dashed var(--sep);white-space:normal;}
.oo-f{display:flex;align-items:center;gap:10px;min-height:26px;}
.oo-k{flex:none;width:74px;font-size:11px;font-weight:600;text-transform:uppercase;
  letter-spacing:.06em;color:var(--text-secondary);}
.oo-v{font-size:13px;color:var(--text-primary);font-variant-numeric:tabular-nums;}
/* Plan column: the entitlement as a WORD. Green means somebody is entitled —
   paid or comped — and neutral means draft, the unpaid floor where every
   gated feature is refused. No holo: an entitlement is a settled state of the
   account, neither the machine acting nor a decision blocking it (§3.1). */
.oo-plan{white-space:nowrap;}
.oo-plan .pill+.pill{margin-left:6px;}
.pill.oo-ent{background:var(--bg-muted);color:var(--text-secondary);}
/* --success-text, not --success. The neighbouring green pills (.of-chip.on,
   .pill.status-verified) predate the -text step and measure 4.40:1 on this
   tint; --success-text measures 6.0:1 in light and is the step that FLIPS for
   dark mode, where --success on a 20%-alpha green over near-black is the same
   3-point-something failure .owner-del-err was moved off. A new rule takes the
   correct step rather than copying the old one for symmetry. */
.pill.oo-ent.paid{background:var(--success-soft);color:var(--success-text);}
.pill.oo-comped{background:var(--accent-soft);color:var(--accent-text-soft);}
.oo-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--border);}
.oo-dot.warm{background:var(--success);}
.oo-dot.cool{background:var(--text-tertiary);}
.oo-dot.cold{background:var(--danger);}
/* Lifecycle (org management). A deactivated workspace reads DANGER, not
   neutral: every other number on that row — members, files, messages — is
   still there and none of it is reachable, so the row must not look ordinary.
   No holo: a suspension is a settled state, neither the machine acting nor a
   decision blocking it (§3.1). */
/* --danger-text, not --danger, and the reason is the one .owner-del-err
   already gives below: --danger is a FILL step that does not flip, and on this
   tint in dark mode it measures 3.72:1 — under AA at 12px/600. B6 made that
   matter more, not less: the pill used to sit in a wide name COLUMN and now
   sits in the row's summary right beside the workspace name, which is the
   first thing read. Measured on --bg-surface: --danger-text is 5.43:1 light
   and 6.39:1 dark. */
.pill.oo-off{background:var(--danger-soft);color:var(--danger-text);margin-left:7px;}
.owner-orgs td.oo-acts{width:44px;padding-left:0;text-align:right;}
/* A blocked action is GREYED and says why, never hidden — an owner who cannot
   find Delete assumes the app is broken; one who sees it disabled under
   "cancel the subscription first" knows the next move. The note text is the
   server's blockedReason, printed verbatim. */
.ovf-item[disabled]{opacity:.45;cursor:not-allowed;}
.ovf-item[disabled]:hover{background:transparent;}
.ovf-note{display:block;padding:0 12px 8px;font-size:11.5px;line-height:1.4;
  color:var(--text-tertiary);max-width:230px;white-space:normal;}
/* The type-the-name delete confirm. The consequence statement is
   .alert.destructive.outline.md now (kit §2.8): it used to be grey body
   text, which is the one register a sentence listing everything about to be
   destroyed should not be in. Outline rather than light on purpose — it sits
   inside a white .modal that already carries a red Delete button, and a
   second filled red panel would fight it for the eye. Only the margin is
   left here.
   .owner-del-err stays coloured TEXT (a one-line validation message, not a
   box) and moves to --danger-text, the step that flips for dark mode.
   min-height:1px is load-bearing: it reserves the line so the modal does not
   jump when the first failed attempt fills it. */
.owner-del-warn{margin:0 0 12px;}
.owner-del-err{color:var(--danger-text);font-size:12.5px;line-height:1.45;min-height:1px;
  white-space:pre-wrap;word-break:break-word;}

/* ── Pipeline: one ROW per workspace, funnel and prose on demand ─────────
   Markup: details.pipe-card > summary.pipe-head
                                 > span.pipe-org + span.pipe-state
                                 + span.pipe-nums
                             + div.pipe-canvas + div.fv-note + details.pipe-raw

   B6 turned the card into a disclosure. It was a tall funnel per workspace
   stacked down the view — the wall this wave was asked to take down — and the
   four numbers it exists to report fit on one line. The funnel is still the
   same FlowStatic render, one click in. */
.pipe-card{background:var(--bg-surface);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:14px 16px;margin-bottom:12px;}
.pipe-card>summary{list-style:none;cursor:pointer;}
.pipe-card>summary::-webkit-details-marker{display:none;}
.pipe-head{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:2px;}
.pipe-org{font-size:14.5px;font-weight:600;letter-spacing:-.01em;}
.pipe-card>summary:hover .pipe-org{color:var(--accent-text);}
.pipe-state{display:flex;gap:6px;flex-wrap:wrap;margin-left:auto;}
/* Own line, always: the chips wrap unpredictably and the four numbers are the
   thing a closed row exists to say, so they must not end up beside them. */
.pipe-nums{flex-basis:100%;font-size:12.5px;color:var(--text-secondary);
  font-variant-numeric:tabular-nums;}
.pipe-canvas{overflow-x:auto;padding:10px 0 2px;}
/* The mini funnel is FlowStatic at card scale. FlowStatic puts the card's
   headline in .fn-title, and here that headline IS the number — so this is
   where the stat-number treatment lands (the A3 .fn-n slot is unused by the
   static renderer, deliberately: one card builder, one title slot). */
.pipe-canvas .flow-node{padding:10px 12px;}
.pipe-canvas .fn-title{font-size:20px;font-weight:700;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;}
.pipe-canvas .fn-kind{font-size:10px;}
.pipe-canvas .fn-sub{font-size:11.5px;}
.pipe-raw{margin-top:6px;}
.pipe-raw>summary{cursor:pointer;font-size:12px;font-weight:600;color:var(--text-secondary);
  list-style:none;padding:3px 0;}
.pipe-raw>summary::-webkit-details-marker{display:none;}
.pipe-raw>summary::after{content:' ▸';font-size:10px;}
.pipe-raw[open]>summary::after{content:' ▾';}
.pipe-raw>summary:hover{color:var(--accent-text);}
.pipe-raw pre{margin:6px 0 0;font:12.5px/1.5 ui-monospace,Consolas,monospace;
  white-space:pre-wrap;word-break:break-word;color:var(--text-secondary);
  background:var(--bg-muted);border-radius:var(--radius);padding:10px 12px;}

/* ── Studio workers: queue chips + fleet cards ──────────────────────────── */
.wk-queue{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;}
.wk-chip{display:inline-flex;align-items:baseline;gap:6px;font-size:12px;
  background:var(--bg-muted);border-radius:var(--radius-pill);padding:4px 11px;
  color:var(--text-secondary);}
.wk-chip b{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--text-primary);}
/* Work in flight IS the machine acting — the one chip allowed holo, and only
   while the number is nonzero (JS adds .live). */
.wk-chip.live{background:var(--holo-soft);color:var(--holo);}
.wk-chip.live b{color:var(--holo);}
.wk-chip.bad{background:var(--danger-soft);color:var(--danger);}
.wk-chip.bad b{color:var(--danger);}

.wk-card{background:var(--bg-surface);border-radius:var(--radius-lg);
  box-shadow:var(--shadow-card);padding:14px 16px;margin-bottom:12px;}
.wk-head{display:flex;align-items:center;gap:9px;flex-wrap:wrap;}
.wk-name{font-size:14px;font-weight:600;letter-spacing:-.01em;}
.wk-hint{font:12px/1 ui-monospace,Consolas,monospace;color:var(--text-tertiary);}
.wk-acts{margin-left:auto;display:flex;gap:6px;align-items:center;}
/* Liveness: holo + pulse ONLY while online. Dark and never-seen stay grey —
   a machine that is off must never look like a machine that is working. */
.wk-dot{width:9px;height:9px;border-radius:50%;background:var(--text-tertiary);flex:none;}
.wk-dot.on{background:var(--holo);}
.wk-meta{font-size:12px;color:var(--text-secondary);margin-top:5px;
  display:flex;gap:6px 14px;flex-wrap:wrap;font-variant-numeric:tabular-nums;}

/* Hardware slot — the model picker (B5), owner-only by the route that feeds it. */
.wk-hw{margin-top:10px;border-top:1px solid var(--sep);padding-top:9px;}
.wk-hw-head{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;font-size:12.5px;
  color:var(--text-secondary);}
.wk-gpu{font-weight:600;color:var(--text-primary);}
.wk-models{display:flex;flex-direction:column;gap:0;margin-top:6px;}
.wk-model{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;font-size:12.5px;
  padding:5px 0;border-top:1px dashed var(--sep);}
.wk-model:first-child{border-top:none;}
.wk-tag{font:12.5px/1.4 ui-monospace,Consolas,monospace;color:var(--text-primary);}
.wk-note{color:var(--text-tertiary);font-size:12px;flex:1 1 220px;}
/* Statuses are facts, not machine state: installed is success, too_big is
   neutral-quiet, licence_review wears the accent because it is a READ the
   owner owes, never a block. No holo — a static recommendation is neither
   live nor blocking (plan §3.1 overrules the appendix here). */
.pill.mp-installed{background:var(--success-soft);color:var(--success);}
.pill.mp-fits{background:rgba(120,120,128,.14);color:var(--text-secondary);}
.pill.mp-too_big{background:rgba(120,120,128,.14);color:var(--text-tertiary);}
.pill.mp-licence_review{background:var(--accent-soft);color:var(--accent-text-soft);}
.pill.mp-unknown{background:rgba(120,120,128,.14);color:var(--text-tertiary);}
/* The reviewed-but-not-installed list is one disclosure away, styled like the
   view's other collapses rather than a raw browser triangle. */
.wk-hw details>summary{cursor:pointer;font-size:12px;font-weight:600;
  color:var(--text-secondary);list-style:none;padding:7px 0 2px;}
.wk-hw details>summary::-webkit-details-marker{display:none;}
.wk-hw details>summary::after{content:' ▸';font-size:10px;}
.wk-hw details[open]>summary::after{content:' ▾';}
.wk-hw details>summary:hover{color:var(--accent-text);}
.wk-mint{display:flex;gap:8px;margin-top:10px;align-items:center;flex-wrap:wrap;}
.wk-mint input{max-width:260px;}
/* Reveal-once key box: shown for one paste, never re-openable (§10.2). */
.wk-reveal code{display:block;padding:10px;margin-top:6px;user-select:all;word-break:break-all;
  background:var(--bg-muted);border-radius:var(--radius);
  font:12.5px/1.5 ui-monospace,Consolas,monospace;}
.wk-reveal .empty-note{margin-top:10px;}
.wk-reveal .btn{margin-top:6px;}
.wk-reveal .btn+.btn{margin-left:6px;}
.wk-dlq{margin-top:14px;}
.wk-dlq-row{display:flex;gap:8px;align-items:center;font-size:12.5px;
  color:var(--text-secondary);padding:5px 0;border-top:1px dashed var(--sep);}
.wk-dlq-row span{flex:1 1 240px;word-break:break-word;}

/* ── Owner Flows tab (B3's rules, evacuated verbatim from the inline block).
   The holo pair marks exactly two §3.1-sanctioned states: the stale chip (a
   publish decision is blocking the machine) and the worker-offline-with-
   queued-jobs strip segment. ─────────────────────────────────────────────── */
.of-strip{display:flex;flex-wrap:wrap;gap:6px 14px;align-items:center;font-size:12.5px;color:var(--text-secondary);background:var(--bg-muted);border:1px solid var(--border);border-radius:10px;padding:8px 12px;margin:4px 0 2px;}
.of-strip .alert{color:var(--holo);font-weight:600;}
.of-card{border:1px solid var(--border);border-radius:var(--radius-lg);background:var(--bg-surface);padding:12px 14px;margin-top:10px;}
.of-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.of-name{font-weight:650;font-size:14px;color:var(--text-primary);}
.of-chip{display:inline-flex;align-items:center;font-size:11.5px;font-weight:600;padding:2px 9px;border-radius:var(--radius-pill);background:rgba(120,120,128,.14);color:var(--text-secondary);}
.of-chip.on{background:var(--success-soft);color:var(--success);}
.of-chip.stale{background:var(--holo-soft);color:var(--holo);}
.of-next{font-size:12.5px;color:var(--text-tertiary);margin-top:4px;}
.of-actions{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px;align-items:center;}
.of-runs{margin-top:10px;border-top:1px solid var(--sep);padding-top:8px;}
.of-run{border:1px solid var(--border);border-radius:10px;padding:8px 10px;margin-top:8px;cursor:pointer;}
.of-run-line{display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:12.5px;color:var(--text-secondary);}
.pill.of-done{background:var(--success-soft);color:var(--success);}
.pill.of-failed{background:var(--danger-soft);color:var(--danger);}
/* "Waiting on Studio worker" reads NEUTRAL, deliberately. A job queued for a
   PC that may be switched off is neither the machine acting nor a decision
   blocking it (plan §3.1) — app.js's playback canvas already reasons exactly
   that for the identical step pill. It also rendered once per parked RUN ROW,
   and the runs list holds up to 50, which by itself broke the "~2 holo
   instances visible per view" budget. The single aggregate holo signal for
   waiting work lives in the pass-telemetry strip, where it is one segment. */
.pill.of-neutral,.pill.of-parked{background:rgba(120,120,128,.14);color:var(--text-secondary);}
.of-hl{display:inline-flex;font-size:11px;background:var(--bg-muted);border:1px solid var(--border);border-radius:var(--radius-pill);padding:1px 8px;color:var(--text-secondary);}
.of-note{font-size:12px;color:var(--text-tertiary);margin-top:4px;}
.of-err{color:var(--danger);font-size:12px;margin-top:4px;white-space:pre-wrap;word-break:break-word;}
.of-steps{margin-top:8px;font-size:12.5px;display:flex;flex-direction:column;gap:0;}
.of-step{display:flex;gap:8px;padding:5px 0;border-top:1px dashed var(--sep);align-items:baseline;flex-wrap:wrap;}
.of-step .pill{font-size:11px;flex:none;}
.of-step-out{color:var(--text-tertiary);white-space:pre-wrap;word-break:break-word;flex:1 1 240px;}
/* Canvas click-through landing flash — orange, because a user click sent us
   here (the .ap-card.flash precedent). */
.of-step.flash{animation:apCardFlash .9s ease-out 1;}
.of-editor{margin-top:12px;border-top:1px solid var(--sep);padding-top:12px;}
/* Per-run canvas playback: the graph the run walked, above its step log. */
.of-playback{margin-top:8px;overflow-x:auto;}
/* Insights banner (§7, owner half): 7d runs, failure rate, time saved.
   Neutral — a weekly summary is neither the machine acting nor a decision
   blocking it, so the only coloured segment is a nonzero failure rate. */
/* It is .alert.primary.light.sm now (kit §2.8) — the same box as the
   client half of this pair, the Ad Program activity banner, which is the
   whole argument for a component: two surfaces that always meant the same
   thing stop drifting apart. The body text goes with it, from
   --text-secondary to --accent-text-soft (4.84:1 on --accent-soft), which is
   what "informational is orange" has said everywhere else in this product
   for a year. No icon slot is filled: the segments are direct children the
   A7 DOM test reads positionally, and a weekly count is a readout, not an
   alarm.
   Three things stay. The strip layout; the numerals; and the failure
   segment, whose selector gains .alert on the ROOT so it keeps out-ranking
   the kit's three-class .alert.light.primary. Its colour moves to
   --danger-text: --danger is a fill step that does not flip, and on this
   tint in dark mode it was the 3.2:1 failure mobile.css documents. */
.of-insights.alert{display:flex;flex-wrap:wrap;gap:6px 14px;align-items:center;margin:4px 0 8px;}
.of-insights.alert .alert{color:var(--danger-text);font-weight:600;}
.of-ins-n{font-weight:650;font-variant-numeric:tabular-nums;}
/* The toolbar slot owns the layout now (.alert-toolbar: pinned right, laid
   out in a row, flex:none). All this adds is the register it speaks in. */
.of-ins-edit{font-size:11.5px;color:var(--text-tertiary);}
.of-ins-edit input{width:66px;padding:4px 7px;border:1px solid var(--border);border-radius:8px;
  font-family:inherit;font-size:12px;background:var(--bg-surface);color:var(--text-primary);}

@media (max-width:760px){
  .owner-body{padding:14px 16px 24px;}
  /* The orgs table is the one place here that genuinely needs to scroll
     sideways rather than reflow. Six columns now rather than ten, but a name,
     a tier, two numbers and a menu still do not fit a phone — and a row whose
     disclosure is open is wider than the row itself. */
  .owner-orgs{display:block;overflow-x:auto;white-space:nowrap;}
}
/* The chevron is the only thing in the B6 chrome that moves. */
@media (prefers-reduced-motion:reduce){
  .oc-cat>summary::before{transition-duration:.01ms;}
}
/* ── 2.9 Donut + legend — the kit's round categorical idiom ────────────────
   Charts.pie draws the ring; these classes give it the two things a ring
   needs to be readable: a legend whose rows cross-highlight with the slices,
   and a readout parked in the hole that switches from the total to whichever
   slice is picked. Generic on purpose — the owner console's plan mix and the
   home dashboard's pipeline both wear it.

   THE HEATWAVE RAMP. Five ordered steps of the existing heat tokens, coolest
   to hottest. Sequential rather than categorical because both series wearing
   it are ORDERED — plan tiers and pipeline stages — so "further along is
   hotter" is a real reading of the picture rather than decoration. Marks read
   --bento-ink, so one variable colours a swatch, a slice and a dot alike. */
.heat-1{--bento-ink:var(--heat-yellow);}
.heat-2{--bento-ink:var(--heat-amber);}
.heat-3{--bento-ink:var(--accent-bright);}
.heat-4{--bento-ink:var(--heat-orange);}
.heat-5{--bento-ink:var(--heat-red);}

.pie-wrap{position:relative;display:flex;align-items:center;justify-content:center;
  padding:8px 0 2px;min-width:0;}
.pie-wrap .pie{display:block;width:100%;height:auto;max-width:230px;}
/* stackedBars' segments consume --bento-ink the same way .pie-slice does, so a
   series wears its .heat-N step and nothing here names a colour. The fallback
   matters: a caller that forgets the ramp gets a readable grey bar, not an
   invisible one. */
.sb-seg{fill:var(--bento-ink,var(--text-tertiary));}
.pie-slice{fill:var(--bento-ink,var(--text-tertiary));stroke:var(--bg-surface);stroke-width:2;
  stroke-linejoin:round;transition:opacity .16s,transform .16s;
  transform-box:fill-box;transform-origin:center;}
/* Dim the rest while one is picked. The original reached for a grayscale
   filter; a filter over a token colour is a colour nobody chose, so this
   fades instead — same read, palette intact. */
.pie-wrap[data-hot] .pie-slice{opacity:.26;}
.pie-wrap[data-hot] .pie-slice.hot{opacity:1;transform:scale(1.04);}

.pie-readout{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;pointer-events:none;text-align:center;
  padding:0 18%;}
.pie-readout .pr-n{font-size:26px;font-weight:700;line-height:1;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;color:var(--text-primary);}
.pie-readout .pr-l{font-size:10px;font-weight:600;text-transform:uppercase;
  letter-spacing:.12em;color:var(--text-secondary);margin-top:5px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%;}
.pie-readout .pr-p{font-size:12px;font-weight:600;color:var(--text-secondary);
  font-variant-numeric:tabular-nums;margin-top:3px;}

.chart-legend{display:flex;flex-direction:column;gap:1px;margin-top:8px;}
.cl-row{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:5px 7px;border:2px solid transparent;border-radius:7px;font-size:12px;
  transition:border-color .14s,background .14s,transform .14s;}
.cl-row:hover,.cl-row.hot{border-color:var(--text-primary);background:var(--bg-muted);
  transform:translateX(3px);}
.cl-k{display:flex;align-items:center;gap:8px;min-width:0;}
.cl-sw{width:11px;height:11px;flex:none;border:2px solid var(--text-primary);
  background:var(--bento-ink,var(--text-tertiary));}
.cl-name{font-weight:600;letter-spacing:.04em;text-transform:uppercase;font-size:11px;
  color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.cl-row.hot .cl-name{color:var(--text-primary);}
.cl-v{font-weight:700;font-variant-numeric:tabular-nums;color:var(--text-primary);flex:none;}

/* Radar — Charts.radar. One area, so it takes the accent rather than a ramp
   step; the dot goes hot on the picked axis. */
.radar-wrap{display:flex;align-items:center;justify-content:center;padding:8px 0 2px;min-width:0;}
.radar-wrap .radar{display:block;width:100%;height:auto;max-width:220px;}
.radar-grid{stroke:var(--text-tertiary);stroke-width:1.5;stroke-dasharray:3 4;}
.radar-axis{stroke:var(--text-tertiary);stroke-width:1.5;}
.radar-area{fill:var(--accent-soft);stroke:var(--accent-bright);stroke-width:3;stroke-linejoin:round;}
.radar-dot{fill:var(--bg-surface);stroke:var(--text-primary);stroke-width:2.5;transition:fill .14s;}
.radar-dot.hot{fill:var(--heat-red);}

@media (prefers-reduced-motion:reduce){
  .pie-slice,.cl-row,.radar-dot{transition-duration:.01ms;}
}

/* ── Owner bento — the fleet at a glance, above the org table ──────────────
   Three panels over ONE payload (GET /api/admin/overview, the same read the
   table below renders), so nothing here is a number the owner cannot scroll
   down and check against a row.

   B6 DE-BRUTALISED the frame, on the owner's own instruction ("revamp it into
   the style of UI on the home page"). What went: the 2px ink border, the 6px
   offset shadow, the dot-grid texture and the 800-weight uppercase heading —
   the neo-brutalist React component's whole signature. It was the one thing
   in this console that did not speak the portal's own language, and once the
   pipeline gained .chart-shell panels of its own the two idioms were sitting
   a scroll apart arguing with each other.

   What stays is the part that was never decoration: three panels over one
   read, the cross-highlight between a legend and its marks, and charts that
   refuse to draw a shape they have no series for. The surface is now the
   kit's .chart-shell — same token, same shadow, same heading scale — so the
   bento, the fleet funnel, the campaign ring and the home dashboard's own
   panels are one component family rather than four. The class names are kept
   because the layout genuinely is a bento (a two-up grid with a stacked
   column); only the skin changed. */
.owner-bento{display:grid;grid-template-columns:1fr;gap:12px;margin-bottom:8px;}
.bento-col{display:flex;flex-direction:column;gap:12px;min-width:0;}
@media (min-width:900px){
  .owner-bento{grid-template-columns:1fr 1fr;align-items:stretch;}
}

.bento-panel{position:relative;min-width:0;background:var(--bg-surface);
  border-radius:var(--radius-lg);box-shadow:var(--shadow-card);
  padding:15px 17px 13px;display:flex;flex-direction:column;}
/* The heading is .chart-shell-head's register now: 14.5/600, the sub in
   --text-secondary beside it. */
.bento-h{font-size:14.5px;font-weight:600;letter-spacing:-.01em;text-transform:none;
  color:var(--text-primary);margin:0 0 10px;display:flex;align-items:baseline;
  justify-content:space-between;gap:10px;flex-wrap:wrap;}
.bento-h .bento-sub{font-size:12px;font-weight:400;letter-spacing:0;
  color:var(--text-secondary);font-variant-numeric:tabular-nums;}
.bento-panel .empty-note{padding:22px 2px;}
.bento-mix .bento-mixbody{flex:1;display:flex;flex-direction:column;justify-content:center;}

/* Bars — Charts.vbars, unreskinned. The three .bento-bars .vb-bar overrides
   that painted these --heat-orange went with the frame: the kit's own bar is
   --accent-bright and that is what the home dashboard's Overview chart draws,
   so two bar charts in one product now agree on what a bar looks like. */
.bento-bars .vbars{margin-top:6px;}

@media (min-width:560px){
  .bento-health .bento-body{display:flex;align-items:center;gap:14px;}
  .bento-health .radar-wrap{flex:1;}
  .bento-health .chart-legend{width:170px;flex:none;margin-top:0;}
}

/* Entrance: a short staggered rise. The shadow no longer settles into place —
   there is no offset shadow left to settle. */
.owner-bento .bento-panel{animation:bento-in .34s cubic-bezier(.16,.84,.24,1) both;}
.owner-bento .bento-col .bento-panel:nth-child(2){animation-delay:.06s;}
.owner-bento>.bento-panel{animation-delay:.12s;}
@keyframes bento-in{
  from{opacity:0;transform:translateY(10px);}
  to{opacity:1;transform:none;}
}
@media (prefers-reduced-motion:reduce){
  .owner-bento .bento-panel{animation:none;}
}

/* Home dashboard pipeline donut — the same kit idiom with no brutalist
   frame: it sits in the shared .chart-shell alongside the Overview bars and
   Recent leads, so it wears what every other panel on that screen wears.
   Full width under the two-column split, with the ring and its legend side
   by side — a 190px ring next to five stage rows is the pair that fits. */
.dash-split .dash-pipe{grid-column:1/-1;}
.dash-pipe-body{display:flex;align-items:center;gap:20px;}
.dash-pipe-body .pie-wrap{flex:none;width:190px;padding:4px 0 0;}
.dash-pipe-body .chart-legend{flex:1;min-width:0;margin-top:0;}
@media (max-width:560px){
  .dash-pipe-body{flex-direction:column;align-items:stretch;}
  .dash-pipe-body .pie-wrap{width:auto;}
  .dash-pipe-body .chart-legend{margin-top:8px;}
}


/* The liveness pulse rides .holo-pulse, which the kit's reduced-motion block
   already stops; this keeps the colour and drops nothing else. */

/* hex-allowlist:start — documented exemption (appendix §6 item 8): SVG
   gradient stops for Charts.holoDefs. <stop> elements take stop-color from
   these classes so body.dark can retheme them; the values mirror --holo-grad
   exactly (styles.css §3.1). No other hex literal is allowed in this file. */
.holo-stop-a{stop-color:#2E86DC;}
.holo-stop-b{stop-color:#22B8CF;}
.holo-stop-c{stop-color:#7A5AF8;}
body.dark .holo-stop-a{stop-color:#3BA8FF;}
body.dark .holo-stop-b{stop-color:#53E0F2;}
body.dark .holo-stop-c{stop-color:#A78BFA;}
/* hex-allowlist:end */

/* Lead finder — ZIP + contactability controls. */
.lf-musts { display: flex; flex-wrap: wrap; gap: 18px; margin-top: 6px; }
.lf-check { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; }
.lf-check input { cursor: pointer; }
#lfZips { width: 100%; }
/* No hex fallback here. --danger is defined unconditionally on :root in
   styles.css, which this file is guaranteed to load after (asserted in the
   foundation suite), so a var() fallback is unreachable — and the one this
   rule shipped with was not even the token's red, so the only thing it could
   ever have done is put an off-palette colour on screen. The scanner reads
   raw text, comments included, so it cannot be quoted here either. */
#lfZipCount.lf-bad { color: var(--danger); font-weight: 600; }

/* ── Meetings ───────────────────────────────────────────────────────────────
   The recorded, consent-gated surface (routes/meetings.js), not the ad-hoc
   channel huddle. Layout follows .vis-body's lesson: the view needs its own
   scroll container or html,body{overflow:hidden} makes everything below the
   fold unreachable. */
.mt-body{flex:1;overflow-y:auto;min-height:0;padding:16px 24px 28px;}
.mt-bar{display:flex;align-items:center;gap:10px;margin-bottom:16px;flex-wrap:wrap;}
.mt-bar #mtNewWrap{display:flex;gap:8px;align-items:center;flex-wrap:wrap;}
/* An explicit `display` beats the UA stylesheet's [hidden]{display:none}, so
   without this the JS setting .hidden = true does nothing and the admin form
   stays on screen for non-admins and for workspaces with no Daily key. */
.mt-bar #mtNewWrap[hidden]{display:none;}
/* styles.css:79 puts width:100% on every text input and select, which in a flex
   row makes each one eat the whole line and stack. These need real widths, not
   a min-width the 100% overrides. */
.mt-bar input[type=text]{width:clamp(200px,32vw,340px);}
.mt-bar select{width:auto;min-width:112px;}
.mt-split{display:grid;grid-template-columns:minmax(240px,320px) 1fr;gap:18px;align-items:start;}
@media(max-width:900px){.mt-split{grid-template-columns:1fr;}}
.mt-sub{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);font-weight:600;margin-bottom:8px;}
.mt-list{display:flex;flex-direction:column;gap:6px;}
.mt-row{text-align:left;width:100%;border:1px solid var(--border);border-radius:10px;background:var(--bg-surface);
  padding:10px 12px;cursor:pointer;display:flex;flex-direction:column;gap:3px;}
.mt-row:hover{border-color:var(--text-muted);}
.mt-row.active{border-color:var(--accent);}
.mt-row b{font-size:13.5px;font-weight:600;}
.mt-row .meta{font-size:11.5px;}
.mt-dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;vertical-align:middle;}
.mt-dot.live{background:var(--danger);}
.mt-dot.done{background:var(--text-muted);}
.mt-detail{border:1px solid var(--border);border-radius:12px;background:var(--bg-surface);padding:16px 18px;}
.mt-detail-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px;}
.mt-detail-head b{font-size:15px;}
.mt-actions{margin-left:auto;display:flex;gap:8px;}
/* .btn is display:inline-flex, which outranks [hidden] the same way #mtNewWrap
   did — without this, Join/Record/End are visible on an ended meeting and to
   non-admins. Same shape as .nav-badge[hidden] and .agent-ambient[hidden]. */
.mt-actions .btn[hidden]{display:none;}
/* Consent is the load-bearing element in this view, so it gets colour. Amber
   while anyone is still deciding, danger once someone has declined. */
.mt-consent{border-radius:8px;padding:9px 12px;font-size:12.5px;line-height:1.55;margin-bottom:12px;
  border:1px solid var(--border);}
.mt-consent.wait{border-color:var(--warning);}
.mt-consent.blocked{border-color:var(--danger);}
.mt-notes-head{font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:var(--text-muted);
  font-weight:600;margin:14px 0 6px;display:flex;gap:8px;align-items:baseline;}
.mt-notes{font-size:13.5px;line-height:1.65;white-space:pre-wrap;}
.mt-notes:empty::after{content:'Notes appear a few minutes after recording starts.';color:var(--text-muted);font-size:12.5px;}
.mt-items{margin-top:12px;display:flex;flex-direction:column;gap:5px;}
.mt-item{font-size:12.5px;display:flex;gap:8px;align-items:baseline;}
.mt-item .k{font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;color:var(--text-muted);
  font-weight:700;min-width:62px;}
/* The call frame is deliberately big — a meeting is the whole screen, unlike
   the 440px .modal default. */
.modal.mt-call{width:min(1100px,94vw);height:min(760px,88vh);padding:0;display:flex;flex-direction:column;}
.mt-call-head{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--border);}
.mt-call-head b{font-size:14px;}
.mt-call-head .btn{margin-left:auto;}
.mt-call-frame{flex:1;min-height:0;}

/* ═══════════════════════════════════════════════════════════════════════════
   §9  PORTAL HOME DASHBOARD (2026-08-27)
   ───────────────────────────────────────────────────────────────────────────
   The stat-card row, Overview column chart and Recent-leads panel that now sit
   ABOVE the existing home (Alice's brief, the setup card, approvals) — those
   are untouched and still render below.

   Adapted from a shadcn/Tailwind dashboard reference. What carried over is the
   LAYOUT: title row with a window control, tab strip, four stat cards, then a
   two-thirds/one-third split of chart and recent list. What did not carry over
   is its palette (near-black bars, generic greys) or its numbers — every
   figure here is the org's own, from the same /api/crm/* and /api/ads/* reads
   the home already performed. No new request, no invented metric.

   Everything below styles existing kit classes (.stat-tile, .chart-shell)
   where one exists; only the genuinely new pieces get new class names.
   ═══════════════════════════════════════════════════════════════════════════ */

.dash-head{display:flex;align-items:center;justify-content:space-between;gap:16px;
  flex-wrap:wrap;padding:20px 24px 0;}
.dash-head h1{font-size:26px;font-weight:700;letter-spacing:-.025em;margin:0;color:var(--text-primary);}
.dash-head-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}

/* Window switcher — the reference's date-range picker. A picker implies you
   can choose any range; these are the windows the timeseries endpoint actually
   accepts, so they are buttons and the label says days. */
.dash-window{display:inline-flex;background:var(--bg-muted);border-radius:var(--radius-pill);padding:3px;gap:2px;}
.dash-window button{border:none;background:none;font:inherit;font-size:12.5px;font-weight:600;
  color:var(--text-secondary);padding:5px 13px;border-radius:var(--radius-pill);cursor:pointer;
  transition:background .15s,color .15s;}
.dash-window button:hover{color:var(--text-primary);}
.dash-window button[aria-pressed="true"]{background:var(--bg-surface);color:var(--text-primary);
  box-shadow:var(--shadow-card);}
.dash-window button:focus-visible{outline:2px solid var(--accent-bright);outline-offset:2px;}

/* Tab strip. All four tabs OPEN — the three that are not built yet say so in
   their own panel (.dash-blank, below).
   There is deliberately NO `cursor:not-allowed` here. Windows draws that as a
   red 🚫, which reads as "this is broken" rather than "this is not built yet",
   and a control that silently refuses a click can only be read as a fault.
   Owner reported exactly that, 2026-08-27. */
.dash-tabs{display:inline-flex;gap:2px;background:var(--bg-muted);border-radius:var(--radius);
  padding:3px;margin:14px 24px 0;}
.dash-tabs button{border:none;background:none;font:inherit;font-size:13px;font-weight:600;
  color:var(--text-secondary);padding:6px 14px;border-radius:7px;cursor:pointer;
  transition:background .14s,color .14s;}
.dash-tabs button:hover{color:var(--text-primary);}
.dash-tabs button[aria-selected="true"]{background:var(--bg-surface);color:var(--text-primary);
  box-shadow:var(--shadow-card);}
.dash-tabs button:focus-visible{outline:2px solid var(--accent-bright);outline-offset:2px;}

/* The empty room behind an unbuilt tab: what it will hold, and where that
   information lives in the product TODAY. The rows are real navigation, not
   decoration — every one opens a view that exists. */
.dash-blank{margin:12px 24px 0;background:var(--bg-surface);border-radius:var(--radius-lg);
  padding:26px 24px;box-shadow:var(--shadow-card);}
.dash-blank .db-lead{font-size:13px;font-weight:700;color:var(--accent-text);
  letter-spacing:.02em;margin-bottom:8px;}
.dash-blank .db-body{margin:0;font-size:13.5px;line-height:1.6;color:var(--text-secondary);max-width:64ch;}
.dash-blank .db-where{margin-top:22px;font-size:11px;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--text-tertiary);}
.dash-blank .db-list{display:flex;flex-direction:column;gap:8px;margin-top:10px;}
.dash-blank .db-row{display:flex;align-items:center;justify-content:space-between;gap:14px;
  width:100%;text-align:left;font:inherit;cursor:pointer;
  background:var(--bg-canvas);border:1px solid var(--border);border-radius:var(--radius);
  padding:12px 14px;transition:border-color .14s,background .14s;}
.dash-blank .db-row:hover{border-color:var(--accent-bright);background:var(--accent-soft);}
.dash-blank .db-row:focus-visible{outline:2px solid var(--accent-bright);outline-offset:2px;}
.dash-blank .db-row-txt{display:flex;flex-direction:column;gap:3px;min-width:0;}
.dash-blank .db-row-label{font-size:13.5px;font-weight:600;color:var(--text-primary);}
.dash-blank .db-row-hint{font-size:12px;color:var(--text-secondary);}
.dash-blank .db-note{margin:16px 0 0;font-size:12.5px;color:var(--text-tertiary);}

/* Card grid: four across, collapsing rather than scrolling — .stat-row scrolls
   horizontally, which hides cards 3 and 4 on a laptop. */
.dash-cards{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px;padding:14px 24px 0;}
@media(max-width:1000px){.dash-cards{grid-template-columns:repeat(2,minmax(0,1fr));}}
@media(max-width:560px){.dash-cards{grid-template-columns:minmax(0,1fr);}}
.dash-cards .stat-tile{width:auto;}
.dash-cards .st-sub{display:block;font-size:11.5px;color:var(--text-secondary);margin-top:5px;}

.dash-split{display:grid;grid-template-columns:minmax(0,1.9fr) minmax(0,1fr);gap:12px;padding:12px 24px 0;}
@media(max-width:1000px){.dash-split{grid-template-columns:minmax(0,1fr);}}
.dash-split .chart-shell{margin:0;}

/* ── the Overview column chart ──────────────────────────────────────────────
   Loggia orange, not the reference's near-black. Solid --accent-bright rather
   than a gradient: an SVG rect cannot take a CSS gradient, and minting a
   <linearGradient> per chart would put colour back in charts.js, which is the
   one thing that file's header forbids. */
.vbars{display:block;width:100%;height:auto;}
.vb-bar{fill:var(--accent-bright);transition:fill .14s;}
.vbars:hover .vb-bar{fill:var(--accent);}
.vb-bar:hover{fill:var(--accent-deep);}
.vb-grid{stroke:var(--border);stroke-width:1;shape-rendering:crispEdges;}
.vb-axis{stroke:var(--border);stroke-width:1;shape-rendering:crispEdges;}
.vb-tick,.vb-xlab{fill:var(--text-tertiary);font-size:10.5px;font-weight:500;}

/* ── recent leads list ──────────────────────────────────────────────────── */
.dash-recent{display:flex;flex-direction:column;gap:2px;margin-top:4px;}
.dash-recent-row{display:flex;align-items:center;gap:11px;padding:9px 2px;}
.dash-recent-row + .dash-recent-row{border-top:1px solid var(--border);}
.dr-avatar{width:32px;height:32px;border-radius:50%;flex:none;display:grid;place-items:center;
  background:var(--accent-soft);color:var(--accent-text);font-size:12px;font-weight:700;
  letter-spacing:.02em;text-transform:uppercase;}
.dr-who{min-width:0;flex:1;}
.dr-name{font-size:13px;font-weight:600;color:var(--text-primary);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.dr-meta{font-size:11.5px;color:var(--text-secondary);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.dr-when{font-size:11.5px;color:var(--text-tertiary);flex:none;white-space:nowrap;}

/* ── header overflow menu (2026-08-27) ──────────────────────────────────────
   Adapted from a 21st.dev smooth-dropdown. The motion carried over — one disc
   growing from the corner it was tapped in, contents fading in behind it, and
   the left-bar hover indicator. The stack did not: no React, no motion/react,
   no @hugeicons, no react-use-measure. A CSS transition on width/height plus a
   measured target height does the same job in one element.

   The curve is a read of the original's spring (damping 34 / stiffness 380):
   fast arrival, no overshoot. */
.dash-menu{position:relative;width:36px;height:36px;flex:none;}
.dash-menu .dm-disc{
  position:absolute;top:0;right:0;width:36px;height:36px;
  display:grid;place-items:center;
  background:var(--bg-muted);color:var(--text-secondary);
  border:1px solid transparent;border-radius:12px;cursor:pointer;padding:0;
  transition:background .14s,color .14s,border-color .14s;
}
.dash-menu .dm-disc:hover{color:var(--text-primary);border-color:var(--border);}
.dash-menu .dm-disc:focus-visible{outline:2px solid var(--accent-bright);outline-offset:2px;}
.dash-menu.open .dm-disc{opacity:0;pointer-events:none;transition:opacity .12s;}

.dash-menu .dm-panel{
  position:absolute;top:0;right:0;z-index:40;
  width:36px;height:36px;overflow:hidden;
  background:var(--bg-surface);border:1px solid var(--border);border-radius:12px;
  box-shadow:var(--shadow-card);
  opacity:0;pointer-events:none;transform-origin:top right;
  transition:width .34s cubic-bezier(.16,.84,.24,1),
             height .34s cubic-bezier(.16,.84,.24,1),
             border-radius .34s cubic-bezier(.16,.84,.24,1),
             opacity .16s ease;
}
.dash-menu.open .dm-panel{width:232px;border-radius:14px;opacity:1;pointer-events:auto;}
.dash-menu .dm-list{padding:8px;display:flex;flex-direction:column;gap:2px;}
.dash-menu .dm-item{
  position:relative;display:flex;align-items:center;gap:11px;width:100%;
  font:inherit;font-size:13.5px;font-weight:500;text-align:left;
  color:var(--text-secondary);background:none;border:none;cursor:pointer;
  padding:8px 10px 8px 13px;border-radius:9px;
  opacity:0;transform:translateX(8px);
  transition:color .16s,background .16s,opacity .18s ease,transform .18s cubic-bezier(.16,.84,.24,1);
}
.dash-menu.open .dm-item{opacity:1;transform:none;}
/* Staggered arrival — the one flourish worth keeping from the original. */
.dash-menu.open .dm-item:nth-child(1){transition-delay:.05s;}
.dash-menu.open .dm-item:nth-child(3){transition-delay:.08s;}
.dash-menu.open .dm-item:nth-child(4){transition-delay:.10s;}
.dash-menu.open .dm-item:nth-child(5){transition-delay:.12s;}
.dash-menu.open .dm-item:nth-child(6){transition-delay:.14s;}
.dash-menu.open .dm-item:nth-child(8){transition-delay:.16s;}
.dash-menu .dm-item:hover{color:var(--text-primary);background:var(--bg-muted);}
.dash-menu .dm-item:focus-visible{outline:2px solid var(--accent-bright);outline-offset:-2px;}
/* The left bar, only on the row you are actually on. */
.dash-menu .dm-item::before{
  content:"";position:absolute;left:4px;top:50%;transform:translateY(-50%) scaleY(.2);
  width:3px;height:18px;border-radius:99px;background:var(--text-primary);
  opacity:0;transition:opacity .16s,transform .16s cubic-bezier(.16,.84,.24,1);
}
.dash-menu .dm-item:hover::before,
.dash-menu .dm-item:focus-visible::before{opacity:1;transform:translateY(-50%) scaleY(1);}
.dash-menu .dm-item.danger:hover{color:var(--danger);background:var(--danger-soft,var(--bg-muted));}
.dash-menu .dm-item.danger:hover::before{background:var(--danger);}
.dash-menu .dm-item svg{flex:none;}
.dash-menu .dm-label{white-space:nowrap;}
.dash-menu .dm-divider{height:1px;background:var(--border);margin:6px 4px;}

@media(prefers-reduced-motion:reduce){
  .dash-menu .dm-panel,.dash-menu .dm-item{transition-duration:.01ms;transition-delay:0s;}
}
