@view-transition { navigation: auto; }
::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.45s; }
::view-transition-old(nsp-headline), ::view-transition-new(nsp-headline) { animation-duration: 0.5s; animation-timing-function: cubic-bezier(0.4,0,0.2,1); }

/* ======================================================
   CAVEAT, SELF-HOSTED

   Raj, 2026-08-12: on his phone the "(badly)" aside on About rendered in a
   font that was not Caveat, while desktop was correct. It could not be
   reproduced in a phone-width Chromium -- Caveat renders there -- which
   leaves the one thing that genuinely differs per platform: the generic
   `cursive` fallback. On iOS that resolves to Snell Roundhand, a copperplate
   script that looks nothing like Caveat's marker hand, so any moment the
   webfont is late or missing the word changes typeface. Serving the file
   ourselves removes the third-party request, the UA sniffing and the
   fallback window in one go, so every device gets the same letterforms.

   The latin subset only (the three marks on this site are ascii), and it is
   the variable file, so one 51KB request covers the 600 About and Work With
   Me use and any weight a later mark wants. Caveat is OFL -- self-hosting is
   what the licence is for. No build step: it is a static file.
====================================================== */
@font-face{
  font-family:'Caveat';
  font-style:normal;
  font-weight:400 700;
  font-display:swap;
  src:url('/fonts/caveat-latin-var.woff2') format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,
                U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,
                U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

/* ======================================================
   GLOBAL RESET + SCREEN SYSTEM
====================================================== */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
/* `[hidden]` is a UA rule at the lowest possible specificity, so any author
   rule that sets `display` beats it. The mobile pass sets
   `a,button{display:inline-flex}` on every link and button on the site,
   which means `el.hidden = true` on either of those does nothing at all
   under 640px -- found 2026-10-02 when the second route's trigger stayed on
   screen after a successful send on a phone and vanished correctly on a
   desktop. Stating it once here fixes the whole class rather than the one
   instance. */
[hidden]{display:none!important;}
html{background:#f5f2ee;}
body{margin:0;padding:0;overflow:hidden;width:100%;height:100vh;height:100dvh;}

:root{
  --bg-warm:#f5f2ee;--ink:#1a1814;--ink-muted:#8a8580;--ink-faint:#c8c3bc;
  /* --ink-muted is 3.2:1 on --bg-warm — under the 4.5:1 floor, so it is for
     tracked labels and eyebrows only. Anything set as a sentence uses this. */
  --ink-body:#5a544c;   /* 6.6:1 on --bg-warm */
  --accent-r:#ff7bac;--accent-g:#3a8c52;--accent-b:#0d8aaf;
  /* the two accents' light tints -- tribe (0)'s sky, and playground's notes
     since 2026-10-06. Ink on them, never paper. */
  --tint-b:color-mix(in srgb, var(--accent-b) 50%, #fff);
  --tint-r:color-mix(in srgb, var(--accent-r) 55%, #fff);
  /* the green tint, Raj's pick off a ten-step ramp of --accent-g (2026-10-06).
     Measured: luminance 0.498 against --tint-b's 0.499, ink on it 9.26:1 --
     the blue tint's own 9.27 -- so the three tints sit level. */
  --tint-g:#9cc5a8;
  --shadow:rgba(30,25,20,0.12);

  /* ---- Easing ----
     Four curves, named, and nothing else ships. The file had seven
     hand-written cubic-beziers with no way to tell which was the house
     curve, which is how a bounce ended up on a link hover and a second
     "standard" curve ended up next to the first.

       --e-out      anything entering, exiting or answering a press. Starts
                    fast, so the interface replies in the frame the user is
                    watching hardest. This is the default; reach for it
                    unless one of the three below is the reason.
       --e-in-out   something already on screen moving to a new place, where
                    both ends need to be gentle.
       --e-drawer   Ionic's drawer curve. The CIAD deck slides on it and the
                    price fill wipes on it -- long travel that should feel
                    weighted rather than snappy.
       --e-dissolve the video intro only. A film dissolve over a second is
                    not a UI transition and should not borrow the UI curve.
       --e-spring   overshoots, and is allowed in exactly one place: the
                    playground recenter, where thirty cards fly home on a
                    stagger. That is a rare, deliberate, playful action.
                    It was also on the CTA hover, which is none of those --
                    a link that springs past its size every time the cursor
                    crosses it. If you are reaching for this on anything a
                    visitor triggers more than once a page, reach for
                    --e-out instead.

     Loops keep their own literal curve at the call site, because a curve
     that has to be symmetric to loop cleanly is not one of these five. */
  --e-out:cubic-bezier(0.23,1,0.32,1);
  --e-in-out:cubic-bezier(0.77,0,0.175,1);
  --e-drawer:cubic-bezier(0.32,0.72,0,1);
  --e-dissolve:cubic-bezier(0.4,0,0.2,1);
  --e-spring:cubic-bezier(0.34,1.56,0.64,1);

  /* ---- Type scale ----
     Twelve steps, no in-between values. If a new size feels needed, one of
     these is the right one — the gaps are what make the hierarchy readable.
     13-15 are tracked uppercase labels only; 16+ is anything read as a
     sentence. Nothing below 13px ships. */
  --t-micro:13px;    /* uppercase eyebrows, tracked +0.16em */
  --t-tiny:14px;    /* small mono UI, chips */
  --t-fine:15px;    /* nav, buttons, captions */
  --t-body:16px;    /* mono body, compact */
  --t-read:17px;    /* mono body, comfortable */
  --t-lead:18px;    /* lead paragraphs */
  --t-h6:20px;
  --t-h5:22px;
  --t-h4:24px;
  --t-h3:26px;
  --t-h2:28px;
  --t-h1:32px;      /* above this, use a clamp() — display type is fluid */

  /* ---- Prose ----
     Sentences are set in the serif; mono is for labels, data and controls.
     Cormorant has a small x-height, so its reading sizes run ~4px larger than
     a sans would to land at the same optical size. */
  --t-prose:22px;
  --t-prose-sm:20px;  /* prose inside a narrow card or list */
  --t-prose-lead:25px;
  --lh-serif:1.55;

  /* ---- Line-height, by role ---- */
  --lh-display:1.05;  /* big serif, set tight */
  --lh-title:1.22;
  --lh-ui:1.5;        /* buttons, labels, single-line UI */
  --lh-prose:1.66;    /* anything with more than one sentence */

  /* ---- Tracking ---- */
  --tr-label:0.16em;  /* uppercase mono */
  --tr-ui:0.08em;
  --tr-display:-0.015em;
}

/* Focus visible — keyboard nav ring */
:focus-visible{outline:2px solid var(--accent-b);outline-offset:3px;border-radius:3px;}
:focus:not(:focus-visible){outline:none;}

/* Visually hidden, still read by screen readers and indexed. Not display:none
   (which removes it from the a11y tree) and not opacity:0 (still focusable). */
.sr-only{
  position:absolute;width:1px;height:1px;
  padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);clip-path:inset(50%);
  white-space:nowrap;border:0;
}

/* ======================================================
   TYPOGRAPHIC BASE
   Applies across both design systems (warm paper + iOS dark).
   Everything here is a default the section rules can still override.
====================================================== */
/* Subpixel smoothing (the default) renders dark-on-light text heavier and
   crisper. `antialiased` thins it — wrong for a light serif at reading size on
   warm paper, which is most of this site. The dark CIAD screen is the
   exception: light-on-dark genuinely wants the thinner rendering. */
body{
  text-rendering:optimizeLegibility;
}
#screen-video{
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale;
}
/* Headings hold together instead of dropping a single orphan word. */
h1,h2,h3,h4,h5,h6,
.hub-title,.hub-opt-title,.reel-intro-title,.reel-name,
.cs-title,.about-title,.od-h,.pg-headline{
  text-wrap:balance;
}
/* Prose gets even rag and no one-word last line. */
p,li,dd,figcaption,
.hub-opt-desc,.reel-credit-row,.od-lead,.od-tier-desc{
  text-wrap:pretty;
}
/* Numerals line up in columns and tables of figures. */
.hub-proof-num,.hub-fact dd,.od-tier-price,.reel-groupmark-n{
  font-variant-numeric:tabular-nums;
  font-feature-settings:"tnum" 1;
}
/* Measure: mono at 12-14px runs unreadable past ~66 characters. */
.hub-opt-desc,
.od-lead,.reel-credit-row{
  max-width:62ch;
}

.screen{position:fixed;inset:0;}
.screen.hidden{opacity:0;visibility:hidden;pointer-events:none;z-index:-1!important;}
#crossfade{position:fixed;inset:0;z-index:99999;background:#0d0d0f;opacity:0;pointer-events:none;transition:opacity 0.45s ease;}
#crossfade.active{opacity:1;pointer-events:all;}
/* Light crossfade variant for warm->warm transitions */
#crossfade.light{background:var(--bg-warm);}

/* ======================================================
   SCREEN 0: VIDEO INTRO
====================================================== */
#screen-video{z-index:500;background:#0d0d0f;overflow:hidden;transition:opacity 1s var(--e-dissolve);height:100vh;height:100dvh;}
#screen-video.fading-out{opacity:0;}

#controls-cover{position:absolute;bottom:0;left:0;right:0;height:52px;z-index:6;background:linear-gradient(to top,rgba(0,0,0,0.7) 0%,transparent 100%);pointer-events:none;}
#vid-cover{position:fixed;inset:0;z-index:20;background:#0d0d0f;display:flex;align-items:center;justify-content:center;flex-direction:column;transition:opacity 1.2s var(--e-dissolve);}
#vid-cover.fade{opacity:0;pointer-events:none;}
#vid-thumbnail{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;transition:opacity 0.6s ease;}
#vid-thumbnail.show{opacity:1;}
@keyframes logo-breathe{0%,100%{opacity:1;transform:scale(1);}50%{opacity:0.65;transform:scale(0.975);}}
#raj-logo{position:relative;z-index:2;width:min(160px,30vw);height:auto;opacity:0;transition:opacity 0.35s ease,transform 0.35s ease;}
#raj-logo.in{opacity:1;transform:scale(1);}
#raj-logo.waiting{animation:logo-breathe 2.6s cubic-bezier(0.45,0,0.55,1) infinite;}
#raj-logo.out{opacity:0;transform:scale(0.97);animation:none;}
#vid-text-overlay{position:fixed;inset:0;z-index:650;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(0,0,0,0.25);pointer-events:none;transition:background 0.6s var(--e-dissolve);}
#vid-text-overlay.clear{background:transparent;}
#vid-headline{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;font-size:clamp(32px,calc(5vw + 4px),46px);color:#fff;letter-spacing:0.015em;text-shadow:0 2px 20px rgba(0,0,0,0.5);opacity:0;transform:translateY(6px);transition:opacity 0.5s var(--e-dissolve),transform 0.5s var(--e-dissolve),color 0.5s var(--e-dissolve),text-shadow 0.5s var(--e-dissolve);view-transition-name:nsp-headline;}
#vid-headline.show{opacity:1;transform:translateY(0);}
#vid-headline.out{opacity:0;transition:opacity 0.4s ease;}
#vid-headline.dark{color:var(--ink);text-shadow:none;}
#vid-white{position:fixed;inset:0;z-index:600;background:var(--bg-warm);opacity:0;pointer-events:none;transition:opacity 0.6s var(--e-dissolve);}
#vid-white.flash{opacity:1;}


/* ======================================================
   SCREEN 1: PLAYGROUND
====================================================== */
#screen-playground{z-index:100;background:var(--bg-warm);font-family:'DM Mono',monospace;cursor:crosshair;user-select:none;-webkit-user-select:none;-webkit-tap-highlight-color:transparent;}

/* ── the nav: a floating capsule, bottom centre ─────────────────────────
   It was a full-width bar welded to the top edge with a hairline under it.
   Raj, 2026-08-26, against oddfellows.tv: take the placement and the
   behaviour from there, not the material. So this is still warm paper at 92%
   behind the same blur(16px) it always had, and it still carries the brand
   and every link -- their bar is three words of glass that picks up whatever
   image is behind it, which works because their whole site is full-bleed
   photography and ours is flat paper on four pages out of seven.

   What did come from them: it floats, it is bottom-anchored so it grows
   upward, and the link row stays welded to its bottom edge while it grows.
   That last part is the whole trick -- a panel that appears above a bar is a
   popover, a bar whose top edge moves up is one object getting bigger.

   Bottom-anchored also means every page had to give back the strip it was
   using down there: --nav-clear is published by shared.js and CIAD's bar,
   playground's recenter, portfolio's hint and cue, About's mosaic floor and
   the two scrolling pages' last row all sit on it. The top clearance those
   pages were paying for the old bar comes back, which is roughly what the
   bottom now costs. */
#pg-nav{
  --nav-gap:20px;
  --nav-r:20px;
  --nav-fg:#f5f2ee;      /* 15.06:1 on the bar */
  --nav-dim:#b3ada4;     /* still 7.4:1 -- dim by colour, never by opacity */
  position:fixed;z-index:900;
  left:50%;bottom:var(--nav-gap);transform:translateX(-50%);
  width:max-content;max-width:calc(100vw - 28px);
  display:flex;flex-direction:column;align-items:stretch;
  background:transparent;
  border:1px solid rgba(255,255,255,0.10);
  border-radius:var(--nav-r);
  /* the site's own two-part shadow, same language as the hero tile: a tight
     contact shadow so it sits on the paper, a long soft one so it floats
     off it. A floating object needs both; the old bar needed neither. */
  box-shadow:0 3px 10px rgba(20,16,12,0.20), 0 26px 60px -18px rgba(20,16,12,0.46);
  /* No overflow:hidden here. It is what a backdrop-filter cannot survive on
     a box whose height is animating -- Chrome stops repainting the filter
     over part of the capsule and the page behind shows through the panel,
     sharp and unblurred, with About's own paragraphs running through the
     rows. The clip belongs to .nav-panel, which is the only thing that needs
     clipping, and which is not the filtered box. */
  overflow:visible;
  /* the old bar was pointer-events:none because it spanned the whole top
     edge and would have eaten playground's drag. A compact capsule with a
     real background underneath it should catch what lands on it. */
  pointer-events:auto;
}
/* The glass is its own layer rather than a filter on #pg-nav itself. With it
   on the nav, Chrome does not repaint the backdrop over the strip a growing
   panel reveals -- the page behind stays sharp and unblurred under the new
   half of the capsule, and you read the nav's rows through About's own
   paragraphs. A separate layer that is sized by the same box but does not
   own the animating overflow repaints correctly. */
/* Smoked glass, near-black, on warm paper.
   Raj asked for pink with a blue marker. Measured, that pairing does not
   work: --accent-b on --accent-r is 1.65:1, so the marker would be invisible
   on its own bar -- the two brand colours sit almost on top of each other in
   luminance. Black is the ground where both of them get a job. Pink on it is
   7.33:1 and the text on the bar is 15.06:1, better than the paper capsule
   it replaces. Apple's material rule lands in the same place independently:
   darker materials separate structural regions, lighter ones draw attention
   to interactive elements, and a nav is structure.

   This is not the dark theme coming back. That was a whole page system --
   near-black ground, geometric sans, folders, a toggle -- and it is still
   gone. This is one floating object, and the ground stays warm paper. */
#pg-nav::before{
  content:'';position:absolute;inset:0;z-index:-1;
  border-radius:inherit;
  background:linear-gradient(rgba(44,40,34,0.74), rgba(22,20,17,0.82));
  backdrop-filter:blur(24px) saturate(1.6);
  -webkit-backdrop-filter:blur(24px) saturate(1.6);
  /* the lighting is what makes glass read as glass: a bright edge where it
     catches the light, a dark one where it does not */
  box-shadow:inset 0 1px 0 rgba(255,255,255,0.17),
             inset 0 -1px 0 rgba(0,0,0,0.38);
}
/* Apple: translucency is a preference, not a given. */
@media(prefers-reduced-transparency:reduce){
  #pg-nav::before{
    background:#1b1815;
    backdrop-filter:none;-webkit-backdrop-filter:none;
  }
}
/* index.html is the only dark page left -- CIAD is light, always. */

/* Just the links now. The wordmark came off on Raj's call: it pointed at
   playground.html and the first link in the row already does, so it was a
   second control for one destination taking up the capsule's left third. */
.nav-row{
  display:flex;align-items:center;justify-content:center;
  padding:14px 24px;
}
.pg-links{display:flex;align-items:center;gap:22px;position:relative;}
.pg-links a,.pg-links button{
  font-family:'DM Mono',monospace;font-size:19px;font-weight:400;letter-spacing:0.10em;
  text-transform:uppercase;color:var(--nav-dim);text-decoration:none;
  transition:color 0.2s ease,transform 0.14s var(--e-out);
  cursor:pointer;white-space:nowrap;position:relative;z-index:1;
  background:none;border:0;padding:0;line-height:1;
}

/* ── the pill ──────────────────────────────────────────────────────────
   One rounded mark that travels between the links, replacing the 1.5px pink
   underline that used to hang off whichever link was current. It is a single
   element positioned by shared.js rather than a background per link, which is
   the only way it can read as one object moving rather than two fading.

   It keeps a view-transition-name, so a real cross-page navigation still
   slides it from the old page's link to the new one instead of cutting --
   that behaviour was the underline's whole reason for being a real element
   and it survives the swap.

   Two accents, one element, and they never mean the same thing: pink, solid,
   where it is resting -- the page you are on. Blue, a wash, while it follows
   the cursor -- a page you might go to. Raj, 2026-08-26, was right that blue
   belonged here and I had it as grey.
   Pink is the solid one because "you are here" should be the louder of the
   two, and because pink on this bar is 7.33:1 against blue's 4.45:1. */
.nav-pill{
  position:absolute;left:0;top:50%;
  height:30px;margin-top:-15px;width:0;
  border-radius:9px;
  background:var(--accent-r);
  pointer-events:none;z-index:0;
  /* width is a layout property and this is a hover-driven element, which is
     normally a finding. It stays: the pill is absolutely positioned inside
     .pg-links, so the layout it dirties is its own box and nothing else's,
     and the alternative -- scaleX on a fixed-width box -- distorts a 9px
     radius into an ellipse on every travel. */
  will-change:transform,width;
  transition:transform 0.22s var(--e-out),
             width 0.22s var(--e-out),
             background-color 0.22s ease,
             opacity 0.18s ease;
  view-transition-name:nav-pill;
}
/* travelling: a wash, not a fill, so the label on top stays legible */
.nav-pill.roaming{background:rgba(13,138,175,0.62);}
/* before shared.js has measured anything, and on a page with no active link */
.nav-pill.idle{opacity:0;}
::view-transition-group(nav-pill){
  animation-duration:0.42s;
  animation-timing-function:var(--e-out);
}
::view-transition-old(nav-pill),::view-transition-new(nav-pill){
  animation-duration:0.42s;
}

/* ── the panel ─────────────────────────────────────────────────────────
   Height is set in px by shared.js and animated; auto does not transition and
   a max-height guess makes the easing land early or late depending on which
   panel opened. --e-drawer, the curve CIAD's deck already travels on: this is
   long travel that should feel weighted rather than snappy.
   The contents arrive after the box has mostly opened, and start 6px low and
   transparent rather than from nothing. */
.nav-panel{
  height:0;overflow:hidden;
  width:0;min-width:100%;
  /* closing is a system response, opening is a decision -- 240 out against
     340 in. The .nav-open rule below supplies the slower open; this base
     duration is what the close falls back to when the class comes off. */
  /* the capsule's top corners, since the panel is what occupies them once it
     is open and the nav no longer clips */
  border-radius:var(--nav-r) var(--nav-r) 0 0;
  transition:height 0.24s var(--e-out);
}
#pg-nav.nav-open .nav-panel{transition:height 0.34s var(--e-drawer);}
.nav-panel-in{
  position:relative;
  padding:18px 18px 6px;
  /* blur with the fade: Apple's rule for glass is that it should arrive as
     material rather than appear at full sharpness, and the blur is also what
     stops the crossfade reading as two overlapping objects */
  opacity:0;transform:translateY(6px);filter:blur(4px);
  transition:opacity 0.2s var(--e-out),transform 0.2s var(--e-out),filter 0.2s var(--e-out);
}
#pg-nav.nav-open .nav-panel-in{opacity:1;transform:none;filter:blur(0);transition-delay:0.09s;}

/* Chrome on every page, so this is not optional. Gentler, not zero: the
   colours still change, the contents still cross-fade, nothing travels and
   nothing grows. */
@media(prefers-reduced-motion:reduce){
  .nav-pill{transition:background-color 0.2s ease,opacity 0.18s ease;}
  .nav-panel,#pg-nav.nav-open .nav-panel{transition:none;}
  .nav-panel-in{transform:none;filter:none;transition:opacity 0.2s ease;}
  #pg-nav.nav-open .nav-panel-in{transform:none;filter:none;}
  .nav-panel-list a::after{transition:color 0.18s ease;}
  .pg-links a:active{transform:none;}
}

.nav-panel-label{
  font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--nav-dim);
}
.nav-panel-list{display:flex;flex-direction:column;gap:2px;}
/* brand (0)'s phone-only row under work; the bar carries it above 639px */
.nav-panel-list a.nav-narrow{display:none;}
/* Raj, 2026-08-26: the rows "do not properly suggest that they can be tapped
   on to land on a different page." An arrow, sliding forward on hover --
   the same signal the case studies list already uses, so it is a word the
   site already speaks rather than a new one. */
.nav-panel-list a{
  display:flex;align-items:center;gap:12px;
  font-family:'DM Mono',monospace;font-size:19px;
  color:var(--nav-fg);text-decoration:none;
  padding:9px 10px;margin:0 -10px;border-radius:9px;
  transition:background-color 0.16s ease;
}
.nav-panel-list a::after{
  content:'\2192';
  margin-left:auto;color:var(--nav-dim);
  transition:transform 0.18s var(--e-out), color 0.18s ease;
}
@media(hover:hover) and (pointer:fine){
  .nav-panel-list a:hover::after{transform:translateX(4px);color:var(--nav-fg);}
}
@media(hover:hover) and (pointer:fine){
  .nav-panel-list a:hover{background:rgba(255,255,255,0.08);}
}
.nav-panel-list a:active{background:rgba(255,255,255,0.13);}
/* the one link out of the panel gets the page's other accent, same split the
   rest of the site runs: pink is the person and the way to reach him */

/* Mobile: the brand comes off. It is a second control pointing at
   playground.html, which the first link in the row already does, and the row
   needs those 42px more than the capsule needs a wordmark on it. */
@media(max-width:639px){
  #pg-nav{--nav-gap:10px;--nav-r:14px;width:calc(100vw - 20px);max-width:calc(100vw - 20px);}
  .nav-row{padding:5px 12px;justify-content:space-between;}
  .pg-brand{display:none;}
  .pg-links{gap:0;flex:1;justify-content:space-between;}
  .pg-links a{font-size:16px !important;letter-spacing:0.05em;white-space:nowrap;}
  /* brand (0) moves from the bar into work's panel -- see NAV in shared.js */
  .pg-links a.nav-wide{display:none;}
  .nav-panel-list a.nav-narrow{display:flex;}
  .nav-pill{height:22px;margin-top:-11px;border-radius:6px;}
  .nav-panel-in{padding:14px 12px 4px;}
  .pg-headline{font-size:32px;}
}
/* Gated. Ungated, a tap on a phone left the link sitting at full opacity
   after the finger lifted, so the nav showed two "current" pages at once
   until the next navigation repainted it. */
@media(hover:hover) and (pointer:fine){
  .pg-links a:hover{color:var(--nav-fg);}
}
.pg-links a.active{color:var(--nav-fg);text-decoration:none;}
/* Whichever link the pill is under has to be legible against the pill, not
   against the bar -- near-black on the pink fill, warm white on the blue
   wash. shared.js moves the class with the mark. */
.pg-links a.pill-here{color:var(--ink);}
#pg-nav.nav-roam .pg-links a.pill-here{color:var(--nav-fg);}
/* The nav is the one thing on this site touched on every page, so its press
   answer is the smallest one that still registers: full opacity and a hair
   under size, back in 140ms. Nothing here fades in or slides -- the pill
   above already carries the only motion this bar is allowed. */
.pg-links a:active{transform:scale(0.96);}
.pg-links a:focus-visible,.nav-panel-list a:focus-visible{
  outline:2px solid var(--accent-r);outline-offset:3px;border-radius:4px;opacity:1;
}

#pg-canvas{position:absolute;inset:0;overflow:hidden;cursor:grab;touch-action:none;background:var(--bg-warm);}
#pg-canvas::before{content:'';position:absolute;inset:0;z-index:0;background-image:radial-gradient(circle, rgba(180,175,168,0.25) 1px, transparent 1px);background-size:24px 24px;pointer-events:none;}
#pg-canvas.dragging{cursor:grabbing;}
#pg-world{position:absolute;top:0;left:0;width:3600px;height:2800px;will-change:transform;z-index:1;transform-origin:0 0;}
.pg-recenter{position:fixed;bottom:calc(var(--nav-clear,86px) + 10px);right:28px;z-index:200;font-family:'DM Mono',monospace;font-size:15px;font-weight:400;letter-spacing:0.12em;text-transform:lowercase;color:var(--ink);background:rgba(245,242,238,0.85);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(0,0,0,0.08);border-radius:20px;padding:8px 18px;cursor:pointer;transition:opacity 0.2s var(--e-out),translate 0.2s var(--e-out),transform 0.16s var(--e-out),background 0.2s ease;box-shadow:0 2px 12px rgba(0,0,0,0.06);opacity:0;translate:0 6px;pointer-events:none;}
/* it rises into place rather than blinking on -- translate, not transform,
   so the press scale below keeps its own property */
.pg-recenter.is-shown{opacity:1;translate:0 0;pointer-events:auto;}
@media(hover:hover) and (pointer:fine){
.pg-recenter:hover{background:rgba(245,242,238,1);transform:scale(1.04);}
}
.pg-recenter:active{transform:scale(0.97);}

/* macOS window drag — card lifts and shrinks under cursor */
.pg-card.dragging-macos{transition:transform 160ms var(--e-out),box-shadow 160ms var(--e-out)!important;z-index:120!important;cursor:grabbing!important;}
.pg-card.drop-settle{transition:box-shadow 300ms var(--e-out)!important;}

.pg-center{position:absolute;text-align:center;z-index:90;pointer-events:none;transform:translate(-50%,-50%);}
.pg-headline{font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;font-size:46px;line-height:1.2;color:var(--ink);letter-spacing:0.015em;white-space:nowrap;view-transition-name:nsp-headline;}
.pg-sub{display:flex;align-items:center;justify-content:center;gap:7px;margin-top:14px;font-family:'DM Mono',monospace;font-size:15px;font-weight:300;letter-spacing:0.22em;text-transform:uppercase;color:var(--ink-muted);}
.pg-sub::before{content:'\2295';font-size:16px;}

.pg-cta{
  display:inline-block;margin-top:20px;
  font-family:'Cormorant Garamond',serif;font-style:italic;font-size:26px;font-weight:400;
  letter-spacing:0.02em;color:var(--accent-r);text-decoration:none;
  pointer-events:all;cursor:pointer;
  /* lands just after the nearest cards, so the words, then the way out, then
     the edges. The 3s opacity "breathe" loop that ran forever after it is
     gone: a link that pulses is motion standing in for emphasis, and it was
     the one thing on the canvas that never held still. */
  opacity:0;animation:pgCtaIn 0.5s var(--e-out) 0.45s forwards;
}
@media(hover:hover) and (pointer:fine){.pg-cta:hover{color:var(--ink);transform:scale(1.04);}}
.pg-cta:active{transform:scale(0.97);}
/* `translate`, not `transform`: with fill forwards a transform keyframe held
   the link at translateY(0) for good, so the hover scale and the press scale
   below never applied -- measured identity under the cursor, 2026-10-06 */
@keyframes pgCtaIn{from{opacity:0;translate:0 8px;}to{opacity:1;translate:0 0;}}

.pg-card{position:absolute;border-radius:5px;overflow:hidden;cursor:grab;box-shadow:3px 6px 24px rgba(30,25,20,0.14),0 1px 4px rgba(30,25,20,0.05);transition:box-shadow 0.25s var(--e-out),transform 0.25s var(--e-out);cursor:pointer;}
@media(hover:hover) and (pointer:fine){.pg-card:hover{box-shadow:4px 10px 38px rgba(30,25,20,0.2);z-index:80!important;}}
.pg-card img{width:100%;height:100%;object-fit:cover;display:block;}
.pg-card-link::after{content:'->';position:absolute;top:10px;right:12px;font-family:'DM Mono',monospace;font-size:16px;color:rgba(255,255,255,0.4);opacity:0;transform:translateX(-4px);transition:opacity 0.2s var(--e-out),transform 0.2s var(--e-out);}
@media(hover:hover) and (pointer:fine){.pg-card-link:hover::after{opacity:1;transform:translateX(0);}}
.pg-video-card::after{display:none;}

.pg-text-card{position:absolute;border-radius:5px;overflow:hidden;box-shadow:3px 6px 24px rgba(30,25,20,0.14),0 1px 4px rgba(30,25,20,0.05);transition:box-shadow 0.25s ease,transform 0.25s ease;}
/* Little details, not callouts (Raj, 2026-10-06: "keep text cards small --
   theyre supposed to be little details not scream for attention"). The card
   hugs its words -- playground.js sets no height on it -- and sits on a
   lighter shadow than the photographs, so it reads as a slip of paper on
   the pile rather than a thing on top of it. */
.pg-text-card{box-shadow:1px 3px 10px rgba(30,25,20,0.10),0 1px 2px rgba(30,25,20,0.05);}
.pg-text-inner{width:100%;padding:9px 12px;display:flex;flex-direction:column;justify-content:space-between;}
/* Cormorant italic, the display face. Caveat was tried for one day
   (2026-10-06) and Raj took it back: "im not liking handwritten". 22, not
   the old 18 -- 22 is already on this page, so the size count holds. */
/* Not full ink (Raj, 2026-10-06: "too harsh of a black"): 60% --ink-body
   into --ink, ~5.8:1 on the blue/green tints and ~6.8 on pink. --ink-body
   alone measured 3.91 on blue -- too thin for a handwriting face. */
.pg-text-h{white-space:pre;font-family:'Cormorant Garamond',serif;font-style:italic;font-size:18px;line-height:1.2;color:color-mix(in srgb, var(--ink-body) 60%, var(--ink));font-weight:500;}
.pg-text-tag{font-family:'DM Mono',monospace;font-size:13px;letter-spacing:0.16em;text-transform:uppercase;color:var(--ink-muted);font-weight:300;}

@keyframes pgFadeIn{from{opacity:0;}to{opacity:1;}}
/* The nav starts visible everywhere. It used to start hidden and be revealed
   by JS on DOMContentLoaded, which meant the incoming page's nav was still
   visibility:hidden when a cross-document view transition took its snapshot —
   the nav ink had nothing to morph into. Only the video intro hides it now. */
