/* ==========================================================================
   Everything the design could not express in a single flat file.

   The source design ("TaskNTrade Site v2.dc.html") had three workarounds
   forced on it by being one self-contained file with no stylesheet:

     1. `style-hover="…"` / `style-focus="…"` attributes, because there is no
        way to write a :hover rule inside a style attribute.
     2. A JS `layout()` function that re-set grid-template-columns on every
        resize, because there is no way to write a media query inline.
     3. JS that wrote inline styles to mark the active nav tab, selected
        campaign, chosen contact method, etc.

   All three are real CSS here. Values are unchanged from the design — this
   file is a change of mechanism, not of appearance.

   Because the page markup keeps the design's inline styles verbatim (that is
   what guarantees fidelity), the hover/focus utilities must use !important to
   win against them. That is exactly the precedence `style-hover` had.
   ========================================================================== */

/* ── 1. hover / focus states (was style-hover= / style-focus=) ───────────── */

.h-op1:hover          { opacity:1 !important }
.h-txt:hover          { color:var(--txt) !important }
.h-lift:hover         { border-color:var(--edge2) !important; transform:translateY(-2px) }
.h-bright:hover       { filter:brightness(1.08) }
.h-bright-11:hover    { filter:brightness(1.1) }
.h-navpill:hover      { color:var(--txt) !important; background:color-mix(in oklab, var(--txt) 5.5%, transparent) !important }
.h-edge2:hover        { border-color:var(--edge2) !important }
.h-acc-bg:hover       { background:var(--orange) !important; color:var(--on-accent) !important }
.h-acc-ring:hover     { background:var(--orange) !important; border-color:var(--orange) !important; color:var(--on-accent) !important }
/* Soft shadows on card-style article/div elements with borders */
article[data-rise],
[data-featgrid] > [style*="border:1px solid"]      { box-shadow:0 4px 12px -4px rgba(10,25,47,.12) }
.h-acc-flat:hover     { background:var(--orange) !important; border-color:transparent !important; color:var(--on-accent) !important }
.h-up2:hover          { transform:translateY(-2px) }
.h-up3-scale:hover    { transform:translateY(-3px) scale(1.05) }
.h-scale:hover        { transform:scale(1.08) }
.h-gap:hover          { gap:12px !important }
.h-acc-edge:hover     { border-color:var(--orange2) !important }
.h-acc-edge-up:hover  { border-color:var(--orange2) !important; transform:translateY(-3px) }
.h-acc-tint:hover     { border-color:color-mix(in oklab, var(--orange) 50%, transparent) !important; background:color-mix(in oklab, var(--orange) 6%, transparent) !important }
.h-tint-10:hover      { background:color-mix(in oklab, var(--txt) 10%, transparent) !important }
.h-tint-11:hover      { background:rgba(10,25,47,.11) !important }
.h-tint-075:hover     { background:rgba(10,25,47,.075) !important }

.f-acc:focus          { border-color:var(--orange2) !important; background:color-mix(in oklab, var(--orange) 6%, transparent) !important }

/* ── 2. responsive layout (was the JS layout() function) ─────────────────────
   The design used two breakpoints: `mid` = 700px, `wide` = 1024px. Each rule
   below is a direct transcription of one layout() line. Mobile-first: the
   base value is layout()'s narrow branch, so `grid-template-columns` is
   deliberately absent from these elements' inline styles.
   ───────────────────────────────────────────────────────────────────────── */

[data-desknav]    { display:none }
[data-deskcta]    { display:none }
[data-mobbtn]     { display:inline-flex }

[data-herogrid],
[data-casegrid],
[data-doorgrid],
[data-svchook],
[data-svcgrid],
[data-svcstat],
[data-mktgrid],
[data-statgrid],
[data-crgrid],
[data-ctgrid],
[data-teamgrid],
[data-footgrid]    { grid-template-columns:1fr }

@media (min-width:700px){
  [data-herogrid]     { grid-template-columns:1.1fr .9fr }
  [data-doorgrid]     { grid-template-columns:1fr 1fr }
  [data-svcgrid]      { grid-template-columns:repeat(2,1fr) }
  [data-svcstat]      { grid-template-columns:repeat(3,1fr) }
  [data-statgrid]     { grid-template-columns:repeat(2,1fr) }
  [data-teamgrid]     { grid-template-columns:repeat(2,1fr) }
  [data-footgrid]     { grid-template-columns:repeat(2,1fr) }
}

/* Homepage hero stat bar: one flat border-y band instead of four separate
   cards, so it reads as a single unit; each stat still gets its own
   hover affordance (a border that was transparent at rest turning
   accent-colored) rather than the whole band lighting up at once.

   The column count moved off the inline style and into here 2026-08-21. Four
   tracks were hardcoded inline at every width, so on a phone each stat got
   ~80px and every label broke over three lines ("TOTAL / AUDIENCE / REACH").
   min-width:0 below kept it from actually overflowing, so this never showed
   up as a bug — just as four cramped columns. 2x2 to 560px, then four up. */
[data-homestat]{ grid-template-columns:repeat(2,1fr) }
@media (min-width:560px){ [data-homestat]{ grid-template-columns:repeat(4,1fr) } }
[data-homestat] > div{
  border:1px solid transparent; border-radius:14px; padding:8px 6px; min-width:0;
  transition:border-color .3s cubic-bezier(.16,1,.3,1), transform .3s cubic-bezier(.16,1,.3,1);
}
[data-homestat] > div:hover{ border-color:var(--orange2); transform:translateY(-2px) }

/* [data-foundergrid] is flex, not grid — found 2026-08-14: Chrome mis-sizes
   a grid row when one 1fr column holds a wide block of reflowing text next
   to a short fixed-size column, undershooting the row height by tens of px
   and cutting the last line off past the card's own border. flex with
   flex:1 1 0% on the text side doesn't have that intrinsic-sizing quirk. */
[data-foundergrid]  { display:flex; flex-direction:column }
@media (min-width:700px){
  [data-foundergrid]              { flex-direction:row; align-items:flex-start }
  [data-foundergrid] > div:first-child { flex:0 0 auto }
  [data-foundergrid] > div:last-child  { flex:1 1 0%; min-width:0 }
}

@media (min-width:1024px){
  [data-desknav]      { display:flex }
  [data-deskcta]      { display:flex }
  [data-mobbtn]       { display:none }
  [data-casegrid]     { grid-template-columns:1fr 1fr }
  [data-svchook]      { grid-template-columns:1.15fr .85fr }
  [data-svcgrid]      { grid-template-columns:repeat(3,1fr) }
  [data-mktgrid]      { grid-template-columns:repeat(3,1fr) }
  [data-statgrid]     { grid-template-columns:repeat(4,1fr) }
  [data-crgrid]       { grid-template-columns:1.5fr .9fr }
  [data-ctgrid]       { grid-template-columns:.85fr 1.15fr }
  [data-footgrid]     { grid-template-columns:1.7fr 1fr 1fr 1fr }
}

/* ── 3. stateful controls (was syncNav() / syncChrome() writing inline styles)
   Base state lives here rather than inline so the .is-on variant can win
   without !important.
   ───────────────────────────────────────────────────────────────────────── */

/* header nav pill — active page. Static per page, set at build time. */
[data-tab]              { background:transparent; color:var(--dim); font-weight:500 }
[data-tab].is-on        { background:color-mix(in oklab, var(--orange) 14%, transparent); color:var(--orange); font-weight:700 }

/* campaign-explorer tabs, contact-method pills, survey pills, department
   pills — one shared look. [data-dbtn] (contact.html's "Which team should
   this go to?") was missing from this list entirely — initDept() in
   js/forms.js toggles .is-on same as every other pill group, but with no
   [data-dbtn] rule here the class had nothing to style, so a click never
   showed a selection. */
[data-case],
[data-mbtn],
[data-sbtn],
[data-dbtn]             { background:color-mix(in oklab, var(--txt) 4%, transparent); color:var(--dim); border-color:var(--edge2) }
[data-sbtn][data-survey="no"],
[data-mbtn][data-method=""] { color:var(--dim2) }
[data-case].is-on,
[data-mbtn].is-on,
[data-sbtn].is-on,
[data-dbtn].is-on       { background:linear-gradient(135deg,var(--orange),var(--orange2)); color:var(--on-accent); border-color:transparent }

/* reel-rail position markers */
[data-dot]              { background:var(--edge2); width:14px }
[data-dot].is-on        { background:var(--orange); width:34px }

/* per-reel sound pill */
[data-mute]             { background:rgba(10,25,47,.55); border-color:rgba(255,255,255,.28) }
/* Every [data-mute] tile carries inline color:#fff (correct at rest — the
   pill sits on a dark scrim) — !important is required to beat that inline
   value once .is-on switches the fill to var(--orange). */
[data-mute].is-on       { background:var(--orange); border-color:transparent; color:var(--on-accent) !important }

/* theme toggle — the design swapped these two icons with a conditional; here
   the current theme on <html> picks one. Sun = "you are in dark, go light". */
.icon-sun                     { display:none }
.icon-moon                    { display:block }
html[data-theme="dark"] .icon-sun  { display:block }
html[data-theme="dark"] .icon-moon { display:none }

/* ── 4. progressive enhancement / no-JS fallbacks ────────────────────────── */

/* Elements the design toggled with a conditional are shipped in the markup and
   hidden with [hidden] instead. The design's inline styles set `display`, which
   would otherwise beat the UA's [hidden] rule — hence !important. */
[hidden]                { display:none !important }

/* The mobile menu and video lightbox are JS-driven overlays; keep them out of
   the flow until opened so they never cover the page with scripting disabled. */
[data-menu],
[data-lightbox-shell]   { display:none }
[data-menu].is-open,
[data-lightbox-shell].is-open { display:block }
[data-lightbox-shell].is-open { display:flex }

/* Reels are lazy-attached by JS (see js/reels.js). Without JS the poster or
   the panel background shows instead of an empty black box. */

/* Wizard steps: only the current one is in the flow. With JS off, step 1 shows
   and the form still submits every field, so nothing is unreachable. */
[data-step]             { display:none }
[data-step].is-on       { display:block }

/* ==========================================================================
   5. ADDITIONS BEYOND THE DESIGN
   Everything below this line is NOT in "TaskNTrade Site v2.dc.html" — it was
   requested after the import. Kept in its own section so the design-fidelity
   rule above stays auditable: if it's above this banner it came from the
   design, if it's below it didn't. See README "Deviations from the design".
   ========================================================================== */

/* 5a (glassmorphism: .glass/.glass-stage/.blob, plus the .chip sector tags)
   removed 2026-08-13 along with the brochure rewrite (§5r) that was its only
   consumer — the section no longer uses frosted glass or a sector-chip list. */

/* ── 5b. "coming soon" / under-construction states ──────────────────────────
   Used wherever real content or an API is not connected yet. The brief was
   explicit that missing data should say so rather than be filled with
   invented placeholder content.
   ───────────────────────────────────────────────────────────────────────── */

@keyframes uc-sweep { 0%{transform:translateX(-120%)} 60%,100%{transform:translateX(220%)} }
@keyframes uc-dot   { 0%,100%{transform:scale(.72);opacity:.55} 50%{transform:scale(1);opacity:1} }
@keyframes uc-track { 0%{transform:translateX(-100%)} 100%{transform:translateX(320%)} }
@keyframes uc-float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-6px)} }
@keyframes typewriter-blink { 0%,100%{border-right-color:transparent} 50%{border-right-color:var(--txt)} }

/* placeholder tile: a shimmer sweep reads as "loading, not broken" */
.uc-tile                { position:relative; overflow:hidden }
.uc-tile::after{
  content:""; position:absolute; top:0; bottom:0; width:38%;
  background:linear-gradient(90deg, transparent, color-mix(in oklab, var(--txt) 7%, transparent), transparent);
  animation:uc-sweep 2.9s cubic-bezier(.4,0,.2,1) infinite;
}
.uc-tile:nth-of-type(2n)::after   { animation-delay:.5s }
.uc-tile:nth-of-type(3n)::after   { animation-delay:1s }