#pg-nav{opacity:1;pointer-events:all;visibility:visible;transition:opacity 0.5s ease,visibility 0.5s ease,transform 0.28s var(--e-out);}
body:has(#screen-video) #pg-nav{opacity:0;pointer-events:none;visibility:hidden;}
body:has(#screen-video) #pg-nav.nav-visible{opacity:1;pointer-events:all;visibility:visible;}

/* ---- tucked: touch only (Raj, 2026-10-04) ----
   "the default nav bar only pops up when user touches screen -- when not
   interacting just reading the nav isnt there -- so inpage and inline
   buttons can take up space that the nav bar currently takes."
   shared.js sets html.nav-tucked on a touch screen: ~2s after arrival, and
   on every scroll down. A tap on anything that is not a control, or a
   scroll up, takes it off. The capsule slides out under the bottom edge
   and the page's own bars (CIAD's, brand (0)'s) drop into the strip it
   held, by --nav-drop. Transform only, so nothing reflows: every
   scroller still reserves the full --nav-clear, so its last line clears
   the capsule when it comes back. */
html.nav-tucked{--nav-drop:calc(var(--nav-clear,74px) - 8px);}
html.nav-tucked #pg-nav:not(.nav-open){
  transform:translate(-50%,calc(100% + var(--nav-gap) + 8px));
  opacity:0;pointer-events:none;
  transition:opacity 0.2s ease 0.08s,transform 0.28s var(--e-out);
}
.od-bar,.q-bar{
  transform:translateY(var(--nav-drop,0px));
  transition:transform 0.28s var(--e-out);
}
@media(prefers-reduced-motion:reduce){
  #pg-nav,.od-bar,.q-bar{transition:opacity 0.2s ease,visibility 0.2s ease;}
}
/* Arrival, and nothing after it. playground.js gives each card a delay off
   its distance from the headline, so the scatter blooms outward.
   It animates the individual `scale` property, never `transform`: the card's
   tilt, its hover lift and its drag warp are all written to `transform`
   inline, and a CSS animation on `transform` outranks an inline style. That
   is exactly what the ambient float used to do -- eight infinite drift loops
   that wrote over every card's transform, so all nineteen rendered flat,
   the hover lift never showed and the drag warp never drew (measured
   2026-10-06), while nineteen things wandered 4-6px at once and the page
   never held still. The float is gone; the tilt is back. */
@keyframes pgCardIn{from{opacity:0;scale:0.96;}to{opacity:1;scale:1;}}
.pg-card,.pg-text-card{animation:pgCardIn 0.5s var(--e-out) both;}

/* The drift came back the same day (Raj: "why is the subtle movement of the
   picture & video cards gone?") -- on the individual `translate` and `rotate`
   properties this time, which compose with the inline `transform` instead of
   replacing it. So a card drifts AND keeps its tilt, its hover lift and its
   drag warp. Eight paths so neighbours never move in step; it holds still
   under the cursor and while held. Loop curve is a literal, per section 3. */
@keyframes pgDrift0{to{translate:4px -5px;rotate:0.3deg;}}
@keyframes pgDrift1{to{translate:-5px -4px;rotate:-0.4deg;}}
@keyframes pgDrift2{to{translate:6px 4px;rotate:0.5deg;}}
@keyframes pgDrift3{to{translate:-4px 6px;rotate:-0.3deg;}}
@keyframes pgDrift4{to{translate:5px -6px;rotate:0.4deg;}}
@keyframes pgDrift5{to{translate:-6px -3px;rotate:-0.5deg;}}
@keyframes pgDrift6{to{translate:3px 5px;rotate:0.2deg;}}
@keyframes pgDrift7{to{translate:-3px -6px;rotate:-0.2deg;}}
.pg-card.pgDrift0{animation:pgCardIn 0.5s var(--e-out) both,pgDrift0 4.2s ease-in-out 0.5s infinite alternate;}
.pg-card.pgDrift1{animation:pgCardIn 0.5s var(--e-out) both,pgDrift1 5.1s ease-in-out 1.0s infinite alternate;}
.pg-card.pgDrift2{animation:pgCardIn 0.5s var(--e-out) both,pgDrift2 4.7s ease-in-out 0.2s infinite alternate;}
.pg-card.pgDrift3{animation:pgCardIn 0.5s var(--e-out) both,pgDrift3 5.6s ease-in-out 1.5s infinite alternate;}
.pg-card.pgDrift4{animation:pgCardIn 0.5s var(--e-out) both,pgDrift4 4.4s ease-in-out 0.8s infinite alternate;}
.pg-card.pgDrift5{animation:pgCardIn 0.5s var(--e-out) both,pgDrift5 5.9s ease-in-out 0.3s infinite alternate;}
.pg-card.pgDrift6{animation:pgCardIn 0.5s var(--e-out) both,pgDrift6 4.1s ease-in-out 1.2s infinite alternate;}
.pg-card.pgDrift7{animation:pgCardIn 0.5s var(--e-out) both,pgDrift7 5.3s ease-in-out 0.7s infinite alternate;}
/* only the drift pauses -- the arrival, if still running, finishes */
.pg-card.dragging-macos{animation-play-state:running,paused;}
@media(hover:hover) and (pointer:fine){.pg-card:hover{animation-play-state:running,paused;}}

@media(prefers-reduced-motion:reduce){
.pg-card,.pg-text-card,.pg-card[class*="pgDrift"]{animation:pgFadeIn 0.5s ease both;}
.pg-card.drop-settle{transition:none!important;}
}

/* 3D card hover — preserve-3d so perspective tilt registers */
.pg-card{transform-style:preserve-3d;}

/* The hub cards no longer tilt. Two cards side by side are a comparison,
   and independently wobbling comparands are harder to compare, not more
   delightful. The hover lift and the CTA carry the interaction instead. */

/* Spring entrance animation */
@keyframes springIn{
  0%{opacity:0;transform:translateY(20px) scale(0.95);}
  60%{transform:translateY(-4px) scale(1.01);}
  100%{opacity:1;transform:translateY(0) scale(1);}
}

/* Magnetic CTA base */
/* No overshoot. This was a back-out curve at 300ms -- a bounce on a hover,
   which is a gesture repeated dozens of times a session, and 1.56 means the
   link visibly springs past its size and settles back every pass of the
   cursor. Bounce belongs on drag-to-dismiss and on things you see once.
   The house ease-out at 200ms answers instantly and stops. */
.pg-cta,.hub-cta-link{transition:color 0.2s ease,transform 0.2s var(--e-out);}

/* ======================================================
   SCREEN 2: THE BASECAMP HUB

   Budgets (see CLAUDE.md "The subtraction rule"):
     type    5 sizes  — display / title / prose / body / label
     accent  1 colour — pink, and its job is: the recommended path
     spacing 3 tiers  — 8 inside a unit, 32 between units, 96 between sections
     objects 0        — this page is a decision, not a scrapbook

   The page asks one question: which of these two. So the two answers sit
   SIDE BY SIDE, and the gap between them (56px) is deliberately larger
   than the padding within them (32px) — otherwise they fuse into one blob
   and the choice stops reading as a choice.

   Two things it stopped doing:

   1. It was centred, in a 620px column, on a 1440px screen — a masthead
      with an empty field either side of it and 128px of nothing under it.
      Now the headline runs along the left and the quote sits in the right
      margin beside it, which is one band instead of two and puts the page
      back inside one viewport.

   2. Both cards had the same border, the same fill and the same radius, so
      neither was the focal point. Uniform emphasis is identical to none.
      The recommended one is now a raised sheet of paper; the other is set
      straight onto the page with a single rule. The recommendation is
      readable at a squint, before a word of it has been read.
====================================================== */
/* `align-content: safe center`, NOT plain centring and NOT auto margins.
   Centring a child that is taller than its scroll container pushes the
   overflow above the scroll origin where it can never be reached -- on a
   phone that ate the entire masthead. `safe` centres when there is room and
   falls back to start when there is not, which is what the auto margins used
   to fake. The margins had to go because an auto margin resolves to a real
   pixel value (27px at 1440x900, 8px at 1366x768) that reads as a spacing
   tier nobody chose. */
#screen-hub{z-index:50;background:var(--bg-warm);display:grid;justify-items:center;align-content:safe center;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;}
#screen-hub::after{content:'';position:absolute;inset:0;z-index:1;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.026'/%3E%3C/svg%3E");pointer-events:none;}

#hub-loader{position:absolute;inset:0;z-index:100;display:flex;align-items:center;justify-content:center;background:var(--bg-warm);transition:opacity 0.35s ease,visibility 0.35s ease;}
#hub-loader.done{opacity:0;visibility:hidden;pointer-events:none;}
#hub-logo{width:min(260px,55vw);height:auto;opacity:0;transform:scale(0.96);animation:hubLogoIn 0.45s var(--e-out) 0.15s forwards;}
@keyframes hubLogoIn{to{opacity:1;transform:scale(1);}}

#hub-back{display:none;}
#back-home{display:none !important;}

#hub-content{
  --hb-display:clamp(33px,calc(3.4vw + 4px),52px);
  --hb-title:28px;
  --hb-prose:21px;
  --hb-body:18px;
  --hb-label:15px;

  /* Four tiers, and each one has to be visibly bigger than the one below or
     it communicates nothing and costs a decision:
       tight    inside a unit
       unit     between units inside a card
       pair     between the two things being compared
       section  between the masthead band and the choice band
     `pair` and `section` used to land 2px apart (56 and 54), which meant the
     page had no readable difference between "these are two options" and
     "this is a new band". `section` is now clearly the largest. */
  --hb-tight:8px;
  --hb-unit:32px;
  --hb-pair:56px;
  --hb-section:clamp(64px,9vh,96px);

  /* fallback for the per-option accent tokens below, so anything that ever
     lands outside a .hub-option still draws a border instead of none */
  --opt-accent:var(--ink);--opt-deep:var(--ink);--opt-rgb:30,25,20;

  position:relative;z-index:10;
  display:flex;flex-direction:column;align-items:stretch;
  text-align:left;
  max-width:1160px;padding:14px 32px calc(8px + var(--nav-clear,86px));
  opacity:0;transition:opacity 0.5s ease;
  width:100%;
  gap:var(--hb-section);
}
#hub-content.visible{opacity:1;}

/* ── masthead: the question on the left, a client answering on the right ── */
.hub-top{
  display:grid;
  grid-template-columns:minmax(0,1.5fr) minmax(280px,0.5fr);
  gap:var(--hb-pair);align-items:end;
}
/* the <br> in the markup does the breaking; no measure limit here, or the
   display type wraps a word per line */
.hub-head{min-width:0;}
.hub-title{
  font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;
  font-size:var(--hb-display);line-height:1.08;letter-spacing:-0.018em;
  color:var(--ink);margin:0;text-wrap:balance;
}
/* ink, not pink. Pink on this page is the recommendation, and a whole pink
   line at display size was the loudest thing on the screen -- louder than
   the card it was meant to point at. Raj, 2026-10-04: the slider card is
   the one focal point, so every pink pixel lives inside it now. */
.hub-title em{font-style:italic;color:var(--ink);}
/* "two ways in. pick what fits." is gone: there are two cards, side by
   side, one of them marked. The sentence was narrating the layout. */

/* ── the choice ────────────────────────────────────── */
/* gap (48) > card padding (28): the two options must read as two things.
   stretch, not start: equal-height cards put both CTAs on the same
   baseline, which is what makes the pair scan as one comparison. */
/* start, not stretch. Stretching made both columns as tall as the taller
   one and then pinned each CTA to the bottom, which left 150px of nothing
   above the second button -- the exact dead band this page was supposed to
   lose. The two offers are compared by starting together and sharing a
   structure, not by ending together. */
.hub-options{
  display:grid;grid-template-columns:1fr 1fr;
  align-items:start;
  gap:var(--hb-pair);width:100%;
}
/* Each option carries its own accent, and the split means something — the
   same rule About runs on. Pink is the recommended path; blue is the one
   you scope yourself. Two accents used interchangeably would be noise;
   these two never appear in the same column.
   --opt-deep is the same hue darkened to 4.6:1 on warm paper, for the two
   places the accent is actually small text rather than a rule. */
.hub-option{
  --opt-accent:var(--accent-r);
  --opt-rgb:255,123,172;
  --opt-deep:var(--accent-r);
  display:flex;flex-direction:column;align-items:flex-start;
  text-align:left;
}
.hub-option:not(.hub-option-primary){
  --opt-accent:var(--accent-b);
  --opt-rgb:13,138,175;
  --opt-deep:#0b7695;
}

/* THE FOCAL POINT. A sheet of paper set down on the page: warmer than the
   ground it sits on, with a shadow that has direction and a warm cast
   rather than the grey default. Everything else on this screen is flat. */
.hub-option-primary{
  padding:32px;border-radius:12px;
  background:#fffdfa;
  border:1px solid rgba(255,123,172,0.20);
  box-shadow:0 1px 2px rgba(72,48,30,0.05),
             0 10px 24px -14px rgba(72,48,30,0.28),
             0 28px 60px -34px rgba(72,48,30,0.30);
  transition:transform 0.28s var(--e-out),
             box-shadow 0.28s var(--e-out),
             border-color 0.28s var(--e-out);
}
@media(hover:hover) and (pointer:fine){
  .hub-option-primary:hover{
    transform:translateY(-3px);
    border-color:rgba(255,123,172,0.34);
    box-shadow:0 2px 4px rgba(72,48,30,0.05),
               0 16px 32px -14px rgba(72,48,30,0.30),
               0 40px 80px -34px rgba(72,48,30,0.34);
  }
}
@media(prefers-reduced-motion:reduce){
  .hub-option-primary{transition:box-shadow 0.28s ease,border-color 0.28s ease;}
  .hub-option-primary:hover{transform:none;}
}

/* The alternative is set straight onto the page. No box, no fill, no
   radius, no shadow -- one rule to hold the column. It is not a lesser
   offer, it is a quieter one, and the quiet is what lets the other card
   read as the recommendation from across the room.
   The rule is the whole blue: a spine, not a fill. Tinting a background
   here would give the column weight and break that. */
.hub-option:not(.hub-option-primary){
  padding:4px 0 0 24px;
  border-left:1px solid rgba(var(--opt-rgb),0.42);
}
/* A note in the margin in Raj's hand, not a SaaS "MOST POPULAR" pill.
   Removes a border, a radius, a tint and a set of uppercase tracking; adds
   three words. Caveat replaces the two font families this page was loading
   and never used (Playfair Display, DM Sans). */
.hub-opt-mark{
  font-family:'Caveat','Cormorant Garamond',serif;font-weight:600;
  font-size:var(--hb-title);line-height:1;color:var(--accent-r);
  display:inline-block;transform:rotate(-2.2deg);transform-origin:left center;
  margin-bottom:var(--hb-tight);
}
.hub-opt-title{
  font-family:'Cormorant Garamond',serif;font-style:italic;
  font-size:var(--hb-title);color:var(--ink);
  margin-bottom:var(--hb-tight);line-height:1.14;letter-spacing:-0.012em;
}
.hub-opt-desc{
  font-family:'DM Mono',monospace;font-weight:400;font-size:var(--hb-body);
  letter-spacing:0;color:var(--ink-body);line-height:var(--lh-prose);
  max-width:44ch;text-wrap:pretty;margin:0;
}

/* Facts — the three things that decide the choice. Two fragments each,
   not three: the unit now lives inside the value string. */
/* ── build your month ────────────────────────────────
   The same interaction CIAD's price screen runs, sized for a card rather
   than a screen. There the month is 7 across and 5 down, because a week's
   width is what makes thirty squares read as a month; here the card has
   about 60px of slack before this page stops fitting one viewport, so it is
   one strip of thirty. A strip still shows the two things that matter -- how
   many, and spread rather than batched -- and it costs a fifth of the height.

   The metaphor budget is not spent twice: this is the *same* object as the
   deck's, not a second one. Their choice rides through on ?pkg= so CIAD
   opens on the package they just built. */
/* --hb-tight, not --hb-unit: the label is part of the block below it and
   the desc above already ends a thought. At --hb-unit this card ran 50px
   past a 900-tall viewport, and this page fits one screen exactly. */
/* width:100% because .hub-option is a flex column with align-items:
   flex-start -- children size to content unless told otherwise, which is
   why .hub-facts already declares it. Without it the slider and the month
   were half the card wide while the copy above ran full width. */
.hub-build{ margin-top:var(--hb-tight);width:100%; }
/* Raj, 2026-08-26: "'drag it - build your month' does not acquire attention.
   a viewer does not see it at once. they need to." It was 11px of tracked
   grey over a 2px line -- the instruction for the one thing on this page a
   visitor is meant to touch, set quieter than the caption under it.
   Three changes, because he said motion alone would not do it: the line goes
   to body size in full ink, the two words that are the instruction take the
   accent, and the control under it doubles in weight. */
.hub-build-label{
  display:block;margin-bottom:var(--hb-tight);
  font-family:'DM Mono',monospace;font-size:18px;
  letter-spacing:0.06em;text-transform:lowercase;
  color:var(--ink-body);
}
.hub-drag{color:var(--accent-r);}
.hub-range{
  -webkit-appearance:none;appearance:none;
  display:block;width:100%;background:transparent;cursor:grab;margin:0;
  /* a 2px track is a 2px target; the padding makes the whole strip grabbable */
  padding:12px 0;touch-action:none;
}
.hub-range:active{cursor:grabbing;}
.hub-range::-webkit-slider-runnable-track{
  height:5px;border-radius:3px;background:rgba(30,25,20,0.13);
}
.hub-range::-moz-range-track{
  height:5px;border-radius:3px;background:rgba(30,25,20,0.13);
}
.hub-range::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;
  width:28px;height:28px;margin-top:-11.5px;border-radius:50%;
  background:var(--accent-r);border:4px solid var(--bg-warm);
  box-shadow:0 3px 14px rgba(255,123,172,0.55), 0 0 0 1px rgba(255,123,172,0.30);
  transition:transform 0.16s var(--e-out);
}
.hub-range::-moz-range-thumb{
  width:28px;height:28px;border-radius:50%;
  background:var(--accent-r);border:4px solid var(--bg-warm);
  box-shadow:0 3px 14px rgba(255,123,172,0.55), 0 0 0 1px rgba(255,123,172,0.30);
}

/* It moves once, when the card arrives. Nothing tells you a thing is
   draggable faster than watching it move -- and this is the rarest kind of
   animation there is, seen once per visit, which is the only kind that is
   allowed to be for delight. A transform on the thumb, not a change of
   value: moving the value would swing the price and the readouts under it
   and read as the page deciding for them. */
@keyframes hubDragHint{
  0%{ transform:translateX(0); }
  38%{ transform:translateX(30px); }
  100%{ transform:translateX(0); }
}
.hub-range.hint::-webkit-slider-thumb{ animation:hubDragHint 1.15s var(--e-in-out) 1; }
.hub-range.hint::-moz-range-thumb{ animation:hubDragHint 1.15s var(--e-in-out) 1; }
@media(prefers-reduced-motion:reduce){
  .hub-range.hint::-webkit-slider-thumb,
  .hub-range.hint::-moz-range-thumb{ animation:none; }
}
.hub-range:active::-webkit-slider-thumb{transform:scale(1.14);}
.hub-range:focus-visible{outline:none;}
.hub-range:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 4px rgba(13,138,175,0.35);}

/* thirty days, one strip. filled are posts, spread across the month rather
   than stacked at the front -- a block would be showing a batch. */
.hub-month{
  display:grid;grid-template-columns:repeat(30,1fr);
  gap:3px;margin-top:var(--hb-tight);
}
.hub-day{
  position:relative;height:12px;border-radius:3px;
  background:rgba(30,25,20,0.07);
}
.hub-day::after{
  content:'';position:absolute;inset:0;border-radius:inherit;
  /* pink, not the deck's blue. On CIAD blue is the craft and the days
     belong to it; here blue means "the path you scope yourself" -- the
     other card -- so blue squares inside the recommended one would be the
     two accents crossing over. */
  background:var(--accent-r);opacity:0;
  transition:opacity 0.28s var(--e-out) var(--d,0ms);
}
.hub-day.is-on::after{opacity:1;}
@media(prefers-reduced-motion:reduce){
  .hub-day::after{transition:none;}
}

.hub-facts{
  display:flex;align-items:stretch;width:100%;
  margin:var(--hb-unit) 0 0;padding:var(--hb-unit) 0 0;
  border-top:1px solid rgba(30,25,20,0.09);
}
/* the build block already separates this from the copy above, so the rule
   and the second unit of padding would be a divider doing spacing's job */
.hub-build + .hub-facts{
  margin-top:var(--hb-unit);padding-top:0;border-top:none;
}
.hub-fact{flex:1;min-width:0;padding-right:12px;}
.hub-fact + .hub-fact{padding-left:12px;border-left:1px solid rgba(30,25,20,0.08);}
.hub-fact dt{
  font-family:'DM Mono',monospace;font-size:var(--hb-label);
  letter-spacing:0.15em;text-transform:uppercase;
  color:var(--ink-muted);opacity:0.75;margin-bottom:var(--hb-tight);
}
/* weight and ink, not a bigger size and not a colour */
.hub-fact dd{
  margin:0;font-family:'DM Mono',monospace;font-weight:500;
  font-size:var(--hb-body);letter-spacing:-0.01em;
  color:var(--ink);line-height:1.35;
}

/* CTA — an actual button. It was pink text at the same weight as a label,
   which gave the page's primary action no more presence than a caption. */
.hub-opt-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:6px;
  margin-top:auto;padding-top:var(--hb-unit);
  align-self:stretch;
  /* 11px was a caption. These are the only two actions on the page and one
     of them is now the address itself, so both go to the page's prose size
     -- both, because the two cards are a comparison and a comparison needs
     one structure. 21px is already in this page's inventory. */
  font-family:'DM Mono',monospace;font-size:21px;
  letter-spacing:0.04em;text-transform:lowercase;
  text-decoration:none;cursor:pointer;
  transition:transform 0.16s var(--e-out),
             background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
/* the anchor carried no colour of its own, so it computed to the browser's
   default link blue. Nothing shows today because every glyph lives in the
   inner span -- but any text added straight to the <a> would render blue. */
.hub-opt-btn{color:inherit;}
.hub-opt-btn::before{content:'';}
.hub-opt-btn-inner{
  display:inline-flex;align-items:center;justify-content:center;gap:6px;
  width:100%;padding:15px 20px;border-radius:100px;
  transition:inherit;
}
/* primary: filled. secondary: outlined. Real weight difference, not a tint. */
.hub-option-primary .hub-opt-btn-inner{
  background:var(--accent-r);color:#fff;
  border:1px solid var(--accent-r);
  box-shadow:0 2px 12px rgba(255,123,172,0.28);
}
/* outlined, and outlined in blue -- the ink border it had made this the
   only element on the page whose colour said nothing. Still no fill and
   still no shadow: the pink one is filled, and that difference is the
   recommendation. */
.hub-option:not(.hub-option-primary) .hub-opt-btn-inner{
  background:transparent;color:var(--opt-deep);
  border:1px solid rgba(var(--opt-rgb),0.42);
}
@media(hover:hover) and (pointer:fine){
  .hub-option-primary:hover .hub-opt-btn-inner{
    background:#ff6aa2;box-shadow:0 4px 18px rgba(255,123,172,0.38);
  }
  .hub-option:not(.hub-option-primary):hover .hub-opt-btn-inner{
    border-color:var(--opt-accent);background:rgba(var(--opt-rgb),0.06);
  }
}
.hub-opt-btn:focus-visible{outline:none;}
.hub-opt-btn:focus-visible .hub-opt-btn-inner{outline:2px solid var(--opt-accent);outline-offset:3px;}
.hub-opt-btn:active{transform:scale(0.985);}

/* ── proof — one signal, not two ───────────────────── */
/* The 25+/100%/2020 strip said the same thing as this quote, only vaguer,
   and the same three numbers already appear on About. A named client
   saying a specific thing outranks three round numbers.
   It sits in the headline's right margin rather than in a section of its
   own below the cards, where it was 128px of gap plus 140px of padding
   past the fold and nobody ever reached it. */
.hub-testi{
  margin:0;text-align:left;
  padding:2px 0 2px var(--hb-unit);
  border-left:2px solid rgba(255,123,172,0.40);
}
.hub-testi-text{
  font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;
  font-size:var(--hb-prose);line-height:1.5;
  color:var(--ink-body);margin:0 0 var(--hb-unit);letter-spacing:0.005em;
  transition:opacity 0.5s ease;
}
.hub-testi-attr{
  font-family:'DM Mono',monospace;font-size:var(--hb-label);
  letter-spacing:0.12em;text-transform:uppercase;
  color:var(--ink-muted);
  transition:opacity 0.5s ease;
}

/* Short desktops (1366x768, 1280x800). Same treatment as About: the unit
   tier contracts and the card's padding with it, so the whole decision
   still lands inside one viewport without a section being cut. */
@media(min-width:860px) and (max-height:860px){
  #hub-content{--hb-unit:22px;--hb-pair:44px;padding:20px 32px calc(6px + var(--nav-clear,86px));}
  .hub-option-primary{padding:26px;}

}

/* Below this the quote can't hold a line beside the headline, so it drops
   under it and the masthead goes back to one column. */
@media(max-width:1023px){
  .hub-top{grid-template-columns:1fr;gap:var(--hb-unit);align-items:start;}
  .hub-head{max-width:22ch;}
  /* Stacked, the quote no longer sits in the headline's right margin, so the
     deep indent has no job left -- and at 24px it exactly matched the gap
     above it, which fused the two into one block. The rule still marks it. */
  .hub-testi{padding-left:var(--hb-tight);}
}
@media(max-width:859px){
  /* Below this the two cards can't sit side by side and still hold prose,
     so they stack -- and the gap has to stay bigger than the padding. */
  .hub-options{grid-template-columns:1fr;gap:var(--hb-pair);}
  #hub-content{padding-bottom:calc(40px + var(--nav-clear,86px));}
}
@media(max-width:639px){
  #hub-content{
    --hb-unit:24px;--hb-pair:44px;
    padding:30px 22px calc(40px + var(--nav-clear,86px));
  }
  .hub-option-primary{padding:24px;}
  .hub-option:not(.hub-option-primary){padding-left:18px;}
  /* The address was `hello@rajkashyap.studio` when this was sized. One unbreakable string: at 21px DM Mono it
     measures 311px, and this card gives a button 327px between the page
     margin and the card's own padding -- so with the pill's 40px of padding
     the label forced #hub-content to 420px on a 390px phone and the whole
     page scrolled sideways. 18px is already in this page's inventory and
     lands the button at 307px. Both buttons move, not just the address:
     the two cards are a comparison and a comparison needs one structure.
     The pill's side padding comes off with it. It is `width:100%` here, so
     the padding does nothing to how the button looks -- it only sets the
     width below which the label breaks, and at 20px the arrow fell to a
     second line with 6px in it. */
  .hub-opt-btn{font-size:var(--hb-body);}
  .hub-opt-btn-inner{padding-inline:12px;}
  /* ---- the calm pass, 2026-10-04 ----
     The headline was capped at 22ch of the *body* size -- 190px on a 390
     phone, so six words broke across five lines in a column half the screen
     wide. It takes the measure now. */
  .hub-head{max-width:none;}
  /* The quote went on a phone. It is Sarah's, set as five lines of grey
     italic between the headline and the offer, which pushed the card the
     visitor came to choose under the fold -- and CIAD's proof screen, the
     page this card links to, carries the same words. Said once. */
  .hub-testi{display:none;}
}

/* ======================================================
   SCREEN 3: OUTLIER CONTENT IN A DAY

   The deck is the day. Five screens, 08:00 to the month after, crossed in
   about a minute -- the page has the shape of the thing it sells. There is
   one action on screen at any moment: the button in the bar, which morphs
   from "next" into "book a call" once the day is over.

   Budgets: two accents, one physical metaphor (the day), five type sizes,
   four spacing tiers. The nine folders, the window manager, the ambient
   video wall, the logo marquee, the newsletter bar and the dark theme are
   all gone.

   The two accents carry the same split as About and Work With Me, and they
   are never swapped: BLUE is the craft -- positioning, the method, the day
   itself. PINK is the payoff and the person -- the outlier, the thirty days,
   the handwritten asides, the one button that books a call.
====================================================== */
#screen-ciad{
  z-index:40;overflow:hidden;
  /* the ground; the bar and the diagrams' label knock-outs read the same var.
     It used to warm across the five screens -- brand (0)'s ghost carries the
     travel now (2026-10-04), so the ground holds still. */
  --od-ground:#f5f2ee;
  background:var(--od-ground);
  font-family:'DM Mono',monospace;
  color:var(--ink);
  --od-1:9px;    /* inside a line -- 9, not 10: a 9 already renders on this
                    page and two spacing values 1px apart are one tier that
                    costs two decisions */
  --od-2:26px;   /* between lines */
  --od-3:56px;   /* between parts */
  --od-4:104px;  /* frame */
}

/* ---- the cursor ----
   Half the screen forward, half back, and the pointer carries the name of
   the section it would take you to. It exists so nobody has to work out how
   to move, or wonder what is coming. Desktop only -- a hover affordance on
   a touch screen fires on tap, which is worse than none. */
.od-cursor{
  position:fixed;top:0;left:0;z-index:60;
  pointer-events:none;will-change:transform;
}
.od-cursor-pill{
  display:flex;align-items:center;gap:9px;
  padding:9px 16px;border-radius:999px;white-space:nowrap;
  background:var(--ink);color:var(--bg-warm);
  font-family:'DM Mono',monospace;font-size:var(--t-fine);
  letter-spacing:var(--tr-ui);text-transform:uppercase;
  box-shadow:0 6px 22px rgba(30,25,20,0.22);
  /* nothing in the real world arrives from nothing */
  opacity:0;transform:scale(0.92);
  transition:opacity 0.16s ease-out,transform 0.16s var(--e-out);
}
.od-cursor.is-on .od-cursor-pill{opacity:1;transform:scale(1);}
.od-cursor-arrow{color:var(--accent-r);font-style:normal;line-height:1;}
.od-cursor-back{display:none;}
.od-cursor.is-back .od-cursor-back{display:block;}
.od-cursor.is-back .od-cursor-fwd{display:none;}

/* ---- the deck ---- */
.od-deck{position:absolute;inset:0;overflow:hidden;touch-action:pan-y;}
@media(hover:hover) and (pointer:fine){
  #screen-ciad.od-hide-native .od-deck{cursor:none;}
}
.od-track{
  display:flex;height:100%;width:100%;
  will-change:transform;
  transition:transform 0.48s var(--e-drawer);
}
.od-slide{
  flex:0 0 100%;height:100%;min-width:0;
  display:flex;
  /* "safe" is the whole point of the second line: a centred flex child that
     grows taller than its box overflows in both directions, so the top of it
     goes under the nav and no amount of scrolling brings it back. safe centre
     gives up centring at exactly the moment centring would hide something,
     and is identical to the first line whenever the slide fits. */
  align-items:center;
  align-items:safe center;
  /* the bar's height is measured, not guessed -- it is one row on a desktop
     and two on a phone, and a slide that clears the wrong one puts type
     under the rail. The nav no longer sits on the top edge, so the top is
     just breathing room now; the bottom pays for the nav capsule as well as
     for od-bar, which is the trade the move cost this page. */
  /* One reservation, the bar and the capsule under it, at every width. The
     last element on four screens each carried --nav-clear of its own instead,
     which left screen one and "the problem" resting partly under the bar
     whenever they grew -- invisible while a fade covered it, not now. */
  padding:var(--od-3) clamp(24px,7vw,90px)
          calc(var(--od-bar-h,84px) + var(--nav-clear,74px) + var(--od-2));
  overflow-y:auto;overflow-x:hidden;
  /* An off-screen slide must not be reachable by tab, but it still has to be
     on screen while it is sliding off -- hiding it the moment it loses focus
     leaves the incoming screen travelling across a blank ground. So the
     outgoing slide keeps its visibility until the travel is over. */
  visibility:hidden;
  transition:visibility 0s linear 0.48s;
}
.od-slide.is-on{visibility:visible;transition:visibility 0s;}
/* Every slide shares one measure, so the left edge never moves as the deck
   travels. A text block that shifts sideways between screens is the fastest
   way to make five screens read as five pages instead of one day. */
.od-inner{width:100%;max-width:1140px;margin:0 auto;}

/* ---- type ----
   Five sizes on this page and no more: display (fluid), 20px for figures,
   14px for anything read as a sentence, 11px for controls, 9px for the
   tracked labels on the rail. */
.od-eyebrow{
  font-size:var(--t-fine);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--ink-muted);margin-bottom:var(--od-2);
}
.od-h{
  font-family:'Cormorant Garamond',serif;font-weight:300;
  font-size:clamp(38px,calc(6.4vw + 4px),78px);line-height:var(--lh-display);
  letter-spacing:var(--tr-display);color:var(--ink);
  margin-bottom:var(--od-2);
}
.od-h em{font-style:italic;color:var(--ink);}
/* Colour lands on the word that carries the meaning, never on a whole line.
   ac-b is the craft, ac-p is the payoff -- see the note at the top. */
.od-h b{font-weight:inherit;}
.ac-b{color:var(--accent-b);}
.ac-p{color:var(--accent-r);}

/* Raj's hand. Set at the price step so it costs no new type size. */
.od-hand{
  font-family:'Caveat','Cormorant Garamond',serif;font-size:24px;line-height:1.1;
  letter-spacing:0;text-transform:none;color:var(--accent-r);
}
/* The last thing on "how we work" and the only place the unlimited-revisions
   promise is made. It carried its own --nav-clear until 2026-10-04; `.od-slide`
   reserves the bar and the capsule for every screen now. */
.od-hand-block{
  display:inline-block;margin-top:var(--od-2);
  transform:rotate(-2deg);
}
.od-lead{
  font-size:var(--t-lead);line-height:var(--lh-prose);color:var(--ink-body);
  max-width:56ch;
}
.od-meta{
  margin-top:var(--od-3);
  font-size:var(--t-fine);letter-spacing:var(--tr-ui);text-transform:uppercase;
  color:var(--ink-muted);
}
.od-lead-close{margin-top:var(--od-2);color:var(--ink);}

/* ---- the problem, drawn ----
   Six disciplines that never stop. They used to be a wrapped list with arrows
   between the words, which *claims* a loop and *draws* a line -- and left
   this screen the same shape as the two either side of it. A closed ring
   shows it: no start, no exit, and the note about it being a full-time job
   now sits inside the thing that makes it one.
   Nothing was added. The list became the ring and the note moved into it.

   Both graphics on this page draw themselves the first time you arrive and
   then hold still (JS adds .od-drawn to the slide, once). A diagram that
   replays every time you come back is a nag, and one that plays on a slide
   nobody is looking at is a cost -- which is what happened to the reel
   stagger that used to live further down this file. */
.od-inner-split{
  display:grid;grid-template-columns:minmax(0,1fr) auto;
  gap:var(--od-4);align-items:center;
}
.od-loop{position:relative;width:min(40vw,370px);aspect-ratio:1;}
.od-loop-ring{position:absolute;inset:0;width:100%;height:100%;}
.od-ring-track,.od-ring-draw{fill:none;}
.od-ring-track{stroke:rgba(30,25,20,0.12);stroke-width:0.3;}
.od-ring-draw{
  stroke:var(--accent-b);stroke-width:0.55;stroke-linecap:round;
  /* The dash is in user units and the circle carries pathLength="100", so one
     dash is exactly one turn. non-scaling-stroke cannot be used here: it moves
     the dash into screen pixels, where 100 units is a fifth of the ring and
     the stroke comes out as five arcs with gaps between them.
     An SVG circle starts at 3 o'clock, so it is turned back a quarter to
     begin at the top, where "ideation" sits. */
  stroke-dasharray:100;stroke-dashoffset:100;
  transform:rotate(-90deg);transform-origin:50% 50%;
  /* linear, and not the ease-out everything else on this page uses: a line
     being traced is constant motion, and an eased one is 93% of the way round
     before the third word has finished resolving, which breaks the only thing
     the timing is for. At a constant speed the turn takes 900ms and each of
     the six stops is exactly 150ms apart. */
  transition:stroke-dashoffset 0.9s linear;
}
.od-drawn .od-ring-draw{stroke-dashoffset:0;}

/* The words sit on the ring rather than outside it, with the ground showing
   through behind each one -- outside, "distribution" pushes the circle small
   enough to stop reading as a circle. */
/* The ring's type is geometry, not reading size: six words sit ON a circle of
   fixed diameter and the centre has to stay two single lines. It is the one
   place on the site that did not take the sitewide +4 -- at 15/13px the six
   labels overlapped each other, "that's a full-time job" wrapped, and POST,
   PSYCHOLOGY and PRODUCTION were pushed off the bottom of a 706px phone.
   Written as literals so nothing changes here by inheriting a token. */
.od-chain{list-style:none;}
.od-chain li{
  position:absolute;left:calc(50% + var(--x));top:calc(50% + var(--y));
  transform:translate(-50%,-50%);
  padding:3px 7px;background:var(--od-ground);white-space:nowrap;
  font-size:11px;letter-spacing:var(--tr-ui);text-transform:uppercase;
  color:var(--accent-b);
  /* each word resolves as the stroke reaches it: 900ms for the full turn,
     so 150ms a stop */
  opacity:0;filter:blur(3px);
  transition:opacity 0.32s ease-out var(--dly),filter 0.32s ease-out var(--dly);
  --dly:calc(var(--n) * 150ms);
}
.od-drawn .od-chain li{opacity:1;filter:blur(0);}
.od-loop-mid{
  position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);
  width:min(48%,178px);text-align:center;
  font-size:11px;letter-spacing:var(--tr-ui);text-transform:uppercase;
  color:var(--ink-muted);
  opacity:0;transition:opacity 0.4s ease-out 0.86s;
}
.od-drawn .od-loop-mid{opacity:1;}
.od-loop-mid .od-hand{
  display:block;margin-top:var(--od-1);
  /* 20px, not the page's 24: this line has to hold one line inside a 240px
     ring, which is what the ring's diameter was chosen around. */
  font-size:20px;
  text-transform:none;letter-spacing:0;
}

/* ---- how we work, drawn ----
   Four stations and the one fact they came for: where their part stops.
   This replaces a three-item spec list whose figures meant nothing standing
   next to each other -- "1-3 days a month" beside "6-20 videos" is two
   numbers, but under the station it describes it is the whole commitment.
   The dashed return is the difference between this and a one-off shoot, so
   it is drawn rather than claimed; it needs no caption, because the arrow
   lands on the station it goes back to. */
.od-path{position:relative;margin-top:var(--od-3);padding-top:38px;}
/* the line runs the whole measure and the stations sit along it, rather than
   the line running station to station -- it starts where the writing starts,
   which is the only left edge this slide has, and carrying on past the last
   station is the thing the return underneath then explains */
.od-path-rail{
  position:absolute;left:0;right:0;top:38px;height:1px;
  background:rgba(30,25,20,0.12);
}
/* 2px and square, where the progress rail at the foot of the page is 3px and
   rounded. They are both full-width blue lines on the same screen and the
   stations are most of what tells them apart -- this is the rest of it. */
.od-path-rail i{
  position:absolute;left:0;top:-1px;width:100%;height:2px;
  background:var(--accent-b);
  transform:scaleX(0);transform-origin:left center;
  /* linear for the same reason as the ring: the stations are timed off where
     the line has actually got to, and an eased line arrives at all four of
     them in the first third of its run */
  transition:transform 0.64s linear;
}
.od-drawn .od-path-rail i{transform:scaleX(1);}

.od-path-steps{
  list-style:none;display:grid;grid-template-columns:repeat(4,1fr);
  align-items:start;
}
.od-step{
  position:relative;padding-top:18px;text-align:center;
  display:flex;flex-direction:column;gap:5px;
}
.od-step::before{
  content:'';position:absolute;left:50%;top:0;
  width:9px;height:9px;margin:-4px 0 0 -4.5px;border-radius:50%;
  background:var(--accent-b);box-shadow:0 0 0 4px var(--od-ground);
  opacity:0;transform:scale(0.6);
  transition:opacity 0.24s ease-out var(--dly),
             transform 0.3s var(--e-out) var(--dly);
}
.od-drawn .od-step::before{opacity:1;transform:scale(1);}
.od-step b,.od-step span{opacity:0;transition:opacity 0.3s ease-out var(--dly);}
.od-drawn .od-step b,.od-drawn .od-step span{opacity:1;}
/* the stations sit at 12.5 / 37.5 / 62.5 / 87.5 percent of a line that takes
   640ms to run, which is where these four numbers come from */
.od-step{--dly:calc(var(--n) * 160ms + 80ms);}
.od-step b{
  font-weight:400;font-size:var(--t-fine);letter-spacing:var(--tr-ui);
  text-transform:uppercase;color:var(--ink);
}
/* the figures carry the accent, not the station names -- the numbers are the
   part being read */
.od-step span{
  font-size:var(--t-micro);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--accent-b);
}

/* The handover, half way along and pink because it is theirs -- the box is
   exactly as tall as the run-up to the line, so the tick lands on the line
   instead of stopping near it, and the dot marks the point it lands on. */
.od-path-out{
  position:absolute;left:50%;top:0;height:38px;transform:translateX(-50%);
  white-space:nowrap;text-align:center;
  font-size:var(--t-micro);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--accent-r);
  opacity:0;transition:opacity 0.4s ease-out 0.34s;
}
.od-drawn .od-path-out{opacity:1;}
.od-path-out::after{
  content:'';position:absolute;left:50%;top:16px;bottom:7px;width:1px;
  background:repeating-linear-gradient(to bottom,
             var(--accent-r) 0 3px,transparent 3px 6px);
}
/* a step smaller than a station dot: this is a moment on the line, not
   another thing that happens */
.od-path-out::before{
  content:'';position:absolute;left:50%;bottom:-3px;
  width:7px;height:7px;margin-left:-3.5px;border-radius:50%;
  background:var(--accent-r);box-shadow:0 0 0 4px var(--od-ground);
}

/* The return, drawn as a staple under the line rather than written out as a
   second copy of the sentence directly above it. One word on it, because an
   unexplained bracket is ornament and ornament is not hierarchy -- and the
   arrowhead lands on the station it goes back to, so one word is enough. */
.od-path-back{
  position:relative;width:75%;height:22px;margin:15px auto 0;
  border:1px dashed var(--accent-b);border-top:0;border-radius:0 0 10px 10px;
  opacity:0.45;
  /* it travels right to left, so it is revealed from the right */
  clip-path:inset(0 0 0 100%);
  transition:clip-path 0.5s var(--e-drawer) 0.68s;
}
.od-drawn .od-path-back{clip-path:inset(0 0 0 0);}
.od-path-back::before{
  content:'';position:absolute;left:-4px;top:-1px;
  border-left:4px solid transparent;border-right:4px solid transparent;
  border-bottom:5px solid var(--accent-b);
}
/* sits on the bottom edge with the ground showing through, the same way the
   words sit on the ring one screen back */
.od-path-back-label{
  position:relative;text-align:center;margin-top:-7px;
  font-size:var(--t-micro);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--accent-b);
  opacity:0;transition:opacity 0.4s ease-out 1.04s;
}
.od-path-back-label span{background:var(--od-ground);padding:0 9px;}
.od-drawn .od-path-back-label{opacity:1;}

/* ---- the work ----
   The deck opens with "only some videos get outlier results" and this is the
   screen that has to make that word true. It was five silent thumbnails under
   a sentence claiming they stood out; the figures are Raj's own, already
   written for these same five clients on the portfolio, and they say it
   instead. The sentence went -- proof beats the sentence about the proof.

   The unit is stated once, above the row. Five captions each repeating "the
   reach of their average post" is the same fact said five times, and under a
   9:16 thumbnail it wraps to three lines and buries the number it qualifies. */
/* Measured 2026-10-02: this line was 13px (desktop) / 14px (390x706) / 29px
   (390x664) behind the nav capsule -- the one line on the screen that says
   what the five multiples are multiples of, and the one the slide cannot
   afford to lose. `.od-slide` reserves `--od-bar-h` and the bar floats one
   --nav-clear above the floor, so the last ~63-71px of every slide sit under
   it. Reserving it here rather than on `.od-slide` is the pattern `.od-foot`
   and the ring already use: charging all five slides 87px they mostly do not
   need took "the problem" from 144px of overflow to 209px, measured. This
   costs the screen nothing -- it had the slack. */
/* The --nav-clear reservation lives on `.od-slide` since 2026-10-04, for
   every screen at once -- no element here carries it any more. */
.od-reels-unit{
  margin-top:var(--od-1);
  font-size:var(--t-micro);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--ink-muted);
}
.od-reels{
  display:flex;gap:14px;margin-top:var(--od-3);
  align-items:flex-start;touch-action:pan-x;
}
.od-reel-fig{flex:0 0 auto;}
/* Desktop: the quote stands beside the row instead of under it. Stacked,
   "the work" ran 100px past a 1440x900 screen and 161px past 1366x768, and
   with the bar's fade gone (2026-10-04) the quote sat straight across the
   rail. There is width to spare beside five reels; there is no height. Her
   words end level with the unit line, so figure and voice still read as one
   claim and its witness. */
@media(min-width:861px){
  .od-inner:has(> .od-reels){
    display:grid;grid-template-columns:max-content minmax(0,1fr);
    column-gap:var(--od-3);align-items:end;
  }
  .od-inner:has(> .od-reels) > .od-eyebrow,
  .od-inner:has(> .od-reels) > .od-h{grid-column:1 / -1;}
  .od-inner:has(> .od-reels) > .od-reels{grid-column:1;grid-row:3;}
  .od-inner:has(> .od-reels) > .od-reels-unit{grid-column:1;grid-row:4;}
  /* top of the row, not the foot: brand (0)'s ghost stands in the bottom
     right corner above the button, and level with the unit line the quote
     ran under it */
  .od-inner:has(> .od-reels) > .od-voice{grid-column:2;grid-row:3 / 5;align-self:start;margin:0;}
}
@media(min-width:861px) and (max-height:860px){
  .od-reel{height:clamp(150px,24vh,300px);}
}
/* Sized from height, not width. A row of 9:16 reels stretched across the
   measure is as tall as the measure is wide, which pushed this screen past
   the viewport the moment it got a heading. Height-first keeps the row
   inside the fold at every size and the aspect ratio picks the width. */
.od-reel{
  flex:0 0 auto;position:relative;
  height:clamp(160px,30vh,360px);
  aspect-ratio:9 / 16;
  border:0;padding:0;background:#e7e1d8;
  border-radius:10px;overflow:hidden;cursor:pointer;
  box-shadow:0 10px 30px rgba(30,25,20,0.10),0 1px 3px rgba(30,25,20,0.06);
  transition:transform 0.2s var(--e-out);
}
/* There was a staggered entrance here. Every slide exists at load, so it
   played to an empty room while the visitor was still on screen one and had
   always finished by the time they arrived. An animation nobody can see is
   just a cost. */
.od-reel img,.od-reel video{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;display:block;
}
.od-reel video{z-index:2;}
@media(hover:hover) and (pointer:fine){
  .od-reel:hover{transform:translateY(-4px);}
}
.od-reel:active{transform:scale(0.98);}
.od-reel-play{
  position:absolute;left:50%;top:50%;z-index:3;
  transform:translate(-50%,-50%);
  width:34px;height:34px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:rgba(255,255,255,0.92);color:var(--ink);
  transition:opacity 0.18s ease;
}
.od-reel-play svg{width:13px;height:13px;margin-left:2px;}
.od-reel.is-playing .od-reel-play{opacity:0;}

/* The multiple is the payoff, so it is pink and set at the figure size the
   price uses -- no new type size, no new colour. The client's name recedes
   under it: the number is what is being read, the name is only what makes it
   checkable. */
/* the tight tier, not a 9 that sits close enough to it to cost a decision
   and communicate nothing */
.od-reel-cap{margin-top:15px;display:flex;flex-direction:column;gap:1px;}
.od-reel-cap b{
  display:block;font-weight:400;font-size:24px;line-height:1.15;
  color:var(--accent-r);
}
/* The hanging "up to" that lived here came off 2026-10-05 on Raj's
   instruction -- the five figures start together without needing it.
   Shashank's "up to 35x" brought it back the same day, inline at the label's
   own size: at 24px the whole phrase is 130px in a 97px phone column and
   wrapped, which dropped that one name below the other four. Untracked and
   lowercase because tracked uppercase was still 107px. */
.od-reel-cap b{white-space:nowrap;}
.od-reel-cap b i{
  font-style:normal;font-family:'DM Mono',monospace;font-size:var(--t-micro);
  color:var(--ink-muted);margin-right:3px;
}
.od-reel-cap span{
  font-size:var(--t-micro);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--ink-muted);
}


/* ---- the month after: they build it ----
   Not a pricing table. Three packages exist and nothing sits between them,
   so the slider snaps -- interpolating would invent prices Raj has never
   quoted. What moves continuously is the month: thirty squares that fill as
   the volume goes up. The page has spent a minute showing you one day; this
   is that day paying out, which is why it belongs here and not in a card. */
/* The tallest of the five screens, and the only one that ends on content
   rather than on air. Its top margin drops a tier so the reclaimed 30px pays
   for the bar clearance below instead of sitting empty above the eyebrow.
   `safe center` still centres it whenever it fits. */
.od-slide-price{padding-top:var(--od-2);}
/* The frame tier between the three blocks became the price of clearing the
   bar: at 56px the screen scrolled 81px to reach its own footer. 26 is the
   tier the phone layout already runs here, so this is one value fewer on the
   page rather than a new one. */
.od-inner-price{display:flex;flex-direction:column;gap:var(--od-2);}
.od-price-head .od-eyebrow{margin-bottom:var(--od-2);}
.od-h-price{margin-bottom:0;}

.od-build{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(220px,auto);
  gap:var(--od-3);align-items:start;
}
.od-build-controls{grid-column:1;}
.od-build-label{
  display:block;margin-bottom:var(--od-2);
  font-size:var(--t-fine);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--ink-muted);
}

/* the slider itself -- native range, so keyboard and touch come for free */
.od-range{
  -webkit-appearance:none;appearance:none;
  width:100%;background:transparent;cursor:grab;margin:0;
  /* the deck is pan-y; without this the browser hands a sideways touch to
     the page instead of to the thumb, and the slider cannot be dragged */
  touch-action:none;
  /* a 2px track is a 2px target -- the padding makes the whole strip grabbable */
  padding:10px 0;
}
.od-range:active{cursor:grabbing;}
.od-range::-webkit-slider-runnable-track{
  height:2px;border-radius:2px;background:rgba(30,25,20,0.16);
}
.od-range::-moz-range-track{
  height:2px;border-radius:2px;background:rgba(30,25,20,0.16);
}
.od-range::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;
  width:20px;height:20px;margin-top:-9px;border-radius:50%;
  background:var(--accent-r);border:3px solid var(--bg-warm);
  box-shadow:0 2px 10px rgba(255,123,172,0.45);
  transition:transform 0.16s var(--e-out);
}
.od-range::-moz-range-thumb{
  width:20px;height:20px;border-radius:50%;
  background:var(--accent-r);border:3px solid var(--bg-warm);
  box-shadow:0 2px 10px rgba(255,123,172,0.45);
}
.od-range:active::-webkit-slider-thumb{transform:scale(1.14);}
.od-range:focus-visible{outline:none;}
.od-range:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 4px rgba(13,138,175,0.35);}

.od-range-marks{display:flex;justify-content:space-between;margin-top:var(--od-1);}
.od-range-marks span{
  font-size:var(--t-micro);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--ink-faint);cursor:pointer;transition:color 0.2s ease;
}
.od-range-marks span:first-child{text-align:left;}
.od-range-marks span:last-child{text-align:right;}
.od-range-marks span.is-on{color:var(--ink);}

/* thirty days. filled squares are posts, spread across the month rather than
   stacked at the front -- a block would be showing a batch, which is the one
   thing this product is not. */
.od-month{
  grid-column:1;
  /* Seven across, because a week is what makes thirty squares read as a
     month rather than a bar chart -- and because an even spread of posts
     lines up into vertical stripes on any column count that divides into
     30. The ragged last row is correct: months are not rectangles.
     The grid's own row gap spaces this from the slider, so no margin here
     or the two stack up. */
  display:grid;grid-template-columns:repeat(7,1fr);
  gap:5px;max-width:250px;
}
/* The fill is an opacity change on an overlay, not a background-color
   transition. background-color repaints; opacity composites. Thirty of them
   repainting on every drag of the slider is the one place on this page that
   could actually drop frames. */
.od-day{
  position:relative;aspect-ratio:1;border-radius:2px;
  background:rgba(30,25,20,0.07);
}
.od-day::after{
  content:'';position:absolute;inset:0;border-radius:inherit;
  background:var(--accent-b);opacity:0;
  transition:opacity 0.28s var(--e-out) var(--d,0ms);
}
.od-day.is-on::after{opacity:1;}

.od-readout{grid-column:2;grid-row:1 / span 2;}
.od-tier-name{
  font-size:var(--t-fine);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--accent-r);margin-bottom:var(--od-1);
}
.od-tier-price{font-size:24px;line-height:1.2;color:var(--ink);margin-bottom:2px;}
.od-tier-vol{
  font-size:var(--t-fine);letter-spacing:var(--tr-ui);text-transform:uppercase;
  color:var(--ink-muted);
}
/* Shoot days are what the client actually gives up, so they read a step
   stronger than the video count sitting above them. */
.od-tier-days{color:var(--ink-body);}
.od-readout .od-hand{display:block;margin-top:var(--od-1);}
.od-cur{
  display:inline-flex;margin-top:var(--od-2);
  gap:2px;padding:3px;border-radius:999px;background:rgba(30,25,20,0.05);
}
.od-cur-btn{
  border:0;background:none;cursor:pointer;border-radius:999px;
  padding:7px 14px;font-family:'DM Mono',monospace;
  font-size:var(--t-fine);letter-spacing:var(--tr-ui);text-transform:uppercase;
  color:var(--ink-muted);transition:color 0.2s ease,background-color 0.2s ease;
}
.od-cur-btn.is-on{background:var(--bg-warm);color:var(--ink);}
.od-cur-btn:active{transform:scale(0.97);}
.od-usd{display:none;}
.show-usd .od-inr{display:none;}
.show-usd .od-usd{display:inline;}

/* This footer once ran under the rail at all three widths; `.od-slide`
   reserves the bar and the capsule now, so it carries nothing of its own. */
.od-foot{
  display:flex;flex-wrap:wrap;gap:var(--od-1) var(--od-3);align-items:baseline;
}
/* The three terms lines are one block, so the disqualifier sits beside the
   stack rather than under it and the footer stays the height it was. */
.od-terms{display:flex;flex-direction:column;gap:var(--od-1);}
.od-note{
  font-size:var(--t-fine);letter-spacing:var(--tr-ui);text-transform:uppercase;
  color:var(--ink-muted);
}
/* The one term on this screen that changes the decision, stepped up off the
   small print it was buried in: the lead's 18px (already on this page, so no
   new size), full ink, and the one weight DM Mono has above the body. Its
   line keeps its muted tail, so the block still reads as three rows and not
   four. See the note at the call site in ciad.html. */
.od-term{
  font-size:var(--t-lead);font-weight:500;color:var(--ink);
}
/* The credential, and the only line in this block that is not muted -- three
   rows at one weight is the uniform-emphasis failure in miniature. No new
   size and no accent: it steps up on value alone. */
.od-proof{
  font-size:var(--t-fine);letter-spacing:var(--tr-ui);text-transform:uppercase;
  color:var(--ink-body);
}
/* the disqualifier. fence sitters don't buy, so it sits right where the
   decision is being made rather than three screens back. */
.od-nope{list-style:none;}
.od-nope li{font-size:var(--t-fine);line-height:1.7;color:var(--ink-muted);}

/* ---- the second route ----
   See the long note at the call site in ciad.html. No accent and no new type
   size: 15px is the footer's own step, and both of this page's accents
   already mean something specific (pink the payoff, blue the craft). This is
   neither, so it steps up on value alone -- the same move .od-proof makes.
   It must stay quieter than the bar's button, which is the screen's one
   action; if this ever reads as loud as that, it has gone wrong. */
/* Not `flex:1 1 100%`. Forcing its own row cost the price slide -- the
   tallest of the five -- 22px on a desktop that had 7px of slack, measured.
   `.od-foot` already wraps, so sharing the row where there is width and
   wrapping where there is not is free on a desktop and one line on a phone.
   The basis is the width the open form needs, so it wraps before it crushes
   the field rather than after. */
.od-aside{flex:1 1 260px;}
/* Screenshotted 2026-10-02 without the arrow and it landed as a fourth line
   of small print under "30 MIN CALL - FREE": same size, same colour, same
   left edge, and nothing anywhere saying it could be clicked. That is the
   failure CLAUDE.md names outright -- a control that gives no sign it is one
   -- so it takes the site's own answer to it, the sliding arrow the nav rows
   carry. --ink-body rather than --ink: it has to read as a link without
   outshouting "three months minimum" directly above it, which is the line
   this block exists to carry. */
.od-aside-ask{
  border:0;background:none;padding:0;cursor:pointer;text-align:left;
  font-family:'DM Mono',monospace;font-size:var(--t-fine);
  line-height:var(--lh-ui);color:var(--ink-body);
  border-bottom:1px solid transparent;
  transition:color 0.18s ease,border-color 0.18s ease;
}
/* The arrow hangs off the SPAN, not off the button. The global mobile rule
   makes every button `display:inline-flex`, which turns a ::after on the
   button into a second flex item: screenshotted on a 390 phone, the label
   wrapped to two lines and the arrow sat alone against the right edge,
   vertically centred between them. On the span it is in the text flow, so
   it follows the last word wherever that word lands. */
.od-aside-ask > span::after{
  content:'\2192';margin-left:7px;display:inline-block;
  transition:transform 0.2s var(--e-out);
}
@media(hover:hover) and (pointer:fine){
  .od-aside-ask:hover{color:var(--ink);border-bottom-color:rgba(30,25,20,0.3);}
  .od-aside-ask:hover > span::after{transform:translateX(3px);}
}
.od-aside-ask:active{color:var(--ink);}
/* open: the arrow has done its job and turns to point at the field */
.od-aside-ask[aria-expanded="true"]{color:var(--ink);}
.od-aside-ask[aria-expanded="true"] > span::after{transform:rotate(90deg);}

/* The tail breaks as one piece or not at all. At 18px the term pushes it
   past the line on a 390 phone and it was splitting between "pay" and
   "upfront" -- a phrase cut in half reads as two facts. Nowrap sends the
   whole clause to the second line instead. */
.od-term-tail{white-space:nowrap;}

.od-aside-form{display:flex;gap:var(--od-1);align-items:center;margin-top:var(--od-1);}
.od-aside-form[hidden]{display:none;}
#od-handle{
  flex:0 1 220px;min-width:0;
  font-family:'DM Mono',monospace;font-size:var(--t-fine);color:var(--ink);
  background:rgba(255,255,255,0.55);
  border:0;border-bottom:1px solid rgba(30,25,20,0.28);border-radius:0;
  padding:8px 10px;
  transition:border-color 0.18s ease,background-color 0.18s ease;
}
#od-handle::placeholder{color:var(--ink-faint);}
#od-handle:focus{
  outline:none;background:rgba(255,255,255,0.9);
  border-bottom-color:var(--accent-r);
}
.od-aside-send{
  border:0;cursor:pointer;
  font-family:'DM Mono',monospace;font-size:var(--t-fine);
  letter-spacing:var(--tr-ui);text-transform:uppercase;
  color:var(--bg-warm);background:var(--ink);
  padding:9px 16px;border-radius:999px;
  transition:transform 0.16s var(--e-out),opacity 0.18s ease;
}
.od-aside-send:active{transform:scale(0.97);}
.od-aside-send[disabled]{opacity:0.5;cursor:default;}
.od-aside-msg{
  margin-top:var(--od-1);
  font-size:var(--t-fine);line-height:var(--lh-ui);color:var(--ink-body);
}
.od-aside-msg:empty{display:none;}
/* The honeypot. Off-screen rather than display:none -- a bot that skips
   hidden fields is the one case this has to catch. */
.od-hp{
  position:absolute;left:-9999px;width:1px;height:1px;
  opacity:0;pointer-events:none;
}

/* ---- the bar: the day on the left, the only action on the right ---- */
/* Lifted off the floor by exactly the strip the nav capsule occupies. It is
   still one row, on a phone too -- that rule is about od-bar's own height,
   and nothing here stacks it. */
.od-bar{
  position:absolute;left:0;right:0;bottom:var(--nav-clear,86px);z-index:20;
  display:flex;align-items:center;gap:var(--od-3);
  padding:22px clamp(24px,7vw,90px) calc(22px + env(safe-area-inset-bottom,0px));
  pointer-events:none;
}
.od-bar > *{pointer-events:auto;}
/* There was a 72%-solid fade behind this whole row. On a phone it laid paper
   over the last ~150px of "the problem", so the ring read as finished and
   nothing said the slide scrolled (Raj, 2026-10-04), so on a phone the bar
   has no surface and the nav tucks away to give the slides room.
   Desktop keeps it. The nav never tucks there, three screens still run
   past a 1366x768 or 1536x864 laptop, and without the fade their last lines
   sat straight across the rail's labels -- type through type. The rail is
   a row of words on desktop, not five dots, so it cannot sit over copy. */
@media(min-width:861px){
  .od-bar::before{
    content:'';position:absolute;inset:0;z-index:-1;
    background:linear-gradient(to top,var(--od-ground) 72%,transparent);
  }
}

/* ---- brand (0)'s ghost: the corner above the bar's button ----
   Asleep on screen one, rising a quarter per screen, awake on the last,
   where the bunny and the cloud step in beside it. Nobody presses it, so
   it lets clicks through. It is a child of the bar: on a tall desktop it
   stands on the bar's top edge over the button; where that corner has
   copy in it (a phone, a short laptop) it drops into the bar's own row. */
.od-bar .od-buddy{
  position:absolute;pointer-events:none;
  right:clamp(24px,7vw,90px);bottom:100%;
  --od-buddy-w:112px;
  display:flex;flex-direction:column;align-items:flex-end;gap:var(--od-1);
}
.od-buddy-ghost{width:var(--od-buddy-w);}
/* the two that join it sit on top, inside the ghost's own column: beside it
   they ran into the disqualifier on the price screen, which is the one
   screen they appear on */
.od-buddy-cast{display:flex;gap:var(--od-1);width:var(--od-buddy-w);}
.od-buddy-tile{
  flex:1 1 0;min-width:0;opacity:0;transform:translateY(10px) scale(0.94);
  transition:opacity .4s var(--e-out),transform .4s var(--e-out);
}
.od-buddy.is-cast .od-buddy-tile{opacity:1;transform:none;transition-delay:.25s;}
.od-buddy.is-cast .od-buddy-tile + .od-buddy-tile{transition-delay:.33s;}
/* Measured 2026-10-04: above the button it is clear of every screen's copy
   at 1440x900, 1536x864 and up, and lands on text at 1366x768 and 1280x720.
   On those short desktops it sits in the bar's own row, ghost only. */
@media(min-width:861px) and (max-height:820px){
  .od-bar .od-buddy{position:static;flex:0 0 auto;--od-buddy-w:44px;}
  .od-buddy-cast{display:none;}
}
/* Not on a phone (Raj, 2026-10-04). The corner covered copy on four of five
   screens, and in the bar the last screen's wide button pushed "the month
   after" to three lines -- a 90px bar became 107. The rail's fill is the
   phone's progress cue. */
@media(max-width:860px){
  .od-bar .od-buddy{display:none;}
}

.od-rail{position:relative;flex:1 1 auto;display:flex;justify-content:space-between;padding-top:14px;}
.od-rail-line{position:absolute;top:0;left:0;right:0;height:1px;background:rgba(30,25,20,0.13);}
/* a highlighter stroke over the hairline, not a hairline in another colour --
   1px on 1px of grey is not a progress bar anyone can read. Blue, because
   the rail is the day, and the day is the craft. */
.od-rail-fill{
  position:absolute;left:0;top:-1px;display:block;
  width:100%;height:3px;border-radius:2px;background:var(--accent-b);
  transform:scaleX(0);transform-origin:left center;
  transition:transform 0.48s var(--e-drawer);
}
.od-tick{
  border:0;background:none;cursor:pointer;padding:6px 0;
  font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--ink-faint);transition:color 0.2s ease;
}
.od-tick.is-on{color:var(--ink);}
@media(hover:hover) and (pointer:fine){ .od-tick:hover{color:var(--ink-body);} }

/* Filled pink with a lift under it, the same treatment the recommended card
   gets on Work With Me. Near-black on warm paper read as another piece of
   type; this reads as the thing to press. */
.od-go{
  flex:0 0 auto;border:1px solid var(--accent-r);cursor:pointer;border-radius:999px;
  padding:13px 26px;background:var(--accent-r);color:#fff;
  box-shadow:0 2px 12px rgba(255,123,172,0.32);
  font-family:'DM Mono',monospace;font-size:var(--t-fine);
  letter-spacing:var(--tr-ui);text-transform:uppercase;
  transition:transform 0.16s ease-out,box-shadow 0.2s ease;
}
@media(hover:hover) and (pointer:fine){
  .od-go:hover{box-shadow:0 4px 18px rgba(255,123,172,0.42);}
}
.od-go:active{transform:scale(0.97);}
/* the label swaps in place -- blur through the middle so the eye reads one
   word changing rather than two words overlapping */
.od-go-label{display:block;transition:filter 0.13s ease,opacity 0.13s ease;}
.od-go-label.is-swapping{filter:blur(3px);opacity:0.35;}

/* ---- the sheet before the booking ----
   See the note at the call site in ciad.html. The same object as brand (0)'s
   own sheets (.q-sheet): warm paper card, 0.28 ink wash, 16px corners,
   in on --e-out from 0.97. A modal, so it scales from the centre. Every type
   size here is already on this page -- the name takes the headline's own
   ramp, the line the lead's, the eyebrow the rail's -- so the deck's budget
   does not move. The name is the one focal point; the button under it is
   the deck's own button, same object, same pink. */
.od-sheet{
  position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
  padding:16px;background:rgba(26,24,20,0.28);
}
.od-sheet[hidden]{display:none;}
.od-sheet-in{
  position:relative;width:100%;max-width:480px;max-height:calc(100dvh - 32px);overflow-y:auto;
  background:var(--bg-warm);border-radius:16px;
  padding:var(--od-3) clamp(24px,6vw,48px);
  box-shadow:0 24px 60px rgba(26,24,20,0.18);
  text-align:center;
  animation:od-sheet-in .22s var(--e-out);
}
@keyframes od-sheet-in{from{opacity:0;transform:scale(0.97);}}
.od-sheet-cast{display:flex;justify-content:center;gap:var(--od-1);margin-bottom:var(--od-2);}
.od-sheet-tile{width:72px;}
.od-sheet-when{
  font-size:var(--t-micro);letter-spacing:var(--tr-label);text-transform:uppercase;
  color:var(--ink-muted);margin-bottom:var(--od-1);
}
/* lining-nums is load-bearing: Cormorant's oldstyle 0 is an o */
.od-sheet-name{
  font-family:'Cormorant Garamond',serif;font-weight:300;font-style:italic;
  font-size:clamp(38px,calc(6.4vw + 4px),78px);line-height:var(--lh-display);
  letter-spacing:var(--tr-display);color:var(--ink);
  font-variant-numeric:lining-nums;
}
.od-sheet-line{
  font-size:var(--t-lead);line-height:var(--lh-prose);color:var(--ink-body);
  margin:var(--od-2) auto;max-width:34ch;font-variant-numeric:lining-nums;
}
.od-sheet-x{
  position:absolute;top:var(--od-1);right:var(--od-1);
  width:44px;height:44px;border:0;background:none;cursor:pointer;border-radius:999px;
  font-family:'DM Mono',monospace;font-size:var(--t-lead);line-height:1;color:var(--ink-muted);
  transition:color 0.18s ease,background-color 0.18s ease;
}
@media(hover:hover) and (pointer:fine){
  .od-sheet-x:hover{color:var(--ink);background:rgba(30,25,20,0.05);}
}
@media(prefers-reduced-motion:reduce){
  .od-sheet-in{animation:none;}
}

/* Short desktops. The price screen is the tallest of the five, so the frame
   tier contracts rather than the month getting cropped. */
@media(min-width:861px) and (max-height:860px){
  #screen-ciad{--od-3:40px;}
  .od-h-price{font-size:48px;}
  /* The price screen is the tallest of the five and the only one that ends
     on content, so on a 768px laptop it was the terms block that went under
     the rail. The month scales rather than losing squares -- all thirty are
     still there -- and this screen's own top margin drops to the tight tier.
     Together that is the 52px the footer needed. */
  .od-month{max-width:200px;}
  .od-slide-price{padding-top:var(--od-1);}
}