/* status badge with a live pulsing dot */
.uc-badge{
  display:inline-flex; align-items:center; gap:8px;
  padding:7px 14px 7px 11px; border-radius:999px;
  font-size:11.5px; font-weight:600; letter-spacing:.12em; text-transform:uppercase;
  background:color-mix(in oklab, var(--orange) 10%, transparent); border:1px solid color-mix(in oklab, var(--orange) 30%, transparent); color:var(--orange2);
}
.uc-badge::before{
  content:""; width:7px; height:7px; border-radius:50%;
  background:var(--orange2); animation:uc-dot 1.6s ease-in-out infinite;
}
.uc-badge-warm          { background:color-mix(in oklab, var(--txt) 6%, transparent);
                          border-color:var(--edge2); color:var(--dim) }
.uc-badge-warm::before  { background:var(--dim2) }

/* indeterminate progress: work in progress, no known completion date */
.uc-bar{
  position:relative; overflow:hidden; height:4px; border-radius:99px;
  background:color-mix(in oklab, var(--txt) 8%, transparent);
}
.uc-bar::after{
  content:""; position:absolute; inset:0 auto 0 0; width:32%; border-radius:99px;
  background:linear-gradient(90deg, var(--orange), var(--orange2));
  animation:uc-track 2.1s cubic-bezier(.65,0,.35,1) infinite;
}

.uc-float               { animation:uc-float 4.5s ease-in-out infinite }

@media (prefers-reduced-motion:reduce){
  .uc-tile::after, .uc-badge::before, .uc-bar::after, .uc-float { animation:none }
  .uc-tile::after       { opacity:0 }
  .uc-bar::after        { width:38% }
}

/* ── 5c. reel captions — readability fix ────────────────────────────────────
   The design set the creator name in plain white over the video itself. On a
   bright frame (a white studio wall, a snow shot) it disappeared. A blurred
   chip plus a text shadow keeps it legible over ANY frame, which a darker
   gradient alone cannot guarantee.
   ───────────────────────────────────────────────────────────────────────── */