/* ---- mobile ---- */
@media(max-width:860px){
  /* The readout drops under the month instead of beside it, and the three
     parts close up: slider, month and price are one object, not three
     things that happen to be stacked. */
  .od-build{grid-template-columns:1fr;gap:var(--od-1);}
  .od-build-controls,.od-month,.od-readout{grid-column:1;}
  .od-readout{grid-row:auto;}

  /* the ring drops under the writing and keeps the same left edge as it --
     it is a circle, but the words on it are still type on this page */
  .od-inner-split{grid-template-columns:1fr;gap:var(--od-3);}
  .od-loop{width:min(100%,320px);}
}
@media(max-width:639px){
  /* the bar is two stacked rows on a phone, so the frame has to clear it */
  #screen-ciad{--od-3:34px;}
  .od-reels{gap:8px;}
  /* five reels shoulder to shoulder is a strip of stamps on a phone: show
     two and a half and let the row scroll */
  .od-reels{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none;}
  .od-reels::-webkit-scrollbar{display:none;}
  .od-reel{height:clamp(150px,24.5vh,240px);}
  /* A reel is ~103px wide here and "copper + cloves" at label tracking is
     105px, so two neighbouring names ran into each other with 2px between
     them. Tighter tracking keeps every name on one line inside its own reel
     -- letting them wrap instead would cost a line the screen hasn't got. */
  .od-reel-cap span{letter-spacing:var(--tr-ui);}
  /* the month has to be reachable without scrolling, so the headline gives
     up the room -- the thing they came to build is the focal point here */
  .od-inner-price{gap:var(--od-2);}
  .od-price-head .od-eyebrow{margin-bottom:var(--od-1);}
  .od-h-price{font-size:30px;}
  .od-month{gap:4px;max-width:128px;}
  .od-build-label{margin-bottom:var(--od-1);}
  /* The month is capped at 172px and the readout sat underneath it, which
     left ~190px of bare paper beside the grid and cost 130px of height the
     screen did not have -- the footer ended up behind the bar. They sit
     side by side now: the month's own height pays for the terms block, and
     the dead band beside the grid is where the price goes. */
  .od-build{grid-template-columns:128px minmax(0,1fr);column-gap:var(--od-2);}
  .od-build-controls{grid-column:1 / -1;}
  .od-month{grid-column:1;grid-row:2;}
  .od-readout{grid-column:2;grid-row:2;}
  /* In a 190px column the currency toggle can no longer ride beside the
     figures, so the readout goes back to one stack with the toggle under it.
     The toggle is 155px wide and still fits. */
  .od-readout .od-cur{margin-top:var(--od-1);}
  .od-foot{gap:var(--od-1);}
  /* The disqualifier goes on a phone. It is three lines and 88px, and on a
     390x706 live viewport it was sitting under the bar's gradient where it
     could be seen but not read -- which is worse than not being there. It
     still runs on every screen wide enough to hold it. */
  .od-nope{display:none;}
  /* four stations across a phone: the station names drop to the label size so
     "positioning" stays on one line, and the ring loses the room it does not
     have */
  .od-step b{font-size:var(--t-micro);}
  /* Four stations across a phone leaves 84px a column, and "1-3 days a month"
     wraps to fill nearly all of it. Without the inline padding the figures
     under two neighbouring stations end up 6px apart and read as one string
     -- the gap between them has to beat the gap inside them. */
  .od-step{padding-inline:5px;}
  /* An eyebrow and the headline it labels are one thing, so the tight tier is
     the right gap between them regardless -- on a phone it is also 16px that
     every screen gets back. */
  .od-eyebrow{margin-bottom:var(--od-1);}
  .od-step span{letter-spacing:var(--tr-ui);}
  /* the run-up to the line, the return under it and the note after it all
     come in a step, which is what this screen owed the 706px viewport */
  /* Bound to the paragraph it draws, the same way the ring is on the screen
     before it -- the bar is the next thing after, 94px down.
     The run-up to the line is one variable shared by the rail's offset and the
     handover tick's height, because all three are the same distance and drift
     apart the moment they are three numbers. It scales with the viewport for
     the same reason the ring does: this screen carries an eight-line paragraph
     on a phone and is the last one to fit. */
  .od-path{--run:clamp(25px,4.2vh,30px);margin-top:var(--od-1);padding-top:var(--run);}
  .od-path-rail{top:var(--run);}
  .od-path-out{height:var(--run);}
  .od-path-back{height:clamp(11px,2vh,14px);margin-top:8px;}
  .od-step{padding-top:14px;gap:2px;}
  .od-hand-block{margin-top:var(--od-1);}
  /* Everything on this screen is sized off one constraint: the centre of the
     ring has to stay two single lines. At three or four it grows into the rows
     where "distribution" and "strategy" sit, and a smaller ring does not fix
     that -- it makes it worse. Dropping the six words and the caption to the
     label size is what buys those two lines their width back, and it is what
     lets the ring come down to 240 so this screen lands inside a real 706px
     Safari viewport rather than the 844 the device claims.
     The box is square because a circle needs a square, but the drawing does
     not reach the top or bottom of it -- the negative margin gives back the
     empty band above "ideation" and below "production" instead of paying for
     it twice.
     The ring sits close to the paragraph on purpose: it is that paragraph's
     six disciplines drawn, not a separate part, and the bar is 110px below
     it, so the two still read as one block against everything else. */
  .od-inner-split{gap:var(--od-1);}
  /* 240 is a floor, not a preference, and it must not scale with the viewport.
     "that's a full-time job" is 140px of Caveat on one line; the ring's inside
     measures 0.68 of its width, so anything under ~240 leaves the hand under
     4px of air a side -- and the moment it wraps, the centre becomes three
     lines and grows straight into the rows where "distribution" and
     "strategy" sit. Scaling this to vh was tried on 2026-08-11 and collided
     on Raj's own phone. Height comes out of the frame instead. */
  .od-loop{
    --ring:min(100%,240px);
    width:var(--ring);
    /* The negative margin gave back the empty arc bands above "ideation" and
       below "production". With the sitewide +4 the paragraph above runs eight
       lines instead of six, so `safe center` stops centring and the 30px the
       ring hangs below its flow box falls outside the slide's scroll range --
       clipped, not scrollable, which took POST, PSYCHOLOGY and PRODUCTION off
       the screen entirely. The slide scrolls; a drawing that is cut off does
       not come back.
       The bottom margin is the nav capsule: `.od-slide` reserves
       `--od-bar-h` only, and the bar sits one --nav-clear above the bottom
       edge, so the last 74px of any slide are behind the capsule. Every other
       screen ends well short of it; this one ends on "production". */
    margin-block:0 var(--nav-clear,74px);
  }
  .od-loop-mid{width:calc(var(--ring) * 0.59);font-size:9px;}
  .od-chain li{padding:2px 5px;font-size:9px;}
  /* The bar was two stacked rows here -- rail, 34px, full-width button -- and
     it came to 168px. A phone in Safari has about 706px of live viewport, not
     the 844 the device claims, so the bar was taking a quarter of the screen
     before a word of content landed and every slide but the first overflowed
     underneath it. One row, as on a desktop. The button keeps a 44px target;
     it is the width it gives up, not the height. */
  .od-bar{gap:var(--od-2);padding-top:14px;padding-bottom:calc(14px + env(safe-area-inset-bottom,0px));}
  .od-go{padding:14px 20px;}
  .od-rail{padding-top:12px;}
  /* The bar already fades content out beneath itself, so the frame tier under
     the content is doing less work here than the gap it costs. */
  .od-slide{padding-bottom:calc(var(--od-bar-h,84px) + var(--od-1));}
  /* only the current stop keeps its label; the rest become tick marks */
  .od-tick{padding:8px 0;}
  .od-tick:not(.is-on){
    position:relative;width:14px;overflow:hidden;
    white-space:nowrap;color:transparent;
  }
  .od-tick:not(.is-on)::after{
    content:'';position:absolute;left:0;top:11px;
    width:14px;height:1px;background:rgba(30,25,20,0.18);
  }
  .od-price-head{gap:var(--od-1);}

  /* ---- the clutter pass (Raj, 2026-10-04: "reduce this clutter man") ----
     Measured at 390x706 first: the deck bar and the nav capsule took 164px
     between them and every slide but the first ran 41-144px under the bar,
     so both drawings sat half behind it. What came off, and why:
     - the rail's labels. One word between four grey dashes, under a line,
       over a capsule full of words -- a third row of type in the bottom
       quarter. The fill line stays as the progress; swiping is the skip.
     - the figures under the stations. "1-3 days a month" and "6-20 videos"
       are on the price screen two swipes later, built by their own hand.
       Say each fact once. They also wrapped to three lines in 84px columns.
     - "then you're done". Its tick crossed the line between two stations
       and its label sat on the line itself. The dashed return still says
       the loop.
     And one reservation instead of four: the slide reserves the bar AND the
     capsule, so the ring, the footer, the quote and the hand note stop each
     carrying --nav-clear of their own. */
  .od-tick{display:none;}
  .od-rail{padding-top:0;align-self:center;height:3px;}
  .od-step span{display:none;}
  .od-path-out{display:none;}
  .od-path{--run:14px;}
  .od-lead{line-height:1.5;}
  .od-slide{padding-bottom:calc(var(--od-bar-h,84px) + var(--nav-clear,74px) + var(--od-1));}
  .od-loop{margin-block:0;}
  /* "the basecamp exists to fill that gap." comes off on a phone (Raj,
     2026-10-06, his pick). At iPhone 13 Chrome's 664px the ring ran 82px
     under the dots and NEXT; the line was the ~70px that could go, and the
     ring under it already draws the gap. */
  .od-lead-close{display:none;}
  /* Five dots, not a line. The line needed a paper capsule under it so text
     scrolling past didn't read as struck through, and on screen one that
     capsule was an empty box that looked like a text field (2026-10-04
     sweep). Dots say "five screens, you are here" before a word is read,
     and are small enough to sit over copy without a backing. The current one
     stretches, in the rail's own blue. The ticks are already buttons, so the
     dots still jump to a screen. */
  .od-rail{
    flex:0 1 auto;min-width:0;height:auto;padding:0;align-self:center;
    display:flex;justify-content:flex-start;align-items:center;gap:0;
  }
  .od-rail-line{display:none;}
  .od-bar .od-tick{
    display:block;position:relative;flex:0 0 auto;
    width:16px;min-height:44px;padding:0;
    text-indent:100%;white-space:nowrap;color:transparent;overflow:hidden;
  }
  .od-bar .od-tick::after{
    content:'';position:absolute;left:5px;top:50%;
    width:6px;height:6px;margin-top:-3px;border-radius:3px;
    background:rgba(30,25,20,0.2);
    transition:width .3s var(--e-out),background-color .3s ease;
  }
  .od-bar .od-tick.is-on{width:30px;}
  .od-bar .od-tick.is-on::after{width:20px;background:var(--accent-b);}

  /* ---- the calm pass (Raj, 2026-10-04: "remove overwhelm from site
     completely") -- what came off the price screen on a phone, and why:
     - "45+ brands · since 2020". Screen one already says it, and on a phone
       the price screen is twelve lines between the slider and the button.
     - the cadence note. The thirty squares draw it -- six blue days spread
       across a month is "one every few days" before anyone reads it.
     The currency switch steps down to the size of the line it qualifies:
     a full pill toggle was the second-largest object on the screen. */
  .od-proof,#od-cadence{display:none;}
  .od-cur{margin-top:var(--od-1);padding:2px;}
  .od-cur-btn{padding:4px 10px;min-height:32px;}
  /* the tier's name was printed twice, 50px apart: lit in the marks under
     the slider, then again in pink over the price. The marks keep it. */
  .od-tier-name{display:none;}
  /* ---- the price screen, cut to what it's for (Raj, 2026-10-05: "still
     VVVV clutttered"; he picked each of these) ----
     Off on a phone: the eyebrow (the rail says where you are), "2 shoot
     days" (the headline says one to three), and the whole footer -- the
     terms, "30 min call · free" and the handle link. That leaves the
     headline, the slider, the month, the price and one button. Desktop
     keeps all of it. */
  .od-price-head .od-eyebrow,#od-days,.od-slide-price .od-foot{display:none;}

  /* ---- "the work" scrolls sideways again (Raj, 2026-10-05) ----
     It was a stacked list of five for one build, with 28px thumbnails:
     "this format you cant even see the reel - v poorly executed". The
     list's rules came off whole; the row is the desktop's sideways row.
     Each client's name wraps to its own reel's width -- two of them ran
     into each other ("copper + clovesupsurge labs"). */
  .od-reel-cap{width:0;min-width:100%;}
  /* testimonials off on a phone (Raj, 2026-10-05) -- the reels are the proof */
  #screen-ciad .od-voice{display:none;}  /* id: .od-voice{display:grid} sits later in the file */

  /* ---- the terms, one voice (2026-10-04) ----
     Three treatments in four lines -- 18px bold uppercase, 15px muted
     uppercase, sentence case -- so the block read as four unrelated things.
     Bullets would have been a fifth. One size now; the commitment is the
     only line in ink, which is all the emphasis it needs. The separator
     dot goes where the line breaks, so no line starts with punctuation. */
  .od-term{font-size:var(--t-fine);}
  .od-term-sep{display:none;}
  .od-term-tail{display:block;}
  .od-foot{row-gap:var(--od-2);}
}

/* ---- reduced motion ---- */
@media(prefers-reduced-motion:reduce){
  .od-track{transition:none;}
  /* the hover lift is movement, and so is the month's staggered sweep */
  .od-reel:hover{transform:none;}
  .od-day::after{transition-delay:0ms;}
  .od-rail-fill{transition:none;}
  #screen-ciad{transition:none;}
  /* the two diagrams arrive finished. Reduced motion is fewer and gentler,
     not zero -- but a stroke travelling round a circle is the movement it
     asks to be spared, and the drawing carries no information the finished
     state doesn't. */
  .od-ring-draw{stroke-dashoffset:0;}
  .od-chain li,.od-loop-mid,.od-path-out,.od-path-back-label,
  .od-step b,.od-step span{opacity:1;filter:none;}
  .od-step::before{opacity:1;transform:scale(1);}
  .od-path-rail i{transform:scaleX(1);}
  .od-path-back{clip-path:inset(0 0 0 0);}
  .od-ring-draw,.od-chain li,.od-loop-mid,.od-path-rail i,
  .od-step::before,.od-step b,.od-step span,
  .od-path-out,.od-path-back,.od-path-back-label{transition:none;}
}

/* ======================================================
   SCREEN 4: ABOUT

   Writing on the left, photographs on the right, bled to the edge of the
   viewport. No cards, no tape, no tilt, no drift — the pictures carry the
   personality and the type stays out of their way. (Emmi reference.)

   Budgets (see CLAUDE.md "The subtraction rule"):
     type    5 sizes  — name / section / text / small / hand
     accent  2 colours, and the split is the whole point:
               blue  = the work   (the basecamp, the 45+ line, work links)
               pink  = the person (the handwritten asides, instagram, mailto)
             Used interchangeably they would be decoration; split like this
             the colour tells you which half of the page you are reading.
     spacing 3 tiers  — 8 inside a unit, 32 between units, 64 between bands.
             Fluid on the section/unit tiers so the writing fits the viewport
             at 768px tall without deleting a section or tightening leading.
     objects 0

   Cormorant for the name and section heads, DM Mono for everything you
   read -- the site's own pairing, same as every other page. Caveat shows
   up on a handful of asides only, never on anything load-bearing.
====================================================== */
#screen-about{z-index:45;background:var(--bg-warm);overflow-y:auto;overflow-x:hidden;}

#about-inner{
  --name:clamp(34px,calc(3.2vw + 4px),50px);
  --sec:32px;
  --text:19px;
  --small:17.5px;
  --hand:24px;

  --sec-rule-gap:13px;

  --t:8px;
  --u:clamp(18px,2.4vh,30px);
  --s:clamp(26px,4.4vh,44px);

  /* Air above the writing, shared by the mosaic so the first line of type and
     the top edge of the photographs sit on one line. One value at every
     height: the short-viewport branch used to cut it back to buy column
     height, which is why raising it did almost nothing on a 900-tall laptop
     -- 920 is a max-height, so the commonest desktop there is was taking the
     short value. The column no longer has to fit anyway.

     Was clamp(34px,4.6vh,52px), and every pixel of the difference was
     clearance for a 46px nav on the top edge. That nav is a capsule at the
     bottom now, so this is just a page margin, and it is the site's own --
     30px is what case-studies and Work With Me both put above their first
     line on a phone. Raj, 2026-08-26: "header adjusted negative space no
     longer required sitewide for any device as nav bar has been brought
     towards the bottom."

     about.js reads this back off getComputedStyle when it packs the hero
     band, so what comes off the top goes into the photographs rather than
     into a gap further down. */
  --top:clamp(30px,3.4vh,44px);

  display:grid;
  grid-template-columns:minmax(380px,48%) 1fr;
  align-items:start;
  min-height:100%;
}
#about-back{display:none;}

/* ── the writing ───────────────────────────────────── */
/* padding-top is the page margin plus room for the italic ascenders of
   Cormorant at display size, which overflow their line box */
/* A flex column so the playlist can sit on the floor of the writing rather
   than wherever the prose happens to end. flex-start + margin-block:auto on
   the docked child, never align-items:center -- centring a child that can
   outgrow an overflow:auto parent pushes the overflow above the scroll
   origin, where it cannot be reached. */
.col{
  /* Fluid, because the room to give it is room the column has to find
     somewhere else, and a 1080-tall screen has less of it spare than a
     900-tall one does. The mosaic takes the same value so the first line of
     type and the top edge of the photographs share a baseline.
     The two 2026-08-10/08-12 raises this carried ("it's too close to the nav
     bar") were both answering a bar on the top edge, and both came back out
     on 2026-08-26 when that bar moved to the bottom. What is left is the
     margin and Cormorant's overflowing ascenders, which is all it ever
     needed to be. */
  padding:var(--top) 38px var(--t) clamp(20px,3.2vw,48px);
  max-width:700px;
  display:flex;flex-direction:column;align-items:stretch;
  /* The writing stays while the photographs run past it. align-self:start is
     required -- a stretched grid item fills the whole (now several-screens
     tall) track and has nothing to travel within.
     sticky *top*, with a negative offset -- not sticky bottom, which simply
     never engages inside #screen-about's scroller (measured: the column just
     scrolls away and leaves half the page bare). about.js sets top to
     viewport minus column height, so a column that overflows scrolls exactly
     far enough to show its last line and pins there, and one that fits pins
     at 0. Which is the behaviour sticky bottom was supposed to give. */
  align-self:start;
  min-height:100vh;
}
.col.pinned{position:sticky;top:0;}
.name{
  font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:400;
  font-size:var(--name);line-height:1.1;letter-spacing:-0.015em;
  color:var(--ink);margin:0 0 var(--t);
}
.lede{
  font-family:'DM Mono',monospace;font-size:var(--text);
  line-height:1.6;color:var(--ink-body);margin:0 0 var(--u);text-wrap:pretty;
}
.col a{
  color:var(--ink);text-decoration:none;
  border-bottom:1px solid rgba(13,138,175,0.45);
  transition:color .18s ease,border-color .18s ease;
}
/* Colour, not scale. These links sit inside sentences, and a transform needs
   inline-block to apply -- which would make "and 40 more" and "the
   portfolio" atomic boxes that jump to the next line whole instead of
   wrapping. A word in running text answers a press by changing colour; only
   things shaped like buttons should shrink. Instant, because the press has
   already happened by the time a 180ms fade would finish. */
.col a:active{color:var(--accent-b);border-bottom-color:var(--accent-b);transition-duration:0s;}
.col a.me:active{color:var(--accent-r);border-bottom-color:var(--accent-r);}
@media(hover:hover) and (pointer:fine){
  .col a:hover{color:var(--accent-b);border-bottom-color:var(--accent-b);}
}
.col a:focus-visible{outline:2px solid var(--accent-b);outline-offset:3px;border-radius:2px;}
/* the person's links, not the work's */
.col a.me{border-bottom-color:rgba(255,123,172,0.55);}
@media(hover:hover) and (pointer:fine){
  .col a.me:hover{color:var(--accent-r);border-bottom-color:var(--accent-r);}
}

/* ── section heads ─────────────────────────────────── */
/* Raj, 2026-08-12: "a team of one needs to be highlighted i guess, same for
   experience and education." At --sec 21 against --text 15 the three heads
   were four points above the body in a face nobody reads as loud, so the
   page had no visible sections at all -- squint at it and the whole column
   is one grey block.

   Two things fix it together, which is what he asked for. Size and air do
   the hierarchy: the heads jump a third, and the gap above each one is now
   several times the gap below it, so a head belongs to what follows rather
   than floating between two paragraphs. The rule is the marker on top of
   that -- short, flush with the column's spine, so it declares a start
   without indenting the heading out of alignment with everything else.

   The rule takes the colour the page already runs on: pink is the person,
   so "a team of one" gets it; blue is the work, so the two records of it do.
   No new colour, and the split says which half you are reading -- the same
   job "one" (pink) and "all of them" (blue) already do inside the words. */
.sec{
  font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:400;
  font-size:var(--sec);color:var(--ink);
  margin:0 0 var(--t);letter-spacing:-0.012em;
  --sec-rule:var(--accent-r);
}
.sec::before{
  content:'';display:block;
  width:22px;height:2px;border-radius:1px;
  background:var(--sec-rule);
  margin-bottom:var(--sec-rule-gap);
}
.recs .sec{--sec-rule:var(--accent-b);}
.secsub{
  font-family:'DM Mono',monospace;font-size:var(--small);
  line-height:1.55;color:var(--ink-muted);margin:0 0 var(--u);text-wrap:pretty;
}

/* ── colour inside a heading ───────────────────────── */
/* The accent lands on the word that carries the meaning, never on the whole
   line -- so the heads that make a claim get one word of ink and the heads
   that are only labels ("experience", "education") get none. Colouring all
   five would make colour the heading style, which is decoration, not
   hierarchy.
   Same split the links already run on: pink is the person (raj, the "one" in
   a team of one), blue is the craft ("all of them", which is pointing at the
   eleven disciplines directly underneath it). Spans only -- a.me keeps its
   own rule and is untouched. */
.col span.me{color:var(--accent-r);}
.col span.work{color:var(--accent-b);}

/* ── a team of one ─────────────────────────────────── */
/* A stacked list in two columns, not pills. Ten bordered chips would be ten
   more boxes on a page whose whole argument is that one person did all of
   this -- a plain list reads as a range, a chip cloud reads as a CV site. */
/* Two columns, not three: at three, "strategy & positioning" wrapped onto a
   second line under a dash that belonged to the line above it, which reads
   as a wrap bug rather than a list. */
.skills{
  list-style:none;padding:0;margin:0;
  columns:2;column-gap:var(--u);
}
.skills li{
  font-family:'DM Mono',monospace;font-size:var(--small);
  line-height:1.7;color:var(--ink-body);
  break-inside:avoid;
  padding-left:15px;position:relative;
}
/* a rule, not a bullet: these are capabilities on a ledger, not a to-do list */
/* The rules used to be blue. Eleven repeated blue ticks were the page's most
   frequent instance of the accent and the least meaningful one -- a bullet
   substitute, spent on ornament. The blue is now on the phrase above them
   instead ("all of them"), which points at this list and means something. */
.skills li::before{
  content:'';position:absolute;left:0;top:0.92em;
  width:7px;height:1px;background:rgba(30,25,20,0.22);
}

/* ── ruled records ─────────────────────────────────── */
/* Experience and education sit side by side. Stacked they were 242px of a
   900px column saying very little; paired they read as one ledger and the
   band below them gets the room back. */
.recs{
  display:grid;grid-template-columns:1fr 1fr;
  gap:var(--u);align-items:start;
}
.rows{list-style:none;padding:0;margin:0;}
/* Flex, not grid: name and role have to run together as one phrase and
   wrap as one, with the year pushed to the right edge. Placing them as two
   grid items in the same cell stacked them on top of each other. */
.row{
  display:flex;align-items:baseline;flex-wrap:wrap;
  column-gap:5px;
  padding:6px 0;
  border-bottom:1px solid rgba(30,25,20,0.11);
  font-family:'DM Mono',monospace;font-size:var(--small);
}
/* the rules separate records; there is nothing after the last one to
   separate it from, so it is decoration */
.row:last-child{border-bottom:none;}
.row-name{color:var(--ink);}
.row-name::after{content:' /';color:var(--ink-faint);}
.row-role{color:var(--ink-muted);}
.row-year{
  margin-left:auto;padding-left:14px;
  color:var(--ink-muted);white-space:nowrap;
  font-variant-numeric:tabular-nums;
}
/* In the paired ledger a column is ~300px, which is not enough for
   "the form co. / editing & photography  2020 — 2022" on one line. Left to
   flex-wrap, three of six rows dropped their year to a second line and the
   other three did not — which reads as a bug, not a layout. So every row in
   the pair is two lines on purpose: the record and its year on top, the role
   underneath. No fact is abbreviated away to make a column fit. */
.recs .row{
  display:grid;grid-template-columns:1fr auto;
  /* --t, not a hand-picked 10. The record and its year are two halves of one
     line, which is exactly what the tight tier is for, and a 10 sitting two
     pixels off the 8 everywhere else was a value nobody chose on purpose. */
  column-gap:var(--t);row-gap:1px;line-height:1.5;
}
.recs .row-name{grid-area:1/1;}
.recs .row-name::after{content:none;}
.recs .row-year{grid-area:1/2;margin:0;padding:0;}
.recs .row-role{grid-area:2/1/2/-1;}

/* ── marks made by hand ────────────────────────────── */
/* Caveat, and only ever on an aside. Nothing you need to read is here.
   Pink, because every aside on this page is the person talking. */
/* The fallback is Cormorant, not the generic `cursive`. `cursive` is Snell
   Roundhand on iOS and Comic Sans on Windows -- a fallback that changes the
   word's typeface per device is the bug Raj reported. Cormorant italic is
   already this page's display face, so a failure stays inside the system. */
.hand{
  font-family:'Caveat','Cormorant Garamond',serif;font-weight:600;
  font-size:var(--hand);line-height:1;color:var(--accent-r);
}

/* Sketched underline -- a drawn squiggle, not a border. */
.col a.sketch{
  border-bottom:none;white-space:nowrap;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 8' preserveAspectRatio='none'%3E%3Cpath d='M1 5.2c18-3 38-3.6 58-1.4 20 2.2 40 1.6 60-1.2' fill='none' stroke='%230d8aaf' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-size:100% 6px;
  background-position:0 100%;
  padding-bottom:6px;
}
@media(hover:hover) and (pointer:fine){
  .col a.sketch:hover{color:var(--accent-b);}
}

/* ── the one claim that has to land ────────────────── */
/* Exactly one highlighter on the page. There were two, three lines apart,
   and two highlights are no highlight -- so the lede's version came off and
   this one got the ink. Raj: the 45+ stat "needs to be highlighted". */
.brag{
  font-family:'DM Mono',monospace;font-size:var(--text);
  line-height:1.9;color:var(--ink);margin:0 0 var(--t);text-wrap:pretty;
}
.brag b{font-weight:500;color:var(--ink);}
.mark{
  background-image:linear-gradient(103deg,
    rgba(13,138,175,0) 0.4%,
    rgba(13,138,175,0.42) 2.4%,
    rgba(13,138,175,0.37) 95%,
    rgba(13,138,175,0) 99%);
  background-size:100% 72%;
  background-position:0 78%;
  background-repeat:no-repeat;
  padding:0 4px;
  -webkit-box-decoration-break:clone;box-decoration-break:clone;
}
/* The client names are the evidence for the line above them, so they hang
   off it directly. The "selected clients" heading was a label for something
   already labelled by the sentence it sits under. */
.clients{
  font-family:'DM Mono',monospace;font-size:var(--small);
  line-height:1.7;color:var(--ink-muted);margin:0 0 var(--s);text-wrap:pretty;
}

/* the column's own bottom padding closes the page; the last block does not
   also need a margin under it */
.col > *:last-child{margin-bottom:0;}

/* ── the playlist ──────────────────────────────────── */
/* Docked to the floor of the writing column. No rule above it and no box
   around it -- spacing is what separates it from the prose, and a divider
   would be an ornament standing in for spacing that already works.
   Pink, because the music is the person talking, not the work. */
.player{
  /* its own clamp rather than --s: at 1080 tall the section tier is 48px and
     that was the two pixels the column overflowed by */
  margin-top:auto;padding-top:clamp(16px,2.2vh,28px);
  display:grid;grid-template-columns:auto 1fr auto;
  /* --u, not 14. 14 against the unit tier's 16 was the page's one pair of
     indistinguishable spacings: two values two pixels apart, doing the same
     job of separating parts of one row, costing a decision and saying
     nothing. The three parts of the player are one unit's worth apart. */
  align-items:center;column-gap:var(--u);
  /* capped, because a progress bar run the full width of the column stops
     reading as a control and starts reading as a rule under the writing --
     and this page separates things with spacing, not rules */
  max-width:400px;
}
.player[hidden]{display:none;}

.pl-btn{
  width:32px;height:32px;flex:0 0 auto;
  display:grid;place-items:center;position:relative;
  border:1px solid rgba(255,123,172,0.45);border-radius:50%;
  background:transparent;color:var(--accent-r);cursor:pointer;
  transition:transform .16s var(--e-out),
             background-color .2s ease,border-color .2s ease;
}
@media(hover:hover) and (pointer:fine){
  .pl-btn:hover{background:rgba(255,123,172,0.12);border-color:var(--accent-r);}
}
.pl-btn:active{transform:scale(0.95);}
.pl-btn:focus-visible{outline:2px solid var(--accent-r);outline-offset:3px;}

/* Both glyphs stacked; the pressed state crossfades between them rather
   than swapping instantly, which at this size reads as a flicker. */
.pl-i{
  grid-area:1/1;width:10px;height:12px;fill:currentColor;
  transition:opacity .15s ease,transform .15s var(--e-out);
}
/* a triangle's visual centre sits left of its bounding box */
.pl-play{transform:translateX(1px);}
.pl-pause{opacity:0;transform:scale(0.86);}
.pl-btn[aria-pressed="true"] .pl-play{opacity:0;transform:translateX(1px) scale(0.86);}
.pl-btn[aria-pressed="true"] .pl-pause{opacity:1;transform:scale(1);}

.pl-body{min-width:0;}
.pl-title{
  font-family:'DM Mono',monospace;font-size:var(--small);
  line-height:1.3;color:var(--ink-muted);margin:0 0 6px;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.pl-bar{
  height:2px;border-radius:2px;overflow:hidden;
  background:rgba(30,25,20,0.10);
}
.pl-bar i{
  display:block;height:100%;border-radius:2px;
  background:var(--accent-r);
  transform:scaleX(0);transform-origin:left center;
  transition:transform .25s linear;
}
.pl-out{
  font-family:'DM Mono',monospace;font-size:var(--small);
  white-space:nowrap;
}

@media(prefers-reduced-motion:reduce){
  .pl-btn,.pl-i{transition:opacity .15s ease;}
  .pl-btn:active{transform:none;}
  .pl-bar i{transition:none;}
}

/* The embed is the audio element. Clipped, not display:none -- a hidden
   iframe can have its media suspended by the browser. */
#sp-host{
  position:fixed;left:0;bottom:0;width:1px;height:1px;
  overflow:hidden;clip-path:inset(50%);opacity:0;pointer-events:none;
}

/* ── the bands ─────────────────────────────────────── */
/* The ledger holds the two credential bands. Which way it folds depends on
   the shape of the window, not on a breakpoint: on a tall screen it runs
   down the page and each band splits internally into two; on a short one it
   turns on its side, skills in one column beside the records. Same content,
   same order, no section deleted to make a 13" laptop fit. */
.ledger{display:grid;grid-template-columns:1fr;margin-bottom:var(--u);}
.band{margin:0 0 var(--s);}
.band:last-child{margin-bottom:0;}

/* ── the photographs ───────────────────────────────── */
/* Masonry via columns. A fixed-height grid with object-fit:cover was
   slicing the tops off people -- every one of these is a tall photograph
   and the tiles were landscape. Columns let each keep its own shape, so
   nothing is cropped and there is no letterboxing either. */
.shots{
  column-count:6;column-gap:7px;
  padding:6px 6px 6px 0;
}
/* Once the gallery script has run, the mosaic is justified rows instead of
   masonry columns: row height is continuous where a column count is
   quantised, so the grid can fill the viewport exactly at any window size
   without cropping a single photograph. */
/* padding-top is --top, the same value the writing takes, so the first line
   of type and the top edge of the photographs sit on one line. (It read
   "clears the fixed nav" -- in two identical copies -- back when there was
   a nav on the top edge to clear.)
   No justify-content and no fixed height any more: the mosaic is taller than
   the screen now, so there is no leftover to distribute. Raj, 2026-08-12,
   asked for playground's quality here -- pictures you move through rather
   than pictures fitted to a pane -- while keeping all thirty-six, and the two
   only reconcile by letting the pane run past the fold. The hero band still
   fills the first screen exactly; everything under it flows at a row height
   chosen for the photographs instead of one solved for the viewport. */
.shots.justified{
  column-count:auto;
  display:flex;flex-direction:column;
  gap:7px;padding:var(--top) 0 calc(6px + var(--nav-clear,86px));
}
.shots.justified .srow{display:flex;gap:7px;}
.shots.justified .tile{margin:0;flex:0 0 auto;}
.shots.justified .tile img{height:100%;object-fit:cover;}

/* The hero band: supporting tiles in a strip on the left, the one
   photograph on the right, bled to the viewport edge. */
.shots.justified .hband{display:flex;gap:7px;align-items:stretch;}
/* the strip spreads to reach the hero's foot; the bottom band never does,
   because its height is exactly the height of the rows in it */
.shots.justified .hstrip{
  display:flex;flex-direction:column;justify-content:space-between;gap:7px;
}
.shots.justified .bband{
  display:flex;flex-direction:column;justify-content:flex-start;gap:7px;
}

/* Every tile that is not the hero. No shadow, no lift, no fade: the
   photographs are printed at full strength and the hero is simply much
   bigger than they are. Raj, 2026-08-10: "i dont like the washed look on
   pictures - the sizing of the main is enough of a focal point." Size is
   doing the hierarchy on its own, so dimming them was taking the pictures
   away to make a point that was already made. */
.tile{
  margin:0 0 7px;
  break-inside:avoid;-webkit-column-break-inside:avoid;
  overflow:hidden;border-radius:4px;background:#e8e3db;
  /* so a lifted tile can sit above the row it is in */
  position:relative;
}

/* Every photograph answers the cursor. Raj, 2026-08-12: "upon hover - idk
   make them nudge or something along the lines."
   5px and 1.02 -- enough to read as a nudge on a 245px tile, small enough to
   survive thirty-four of them being hoverable. transform and box-shadow only:
   both composite, and neither reflows the justified row it sits in, which
   anything touching width or margin would. 200ms ease-out because the tile is
   arriving; an ease-in here reads as lag at the exact moment the cursor lands.
   The other half of this hover is in about.js -- the drift stops. */
@media(hover:hover) and (pointer:fine){
  .shots.justified .tile:not(.hero){
    transition:transform .2s var(--e-out),
               box-shadow .2s var(--e-out);
  }
  .shots.justified .tile:not(.hero):hover{
    transform:translateY(-5px) scale(1.02);
    box-shadow:0 2px 6px rgba(45,33,22,0.10), 0 14px 30px -12px rgba(45,33,22,0.26);
    z-index:2;
  }
}
@media(prefers-reduced-motion:reduce){
  .shots.justified .tile:not(.hero){transition:none;}
  .shots.justified .tile:not(.hero):hover{transform:none;}
}
.tile img{width:100%;height:auto;display:block;border-radius:inherit;}

/* The one photograph that gets depth. */
.tile.hero{
  border-radius:9px;
  box-shadow:0 2px 5px rgba(45,33,22,0.09), 0 18px 44px -16px rgba(45,33,22,0.30);
  /* 280ms, not the 400 the old uniform tiles used: this is a hover, and a
     hover that takes four tenths of a second to arrive feels like lag rather
     than weight */
  transition:transform .28s var(--e-out),
             box-shadow .28s var(--e-out);
}
@media(hover:hover) and (pointer:fine){
  .tile.hero:hover{
    transform:translateY(-4px);
    box-shadow:0 2px 5px rgba(45,33,22,0.09), 0 26px 60px -18px rgba(45,33,22,0.34);
  }
}
@media(prefers-reduced-motion:reduce){
  .tile.hero{transition:none;}
  .tile.hero:hover{transform:none;}
}

/* ── the ascii reveal ──────────────────────────────── */
/* Every photograph arrives as a coarse drawing of itself and dissolves into
   the print. It is not a resting state: the canvas is removed the frame it
   has finished fading, so nothing this rule describes survives the arrival.
   Ink on paper, one colour -- blue and pink mean the work and the person on
   this page, and neither of them means texture. */
.tile-ascii{
  position:absolute;inset:0;width:100%;height:100%;
  display:block;border-radius:inherit;
  pointer-events:none;z-index:1;
  /* Exiting, so --e-out. Not --e-dissolve, which is the video intro's and is
     a film cut over a second; this is a half-second hand-off between two
     pictures of the same thing.
     The blur is the crossfade mask: without it you watch two distinct images
     overlap for half a second, with it the characters melt into the grain of
     the photograph under them. 3px, transient, on a tile-sized layer. */
  transition:opacity .5s var(--e-out), filter .5s var(--e-out);
}
.tile-ascii.gone{opacity:0;filter:blur(3px);}

/* The photograph waits for its drawing. On a cold load the image has not
   decoded yet and this does nothing; on a reload out of cache it is the
   difference between the print flashing for two frames before the ascii
   lands on top of it. about.js clears it per tile, and on a timer if a
   decode never comes. */
.tile.ascii-wait img{opacity:0;}

/* the reveal script fades whole cards in; nothing here needs it */
.obj{opacity:1;}

/* ── narrower desktops ─────────────────────────────── */
@media(max-width:1160px){ .shots{column-count:5;} }
@media(max-width:1000px){ .shots{column-count:4;} }
@media(max-width:1100px){
  #about-inner{grid-template-columns:minmax(300px,44%) 1fr;}
  /* --top, like every other width, so the mosaic and the writing keep their
     shared top line here too. */
  .col{padding:var(--top) 30px 20px clamp(20px,3.5vw,40px);}
  /* below this the ledger's two columns start breaking "visual
     communication" onto its own line, which reads as a wrap bug */
  .recs{grid-template-columns:1fr;gap:var(--u);}
}

/* Short viewports. The writing, not the photographs, is what overflows
   there. Both bands already run in columns of their own, so the only thing
   left is a single step down in type.
   The threshold is 920, not 880: a 900-tall laptop is the commonest desktop
   there is, and the playlist plus the Dhiman line put the column 137px over
   at that height. Stepping the scale is what buys those back -- the
   alternative was deleting a section, and the section nearest the chopping
   block was the education list Raj has already corrected twice. */
@media(min-width:821px) and (max-height:920px){
  #about-inner{
    --name:clamp(30px,calc(2.4vw + 4px),37px);--sec:28px;--text:17.5px;--small:16.5px;--hand:21px;
    --sec-rule-gap:10px;
    --u:16px;--s:24px;
  }
  .col{padding:var(--top) 34px var(--t) clamp(18px,3vw,42px);}
  /* at 12.5px the longest skill clears a third-width column, so the list
     loses a row here without anything wrapping */
  .skills{columns:3;}
  .skills li{line-height:1.75;}
  .row{padding:5px 0;}
}

/* ── stacked ───────────────────────────────────────── */
@media(max-width:820px){
  #about-inner{grid-template-columns:1fr;}
  .col{
    /* 86px was the 46px nav plus its air. On a 706px viewport that was 12%
       of the screen spent on a bar that is no longer up there -- and this is
       the page's first word. --top, like every other width. */
    padding:var(--top) 20px 40px;
    max-width:640px;margin:0 auto;
    /* one column: there is nothing beside the writing to stay put for */
    min-height:0;
  }
  .col.pinned{position:static;}
  .recs{grid-template-columns:1fr;}
  .shots{column-count:3;padding:0 14px 18px;}

  /* The hero, lifted out of the mosaic and set under the intro. Raj,
     2026-08-12: "on phone, about is too text heavy from the start - visitor
     only sees images after all the text is over." Every word came before
     every picture; now a photograph lands inside the first scroll and the
     other thirty-five still close the page. about.js does the move, because
     the desktop mosaic needs the same element back. */
  .tile.hero.inline{
    width:100%;margin:0 0 var(--s);
    border-radius:6px;
  }
  .tile.hero.inline img{width:100%;height:auto;}
}
@media(max-width:430px){
  #about-inner{--name:36px;--sec:29px;--text:18.5px;--small:17px;--hand:23px;--sec-rule-gap:11px;}
  /* one column of skills, not two: at 350px wide "strategy & positioning"
     wraps, and a wrapped two-column list reads as broken alignment */
  .skills{columns:1;}
  .shots{column-count:2;column-gap:7px;padding:0 12px 18px;}
  .tile{margin-bottom:7px;}
  .row-year{margin-left:auto;}
}

/* ======================================================
   TOKENS
   What is left of the old two-system split. CIAD used to be the dark half;
   it is warm paper now, so the dark token set, the theme toggle and every
   var they fed (--win, --blob, --gal-op, --trust-*, --cur-*) went with it.
====================================================== */
:root{ --tr:0.22s ease; }
[data-theme="light"]{ --bg:var(--bg-warm); --text:var(--ink); }

/* ======================================================
   RESET
====================================================== */
*{margin:0;padding:0;box-sizing:border-box;}
html{
  width:100%;height:100%;
  background:var(--bg);
  transition:background var(--tr);
}
body{
  width:100%;height:100%;
  /* iOS Safari needs this to paint the full viewport bg */
  min-height:100dvh;
  background:var(--bg);
  font-family:'DM Mono',monospace;
  color:var(--text);
  overflow:hidden;
  /* No blanket user-select:none. It sat here since the SPA and made every
     word on the site unselectable -- the email address, a price, a client
     name, a line of a case study. Raj, 2026-08-26: "why is none of the text
     sitewide clickable or like selectable?" Nothing here needs it: both
     sliders are native <input type=range>, which never select text, and the
     two surfaces that genuinely drag declare it themselves (below). A
     visitor being able to copy an address is worth more than a stray
     highlight. */
  -webkit-tap-highlight-color:transparent;
  transition:background var(--tr),color var(--tr);
}

/* The three surfaces where a drag or a click-anywhere gesture owns the
   pointer, so a selection would fight it:
     #screen-playground  pan/zoom canvas (declares its own, further down)
     #screen-ciad        the deck swipes, and a click on the left or right
                         third of any screen -- paragraph included -- turns
                         the page
     #screen-video       the intro, which has no text to select
     #pg-nav             chrome, not content
   Everything else on the site is now selectable. */
#screen-ciad,#screen-video,#pg-nav{user-select:none;-webkit-user-select:none;}

/* Selection is visible sitewide now, so it gets the brand's own colour
   rather than the UA's blue. Pink at 0.28 keeps the ink above 4.5:1. */
::selection{background:rgba(255,123,172,0.28);color:var(--ink);}
::-moz-selection{background:rgba(255,123,172,0.28);color:var(--ink);}
/* Extra fixed layer so iOS Safari never shows white behind content */
body::before{
  content:'';
  position:fixed;inset:0;
  background:var(--bg);
  z-index:-1;
  transition:background var(--tr);
}
body::after{content:'';position:fixed;inset:0;background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");pointer-events:none;z-index:3000;opacity:0.2;}

/* ======================================================
   MOBILE UI/UX GLOBAL PASS
   95% of visitors are on phone. Mobile is the primary experience.
====================================================== */
@media(max-width:639px){

  /* ---- GLOBAL: prevent horizontal scroll everywhere ---- */
  html,body{ overflow-x:hidden !important; max-width:100vw; }

  /* ---- GLOBAL: minimum tap target 44px (nav links excluded — handled by compact nav) ---- */
  a,button,.hub-opt-btn{
    min-height:44px;
    display:inline-flex;
    align-items:center;
  }

  /* ---- INDEX: video fills correctly, CTA tappable ---- */
  #screen-video{ overflow:hidden; }
  #vid-text-overlay{ padding:0 24px; }
  #vid-headline{ font-size:clamp(26px,calc(6vw + 4px),36px); }

  /* ---- PLAYGROUND: artsy vertical feed (mobile) ---- */
  #pg-canvas{ overflow-y:auto; overflow-x:hidden; touch-action:auto; cursor:auto; -webkit-overflow-scrolling:touch; }
  #pg-world{ display:none; }
  .pg-recenter{ display:none; }

  #pg-feed{ position:relative; z-index:2; padding:90px 0 22vh; }

  /* fixed, always-centred hero — blends over the scrolling cards */
  .pgf-hero-fixed{
    position:fixed; top:50%; left:50%; transform:translate(-50%,-50%);
    z-index:40; width:min(88vw,440px); text-align:center;
    pointer-events:none; mix-blend-mode:exclusion; color:#fff;
    will-change:transform;
  }
  .pgf-hero-fixed .pgf-title{
    font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:300;
    font-size:clamp(28px,calc(7vw + 4px),40px); line-height:1.04; letter-spacing:0.01em;
    margin:0; color:#fff;
  }
  /* 18, and .pgf-outro-cta matches it. The same action was set at 16 here
     and 22 at the foot of the feed -- one action, two sizes, and the sixth
     size on a five-size budget. 18 is the meeting point: a better tap target
     than 16 for the only link on a phone's first screen, and small enough
     that the fixed hero block does not grow down into the row beneath it.
     22 here was tried and pushed the CTA onto the cards below. */
  .pgf-hero-fixed .pgf-cta{
    display:inline-block; margin-top:13px; pointer-events:auto;
    font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:400;
    font-size:22px; color:#fff; text-decoration:none;
    border-bottom:1px solid rgba(255,255,255,0.55); padding-bottom:2px;
  }
  .pgf-hero-fixed .pgf-cta:active{ transform:scale(0.97); }
  /* closing moment at the end of the feed */
  .pgf-outro{ position:relative; z-index:2; text-align:center; padding:48px 24px 32px; }
  .pgf-outro-line{
    font-family:'DM Mono',monospace; font-size:15px; letter-spacing:0.18em;
    text-transform:uppercase; color:var(--ink-muted); margin-bottom:16px;
  }
  .pgf-outro-cta{
    display:inline-block; pointer-events:auto;
    font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:400;
    font-size:22px; color:var(--accent-b,#0d8aaf); text-decoration:none;
    border-bottom:1px solid rgba(13,138,175,0.4); padding-bottom:2px;
    transition:transform .14s var(--e-out);
  }
  .pgf-outro-cta:active{ transform:scale(0.97); }

  /* editorial rows — varied width, everything floats, comfortable gaps */
  .pgf-row{ display:flex; align-items:flex-start; margin-bottom:22px; }
  .pgf-solo-l{ justify-content:flex-start; padding-left:20px; }
  .pgf-solo-r{ justify-content:flex-end; padding-right:20px; }
  .pgf-solo-c, .pgf-wide{ justify-content:center; }
  /* 22, not 16. A paired row can hold a text card, whose 30px of internal
     padding was larger than the gap separating it from the photo beside it
     -- so the two read as one object rather than two. Raj's standing note on
     spacing is that the answer is always more room between things, never
     less, so the gap went up rather than the card getting tighter. */
  .pgf-pair{ justify-content:center; gap:22px; padding:0 20px; }
  .pgf-solo-l .pgf-item, .pgf-solo-r .pgf-item{ width:64%; }
  .pgf-solo-c .pgf-item{ width:70%; }
  .pgf-wide .pgf-item{ width:86%; }
  .pgf-pair .pgf-item{ flex:1 1 0; min-width:0; }

  .pgf-item{
    border-radius:7px; overflow:hidden;
    box-shadow:4px 8px 26px rgba(30,25,20,0.14), 0 1px 5px rgba(30,25,20,0.06);
    transform:rotate(var(--rot,0deg));
    opacity:0; animation:pgfIn 0.5s var(--e-out) both;
  }
  /* every card sizes to its media's natural ratio */
  .pgf-item img, .pgf-item video{ width:100%; height:auto; display:block; }
  .pgf-video{ background:#15120f; }
  /* 20 vertical, down from 30: the remaining half of the same proximity
     problem. The card still reads as a note -- the text is centred in it and
     the shape is unchanged -- it just no longer holds more air inside itself
     than there is between it and its neighbour. */
  /* Small, as on desktop (Raj, 2026-10-06: "little details not scream for
     attention"): the note hugs its words and never takes a photo's width. */
  .pgf-text{
    display:flex; align-items:center; justify-content:center;
    padding:10px 14px; text-align:center;
    box-shadow:1px 3px 10px rgba(30,25,20,0.10), 0 1px 2px rgba(30,25,20,0.05);
  }
  /* wide enough for his longest line ("i guess you can't defeat someone",
     ~230px) so his breaks hold at 375 and 390 */
  .pgf-row .pgf-text{ width:auto; max-width:68%; flex:0 0 auto; }
  .pgf-text span{
    font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:500;
    white-space:pre-line; font-size:18px; line-height:1.22; color:color-mix(in srgb, var(--ink-body) 60%, var(--ink));
  }
  @keyframes pgfIn{
    from{ opacity:0; transform:rotate(var(--rot,0deg)) translateY(14px); }
    to{ opacity:1; transform:rotate(var(--rot,0deg)) translateY(0); }
  }

  /* About's mobile rules live with the bento grid in SCREEN 4, not here. */

  /* ---- HUB: three facts side by side get too cramped under ~400px.
     Sizes come from the tokens on #hub-content -- nothing new here. ---- */
  .hub-option{ padding:22px; }
  /* One grid with the labels' own column. A fixed 104px broke "you show
     up", "your month" and "working since" onto two lines each, so a
     three-row table read as six. The values get their full width first and
     the labels take what is left -- if anything has to wrap it is the quiet
     word, never the fact ("2020 · 45+ / brands" was the other way round). */
  .hub-facts{ display:grid; grid-template-columns:minmax(0,max-content) max-content; gap:var(--hb-tight) var(--hb-unit); align-items:baseline; }
  .hub-fact{ display:contents; }
  .hub-fact + .hub-fact{ padding-left:0; border-left:0; }
  .hub-fact dt{ margin-bottom:0; }

  /* CIAD's mobile rules live with the deck in SCREEN 3, not here. */

  /* ---- Nav bar: safe area ----
     The capsule is on the bottom edge now, so the inset it has to respect is
     the home indicator's, not the notch's. It goes on the row's padding
     rather than on the nav's offset so that shared.js measures it into
     --nav-clear along with everything else. */
  .nav-row{
    padding-bottom:calc(5px + env(safe-area-inset-bottom,0px));
  }


}

/* ======================================================
   SCREEN: PORTFOLIO — full-screen vertical reel
====================================================== */
#screen-port{
  z-index:35;
  background:var(--bg-warm);
  overflow-y:scroll;
  overflow-x:hidden;
  -webkit-overflow-scrolling:touch;
  scroll-snap-type:y mandatory;
  scrollbar-width:none;
}
#screen-port::-webkit-scrollbar{ width:0; height:0; display:none; }

/* Scroll progress line (under nav) */
#reel-progress{
  position:fixed; top:0; left:0; right:0;
  height:2px; z-index:60;
  background:rgba(40,30,20,0.06);
  pointer-events:none;
}
#reel-progress-fill{
  height:100%; width:100%;
  transform:scaleX(0); transform-origin:left center;
  background:var(--accent-r); opacity:0.6;
}

/* View toggle — shuffled / by client. Sits under the nav, top-right. */
#reel-view{
  position:fixed; top:22px; right:22px; z-index:70;
  display:grid; grid-template-columns:1fr 1fr;
  padding:3px;
  background:rgba(245,242,238,0.76);
  backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  border:1px solid rgba(30,25,20,0.09);
  border-radius:100px;
  box-shadow:0 2px 10px -4px rgba(45,33,22,0.14);
}
/* The selected pill is one element that slides, not two backgrounds that
   swap. Equal grid columns mean it travels on translateX alone — scaling a
   100px-radius pill to fit a different width visibly deforms the ends. */
.reel-view-ink{
  position:absolute; left:3px; top:3px;
  width:calc((100% - 6px) / 2); height:calc(100% - 6px);
  background:var(--ink); border-radius:100px; z-index:0;
  transition:transform 0.32s var(--e-out);
}
#reel-view[data-mode="client"] .reel-view-ink{ transform:translateX(100%); }
#reel-view.no-anim .reel-view-ink{ transition:none; }
.reel-view-btn{
  position:relative; z-index:1;
  appearance:none; border:0; background:transparent; cursor:pointer;
  font-family:'DM Mono',monospace; font-size:14px;
  letter-spacing:0.13em; text-transform:lowercase; white-space:nowrap;
  color:var(--ink-muted);
  padding:7px 15px; border-radius:100px;
  transition:color 0.24s ease, transform 0.16s ease-out;
}
.reel-view-btn.on{ color:var(--bg-warm); }
.reel-view-btn:active{ transform:scale(0.96); }
.reel-view-btn:focus-visible{ outline:2px solid var(--accent-b); outline-offset:2px; }
@media(hover:hover) and (pointer:fine){
  .reel-view-btn:not(.on):hover{ color:var(--ink); background:rgba(30,25,20,0.05); }
}
/* cross-fade while the reel is rebuilt underneath */
#screen-port #reel{ transition:opacity 0.18s ease-out; }
#screen-port.reel-swapping #reel{ opacity:0; }

/* Tap-for-sound hint */
/* It comes after the first scroll and leaves by itself (portfolio.js showHintOnce). At the top, under the
   shuffled/by-client row: at the bottom it sat on the piece's "case study" pill and its caption. */
#reel-hint{
  position:fixed; left:50%; top:70px;
  transform:translateX(-50%); z-index:60;
  font-family:'DM Mono',monospace;
  font-size:14px; letter-spacing:0.16em; text-transform:uppercase;
  color:var(--ink-muted);
  background:rgba(245,242,238,0.72);
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  border:1px solid rgba(30,25,20,0.08);
  border-radius:100px; padding:8px 16px;
  white-space:nowrap;   /* it wrapped to two lines on a phone, over the video */
  pointer-events:none;
  transition:opacity 0.4s ease, transform 0.4s ease;
}
#reel-hint.gone{ opacity:0; transform:translate(-50%,-8px); }

/* ---- Slides ---- */
.reel-slide{
  position:relative;
  height:100vh; height:100dvh;
  min-height:100vh; min-height:100dvh;
  scroll-snap-align:start;
  scroll-snap-stop:always;
  display:flex; align-items:center; justify-content:center;
  overflow:hidden;
}
.reel-slide::after{
  content:''; position:absolute; inset:0; z-index:3; pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.018'/%3E%3C/svg%3E");
}
/* media + label share a frame sized to the video so the title sits on its edge */
/* --vr is copied here from the media box (portfolio.js, reelSetShape) so the
   label can work out the frame's height -- see .reel-label */
.reel-frame{ position:relative; display:flex; --vr:0.5625; }

/* Intro */
.reel-intro{ flex-direction:column; text-align:center; }
.reel-intro-inner{ position:relative; z-index:2; padding:0 24px; }
.reel-intro-eyebrow{
  font-family:'DM Mono',monospace; font-size:15px;
  letter-spacing:0.34em; text-transform:uppercase;
  color:var(--ink-muted); margin-bottom:20px;
}
.reel-intro-title{
  font-family:'Cormorant Garamond',serif; font-weight:300;
  font-size:clamp(60px,calc(13vw + 4px),154px); line-height:0.9;
  color:var(--ink); letter-spacing:-0.015em; margin:0;
}
.reel-intro-title em{ font-style:italic; color:var(--accent-r); }
/* the title turns into the nudge (portfolio.js setupNudge): out fast, back from just below, a 2px blur to hide the swap */
.reel-intro-title{
  transition:opacity 0.42s var(--e-out), transform 0.42s var(--e-out), filter 0.42s var(--e-out);
}
.reel-intro-title.swap-out{
  opacity:0; transform:translateY(-8px); filter:blur(2px);
  transition-duration:0.24s;
}
.reel-intro-title.swap-in{
  opacity:0; transform:translateY(8px); filter:blur(2px);
  transition:none;
}
@media (prefers-reduced-motion: reduce){
  .reel-intro-title, .reel-intro-title.swap-out{ transition:none; transform:none; filter:none; }
}
.reel-intro-sub{
  font-family:'DM Mono',monospace; font-size:15px;
  letter-spacing:0.14em; color:var(--ink-muted); margin-top:28px;
}
/* The nudge (Raj, 2026-11-14, option B): the words are the button and the arrow is part of them. It replaces the
   scroll pill that sat at the foot of the slide (.reel-cue, removed). The whole word group is the tap target. */