.reel-cap{
  display:inline-block; padding:8px 13px; border-radius:14px;
  background:rgba(8,9,12,.52);
  backdrop-filter:blur(10px) saturate(1.2);
  -webkit-backdrop-filter:blur(10px) saturate(1.2);
  border:1px solid rgba(255,255,255,.16);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.14);
}
.reel-cap-name          { color:#fff; text-shadow:0 1px 3px rgba(0,0,0,.6) }
.reel-cap-brand         { color:#BFD0FF; text-shadow:0 1px 3px rgba(0,0,0,.6) }
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .reel-cap             { background:rgba(8,9,12,.78) }
}

/* ── 5d. team photo slots — drop a file in, it appears ──────────────────────
   Each slot ships the placeholder AND an <img> that hides itself via onerror
   when the file isn't there yet (see src/pages/about.html). No build step, no
   manifest, no code edit when a photo finally arrives.

   Bug found + fixed 2026-08-14, testing the first real drop-in (ceo.jpg): the
   hide-on-load rule below never actually worked. Every .photo-fallback in
   about.html carries its own inline `style="display:grid;…"`, and an inline
   style always beats an external stylesheet rule of any selector specificity
   short of !important — so the fallback (icon, and for the founder slot, the
   "Portrait coming soon" text) kept rendering ON TOP of a successfully loaded
   photo. Needed the same fix CLAUDE.md documents for style-hover=/style-focus=:
   !important, because that's exactly the precedence being overridden. */

.photo-slot             { position:relative; overflow:hidden;
                          background:color-mix(in oklab, var(--txt) 3%, transparent);
                          border:1px dashed var(--edge2) }
/* A real photo's own background rarely matches the surrounding card (this
   one is a studio black, jarring flush against a light-theme white card) —
   inset the image a few px so the slot's own tinted background shows as a
   thin mat around it instead of the photo butting straight into the card
   edge. Needs its own border-radius since it no longer reaches the parent's
   clipped edge (`inherit` pulls whatever radius each inline-styled instance
   sets on .photo-slot). */
.photo-slot img         { position:absolute; inset:5px; width:calc(100% - 10px); height:calc(100% - 10px);
                          object-fit:cover; object-position:center 18%; border-radius:inherit }
.photo-slot img:not([hidden]) ~ .photo-fallback { display:none !important }
.photo-slot:has(img:not([hidden])) {
  border-style:solid; border-width:2px;
  box-shadow:0 10px 24px -12px rgba(0,0,0,.35);
}

/* ── 5d-2. founder message — an editorial serif for the one quoted voice
   on the site ──────────────────────────────────────────────────────────────
   Every other paragraph on this site is --body (Instrument Sans, a neutral
   UI sans). The founder message (src/pages/about.html) is the one place
   real spoken/written words from a named person are quoted at length, so it
   gets its own typographic voice: Fraunces italic, a serif with warmth this
   grotesque/neutral pairing doesn't have on its own. Scoped to this one
   block via a class rather than touching --body globally. */
.founder-quote          { font-family:"Fraunces", Georgia, serif; font-style:italic;
                          font-optical-sizing:auto; font-weight:400 }
.founder-quote-name     { font-family:"Fraunces", Georgia, serif; font-weight:600; font-style:normal }

/* ── 5e. layout for the pages added after the design ────────────────────── */

[data-deptgrid],
[data-featgrid],
[data-samplegrid],
[data-trackgrid],
[data-evrgrid],
[data-tengrid],
[data-brochgrid],
[data-teamphotos],
[data-hookgrid],
[data-signalgrid]       { grid-template-columns:1fr }

@media (min-width:700px){
  [data-hookgrid]       { grid-template-columns:repeat(2,1fr) }
  [data-featgrid]       { grid-template-columns:repeat(2,1fr) }
  [data-samplegrid]     { grid-template-columns:repeat(2,1fr) }
  [data-evrgrid]        { grid-template-columns:repeat(2,1fr) }
  [data-tengrid]        { grid-template-columns:repeat(2,1fr) }
  [data-teamphotos]     { grid-template-columns:repeat(3,1fr) }
  [data-brochgrid]      { grid-template-columns:1fr 1fr }
  [data-signalgrid]     { grid-template-columns:repeat(2,1fr) }
}

@media (min-width:1024px){
  [data-deptgrid]       { grid-template-columns:repeat(3,1fr) }
  [data-samplegrid]     { grid-template-columns:repeat(3,1fr) }
  [data-trackgrid]      { grid-template-columns:1.35fr .65fr }
  [data-tengrid]        { grid-template-columns:repeat(5,1fr) }
  [data-teamphotos]     { grid-template-columns:repeat(3,1fr) }
  [data-hookgrid]       { grid-template-columns:repeat(4,1fr) }

  /* Overrides the design's 4-column footer: the Teams column makes five.
     Deliberately here and not in §2, so §2 stays a faithful transcription. */
  [data-footgrid]       { grid-template-columns:1.5fr 1fr 1fr 1fr 1fr }
}

@media (min-width:700px) and (max-width:1023.98px){
  [data-footgrid]       { grid-template-columns:repeat(3,1fr) }
}

/* ── 5f. theme + contrast corrections ───────────────────────────────────────
   Everything here fixes a value that is wrong in one of the two themes. All of
   it belongs in the design file; it lives here instead so `css/theme.css` and
   §1-4 stay a byte-faithful transcription and remain diffable against the
   source. Each rule names the value it corrects so the list can be handed
   upstream and then deleted from here.

   The pattern behind most of them: a token that FLIPS between themes used as
   foreground on a surface that DOESN'T (a hardcoded dark scrim, a #fff fill,
   video). The tell is `rgba(10,25,47,…)` — that is `#0A1128`, which is exactly
   what `--navy` holds, and `--navy` does flip. It is referenced nowhere.
   ───────────────────────────────────────────────────────────────────────── */

/* --dim2 and --orange2's corrections used to live here (raised alpha, a
   darker teal for text/border roles) because the mint accent failed contrast
   in one of those roles. The 2026-08-13 blue palette (css/theme.css) already
   ships --dim/--dim2 and --orange/--orange2 as flat, unified-per-theme
   values with headroom on both roles (~4.9-5.2:1) — so both corrections are
   gone from here, not just re-pointed. --creator is gone outright: it had
   one consumer (creators.html's eyebrow label, now var(--orange) directly)
   and had already collapsed to --orange2's value in the mint pass. */
:root{
  /* "Success" existed as two unrelated literals, one failing contrast in each
     theme. One token, one value per theme, instead of one value that had to
     lose in one of them. Untouched by either accent rebrand — this is a
     distinct semantic color, not the brand accent. */
  --ok:#15774B;
  /* Text/icons on a var(--orange)/var(--orange2) FILL — the is-on pills, the
     skip link. Was #fff through the blue and coral eras. The 2026-08-17 bright
     rebrand (#FF6B35 light / #FF8C42 dark) put both fills far too high on the
     luminance scale for white to sit on: 2.84:1 and 2.31:1 respectively. Dark
     ink clears 6.53:1 / 8.01:1 on the same fills, so this token now goes dark
     rather than flipping per theme — the fill is light in BOTH themes. */
  --on-accent:#0F1419;
}
html[data-theme="dark"]{
  /* Was #5FE0A4 — a neon mint that the brief explicitly ruled out. Desaturated
     to a sage emerald that still clears 8.44:1 on --ink; green keeps its
     success/up semantics, it just stops glowing. */
  --ok:#6DBF9B;
}

/* The logo mark holds two images: logo.png (navy, light mode) and
   logo-dark.png (crisp white, dark mode). CSS owns the swap.

   These three rules carry !important for a specific reason, and removing it
   silently breaks the dark-mode mark. Both <img> tags carry an inline
   `display:block` (part of the transcribed design), and inline styles beat
   any un-flagged stylesheet rule — so `display:none` alone never applied.
   The markup used to compensate with a `hidden` attribute on the dark image,
   but [hidden]{display:none!important} up at line ~154 then beat the
   dark-theme `display:block` too, leaving dark mode with NO logo at all once
   the old white-chip workaround was removed. The attribute is gone from the
   partials now; specificity lives here and only here. */
.logo-mark{ display:inline-flex; align-items:center; flex-shrink:0 }
.logo-mark img:last-child{ display:none !important }
html[data-theme="dark"] .logo-mark img:first-child{ display:none !important }
html[data-theme="dark"] .logo-mark img:last-child{ display:block !important }

/* Secondary CTA hover. Both of these resolved to rgba(10,25,47,…) — in dark
   theme that IS the page background, so the button's fill vanished on hover
   and the affordance ran backwards. `.h-tint-10` one line up already did it
   correctly with color-mix; these two now match it. */
.h-tint-11:hover      { background:color-mix(in oklab, var(--txt) 11%, transparent) !important }
.h-tint-075:hover     { background:color-mix(in oklab, var(--txt) 7.5%, transparent) !important }

/* The non-leading rows of the engagement summary (/collaborations/) and the
   format distribution (/insights/). Two earlier passes on this: first the
   fills were hardcoded navy, 1.09:1 against their own track in dark theme —
   the filled portion was indistinguishable from the empty one. That was
   fixed with a neutral grey mix of --txt, which reads clearly but introduced
   a different problem, visible as soon as the sections were looked at whole:
   one orange bar above five grey ones reads as "one active row, five
   disabled" — grey is the disabled colour in every UI convention — when in
   fact all six are equally real results, 5.20% down to 3.71%. Now a muted
   tint of the accent instead: same family as the leading bar, clearly
   subordinate to it, obviously not switched off. Kept as one rule with
   !important because it has to beat the inline background on each span.
   55% is measured, not eyeballed: against the bars' own empty track
   (--txt at 7% over --ink) it lands at 2.24:1 light / 2.83:1 dark, where
   42% gave only 1.81:1 in light — enough to see, not enough to be sure of. */
[data-bar].bar-rest   { background:color-mix(in oklab, var(--orange2) 55%, var(--panel2)) !important }

/* /services/ — the six "this is for you if…" cards. Their descriptions run to
   two or three lines, and one title wraps ("Reporting & performance
   analysis"), so the "See how X runs it" link landed at a different height in
   every card and each row read as ragged. Pushing the link to the card floor
   puts all three of a row on one line. !important because each card carries
   an inline display:block and each link an inline margin-top — the same
   inline-beating precedence the hover rules above already rely on, which is
   why CLAUDE.md sanctions !important in this file. */
[data-svcgrid] > a{ display:flex !important; flex-direction:column }
[data-svcgrid] > a > span:last-child{ margin-top:auto !important; padding-top:18px }

/* ── Short side-columns: stick, don't strand ─────────────────────────────────
   Four sections pair a tall primary column with a much shorter secondary one
   — /services/advert/'s mid-flight rail beside the tracker panel, /contact/'s
   details beside the message form, and the trust asides on /creators/ and
   /partner/. In every one the short column ran out 300-400px before the tall
   one, leaving an obvious empty block at the bottom of the row. Sticking it
   turns that dead space into something useful: the reassurance (or the phone
   number) stays on screen while the reader works down the form next to it,
   which is the whole reason those columns exist. Desktop only — below 1024px
   these grids are a single column and there is nothing to stick to.
   align-self:start is required, not decoration: a grid item defaults to
   stretch, which fills the row and leaves sticky no slack to move in. */
@media (min-width:1024px){
  [data-trackgrid] > div:last-child,
  [data-ctgrid]    > div:first-child,
  [data-crgrid]    > aside{
    position:sticky; top:100px; align-self:start;
  }
}
@media (prefers-reduced-motion:reduce){
  [data-trackgrid] > div:last-child,
  [data-ctgrid]    > div:first-child,
  [data-crgrid]    > aside{ position:static }
}

/* Controls that sit on a hardcoded near-black surface (the reel scrim, the
   lightbox backdrop) must not use tokens that flip — in light theme --edge2 is
   dark ink on near-black and the ring disappeared entirely. */
.on-dark              { border-color:rgba(255,255,255,.28) !important; color:#fff }

/* Marker for the inverse case: elements whose inline style hardcodes a
   literal color:#fff over a var(--orange)/var(--orange2) fill or gradient
   (every gradient CTA button). A literal, not a var(), so raising --orange
   for dark-theme text legibility above doesn't reach it — needs its own
   !important override the same way --on-accent needs its own token. */
.accent-fill          { color:var(--on-accent) !important }

/* The mute pill sits at the top of the tile where the scrim gradient is still
   fully transparent, so it had plain white on raw video — the exact failure
   §5c already fixed for the caption 40px lower in the same tile. */
[data-mute]           { text-shadow:0 1px 3px rgba(0,0,0,.65) }

/* Focus must not land under the 70px sticky header (WCAG 2.4.11). */
html                  { scroll-padding-top:86px }

/* Rail markers were 14x5 with 22px centres — under the 24px minimum on both
   size and spacing. Grow the hit area only; the painted bar is unchanged.

   Colour added 2026-08-21. At rest these were var(--edge2), a token meant for
   hairline borders — on the reel rails (home and /services/advert/) twelve of
   them were all but invisible, so the only cue that the rail had more tiles
   was the single orange active marker. That mattered more after the line
   telling people they could "tap a marker to jump to one" was removed from
   /services/advert/ on request: the control lost its explanation and its
   visibility in the same pass. The base rule lives in the frozen §1-4
   transcription, so this overrides from §5 rather than editing it, the way
   §5e already overrides the footer's column count. .is-on keeps its accent
   fill — higher specificity, unaffected by this. */
[data-dot]{
  /* !important: the markup's inline height:5px otherwise wins and shrinks the
     tap area back to 5px (found by the 2026-10-07 mobile audit). The bar still
     draws 5px tall: the padding is clipped out of the background. */
  height:25px !important; padding:10px 0; background-clip:content-box; box-sizing:border-box;
  background:color-mix(in oklab, var(--txt) 32%, transparent);
}

/* Skip link — visually hidden until focused. */
.skip{
  position:absolute; left:14px; top:-60px; z-index:200;
  padding:12px 18px; border-radius:0 0 12px 12px;
  background:var(--orange); color:var(--on-accent); font-size:14px; font-weight:600;
  transition:top .2s cubic-bezier(.16,1,.3,1);
}
.skip:focus            { top:0; color:var(--on-accent) }

/* The design's blanket `transition:all .5s` plus the metric bars' width
   transition are not covered by the existing reduced-motion blocks. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after { transition-duration:.01ms !important; animation-duration:.01ms !important }
  [data-rail]          { scroll-behavior:auto }
}

/* Small pill badge, reused for the case-study viewer's per-case badge
   (js/signal.js) — was originally written for the case-file disclosure
   cards this section replaced. */
.case-file-badge{
  display:inline-flex; align-items:center; background:color-mix(in oklab, var(--orange) 14%, transparent);
  border:1px solid color-mix(in oklab, var(--orange) 35%, transparent); border-radius:999px; padding:5px 12px;
  font-size:11px; font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--orange2);
}
/* Small fact/flag tags on a case-study card (js/signal.js). */
.case-tags{ display:flex; flex-wrap:wrap; gap:6px; margin-top:10px }
.case-tag{
  display:inline-flex; align-items:center; font-size:11.5px; font-weight:600; line-height:1.3;
  padding:5px 10px; border-radius:8px; background:color-mix(in oklab, var(--txt) 5%, transparent);
  border:1px solid var(--edge2); color:var(--dim);
}
.case-tag-warn{
  background:color-mix(in oklab, var(--orange) 8%, transparent);
  border-color:color-mix(in oklab, var(--orange) 22%, transparent); color:var(--orange2);
}

/* OSM model 4-stage flow removed 2026-08-13 along with the Signal page
   section that used it (see js/signal.js's git history for context) — no
   other page referenced .osm-flow/.osm-stage. */

/* Advert tracker — segmented bar for a brand with more than one round, so the
   bar doesn't visually claim a single campaign. Segments are sized by each
   round's share of that brand's combined reach; the wrapping div's own width
   is still the brand's portfolio-relative share, unchanged from before. */
.advert-segbar{ display:flex; height:100% }
.advert-segbar span{ height:100%; background:linear-gradient(90deg,var(--orange),var(--orange2)) }
.advert-segbar span:not(:first-child){ border-left:1px solid var(--panel) }
/* Signal case-study viewer — one case at a time, rendered by js/signal.js
   into this shared template (same architecture as the campaign explorer's
   CASES array in js/cases.js: one render(), not ten copies of the markup).
   .sc-pills/.sc-pill (the brand picker) retired 2026-08-13 — replaced with a
   native <select> in dept-signal.html because the pill row's .is-on state
   was never actually styled here, so nothing showed which case was active.
   .sc-roundpill (the round picker within a brand) keeps its pill look but
   gained the .is-on style it was missing too. */
.sc-roundpills{ display:flex; flex-wrap:wrap; gap:7px; margin-top:16px }
.sc-roundpill{
  font-size:12.5px; font-weight:600; padding:8px 13px; border-radius:999px;
  border:1px solid var(--edge2); cursor:pointer; background:none; color:var(--dim2);
}
.sc-roundpill.is-on{
  border-color:var(--orange2); color:var(--orange2);
  background:color-mix(in oklab, var(--orange) 10%, transparent);
}

.sc-card{ border:1px solid var(--edge); border-radius:26px; background:linear-gradient(180deg,var(--panel),var(--panel2)); padding:34px 28px; margin-top:22px }
.sc-head{ display:flex; flex-wrap:wrap; align-items:flex-start; justify-content:space-between; gap:16px }
.sc-title{ font-family:var(--display); font-weight:800; font-size:clamp(26px,3.4vw,36px); letter-spacing:-.02em; margin-top:14px }
/* Bumped 2026-08-13 for readability — was 15px, easy to skim past on a page
   whose whole point is that the numbers below it are worth reading closely. */
.sc-objective{ margin-top:12px; font-size:17px; line-height:1.6; color:var(--txt); max-width:42rem }

.sc-stats{ display:grid; grid-template-columns:1.4fr 1fr 1fr; gap:14px; margin-top:26px }
.sc-stat{ border:1px solid var(--edge); border-radius:18px; padding:20px; background:color-mix(in oklab, var(--txt) 3%, transparent) }
.sc-stat-hero{
  background:linear-gradient(160deg,color-mix(in oklab, var(--orange) 10%, var(--panel)),var(--panel));
  border-color:color-mix(in oklab, var(--orange) 25%, var(--edge));
}
.sc-stat-icon{ display:inline-flex; align-items:center; justify-content:center; width:32px; height:32px; border-radius:10px; background:color-mix(in oklab, var(--orange) 12%, transparent); color:var(--orange2) }
.sc-stat-label{ display:block; margin-top:12px; font-size:11px; letter-spacing:.1em; text-transform:uppercase; color:var(--dim2) }
.sc-stat-val{ display:block; margin-top:4px; font-family:var(--display); font-weight:800; letter-spacing:-.02em }
.sc-stat-hero .sc-stat-val{ font-size:34px }
.sc-stat:not(.sc-stat-hero) .sc-stat-val{ font-size:21px }
.sc-stat-bar{ height:5px; border-radius:99px; background:color-mix(in oklab, var(--txt) 8%, transparent); margin-top:12px; overflow:hidden }
.sc-stat-bar span{ display:block; height:100%; border-radius:99px; background:linear-gradient(90deg,var(--orange),var(--orange2)) }

.sc-mix{ margin-top:26px; border-top:1px solid var(--edge); padding-top:22px }
.sc-mix h4{ font-family:var(--display); font-weight:700; font-size:13px; letter-spacing:.12em; text-transform:uppercase; color:var(--dim2); margin:0 }
.sc-mixbar{ display:flex; height:14px; border-radius:99px; overflow:hidden; margin-top:14px }
.sc-mixbar span:not(:first-child){ border-left:1px solid var(--panel) }
.sc-mixlegend{ display:flex; flex-wrap:wrap; gap:14px; margin-top:12px; font-size:12.5px; color:var(--dim) }
.sc-mixlegend b{ display:inline-block; width:9px; height:9px; border-radius:3px; margin-right:6px }

.sc-flags{ display:flex; flex-wrap:wrap; gap:6px; margin-top:16px }
.sc-conclusion{ margin-top:18px; padding:16px 18px; border-radius:14px; background:color-mix(in oklab, var(--txt) 4%, transparent); font-size:14.5px; font-weight:600 }
.sc-growth,.sc-osm{ margin-top:14px; padding:16px 18px; border-radius:14px; font-size:14px; line-height:1.62 }
.sc-growth{ background:color-mix(in oklab, var(--ok) 8%, transparent); border:1px solid color-mix(in oklab, var(--ok) 22%, transparent) }
.sc-osm{ background:color-mix(in oklab, var(--orange) 6%, transparent); border:1px solid color-mix(in oklab, var(--orange) 18%, transparent) }
.sc-osm b, .sc-growth b{ color:var(--txt) }

@media (max-width:700px){
  .sc-stats{ grid-template-columns:1fr }
}

/* Campaign explorer — multi-round summary bars for brands with more than one
   campaign (IME, Fonepoints, Royal Enfield). Built by js/cases.js#renderRounds. */
.case-rounds-label{ font-size:11px; font-weight:700; letter-spacing:.1em; text-transform:uppercase; color:var(--dim); margin-bottom:10px }
.case-rounds-row{ display:grid; grid-template-columns:auto 1fr auto; align-items:center; gap:10px; margin-top:8px; font-size:12.5px }
.case-rounds-name{ font-weight:600; color:var(--dim); white-space:nowrap }
.case-rounds-track{ height:6px; border-radius:99px; background:color-mix(in oklab, var(--txt) 8%, transparent); overflow:hidden; display:block }
.case-rounds-fill{ display:block; height:100%; border-radius:99px; background:linear-gradient(90deg,var(--orange),var(--orange2)) }
.case-rounds-val{ font-weight:600; text-align:right; white-space:nowrap }

/* ==========================================================================
   5g. SIGNAL DATA-VIZ SYSTEM
   Added 2026-08-12 with the Signal page rebuild. Everything here is CSS, not
   SVG, deliberately: nine of the ten charts on that page are one flat
   rectangle per datum, which CSS does natively with real DOM text (so screen
   readers get the numbers for free), automatic reflow, and automatic theme
   flipping. SVG would cost all three and buy nothing.

   Colour is computed, not picked. Two separate ramps, because the data has
   two separate shapes:
     --viz-t1..t3  ORDINAL. Nano < Micro < Mid-Micro is a real order, so it is
                   one hue at rising strength. Never reorder these.
     --viz-c1..c3  CATEGORICAL. Instagram/TikTok/Facebook have no order, so
                   they are distinct hues.
   Contrast was checked against --panel2 (the darkest surface a chart can land
   on), in BOTH themes. Do not substitute a value without re-checking it.

   Two rules that fall out and must not be broken:
     1. Diaspora is NOT on the tier ramp. It is audience geography, not a
        size, and a 4-step ordinal ramp fails the contrast floor anyway.
     2. Text never wears a series colour. --viz-t1 is far too light to be
        legible as type; values and labels stay --txt / --dim.

   --viz-t3 / --viz-c1 in light theme were mint (#0CF2A0) — a leftover from
   the mint accent rebrand that got missed when the site moved to blue:
   they're bare hex, not var(--orange), so neither rebrand's find/replace
   ever touched them. Re-anchored to the same blue family as --viz-t1/t2
   below (2026-08-13), one step deeper/more saturated for t3 to keep the
   ordinal "rising strength" reading; c1 sits at its own point so it stays
   visually distinct from t2/t3 if a page ever shows both series together.
   Dark theme's values were already blue and are untouched. */
.viz{
  --viz-surface : var(--panel);
  --viz-grid    : color-mix(in oklab, var(--txt) 22%, transparent);
  --viz-t1      : #5A7AAA;
  --viz-t2      : #2A4A8A;
  --viz-t3      : #0A192F;
  --viz-c1      : #FF6B35;
  --viz-c2      : #2A5A9A;
  --viz-c3      : #0A192F;
  --viz-warn    : #B45309;   /* was #D97706 — 2.90:1 on --viz-surface, and this token carries text */

  /* ── Creator-tier categorical scale ─────────────────────────────────────
     Added 2026-08-21. The six tiers used to borrow from the 3-step ordinal
     ramp (--viz-t1/t2/t3) plus two of the categorical accents
     (--viz-c2/c3), and those sets COLLIDE: in this theme --viz-t3 and
     --viz-c3 are both #0A192F, so "Mid-Micro" and "Influencer" rendered as
     the identical near-black; in dark, --viz-t3 and --viz-c2 are both
     #5B8DFD, so "Mid-Micro" and "Diaspora" were identical. The old
     dot-matrix chart hid that, because each tier was also a spatially
     separate block of icons — position carried the grouping and colour was
     only a hint. The donut that replaced it has nothing BUT colour per
     wedge, which is what exposed the clash.
     Six own hues, not a lightness ramp: blue / indigo / teal / violet /
     rose / slate. Verified numerically rather than by eye — every value is
     >=3.5:1 against --viz-surface in its own theme, and the closest pair is
     dE76 25.4 apart (luminance contrast is the wrong test between two
     wedges: teal and violet can share a luminance and still be obviously
     different colours). Untiered stays deliberately neutral slate — it is a
     recording gap, not a tier, and must never read as a sixth size step. */
  --tier-1      : #1E88C7;   /* Nano       */
  --tier-2      : #24307A;   /* Micro      */
  --tier-3      : #0F766E;   /* Mid-Micro  */
  --tier-4      : #7C3AED;   /* Diaspora   */
  --tier-5      : #BE123C;   /* Influencer */
  --tier-6      : #64748B;   /* Untiered   */
}
html[data-theme="dark"] .viz{
  --viz-surface : var(--panel2);
  --viz-grid    : color-mix(in oklab, var(--txt) 18%, transparent);
  --viz-t1      : #FF8C42;
  --viz-t2      : #A56B8A;
  --viz-t3      : #5B8DFD;
  --viz-c1      : #FF8C42;
  --viz-c2      : #5B8DFD;
  --viz-c3      : #F59E0B;
  --viz-warn    : #F59E0B;

  --tier-1      : #7FC8FF;
  --tier-2      : #3B82F6;
  --tier-3      : #2DD4BF;
  --tier-4      : #C98BFF;
  --tier-5      : #FB7185;
  --tier-6      : #94A3B8;
}

/* Charts must sit on a FLAT surface. .sc-card is a gradient, so a chart
   dropped straight onto it has no defined contrast reference. */
.viz-panel{
  background:var(--viz-surface); border:1px solid var(--edge);
  border-radius:18px; padding:20px 18px;
}

/* ── 5g.1 unit strip — removed 2026-08-21 ───────────────────────────────────
   Was one 11px square per creator, in the per-case roster card. Retired with
   the rest of the dot-matrix family (owner: no dot-matrix chart anywhere on
   the site); that card now renders the .viz-donut-wrap.is-sm variant.
   Nothing references .viz-units or .vu any more.
   ───────────────────────────────────────────────────────────────────────── */

/* Shared legend. Prints the COUNT as text next to every swatch — this is what
   carries the meaning when colour can't (low-contrast fills, colour-blind
   readers, forced-colors mode). Never ship a fill without it. */
.viz-key{
  display:flex; flex-wrap:wrap; gap:8px 18px; margin-top:14px;
  list-style:none; padding:0; font-size:12.5px; font-weight:600; color:var(--dim);
}
.viz-key li{ display:inline-flex; align-items:center }
.viz-key i{
  width:10px; height:10px; border-radius:3px;
  display:inline-block; margin-right:7px; flex-shrink:0;
}
.viz-key b{ color:var(--txt); font-weight:700; margin-left:6px; font-variant-numeric:tabular-nums }
.viz-key .is-zero{ opacity:.45 }   /* a booked-zero tier is information, not noise */

/* ── 5g.2 gate bar — target vs actual ────────────────────────────────────────
   The fill is SEGMENTED at every 1x of target, so four accent-colored blocks
   read as "four times over" before the reader finds the label. A plain long bar can't
   distinguish 398% from 586%, and makes 96.8% look like 100%.
   Denominator is max(actual,target); the 2% right margin keeps the target
   tick visible when the target is the larger of the two.
   ───────────────────────────────────────────────────────────────────────── */

.tg-head{ display:flex; align-items:center; justify-content:space-between; gap:14px; flex-wrap:wrap }
.tg-lab{
  font-size:12.5px; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; color:var(--dim);
}
/* Bumped 2026-08-13 — this is "the answer, above everything" per the
   comment on its mount point in dept-signal.html; 15px read as a small
   status pill instead of the headline stat the page leads with. */
.tg-badge{
  font-family:var(--display); font-weight:800; font-size:clamp(21px,2.6vw,27px); letter-spacing:-.015em;
  /* Was var(--viz-c1) — identical to --orange, which theme.css forbids as a
     text colour (2.69:1). The tint behind it stays --viz-c1 so the chart's
     colour identity is unchanged; only the glyph moves to the text-safe orange. */
  color:var(--orange2); padding:7px 16px; border-radius:99px;
  background:color-mix(in oklab, var(--viz-c1) 12%, transparent);
  white-space:nowrap;
}
.tg-track{
  position:relative; height:22px; border-radius:7px; margin-top:13px;
  background:color-mix(in oklab, var(--viz-c1) 10%, var(--viz-surface));
}
.tg-fill{
  display:block; height:100%; border-radius:7px; background:var(--viz-c1);
  background-image:repeating-linear-gradient(90deg,
    transparent 0 calc(var(--seg) - 2px),
    var(--viz-surface) calc(var(--seg) - 2px) var(--seg));
}
.tg-rule{
  position:absolute; left:var(--tick); top:-6px; bottom:-6px;
  width:2px; background:var(--txt); opacity:.62;
}
.tg-foot{
  display:flex; justify-content:space-between; gap:14px; margin-top:10px;
  font-size:13px; font-weight:600; color:var(--dim);
}
.tg-foot b{ color:var(--txt); font-variant-numeric:tabular-nums }

/* Shortfall. Colour is never the only signal — the badge also carries a
   downward marker and the words "short". */
.tg.is-short .tg-fill { background:var(--viz-warn); background-image:none }
.tg.is-short .tg-badge{
  color:var(--viz-warn);
  background:color-mix(in oklab, var(--viz-warn) 14%, transparent);
}
/* No target on record — an honest gap, not a zero-width bar. */
.tg.is-none .tg-track{ opacity:.4 }
.tg.is-none .tg-badge{
  color:var(--dim); background:color-mix(in oklab, var(--txt) 7%, transparent);
}

/* ── 5g.3 lollipop — achievement across every scored campaign ────────────────
   Diverging against a 1.0x baseline, because the story is "eight cleared it,
   one didn't". Scale is linear 0..6x, so the baseline sits at 16.667%.
   ───────────────────────────────────────────────────────────────────────── */

.viz-lolli{ margin-top:6px }
.vl-row{
  display:grid; grid-template-columns:minmax(110px,24%) 1fr minmax(88px,auto);
  align-items:center; gap:14px; padding:8px 0;
}
.vl-name{ font-size:14.5px; font-weight:600; color:var(--txt); text-align:right }
.vl-plot{ position:relative; height:14px }
.vl-plot::before{                       /* the 1.0x reference — solid, never dashed */
  content:""; position:absolute; left:var(--base,16.667%); top:-8px; bottom:-8px;
  width:1px; background:var(--viz-grid);
}
.vl-stem{
  position:absolute; top:50%; height:2px; border-radius:99px;
  left:min(var(--base,16.667%),var(--x)); right:calc(100% - max(var(--base,16.667%),var(--x)));
  background:var(--viz-c1); transform:translateY(-50%);
}
.vl-dot{
  position:absolute; top:50%; left:var(--x); width:11px; height:11px;
  margin:-5.5px 0 0 -5.5px; border-radius:50%; background:var(--viz-c1);
  box-shadow:0 0 0 2px var(--viz-surface);
}
.vl-val{ font-size:13.5px; font-weight:700; font-variant-numeric:tabular-nums }
.vl-row.is-short .vl-stem,
.vl-row.is-short .vl-dot{ background:var(--viz-warn) }
.vl-row.is-short .vl-val{ color:var(--viz-warn) }
@media (max-width:700px){
  .vl-row{ grid-template-columns:1fr auto; gap:6px 12px }
  .vl-name{ text-align:left }
  .vl-plot{ grid-column:1 / -1; order:2 }
}

/* ── 5g.4a tier donut — one ring, one wedge per tier ──────────────────────────
   Replaced the person-icon dot matrix 2026-08-21 (owner's call: no dot-matrix
   chart anywhere on the site) with a single conic-gradient ring — still a
   plain DOM element with an inline background, not SVG, matching this file's
   header comment ("charts are built as DOM elements... reflows and
   theme-flips for free"). The legend still prints the real count/percentage
   as text next to each swatch, so nothing that mattered about the matrix's
   accessibility story was lost — only the "one shape per unit" render. */
/* Ring + legend as a two-up that actually consumes the panel it sits in.
   First cut centred a 190px ring with the legend beside it and let the pair
   sit at their natural width, which on Signal's full-bleed panel left the
   right ~45% of the box empty — the dot matrix it replaced had filled that
   space with 341 icons. justify-content:space-between plus a legend that
   grows pushes the two apart to the panel's own edges instead. */
.viz-donut-wrap{
  display:flex; gap:28px 44px; align-items:center; flex-wrap:wrap;
  justify-content:space-between;
}
.viz-donut{
  width:190px; height:190px; border-radius:50%; flex-shrink:0;
  position:relative;
}
/* Per-case roster card: same component, half the panel width to work in.
   The centre hole is opened up from 27% to 21% here on purpose — the label
   ("booked") is nearly as wide as the hole at this diameter, and at 27% it
   sat hard against the ring. */
.viz-donut-wrap.is-sm{ gap:20px; justify-content:flex-start }
.viz-donut-wrap.is-sm .viz-donut{ width:104px; height:104px }
.viz-donut-wrap.is-sm .viz-donut-center{ inset:21% }
.viz-donut-wrap.is-sm .viz-donut-center b{ font-size:16px }
.viz-donut-wrap.is-sm .viz-donut-center span{ font-size:8px; margin-top:2px; letter-spacing:.03em }
.viz-donut-wrap.is-sm .viz-key{ flex:1 1 130px; min-width:130px }
.viz-donut-center{
  position:absolute; inset:27%; border-radius:50%; background:var(--viz-surface);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; padding:4px;
}
.viz-donut-center b{
  font-family:var(--display); font-weight:800; font-size:24px; line-height:1;
  color:var(--txt); font-variant-numeric:tabular-nums;
}
.viz-donut-center span{
  font-size:10.5px; font-weight:600; color:var(--dim); margin-top:5px;
  text-transform:uppercase; letter-spacing:.06em;
}
/* Two legend columns on a wide panel — six one-per-line items next to a
   190px ring left a tall thin list against a lot of empty panel. */
.viz-donut-wrap .viz-key{ flex:1 1 220px; min-width:220px; margin-top:0 }
.viz-donut-wrap .viz-key-lg{ flex:1 1 420px }
@media (min-width:760px){
  .viz-donut-wrap .viz-key-lg{
    display:grid; grid-template-columns:repeat(2,minmax(170px,1fr));
    gap:16px 32px; align-content:center;
  }
}

/* Tier -> colour comes from the shared --tier-* scale defined in the .viz
   block above, read live by js/campaigns.js's TIER_COLORS and
   js/reach-estimate.js's TIER_COLOR, which set each wedge's and each legend
   swatch's colour inline from the same map — so a wedge and its swatch
   cannot drift apart, and "Nano" is one colour everywhere on the site.

   /partner/'s reach estimator reuses this component unmodified for its own
   4-tier split (nano/micro/mid/macro), just with far fewer bookings per
   submission than Signal's portfolio total. It used to need a bespoke
   --reach-macro property for its 4th tier, because the old mapping drew
   from --viz-t*/--viz-c* — two sets that collide in both themes — and had
   simply run out of non-duplicate values. The --tier-* scale removed the
   cause, so that property and its two declarations are gone with it.
   Rendered by js/reach-estimate.js's renderMatrix(), called from
   js/forms.js's renderEstimate(). */

/* Bigger-label modifier — used by Signal's own portfolio tier donut AND
   /partner/'s reach-estimator donut (js/reach-estimate.js), the only two
   .viz-key-lg consumers. Other legends on the page (per-case roster strip,
   platform split) use the plain, smaller .viz-key and are untouched by
   anything here.

   One item per line, 2026-08-19: the base .viz-key's flex-wrap packed items
   into whatever the flex algorithm found room for on each row — on
   Signal's 6-item legend (Nano/Micro/Mid-Micro/Diaspora/Influencer/
   Untiered) that meant some rows held two short labels and others held one
   long one, leaving ragged, uneven whitespace in the column next to the
   donut rather than a clean list. flex-direction:column forces exactly
   one item per row regardless of label length — the fix is generic to
   .viz-key-lg, not specific to Signal's item count, so /partner/'s 4-tier
   legend gets it too. */
.viz-key-lg{
  font-size:16.5px; font-weight:600; color:var(--txt); gap:14px;
  flex-direction:column; align-items:flex-start;
}
.viz-key-lg i{ width:15px; height:15px }
.viz-key-lg b{ font-size:18px; margin-left:8px }

@media (max-width:520px){
  .viz-donut-wrap{ justify-content:center }
}

/* ── 5g.4b roster bars — replaces the 8-column em-dash table ─────────────────
   Redesign pass 2026-08-13: horizontal and UNSTACKED — one row per campaign,
   a thin bar per tier present in that roster, side by side rather than piled
   on top of each other, so two campaigns' Nano counts are directly
   comparable by bar length instead of buried inside a stack. Every bar is
   scaled against the single largest tier-count booked by any campaign in the
   portfolio (not that campaign's own total), so length means the same thing
   in every row. Hover/focus reveals the exact tier + count via a real
   visible tooltip (not just a native title) — data-tip + ::after, matching
   no other component on the page since this is the first one that needed a
   floating label instead of an always-visible number. */
.rb-row{
  display:grid; grid-template-columns:minmax(120px,22%) 1fr minmax(52px,auto);
  align-items:center; gap:14px; padding:11px 0;
  border-bottom:1px solid var(--edge);
}
.rb-row:last-child{ border-bottom:none }
.rb-name{ font-size:13px; font-weight:600; color:var(--txt) }
.rb-bars{ display:flex; flex-direction:column; gap:4px; padding:2px 0 }
.rb-bar{
  position:relative; height:8px; border-radius:99px; min-width:3px;
  cursor:default;
}
.rb-bar:focus-visible{ outline:2px solid var(--viz-c1); outline-offset:3px }
.rb-bar::after{
  content:attr(data-tip); position:absolute; left:0; bottom:calc(100% + 7px);
  white-space:nowrap; font-size:11.5px; font-weight:600; color:var(--viz-surface);
  background:var(--txt); padding:5px 9px; border-radius:7px;
  opacity:0; transform:translateY(3px); pointer-events:none;
  transition:opacity .14s ease, transform .14s ease; z-index:2;
  font-variant-numeric:tabular-nums;
}
.rb-bar:hover::after, .rb-bar:focus-visible::after{ opacity:1; transform:translateY(0) }
.rb-total{ font-size:13px; font-weight:700; color:var(--txt); text-align:right; font-variant-numeric:tabular-nums }

/* ── 5g.5 KPI card ───────────────────────────────────────────────────────────
   What made the old .sc-stat generic: three cards at identical weight, and a
   bar whose denominator was the portfolio maximum — invisible to the reader,
   so the bar meant nothing. Two fixes: an explicit type hierarchy, and a
   NAMED referent printed beside every meter with a tick at the reference
   point. A progress bar the reader can't calibrate is decoration.
   ───────────────────────────────────────────────────────────────────────── */

.kpi-grid{ display:grid; gap:14px; grid-template-columns:1fr }
@media (min-width:700px) { .kpi-grid{ grid-template-columns:repeat(2,1fr) } }
@media (min-width:1024px){ .kpi-grid{ grid-template-columns:1.6fr 1fr 1fr 1fr } }

/* The 4-track template above is the PORTFOLIO row's (four cards, first one
   emphasised). The per-case card renders two or three cards into the same
   component, and inherited that same 4-track template — so on a case with
   three cards the fourth track stayed empty and the row visibly stopped
   about 40% short of the panel's right edge. auto-fit sizes to whatever
   count the render actually produced. */
.kpi-grid.is-fill{ grid-template-columns:repeat(auto-fit,minmax(210px,1fr)) }

.kpi{
  display:flex; flex-direction:column;
  border:1px solid var(--edge); border-radius:20px;
  background:var(--viz-surface); padding:22px 22px 18px; min-height:168px;
  box-shadow:0 4px 12px -4px rgba(10,25,47,.12);
}
html[data-theme="dark"] .kpi{
  box-shadow:0 4px 12px -4px rgba(0,0,0,.20);
}
.kpi-label{
  font-size:12.5px; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; color:var(--dim);
}
.kpi-val{
  font-family:var(--display); font-weight:800;
  font-size:clamp(38px,4.4vw,50px); line-height:.9; letter-spacing:-.035em;
  color:var(--txt); margin-top:12px;
}
.kpi-val u{
  font-size:.42em; font-weight:600; letter-spacing:-.01em;
  color:var(--dim); text-decoration:none; margin-left:4px;
}
/* margin-bottom is the FLOOR gap to the meter; margin-top:auto on the meter
   absorbs any leftover height above that. Without it, a two-line comparator
   collides with the meter on a narrow card. */
.kpi-cmp{ font-size:14px; font-weight:600; color:var(--dim); margin-top:9px; margin-bottom:16px }
.kpi-cmp b{ color:var(--txt); font-weight:700; font-variant-numeric:tabular-nums }
.kpi-meter{
  position:relative; height:6px; border-radius:99px; margin-top:auto;
  background:color-mix(in oklab, var(--viz-c1) 12%, var(--viz-surface));
}
.kpi-meter > i{ display:block; height:100%; border-radius:99px; background:var(--viz-c1) }
.kpi-meter > s{                     /* the named referent, the whole point */
  position:absolute; top:-4px; bottom:-4px; left:var(--ref);
  width:2px; background:var(--txt); opacity:.55; text-decoration:none;
}
/* min-height reserves TWO lines whether this card's footnote needs them or
   not. The meter above is margin-top:auto inside a stretch-height flex card,
   so its position is measured up from the bottom — meaning a one-line
   footnote ("16 campaigns · 10 brands") pushed its meter ~20px lower than a
   two-line one ("Weighted across the 14 campaigns that recorded it") on the
   very same row. Four meters at three different heights read as sloppy on
   the page a brand is asked to judge our reporting by. Reserving the taller
   of the two puts every meter and every footnote baseline on one line. */
.kpi-foot{
  font-size:13px; font-weight:600; color:var(--dim); margin-top:9px;
  line-height:1.45; min-height:2.9em;
}
.kpi-meter + .kpi-foot{ margin-top:9px }
.kpi.is-warn .kpi-meter > i{ background:var(--viz-warn) }
.kpi.is-warn .kpi-cmp b   { color:var(--viz-warn) }
@media (min-width:1024px){
  .kpi:first-child           { padding:26px 26px 20px }
  .kpi:first-child .kpi-val  { font-size:clamp(52px,5.4vw,72px) }
}

/* ── 5g.6 honest gaps ────────────────────────────────────────────────────────
   "Not recorded" must never look like a zero. Absent gets muted type and NO
   bar at all; a real zero keeps its bar track.
   ───────────────────────────────────────────────────────────────────────── */

.is-unrecorded .kpi-val,
.is-unrecorded [data-sc-stat-val]{
  font-size:19px; font-weight:600; letter-spacing:0; color:var(--dim2);
  font-family:var(--body); line-height:1.3;
}
.is-unrecorded .kpi-meter,
.is-unrecorded .sc-stat-bar{ display:none }

/* ── 5g.7 conversion callout — the only business-outcome claim on the site ───
   Every figure here is reported or calculated by the client's own campaign
   report, not measured by us, so the "estimated" label is structural: it is
   part of the component, not an optional prop a future edit can drop.
   ───────────────────────────────────────────────────────────────────────── */

.viz-conv{
  margin-top:18px; padding:20px 20px 18px; border-radius:16px;
  background:color-mix(in oklab, var(--ok) 8%, transparent);
  border:1px solid color-mix(in oklab, var(--ok) 26%, transparent);
}
.viz-conv-val{
  font-family:var(--display); font-weight:800; font-size:clamp(30px,3.6vw,40px);
  line-height:1; letter-spacing:-.03em; color:var(--txt);
}
.viz-conv-lab{ font-size:14px; font-weight:600; color:var(--txt); margin-top:8px }
.viz-conv-note{ font-size:12.5px; line-height:1.6; color:var(--dim); margin-top:9px }
.viz-conv-tag{
  display:inline-flex; align-items:center; gap:6px; margin-bottom:12px;
  padding:5px 11px; border-radius:99px; font-size:10.5px; font-weight:700;
  letter-spacing:.1em; text-transform:uppercase;
  color:var(--ok); background:color-mix(in oklab, var(--ok) 14%, transparent);
}

/* ── 5g.8 section rhythm for a numbers-first page ────────────────────────── */

/* The page container lives HERE, not in an inline style, because an inline
   `margin:0 auto` silently beats a class `margin-top` — which is exactly the
   bug that collapsed every section gap on the first build. */
.sig-sec{ max-width:1240px; margin:78px auto 0; padding:0 22px }
.sig-eyebrow{
  font-size:11px; font-weight:700; letter-spacing:.16em;
  text-transform:uppercase; color:var(--dim2);
}
.sig-h{
  font-family:var(--display); font-weight:800;
  font-size:clamp(26px,3.4vw,40px); line-height:1.02; letter-spacing:-.03em;
  margin-top:10px;
}
.sig-lede{
  margin-top:12px; font-size:15.5px; line-height:1.6;
  color:var(--dim); max-width:40rem;
}

/* ── 5i. Global type scale — redesign pass, 2026-08-12 ────────────────────────
   theme.css stays a byte-faithful transcription (per CLAUDE.md); the base
   type change lives here instead, same mechanism §5f already uses for token
   corrections. Headers were already tight-tracked (Bricolage Grotesque,
   -.02em to -.035em) — that part of the brief was already true. What wasn't:
   an explicit readable body size/line-height, and a named scale for new work
   to use instead of another ad-hoc inline px value. Existing inline sizes
   are NOT swept to these tokens in this pass — 1,300+ occurrences across 11
   pages, high mechanical risk for a cosmetic-only gain; new/touched markup
   should reach for these tokens going forward. */
:root{
  --fs-btn:15px; --fs-btn-lg:15.5px; --r-btn:4px;
}
body{ font-size:16px; line-height:1.6 }

/* ── Button component — primary (fill) / secondary (outline) / ghost (text) ──
   Redesign pass, 2026-08-12 — authorized amendment to CLAUDE.md's design-
   fidelity rule (see that file). Was a full pill (border-radius:999px); moved
   to a structural radius for a less "soft agency," more enterprise-dashboard
   feel — everything else about the interaction model (the .18s transition
   list, the .98 press-scale, the reduced-motion branch) already matched the
   brief and is kept as-is. */

.btn{ display:inline-flex; align-items:center; justify-content:center; gap:8px;
  position:relative; overflow:hidden; font-size:var(--fs-btn); font-weight:600; padding:12px 20px;
  border-radius:var(--r-btn); border:1px solid transparent; cursor:pointer; text-decoration:none;
  transition:transform .32s cubic-bezier(.16,1,.3,1), box-shadow .32s cubic-bezier(.16,1,.3,1),
  filter .32s cubic-bezier(.16,1,.3,1), background .32s cubic-bezier(.16,1,.3,1),
  border-color .32s cubic-bezier(.16,1,.3,1), color .32s cubic-bezier(.16,1,.3,1);
}
.btn:focus-visible{
  outline:none; box-shadow:0 0 0 2px var(--panel), 0 0 0 4px var(--orange2);
}

/* "Flow" hover, 2026-08-13 — from a pasted React/Tailwind/shadcn reference
   component (FlowButton). Reimplemented here in plain CSS on the existing
   .btn system instead of the literal shadcn/Tailwind/TypeScript project the
   reference assumed: this codebase has none of those (see CLAUDE.md — no
   npm, no bundler), and the goal is the interaction, not that stack. A
   circle grows from the button's centre on hover (clipped to the button's
   own rounded corners via overflow:hidden above) and sits BEHIND the label
   (z-index:-1, so it tints the fill rather than covering the text) — same
   idea as the reference's expanding circle, using the site's own accent
   instead of the reference's literal black, per "keep it simple, no neon"
   in the same request. The trailing icon nudges forward to read as motion
   flowing toward it, not just a colour change. */
.btn::before{
  content:""; position:absolute; top:50%; left:50%; z-index:-1;
  width:0; height:0; border-radius:50%; transform:translate(-50%,-50%);
  background:color-mix(in oklab, var(--txt) 14%, transparent);
  transition:width .6s cubic-bezier(.19,1,.22,1), height .6s cubic-bezier(.19,1,.22,1);
}
.btn:hover::before{ width:340px; height:340px }
.btn svg{ transition:transform .4s cubic-bezier(.34,1.56,.64,1) }
.btn:hover svg{ transform:translateX(3px) }

/* Primary button, singular flat fill — no gradient. Colour comes from
   --orange/--orange2 (css/theme.css, the 2026-08-13 shadcn-style blue
   palette: #2563EB light / #3B82F6 dark), not a literal hex, so this one
   surface tracks the token instead of needing its own edit the next time the
   accent changes. Shadow kept flat and low — 2026-08-13 follow-up: was a
   wider, brighter color-mix'd glow that read as "neon" against the flat
   palette everywhere else; this is a plain low dark shadow for lift, not a
   coloured one. The hover circle above is deepened with black rather than
   more of the same blue, so hovering reads as "richer," not "brighter." */
.btn-primary{ background:#0A192F; border-color:#0A192F;
  color:#fff; box-shadow:0 8px 16px -8px rgba(0,0,0,.25);
}
.btn-primary::before{ background:color-mix(in oklab, black 18%, transparent) }
.btn-primary:hover{ transform:translateY(-2px) scale(1.02);
  box-shadow:0 10px 20px -8px rgba(0,0,0,.3);
}
/* In dark theme --navy resolves to #F5F7FA, so the fill inverts to near-white.
   .btn-primary's own `color:#fff` then sat at 1.07:1 on it — the nav CTA lost
   its label entirely. The text has to invert with the fill, not just the fill. */
html[data-theme="dark"] .btn-primary{
  background:var(--navy); border-color:var(--navy); color:var(--ink);
}
html[data-theme="dark"] .btn-primary::before{ background:color-mix(in oklab, black 12%, transparent) }

.btn-secondary{ background:color-mix(in oklab, var(--txt) 4%, transparent);
  border-color:var(--edge2); color:var(--txt);
}
.btn-secondary:hover{ border-color:var(--orange2); transform:translateY(-2px) scale(1.02); }

/* Text-only, no fill or border at rest — for a tertiary action next to a
   primary/secondary pair, or standalone where a bordered button would be
   too heavy (e.g. inline in a card footer). */
.btn-ghost{ background:transparent; border-color:transparent; color:var(--orange2); padding-left:8px; padding-right:8px }
.btn-ghost:hover{ transform:none }

.btn:active{ transform:translateY(0) scale(.98); }
.btn-ghost:active{ transform:scale(.98); }

.btn-lg{ padding:16px 26px; font-size:var(--fs-btn-lg); min-height:56px; }

/* Loading state — swaps label opacity for a spinner rather than resizing the
   button, so nothing shifts when a submit starts. */
.btn[aria-busy="true"]{ color:transparent !important; pointer-events:none }
.btn[aria-busy="true"]::after{
  content:""; position:absolute; inset:0; margin:auto; width:16px; height:16px; border-radius:50%;
  border:2px solid color-mix(in oklab, var(--on-accent) 30%, transparent);
  border-top-color:var(--on-accent);
  animation:btn-spin .7s linear infinite;
}
.btn-secondary[aria-busy="true"]::after,
.btn-ghost[aria-busy="true"]::after{
  border-color:color-mix(in oklab, var(--orange2) 30%, transparent);
  border-top-color:var(--orange2);
}
@keyframes btn-spin{ to{ transform:rotate(360deg) } }

@media (prefers-reduced-motion:reduce){
  .btn{ transition:none; }
  .btn-primary:hover, .btn-secondary:hover{ transform:none; }
  .btn[aria-busy="true"]::after{ animation:none }
  .btn::before{ transition:none }
  .btn:hover::before{ width:0; height:0 }
  .btn svg{ transition:none }
  .btn:hover svg{ transform:none }
}

/* ── 5g.9 forced colors — keep every chart visible when the OS overrides ─── */

@media (forced-colors:active){
  .vl-dot, .vl-stem, .tg-fill, .kpi-meter > i, .case-rounds-fill,
  .sc-mixbar span, .viz-key i{
    background:CanvasText; forced-color-adjust:none;
  }
  .tg-rule, .kpi-meter > s{ background:CanvasText }
  /* A conic-gradient background image (unlike a background-color) is not
     preserved by forced-color-adjust:none — the OS strips it outright. The
     legend text still carries every count, so the donut degrades to a
     plain visible ring rather than disappearing. */
  .viz-donut{ background:Canvas !important; border:2px solid CanvasText; forced-color-adjust:none }
  .viz-donut-center{ background:Canvas !important }
  .rb-bar{ background:CanvasText !important; forced-color-adjust:none }
}

/* ── 5h. Metric card component (Collaborations page only) ────────────────────
   A generic icon/value/title/trend-or-link card, data-driven by js/metric-
   grid.js. This is a component preview using placeholder data — not a real
   TaskNTrade feature — so it carries the same .uc-badge-warm "not live" label
   the rest of the site already uses for coming-soon states, rather than being
   presented as real reporting. --metric-up/-down are scoped to this component
   only and reuse the exact values already proven for --ok elsewhere. */
:root{
  --metric-up:#15774B;
  --metric-down:#B3261E;
}
html[data-theme="dark"]{
  --metric-up:#6DBF9B;
  --metric-down:#FF8A72;
}

.metric-grid{ display:grid; gap:14px; grid-template-columns:1fr }
@media (min-width:700px) { .metric-grid{ grid-template-columns:repeat(2,1fr) } }
@media (min-width:1024px){ .metric-grid{ grid-template-columns:repeat(4,1fr) } }

.metric-card{
  border:1px solid var(--edge); border-radius:20px; padding:22px 20px;
  background:var(--panel); display:flex; flex-direction:column; gap:14px;
  min-height:150px;
}
.metric-icon{
  display:inline-flex; align-items:center; justify-content:center;
  width:40px; height:40px; border-radius:12px; flex-shrink:0;
  background:color-mix(in oklab, var(--orange) 12%, transparent);
  border:1px solid color-mix(in oklab, var(--orange) 25%, transparent);
  color:var(--orange2);
}
.metric-value{ font-family:var(--display); font-weight:800; font-size:clamp(26px,3vw,32px); letter-spacing:-.03em }
.metric-title{ font-size:13px; color:var(--dim); margin-top:4px }
.metric-foot{ margin-top:auto }
.metric-trend{ display:inline-flex; align-items:center; gap:5px; font-size:13px; font-weight:600 }
.metric-trend.is-up      { color:var(--metric-up) }
.metric-trend.is-down    { color:var(--metric-down) }
.metric-trend.is-neutral { color:var(--dim2) }
.metric-link{ display:inline-flex; align-items:center; gap:6px; font-size:13px; font-weight:600; color:var(--orange2) }

/* Loading skeleton — .metric-skel blocks are the only children while loading. */
.metric-card.is-loading .metric-skel{
  display:block; border-radius:8px;
  background:color-mix(in oklab, var(--txt) 6%, transparent);
  animation:metric-shimmer 1.4s ease-in-out infinite;
}
@keyframes metric-shimmer{ 0%,100%{opacity:.55} 50%{opacity:1} }

/* Error state — kept as a real, styled path so a future live data source has
   somewhere to render a failure, but nothing in the current placeholder data
   triggers it (shipping a fake error on a real page would be its own lie). */
.metric-card.is-error{ color:var(--dim) }
.metric-errmsg{ display:flex; align-items:center; gap:8px; font-size:13px }

@media (prefers-reduced-motion:reduce){
  .metric-card.is-loading .metric-skel{ animation:none; opacity:.7 }
}

/* 5j (grayscale-at-rest trust-logo marquee) removed 2026-08-13 at the user's
   request — logos show in their real color at rest now, same base .h-op1
   opacity-lift-on-hover as every other pass over this marquee. */

/* 5k (brochure accordion) removed 2026-08-13 along with the brochure section
   rewrite below (§5r) — the accordion no longer has any markup using it. */

/* 5l (all-brands dashboard table, Signal page) removed 2026-08-13 at the
   user's request — the ten-row relationship table was judged unnecessary
   next to the per-case viewer immediately below it on the same page. */

/* 5m (Also worked with — brand bento) removed 2026-08-13 at the user's
   request — the 18-brand grid was judged unnecessary on this page.
   .brand-tile-mono survives below: the testimonial rail above still uses it
   for Fonepoints/Saino's letter-badge fallback, its only remaining
   consumer. */
.brand-tile-mono{
  display:inline-flex; align-items:center; justify-content:center;
  width:52px; height:52px; border-radius:14px; flex-shrink:0;
  background:color-mix(in oklab, var(--orange) 12%, transparent);
  border:1px solid color-mix(in oklab, var(--orange) 25%, transparent);
  font-family:var(--display); font-weight:800; font-size:17px; color:var(--orange2);
}

/* Results-rail card (Collaborations page). Was six near-identical inline
   `style=` blocks; pulled into a class so the hover lift below doesn't need
   to be duplicated six times, and so the card can react to hover at all —
   an inline style has no pseudo-class to hang one on. The rail's own
   .ribbon:hover already pauses the marquee; this adds a per-card response on
   top of that, same lift+shadow recipe .btn-primary:hover already uses. */
.testimonial-card{
  flex:0 0 380px; border:1px solid var(--edge); border-radius:22px;
  padding:26px; background:var(--panel);
  transition:transform .22s cubic-bezier(.16,1,.3,1), border-color .22s ease, box-shadow .22s ease;
}
.testimonial-card:hover{
  transform:translateY(-3px);
  border-color:color-mix(in oklab, var(--orange) 32%, var(--edge));
  box-shadow:0 14px 28px -14px rgba(0,0,0,.3);
}
@media (prefers-reduced-motion:reduce){
  .testimonial-card{ transition:none }
  .testimonial-card:hover{ transform:none }
}

/* 5n (audit & transparency panel) removed 2026-08-13 along with the Signal
   page's OSM Model section, at the user's request — both read as too much
   for a sales-facing page. */

/* 5o (creator roadmap) removed 2026-08-13 — folded into the brochure
   rewrite's per-audience bento (§5r), which replaced both sides' panels. */

/* ── 5p. Hero rotating word — typewriter (js/site.js initRotator) ───────────
   Redesign pass, 2026-08-13 (follow-up): was a plain crossfade between whole
   words; now js/site.js types and erases character by character, and this
   is just the blinking caret that goes with it. Reduced-motion drops the
   animation AND initRotator returns early, so the caret sits solid instead
   of blinking next to a word that never changes. */
.hero-rotator{ display:inline-block }
.hero-rotator::after{
  content:""; display:inline-block; width:3px; height:.9em; margin-left:2px;
  background:currentColor; vertical-align:-.05em;
  animation:hero-caret 1s step-end infinite;
}
@keyframes hero-caret{ 50%{ opacity:0 } }
@media (prefers-reduced-motion:reduce){
  .hero-rotator::after{ animation:none }
}

/* ── 5q. Audience toggle (Brands / Creators) — hero + brochure ────────────────
   Redesign pass, 2026-08-13 (js/site.js initAudienceToggles). Follow-up,
   same day: replaced the flat background-swap on .is-on with a single
   sliding pill (.aud-toggle-fill, positioned in JS since the two labels are
   different widths) so the active state visibly travels between tabs
   instead of just recoloring in place — asked for as "better switch
   animation," and it also gives the eye something to track right as a
   typewriter cycle hands off between panels. */
.aud-toggle{ position:relative; display:inline-flex; gap:4px; padding:4px; border-radius:999px; background:color-mix(in oklab, var(--txt) 5%, transparent); border:1px solid var(--edge); margin-bottom:22px }
.aud-toggle-fill{
  position:absolute; top:4px; bottom:4px; left:0; width:0; border-radius:999px;
  background:var(--panel); box-shadow:0 1px 2px rgba(0,0,0,.06);
  transition:transform .45s cubic-bezier(.65,0,.35,1), width .45s cubic-bezier(.65,0,.35,1);
}
.aud-tab{
  position:relative; z-index:1;
  font-size:13px; font-weight:600; padding:9px 16px; border-radius:999px;
  border:none; cursor:pointer; background:none; color:var(--dim);
  transition:color .3s ease;
}
.aud-tab.is-on{ color:var(--txt) }
@media (prefers-reduced-motion:reduce){
  .aud-toggle-fill{ transition:none }
}

/* Panel crossfade — redesign pass, 2026-08-13 (follow-up): the switch used
   to be an instant [hidden] flip, which read as a jump right as a
   typewriter cycle finished. js/site.js now sequences outgoing-fade, THEN
   the hidden-attribute swap, THEN incoming-fade, so this only needs a
   single opacity/transform transition plus the two transient classes that
   bracket it — no absolute positioning, since the two panels are never
   visible at the same instant. */
[data-aud-panel]{ transition:opacity .22s cubic-bezier(.4,0,.2,1), transform .22s cubic-bezier(.4,0,.2,1) }
[data-aud-panel].aud-panel-out{ opacity:0; transform:translateY(-8px) }
[data-aud-panel].aud-panel-in{ opacity:0; transform:translateY(8px) }
@media (prefers-reduced-motion:reduce){
  [data-aud-panel]{ transition:none }
}

/* ── 5r. Brochure split-pillar layout — redesign pass, 2026-08-13 ─────────────
   Replaces the two always-visible glass panels (§5a/§5k, both removed) with
   one pillar at a time: a value-prop card plus a small bento of real
   numbers. flat/matte throughout — no backdrop-filter, no decorative blobs. */
.pillar-wrap{ display:grid; gap:20px; grid-template-columns:1fr }
@media (min-width:960px){ .pillar-wrap{ grid-template-columns:5fr 7fr; align-items:stretch } }

.pillar-card{
  display:flex; flex-direction:column; border:1px solid var(--edge); border-radius:24px;
  padding:32px 28px; background:var(--panel);
}
.pillar-icon{
  display:inline-flex; align-items:center; justify-content:center; flex-shrink:0;
  width:48px; height:48px; border-radius:14px;
  background:color-mix(in oklab, var(--orange) 12%, transparent);
  border:1px solid color-mix(in oklab, var(--orange) 25%, transparent);
  color:var(--orange2);
}
.pillar-card-foot{ margin-top:auto; padding-top:22px }

.pillar-bento{ display:grid; gap:14px; grid-template-columns:1fr }
@media (min-width:560px){ .pillar-bento{ grid-template-columns:1fr 1fr } }

.pillar-stat{
  border:1px solid var(--edge); border-radius:18px; padding:20px;
  background:var(--panel); display:flex; flex-direction:column; gap:2px;
}
.pillar-stat-icon{ color:var(--dim2); margin-bottom:12px }
.pillar-stat-val{ font-family:var(--display); font-weight:800; font-size:24px; letter-spacing:-.02em }
.pillar-stat-lab{ font-size:12px; font-weight:600; color:var(--dim); margin-top:2px }
.pillar-stat-note{ margin:8px 0 0; font-size:12px; line-height:1.5; color:var(--dim2) }

.pillar-highlight{
  grid-column:1 / -1; display:flex; align-items:center; justify-content:space-between; gap:16px;
  border:1px solid var(--edge); border-radius:18px; padding:18px 20px;
  background:color-mix(in oklab, var(--txt) 4%, transparent);
}
.pillar-highlight-title{ font-size:14px; font-weight:700 }
.pillar-highlight-sub{ margin-top:3px; font-size:12.5px; color:var(--dim) }
.pillar-highlight-badge{
  flex-shrink:0; font-size:12px; font-weight:700; font-family:monospace;
  padding:6px 11px; border-radius:8px; background:var(--panel); border:1px solid var(--edge);
  color:var(--orange2);
}

/* ── 5s. Reach-estimator result tiles (/partner/ only) ─────────────────────
   Three tiles (views / cost per 1,000 / creators) since the 2026-10-02
   shortening: stacked on phones, three across from 640px. */
[data-est-grid3]{ grid-template-columns:1fr }
@media (min-width:640px){ [data-est-grid3]{ grid-template-columns:repeat(3,1fr) } }
/* "Other campaigns" cards: compact, three across from 640px. */
@media (min-width:640px){ [data-est-approaches-body]{ grid-template-columns:repeat(3,1fr) } }
.cm-compact{ display:flex; flex-direction:column }
.cm-compact .cm-head{ flex-direction:column; gap:4px }
.cm-compact .cm-line{ font-size:13.5px; line-height:1.55; color:var(--dim); margin-top:8px }
.cm-compact .cm-foot{ margin-top:auto; padding-top:12px }

/* §5w — case index grid (/services/signal/case-studies/), 2026-08-26.
   Every campaign in the dataset gets a named, clickable card here. It exists
   because the page's only selector until now was a closed <select>: a campaign
   could be present in js/campaigns.js, correct on every chart, and still
   unnamed anywhere a visitor could see — which is exactly what happened to
   GWM and Expert Education when they were added. Cards are <button>s, not
   links: they drive the in-page viewer below rather than navigating, so the
   pressed state is real aria-pressed and the keyboard gets it for free.
   Auto-fit rather than a fixed column count — twelve brands at three
   breakpoints is the kind of thing that goes stale as the dataset grows. */
.ci-grid{
  display:grid; gap:12px;
  grid-template-columns:repeat(auto-fit, minmax(210px, 1fr));
}
.ci-card{
  display:flex; flex-direction:column; align-items:flex-start; gap:8px;
  text-align:left; cursor:pointer; font:inherit; color:var(--txt);
  padding:16px 16px 14px;
  border:1px solid var(--edge); border-radius:16px;
  background:color-mix(in oklab, var(--txt) 3%, transparent);
  transition:border-color .18s ease, background .18s ease, transform .18s ease;
}
.ci-card:hover{
  border-color:color-mix(in oklab, var(--orange) 42%, var(--edge));
  background:color-mix(in oklab, var(--orange) 5%, transparent);
}
/* Selected state carries a real border and fill, not just a colour shift:
   this is the control that tells you which case the viewer below is showing,
   and the site's one dark-theme-safe way to say "this one" is the accent
   tint the nav pills already use. */
.ci-card.is-on{
  border-color:var(--orange2);
  background:color-mix(in oklab, var(--orange) 11%, transparent);
}
.ci-badge{
  font-size:10.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  color:var(--orange2);
}
.ci-name{
  font-family:var(--display); font-weight:800; font-size:17px; letter-spacing:-.02em;
  line-height:1.15;
}
.ci-figs{ display:flex; flex-wrap:wrap; gap:4px 14px; margin-top:auto; padding-top:4px }
.ci-fig{ display:flex; flex-direction:column; line-height:1.2 }
.ci-fig b{ font-family:var(--display); font-weight:700; font-size:15px; letter-spacing:-.02em }
.ci-fig i{ font-style:normal; font-size:11px; color:var(--dim2) }
@media (prefers-reduced-motion:reduce){
  .ci-card{ transition:none }
}

/* §5x — matched case study on /partner/, 2026-08-26.
   The estimator's result panel now closes on a real campaign from the audited
   portfolio rather than only on projected numbers. Deliberately styled as a
   quotable panel, not a fifth KPI tile: everything above it in that panel is
   an estimate from illustrative constants, and this is the one block on the
   screen whose figures were actually delivered. The distinction has to be
   visible, hence its own surface and the "delivered" framing in the markup. */
.cm-card{
  border:1px solid var(--edge2); border-radius:18px; padding:20px;
  background:color-mix(in oklab, var(--txt) 3%, transparent);
}
.cm-head{ display:flex; flex-wrap:wrap; align-items:baseline; gap:8px 12px }
.cm-name{
  font-family:var(--display); font-weight:800; font-size:19px; letter-spacing:-.025em;
}
.cm-badge{
  font-size:10.5px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
  color:var(--orange2);
}
.cm-why{ font-size:13.5px; line-height:1.6; color:var(--dim); margin-top:8px }
.cm-figs{
  display:grid; gap:10px; margin-top:16px;
  grid-template-columns:repeat(auto-fit, minmax(120px, 1fr));
}
.cm-fig{
  border:1px solid var(--edge); border-radius:13px; padding:12px 13px;
  background:var(--panel);
}
.cm-fig b{
  display:block;
  font-family:var(--display); font-weight:800; font-size:19px; letter-spacing:-.03em;
}
.cm-fig span{
  display:block; font-size:11px; font-weight:700; letter-spacing:.1em;
  text-transform:uppercase; color:var(--dim2); margin-top:3px;
}
.cm-result{ font-size:14.5px; font-weight:700; line-height:1.6; margin-top:14px }
.cm-foot{ display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin-top:14px }

/* ---- §5f. Legal pages ------------------------------------------------------
   /privacy/, /terms/, /cookies/, /refunds/. These are the only long-form prose
   pages on the site, so they get real classes instead of the inline-style idiom
   the design-transcribed pages use: there is no design to be faithful to here,
   and four pages repeating the same twelve declarations inline is how they drift
   apart. Type scale reuses the existing --fs-* tokens.
   -------------------------------------------------------------------------- */
.legal{ max-width:760px; margin:0 auto; padding:70px 22px 90px }
.legal-eyebrow{
  font-size:11.5px; font-weight:600; letter-spacing:.18em;
  text-transform:uppercase; color:var(--orange2);
}
.legal h1{
  font-family:var(--display); font-weight:800;
  font-size:clamp(36px,5vw,60px); line-height:1; letter-spacing:-.035em;
  margin-top:16px;
}
.legal-meta{ margin-top:18px; font-size:13.5px; color:var(--dim2) }
.legal-lede{
  margin-top:22px; font-size:17px; line-height:1.75; color:var(--dim);
  padding-bottom:26px; border-bottom:1px solid var(--edge);
}
.legal h2{
  font-family:var(--display); font-weight:700; font-size:24px;
  letter-spacing:-.02em; margin-top:44px;
}
.legal h3{ font-size:16px; font-weight:700; margin-top:26px }
.legal p, .legal li{ font-size:15px; line-height:1.8; color:var(--dim); margin-top:14px }
.legal ul{ margin-top:6px; padding-left:22px; list-style:disc }
.legal li{ margin-top:8px }
.legal a{ color:var(--orange2); text-decoration:underline; text-underline-offset:3px }
.legal strong{ color:var(--txt); font-weight:700 }
.legal table{ width:100%; border-collapse:collapse; margin-top:18px; font-size:14px }
.legal th, .legal td{
  text-align:left; padding:11px 12px; border:1px solid var(--edge);
  line-height:1.6; vertical-align:top;
}
.legal th{ background:color-mix(in oklab, var(--txt) 3.5%, transparent); font-weight:700 }
.legal td{ color:var(--dim) }
/* Wide tables must scroll inside themselves, never the page body. */
.legal-scroll{ overflow-x:auto; -webkit-overflow-scrolling:touch }
.legal-note{
  margin-top:26px; padding:18px 20px; border-radius:16px;
  border:1px solid color-mix(in oklab, var(--orange) 32%, transparent);
  background:color-mix(in oklab, var(--orange) 8%, transparent);
}
.legal-note p{ margin-top:0; color:var(--txt) }
.legal-toc{
  margin-top:30px; padding:20px 22px; border-radius:16px;
  border:1px solid var(--edge); background:var(--panel);
}
.legal-toc p{ margin-top:0; font-size:11.5px; font-weight:600; letter-spacing:.14em;
  text-transform:uppercase; color:var(--dim2) }
.legal-toc ul{ list-style:none; padding-left:0; margin-top:12px }
.legal-toc li{ margin-top:7px }

/* ---- §5g. Form consent -----------------------------------------------------
   The privacy acknowledgement that sits above every submit button. A class,
   not four inline copies, because the same box appears on all four forms and
   copies drift. Deliberately NOT styled like the /creators/ survey-consent box
   (which is orange-tinted): that one is a separate, narrower, optional consent
   and the two should not read as the same control.

   No `required` attribute anywhere on these — on /partner/ the box lives on
   wizard step 2, and a hidden required field blocks submission with no visible
   bubble. Validation is JS-side in forms.js validate() plus REQUIRED_FIELDS
   server-side, which is the pattern the rest of this form already uses.
   -------------------------------------------------------------------------- */
.consent-box{
  display:flex; align-items:flex-start; gap:12px;
  padding:15px 16px; border-radius:14px; cursor:pointer;
  border:1px solid var(--edge2);
  background:color-mix(in oklab, var(--txt) 3%, transparent);
}
.consent-box input[type=checkbox]{
  margin-top:1px; width:20px; height:20px; flex-shrink:0;
  accent-color:var(--orange); cursor:pointer;
}
.consent-box span{ font-size:13.5px; line-height:1.6; color:var(--txt) }
.consent-box a{ color:var(--orange2); text-decoration:underline; text-underline-offset:2px }
.consent-box.is-bad{
  border-color:color-mix(in oklab, var(--orange) 55%, transparent);
  background:color-mix(in oklab, var(--orange) 9%, transparent);
}

/* Header CTA row, 2026-10-02: the Careers button made the row too wide for
   1024–1279px, where "For creators" and "Partner with us" wrapped to two lines.
   Nothing in the row wraps; "For creators" steps out in that band only (it is
   still in the mobile menu and the footer). */
[data-deskcta] a { white-space:nowrap }
@media (min-width:1024px) and (max-width:1279px){
  [data-deskcta] [data-narrow-hide] { display:none }
  [data-desknav] .h-navpill         { padding:9px 8px !important; font-size:13px !important }
  [data-deskcta]                    { gap:8px !important }
  [data-deskcta] .btn               { padding:12px 14px !important }
  [data-deskcta] .btn svg           { display:none }
  [data-siteheader] > div           { gap:12px !important }
}

/* /about/ team-lead cards: small portrait beside the text (2026-10-02). */
[data-leadcard]{ display:flex; align-items:center; gap:16px }

/* Portraits never move: no shimmer (that is .uc-tile, for empty placeholders
   only) and no transition from the global img rule in css/theme.css. */
.photo-slot img { transition:none }

/* ── Case-study lock (2026-10-05) ──────────────────────────────────────────
   The lock panel on /services/signal/case-studies/, the team-code disclosure,
   and copy deterrence on the unlocked viewer. The real lock is server-side
   (functions/api/cases.js); this only discourages copying what an unlocked
   visitor sees. A screenshot can't be prevented, so the card carries a faint
   "Prepared for <company>" watermark. */
.cs-lock{ display:grid; gap:28px; grid-template-columns:1fr; align-items:start }
@media (min-width:900px){ .cs-lock{ grid-template-columns:5fr 6fr; gap:48px } }
.cs-team{ margin-top:16px; font-size:14px; color:var(--dim) }
.cs-team summary{ cursor:pointer; font-weight:600 }
.cs-team form{ display:flex; flex-wrap:wrap; gap:10px; margin-top:12px }
.cs-team input{ flex:1 1 200px }
.cs-team [data-cases-team-msg]{ flex-basis:100%; margin:0; color:var(--orange2) }
[data-sig-viewer].is-guarded{ -webkit-user-select:none; user-select:none; -webkit-touch-callout:none }
.cs-confidential{ margin:18px 0 0; font-size:11.5px; font-weight:600; letter-spacing:.14em;
  text-transform:uppercase; color:var(--dim2) }
.cs-confidential:empty{ display:none }
[data-sig-viewer].is-marked{ position:relative }
[data-sig-viewer].is-marked::after{ content:""; position:absolute; inset:0; pointer-events:none;
  z-index:3; background-image:var(--wm); background-repeat:repeat }
@media print{ [data-sig-viewer], [data-cases-lock]{ display:none !important } }

/* ── Careers: hiring areas (2026-10-07) ─────────────────────────────────────
   One card per department; 1 column on phones, 2 from 640px, 3 from 1000px. */
.hire-areas{ list-style:none; padding:0; margin:14px 0 0; display:grid; gap:10px; grid-template-columns:1fr }
@media (min-width:640px){ .hire-areas{ grid-template-columns:repeat(2,1fr) } }
@media (min-width:1000px){ .hire-areas{ grid-template-columns:repeat(3,1fr) } }
.hire-areas li{ border:1px solid var(--edge); border-radius:16px; padding:16px 18px; background:var(--panel); display:grid; gap:4px }
.hire-areas b{ font-family:var(--display); font-weight:700; font-size:16.5px; letter-spacing:-.01em }
.hire-areas span{ font-size:14px; line-height:1.5; color:var(--dim) }
.hire-jd-soon{ margin-top:6px; font-size:13px; font-style:normal; color:var(--dim2) }
.hire-status{ justify-self:start; font-style:normal; font-size:11.5px; font-weight:700; letter-spacing:.04em; padding:3px 9px; border-radius:999px; margin-bottom:4px;
  background:color-mix(in oklab, var(--ok) 14%, transparent); color:var(--ok) }
.hire-status.is-closed, .hire-status.is-not_hiring{ background:color-mix(in oklab, var(--txt) 8%, transparent); color:var(--dim) }
.hire-areas li.is-closed{ opacity:.72 }
.hire-jd{ margin-top:6px; font-size:14px }
.hire-jd summary{ cursor:pointer; font-weight:600; color:var(--orange2); text-decoration:underline; text-underline-offset:3px; padding:4px 0 }
.hire-jd + .hire-jd{ margin-top:0 }
.hire-jd h3{ font-size:15.5px; margin:10px 0 4px }
.hire-jd h4{ font-size:13px; text-transform:uppercase; letter-spacing:.08em; color:var(--dim2); margin:12px 0 4px }
.hire-jd p, .hire-jd ul{ margin:0; color:var(--dim); line-height:1.55 }
.hire-jd ul{ padding-left:18px }
.hire-areas .hire-jd li{ display:list-item; border:0; border-radius:0; padding:2px 0; background:none; font-size:14px }
.hire-areas .hire-any{ background:color-mix(in oklab, var(--orange) 9%, transparent); border-color:color-mix(in oklab, var(--orange) 28%, transparent) }

/* Mobile audit 2026-10-07: footer links were 23px tall, under the 24px
   minimum touch target. A little vertical padding, no visual change. */
@media (max-width:640px){ footer a.h-txt{ display:inline-block; padding:4px 0 } }

/* ── Forms side by side on wide screens (2026-10-07) ────────────────────────
   .f-grid forms pair short fields (.f-half) two to a row from 900px; anything
   else (messages, pills, consent, errors, submit) spans the full width.
   Phones keep the single column. */
@media (min-width:900px){
  .f-grid{ grid-template-columns:1fr 1fr; column-gap:18px }
  .f-grid > *{ grid-column:1 / -1 }
  .f-grid > .f-half{ grid-column:auto }
}
/* Careers: hiring areas beside the form on desktop; the areas stack in one
   column there and stay put while the form scrolls. */
.careers-split{ max-width:1240px; margin:0 auto; padding:0 22px; display:grid; gap:36px }
@media (min-width:1000px){
  .careers-split{ grid-template-columns:4fr 7fr; gap:44px; align-items:start }
  .careers-areas{ position:sticky; top:96px }
  .careers-areas .hire-areas{ grid-template-columns:1fr }
}


/* About › Leadership holds two cards since the CEO moved to the founder block
   above (owner's call, 2026-10-09): two columns at the size three had, not a
   gap where the third was. Overrides §1-4's repeat(3,1fr). */
@media (min-width:700px){
  [data-teamphotos]     { grid-template-columns:repeat(2,minmax(0,380px)); justify-content:center }
}