.reel-nudge{
  /* the phone's a,button{inline-flex;min-height:44px} (MOBILE GLOBAL PASS) would lay "scroll" and "through"
     side by side; the words are far past 44px tall already, so it opts out */
  display:inline-block; min-height:0;
  appearance:none; background:none; border:0; padding:0; margin:0; cursor:pointer;
  font:inherit; color:inherit; letter-spacing:inherit; line-height:inherit; text-align:inherit;
  transition:transform 0.16s ease-out;
}
.reel-nudge:active{ transform:scale(0.98); }
.reel-nudge:focus-visible{ outline:2px solid #fff; outline-offset:10px; border-radius:6px; }
.reel-arrow{
  display:inline-block; width:0.5em; height:0.5em; margin-left:0.16em; vertical-align:-0.02em;
  animation:reelArrow 1.7s ease-in-out infinite;   /* a loop: a literal symmetric curve, per CLAUDE.md § 3 */
}
@keyframes reelArrow{
  0%,100%{ transform:translateY(0); }
  50%{ transform:translateY(0.12em); }
}
@media (prefers-reduced-motion: reduce){ .reel-arrow{ animation:none; } }

/* Glass: the intro slide's background, painted by glass.js (portfolio.html
   only). Paper until the photo is decoded, then it comes up. The words sit on
   it in paper-white, with no glow behind them (TASTE 2026-06-21). */
.glass-canvas{
  position:absolute; inset:0; width:100%; height:100%; display:block; z-index:0;
  opacity:0; transition:opacity 0.7s var(--e-out);
}
.glass-canvas.ready{ opacity:1; }
.reel-intro.glass .reel-intro-eyebrow,
.reel-intro.glass .reel-intro-sub{ color:#fff; }
.reel-intro.glass .reel-intro-title{ color:#fff; }
/* the pointer: 0.4 of a grid square, pale warm green-white, a 1px halo (reference, glass.md § 6) */
.glass-dot{
  position:absolute; left:0; top:0; z-index:1; pointer-events:none;
  width:var(--d,7px); height:var(--d,7px);
  margin:calc(var(--d,7px) / -2) 0 0 calc(var(--d,7px) / -2); border-radius:50%;
  background:#e6ffd2; box-shadow:0 0 0 1px rgba(230,255,210,0.28), 0 0 6px rgba(230,255,210,0.45);
  opacity:0; transition:opacity 0.3s var(--e-out);
}
.glass-dot.on{ opacity:0.95; }

/* Media frame.

   The box is sized here, not by the media inside it. It used to shrink-wrap
   a <video> with max-height/max-width and auto dimensions -- and a <video>
   with no source decoded yet has an intrinsic size of 300x150, so every reel
   painted as a small landscape card and then snapped to a 425x756 portrait
   the instant its poster or its manifest arrived. On a fast connection that
   is one frame; on a slow one it is the several seconds Raj is describing,
   and it happens once per slide all the way down the page.

   Same fix as .cs-media, and deliberately the same property: --vr is a
   unitless width/height that portfolio.js overwrites from the video's own
   videoWidth/videoHeight (or an image's naturalWidth/Height) once it knows.
   The default is 9/16 because every reel in the bank is a reel, so for the
   videos the reserved box is already the final box and nothing moves at all.
   Height is the binding dimension -- 84vh unless that would push the width
   past 92vw, which is the wide case. */
.reel-media{
  --vr:0.5625;
  position:relative; z-index:2;
  aspect-ratio:var(--vr);
  height:min(84vh, calc(92vw / var(--vr)));
  height:min(84dvh, calc(92vw / var(--vr)));
  max-width:92vw;
  display:flex; align-items:center; justify-content:center;
  border-radius:8px; overflow:hidden; background:#15120f;
  box-shadow:0 40px 90px -28px rgba(45,33,22,0.42), 0 10px 30px -12px rgba(45,33,22,0.22);
  opacity:0; transform:scale(0.985) translateY(8px);
  transition:opacity 0.38s var(--e-out), transform 0.38s var(--e-out);
}
.reel-slide.seen .reel-media{ opacity:1; transform:none; }
.reel-media video,
.reel-media img{
  display:block;
  width:100%; height:100%;
  object-fit:cover;
}
.reel-media.is-video video{ cursor:pointer; }

/* Audio chip */
.reel-audio{
  position:absolute; top:14px; right:14px; z-index:5;
  width:38px; height:38px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:rgba(20,16,12,0.46); color:#fff; cursor:pointer;
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  border:1px solid rgba(255,255,255,0.18);
  transition:transform 0.16s ease-out, background 0.2s ease;
}
.reel-audio svg{ width:18px; height:18px; }
.reel-audio:active{ transform:scale(0.92); }
.reel-audio.on{ background:var(--accent-r); border-color:transparent; }
@media(hover:hover) and (pointer:fine){
  .reel-audio:hover{ background:rgba(20,16,12,0.66); }
  .reel-audio.on:hover{ background:var(--accent-r); }
}

/* Label — stacked on the video's bottom-left */
/* It reserved the nav's whole height under itself, but the frame is centred
   in the slide, so the nav only covers the part of it that reaches into the
   bottom --nav-clear of the screen: (screen - frame) / 2 short of the full
   clearance. Reserving all 86px lifted the words to mid-frame over the
   subtitles, and on a ~210px landscape frame pushed them out of its top onto
   the paper in white. Raj, 2026-10-06: "the text starts coming all over".
   The scrim is the label's own background now, not a fixed 48% of the
   frame, so it is exactly as tall as the words it sits behind. */
.reel-label{
  position:absolute; left:0; right:0; bottom:0; z-index:4;
  --frame-h:min(84vh, calc(92vw / var(--vr)));
  --frame-h:min(84dvh, calc(92vw / var(--vr)));
  padding:28px 24px calc(20px + max(0px, var(--nav-clear,86px) - (100vh - var(--frame-h)) / 2));
  padding:28px 24px calc(20px + max(0px, var(--nav-clear,86px) - (100dvh - var(--frame-h)) / 2));
  pointer-events:none; text-align:left;
  background:linear-gradient(to top, rgba(18,13,8,0.68), rgba(18,13,8,0.5) 60%, rgba(18,13,8,0));
}
/* Raj, 2026-10-04: the words step out of the way once the viewer is 3s into
   the video. portfolio.js counts real playback (not time on the slide) and
   sets .label-out; landing on the slide again brings them back. The scrim
   only exists for the words, so it goes with them. Slow and quiet, because
   nobody asked for it -- it should be noticed only as the picture opening up. */
.reel-label,.reel-groupmark{transition:opacity 0.7s ease;}
.reel-slide.label-out .reel-label,
.reel-slide.label-out .reel-groupmark{opacity:0;}
.reel-slide.label-out .reel-pglink{pointer-events:none;}
/* the words also come back at the end of the first play (portfolio.js), and
   on a desktop whenever the cursor is on the frame */
@media(hover:hover) and (pointer:fine){
  .reel-slide.label-out .reel-frame:hover .reel-label{opacity:1;}
  .reel-slide.label-out .reel-frame:hover .reel-pglink,
  .reel-slide.label-out .reel-frame:hover .reel-cslink{pointer-events:auto;}
}
.reel-cat{
  font-family:'DM Mono',monospace; font-size:13px;
  letter-spacing:0.18em; text-transform:uppercase;
  color:rgba(255,255,255,0.72); margin-bottom:7px;
}
.reel-name{
  font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:400;
  font-size:clamp(26px,calc(3vw + 4px),44px); line-height:1.03; color:#fff;
}
.reel-sub{
  font-family:'DM Mono',monospace; font-size:14px;
  letter-spacing:0.18em; text-transform:uppercase;
  color:rgba(255,255,255,0.78); margin-top:8px;
}
/* This page's spacing is 8 / 12 / 20 / 28 and nothing between. It used to run
   an 11 here against the credit block's 12 -- two values one pixel apart,
   both doing the same job of setting a small gap under a line of type. On a
   phone the same pair was 11 and 10. Neither difference is visible; both
   cost a decision every time something new is added near them. */
.reel-pglink{
  display:inline-block; margin-top:12px; pointer-events:auto;
  font-family:'DM Mono',monospace; font-size:14px;
  letter-spacing:0.14em; text-transform:uppercase;
  color:#fff;
  /* an underline, not a border-bottom: the phone's 44px tap rule makes this
     link 44px tall, and a border then hung ~30px under the words */
  text-decoration:underline; text-decoration-thickness:1px; text-underline-offset:5px;
  text-decoration-color:rgba(255,255,255,0.5); transition:text-decoration-color 0.2s ease;
}
@media(hover:hover) and (pointer:fine){.reel-pglink:hover{ text-decoration-color:#fff; }}
.reel-pglink:active{ transform:scale(0.97); }

/* Credit block — what raj did, what it did. Sits below the client name,
   separated by a hairline so it reads as a footnote, not a headline. */
.reel-credit{
  margin-top:12px; padding-top:12px;
  max-width:46ch;
  border-top:1px solid rgba(255,255,255,0.18);
}
.reel-credit-row{
  margin:0; font-family:'DM Mono',monospace;
  font-size:15px; line-height:1.66; letter-spacing:0.005em;
  color:rgba(255,255,255,0.82);
  text-wrap:pretty;
}
.reel-credit-row + .reel-credit-row{ margin-top:5px; }
.reel-credit-key{
  display:inline-block; margin-right:8px;
  font-size:13px; letter-spacing:0.16em; text-transform:uppercase;
  color:rgba(255,255,255,0.5);
}
.reel-todo{ color:var(--accent-r); opacity:0.85; font-style:italic; }
/* The way into the study (2026-10-06). It was the outcome line, then that
   line under a blue highlighter, and neither said "press me" (Raj: "it's not
   clear enough that it's a button"). A solid pill under the claim does:
   --tint-b with ink on it (~9:1), blue as everywhere a link leads to the
   work; pink on this page is sound and the client marks. Type is the
   playground link's 14px tracked caps, so no new size. On a phone the
   44px tap rule sets its height; it is centred inside that. */
.reel-cslink{
  display:inline-flex; align-items:center; gap:8px;
  margin-top:12px; padding:8px 20px; border-radius:999px;
  font-family:'DM Mono',monospace; font-size:14px;
  letter-spacing:0.14em; text-transform:uppercase;
  color:var(--ink); background:var(--tint-b);
  text-decoration:none; pointer-events:auto;
  -webkit-tap-highlight-color:transparent;
  transition:transform 0.16s var(--e-out);
}
.reel-cslink-arrow{
  display:inline-block;
  transition:transform 0.18s var(--e-out);
}
@media(hover:hover) and (pointer:fine){
  .reel-cslink:hover .reel-cslink-arrow{ transform:translateX(4px); }
}
.reel-cslink:active{ transform:scale(0.97); }
.reel-slide.label-out .reel-cslink{ pointer-events:none; }

/* Client view — marks where each client's run begins */
.reel-groupmark{
  position:absolute; top:0; left:0; z-index:4;
  display:flex; align-items:baseline; gap:10px;
  padding:18px 24px;
  font-family:'DM Mono',monospace;
  pointer-events:none;
  text-shadow:0 1px 12px rgba(18,13,8,0.55);
}
.reel-groupmark-n{
  font-size:13px; letter-spacing:0.2em;
  color:var(--accent-r);
}
.reel-groupmark-name{
  font-size:14px; letter-spacing:0.16em; text-transform:uppercase;
  color:rgba(255,255,255,0.92);
}
.reel-groupmark-count{
  font-size:13px; letter-spacing:0.12em;
  color:rgba(255,255,255,0.55);
}
/* a hairline down the frame's left edge for the whole of a client's run */
.reel-slide.is-group-start .reel-frame::before{
  content:''; position:absolute; top:0; left:0; width:2px; height:74px; z-index:4;
  background:linear-gradient(to bottom, var(--accent-r), transparent);
  pointer-events:none;
}

/* Mobile: full-bleed media */
@media(max-width:639px){
  .reel-media{ border-radius:0; box-shadow:none; background:transparent; }
  .reel-media video,
  .reel-media img{ max-width:100vw; max-height:100dvh; }
  .reel-label{
    padding:28px 20px calc(20px + max(0px, var(--nav-clear,86px) - (100vh - var(--frame-h)) / 2));
    padding:28px 20px calc(20px + max(0px, var(--nav-clear,86px) - (100dvh - var(--frame-h)) / 2));
  }
  .reel-credit{ max-width:100%; margin-top:12px; padding-top:12px; }
  /* same size; 1.66 was paragraph leading on two-line footnotes */
  .reel-credit-row{ font-size:15px; line-height:1.45; }
  /* A landscape frame is ~210px tall on a phone: the words go under it, on
     the paper, in ink (Raj's pick, 2026-10-06), and nothing covers the shot. */
  /* the frame's width is the video's, or the label's longest line would
     widen it and push the video off-centre */
  .reel-frame.is-wide{ flex-direction:column; width:92vw; }
  .reel-frame.is-wide .reel-label{ position:static; background:none; padding:20px 0 0; }
  .reel-frame.is-wide .reel-cat,
  .reel-frame.is-wide .reel-sub,
  .reel-frame.is-wide .reel-credit-key{ color:var(--ink-muted); }
  .reel-frame.is-wide .reel-name,
  .reel-frame.is-wide .reel-credit-row,
  .reel-frame.is-wide .reel-pglink{ color:var(--ink); }
  .reel-frame.is-wide .reel-credit{ border-top-color:rgba(30,25,20,0.14); }
  .reel-frame.is-wide .reel-pglink{ text-decoration-color:rgba(30,25,20,0.4); }
  #reel-view{ right:12px; top:14px; }
  .reel-view-btn{ font-size:13px; padding:6px 10px; letter-spacing:0.08em; }
  .reel-groupmark{ padding:14px 18px; gap:8px; }
}

/* ======================================================
   ACCESSIBILITY: REDUCED MOTION
====================================================== */
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:0.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:0.01ms !important;
  }
  /* The view-transition pseudo tree lives outside normal selector matching —
     `*` never reaches it, so page morphs ran at full speed for people who
     asked for less motion. Cut them here explicitly. */
  ::view-transition-group(*),
  ::view-transition-image-pair(*),
  ::view-transition-old(*),
  ::view-transition-new(*){
    animation:none !important;
  }
  .obj,.hub-content,.pg-card,.pg-text-card{
    transition:opacity 0.2s ease !important;
    transform:none !important;
    animation:none !important;
  }
  @keyframes springIn{0%,100%{opacity:1;transform:none;}}
  @keyframes pgFadeIn{0%,100%{opacity:1;}}
  @keyframes hubLogoIn{0%,100%{opacity:1;transform:none;}}
}


/* ======================================================
   SCREEN: CASE STUDIES
====================================================== */
#cs-screen{ z-index:35; background:var(--bg-warm); overflow-y:auto; overflow-x:hidden; -webkit-overflow-scrolling:touch; }
#cs-screen::after{ content:''; position:fixed; inset:0; z-index:0; pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.018'/%3E%3C/svg%3E"); }

#cs-listwrap{ position:relative; z-index:2; max-width:920px; margin:0 auto; padding:48px 24px calc(40px + var(--nav-clear,86px)); }
.cs-head{ text-align:center; margin-bottom:56px; }
.cs-eyebrow{ font-family:'DM Mono',monospace; font-size:15px; letter-spacing:0.3em; text-transform:uppercase; color:var(--ink-muted); margin-bottom:16px; }
.cs-title{ font-family:'Cormorant Garamond',serif; font-weight:300; font-size:clamp(44px,calc(8vw + 4px),76px); line-height:0.98; color:var(--ink); margin:0; letter-spacing:-0.01em; }
.cs-title em{ font-style:italic; color:var(--accent-r); }
/* --ink-body, not muted: it is a sentence, and --ink-muted is the tracked
   labels' colour only (see :root). balance, so the first sentence breaks
   into two even lines and "50k views." is not left alone on one. */
.cs-sub{ font-family:'DM Mono',monospace; font-size:16px; letter-spacing:0.06em; color:var(--ink-body); margin-top:16px; text-wrap:balance; }
/* 28, not 14. The gap between two cards has to beat the 22px padding inside
   them or the six read as one blob -- the audit was calling this fused, and it
   was right. Raj kept the card layout, so the fix is the spacing, not the box. */
#cs-list{ display:flex; flex-direction:column; gap:28px; }
/* A pane of glass over that client's own video (Raj, 2026-11-14, off the glass
   on portfolio's first slide): heavy frost so only the video's colour and
   movement come through, the wire inside the glass, a soft light that moves
   against the pointer, white words. Removed: the white wash, the hairline
   border and the hover's lighter wash -- the glass is the box now.
   Kept dark on purpose: measured worst-case contrast over four moments of
   every client's video, every line passes WCAG at 1440 and 390 (before the
   smoke, 3 of 28 did). Raj: "keep it this dark". The video, glass and rim
   are below, after the row's own parts. */
.cs-card{ display:flex; align-items:center; gap:16px; width:100%; text-align:left; cursor:pointer; font:inherit; color:inherit;
  position:relative; overflow:hidden; isolation:isolate;
  background:#2a2520; border:0; border-radius:14px; padding:28px 30px;
  box-shadow:0 24px 60px -30px rgba(30,22,14,0.55);
  --lx:30%; --ly:20%;
  transition:transform .25s var(--e-out), box-shadow .25s; }
@media(hover:hover) and (pointer:fine){ .cs-card:hover{ transform:translateY(-2px); box-shadow:0 30px 70px -30px rgba(30,22,14,0.65); } }
.cs-card:active{ transform:translateY(0) scale(0.995); }
/* Muted, not blue. This page runs one accent, and an ordinal is not what it
   should be spent on -- the multiple is the only figure here that means
   anything, so pink goes there and nowhere else. 12px, not 13: 12 is already
   the page's small-type size and two sizes a pixel apart communicate nothing. */
.cs-card-no{ font-family:'DM Mono',monospace; font-size:16px; color:var(--ink-muted); letter-spacing:0.1em; flex-shrink:0; }
.cs-card-body{ flex:1; min-width:0; }
/* Blue, and this page now runs two accents on purpose. Raj, 2026-08-12: "the
   client names should be coloured/highlighted. needs contrast in some way."
   They were dark ink on a near-white card, the same value as the teaser and
   the sector under them, so the row's loudest thing was the arrow.
   The split is the site's own and it carries meaning here: blue is *who*,
   pink is *what it did*. Keeping the multiple as the page's only pink is the
   point -- if the names took pink too, six names and six figures in one
   colour would leave nothing for the eye to rank. Don't use the two
   interchangeably. */
.cs-card-client{ font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:400; font-size:clamp(26px,calc(3vw + 4px),34px); line-height:1.05; color:var(--accent-b); }
.cs-card-sector{ font-family:'DM Mono',monospace; font-size:13px; letter-spacing:0.16em; text-transform:uppercase; color:var(--ink-muted); margin-top:8px; }
.cs-card-teaser{ font-family:'DM Mono',monospace; font-size:16px; color:var(--ink-body); margin-top:8px; line-height:1.5; }
/* "work in progress" -- Raj, 2026-10-06: his active recurring clients. It
   rides in the sector line at the sector's own size, so no new type size;
   ink inside a hairline rather than muted, so it reads as a tag on the line
   and not as one more word of it. The first thing on the line when a study
   has no sector yet (gilas), so it never trails a gap.
   Raj, 2026-10-06, later the same day: the filled blue pill became About's
   highlighter in a light wash of the brand blue -- the same swipe as .mark,
   at the tribe cover's lighter alpha. Ink on top; the pill is gone. */
/* In About's hand (Raj, 2026-10-06): Caveat 600, Cormorant as the fallback
   for the reason at the top of this file. The eyebrow's tracking and
   uppercase are undone -- a marker hand set in tracked capitals is type
   pretending to be handwriting. */
.cs-wip{ display:inline-block; color:var(--ink); white-space:nowrap;
  background-image:linear-gradient(103deg,
    rgba(13,138,175,0) 0.4%,
    rgba(13,138,175,0.26) 2.4%,
    rgba(13,138,175,0.22) 95%,
    rgba(13,138,175,0) 99%);
  background-size:100% 62%; background-position:0 82%; background-repeat:no-repeat;
  padding:0 6px;
  font-family:'Caveat','Cormorant Garamond',serif; font-weight:600; letter-spacing:0; text-transform:none;
  /* Caveat's x-height is far under DM Mono's; Raj asked for it bigger.
     24 is a size the page already sets (the row arrow). line-height 1 so the
     taller face doesn't push the eyebrow line down. */
  font-size:24px; line-height:1; vertical-align:-0.12em; }
.cs-wip.is-after{ margin-left:12px; }
/* The row's footer: what the engagement actually was, so the list answers
   "how much work, over how long" without a click. Raj, 2026-08-23: the page
   "should properly summarise the context inside for each client - with key
   metrics or whatever relevant a client would seek before hiring me."
   9px uppercase is the sector's exact size, so nothing enters the type
   budget, and the case change keeps it from fusing with the 12px teaser
   directly above. The multiple is deliberately not in here -- it has its own
   column and its own drawn bar two inches to the right. Studies with nothing
   logged past their multiple render no line at all. */
.cs-card-facts{ font-family:'DM Mono',monospace; font-size:13px; letter-spacing:0.16em; text-transform:uppercase; color:var(--ink-muted); margin-top:16px; line-height:1.6; }
/* The one figure on the row that carries meaning, so it gets the page's one
   accent. Fixed width and left-aligned, not right-aligned: six multiples in a
   column are being compared, and comparison needs them to *start* together --
   right-aligning "3&times;" against "up to 5&times;" puts the digits in
   different places. The hedge sits on its own line above the number for the
   same reason it does on CIAD, and it is never dropped. */
/* 210, not 112. The number now labels a drawing of itself and the drawing
   needs room to be legible: at 112px the whole 20x scale gave 5.6px per
   multiple, so 2.5x and 3x were three pixels apart and the picture said less
   than the digits did. The desktop row had ~300px of bare paper between the
   teaser and this column, so the width came out of nothing. */
.cs-card-mult{
  flex:0 0 auto; width:210px; text-align:left;
  font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:400;
  font-size:clamp(26px,calc(3vw + 4px),34px); line-height:1; letter-spacing:-0.01em;
  color:var(--accent-r);
}
.cs-card-mult > i{
  display:block; font-style:normal; font-family:'DM Mono',monospace;
  font-size:13px; letter-spacing:0.16em; text-transform:uppercase;
  color:var(--ink-muted); margin-bottom:3px;
}

/* ── the multiple, drawn ─────────────────────────────
   The number says 3x. The line shows it, against the client's own average
   post -- which is the thing every figure on this page is a multiple of and
   the thing a visitor has no feel for until they see it. Six of them down
   the page is the shape of the work before a word is read.

   One scale for all six rows (0 to 20x, the largest claim here), so the
   lines compare to each other as well as to their own baselines. A per-row
   scale would draw six identical bars and say nothing.

   Solid is what the work reliably did; the lighter tail is how far it went.
   That is how a hedge draws: "3-11x" is solid to 3 and light to 11, and
   "up to 5x" claims no floor so it is solid only to the client's own average
   with the whole reach above it in the tail. The hedges never flatten into a
   single number here any more than they do in the type.

   No new colour: the tail is the same pink at a third strength, which is the
   accent doing two jobs of one meaning rather than two meanings. */
.cs-bar{
  display:block; position:relative;
  height:9px; margin-top:8px;
}
.cs-bar-track{
  position:absolute; left:0; right:0; top:4px; height:1px;
  background:rgba(30,25,20,0.13);
}
/* their own average post: a tick, not a line, because it is a place on the
   scale rather than a quantity */
.cs-bar-base{
  position:absolute; top:0; width:1px; height:9px;
  background:rgba(30,25,20,0.34);
}
.cs-bar-solid,.cs-bar-reach{
  position:absolute; left:0; top:3px; height:3px; border-radius:2px;
  width:var(--to);
  transform:scaleX(0); transform-origin:left center;
  /* linear: a line being traced is constant motion, and an eased one
     arrives before the number beside it has finished being read. Same rule
     as CIAD's ring and stationed line. */
  transition:transform 0.62s linear;
}
.cs-bar-solid{ background:var(--accent-r); }
/* the tail starts after the solid part has got where it is going, so the
   line reads as one gesture continuing rather than two racing */
.cs-bar-reach{ background:rgba(255,123,172,0.34); transition-delay:90ms; }
.cs-bar.is-drawn .cs-bar-solid,
.cs-bar.is-drawn .cs-bar-reach{ transform:scaleX(1); }

/* said once, on the first row, and it becomes the legend for the five under
   it. Six copies of this would be the same fact stated six times. */
.cs-bar-legend{
  position:absolute; top:12px;
  font-family:'DM Mono',monospace; font-size:13px; font-style:normal;
  letter-spacing:0.1em; color:var(--ink-muted); white-space:nowrap;
  transform:translateX(-2px);
}
.cs-bar.has-legend{ margin-bottom:16px; }

@media(prefers-reduced-motion:reduce){
  .cs-bar-solid,.cs-bar-reach{ transform:scaleX(1); transition:none; }
}
.cs-card-arrow{ font-size:24px; color:var(--ink-muted); flex-shrink:0; transition:transform .25s, color .2s; }
/* Blue on hover, not pink. The arrow goes *into* a client, so it belongs to
   the who-half with the name it sits beside -- one gesture lighting one row.
   Pink stays on the booking CTA at the foot, which is the only other action
   here; two colours both meaning "go" would be the interchangeable use the
   budget exists to stop. */
@media(hover:hover) and (pointer:fine){ .cs-card:hover .cs-card-arrow{ transform:translateX(4px); color:var(--accent-b); } }

/* ── the glass card's layers (see .cs-card) ─────────────
   Back to front: the client's video, frosted; the glass (smoke, wire, light);
   a hairline rim; then the row's words on top, never blurred. */
.cs-glass-vid{
  position:absolute; inset:-12%; width:124%; height:124%; object-fit:cover; z-index:-3;
  filter:blur(26px) saturate(1.45) brightness(0.42);
  transform:translateZ(0);
}
/* last layer first in the list is the smoke; the wire is portfolio's 17px
   cell at a quieter 7%; the light is the reflection that mirrors the pointer */
.cs-glass{
  position:absolute; inset:0; z-index:-2; pointer-events:none;
  background-image:
    radial-gradient(ellipse 42% 70% at var(--lx) var(--ly), rgba(255,246,236,0.16), rgba(255,246,236,0.05) 45%, rgba(255,246,236,0) 72%),
    repeating-linear-gradient(0deg, rgba(255,255,255,0.07) 0 1px, transparent 1px 17px),
    repeating-linear-gradient(90deg, rgba(255,255,255,0.07) 0 1px, transparent 1px 17px),
    linear-gradient(90deg, rgba(16,12,9,0.64), rgba(16,12,9,0.58) 60%, rgba(16,12,9,0.64));
}
/* the glass's fine ripple, strongest where the light is */
.cs-glass::after{
  content:''; position:absolute; inset:0;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity:0.10; mix-blend-mode:overlay;
  -webkit-mask-image:radial-gradient(ellipse 50% 80% at var(--lx) var(--ly), #000, rgba(0,0,0,0.25) 70%);
          mask-image:radial-gradient(ellipse 50% 80% at var(--lx) var(--ly), #000, rgba(0,0,0,0.25) 70%);
}
.cs-card::after{
  content:''; position:absolute; inset:0; border-radius:inherit; z-index:-1; pointer-events:none;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,0.16), inset 0 1px 0 rgba(255,255,255,0.22);
}
/* On the glass the words go white (Raj's pick), so these follow the row's own
   rules above and win over their ink colours. The client name was blue for
   "who"; on dark glass it is white, and pink keeps "what it did". The soft
   dark halo is the reference's own (directions/glass.md § 7). */
.cs-card-client,
.cs-card-teaser{ color:#fff; }
.cs-card-no,
.cs-card-sector,
.cs-card-facts,
.cs-card-mult > i,
.cs-bar-legend{ color:rgba(255,255,255,0.86); }
.cs-card-arrow{ color:rgba(255,255,255,0.75); }
.cs-card-body,
.cs-card-no,
.cs-card-mult,
.cs-card-arrow{ text-shadow:0 0 16px rgba(10,8,6,0.55), 0 1px 2px rgba(10,8,6,0.45); }
/* "work in progress": the same blue swipe, stronger, white on it -- the
   paper-weight wash went muddy on dark glass */
.cs-wip{ color:#fff;
  background-image:linear-gradient(103deg,
    rgba(13,138,175,0) 0.4%, rgba(13,138,175,0.62) 2.4%, rgba(13,138,175,0.56) 95%, rgba(13,138,175,0) 99%); }
/* the drawn multiple carries more weight on glass: 3px read as a thread */
.cs-bar{ height:11px; }
.cs-bar-track{ top:5px; background:rgba(255,255,255,0.22); }
.cs-bar-base{ height:11px; background:rgba(255,255,255,0.6); }
.cs-bar-solid,.cs-bar-reach{ top:3px; height:5px; border-radius:3px; }
.cs-bar-reach{ background:rgba(255,123,172,0.45); }

/* phones: no pointer, so the light drifts on its own; reduced motion holds it */
@property --lx{ syntax:'<percentage>'; inherits:true; initial-value:30%; }
@property --ly{ syntax:'<percentage>'; inherits:true; initial-value:20%; }
@keyframes csGlassDrift{ 0%{ --lx:20%; } 50%{ --lx:80%; } 100%{ --lx:20%; } }
@media(hover:none){ .cs-glass{ animation:csGlassDrift 6.9s linear infinite; } }
@media(prefers-reduced-motion:reduce){ .cs-glass{ animation:none; } }
.cs-foot{ text-align:center; margin-top:56px; }
.cs-foot-line{ font-family:'DM Mono',monospace; font-size:15px; letter-spacing:0.16em; text-transform:uppercase; color:var(--ink-muted); margin-bottom:16px; }
/* Pink and on the page's own display size. It was 24px blue -- which was both
   the sixth type size and the last second accent on the page, one rule causing
   two breaches. It shares the card headline's clamp now, so it adds no size,
   and it takes the one accent because the action is the payoff. */
.cs-cta{ display:inline-block; font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:400; font-size:clamp(30px,calc(3.2vw + 4px),36px); color:var(--accent-r); text-decoration:none; border-bottom:1px solid rgba(255,123,172,0.45); padding-bottom:2px; transition:transform .2s; }
.cs-cta:active{ transform:scale(0.98); }

/* ---- a study: one template for all six (2026-10-05, CASE-STUDY-PLAN.md) ----
   Screen one is a full-width proof band -- name, contrast graph, receipts,
   terms, with the video beside them. Under it a 620px reading column: the
   ten-second read (the shift, the lift, the quote), then the story folded.
   It was one 620px column on a 1440 screen with the video sitting left of
   430px of nothing, and 4,376px of it.

   Five type sizes rendered, the budget: the 3x (62-96), the name and the
   lift and the comment figures (30-36), the shift and the quote (22-28),
   body (18), labels (14). The lift came down from 30-44 to the name's ramp
   to pay for the graph -- it is still the only display line in the column. */
#cs-detail{ position:relative; z-index:2; max-width:1180px; margin:0 auto; padding:44px 40px calc(56px + var(--nav-clear,86px)); }
/* Muted 14px with nothing behind it, it read as a caption (Raj, 2026-10-06:
   "backlink ... isnt clear. maybe add highlighter effect behind it"). The
   wash is .cs-lift-hl's blue swipe, so it is a mark the page already makes;
   blue because it goes to the list of who, never pink, which is the booking.
   It overhangs the words by 5px and gives the 5px back, so nothing moves. */
.cs-back{
  font-family:'DM Mono',monospace; font-size:14px; letter-spacing:0.1em; text-transform:uppercase;
  color:var(--ink); text-decoration:none; border:none; cursor:pointer;
  padding:6px 5px; margin:0 -5px;
  background:linear-gradient(103deg,
    rgba(13,138,175,0) 0.4%,
    rgba(13,138,175,0.24) 2.4%,
    rgba(13,138,175,0.18) 95%,
    rgba(13,138,175,0) 99%) 0 72% / 100% 58% no-repeat;
  transform-origin:left center;
  transition:transform .14s var(--e-out);
  -webkit-tap-highlight-color:transparent;
  /* the phone's a,button{min-height:44px;inline-flex} would size the swipe
     to a 44px block and eat the space before the arrow; the box stays the
     words' size and ::before carries the tap area instead */
  display:inline-block; min-height:0; position:relative;
}
.cs-back::before{ content:''; position:absolute; inset:-8px 0; }
.cs-back .cs-arr{ display:inline-block; transition:transform .18s var(--e-out); }
@media(hover:hover) and (pointer:fine){
  .cs-back:hover .cs-arr{ transform:translateX(-4px); }
  .cs-next:hover .cs-arr{ transform:translateX(4px); }
}
.cs-back:active{ transform:scale(0.97); }
.cs-next{ transform-origin:right center; }
/* The foot of a study: back out, or on to the next. Under the reels it closes
   (the reels are the last of this client), and well clear of the booking. */
.cs-pager{
  display:flex; flex-wrap:wrap; justify-content:space-between; align-items:start;
  gap:12px 28px; margin-top:28px;
}
.cs-hero-eyebrow{ font-family:'DM Mono',monospace; font-size:14px; letter-spacing:0.18em; text-transform:uppercase; color:var(--ink-muted); margin:24px 0 10px; }
/* Blue, the who-half of the split -- the colour you clicked it in. Smaller
   than the 3x on purpose: whichever is larger is what the page is about. */
.cs-hero-client{
  font-family:'Cormorant Garamond',serif; font-weight:300;
  font-size:clamp(30px,calc(3.2vw + 4px),36px); line-height:1.15;
  color:var(--accent-b); margin:0;
}
/* a real &middot; in the markup, never a CSS content escape -- the escape
   once put a NUL byte in this file */
.cs-hero-person{ font-style:italic; color:var(--ink-muted); }
.cs-hero-sep{ font-style:normal; color:var(--ink-faint); margin:0 0.42em; }

/* The band. Grid areas so the phone can put the video between the graph
   and the receipts without a second copy of the markup. */
.cs-band{
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  /* The back link stays at the top; the name and the proof under it centre
     on the video as one block (Raj, 2026-10-05). A study with only a graph
     left ~300px of paper under it beside a 640px video. The two 1fr rows
     split whatever the video is taller by, so on a full band (c+c) they
     collapse to nothing. */
  grid-template-rows:auto 1fr auto auto auto auto 1fr;
  grid-template-areas:"back media" ". media" "head media" "vs media" "rc media" "spec media" ". media";
  column-gap:72px;
}
.cs-band-head{ grid-area:head; }
.cs-band .cs-back{ grid-area:back; justify-self:start; align-self:start; }
/* The frame takes the video's shape: --vr is set from videoWidth/Height
   once metadata lands (csFitFrame), and defaults to 9/16 because nearly
   every reel is vertical. Width comes from the height cap, so the box is
   reserved before the network answers. */
.cs-media{
  grid-area:media; align-self:start;
  --vr:0.5625;
  aspect-ratio:var(--vr);
  /* capped at 44vw: upsurge's one landscape cut would otherwise take 1140px
     of a 1440 band and crush the graph into a 70px column */
  /* 44vw alone is measured off the window, not the 1180px band: at 1920 a
     landscape cut took 845px and left the name a 183px column. 600 is the
     ceiling that keeps the graph its room at any width. */
  width:min(calc(min(72vh, 640px) * var(--vr)), 44vw, 600px);
  border-radius:10px; overflow:hidden; background:#15120f;
  box-shadow:0 30px 70px -26px rgba(45,33,22,0.4);
}
.cs-media video{ width:100%; height:100%; object-fit:cover; display:block; }
/* A landscape hero (upsurge): 357px tall beside a ~520px column left a
   dead band under the video. Here it pairs with the name and the graph --
   about the same height -- and the receipts and terms run full width
   under both, so the two halves end together. */
/* The receipts sit under the landscape video, not under the graph: run full
   width they used the left 360px of the band and left the paper under the
   video bare. */
.cs-band.is-wide{ grid-template-areas:"back media" "head media" "vs media" "vs rc" "spec spec"; grid-template-rows:auto auto auto 1fr auto; }
.cs-band.is-wide .cs-receipts{ margin-top:40px; }
.cs-band.is-wide .cs-media{ align-self:start; }
/* A landscape study with no multiple: name beside the video, centred on it,
   and the views and receipts full width under both (gilas, 2026-10-06 --
   thirteen bars in the video's column squeezed the name to three lines). */
/* The name's own row is the flexible one (1fr), so align-self:center really
   centres it on the video. It sat in an auto row with a bare 1fr row under
   it, so the centring did nothing and the name hung at the top over a
   ~280px blank band. */
@media(min-width:761px){
.cs-band.is-wide.no-vs{ grid-template-areas:"back media" "head media" "rc rc" "spec spec"; grid-template-rows:auto 1fr auto auto; }
.cs-band.is-wide.no-vs .cs-band-head{ align-self:center; }
}

/* The contrast graph -- see csVsHTML(). Numbers in a column of their own so
   every row starts at one edge; the 3x is the page's focal point. */
/* Each row: the figure, its label beside it on the figure's foot, and the
   bar under both at the column's full width -- so all bars start at one edge
   whatever the figure's width (see csVsHTML). */
.cs-vs{ grid-area:vs; margin-top:44px; display:flex; flex-direction:column; gap:24px; }
.cs-vs-row{ display:grid; grid-template-columns:auto minmax(0,1fr); column-gap:16px; align-items:end; }
.cs-vs-row .cs-vs-track{ grid-column:1 / -1; margin-top:10px; }
/* a label that wraps on a phone ("the best one -- why i started copper +
   cloves") put the figure beside its LAST line (align-items:end). Start puts
   it beside the first. On a one-line row the figure and the label are both
   18px boxes, so start and end are the same place and nothing else moves.
   (First-baseline alignment was tried and grew every row by 1px.) The hero
   row keeps end: its big figure is the tall item and the label hangs on its
   foot. */
.cs-vs-row.is-base,.cs-vs-row.is-more{ align-items:start; }
/* There from the first frame (Raj, 2026-11-14: "numbers show at once"). They
   used to fade in after their bar finished, so a study opened on two or three
   seconds of empty bars -- the seconds a visitor decides in. The bars still
   draw; the numbers no longer wait for them. */
.cs-vs-x{
  font-family:'DM Mono',monospace; font-size:18px; line-height:1; color:var(--ink-muted);
}
/* a second multiple is still what the work did, so it is pink like the
   hero -- only smaller. In ink it read as a second label. */
.is-more .cs-vs-x{ color:var(--accent-r); }
/* Lining figures are load-bearing: Cormorant's oldstyle "3" drops below the
   baseline and reads about a third of its declared size. */
.is-hero .cs-vs-x{
  font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:400;
  font-size:clamp(62px,calc(8vw + 4px),96px); line-height:0.82; letter-spacing:-0.015em;
  font-variant-numeric:lining-nums; font-feature-settings:'lnum' 1;
  color:var(--accent-r);
}
/* "up to" is part of the fact and never drops; it hangs above the number */
.cs-vs-x i{ display:block; font-style:normal; font-size:14px; font-family:'DM Mono',monospace; letter-spacing:0.14em; text-transform:uppercase; color:var(--ink-muted); margin-bottom:6px; }
.cs-vs-l{ font-family:'DM Mono',monospace; font-size:14px; letter-spacing:0.14em; text-transform:uppercase; color:var(--ink-muted); }
/* the hero label sits on the big figure's baseline, not its box bottom */
.is-hero .cs-vs-l{ padding-bottom:0.3em; }
/* 8 / 14 was a hairline beside a 96px figure -- the bars are the proof,
   so they carry weight now (Raj, 2026-10-05: "make the graphics clearer if
   not bigger"). The hero stays the thickest. */
.cs-vs-track{ position:relative; display:block; height:12px; }
.is-hero .cs-vs-track{ height:22px; }
.cs-vs-track i{
  position:absolute; left:0; top:0; height:100%; border-radius:2px;
  transform:scaleX(0); transform-origin:left center;
  /* linear: a line being traced is constant motion, and --t keeps the speed
     the same for a stub and a full bar. Once, then held. */
  transition:transform var(--t) linear var(--d);
}
.is-base .cs-vs-solid{ background:rgba(30,25,20,0.42); }
.is-hero .cs-vs-solid{ background:var(--accent-r); }
.is-more .cs-vs-solid,
.cs-vs-reach{ background:rgba(255,123,172,0.34); }
.cs-vs.is-drawn .cs-vs-track i{ transform:scaleX(1); }
.cs-vs.is-drawn .cs-vs-x{ opacity:1; }

/* The receipts: blue, quiet, bottom-aligned so every caption shares a line. */
.cs-receipts{ grid-area:rc; display:flex; flex-wrap:wrap; align-items:flex-end; gap:28px 48px; margin-top:44px; }
.cs-run-bars{ --run-h:84px; display:flex; align-items:flex-end; gap:6px; }
/* the count sits UNDER a fixed --run-h slot (84px; was 56, too small to read
   the shape at a glance), never inside it, so the heights
   resolve against a box the label is not eating. 36px wide: a 4-character
   count ("6.5k") is ~34px of 14px mono, and at 24 they ran into each other. */
.cs-run-bar{ width:36px; }
.cs-run-bar i{
  display:block; width:100%;
  /* a 2px floor: "0 followers" drew nothing at all, so upsurge's before/after
     read as one lonely bar */
  height:max(2px, calc(var(--run-h) * var(--h)));
  margin-top:calc(var(--run-h) - max(2px, calc(var(--run-h) * var(--h))));
  border-radius:2px 2px 0 0;
  background:var(--accent-b); opacity:0.85;
  transform:scaleY(0); transform-origin:bottom center;
  transition:transform 0.55s linear;
}
.cs-run.is-drawn .cs-run-bar i{ transform:scaleY(1); }
/* A measured "before" (the fresh factory's 1-2k), drawn across the bars at
   its true height on the same scale, so the after towers over it with no
   caption doing the arguing. Top-anchored: every bar is a 56px slot from
   the top of the row, whatever sits under it. Over the bars, in ink, so it
   reads where it crosses them. */
.cs-run.has-base .cs-run-bars{ position:relative; }
.cs-run-base{
  position:absolute; left:-6px; right:-6px; z-index:1;
  top:calc(var(--run-h) * (1 - var(--b1)));
  height:max(2px, calc(var(--run-h) * (var(--b1) - var(--b0))));
  background:rgba(30,25,20,0.22);
  border-top:1px dashed rgba(30,25,20,0.6);
  opacity:0; transition:opacity .2s var(--e-out) .6s;
}
.cs-run.is-drawn .cs-run-base{ opacity:1; }
.cs-run-base-l{ color:var(--ink); }
/* 45ms a bar, for any length: it was six nth-child rules, so a seventh bar
   onward drew with the first (gilas has thirteen) */
.cs-run-bar i{ transition-delay:calc(var(--i, 0) * 45ms); }
.cs-run-bar b{ display:block; text-align:center; font-family:'DM Mono',monospace; font-weight:400; font-size:14px; letter-spacing:0.02em; color:var(--ink); margin-top:8px; }
.cs-run-label{ font-family:'DM Mono',monospace; font-size:14px; letter-spacing:0.14em; text-transform:uppercase; color:var(--ink-muted); margin-top:10px; }
.cs-fig-v{
  font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:400;
  font-size:clamp(30px,calc(3.2vw + 4px),36px); line-height:1;
  font-variant-numeric:lining-nums; font-feature-settings:'lnum' 1;
  color:var(--accent-b);
}
/* terms folded into the receipts row: same figure, ink not blue */
.cs-fig.is-term .cs-fig-v{ color:var(--ink); }
/* the terms the work happened under, receding */
.cs-fact-spec{ grid-area:spec; font-family:'DM Mono',monospace; font-size:18px; letter-spacing:0.04em; color:var(--ink-body); margin-top:28px; }
.cs-nb{ white-space:nowrap; }

/* ---- the reading column ---- */
.cs-col{ max-width:620px; margin:0 auto; }
.cs-read{ margin-top:120px; }
/* Two of his phrases side by side. No arrow and no rule: two columns
   starting together are the comparison. 2017 recedes, today is ink. */
.cs-shift{ display:grid; grid-template-columns:1fr 1fr; gap:40px; align-items:start; }
/* Raj, 2026-10-06: the two words are the display line and his phrases are
   the read under them. before is blue, after is pink -- pink is "what it
   did" on this page, and after is what it did. The tracked-caps label and
   the italic phrase swapped faces rather than adding one. */
.cs-shift .cs-run-label{
  font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:400;
  font-size:clamp(22px,calc(2.6vw + 4px),28px); line-height:1.1;
  letter-spacing:0; text-transform:none; margin-top:0;
}
.cs-shift-from .cs-run-label{ color:var(--accent-b); }
.cs-shift-to .cs-run-label{ color:var(--accent-r); }
.cs-shift p{
  font-family:'DM Mono',monospace; font-size:18px; line-height:1.6;
  color:var(--ink); margin:10px 0 0;
}
.cs-shift-from p{ color:var(--ink-body); }
/* his one lifted line -- cut from its paragraph, never copied. In his hand
   since 2026-10-06 (Raj: "taglines all handwritten"), blue because blue is
   Raj's own notes (playground's cards), and a pink line here would sit right
   under the pink "after". Caveat 600, Cormorant fallback per the top of this
   file. Caveat's x-height is far under Cormorant's, so the ramp stays. */
/* Ink since 2026-10-06 (Raj: "keep the text black on these taglines -
   implement highlighter blue and pink and underlines on power/imp words").
   The colour moved off the type and into the marks: one highlighter, one
   drawn underline per line (csLiftMarks), pink on what the work did, blue
   on how it was done. */
.cs-lift{
  font-family:'Caveat','Cormorant Garamond',serif; font-weight:600;
  font-size:clamp(30px,calc(3.2vw + 4px),36px); line-height:1.18;
  color:var(--ink); max-width:24ch; margin:72px 0 0 -1px;
}
.cs-lift-hl{
  --hl:255,123,172; --hl-a:0.38;
  background-image:linear-gradient(103deg,
    rgba(var(--hl),0) 0.4%,
    rgba(var(--hl),var(--hl-a)) 2.4%,
    rgba(var(--hl),calc(var(--hl-a) - 0.06)) 95%,
    rgba(var(--hl),0) 99%);
  background-size:100% 58%; background-position:0 80%; background-repeat:no-repeat;
  padding:0 5px; margin:0 -5px;
  -webkit-box-decoration-break:clone; box-decoration-break:clone;
}
/* blue is far darker than pink at one alpha; 0.24 reads level with it */
.cs-lift-hl.is-b{ --hl:13,138,175; --hl-a:0.24; }
/* the same drawn stroke as the story's .cs-sketch, heavier for a display
   line; it may wrap here, unlike in the story, so it repeats per line */
.cs-lift-u{
  padding-bottom:4px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 8' preserveAspectRatio='none'%3E%3Cpath d='M1 5.2c18-3 38-3.6 58-1.4 20 2.2 40 1.6 60-1.2' fill='none' stroke='%230d8aaf' stroke-width='2.2' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-size:100% 8px; background-position:0 100%;
  -webkit-box-decoration-break:clone; box-decoration-break:clone;
}
.cs-quote{ margin:72px 0 0; }
.cs-quote blockquote{ font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:300; font-size:clamp(22px,calc(2.6vw + 4px),28px); line-height:1.5; color:var(--ink); margin:0 0 12px; }
.cs-quote figcaption{ font-family:'DM Mono',monospace; font-size:14px; letter-spacing:0.1em; text-transform:uppercase; color:var(--ink-muted); }

/* The fold. Grid rows 0fr -> 1fr animate to the content's own height with
   nothing measured. Enter 300ms, exit 200ms: slow where the visitor is
   deciding, fast where the page is answering. visibility keeps a closed
   story out of the tab order and away from screen readers. */
.cs-story-wrap{ margin-top:72px; }
.cs-story-btn{
  display:flex; align-items:center; justify-content:center; gap:10px; width:100%;
  font-family:'DM Mono',monospace; font-size:14px; letter-spacing:0.14em; text-transform:uppercase;
  color:var(--ink); background:var(--tint-r);
  border:0; border-radius:999px; padding:18px 24px; cursor:pointer;
  transition:transform .16s var(--e-out), background-color .2s ease;
}
/* Solid pastel pink, ink on it (Raj, 2026-10-06: "make the button clearer").
   A white wash on paper read as a box, not a button. Pink here is the same
   pink as the booking CTA: both are a way further into the study. */
@media(hover:hover) and (pointer:fine){ .cs-story-btn:hover{ background:color-mix(in srgb, var(--accent-r) 75%, #fff); } }
.cs-story-btn:active{ transform:scale(0.97); }
.cs-story-btn span{ display:inline-block; transition:transform .2s var(--e-out); }
.is-open .cs-story-btn span{ transform:rotate(180deg); }
.cs-story{ display:grid; grid-template-rows:0fr; transition:grid-template-rows .2s var(--e-out); }
.is-open .cs-story{ grid-template-rows:1fr; transition-duration:.3s; }
.cs-story-inner{ overflow:hidden; min-height:0; visibility:hidden; transition:visibility 0s linear .2s; }
.is-open .cs-story-inner{ visibility:visible; transition-delay:0s; }
.cs-story-inner > :first-child{ margin-top:56px; }

/* Beats: the gap between them several times the gap under a label, so the
   structure shows with no rule drawn. */
.cs-section{ margin-bottom:72px; }
/* "more we made for them" still uses the small label */
.cs-section-label{ font-family:'DM Mono',monospace; font-size:14px; letter-spacing:0.18em; text-transform:uppercase; color:var(--ink-muted); margin-bottom:10px; }
/* The beat heads: italic display on the name's ramp, so no new size, each
   behind the tagline's highlighter (Raj, 2026-10-06: "add highlighter effect
   to them"). It takes this page's split -- blue for the two beats about the
   client and the work, pink for what it did. It replaced the 22px rule above
   each head, which was carrying the same split: two marks for one job. The
   gap above a head (72) is several times the gap below it (20), so a head
   belongs to what follows. */
.cs-beat{
  font-family:'Cormorant Garamond',serif; font-style:italic; font-weight:400;
  font-size:clamp(30px,calc(3.2vw + 4px),36px); line-height:1.1; letter-spacing:-0.012em;
  color:var(--ink); margin:0 0 20px;
}
.cs-section-body{ font-family:'DM Mono',monospace; font-size:18px; line-height:1.8; color:var(--ink); max-width:62ch; margin:0; }
.cs-section-body + .cs-section-body{ margin-top:24px; }
/* About's two marks, one of each per study. The highlighter is pink here
   because it sits on what the work did; ink stays on top of it. */
.cs-hl{
  color:var(--ink);
  background-image:linear-gradient(103deg,
    rgba(255,123,172,0) 0.4%,
    rgba(255,123,172,0.34) 2.4%,
    rgba(255,123,172,0.30) 95%,
    rgba(255,123,172,0) 99%);
  background-size:100% 72%; background-position:0 78%; background-repeat:no-repeat;
  /* the swipe overhangs the words without moving them: in a mono face the
     padding About uses shows up as a stray space before the full stop */
  padding:0 4px; margin:0 -4px;
  -webkit-box-decoration-break:clone; box-decoration-break:clone;
}
/* It wraps, and the stroke repeats per line, like .cs-lift-u. It was nowrap:
   at 390 a phrase wider than the column ("i sat neelam and prabhjot down in
   the construction site") ran past the edge and the story's fold clipped
   the words off -- measured on three studies. */
.cs-sketch{
  padding-bottom:5px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 8' preserveAspectRatio='none'%3E%3Cpath d='M1 5.2c18-3 38-3.6 58-1.4 20 2.2 40 1.6 60-1.2' fill='none' stroke='%230d8aaf' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-size:100% 6px; background-position:0 100%;
  -webkit-box-decoration-break:clone; box-decoration-break:clone;
}

/* Everything else made for that client, each tile at its video's own ratio. */
.cs-reels{ margin:72px 0 0; }
.cs-reelrow{
  display:flex; align-items:center; gap:12px; overflow-x:auto; padding:4px 0 10px;
  scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
}
.cs-reelrow::-webkit-scrollbar{ display:none; }
/* A tile sliced by the column edge read as broken (Raj, 2026-10-05). The
   fade says "there is more this way"; it comes off once the row is
   scrolled to its end, or when nothing overflows (case-studies.js). */
.cs-reelrow.has-more{
  -webkit-mask-image:linear-gradient(to right, #000 calc(100% - 72px), transparent);
          mask-image:linear-gradient(to right, #000 calc(100% - 72px), transparent);
}
/* Sized by width off the row height, never wider than the row: a landscape
   tile at full row height was 401px on a 354px phone row and could never be
   seen whole. Clamped, it keeps its ratio, comes out shorter, and sits
   centred against the portraits beside it. */
.cs-reel{
  --vr:0.5625;
  flex:0 0 auto;
  width:min(calc(clamp(200px,32vh,300px) * var(--vr)), 100%);
  aspect-ratio:var(--vr);
  border-radius:8px; overflow:hidden; background:#15120f;
  scroll-snap-align:start;
}
.cs-reel video{ width:100%; height:100%; object-fit:cover; display:block; }
/* A landscape reel, on a row of its own under the portraits: the full
   column width at its own 16:9, starting on the same left edge. */
.cs-reels .cs-reel-wide{ --vr:1.7778; width:100%; margin-top:12px; }

/* On a desktop the row wraps into a grid of four: every tile whole.
   As a scroller it hid its scrollbar, and a mouse wheel only scrolls down,
   so the last tile sat cut off under the fade with no way to reach it
   (Raj, 2026-10-05). A phone keeps the swipe row -- a finger can swipe. */
@media(min-width:761px){
  .cs-reelrow{ display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); align-items:start;
               overflow:visible; scroll-snap-type:none; padding:0; }
  .cs-reelrow.has-more{ -webkit-mask-image:none; mask-image:none; }
  .cs-reelrow .cs-reel{ width:100%; }
}

/* Every video opens in the reel page's player, sound on (case-studies.js,
   csOpenPlayer). The card, scrim, label and sound chip are portfolio's own
   .reel-* rules; this is only the paper behind them and the close chip. */
/* the thumbnail under the video -- see csPosterBg() in case-studies.js */
.cs-media,.cs-reel{ background-image:var(--poster, none); background-size:cover; background-position:center; }
.cs-media video,.cs-reel video{ cursor:pointer; }
.cs-media,.cs-reel{ transition:transform .16s var(--e-out); }
.cs-media:active,.cs-reel:active{ transform:scale(0.98); }
/* Not a true full screen: the study stays behind, blurred and grained, no
   tint (Raj, 2026-10-05). The blur is on this box, which only ever animates
   opacity -- never its height, which would kill the backdrop-filter. */
.cs-player{
  position:fixed; inset:0; z-index:950; background:transparent;
  /* 18 -> 8 (Raj, 2026-10-05: "reduce blur strength") -- the study should
     still be recognisable behind the card */
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  overscroll-behavior:contain; cursor:pointer;
  opacity:0; transition:opacity .15s var(--e-out);
}
.cs-player.is-open{ opacity:1; transition-duration:.22s; }
/* the grain, over the blur and under the card -- the reel slide's own noise
   at a weight that reads on a blurred page */
.cs-player::before{
  content:''; position:absolute; inset:0; pointer-events:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.16'/%3E%3C/svg%3E");
}
.cs-player .reel-media{ cursor:default; }
/* the scrim exists for the label, and there is no label here (his pick) */
/* a button here, a div on the reel page: undo the button chrome and the
   global 44px phone rule, which would stretch a 38px circle into a pill */
.cs-player .reel-audio{ padding:0; min-height:0; font:inherit; }
.cs-player .reel-audio:focus:not(:focus-visible){ outline:none; }
/* next: the sound chip's glass twin on the opposite corner, never pink --
   pink is "sound is on" */
/* Brand blue (Raj, 2026-10-05). Pink stays "sound is on", blue is "go on" --
   the nav's own split, two colours never meaning the same thing. White on
   #0d8aaf is 3.9:1, over the 3:1 an icon needs. */
.cs-player .cs-player-next{ top:auto; bottom:14px; background:var(--accent-b); border-color:transparent; }
@media(hover:hover) and (pointer:fine){ .cs-player .cs-player-next:hover{ background:var(--accent-b); filter:brightness(1.08); } }
/* the reel page goes edge to edge on a phone; here the page shows round it,
   so the card keeps its corners and its lift */
@media(max-width:639px){
  .cs-player .reel-media{ border-radius:8px; box-shadow:0 40px 90px -28px rgba(45,33,22,0.42), 0 10px 30px -12px rgba(45,33,22,0.22); }
}
.cs-cta-wrap{ margin-top:72px; text-align:center; }

@media(prefers-reduced-motion:reduce){
  .cs-vs-track i,.cs-run-bar i{ transform:none; transition:none; }
  .cs-run-base{ opacity:1; transition:none; }
  .cs-vs-x{ opacity:1; transition:none; }
  .cs-story,.cs-story-inner,.cs-story-btn span{ transition:none; }
}

@media(max-width:760px){
  /* The sticky chip bar is gone with the index. It cost 48px under a 50px nav
     -- 14% of a 706px viewport, permanently -- and showed 3.5 of 6 chips with
     no affordance saying the rest were off to the right. */
  #cs-listwrap{ padding:34px 18px calc(30px + var(--nav-clear,86px)); }
  #cs-detail{ padding:30px 18px calc(44px + var(--nav-clear,86px)); }
  .cs-card{ padding:16px 18px; gap:16px; }
  /* The band stacks: name, graph (the 3x on screen one), video, receipts,
     terms. */
  /* name, graph, receipts, terms, THEN the video (Raj, 2026-10-05): a
     full-width 9:16 between the graph and the receipts made the proof two
     screens tall, and the receipts are the proof. Still full width. */
  .cs-band,.cs-band.is-wide{ grid-template-columns:minmax(0,1fr); grid-template-rows:none;
            grid-template-areas:"back" "head" "vs" "rc" "spec" "media"; }
  .cs-vs{ margin-top:28px; }
  /* full column width on a phone -- Raj's rule. Height follows --vr, so the
     one horizontal cut sits short instead of being blown up. */
  .cs-media{ width:100%; margin-top:40px; }
  .cs-receipts{ margin-top:40px; gap:28px 36px; }
  /* thirteen bars at 36px are 540px: a dense run shares the column instead.
     Every count still prints (Raj, 2026-10-06: "all view numbers not
     showing" -- it was the peak only). A bar is ~23px and "6.2k" ~35px, so
     the counts alternate between two rows: each gets two bars' width. The
     negative inline margin widens the label's box past its bar so the text
     centres on the bar instead of overflowing to the right. */
  .cs-run.is-dense{ flex:1 1 100%; min-width:0; width:100%; }
  .cs-run.is-dense .cs-run-bars{ gap:3px; width:100%; }
  .cs-run.is-dense .cs-run-bar{ flex:1 1 0; width:auto; min-width:0; }
  .cs-run.is-dense .cs-run-bar b{ white-space:nowrap; margin-inline:-20px; }
  /* translate, not margin: the row is align-items:flex-end, so a taller
     label lifts its own bar off the baseline. The label below pays for the
     second row instead. */
  .cs-run.is-dense .cs-run-bar:nth-child(even) b{ translate:0 1.4em; }
  .cs-run.is-dense .cs-run-label{ margin-top:calc(10px + 1.4em); }
  /* one fact wider than the screen ("3 weeks across singapore, malaysia,
     and thailand") has to break, or it runs off the edge */
  .cs-fact-spec .cs-nb{ white-space:normal; }
  .cs-read{ margin-top:72px; }
  .cs-shift{ grid-template-columns:1fr; gap:24px; }
  .cs-lift{ margin-top:56px; }
}
/* "no testimonials on a phone anywhere" -- Raj, 2026-10-05 */
@media(max-width:639px){ .cs-quote{ display:none; } }

/* A study's name and its person on a phone: two lines, never one wrapped
   one. Run together they broke mid-name -- "copper + / cloves · sarah
   edwards" -- so the client was split and the person was glued to half of it. */
@media(max-width:600px){
  .cs-hero-sep{ display:none; }
  .cs-hero-person{ display:block; }
}

/* The list row on a phone. It had no layout of its own -- it kept the
   desktop's four-column flex all the way down to 390px, where the multiple's
   fixed 112px column and the arrow together take more than half the width.
   The teaser was left about 150px and broke to four lines ("saying the
   things / only she could / say."), the row number floated somewhere beside
   the middle of that block instead of beside the name it numbers, and the
   card grew tall enough that two rows barely fit a screen.

   Stacked, the row reads in the order it argues in: who, what kind, what it
   did, and then the sentence about it. display:contents on .cs-card-body
   lifts its three children into the card's own grid so they can take rows of
   their own -- the body has no box worth keeping, only flex:1 and min-width.

   The multiples still start together, which is the whole reason they are in
   a column on desktop: column 2 begins where the row number ends, and every
   number is two digits wide, so all six figures share a left edge down the
   page. "up to" still hangs on its own line above its number. */
@media(max-width:600px){
  .cs-card{
    display:grid; grid-template-columns:auto 1fr auto;
    column-gap:8px; row-gap:6px; align-items:start;
  }
  .cs-card-body{ display:contents; }
  .cs-card-no{ grid-area:1/1; line-height:1.35; }
  .cs-card-client{ grid-area:1/2; }
  .cs-card-arrow{ grid-area:1/3; line-height:1; }
  /* The sector sits close to the name because it describes it. The multiple
     and the teaser are separate facts and get more room -- without it, "F&B"
     and the "UP TO" above a hedged figure are two tracked uppercase labels
     of the same size six pixels apart, and they read as one two-line label
     rather than the end of one thing and the start of another. */
  .cs-card-sector{ grid-area:2/2/2/-1; margin-top:0; }
  /* "work in progress" sits on the sector's line and wraps only when the two
     cannot fit. A gap, not the tag's margin: a margin indented the wrapped
     tag off the column's left edge (upsurge at 390); a gap only exists
     between two things on one line. (The study header used to do this for
     its eyebrow; that tag is gone, so the rule lives here now.) */
  .cs-card-sector:has(.cs-wip.is-after){ display:flex; flex-wrap:wrap; align-items:baseline; column-gap:12px; }
  .cs-card-sector .cs-wip.is-after{ margin-left:0; }
  .cs-card-mult{ grid-area:3/2/3/-1; width:auto; margin-top:8px; }
  .cs-card-teaser{ grid-area:4/2/4/-1; margin-top:4px; }
  /* Row 5, explicitly. .cs-card-body is display:contents here, so a child
     without a grid-area auto-places into column 1 beside the row number and
     takes the layout with it. */
  .cs-card-facts{ grid-area:5/2/5/-1; margin-top:8px; }
}



/* ======================================================
   SCREEN: BRAND (0)  (questionnaire.html)

   Raj's thirty questions, one per screen, after someone books a call. Named
   brand (0) on 2026-10-03: the zero edition, before the first video. Three
   acts in his words -- subconscious, conscious, consensus -- each with a
   kawaii mascot that grows from a baby as its act is answered
   (questionnaire-mascots.js). The cover works like avara.xyz: the three in a
   row; pick one and its act slides in beside it. The watercolour plant and
   washes were retired on 2026-10-04 for this style.

   The direction, his pick: Apple's scale and silence, on a left-aligned
   grid. One thing per screen, a huge size against a small one, a lot of
   paper, and the question's number hanging in its own column so every
   other line on the screen shares one left edge.

   Budgets:
     type    5 sizes -- mega (the name, an act, a part's number), display
             (titles and question numbers), 25 his part intros and the act
             lists, 17 everything read, typed or pressed, 13 labels. The old
             15 step folded into 17: two body sizes 2px apart were a
             decision nobody could see.
     colour  the two accents, and the split is the acts: blue is
             subconscious, pink is conscious, both is consensus. They arrive
             as pastel TINTS of the two accents inside ink outlines, never as
             type: paper on blue measures 3.57:1 and paper on pink 2.17:1, so
             pink as text fails at any size. Pink is also the one action.
     spacing 4 tiers.
     objects 1 -- the cast: three mascots of one drawing language, the way
             the plant was one object of many pieces.

   The nav capsule is on this page too (Raj, 2026-10-04: "nav should remain
   even on brand 0"); .q-stage reserves the bar and --nav-clear at its foot.
====================================================== */
/* the mascots' palette, read by both pages they appear on: tints of the two
   accents for the tiles -- no third colour */
#screen-q,#screen-ciad{
  --qm-tile-b:color-mix(in srgb, var(--accent-b) 70%, #a39d94);   /* ink on it 4.83:1 */
  --qm-tile-r:color-mix(in srgb, var(--accent-r) 75%, #a39d94);   /* ink on it 6.99:1 */
  --qm-sky-top:var(--tint-b);
  --qm-sky-low:var(--tint-r);
}
#screen-q{
  --q-1:10px;                     /* inside one unit */
  --q-2:22px;                      /* between the parts of a question */
  --q-3:clamp(44px,5vh,60px);      /* between questions */
  --q-4:clamp(64px,8vh,88px);      /* between bands */
  --q-col:640px;                   /* the measure the writing sits on */
  --q-gw:clamp(220px,22vw,320px);  /* the mascot's column */
  --q-gut:clamp(20px,4vw,48px);

  --q-mega:clamp(64px,calc(7vw + 20px),148px);
  --q-disp:clamp(34px,calc(3vw + 14px),52px);
  --q-hang:1.5em;                  /* the number column, in display ems */

  /* the back/next row, measured: 22px above and below a 50px pill */
  --q-bar-h:94px;

  z-index:40;background:var(--bg-warm);
  display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-rows:minmax(0,1fr);
}
.q-body{overflow:hidden;}
/* The screen is built by questionnaire.js, so until it runs there is paper
   and an empty pink button with no label -- watch-motion filmed it sitting
   there for ~350ms (2026-10-04). Nothing shows until the page is whole. */
#screen-q{opacity:0;}
.q-ready #screen-q{opacity:1;transition:opacity .3s var(--e-out);}

.q-main{position:relative;display:flex;flex-direction:column;min-width:0;min-height:0;height:100%;}

/* ---- top row: where you are, in his three words ---- */
.q-top{
  flex:0 0 auto;display:flex;align-items:center;justify-content:space-between;
  gap:var(--q-2);padding:var(--q-2) var(--q-gut);
}
.q-eyebrow{min-height:1em;}
.q-rail{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:6px var(--q-2);
  font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-label);text-transform:uppercase;
}
.q-rail-act{color:var(--ink-faint);transition:color .2s ease;}
.q-rail-done{color:var(--ink-muted);}
.q-rail-on{color:var(--ink);}
.q-rail-part{color:var(--ink-muted);margin-left:var(--q-2);}

/* ---- quiet text buttons: stuck, send what i have, start from 1 ---- */
.q-quiet{
  border:0;background:none;cursor:pointer;padding:0;white-space:nowrap;
  font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--ink-body);
  border-bottom:1px solid transparent;
  transition:color .18s ease,border-color .18s ease;
}
@media(hover:hover) and (pointer:fine){
  .q-quiet:hover{color:var(--ink);border-bottom-color:rgba(30,25,20,0.3);}
}

/* ---- the scrolling column ---- */
.q-stage{
  flex:1 1 auto;overflow-y:auto;overflow-x:hidden;outline:none;
  /* The column runs to the floor and the bar floats over it (Raj,
     2026-10-04: the bar was a solid band that hid what was behind it). The
     foot reserves the bar and the capsule, so the last line can always be
     scrolled clear of both; scroll-padding does the same for scrollIntoView. */
  padding:var(--q-4) var(--q-gut) calc(var(--q-bar-h) + var(--nav-clear,86px) + var(--q-2));
  scroll-padding-bottom:calc(var(--q-bar-h) + var(--nav-clear,86px));
  /* "dead centre" is vertical too (Raj, 2026-10-05, a red mark in the middle
     of an empty screen under question 26). Auto margins in a flex column
     centre what fits and fall to 0 on what doesn't, so a long question still
     starts at the top and scrolls -- never cut off above. The cover keeps
     its own layout. */
  display:flex;flex-direction:column;
}
.q-screen{
  --q-screen-w:calc(var(--q-col) + var(--q-disp) * 1.5 + var(--q-2));
  max-width:var(--q-screen-w);margin:0 auto;width:100%;flex:none;
}
.q-screen:not(.q-screen-cover){margin-top:auto;margin-bottom:auto;}
/* Auto margins centre it in the stage, and the stage is lopsided: 62px of top
   row plus --q-4 above, the bar and the capsule below. That put the middle
   ~30px high. This spacer is the difference, so the middle of the question is
   the middle of the screen; it shrinks first when a question is too tall.
   62px is .q-top's measured height on desktop. A phone scrolls every question
   anyway, so it is desktop only. */
@media(min-width:900px){
  .q-stage:has(> .q-screen:not(.q-screen-cover))::before{
    content:'';flex:0 1 calc(var(--q-bar-h) + var(--nav-clear,86px) + var(--q-2) - var(--q-4) - 62px);
  }
}
.q-screen{
  transition:opacity .24s var(--e-out),transform .24s var(--e-out);
}
.q-screen.q-in{opacity:0;transform:translateY(8px);transition:none;}

/* the grid: a hanging number, then one left edge for everything else */
.q-grid{
  display:grid;grid-template-columns:calc(var(--q-disp) * 1.5) minmax(0,1fr);
  column-gap:var(--q-2);align-items:baseline;
}
.q-grid-body,.q-reading,.q-end-sec,.q-send-form,.q-part-head,.q-part-why{
  margin-left:calc(var(--q-disp) * 1.5 + var(--q-2));
}

/* ---- the numbers, each on a flat pastel badge in its act's colour ---- */
.q-num{
  position:relative;display:inline-flex;justify-content:center;align-items:center;
  font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;
  font-variant-numeric:lining-nums;color:var(--ink);line-height:1;
}
.q-num-t{position:relative;}
.q-badge{
  position:absolute;left:50%;top:50%;width:1.7em;height:1.7em;
  transform:translate(-50%,-50%);overflow:visible;pointer-events:none;
}
.q-badge-tile{fill:var(--qm-tile-b);}
.q-badge-1{fill:var(--qm-tile-r);}
.q-num-q{font-size:var(--q-disp);justify-self:start;min-width:1.1em;}
.q-num-xl{font-size:var(--q-mega);letter-spacing:var(--tr-display);}

/* ---- display type ---- */
.q-mega,.q-act-word{
  font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;
  /* load-bearing: Cormorant's oldstyle 0 is an o, and brand (o) is not the name */
  font-variant-numeric:lining-nums;
  font-size:var(--q-mega);line-height:0.92;letter-spacing:-0.025em;color:var(--ink);
}
.q-part-title,.q-q-title,.q-send-h{
  font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;
  font-size:var(--q-disp);line-height:var(--lh-display);
  letter-spacing:var(--tr-display);color:var(--ink);text-wrap:balance;
}
.q-byline,.q-act-n{
  font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--ink-muted);
}
.q-byline{margin-top:var(--q-2);}

/* ---- the cover: the name, and the cast -- avara's front door ----
   The three characters in a row. Picked, one comes to the middle at 1.4x
   while the others step back to 0.62 and dim; its act slides in from the
   right (from below on a phone). Movement on screen is --e-in-out; the
   panel's travel is --e-drawer. Transforms and opacity only. */
.q-screen-cover{
  min-height:100%;display:flex;flex-direction:column;justify-content:center;
  padding-left:calc(var(--q-disp) * 1.5 + var(--q-2));
}
/* what brand (0) is, between the name and the cast. Sizes off the page's own
   ladder -- the reframe at the question-title size, the line after it at the
   serif his part intros are set in -- so the cover adds no sixth size. */
/* lining-nums: the cover line says "tribe (0)", and Cormorant's oldstyle 0 is an o */
.q-cover-ctx{margin-top:var(--q-3);max-width:var(--q-col);font-variant-numeric:lining-nums;}
.q-cover-line{
  font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;
  font-size:var(--q-disp);line-height:var(--lh-display);
  letter-spacing:var(--tr-display);color:var(--ink);text-wrap:balance;
}
/* About's section-head rule (Raj, 2026-10-05: "the text effects and
   heirarchy type shit we did on about"). Short, flush with the left edge,
   above the line rather than beside it, so it marks where the cover starts
   talking without indenting it off the spine. */
.q-cover-line::before,
/* the end screen's two heads are sections, exactly what About's rule marks */
.q-end-sec .q-part-title::before,.q-screen-end .q-send-h::before{
  content:'';display:block;width:22px;height:2px;border-radius:1px;
  background:var(--accent-b);margin-bottom:var(--q-1);
}
/* The reframe is the loudest line, so the two after it step down to About's
   pairing: Cormorant for what you look at, DM Mono for what you read. They
   were 25px serif under a 34px serif on a phone -- one size step apart in one
   face, which squints to three equal lines. 17 is the page's read size. */
.q-cover-then{
  margin-top:var(--q-2);
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:300;
  line-height:var(--lh-prose);color:var(--ink-body);text-wrap:pretty;
}
/* Words marked inside his lines, found by exact phrase in questionnaire.js --
   nothing he wrote changes. All blue, because pink on this page is the one
   action (start) and pink type fails on paper at any size; blue type is
   3.57:1, which passes only large, so the coloured word is the one in the
   display line and the marks on the read-size lines are drawn, not type. */
.q-cv-word{color:var(--accent-b);}
.q-cv-sketch{
  white-space:nowrap;padding-bottom:5px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 8' preserveAspectRatio='none'%3E%3Cpath d='M1 5.2c18-3 38-3.6 58-1.4 20 2.2 40 1.6 60-1.2' fill='none' stroke='%230d8aaf' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-size:100% 6px;background-position:0 100%;
}
/* the one highlighter: the line that makes this matter. Ink on it, never
   blue type -- the colour is the swipe behind the words. */
.q-cv-hl,.q-ex-hl{
  --q-hl:13,138,175;
  color:var(--ink);
  background-image:linear-gradient(103deg,
    rgba(var(--q-hl),0) 0.4%,
    rgba(var(--q-hl),0.30) 2.4%,
    rgba(var(--q-hl),0.26) 95%,
    rgba(var(--q-hl),0) 99%);
  background-size:100% 72%;background-position:0 78%;background-repeat:no-repeat;
  padding:0 4px;
  -webkit-box-decoration-break:clone;box-decoration-break:clone;
}
.q-cast{position:relative;margin-top:var(--q-3);height:clamp(300px,42vh,400px);}
/* Wide enough, the name and what it is stand side by side. Stacked, the
   context pushed the cast 281px under a 1440x900 fold; beside the name it
   costs no height of its own, and the cast is back on screen one. */
@media(min-width:1100px){
  .q-stage:has(.q-screen-cover){padding-top:var(--q-3);padding-bottom:var(--q-3);}
  .q-screen-cover{
    max-width:1180px;padding-left:0;
    display:grid;grid-template-columns:auto minmax(0,1fr);
    column-gap:var(--q-4);align-content:center;
  }
  .q-screen-cover .q-mega{grid-column:1;grid-row:1;align-self:end;}
  .q-screen-cover .q-byline{grid-column:1;grid-row:2;}
  .q-screen-cover .q-cover-ctx{grid-column:2;grid-row:1 / span 2;margin-top:0;align-self:end;}
  .q-screen-cover .q-cast{grid-column:1 / -1;}
}
@media(min-width:1100px) and (max-height:820px){
  .q-stage:has(.q-screen-cover){padding-top:var(--q-2);}
  .q-screen-cover .q-cast{margin-top:var(--q-2);}
}
.q-cast-stage{position:absolute;inset:0;transition:transform .55s var(--e-in-out);}
.q-cast-i{
  position:absolute;left:50%;top:50%;width:240px;min-height:0;
  display:flex;flex-direction:column;align-items:center;gap:var(--q-1);
  border:0;background:none;padding:0;cursor:pointer;
  transform:translate(-50%,-50%);
  transition:transform .55s var(--e-in-out),opacity .35s ease;
}
.q-cast-who{display:block;width:240px;transition:transform .2s var(--e-out);}
.q-cast-w{
  font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--ink-body);
}
.q-cast-i.is-on .q-cast-w{color:var(--ink);}
@media(hover:hover) and (pointer:fine){
  .q-cast-i:hover .q-cast-who{transform:translateY(-4px);}
}
.q-cast-i:focus-visible{outline:none;}
.q-cast-i:focus-visible .q-cast-w{color:var(--ink);text-decoration:underline;}
.q-cast-arrows{
  position:absolute;left:50%;bottom:-8px;display:flex;gap:var(--q-1);
  transform:translateX(-50%);opacity:0;pointer-events:none;
  transition:opacity .3s ease,transform .55s var(--e-in-out);
}
.q-cast-arrow{
  width:44px;height:44px;border-radius:50%;cursor:pointer;
  border:1px solid rgba(30,25,20,0.18);background:rgba(255,255,255,0.6);
  font-family:'DM Mono',monospace;font-size:var(--t-read);color:var(--ink);
  transition:transform .16s var(--e-out),background-color .18s ease;
}
.q-cast-arrow:active{transform:scale(0.97);}
@media(hover:hover) and (pointer:fine){ .q-cast-arrow:hover{background:#fff;} }
.q-cast-panel{
  position:fixed;top:0;right:0;bottom:0;z-index:3;width:min(440px,38vw);
  overflow-y:auto;padding:var(--q-4) var(--q-3) var(--q-3);
  background:var(--bg-warm);box-shadow:-24px 0 60px rgba(26,24,20,0.10);
  transform:translateX(100%);visibility:hidden;
  transition:transform .5s var(--e-drawer),visibility 0s linear .5s;
}
.q-cast-close{position:absolute;top:var(--q-2);right:var(--q-2);}
.q-cast-word{
  font-family:'Cormorant Garamond',serif;font-style:italic;font-weight:300;
  font-size:var(--q-disp);line-height:var(--lh-display);letter-spacing:var(--tr-display);
  color:var(--ink);margin-top:var(--q-1);
}
.q-cast-panel .q-count{margin-top:var(--q-1);}
.q-cast-panel .q-act-parts{margin-top:var(--q-2);}
.q-cast-panel .q-go{margin-top:var(--q-3);}
.q-cast-panel.is-open{transform:none;visibility:visible;transition:transform .5s var(--e-drawer),visibility 0s;}
.q-cast.is-focus .q-cast-stage{transform:translateX(calc(min(440px,38vw) / -2));}
.q-cast.is-focus .q-cast-arrows{opacity:1;pointer-events:auto;transform:translateX(calc(-50% - min(440px,38vw) / 2));}
/* the panel names the act, so the labels step aside -- scaled up, the picked
   one's label landed on the arrows */
.q-cast-w{transition:opacity .25s ease;}
.q-cast.is-focus .q-cast-w{opacity:0;}
.q-mega,.q-byline,.q-cover-ctx{transition:opacity .3s ease;}
/* an act open: the panel says what it is now, and would cut the reframe
   mid-word on its way in */
.has-focus .q-cover-ctx{opacity:0;}

/* ---- reading: his intro and his two closing sections ---- */
.q-reading{margin-top:0;}
.q-end-sec .q-reading{margin-left:0;margin-top:var(--q-3);}
.q-reading > * + *{margin-top:var(--q-2);}
.q-p,.q-quote p,.q-ul li,.q-prompt{
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:300;
  line-height:var(--lh-prose);color:var(--ink-body);text-wrap:pretty;
}
.q-p b,.q-quote b,.q-ul b,.q-prompt b{font-weight:500;color:var(--ink);}
/* his opening paragraphs are the voice of the page: set in the serif, big */
.q-reading-intro .q-quote p{
  font-family:'Cormorant Garamond',serif;font-size:var(--t-prose-lead);
  line-height:var(--lh-serif);color:var(--ink-body);font-weight:400;
}
/* his bold, kept loud: weight 500 vanishes in this face at this size, so the
   emphasis is ink italic against body ink -- same family, not a swap */
.q-reading-intro .q-quote b{font-weight:400;font-style:italic;color:var(--ink);}
.q-reading-intro .q-quote{margin-bottom:var(--q-3);}
.q-quote p + p{margin-top:var(--q-2);}
.q-ul{list-style:none;padding:0;}
.q-ul li{position:relative;padding-left:var(--q-2);}
.q-ul li + li{margin-top:var(--q-1);}
.q-ul li::before{content:'\2013';position:absolute;left:0;color:var(--ink-muted);}
.q-read-table{width:100%;border-collapse:collapse;}
.q-read-table th,.q-read-table td{
  text-align:left;vertical-align:top;padding:var(--q-1) var(--q-2) var(--q-1) 0;
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:300;
  line-height:var(--lh-ui);color:var(--ink-body);
}
.q-read-table th{font-weight:400;color:var(--ink);}

/* ---- an act's own screen: its mascot, its word, huge, and its parts ---- */
.q-screen-act{min-height:100%;display:flex;}
.q-act{
  position:relative;flex:1 1 auto;isolation:isolate;
  display:flex;flex-direction:column;justify-content:flex-end;
  padding:var(--q-4) var(--q-3) var(--q-3);
}
.q-act-who{width:clamp(150px,24vh,240px);margin-bottom:var(--q-2);}
.q-act-word{margin-top:var(--q-1);}
.q-act-parts{
  list-style:none;padding:0;margin-top:var(--q-3);
  font-family:'Cormorant Garamond',serif;font-size:var(--t-prose-lead);
  line-height:var(--lh-serif);color:var(--ink);
}
.q-act-parts li{display:flex;gap:var(--q-2);align-items:baseline;}
.q-act-pn{
  flex:0 0 1.2em;font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-ui);color:var(--ink-body);
}

/* ---- centred: Raj, 2026-10-05 ----
   "centre align header pages (subconscious, etc) and also the question and
   its boxes." The act screens, a part's opening and every question sit on
   one centre line; the number's badge moves above its title. Inside the
   boxes, lists and tables, text stays left -- typing centred is not
   reading. This replaces the hanging-number grid on these screens. */
/* safe: centred when it fits, scrolls when it doesn't -- plain centre cut the
   last part off at 1440x900 */
.q-screen-act .q-act{align-items:center;text-align:center;justify-content:safe center;padding-top:0;padding-bottom:0;}
/* the list centres as one block with its rows on a shared left edge --
   centring each row made the numbers zigzag */
.q-screen-act .q-act-parts{text-align:left;}
.q-screen-part .q-part-head{align-items:center;text-align:center;margin-left:auto;margin-right:auto;}
.q-screen-part .q-part-why{margin-left:auto;margin-right:auto;max-width:var(--q-col);text-align:center;}
.q-screen-q .q-q-head{display:flex;flex-direction:column;align-items:center;text-align:center;}
.q-screen-q .q-q-head .q-num-q{margin-bottom:var(--q-1);}
.q-screen-q .q-q-title{justify-content:center;}
.q-screen-q .q-grid-body{margin-left:auto;margin-right:auto;max-width:var(--q-col);}
.q-screen-q .q-prompt{text-align:center;}
.q-screen-q .q-examples{display:flex;flex-direction:column;align-items:center;}
/* the mark flows inline with its text: as a flex item it sat far left of a
   wrapped, centred line */
.q-screen-q .q-ex{display:block;text-align:center;}
.q-screen-q .q-ex-mark{margin-right:.5em;}
.q-screen-q .q-field{text-align:left;}
.q-screen-q .q-more{display:block;margin-left:auto;margin-right:auto;}
.q-screen-q .q-nudge{text-align:center;}
.q-screen-q .q-nudge-row{justify-content:center;}
/* Centred on the screen, not on what the mascot leaves: Raj, 2026-10-04,
   "centre align all questions and answer boxes". margin:auto centred the
   column 160px left of the screen's middle at 1810. Pushed as far right as
   the stage allows; where it can't reach the middle it stops at the
   mascot's edge rather than running under it. The list's numbers hang in
   the margin so the boxes share the prompt's two edges. */
/* Dead centre at every width (Raj, 2026-10-04: "why are questions and
   boxes still not dead centred?"). The rule above only reached the middle
   on screens wide enough to hold the mascot's column too; below ~1500px it
   stopped at the mascot's edge -- 15px left at 1440, 113px at 1024. Now the
   column is centred on the screen, full stop, and the mascot is what gives:
   it floats in the right-hand margin and shrinks to whatever that margin
   is (--q-side), down to 96px. Where even that does not fit, the column
   narrows symmetrically, so it stays centred while it does. */
@media(min-width:900px){
  #screen-q{
    /* declared here too: --q-side needs it, and it was only on .q-screen */
    --q-screen-w:calc(var(--q-col) + var(--q-disp) * 1.5 + var(--q-2));
    --q-side:clamp(96px, calc((100vw - var(--q-screen-w)) / 2 - var(--q-gut) * 2), var(--q-gw));
    grid-template-columns:minmax(0,1fr);
  }
  #screen-q > .q-garden{
    position:absolute;top:0;right:0;bottom:0;
    width:calc(var(--q-side) + var(--q-gut));
  }
  #screen-q:has(> .q-garden:not([hidden])) .q-screen{
    max-width:min(var(--q-screen-w), calc(100vw - (var(--q-side) + var(--q-gut) * 2) * 2));
  }
  .q-screen-q .q-list{margin-left:-28px;}
}

/* ---- a part's opening ---- */
.q-screen-part{padding-top:var(--q-3);}
.q-part-head{display:flex;flex-direction:column;align-items:flex-start;}
.q-part-head .q-num-xl{margin-bottom:var(--q-3);}   /* the blot spills past the numeral */
.q-part-why{margin-top:var(--q-2);}
/* Mono at the read size, as on the cover: a 25px serif under a serif title
   was one step apart in one face. A phone already set it this way; now
   every width does, and the phone's override is gone. */
.q-part-why p{
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:300;
  line-height:var(--lh-prose);color:var(--ink-body);text-wrap:pretty;
}

/* ---- a question ---- */
.q-q-head{margin-bottom:var(--q-2);}
.q-prompt + .q-prompt{margin-top:var(--q-1);}

/* his examples: what he can use against what he can't, before the box */
.q-examples{margin-top:var(--q-2);}
.q-ex{
  display:flex;gap:var(--q-1);align-items:baseline;
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:300;
  line-height:var(--lh-ui);color:var(--ink-body);
}
.q-ex + .q-ex{margin-top:var(--q-1);}
.q-ex-bad{color:var(--ink-muted);}
.q-ex-mark{flex:0 0 auto;color:var(--ink);}
/* The answer he can use is the line that has to land, so it takes the
   cover's highlighter, in ink -- and the swipe is the act's tint (blue for
   subconscious, pink for conscious), the same split as the number badge
   above it. A tint behind ink, never type in colour. */
.q-ex-good{color:var(--ink);}
.q-screen[data-act="1"] .q-ex-hl{--q-hl:255,123,172;}
.q-ex-bad .q-ex-mark{color:var(--ink-muted);}
.q-mark{color:var(--ink);}

.q-field{margin-top:var(--q-2);}

/* the boxes. They grow with what is written in them. */
.q-box,.q-short-input,.q-opt-fill{
  display:block;width:100%;resize:none;
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:300;
  line-height:var(--lh-prose);color:var(--ink);
  background:rgba(255,255,255,0.6);
  border:1px solid rgba(30,25,20,0.14);border-radius:12px;
  padding:14px 16px;
  transition:border-color .18s ease,box-shadow .18s ease,background-color .18s ease;
}
.q-box{min-height:calc(var(--t-read) * 1.66 * 5 + 30px);}
.q-line{min-height:0;}
.q-box:focus,.q-short-input:focus,.q-opt-fill:focus{
  outline:none;border-color:rgba(30,25,20,0.4);background:rgba(255,255,255,0.9);
  box-shadow:0 0 0 3px rgba(255,123,172,0.28);
}

/* lists: numbered, one line each until they need more */
.q-list{list-style:none;padding:0;counter-reset:q;}
.q-list li{counter-increment:q;display:grid;grid-template-columns:28px 1fr;align-items:baseline;}
.q-list li + li{margin-top:var(--q-1);}
.q-list li::before{
  content:counter(q) '.';font-family:'DM Mono',monospace;font-size:var(--t-micro);
  color:var(--ink-muted);
}
.q-more{
  margin-top:var(--q-1);border:0;background:none;cursor:pointer;padding:6px 0;
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:300;color:var(--ink-body);
  transition:color .18s ease,transform .16s var(--e-out);
}
.q-list + .q-more{margin-left:28px;}
@media(hover:hover) and (pointer:fine){ .q-more:hover{color:var(--ink);} }

/* tables: one row to start, a row at a time after */
.q-table{
  display:grid;grid-template-columns:repeat(var(--cols),minmax(0,1fr));
  gap:var(--q-1);align-items:start;
}
.q-th{
  font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-ui);color:var(--ink);line-height:var(--lh-ui);
  padding-bottom:4px;
}
.q-td-label{display:none;}
.q-td-ex{
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-style:italic;font-weight:300;
  line-height:var(--lh-ui);color:var(--ink-muted);padding:0 2px var(--q-1);
}

/* a pick-one */
.q-choice{display:flex;flex-direction:column;gap:var(--q-1);}
.q-opt{
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--q-1);cursor:pointer;
  padding:14px 16px;border-radius:12px;
  border:1px solid rgba(30,25,20,0.14);background:rgba(255,255,255,0.35);
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:300;
  line-height:var(--lh-ui);color:var(--ink-body);
  transition:border-color .18s ease,background-color .18s ease,color .18s ease;
}
.q-opt input[type=radio]{accent-color:var(--ink);margin:0;flex:0 0 auto;}
.q-opt:has(input:checked){border-color:rgba(30,25,20,0.45);background:rgba(255,255,255,0.9);color:var(--ink);}
.q-opt-text{flex:1 1 200px;}
.q-opt-fill{flex:1 1 100%;padding:8px 12px;}
.q-short{display:flex;flex-direction:column;gap:6px;}
.q-short-label{font-family:'DM Mono',monospace;font-size:var(--t-micro);letter-spacing:var(--tr-ui);color:var(--ink-body);}

/* ---- the nudge: his line, once, when an answer is an about page ----
   His voice, so set the way his intro is -- the serif at 25, his bold as ink
   italic. Two quiet buttons: the bar's next is already the one pink thing,
   and pressing it again carries on. */
.q-nudge{
  margin-top:var(--q-2);
  transition:opacity .24s var(--e-out),transform .24s var(--e-out);
}
.q-nudge.q-in{opacity:0;transform:translateY(6px);transition:none;}
.q-nudge-line{
  font-family:'Cormorant Garamond',serif;font-size:var(--t-prose-lead);
  line-height:var(--lh-serif);color:var(--ink-body);text-wrap:pretty;
}
.q-nudge-line b{font-weight:400;font-style:italic;color:var(--ink);}
.q-nudge-row{display:flex;flex-wrap:wrap;gap:var(--q-2);margin-top:var(--q-1);}

/* ---- the last screen ---- */
.q-end-sec + .q-end-sec{margin-top:var(--q-4);}
.q-send-form{margin-top:var(--q-4);}
.q-sheet-in .q-send-form{margin-left:0;margin-top:0;}
.q-send-sub{
  margin-top:var(--q-1);font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:300;
  line-height:var(--lh-prose);color:var(--ink-body);
}
.q-who{display:grid;grid-template-columns:1fr 1fr;gap:var(--q-2);margin-top:var(--q-2);}
.q-send-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--q-2);margin-top:var(--q-2);}
.q-count,.q-send-msg{
  margin-top:var(--q-1);font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-ui);color:var(--ink-muted);
}
.q-send-msg:empty{display:none;}
.q-hp{position:absolute;left:-9999px;width:1px;height:1px;opacity:0;}

/* ---- the bar ---- */
/* No surface of its own: the bar is a row of objects floating over the
   column, one --nav-clear above the floor. Only the controls are solid --
   a band the width of the screen hid the mascot and the last lines of a
   question behind it. Clicks between the controls fall through to the page. */
.q-bar{
  position:absolute;left:0;right:0;bottom:var(--nav-clear,86px);z-index:2;
  display:flex;align-items:center;justify-content:space-between;
  /* more than the pill's own side padding, or the bar fuses */
  gap:var(--q-3);padding:var(--q-2) var(--q-gut);
  pointer-events:none;
}
.q-bar > *{pointer-events:auto;}
/* next always sits on the right. With back hidden on the cover and the mid
   hidden on a phone it was the only child, and space-between put it left
   (Raj, 2026-10-06: "start should be on the right"). */
.q-bar .q-next{margin-left:auto;}
.q-bar-mid{display:flex;align-items:center;gap:var(--q-2);min-width:0;}
/* back and the save note each get the paper they stand on, so text scrolling
   under them never runs through their words */
.q-bar .q-back,.q-bar-mid{
  background:color-mix(in srgb, var(--bg-warm) 88%, transparent);
  -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
  border-radius:999px;
  box-shadow:0 0 0 1px rgba(30,25,20,0.08),0 6px 18px rgba(26,24,20,0.06);
}
.q-bar-mid{padding:10px 18px;}
.q-bar-mid:not(:has(.q-save:not(:empty))):not(:has(#q-send-btn:not(:empty):not([hidden]))){display:none;}
.q-save{
  font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-ui);color:var(--ink-muted);white-space:nowrap;
}
.q-nav{
  border:0;background:none;cursor:pointer;white-space:nowrap;
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:300;color:var(--ink-body);
  padding:12px 4px;transition:color .18s ease,transform .16s var(--e-out);
}
/* same height as next, so the two pills share one row */
.q-nav.q-back{padding:14px 22px;}
.q-nav[hidden],.q-go[hidden]{display:none;}
/* the one action: the loudest thing on the bar. Pink fill, ink type --
   7.3:1, where paper on this pink would be 2.2:1. */
.q-next,.q-go{
  border:0;cursor:pointer;border-radius:999px;white-space:nowrap;
  background:var(--accent-r);color:var(--ink);
  font-family:'DM Mono',monospace;font-size:var(--t-read);font-weight:400;
  padding:14px 28px;
  transition:transform .16s var(--e-out),box-shadow .18s ease;
}
@media(hover:hover) and (pointer:fine){
  .q-nav.q-back:hover{color:var(--ink);}
  .q-next:hover,.q-go:hover{box-shadow:0 6px 20px rgba(255,123,172,0.4);}
}
.q-cover-go{display:none;}   /* phone only -- see the max-width block */
.q-nav:active,.q-cover-go:active,.q-go:active,.q-quiet:active,.q-more:active{transform:scale(0.97);}
.q-go:disabled{opacity:0.5;cursor:default;}

/* ---- the mascots ----
   Drawn by questionnaire-mascots.js after ip-as-logo: no outlines, a cream
   character on a tile, rising out of a lower corner as it grows -- the
   ghost and the cloud from the left, the bunny from the right. The idle bob
   is a loop, so it keeps a literal symmetric curve at the call site. */
.qm{display:block;width:100%;height:auto;filter:drop-shadow(0 10px 18px rgba(26,24,20,.10));}
.qm-tile{fill:var(--qm-tile-b);}
.qm-k1 .qm-tile{fill:var(--qm-tile-r);}
.qm-sky-top{stop-color:var(--qm-sky-top);}
.qm-sky-mid{stop-color:color-mix(in srgb, var(--qm-sky-top) 50%, var(--qm-sky-low));}
.qm-sky-low{stop-color:var(--qm-sky-low);}
.qm-eye{fill:var(--ink);}
.qm-lid{fill:none;stroke:var(--ink);stroke-width:7.5;stroke-linecap:round;}
.qm-grow{transform-box:view-box;transform-origin:0 200px;transition:transform .6s var(--e-out);}
.qm-k1 .qm-grow{transform-origin:200px 200px;}
.qm-still .qm-grow{transition:none;}
.qm-bob{animation:qm-bob 3.2s cubic-bezier(.45,0,.55,1) infinite alternate;}
@keyframes qm-bob{to{transform:translateY(-3px);}}
.qm-eyes{transform-box:fill-box;transform-origin:center;transition:transform .07s ease;}
.qm-blinking .qm-eyes{transform:scaleY(.12);}
/* the ghost sleeps; a specific answer wakes it for a moment */
.qm-awake{opacity:0;}
.qm-asleep,.qm-awake{transition:opacity .18s ease;}
.qm-react .qm-awake{opacity:1;}
.qm-react .qm-asleep{opacity:0;}
/* awake and staying awake: CIAD's last screen */
.qm-woken .qm-awake{opacity:1;}
.qm-woken .qm-asleep{opacity:0;}
/* the reward for a specific answer: one hop */
.qm-react .qm-hop{animation:qm-hop .62s var(--e-out);}
@keyframes qm-hop{
  35%{transform:translateY(-14px);}
  70%{transform:translateY(0);}
  84%{transform:translateY(-3px);}
}

/* the act's mascot beside the question, and how far along it is */
.q-garden{
  position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:var(--q-1);width:var(--q-gw);height:100%;
  /* the screen no longer reserves the capsule, so the column does */
  padding:var(--q-2) var(--q-gut) calc(var(--q-2) + var(--nav-clear,86px)) 0;
}
.q-garden[hidden]{display:none;}
.q-buddy{width:100%;max-width:260px;}
.q-buddy-cap{
  font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-ui);color:var(--ink-muted);
}

/* ---- sheets: send what i have, if you're stuck ---- */
.q-sheet{
  position:fixed;inset:0;z-index:60;display:flex;align-items:center;justify-content:center;
  padding:var(--q-2);background:rgba(26,24,20,0.28);
}
.q-sheet[hidden]{display:none;}
.q-sheet-in{
  width:100%;max-width:560px;max-height:calc(100dvh - 44px);overflow-y:auto;
  background:var(--bg-warm);border-radius:16px;padding:var(--q-3) var(--q-3) var(--q-2);
  box-shadow:0 24px 60px rgba(26,24,20,0.18);
  animation:q-sheet-in .22s var(--e-out);
}
.q-sheet-in .q-reading{margin-left:0;margin-top:var(--q-2);}
.q-sheet-in > .q-quiet{margin-top:var(--q-2);}
@keyframes q-sheet-in{from{opacity:0;transform:scale(0.97);}}

/* ---- phone: the mascot goes under the question ---- */
@media(max-width:899px){
  #screen-q{display:block;}
  #screen-q > .q-garden{display:none;}
  .q-screen .q-garden{width:auto;height:auto;padding:0;margin-top:var(--q-3);}
  /* 96, down from 150 (calm pass, 2026-10-04). At 150 the tile rose up
     between back and next and read as a third, bigger button. It is the
     progress, not an action -- it should be noticed, not pressed. The act
     screens' own mascot steps down with it. */
  .q-act-who{width:clamp(96px,16vh,140px);}
  /* the mascot as the top row's mark: a small tile before the act's word */
  .q-top .q-garden{width:auto;height:auto;padding:0;margin-right:var(--q-1);flex:0 0 auto;}
  .q-top{gap:var(--q-1);}
  /* the list's numbers move inside their boxes: hanging beside them they
     pushed the boxes 14px right of the centre line every other line sits on */
  .q-list li{display:block;position:relative;}
  .q-list li::before{position:absolute;left:16px;top:16px;z-index:1;pointer-events:none;}
  .q-list li .q-box,.q-list li .q-short-input{padding-left:40px;}
  .q-list + .q-more{margin-left:auto;}
  .q-top .q-buddy{width:40px;max-width:none;}
  .q-top .q-buddy-cap{display:none;}
  .q-top .q-garden + .q-eyebrow{margin-right:auto;}
  /* the cast: smaller, and the act comes up as a sheet from below */
  .q-cast{height:clamp(220px,34vh,300px);}
  .q-cast-i{width:132px;}
  .q-cast-who{width:132px;}
  /* the dimmed characters are the way between acts here; arrows would sit
     under the sheet's edge */
  .q-cast-arrows{display:none;}
  .q-cast-panel{
    top:auto;left:0;width:auto;max-height:52dvh;border-radius:18px 18px 0 0;
    padding:var(--q-3) var(--q-2) var(--q-2);transform:translateY(100%);
    box-shadow:0 -20px 50px rgba(26,24,20,0.12);
  }
  /* an act open on a phone: the name steps aside so the characters can move
     up into its room and stay in view above the sheet */
  .has-focus .q-mega,.has-focus .q-byline,.has-focus .q-cover-ctx{opacity:0;}
  /* The cover's words end above the floating start pill (Raj, 2026-10-05:
     the pill sat on "basecamp will not work with them"). Both gaps step down
     a tier on the cover only; the rule above the reframe still marks the
     break, so the byline-to-reframe gap no longer has to. The stage's top
     padding goes to 0, not a tier: .q-top's own 22px already sits above the
     name, and at --q-2 a 375x664 handset still clipped "with them" by 6px. */
  .q-stage:has(.q-screen-cover){padding-top:0;}
  .q-screen-cover .q-cover-ctx{margin-top:var(--q-2);}
  /* the way in sits in the text here, right-aligned under its last line;
     the bar's floating copy covered the copy whenever the nav was up */
  .q-screen-cover .q-cover-go{display:block;margin:var(--q-2) 0 0 auto;}
  #screen-q:has(.q-screen-cover) .q-bar .q-next{display:none;}
  .q-cast.is-focus .q-cast-stage{transform:translateY(-26vh);}

  /* one left edge on a phone: the number sits above its title, not beside */
  .q-stage{padding-top:var(--q-3);}
  .q-grid{display:block;}
  .q-grid .q-num-q{margin-bottom:var(--q-1);}
  .q-grid-body,.q-reading,.q-end-sec,.q-send-form,.q-part-head,.q-part-why,.q-screen-cover{margin-left:0;padding-left:0;}
  .q-act{padding:var(--q-3) var(--q-2) var(--q-2);}
  .q-top,.q-bar{padding-left:20px;padding-right:20px;}
  .q-bar-mid{display:none;}   /* two things fit this bar on a 390 */
  .q-send-inline{display:block;margin:var(--q-2) auto 0;}
  /* the rail keeps only the act you are in */
  .q-rail-act:not(.q-rail-on),.q-rail-part{display:none;}   /* the act word and the question's own number say where you are */
  /* a table becomes a stack of labelled rows */
  .q-table{grid-template-columns:1fr;}
  .q-th{display:none;}
  .q-td-label{
    display:block;margin-bottom:4px;font-family:'DM Mono',monospace;
    font-size:var(--t-micro);letter-spacing:var(--tr-ui);color:var(--ink-muted);
  }
  .q-td-first{margin-top:var(--q-1);}
  .q-td-ex{padding-bottom:0;}
  .q-who{grid-template-columns:1fr;}
}

@media(prefers-reduced-motion:reduce){
  .q-screen,.q-screen.q-in{transform:none;}
  .q-sheet-in{animation:none;}
  .q-nudge,.q-nudge.q-in{transform:none;}
  .qm-bob,.qm-react .qm-hop{animation:none;}
  .qm-grow,.q-cast-stage,.q-cast-i,.q-cast-panel{transition-property:opacity,visibility;}
}

/* ---- one client, saying it ----
   See the note at the call site in ciad.html. Serif because it is somebody
   speaking and every other voice on this deck is set in mono; no accent,
   because both of this page's accents already mean something and a quote
   does not need colour to be the only serif on the screen besides the
   headline. The attribution carries the page's label step, so no new size. */
/* The last element on "the work". Its capsule clearance moved to
   `.od-slide` on 2026-10-04. */
.od-voice{
  margin-top:var(--od-2);max-width:52ch;
}
.od-voice-text{
  font-family:'Cormorant Garamond',serif;font-size:var(--t-h6);
  line-height:var(--lh-serif);color:var(--ink);
  text-wrap:pretty;
}
.od-voice-attr{
  margin-top:var(--od-1);
  font-family:'DM Mono',monospace;font-size:var(--t-micro);
  letter-spacing:var(--tr-label);text-transform:uppercase;color:var(--ink-muted);
}
/* the brand in the voice's own serif, italic (Raj, 2026-10-04) -- the
   person stays in the label face, the company reads as a name */
.od-voice-brand{
  display:block;
  font-family:'Cormorant Garamond',serif;font-style:italic;font-size:var(--t-fine);
  letter-spacing:0;text-transform:none;color:var(--ink-body);
}
/* on its own line the brand needs no dash; it broke mid-name beside the
   person ("copper / + cloves") */
.od-voice-sep{display:none;}
/* Four voices in one cell: the box is the tallest one's height, so the
   reels and the unit line never move when they change. A soft cross-fade
   with a touch of blur so the eye reads one quote turning into the next,
   not two overlapping. */
.od-voice{display:grid;}
.od-voice-item{
  grid-area:1 / 1;opacity:0;filter:blur(3px);
  transition:opacity .6s ease,filter .6s ease;
}
.od-voice-item.is-on{opacity:1;filter:none;}
@media(prefers-reduced-motion:reduce){ .od-voice-item{filter:none;transition:opacity .3s ease;} }
@media(max-width:639px){
  /* the reel row already scrolls here; the quote gets the body step so it
     does not out-shout the five figures it is standing under */
  .od-voice-text{font-size:var(--t-fine);}
  .od-voice{margin-top:var(--od-1);}
}

/* ---- what goes into positioning ----
   See the note at the call site in ciad.html. Three named sources converging
   on the first station. It borrows the station dot exactly -- 9px, accent-b,
   the same 4px ground halo -- because it is the front of the drawing that was
   already on this screen, not a second drawing. Nothing new in the type or
   colour budget: the labels are the rail's own 13px tracked step and the dot
   and drop are --accent-b, which this diagram already uses.

   It occupies the space .od-path already reserved above the rail, so the only
   height it costs is its own three lines -- and the prose sentence it replaced
   was three lines on a phone. */
/* Separated from the prose above it -- screenshotted glued to the paragraph,
   where it read as four more lines of copy rather than as the front of the
   drawing. */
.od-feed{position:relative;margin-top:var(--od-2);}
/* An L, not a floating tick. The first screenshot had a 12px hairline
   stranded 47px short of the rail, connecting nothing. This runs along from
   the list, then drops onto `positioning` -- the stations sit at 12.5 / 37.5
   / 62.5 / 87.5 percent, so 12.5% is the first of them. The horizontal is
   ::after on the feed; the vertical is .od-feed-drop. */
.od-feed::after{
  content:'';position:absolute;left:0;top:100%;
  width:12.5%;height:1px;background:rgba(13,138,175,0.3);
  transform:scaleX(0);transform-origin:left center;
  transition:transform 0.26s linear 260ms;
}
.od-drawn .od-feed::after{transform:scaleX(1);}
/* The feed is a SIBLING above .od-path, not a child of it. Inside, it
   collided: .od-path has padding-top:38px and its rail is absolutely
   positioned at top:38px, so anything in normal flow lands exactly on the
   line -- screenshotted, the three sources sat between the rail and the
   station dots. With the feed above it, the two read as one object, so the
   diagram loses its own top margin. */
.od-feed + .od-path{margin-top:var(--od-1);padding-top:20px;}
.od-feed + .od-path .od-path-rail{top:20px;}
.od-feed-list{
  list-style:none;display:flex;flex-direction:column;gap:3px;
  padding-bottom:12px;
}
.od-feed-list li{
  position:relative;padding-left:17px;
  font-size:var(--t-micro);letter-spacing:var(--tr-label);
  text-transform:uppercase;color:var(--ink-muted);
  /* staggered off the same clock the stations use, so the sources arrive
     before the line that carries them */
  opacity:0;transition:opacity 0.3s ease-out var(--fdly);
}
.od-feed-list li:nth-child(1){--fdly:40ms;}
.od-feed-list li:nth-child(2){--fdly:130ms;}
.od-feed-list li:nth-child(3){--fdly:220ms;}
.od-drawn .od-feed-list li{opacity:1;}
.od-feed-list li::before{
  content:'';position:absolute;left:0;top:50%;
  width:7px;height:7px;margin-top:-3.5px;border-radius:50%;
  background:var(--accent-b);opacity:0.45;
}
/* the drop into `positioning`. The stations sit at 12.5 / 37.5 / 62.5 / 87.5
   percent of the rail, so this lands on the first of them. */
/* reaches the rail: the gap below the feed is .od-path's margin-top
   (--od-1) plus its padding-top, which the rule below trims to 20px */
.od-feed-drop{
  position:absolute;left:12.5%;top:100%;width:1px;
  height:calc(var(--od-1) + 20px);
  background:rgba(13,138,175,0.45);
  transform:scaleY(0);transform-origin:top center;
  transition:transform 0.26s linear 500ms;
}
.od-drawn .od-feed-drop{transform:scaleY(1);}

@media(max-width:639px){
  /* The three sources come off on a phone (Raj, 2026-10-06, his pick). Even
     on one row they were two lines plus their drop, and on an iPhone 13 in
     Chrome they put "and unlimited revisions" ~50px under next. The lead
     already says what positioning looks at. The path takes the lead's gap. */
  .od-feed{display:none;}
  .od-feed + .od-path{margin-top:var(--od-2);padding-top:var(--run);}
  .od-feed + .od-path .od-path-rail{top:var(--run);}
}
@media(prefers-reduced-motion:reduce){
  .od-feed-list li{opacity:1;transition:none;}
  .od-feed-drop{transform:scaleY(1);transition:none;}
  .od-feed::after{transform:scaleX(1);transition:none;}
}
