/* ritikadas.in — all styling for every page on the site.
   Extracted from index.html so the article pages under /writing/ share one
   source of truth instead of each carrying its own copy to drift out of step.
   Two rules deliberately stay inline in index.html: the pre-paint theme script,
   which has to run before the first paint or a manually chosen theme flashes the
   wrong way, and a <noscript> rule that hides the preloader when JS is off. */

:root{
  --paper:#EDE8E1; --paper-alt:#E7E1D8; --surface:#F6F2EB; --surface-2:#FBF8F2; --soft-band:#F6F5F2;
  --ink:#211E24; --ink-2:#38333B; --muted:#6A6369; --faint:#6E676B; --faint-deco:#8B8387;
  
  /* BUILD SPEC EXACT TOKENS: MUTED INDIGO */
  --accent:#4B4F91;
  --accent-soft:#7C81BE;
  --accent-wash:#E5E2EE;
  
  --hairline:#DBD3C9; --hairline-strong:#CFC6BA;
  --shadow:0 1px 2px rgba(33,30,36,.04),0 10px 34px rgba(33,30,36,.07);
  --shadow-lift:0 3px 10px rgba(33,30,36,.09),0 28px 66px rgba(33,30,36,.17);
  --r-sm:9px; --r:14px; --r-lg:18px;
  --display:'Bricolage Grotesque',sans-serif; --body:'Inter',sans-serif; --mono:'IBM Plex Mono',monospace;
  --wrap:1080px; --measure:58ch;
}

/* ---- DARK THEME OVERRIDES (BUILD SPEC §3) ---- */
[data-theme="dark"] {
  --paper:#1A171C; --paper-alt:#221E24; --surface:#25202B; --surface-2:#2B2533; --soft-band:#1C1821;
  --ink:#EDE8E1; --ink-2:#DDD6E3; --muted:#A29ABC; --faint:#948B9E; --faint-deco:#776F7E;
  --accent:#878DC9; --accent-soft:#A3A7D8; --accent-wash:rgba(130,136,196,0.18);
  --hairline:#393242; --hairline-strong:#4B4257;
  --shadow:0 2px 8px rgba(0,0,0,.4);
  --shadow-lift:0 4px 14px rgba(0,0,0,.5),0 18px 48px rgba(0,0,0,.72);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper:#1A171C; --paper-alt:#221E24; --surface:#25202B; --surface-2:#2B2533; --soft-band:#1C1821;
    --ink:#EDE8E1; --ink-2:#DDD6E3; --muted:#A29ABC; --faint:#948B9E; --faint-deco:#776F7E;
    --accent:#878DC9; --accent-soft:#A3A7D8; --accent-wash:rgba(130,136,196,0.18);
    --hairline:#393242; --hairline-strong:#4B4257;
    --shadow:0 2px 8px rgba(0,0,0,.4);
    --shadow-lift:0 4px 14px rgba(0,0,0,.5),0 18px 48px rgba(0,0,0,.72);
  }
}

*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}
html{scroll-behavior:smooth}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}

/* ---- KEYBOARD FOCUS ----
   There was not one :focus rule on this site. Tab through it and nothing told you
   where you were — which makes the whole thing unusable without a mouse, and is the
   single most common accessibility failure there is.

   :focus-visible rather than :focus, so it appears for keyboard users and never
   draws a ring round a button someone merely clicked. The offset keeps it clear of
   the element's own border instead of sitting on top of it. */
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:3px}
.card-link:focus-visible{outline-offset:6px}
.btn:focus-visible,.theme-btn:focus-visible,.nav-toggle:focus-visible{outline-offset:2px}
/* A link that stretches an invisible overlay across a card has no shape of its own,
   so the ring is drawn on the card instead. */
.card:has(.card-link:focus-visible){outline:2px solid var(--accent);outline-offset:4px}

/* Skip link — the first thing a keyboard or screen-reader user meets. Off-screen
   until focused, so it costs sighted visitors nothing. */
.skip-link{position:absolute;left:-9999px;top:0;z-index:2000;background:var(--ink);
  color:var(--paper);font-family:var(--mono);font-size:13px;padding:12px 18px;
  border-radius:0 0 10px 0;text-decoration:none}
.skip-link:focus{left:0}

/* BUTTERY SMOOTH 60-120FPS NATIVE SCROLLING (NO COMPOSITOR STUTTER) */
body{
  background:var(--paper);
  color:var(--ink);
  font-family:var(--body);
  font-size:17px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  /* clip, not hidden: `hidden` makes the body a scroll container, which
     silently disables position:sticky for everything inside it — that is what
     the case-study cascade depends on. `clip` contains the overflow without
     creating the container. */
  overflow-x:clip;
}

.wrap{max-width:var(--wrap);margin:0 auto;padding:0 28px}
.eyebrow{font-family:var(--mono);font-size:13px;font-weight:500;letter-spacing:.13em;text-transform:uppercase;color:var(--accent)}
.eyebrow.muted{color:var(--faint)}

/* ---- GPU-ACCELERATED PRELOADER CURTAIN ---- */
.preloader{position:fixed;inset:0;z-index:1000;display:flex;pointer-events:auto;will-change:visibility}
.preloader-panel{
  flex:1;
  height:100%;
  background:#1A171C;
  will-change:transform;
  backface-visibility:hidden;
  -webkit-backface-visibility:hidden;
  transform:translateZ(0);
  transition:transform 0.6s cubic-bezier(0.77,0,0.175,1);
}
.preloader-panel.left{transform-origin:top}
.preloader-panel.right{transform-origin:bottom}
.preloader-content{position:absolute;inset:0;z-index:1010;padding:48px 40px;display:flex;flex-direction:column;justify-content:space-between;pointer-events:none;will-change:opacity}
.preloader-top{display:flex;justify-content:space-between;align-items:center}
.preloader-brand{font-family:var(--mono);font-size:13px;letter-spacing:.15em;text-transform:uppercase;color:#A3A7D8}
.preloader-num-wrap{margin:auto 0;text-align:right}
.preloader-num{font-family:var(--display);font-size:clamp(70px,16vw,190px);font-weight:400;color:#EDE8E1;line-height:.9;letter-spacing:-.04em}
.preloader-bottom{display:flex;justify-content:space-between;align-items:flex-end}
.preloader-tag{font-family:var(--display);font-size:clamp(20px,4vw,44px);color:rgba(237,232,225,.85);font-weight:500}
.preloader-subtext{font-family:var(--mono);font-size:11px;color:rgba(237,232,225,.5);text-transform:uppercase;letter-spacing:.1em}
.preloader.done .preloader-panel.left{transform:translate3d(0,-100%,0)}
.preloader.done .preloader-panel.right{transform:translate3d(0,100%,0)}
.preloader.done .preloader-content{opacity:0;transition:opacity 0.35s ease-out}
.preloader.done{pointer-events:none;visibility:hidden;transition:visibility 0s linear 0.6s}

/* DARK MODE CONTRAST REVEAL */
[data-theme="dark"] .preloader-panel { background: #EDE8E1 !important; }
[data-theme="dark"] .preloader-brand { color: #4B4F91 !important; }
[data-theme="dark"] .preloader-num { color: #211E24 !important; }
[data-theme="dark"] .preloader-tag { color: rgba(33, 30, 36, 0.85) !important; }
[data-theme="dark"] .preloader-subtext { color: rgba(33, 30, 36, 0.55) !important; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .preloader-panel { background: #EDE8E1 !important; }
  :root:not([data-theme="light"]) .preloader-brand { color: #4B4F91 !important; }
  :root:not([data-theme="light"]) .preloader-num { color: #211E24 !important; }
  :root:not([data-theme="light"]) .preloader-tag { color: rgba(33, 30, 36, 0.85) !important; }
  :root:not([data-theme="light"]) .preloader-subtext { color: rgba(33, 30, 36, 0.55) !important; }
}

@media (prefers-reduced-motion:reduce){.preloader{display:none!important}}

/* The custom cursor block that used to sit here is gone, along with the
   `cursor:none` rules that hid the native pointer to make room for it. It put a
   ring on screen five frames behind the real pointer, so you aimed with
   something that was never where the click would land. Nothing replaces it: the
   native cursor is precise, and it is whatever the visitor chose at the OS
   level, including any accessibility size or contrast setting. */

/* ---- SIDE DOTS NAV ---- */
.side-nav{position:fixed;right:24px;top:50%;transform:translateY(-50%);z-index:90;display:flex;flex-direction:column;gap:14px}
.side-dot{width:9px;height:9px;border-radius:50%;border:1.5px solid var(--hairline-strong);background:transparent;transition:all .25s;position:relative;cursor:pointer;text-decoration:none}
.side-dot:hover,.side-dot.active{border-color:var(--accent);background:var(--accent);transform:scale(1.35)}
.side-dot .tooltip{position:absolute;right:22px;top:50%;transform:translateY(-50%) translateX(6px);background:var(--ink);color:var(--paper);font-family:var(--mono);font-size:10px;padding:4px 9px;border-radius:5px;white-space:nowrap;opacity:0;pointer-events:none;transition:all .2s}
.side-dot:hover .tooltip{opacity:1;transform:translateY(-50%) translateX(0)}
@media(max-width:900px){.side-nav{display:none}}

/* HARDWARE ACCELERATED STICKY NAV (NO SCROLL REPAINT JANK) */
nav{
  position:sticky;
  top:0;
  z-index:50;
  background:rgba(237,232,225,.88);
  backdrop-filter:blur(12px);
  -webkit-backdrop-filter:blur(12px);
  border-bottom:1px solid transparent;
  transform:translateZ(0);
  will-change:background-color,border-color;
  transition:border-color .2s,background-color .2s;
}
[data-theme="dark"] nav{background:rgba(26,23,28,.88)}
nav.scrolled{border-bottom-color:var(--hairline);background:rgba(237,232,225,.96)}
[data-theme="dark"] nav.scrolled{background:rgba(26,23,28,.96)}
/* The bar runs to the edges rather than sitting inside the 1080px content column.
   It was already geometrically symmetric — equal gutters both sides — but the row
   holds one small signature on the left against a theme pill, a status badge and
   five links on the right, so it read right-heavy. Pushing both ends outward gives
   the signature weight to answer that cluster with. It no longer aligns with the
   content below, which is the accepted trade for a full-width bar. */
/* The bar still runs edge to edge rather than sitting in the content column, so the
   signature can answer the link cluster across the full width. But the cluster lost
   the availability badge and two links, so the old inset left the two sides stranded
   at opposite ends of a mostly empty bar. Pulled inward — enough to close the gap,
   not so far that it becomes a centred strip. */
.nav-in{max-width:none;margin:0;padding:13px clamp(18px,5.5vw,112px) 11px;display:flex;align-items:center;justify-content:space-between}

.brand{display:flex;align-items:center;gap:10px;text-decoration:none}

/* TOP NAV BRAND MARK — one external SVG, painted through a CSS mask.
   It used to be ~9KB of inline <path> repeated in every HTML file on the site,
   which was the price of letting the theme drive its colour. A mask buys the
   same thing for one <span>: the element's background is the ink, the file only
   supplies the shape, so --brand still switches with the theme and the asset is
   cached once instead of re-downloaded inside every page. */
:root{ --brand:#0809A4; --logo-h:48px; --nav-h:73px; --pdeo-label:#3A3D73; --pill-line:#9B9084; --hm-me:#A9AEE6;
       --study-reddit:#CE3700; --study-youtube:#E00000; --crew-brand:#A62B22; --crew-line:rgba(166,43,34,.38); --crew-tint:rgba(166,43,34,.07); }
[data-theme="dark"]{ --brand:#EDE8E1; --pdeo-label:#A3A7D8; --pill-line:#5F5570; --hm-me:#3A3D73;
       --study-reddit:#FF7A47; --study-youtube:#FF6B6B; --crew-brand:#E8776A; --crew-line:rgba(232,119,106,.40); --crew-tint:rgba(232,119,106,.10); }
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){ --brand:#EDE8E1; --pdeo-label:#A3A7D8; --pill-line:#5F5570; --hm-me:#3A3D73;
       --study-reddit:#FF7A47; --study-youtube:#FF6B6B; --crew-brand:#E8776A; --crew-line:rgba(232,119,106,.40); --crew-tint:rgba(232,119,106,.10); }
}
/* aspect-ratio replaces width:auto — a masked <span> is an empty box and has no
   intrinsic size to derive a width from. 1610/507 is the source viewBox. */
.brand-mark{
  height:var(--logo-h);
  aspect-ratio:1610/507;
  display:block;
  background:var(--brand);
  -webkit-mask:url(/assets/brand-signature.svg) no-repeat center/contain;
          mask:url(/assets/brand-signature.svg) no-repeat center/contain;
  transition:transform .25s, background-color .3s;
}
/* The signature is a FILLED outline, not a stroked line, so its weight can't be
   dialled directly. Stroking the same path in the same colour grows it by half
   the stroke width on every edge — what a thicker pen does. 2.98 units measures
   as ~23% more ink at this size. That stroke is now baked into
   assets/brand-signature.svg, since a mask reads the file's alpha and cannot be
   restyled from here — --logo-weight lives in that file's stroke-width now. */
.brand:hover .brand-mark{ transform:scale(1.04); }

/* THEME TOGGLE BUTTON */
.theme-btn{
  font-family:var(--mono);
  font-size:12px;
  background:var(--surface);
  border:1px solid var(--hairline-strong);
  color:var(--ink);
  padding:5px 12px;
  border-radius:20px;
  cursor:pointer;
  display:inline-flex;
  align-items:center;
  gap:6px;
  transition:all .2s;
  user-select:none;
}
.theme-btn:hover{border-color:var(--accent);color:var(--accent)}

/* The availability badge that used to sit here is gone. It was the widest item in
   a bar that already carried a control, a status and five destinations, and it said
   what the Contact section says anyway — where the claim is more credible, because
   it sits next to an actual way to get in touch. */

/* Every label here is one idea, and three of the five are two words long. In a
   monospace face the space inside "Shipped Products" is the same width as any other
   character, which left it barely narrower than the gap separating one link from the
   next — so five labels read as ten loose words. Tightened within, widened between,
   so each item groups as a single thing. */
.nav-links{display:flex;gap:26px;align-items:center}
.nav-links a{font-family:var(--mono);font-size:12.5px;word-spacing:-2.5px;color:var(--muted);text-decoration:none;transition:color .2s}
.nav-links a:hover{color:var(--ink)}
.nav-cta{color:var(--accent)!important;font-weight:600}

/* ---- MOBILE MENU ----
   Below 640px the nav links are hidden and the side dots disappear below 900px,
   so without this there is no way to move between sections on a phone. */
.nav-toggle{display:none;align-items:center;justify-content:center;width:38px;height:38px;padding:0;border:1px solid var(--hairline-strong);border-radius:9px;background:var(--surface);cursor:pointer;flex:none}
.nav-toggle:hover{border-color:var(--accent)}
.nav-toggle-bars{display:block;position:relative;width:16px;height:10px}
.nav-toggle-bars span{position:absolute;left:0;right:0;height:1.5px;background:var(--ink);border-radius:2px;transition:transform .22s ease,opacity .22s ease}
.nav-toggle-bars span:nth-child(1){top:0}
.nav-toggle-bars span:nth-child(2){bottom:0}
.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(1){transform:translateY(4.25px) rotate(45deg)}
.nav-toggle[aria-expanded="true"] .nav-toggle-bars span:nth-child(2){transform:translateY(-4.25px) rotate(-45deg)}
@media (prefers-reduced-motion:reduce){.nav-toggle-bars span{transition:none}}

.nav-menu{display:none;border-top:1px solid var(--hairline);background:var(--paper)}
.nav-menu.open{display:block}
.nav-menu .nm-in{max-width:var(--wrap);margin:0 auto;padding:14px 16px 18px;display:flex;flex-direction:column}
.nav-menu a{font-family:var(--mono);font-size:14px;letter-spacing:.02em;color:var(--ink-2);text-decoration:none;padding:13px 2px;border-bottom:1px solid var(--hairline);min-height:46px;display:flex;align-items:center}
.nav-menu a:last-child{border-bottom:none}
.nav-menu a:active{color:var(--accent)}
/* the menu is a small-screen affordance only */
@media(min-width:641px){.nav-menu{display:none!important}}

@media(max-width:640px){
  .nav-toggle{display:inline-flex}
  .nav-links a:not(.nav-cta){display:none}
  #theme-text{display:none}                 /* icon-only toggle to make room */
  .theme-btn{padding:6px 9px}
  .nav-links{gap:10px}
}

/* hero */
/* ---- ELEVATOR PITCH ----
   The opening screen holds one thing: the claim, the one line of proof behind
   it, and two ways in. Nothing else competes, and nothing is half-visible at
   the fold — a visitor reads the pitch whole before deciding to scroll.
   svh, not vh: on mobile vh is measured against the *expanded* viewport, so a
   100vh hero sits partly under the browser chrome on first paint. */
header.elevator{
  position:relative;
  /* The nav is sticky, so it OCCUPIES flow: the header starts at y=nav-height,
     not y=0. A plain 100svh therefore overshot the fold by exactly the nav's
     height and pushed the marquee just out of view — and the old padding-top
     was clearing a nav that was never overlapping in the first place.
     --nav-h is re-measured in JS so this holds at every breakpoint. */
  height:calc(100vh - var(--nav-h));
  height:calc(100svh - var(--nav-h));
  display:flex;
  flex-direction:column;
  padding:0 0 clamp(28px,8vh,82px);
}
/* The cue and the marquee are the last two flex children, so they sit at the
   bottom of the fold in normal flow. They were absolutely positioned before,
   against a header using min-height — content pushed the header past one
   viewport, so bottom:0 was below the fold and the marquee only appeared on
   scroll. A fixed height and flow order makes that impossible. */
/* Aligned to the top, not centred. Centring inside a full-height header put a
   ~250px void between the nav and the eyebrow on a tall window — the fold read
   as empty above the pitch. Space below is fine: the marquee anchors that edge. */
/* Centred in the space above the marquee, not top-aligned. The fold is ~755px
   and the content ~450px, so ~300px is unavoidably empty; top-aligning dumped
   all of it into one 350px hole under the buttons, which reads as a bug. Split
   evenly it is ~120px top and bottom, which reads as breathing room. */
.hero-body{flex:1 1 auto;display:flex;align-items:center;min-height:0;padding:12px 0}
/* On a short window, shrink the type rather than let the marquee fall out. */
@media (max-height:700px){
  h1.hero{font-size:clamp(27px,3.4vw,40px)}
  .hero-sub{font-size:16px;margin-top:16px}
  .hero-eyebrow{margin-bottom:16px}
  .hero-actions{margin-top:22px}
  .hero-actions .btn{padding:11px 18px}
}

/* ---- ENTRANCE ----
   What both references do on load: the fold assembles itself in order, so the
   eye lands on the eyebrow, then the claim, then the way in. No splash screen
   and nothing withheld — the text is in the HTML and paints immediately; only
   the reveal is staggered. */
.hero-enter{opacity:0;transform:translateY(14px);animation:hero-in .68s cubic-bezier(.2,.7,.2,1) forwards}
@keyframes hero-in{to{opacity:1;transform:none}}
.hero-eyebrow{animation-delay:.06s}
h1.hero{animation-delay:.17s}
.hero-sub{animation-delay:.32s}
.hero-actions{animation-delay:.45s}
.hero-marquee{animation-delay:.60s}
@media (prefers-reduced-motion:reduce){.hero-enter{opacity:1;transform:none;animation:none}}
/* The block still fills whatever screen it lands on, but the type is sized to
   fit a 1366x768 laptop — ~625px of usable viewport once browser chrome and the
   nav are gone. Sizing to the device instead would cramp the short laptops and
   sprawl on the tall monitors. Measured stack: 44 eyebrow + 195 headline +
   89 sub + 86 actions = ~414px, inside the ~525px available. */
/* ---- CAPABILITY MARQUEE ----
   The strip that closes the fold. It lists what she works with and claims
   nothing — the last version asserted outcomes, which read as more of the same
   boasting the rest of the page already does.
   The track holds the list twice and travels -50%, so the second copy is
   exactly where the first started and the loop has no seam. Edges are masked
   rather than cut, so items fade instead of clipping mid-word. */
.hero-marquee{flex:none;overflow:hidden;background:var(--ink);padding:13px 0;
  -webkit-mask-image:linear-gradient(to right,transparent,#000 7%,#000 93%,transparent);
          mask-image:linear-gradient(to right,transparent,#000 7%,#000 93%,transparent)}
.hm-track{display:flex;width:max-content;animation:hm-scroll 74s linear infinite}
.hero-marquee:hover .hm-track{animation-play-state:paused}
.hm-item{display:inline-flex;align-items:center;font-family:var(--mono);font-size:12px;letter-spacing:.09em;text-transform:uppercase;color:var(--paper);white-space:nowrap}
/* The bar is background:var(--ink), which flips with the theme — so any fixed colour
   passes on one side and fails on the other. The indigo accent was 2.22:1 in light;
   lifting it to --accent-soft then broke dark at 1.9:1. Mixing from --paper, the same
   token the marquee text uses, tracks the bar in both themes and stays quieter. */
.hm-dot{color:color-mix(in srgb,var(--paper) 70%,var(--ink));font-size:7px;margin:0 26px}
/* The band inverts between themes — a dark strip in light mode, light in dark —
   so --hm-me inverts with it. The personal lines carry their own colour so the
   change of register is visible, not something you infer from the wording. */
.hm-item.hm-me{color:var(--hm-me)}
@keyframes hm-scroll{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@media (prefers-reduced-motion:reduce){.hm-track{animation:none}}
@media(max-width:640px){.hm-item{font-size:11px}.hm-dot{margin:0 18px}}
.wa-ico{width:15px;height:15px;vertical-align:-3px;margin-right:5px}
.hero-eyebrow{margin-bottom:26px;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
h1.hero{font-family:var(--display);font-weight:500;font-size:clamp(31px,4.3vw,56px);line-height:1.06;letter-spacing:-.028em;max-width:none}
h1.hero em{font-style:normal;color:var(--accent)}
.hero-sub{margin-top:26px;font-size:clamp(17px,1.5vw,20px);line-height:1.55;color:var(--ink-2);max-width:66ch}
.hero-actions{margin-top:34px;display:flex;gap:14px;flex-wrap:wrap}
.btn{font-family:var(--mono);font-size:13px;letter-spacing:.02em;padding:12px 20px;border-radius:10px;text-decoration:none;display:inline-flex;align-items:center;gap:9px;transition:transform .15s,box-shadow .2s,border-color .2s,background .2s;cursor:pointer;border:none;min-height:44px}
.btn-primary{background:var(--ink);color:var(--paper)}
.hero-actions .btn{padding:15px 26px;font-size:14px}
.btn-primary:hover{transform:translateY(-1px);box-shadow:var(--shadow-lift)}
.btn-ghost{background:transparent;color:var(--ink);border:1px solid var(--hairline-strong)}
.btn-ghost:hover{border-color:var(--ink);transform:translateY(-1px)}
.btn .arr{transition:transform .2s}.btn:hover .arr{transform:translate(2px,-2px)}

/* sections */
/* 34, not 56. At 56 the gap from the last card of one section to the next heading
   measured 113px — the two paddings plus the heading's own. That much dead ground
   stops reading as a break between sections and starts reading as a page that has
   run out of things to say. The band colour already marks where one section ends,
   so the space does not have to do it twice. Now 69px. */
section{padding:34px 0}
.band{background:var(--soft-band);border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline)}
.sec-head{display:flex;align-items:center;gap:18px;margin-bottom:28px}
.sec-head .concept{font-family:var(--display);font-weight:600;font-size:clamp(22px,3.2vw,32px);letter-spacing:-.02em;line-height:1.12}
/* Product and study marks beside a section heading. Was three inline style
   attributes repeated per logo; now one class, so the second section could
   reuse it rather than copying twenty lines of style attribute. */
.concept.with-marks{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.concept-marks{display:inline-flex;align-items:center;gap:8px;background:var(--surface-2);
  border:1px solid var(--hairline-strong);padding:5px 12px;border-radius:20px;margin-left:4px}
.concept-marks img{display:block;width:20px;height:20px}
/* The wordmark logos read smaller than the square tiles at the same box, so
   they get a couple of pixels back. */
.concept-marks img.wide{width:22px;height:22px}
/* App tiles keep the corner radius they have on their own cards. */
.concept-marks img.tile{border-radius:5px}
.sec-head .sub{color:var(--muted);font-size:14.5px;margin-top:3px}

/* ---- STICKY SECTION HEADINGS ----
   Each section's heading pins under the nav while its own content scrolls
   beneath it, and the next heading pushes it out on arrival. Sections arrive
   at the reader rather than the reader travelling to them, and on a phone you
   always know which one you are in. `.sec-head` exists only on the homepage,
   so this reaches nothing else.

   Frosted rather than filled. A plain section paints --paper and a card paints
   --surface-2, a real step in tone that stayed invisible until the heading
   gained an edge; letting what passes underneath tint the bar removes the step
   instead of trying to match two colours that never match. Same treatment the
   nav already uses, so the two read as one system. */
.sec-head{
  position:sticky;
  top:var(--nav-h);
  z-index:6;
  padding:18px 0 14px;
  margin-bottom:20px;
  background:color-mix(in srgb, var(--paper) 78%, transparent);
  -webkit-backdrop-filter:saturate(1.5) blur(12px);
          backdrop-filter:saturate(1.5) blur(12px);
  border-bottom:1px solid transparent;
  /* Only paint-level properties animate here. padding, font-size and max-height all
     force the page to be laid out again on every frame, and this bar re-blurs what is
     behind it on every frame too — five of them did. The size change now lands in one
     step; the shadow and the hairline are what fade. */
  transition:box-shadow .3s ease,border-color .3s ease;
  /* Same reason the nav carries it, twenty lines up. A frosted bar that stays put
     while the page moves under it has to re-read and re-blur everything behind it
     on every frame. Without its own layer that redraw lands on the main one, and
     the bar strobes and dithers instead of holding still. */
  transform:translateZ(0);
}
section.band .sec-head{background:color-mix(in srgb, var(--soft-band) 78%, transparent)}
@supports not (backdrop-filter:blur(1px)){
  .sec-head{background:var(--paper)}
  section.band .sec-head{background:var(--soft-band)}
}
.sec-head .concept{transition:none}
.sec-head .sub{max-height:44px;overflow:hidden;transition:opacity .18s ease}
.sec-head .concept-marks{transition:transform .3s cubic-bezier(.2,.7,.2,1);transform-origin:left center}

/* Pinned. At 375px the nav is 73px and a full heading another 74px — a quarter
   of a small phone screen before any content. Dropping the subtitle and the
   type size takes the heading to ~34px. */
.sec-head.stuck{padding:9px 0 8px;box-shadow:0 10px 24px -20px rgba(33,30,36,.5);border-bottom-color:var(--hairline);
  /* The heading gives back, as margin, exactly the height it loses to the collapse.
     --stuck-pad is measured in script.js, because the number moves with breakpoint and
     webfont. The section is then the same height pinned or not, so the page never gets
     shorter under the reader — which is what let the last section pin, lose its own
     scroll to the page bottom, and unpin itself. It does not animate; nothing here
     animates layout any more. */
  margin-bottom:var(--stuck-pad,20px)}
.sec-head.stuck .concept{font-size:clamp(16px,2.1vw,20px)}
.sec-head.stuck .sub{max-height:0;opacity:0;margin-top:0}
.sec-head.stuck .concept-marks{transform:scale(.82)}
.stick-sentinel{height:1px;margin-bottom:-1px;pointer-events:none}

/* ---- SCROLL-DRIVEN PIN ----------------------------------------------------
   Everything above flips the heading between two sizes at one line. That reads
   as a switch, not as scrolling. Here the browser drives the same change off
   scroll position itself, so the heading is mid-size while you are mid-scroll,
   and it runs on the compositor rather than on the main thread — the reason the
   300ms transition felt like lag and this does not.

   The sentinel becomes the ruler. It is 130px tall and pulled back by the same
   130px, so it changes no layout, and its bottom edge still sits exactly on the
   heading's top edge. `view-timeline-inset` moves the measuring line down to
   where the nav ends. So `exit 0%` is 130px before the heading reaches the nav,
   and `exit 100%` is the moment it arrives — the heading finishes shrinking
   exactly as it lands.

   Only Chrome and Edge support this today. Everything else falls through to the
   class-based version above, which still works. -------------------------- */
@supports (animation-timeline: view()) {
  /* The name has to be hoisted to the element that contains both the ruler and the
     heading. Declaring it on the ruler alone is not enough — the heading cannot see
     it, the timeline never attaches, and the animation sits at its end state at every
     scroll position, which is exactly the "big, then small" with nothing in between. */
  section > .wrap{timeline-scope:--pin}
  .stick-sentinel{
    /* margin-TOP, so the ruler lies in the 130px ABOVE the heading and its bottom edge
       ends on the heading's top edge. With margin-bottom the heading was pulled up onto
       the ruler instead, and the whole measurement ran 130px late. Layout is unchanged
       either way, and the observer still trips on the same edge. */
    height:130px;margin-top:-130px;margin-bottom:0;
    view-timeline-name:--pin;
    view-timeline-axis:block;
    view-timeline-inset:var(--nav-h) auto;
  }
  .sec-head          {animation:pin-bar   linear both;animation-timeline:--pin;animation-range:exit 0% exit 100%}
  .sec-head .concept {animation:pin-title linear both;animation-timeline:--pin;animation-range:exit 0% exit 100%;
                      transform-origin:left center}
  .sec-head .sub     {animation:pin-sub   linear both;animation-timeline:--pin;animation-range:exit 0% exit 76%}
  .sec-head .concept-marks{animation:pin-marks linear both;animation-timeline:--pin;animation-range:exit 0% exit 100%}
  /* The class no longer owns size — it would compound with the animation. It keeps
     the shadow and the hairline, which are the parts that should still snap on. */
  /* The class must not shrink anything the timeline is already shrinking. padding,
     margin, max-height and opacity are all animated, so the animation outranks the
     class on those. font-size and transform are NOT animated — scale is — so these
     two leak through and snap the last 45% of the change in a single frame, which is
     the jump you can still feel after everything else glides. Measured off a screen
     recording: the title slid 456px to 350px and then went to 264px in one frame. */
  .sec-head.stuck .concept{font-size:clamp(22px,3.2vw,32px)}
  .sec-head.stuck .concept-marks{transform:none}
}
/* The bar gives back as margin exactly what it loses in padding and subtitle, all
   the way through, so the section is one height at every point in the scroll and
   the page can never shorten under the reader. */
@keyframes pin-bar{
  from{padding-top:18px;padding-bottom:14px;margin-bottom:20px}
  to  {padding-top:9px; padding-bottom:8px; margin-bottom:82px}
}
/* Scale, not font-size. Scale is drawn, font-size is laid out. */
@keyframes pin-title{from{scale:1}      to{scale:.64}}
@keyframes pin-marks{from{scale:1}      to{scale:.82}}
@keyframes pin-sub  {from{opacity:1;max-height:44px;margin-top:3px}
                     to  {opacity:0;max-height:0;   margin-top:0}}

[data-theme="dark"] .sec-head.stuck{box-shadow:0 10px 26px -20px rgba(0,0,0,.75)}

@media(max-width:640px){.sec-head{padding:14px 0 11px}.sec-head.stuck{padding:8px 0 7px}}
/* A heading that eats a third of the screen is worse than no heading. */
@media (prefers-reduced-motion:reduce),(max-height:520px){
  .sec-head{position:static;background:none;border-bottom:none;box-shadow:none;
    backdrop-filter:none;-webkit-backdrop-filter:none;margin-bottom:28px;transition:none}
  .sec-head .sub{max-height:none;opacity:1}
}

/* about & audience switcher */
.audience-wrapper{margin-bottom:34px}
.audience-title{font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--faint);margin-bottom:12px}
.audience-tabs{display:flex;gap:10px;flex-wrap:wrap}
.aud-tab{font-family:var(--mono);font-size:12px;padding:8px 15px;border-radius:20px;border:1px solid var(--pill-line);background:transparent;color:var(--muted);cursor:pointer;transition:all .2s;user-select:none;min-height:36px}
.aud-tab:hover{border-color:var(--accent);color:var(--ink)}
.aud-tab.active{background:var(--accent);color:var(--paper);border-color:var(--accent)}

.about-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:36px;margin-top:8px}
.about-col{background:var(--surface-2);border:1px solid var(--hairline);border-radius:var(--r);padding:24px 26px;transition:transform .2s,box-shadow .2s}
.about-col:hover{box-shadow:var(--shadow-lift)}
.about-col .q{font-family:var(--mono);font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--faint);margin-bottom:12px;display:flex;align-items:center;gap:8px}
.about-col p{font-size:15px;line-height:1.6;color:var(--ink-2);transition:opacity .25s}
.about-col p b{color:var(--ink);font-weight:600}
/* Short declarative line-groups instead of one dense paragraph. */
.about-col p .ln{display:block}
.about-col p .ln + .ln{margin-top:11px}

/* Column icons — drawn inline so there is no library to load, stroked in
   var(--accent) so light and dark need no extra rules. */
.ico{width:18px;height:18px;flex:none;fill:none;stroke:var(--accent);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
@media(max-width:760px){.about-grid{grid-template-columns:1fr;gap:20px}}

/* framing band */
.framing{padding:42px 0}
.framing .wrap{display:grid;grid-template-columns:1fr 1.4fr;gap:36px;align-items:center}
@media(max-width:760px){.framing{padding:32px 0}.framing .wrap{grid-template-columns:1fr;gap:12px}}
.framing h2{font-family:var(--display);font-weight:600;font-size:clamp(20px,2.7vw,26px);letter-spacing:-.02em;line-height:1.18;color:var(--ink)}
.framing p{color:var(--ink-2);font-size:15.5px;line-height:1.6}

@media (hover:none){.card{transform:none!important}}

/* CARD LAYOUT */
.projects{display:flex;flex-direction:column;gap:24px;margin-top:28px}
.card{background:var(--surface-2);border:1px solid var(--hairline);border-radius:var(--r);padding:28px 32px;box-shadow:var(--shadow);transition:box-shadow .25s,border-color .25s;text-decoration:none;color:inherit;position:relative}
/* Hover is depth only — no lift, no tilt. A card that moves under the cursor reads
   as a toy; a card that deepens its shadow reads as a surface you can pick up. The
   shadow carries the whole signal now, so it is pitched stronger than the resting one. */
.card:hover{border-color:var(--hairline-strong);box-shadow:var(--shadow-lift)}
.card.written{border-style:dashed;border-color:var(--hairline-strong);background:var(--surface)}
.card.wip{opacity:.76}

/* CARD MAIN GRID */
.card-main-grid{display:grid;grid-template-columns:1.45fr 1fr;gap:36px;align-items:start}
.card-details-box{border-radius:var(--r-sm);padding:4px;transition:background .2s}

/* METRIC BADGE CALLOUT */
/* Two rows: the figure and what it measures, then the evidence under it. A bare
   "70%" says nothing on its own, and "70% / 7 of 10 returned..." run together on one
   line read as a single sentence that had to be parsed. Naming the unit on the top
   row makes the number mean something before the sample size qualifies it. */
.card-metric-badge{display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:0 7px;background:var(--accent-wash);border:1px solid var(--hairline-strong);border-radius:var(--r-sm);padding:8px 14px;margin-bottom:16px}
.card-metric-unit{font-family:var(--display);font-weight:600;font-size:14px;color:var(--ink);letter-spacing:-.01em}
/* flex-basis forces the evidence onto its own row however short the figure is. */
.card-metric-badge .card-metric-lbl{flex-basis:100%;margin-top:4px}
/* nowrap because the figures are now phrases, not single numbers — "7 of 10" broke
   across two lines and read as two separate stats. The sample size is the point of
   writing them this way, so it has to stay attached to the number. */
.card-metric-num{font-family:var(--display);font-weight:700;font-size:22px;color:var(--accent);line-height:1;white-space:nowrap}
.card-metric-lbl{font-family:var(--mono);font-size:10.5px;text-transform:uppercase;color:var(--ink-2);letter-spacing:.05em}

.card-tags{display:flex;gap:9px;align-items:center;margin-bottom:14px;flex-wrap:wrap}
.tag{font-family:var(--mono);font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);border:1px solid var(--hairline-strong);border-radius:20px;padding:4px 11px}
.tag.live{color:var(--accent);border-color:var(--hairline-strong);background:var(--accent-wash);font-weight:500}
.tag.paper{border-style:dashed}
.card h3{font-family:var(--display);font-weight:600;font-size:23px;letter-spacing:-.02em;margin-bottom:15px;display:flex;align-items:center;gap:12px;line-height:1.15}
/* The heading is a flex row and the arrow rides to its right edge on margin-left:auto.
   That only works while the arrow is a flex child, so the link — not the h3 — has to
   be the row. Putting the arrow inside the anchor also makes it clickable, which
   matters more since the product cards stopped being one big link. */
.card h3 .card-link{display:flex;align-items:center;gap:12px;flex:1;min-width:0}
.card h3 .arr{margin-left:auto;color:var(--faint-deco);font-size:18px;transition:transform .2s,color .2s}
.card:hover h3 .arr{transform:translate(3px,-3px);color:var(--accent)}
/* The four labels carry the structure of every case study, and at 9px in the
   faint grey they measured 3.1:1 — effectively invisible. Now a two-column
   grid so every row's text starts at the same place, with the label in a
   deeper indigo (9.5:1) that reads as a label rather than as body copy. */
.pdeo{display:flex;flex-direction:column;gap:14px}
.pdeo .row{display:grid;grid-template-columns:84px 1fr;gap:14px;align-items:baseline;font-size:14px;line-height:1.55;color:var(--ink-2)}
.pdeo .k{font-family:var(--mono);font-size:10.5px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--pdeo-label)}
.pdeo .row b{color:var(--ink);font-weight:600}
.meta{display:flex;gap:16px;margin-top:18px;padding-top:14px;border-top:1px solid var(--hairline)}
.meta .cell{flex:1;min-width:0}
.meta .mk{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.meta .mv{font-size:12px;color:var(--muted);margin-top:3px;line-height:1.35}

/* LIVE INTERACTIVE MICRO-WIDGETS (AUTHENTIC TO GITHUB REPOS) */
.mini-widget-box{background:var(--paper-alt);border:1px solid var(--hairline-strong);border-radius:var(--r);padding:18px;display:flex;flex-direction:column;justify-content:space-between;min-height:240px;align-self:start;box-shadow:inset 0 1px 3px rgba(0,0,0,.03)}
.widget-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid var(--hairline)}
.widget-title{font-family:var(--mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--accent);font-weight:600}
.widget-status{font-family:var(--mono);font-size:10.5px;padding:2px 7px;border-radius:10px;background:var(--accent-wash);color:var(--accent);font-weight:500}
.widget-controls{display:flex;flex-direction:column;gap:10px}
.widget-label{font-family:var(--mono);font-size:10px;color:var(--ink-2);display:flex;justify-content:space-between}

/* RANGE SLIDER MOBILE TOUCH TARGETS */
.widget-slider{
  width:100%;
  height:8px;
  border-radius:4px;
  accent-color:var(--accent);
  cursor:pointer;
}
.widget-slider::-webkit-slider-thumb{width:22px;height:22px;border-radius:50%;background:var(--accent);cursor:pointer;}
.widget-slider::-moz-range-thumb{width:22px;height:22px;border-radius:50%;background:var(--accent);cursor:pointer;}

.widget-result{background:var(--surface-2);border:1px solid var(--hairline);border-radius:var(--r-sm);padding:12px;margin-top:10px;font-family:var(--mono);font-size:11px;color:var(--ink);line-height:1.5}
/* Traffic-light verdicts. The tier is always spelled out in words beside the
   colour ("Recommended for you"), so colour is never the only signal — but the
   text still has to be readable. Bright green on a pale panel measured 2.08:1,
   the worst contrast on the site. Split by theme: no single green clears both. */
:root{ --v-good:#007400; --v-warn:#A74500; --v-bad:#C61010; }
[data-theme="dark"]{ --v-good:#27C93F; --v-warn:#F0A429; --v-bad:#FF6B6B; }
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){ --v-good:#27C93F; --v-warn:#F0A429; --v-bad:#FF6B6B; }
}
.widget-result .verdict-good{color:var(--v-good);font-weight:600}
.widget-result .verdict-warn{color:var(--v-warn);font-weight:600}
.widget-result .verdict-bad{color:var(--v-bad);font-weight:600}
.widget-btn-row{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}
.fit-row{flex-wrap:nowrap}
.fit-row .fit-btn{flex:1 1 0;min-width:0}
.widget-btn{font-family:var(--mono);font-size:10px;padding:6px 10px;border-radius:6px;border:1px solid var(--hairline-strong);background:var(--surface);color:var(--ink-2);cursor:pointer;transition:all .15s;min-height:36px;display:inline-flex;align-items:center;justify-content:center}
.widget-btn.active,.widget-btn:hover{background:var(--accent);color:var(--paper);border-color:var(--accent)}
/* Retailer name stacked above the garment — all three commerce sites stay visible. */
.fit-btn{flex-direction:column;gap:1px;line-height:1.2;padding:7px 4px;text-align:center;white-space:nowrap}
.fit-btn .fb-r{font-size:10px;letter-spacing:.1em;text-transform:uppercase;opacity:.8}

/* ---- WIDGET FLIP ----
   Two physical slots, spun forward forever. The screenshots lead — a card should
   open on the real product, not on a toy — and the interactive widget sits last,
   so the sequence ends on something to poke at. Each press moves the next panel
   into whichever slot is about to face the viewer and adds another half-turn.

   The obvious approach — one face per screenshot, pre-rotated — cannot work
   past two: with three faces the last press has to unwind 360deg back to the
   start, which reads as the card flipping to itself and then onwards. Rotating
   in one direction and swapping the content is what makes the third press
   behave like the first two. */
/* Case studies have no widget: the argument runs the full width of the card,
   with the four rows in a 2x2 so the line length stays readable. */
/* ---- CARD LINKS ----
   The title carries the real link, and a stretched pseudo-element makes the whole
   details box clickable — so there is one sensible link for a screen reader rather
   than a block-level <a> wrapping a heading and four paragraphs. The repo link
   sits above that overlay on its own z-index so it stays separately clickable. */
.card-details-box{position:relative}
/* A written card's job is to open a question, not answer it, so it ends on a line
   naming what the article resolves and a visible way in. The card is already one
   large link, so .card-cta is painted rather than interactive — a real <a> here
   would nest a link inside a link and give screen readers two targets for one
   destination. Hover is driven from the card, since the click overlay sits on top. */
/* ---- PUBLISHED-STUDY CARD ----
   A card whose article exists is a blog preview, so it is built like one: category,
   headline, standfirst, a line of facts, a way in. The PDEO grid stays on the two
   studies that still have to argue their case inside the card itself. */
.card-dek{margin-top:14px;font-size:15.5px;line-height:1.65;color:var(--ink-2);max-width:58ch}
.card-foot{margin-top:26px;display:flex;flex-direction:column;align-items:flex-start;gap:18px}
.card-facts{font-family:var(--mono);font-size:11px;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}
/* A published study is finished work, so it loses the dashed outline the unwritten
   ones carry — dashed reads as draft, which is the opposite of "go and read this". */
.card.written.with-art{border-style:solid;border-color:var(--hairline)}
.card.written.with-art:hover{border-color:var(--crew-line)}
.tag.solid{border-style:solid}

.card-tease{margin-top:16px;font-size:14px;line-height:1.6;color:var(--muted)}
.card-cta{display:inline-flex;align-items:center;gap:9px;margin-top:18px;padding:11px 18px;min-height:44px;font-family:var(--mono);font-size:12.5px;letter-spacing:.02em;color:var(--paper);background:var(--ink);border-radius:10px;transition:box-shadow .2s}
.card:hover .card-cta{box-shadow:var(--shadow-lift)}
.card-cta .arr{transition:transform .2s}
.card:hover .card-cta .arr{transform:translate(2px,-2px)}
@media (prefers-reduced-motion:reduce){.card-cta,.card-cta .arr{transition:none}}

.card-link{color:inherit;text-decoration:none}
/* The stretched overlay only makes sense where the card has ONE destination — the
   written studies, which also show a visible "Read the case study" button, so the
   click target is announced.

   The product cards have three: the live demo, the source, and a widget you are
   meant to poke at. A full-card link there is an invisible fourth target with no
   affordance, sitting over an interactive demo — the hover pill used to hint at it,
   and with that gone it would just be a surface that navigates away when someone
   meant to drag a slider. Their links are now named in plain text instead. */
/* inset:0 resolves against the nearest positioned ancestor, and .card-details-box
   is one — so this covered the text column and nothing else. The preview art and
   the card's own padding were dead space, which is most of a card's surface on a
   wide screen. Taking the details box out of the positioning flow hands the
   overlay to .card, which is the thing a reader is aiming at. */
.card.written .card-details-box{position:static}
.card.written .card-link::after{content:"";position:absolute;inset:0;z-index:1;
  border-radius:var(--r)}
.card-link:hover{color:var(--accent)}
.card-src{position:relative;z-index:2;margin-top:16px;display:flex;justify-content:space-between;align-items:center;gap:18px;flex-wrap:wrap}
/* These were 11px muted uppercase with a hairline under them — the quietest type on
   the card, carrying the two things the card most wants you to do. They are buttons
   now, in the same shape and size as the hero's and the written cards': live demo
   solid, source outlined, so the pair reads as primary-then-secondary rather than as
   two equal footnotes. 44px min-height doubles as the tap target. */
.card-src a{display:inline-flex;align-items:center;gap:9px;padding:11px 18px;min-height:44px;
  font-family:var(--mono);font-size:12.5px;letter-spacing:.02em;border-radius:10px;
  text-decoration:none;border:1px solid transparent;
  transition:transform .15s,box-shadow .2s,border-color .2s,background .2s,color .2s}
.card-src a.src-live{background:var(--ink);color:var(--paper)}
/* The middle weight. Solid says "use the product", outlined says "read the code",
   and the write-up sits between them in what it offers — so it is drawn between
   them too: filled enough to read as an action, quiet enough not to compete with
   the demo. Accent-wash rather than a new colour, so it stays inside the palette. */
.card-src a.src-read{background:var(--accent-wash);color:var(--accent);border-color:transparent;font-weight:500}
.card-src a.src-read:hover{border-color:var(--accent)}
.card-src a.src-code{background:transparent;color:var(--ink);border-color:var(--hairline-strong)}
.card-src a:hover{box-shadow:var(--shadow-lift)}
.card-src a.src-code:hover{border-color:var(--ink)}

/* Stacked, they go full width and keep their order rather than sitting at opposite
   ends of a narrow card with a gulf between them. */
@media(max-width:520px){
  .card-src{gap:10px}
  .card-src a{flex:1 1 100%;justify-content:center}
}
/* Same pair, moved up under the summary. Most people now arrive on these pages from
   a link rather than from the homepage, and the buttons used to sit below the whole
   Problem/Decision/Evidence/Outcome block — a scroll away from the only two things
   the page wants you to do. Up here they are visible on landing.
   The card version spreads to the edges of a narrow column; across the full width of
   the header that opens a gulf between the two, so these group left instead and take
   their natural width. */
.art-cta{justify-content:flex-start;margin-top:22px}
.art-cta + .card-note{margin-top:10px;max-width:52ch}
/* Written cards run full width by default — there is no widget to sit beside. A card
   whose article has preview art gets the media column back, which is also what stops
   a short card looking like a thin strip next to the shipped-product cards. */
.card.written .card-main-grid{grid-template-columns:minmax(0,1fr)}
/* Centred, not stretched. Now the art is square the two columns are close in height,
   so pinning the button to the bottom of the text column only opened a void between
   the paragraph and the button. Centring balances them instead. */
.card.written.with-art .card-main-grid{grid-template-columns:1.45fr 1fr;align-items:center}
/* Natural aspect rather than object-fit:cover, so nothing is cropped whatever the
   text column does. The art is 1000x1400; at the ~377px this column resolves to,
   that lands around 528px tall and the art sets the card's height. */
.card-art{margin:0;border-radius:var(--r);overflow:hidden;border:1px solid var(--hairline);line-height:0}
.card-art img{display:block;width:100%;height:auto}
@media(max-width:880px){
  /* Stacked, the art leads — a full-width 2:3 image would otherwise dwarf the text,
     and it reads better as the card's opening image than as a footer under the button. */
  .card.written.with-art .card-main-grid{grid-template-columns:1fr}
  /* width + justify-self, not `margin:0 auto`. Auto margins cancel a grid item's
     stretch, which left the figure shrink-to-fit — 4px wide until the image loaded,
     and 4px forever if it ever failed. An explicit width keeps the aspect-ratio box
     reserving its space from first paint. */
  .card-art{order:-1;width:min(300px,100%);justify-self:center;margin:0 0 4px}
}

/* ============================================================================
   WORK GRID — the homepage index (tier 1)
   The homepage used to expand every project inline: four PROBLEM/DECISION rows,
   a screenshot flipper, a live widget and a spec drawer, three times over. That
   is 470 of 754 lines before a reader has decided what they care about. These
   cards carry a picture, a line and a number, and the depth moved to /work/.
   .projects above is a one-column stack and cannot do this, so the grid is new;
   everything inside a card reuses the components already defined above. */
.work-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:22px;margin-top:28px}
/* Column, not the 1.45fr/1fr split .card-main-grid uses — at a third of the wrap
   width there is no room for two columns, so the art stacks over the text. */
.work-card{display:flex;flex-direction:column;padding:20px}
.work-card .card-art{aspect-ratio:16/10;margin-bottom:16px}
/* cover, unlike the full-width .card-art above — a fixed box keeps the grid rows
   level. Anchored to the top, not the centre: every one of these images is built by
   tools/og-card.py with its mono kicker on the first line, and a centre crop of a
   1:1 source into a 16:10 box would cut exactly that line off. */
.work-card .card-art img{height:100%;object-fit:cover;object-position:center top}
/* The whole card is the link here. On the old homepage cards this was deliberately
   avoided — they carried three destinations and a slider you were meant to drag, so
   a full-card overlay was an invisible fourth target sitting on top of a demo. A
   grid card has exactly one destination and nothing to poke, which is the condition
   that overlay was always waiting for. */
.work-card .card-link::after{content:"";position:absolute;inset:0;z-index:1;border-radius:var(--r)}
/* Block, not the flex row .card h3 uses. That row pins the arrow to the card's right
   edge with margin-left:auto, which reads fine beside a one-line heading and reads as
   a stray glyph beside a heading that wraps — "Amazon Discovery Intelligence" takes
   two lines in a third of the wrap. Inline, the arrow stays attached to the last word. */
.work-card h3{display:block;font-size:19px;margin-bottom:0}
.work-card h3 .card-link{display:inline;flex:none}
.work-card h3 .arr{margin-left:5px;font-size:15px}
/* The arrow is inline now, so on a title that fills the line it wrapped alone onto
   the next one and read as a stray glyph. Binding it to the final word means the
   pair moves together: "Amazon Discovery / Intelligence 97", never an orphan. */
.nb{white-space:nowrap}
.work-card .card-dek{margin-top:10px;font-size:14.5px;line-height:1.6;max-width:none}
/* auto pushes the foot down so the metric line sits on the same baseline in every
   card of a row, however long the dek above it runs. */
.work-card .card-foot{margin-top:auto;padding-top:18px;gap:0}
.work-card .card-metric-badge{margin-bottom:0;padding:5px 11px}
.work-card .card-metric-num{font-size:17px}
.work-card .card-tags{margin-bottom:10px}
/* The study cards keep their solid border in the grid too — dashed reads as draft. */
.work-card.written{border-style:solid}
@media(max-width:880px){
  /* Undoes the stacked-card override above, which assumed the wide two-column
     layout. In the grid the art is already on top and already full width. */
  .work-card .card-art{order:0;width:auto;justify-self:auto;margin:0 0 16px}
}

/* ============================================================================
   PROJECT PAGE (tier 2) — /work/<slug>/
   Reuses the article header (.art-head, .art-title, .art-meta) and the card
   interior (.card-main-grid, .pdeo, .widget-flip, .card-src) wholesale. Only the
   spacing between those borrowed blocks is new, plus headings for the two
   sections that used to be tabs inside a folded drawer. */
.wk-main{padding:34px 0 44px}
.wk-sec{padding:44px 0}
.wk-h2{font-family:var(--display);font-weight:600;font-size:clamp(21px,2.3vw,28px);line-height:1.2;letter-spacing:-.02em;color:var(--ink);margin:0 0 20px}
/* Paragraphs, not bullets. The rejections read as short arguments — a claim and
   then the reasoning that earned it — and the article pages already set that
   pattern: a bold noun phrase naming the thing, then plain prose. A bulleted list
   would make six arguments look like a feature comparison. */
.wk-prose{max-width:72ch}
.wk-prose p{font-size:16px;line-height:1.72;color:var(--ink-2);margin:0 0 20px}
.wk-prose p:last-child{margin-bottom:0}
.wk-prose strong{color:var(--ink);font-weight:600}
.wk-prose em{color:var(--ink-2)}
/* REJECTION TEASER — the hook, directly under the header's meta strip.
   The reasoning that shows judgment sits mid-page, which a skimmer never reaches.
   Three of the six as bare noun phrases, on one line, buy that section a reader. */
.wk-teaser{display:flex;gap:16px;align-items:baseline;flex-wrap:wrap;
  border-bottom:1px solid var(--hairline);padding:14px 0 16px}
.wk-teaser-k{font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.1em;
  text-transform:uppercase;color:var(--pdeo-label);flex:none}
.wk-teaser p{margin:0;font-size:14px;line-height:1.6;color:var(--muted)}
/* Each phrase keeps its own full stop, so the line reads as a list of refusals
   rather than one sentence that happens to contain commas. */
.wk-teaser p span{color:var(--ink-2)}
.wk-teaser a{color:var(--accent);text-decoration:none;border-bottom:1px solid var(--pill-line);
  white-space:nowrap;margin-left:4px}
.wk-teaser a:hover{border-color:var(--accent)}
@media(max-width:640px){.wk-teaser{gap:8px}.wk-teaser p{font-size:13.5px}}

/* SPEC ROWS — the conclusion, then the breakdown
   Arithmetic does not need paragraphs around it. The lead states the answer and the
   rows carry the workings, so a reader can take the number and leave, or stay for
   the reasoning. Reuses .pdeo, with a wider label column: "The real work" does not
   fit the 84px that "PROBLEM" was sized for. */
.wk-lead{font-size:17px;line-height:1.6;color:var(--ink);margin:0 0 26px;max-width:60ch}
/* Capped to a reading measure. Without it the value column runs the full 1180px
   wrap and a two-line row becomes one 900px line nobody tracks to the end of. */
.pdeo.spec{gap:16px;max-width:680px}
.pdeo.spec .row{grid-template-columns:132px 1fr;font-size:14.5px}
.pdeo.spec .k{text-transform:uppercase;color:var(--pdeo-label);line-height:1.5}
.pdeo.spec .v b{color:var(--ink);font-weight:600}
@media(max-width:640px){
  /* Stacked. At 390px a 118px label column leaves the value about 40 characters,
     which breaks every sentence across four lines. */
  .pdeo.spec .row{grid-template-columns:1fr;gap:4px}
}

/* PROSE + DIAGRAM SPLIT
   The reading measure only uses 72ch of an 1180px wrap, so the right third of these
   sections was empty. A diagram belongs there: it is read alongside the argument
   rather than after it, and it costs no vertical space. */
.wk-split{display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:0 48px;align-items:start}
@media(max-width:980px){
  /* Under the text, not beside it. A 320px column of numbers squeezed next to a
     narrowed paragraph is worse than either one alone. */
  .wk-split{grid-template-columns:1fr;gap:32px 0}
  .cost-fig{max-width:420px}
}

/* SIDE FIGURES — a screenshot in the right column rather than under the rows
   The rows cap at 680px, so a full-width image below them left a band of empty
   page beside the text and pushed everything down. Beside the argument it costs
   no vertical space and is read against the point it illustrates. It keeps
   .fig-body so the lightbox still opens it at full size, which matters more here
   than usual: at 320px a screenshot is a thumbnail, not a readable image. */
/* Money inside a body row. The figures are the argument in the economics section —
   a third of a rupee per verdict, ₹3.6 a user a month — and in plain bold they read
   as emphasis rather than as the number to take away. */
.amt{color:var(--accent);font-weight:600;font-variant-numeric:tabular-nums}
/* A named company inside a body row. Deliberately not the accent colour: accent is
   the link colour on this site, and a coloured company name reads as clickable. */
.co{font-weight:600;color:var(--ink)}
.wk-side-fig{margin:6px 0 0}
.wk-side-fig img{display:block;width:100%;height:auto;border-radius:10px;border:1px solid var(--hairline)}
.wk-side-fig figcaption{font-size:12px;line-height:1.5;color:var(--muted);margin-top:12px}
@media(max-width:980px){
  /* Centred and capped once it drops below the text, so a portrait phone shot does
     not become a full-width tower. */
  .wk-side-fig{max-width:330px;margin:0 auto}
}

/* COST DIAGRAM
   Inline SVG so it takes its colours from the page and switches with the theme —
   the same reason the article diagrams are inline. No fills of its own beyond the
   accent washes, so it sits on whichever band it lands in. */
.cost-fig{margin:6px 0 0}
.cost-fig svg{display:block;width:100%;height:auto;overflow:visible}
.cost-fig figcaption{font-size:12px;line-height:1.5;color:var(--muted);margin-top:14px}
.cd-k{font-family:var(--mono);font-size:9.5px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;fill:var(--pdeo-label)}
.cd-v{font-family:var(--mono);font-size:10px;fill:var(--ink-2)}
.cd-lg{font-family:var(--body);font-size:11.5px;fill:var(--ink-2)}
.cd-lv{font-family:var(--mono);font-size:10px;fill:var(--muted)}
.cd-note{font-family:var(--body);font-size:10.5px;font-style:italic;fill:var(--muted)}
.cd-sum{font-family:var(--mono);font-size:13px;font-weight:500;fill:var(--ink)}
.cd-big{font-family:var(--display);font-size:23px;font-weight:700;fill:var(--accent)}
/* Decoration, not text: the flow is also stated in the <desc>. Held to the 3:1
   non-text bar (WCAG 1.4.11) rather than the 4.5 text bar. */
.cd-arrow{font-size:14px;fill:var(--faint-deco)}
.cd-rule{stroke:var(--hairline);stroke-width:1}
.cd-panel{fill:var(--accent-wash);stroke:var(--hairline-strong);stroke-width:1}
.cd-panel-strong{stroke:var(--accent)}
/* Three weights of the accent, so the 61% block reads as the dominant share without
   introducing a colour the palette does not already have. */
.cd-fill-a{fill:var(--accent)}
.cd-fill-b{fill:var(--accent);opacity:.45}
.cd-fill-c{fill:var(--accent);opacity:.2}
.cd-fill-d{fill:var(--accent);opacity:.62}
.cd-warn{fill:var(--crew-brand)}
/* The ease band is drawn as an outline, not a fill: it is the space the garment
   leaves around the body, so showing it as empty is the honest picture. */
.cd-gap{fill:none;stroke:var(--accent);stroke-width:1;stroke-dasharray:2 2}
/* Timeline dots. The skipped moment is drawn hollow and struck through rather than
   simply left out — the point is that it is the one everyone else aims at. */
.cd-dot-on{fill:var(--accent)}
.cd-dot-off{fill:var(--paper);stroke:var(--crew-line);stroke-width:1.5}
.cd-strike{stroke:var(--crew-brand);stroke-width:1.5;stroke-linecap:round}
/* Pipeline steps. Ten quiet rows and three accented ones, so "3 of 13" is countable
   at a glance instead of asserted in a badge. */
.cd-step{fill:var(--surface-2);stroke:var(--hairline);stroke-width:1}
.cd-step-ai{fill:var(--accent-wash);stroke:var(--accent);stroke-width:1}
.cd-num{font-family:var(--mono);font-size:8.5px;fill:var(--faint)}
.cd-num-ai{font-family:var(--mono);font-size:8.5px;font-weight:700;fill:var(--accent)}
.cd-step-t{font-family:var(--body);font-size:10.5px;fill:var(--ink-2)}
.cd-step-ai-t{font-family:var(--mono);font-size:8.5px;font-weight:700;letter-spacing:.08em;fill:var(--accent)}

.wk-deeper{padding:48px 0 60px}
/* The go-deeper cards carry no art, so the arrow is the only thing marking them as
   a way onward — it points down for a jump within the page, out for a new one. */
.wk-deeper .work-card h3{margin-bottom:0}
@media(max-width:640px){.wk-main{padding:24px 0 32px}.wk-sec{padding:32px 0}.wk-deeper{padding:34px 0 44px}}

.card.written .pdeo{display:grid;grid-template-columns:1fr 1fr;gap:18px 44px}
/* A card whose article is live carries one lead paragraph rather than a 2x2 of rows,
   so it runs the full width instead of sitting in the left-hand column. */
.card.written .pdeo.lead{grid-template-columns:1fr}
@media (max-width:900px){.card.written .pdeo{grid-template-columns:1fr}}
.widget-flip{perspective:1600px;align-self:start}
.widget-flip-inner{position:relative;transform-style:preserve-3d;
  transform:rotateY(calc(var(--turn,0) * 180deg));
  transition:transform .55s cubic-bezier(.2,.7,.2,1), height .55s cubic-bezier(.2,.7,.2,1)}
.flip-slot{position:absolute;top:0;left:0;right:0;
  backface-visibility:hidden;-webkit-backface-visibility:hidden}
.flip-slot.in-flow{position:relative}      /* the visible slot sets the height */
.flip-slot-b{transform:rotateY(180deg)}
.flip-pool{display:none}
.flip-panel{justify-content:flex-start}
.shot-face{padding:0;overflow:hidden}
.widget-shot{width:100%;height:auto;display:block;border-radius:var(--r) var(--r) 0 0}
/* order:-1 rather than reordering the markup in nine places. The bar now sits where
   .widget-head does, so the button stays put across all three faces. */
.shot-bar{order:-1;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 11px;border-bottom:1px solid var(--hairline-strong);background:var(--surface-2)}
.shot-cap{font-family:var(--mono);font-size:11px;line-height:1.4;color:var(--muted);margin:0}
.savio-why{margin-top:8px;font-size:11px;line-height:1.5;color:var(--muted)}
.flip-btn{font-family:var(--mono);font-size:11px;letter-spacing:.07em;text-transform:uppercase;padding:4px 9px;border-radius:20px;border:1px solid var(--hairline-strong);background:var(--surface);color:var(--accent);cursor:pointer;white-space:nowrap;font-weight:600}
.flip-btn:hover{background:var(--accent);color:var(--paper);border-color:var(--accent)}
@media (prefers-reduced-motion:reduce){.widget-flip-inner{transition:none}}

/* FULL-WIDTH DRAWER ROW */
.card-drawer-row{grid-column:1 / -1;margin-top:16px;padding-top:12px;border-top:1px solid var(--hairline)}
.btn-spec-toggle{font-family:var(--mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--accent);background:transparent;border:1px solid var(--hairline-strong);border-radius:var(--r-sm);padding:8px 14px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:all .2s;width:fit-content;min-height:38px}
.btn-spec-toggle:hover{background:var(--accent-wash);border-color:var(--accent)}
.btn-spec-toggle .chevron{transition:transform .2s}
.btn-spec-toggle.open .chevron{transform:rotate(180deg)}
.spec-drawer{display:none;margin-top:16px;padding:20px 22px;background:var(--surface);border:1px solid var(--hairline-strong);border-radius:var(--r-sm)}
.spec-drawer.open{display:block;animation:fadeIn .25s ease-out}
.spec-tabs{display:flex;gap:18px;border-bottom:1px solid var(--hairline);padding-bottom:8px;margin-bottom:14px}
.spec-tab-btn{font-family:var(--mono);font-size:11px;text-transform:uppercase;color:var(--muted);background:none;border:none;cursor:pointer;padding:6px 0;border-bottom:2px solid transparent;transition:all .2s;min-height:32px}
.spec-tab-btn.active{color:var(--accent);border-bottom-color:var(--accent);font-weight:500}
.spec-tab-content{font-size:14px;line-height:1.58;color:var(--ink-2);display:none}
.spec-tab-content.active{display:block}
/* Inline code — replaces literal backticks, which don't render as formatting in HTML */
code{font-family:var(--mono);font-size:.86em;background:var(--accent-wash);border:1px solid var(--hairline);border-radius:4px;padding:1px 5px;color:var(--ink);word-break:break-word}
@keyframes fadeIn{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}

/* PM TOOLBOX GRID */
.toolbox-section{margin-top:48px;background:var(--surface-2);border:1px solid var(--hairline);border-radius:var(--r);padding:28px 32px}
/* WHAT I WORK WITH
   Was a grid of bordered, rounded, padded chips inside Product Thinking. Two
   problems: it read as a footnote to the three studies rather than a statement
   about the work as a whole, and chips shaped like that are buttons — on a site
   where everything else that looks pressable is pressable, twelve dead controls
   is the one place the styling lies. Now plain text in three labelled columns,
   separated by hairlines rather than boxes. */
.toolbox-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:28px 40px;margin-top:26px}
.toolbox-card{padding:16px 0 0;border-top:1px solid var(--hairline)}
.toolbox-title{font-family:var(--mono);font-size:10.5px;text-transform:uppercase;letter-spacing:.1em;color:var(--pdeo-label);margin-bottom:10px;font-weight:700}
/* A real list, so it reads as one item per line rather than a run-on sentence
   broken up with punctuation. 15px, not the 10.5px the chips used — they were
   small because a chip has to be; as text it can sit at a readable size. */
/* Markers drawn as a grid column rather than by list-style, so the bullet starts
   exactly on the section's left edge instead of hanging outside it. padding-left
   plus an outside marker put the dot ~11px left of the heading and hairline above
   it, which is what made the column look misaligned. The 1fr text column also
   keeps a wrapped item lined up under its own text, not under its bullet. */
.tool-list{margin:0;padding:0;list-style:none;font-size:15px;line-height:1.55;color:var(--ink-2)}
.tool-list li{display:grid;grid-template-columns:15px minmax(0,1fr);margin:0 0 7px}
.tool-list li:last-child{margin-bottom:0}
.tool-list li::before{content:"•";color:var(--muted)}
@media(max-width:640px){.toolbox-grid{gap:22px 0}}

/* experience */
/* Rail + body. The measure that keeps the prose readable is now enforced by the
   grid column rather than by a max-width sitting inside a much wider box, so the
   card has no unreachable half. */
.exp{background:var(--surface-2);border:1px solid var(--hairline);border-radius:var(--r);padding:30px 32px;box-shadow:var(--shadow);margin-top:26px;
  display:grid;grid-template-columns:210px minmax(0,1fr);gap:14px 40px;align-items:start}
/* The date leads the rail. It is the one fact a reader scans a job history for,
   and in the corner of a wide card it was the thing marooned furthest from
   everything else. */
.exp-when{font-family:var(--mono);font-size:12px;color:var(--faint)}
.exp-role{font-family:var(--display);font-weight:600;font-size:19px;letter-spacing:-.01em;line-height:1.2;margin:7px 0 8px}
.exp-org{font-family:var(--mono);font-size:12.5px;line-height:1.5;color:var(--accent)}
.exp p{color:var(--muted);font-size:15px;line-height:1.62}
.exp p b{color:var(--ink-2);font-weight:600}
/* The lead line carries how the role came about; the list carries what it was.
   No bold lead-ins inside the items — a bolded stub followed by an explanation
   is a formatting tell, and these lines are short enough not to need one. */
.exp-lead{margin-bottom:20px;max-width:62ch}
/* Two columns. The four lines are short, parallel claims about the same role, so
   read side by side they scan as a set; stacked in a single column inside a wide
   card they read as a paragraph broken up with dots. display:list-item keeps the
   markers, which grid would otherwise be free to drop. */
.exp-list{margin:0;padding:0;list-style:none;
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:11px 34px}
/* Same fix as .tool-list: the bullet was hanging left of the lead paragraph it
   sits under. A marker column lines the first one up with the text above it. */
.exp-list li{display:grid;grid-template-columns:15px minmax(0,1fr);
  color:var(--muted);font-size:15px;line-height:1.62;margin:0}
.exp-list li::before{content:"•";color:var(--hairline-strong)}
/* 880 is where the card grids collapse; the rail goes with them so the section
   doesn't keep a two-column header over one-column content. */
@media(max-width:880px){
  .exp{grid-template-columns:1fr;gap:18px}
  .exp-list{grid-template-columns:1fr;gap:9px}
}
@media(max-width:880px){.card-main-grid{grid-template-columns:1fr;gap:22px}.mini-widget-box{width:100%}.toolbox-grid{grid-template-columns:1fr}}

/* contact */
.contact{text-align:center}
.contact h2{font-family:var(--display);font-weight:500;font-size:clamp(28px,4.4vw,44px);letter-spacing:-.025em;margin:16px 0 16px;line-height:1.1}
.contact p{color:var(--muted);max-width:48ch;margin:0 auto 24px}
.contact-links{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}

footer{border-top:1px solid var(--hairline);padding:26px 0}
.foot-in{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px}
.foot-in span{font-family:var(--mono);font-size:12px;color:var(--faint)}

/* REVEAL ON SCROLL (FAST 0.4s HARDWARE ACCELERATED TRANSITION) */
.reveal{opacity:0;translate:0 12px;will-change:opacity,translate;transition:opacity .45s cubic-bezier(.2,.7,.2,1),translate .45s cubic-bezier(.2,.7,.2,1)}
.reveal.in{opacity:1;translate:0 0;will-change:auto}

@media (max-width: 640px) {
  .wrap { padding: 0 16px; }
  .nav-in { padding: 10px 16px 8px; }
  :root { --logo-h: 34px; }

  /* height:auto, not a fixed 100svh. On a phone the headline runs to five or
     six lines and the two buttons stack full-width, so the content can exceed
     the fold — with a fixed height it overflowed and the marquee landed on top
     of the Resume button. Now the header grows instead; the marquee slipping
     below the fold on a small phone is the right trade against overlap. */
  header.elevator { height:auto; min-height:calc(100svh - var(--nav-h)); padding: 0 0 clamp(14px,3vh,30px); }
  h1.hero { font-size: 28px; line-height: 1.09; max-width:none; }
  .hero-eyebrow { margin-bottom: 18px; gap: 8px; }
  .eyebrow { font-size: 11.5px; letter-spacing: .1em; }
  .hero-sub { font-size: 16px; margin-top: 18px; max-width:none; }
  .hero-actions { margin-top: 24px; gap: 10px; }
  .hero-actions .btn { padding: 13px 20px; }
  .hero-actions { flex-direction: column; width: 100%; }
  .hero-actions .btn { width: 100%; justify-content: center; }
  
  .audience-wrapper { margin-bottom: 24px; }
  .audience-tabs {
    display: flex;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 4px;
    scrollbar-width: none;
  }
  .audience-tabs::-webkit-scrollbar { display: none; }
  .aud-tab { flex-shrink: 0; white-space: nowrap; font-size: 11px; padding: 6px 12px; }
  
  .card { padding: 20px 16px; border-radius: 12px; }
  .card-main-grid { gap: 18px; }
  .card h3 { font-size: 20px; }
  .pdeo .row{grid-template-columns:1fr;gap:5px}
  .card-metric-badge { width: 100%; min-width: 0; flex-direction: column; align-items: flex-start; gap: 4px; padding: 8px 12px; }
  /* The label carries flex-basis:100% so it drops onto its own line in the row
     layout. Once the badge turns into a column that basis becomes a HEIGHT, the
     label falls back to sizing on its own text, and a sentence that never wraps
     pushed the badge — and the card with it — 75px wider than the phone. Stretch
     it across the column instead and let the words wrap. */
  .card-metric-badge .card-metric-lbl { flex: 0 0 auto; align-self: stretch; margin-top: 2px; }
  .card-metric-num { font-size: 20px; }
  .meta { flex-wrap: wrap; gap: 12px; }
  .meta .cell { min-width: 45%; }
  
  .mini-widget-box { padding: 14px; min-height: auto; border-radius: 12px; }
  .widget-btn-row { width: 100%; gap: 4px; }
  .widget-btn { flex: 1; text-align: center; padding: 7px 4px; font-size: 9.5px; }
  
  .spec-drawer { padding: 16px 14px; }
  .spec-tabs { gap: 12px; }
  .spec-tab-btn { font-size: 10px; }

  .toolbox-section { padding: 20px 16px; margin-top: 32px; }
  .exp { padding: 20px 16px; }
  
  .contact-links { flex-direction: column; width: 100%; }
  .contact-links .btn { width: 100%; justify-content: center; }

  .preloader-content { padding: 32px 20px; }
  .preloader-tag { font-size: 22px; }
}

/* ============================================================
   ARTICLE PAGES  (/writing/<name>/)
   Long-form case studies. Body text sits on a reading measure;
   wide figures break out past it, which is how the reference
   handles images — narrower text, wider evidence.
   ============================================================ */
.art-wrap{max-width:1180px;margin:0 auto;padding:0 28px}
.art-head{padding:34px 0 0}
.art-back{display:inline-flex;align-items:center;gap:7px;font-family:var(--mono);font-size:11.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);text-decoration:none;margin-bottom:22px;transition:color .2s}
.art-back:hover{color:var(--accent)}
/* Case-study badge — sits beside the title on its first baseline. Solid fill on
   purpose: every other element in an article header is paper and a hairline, so
   only a filled block reads as a category rather than as more metadata. */
.art-titlerow{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
.art-badge{flex:none;font-family:var(--mono);font-size:10px;font-weight:700;
  letter-spacing:.14em;text-transform:uppercase;background:var(--accent);color:var(--paper);
  padding:6px 11px 5px;border-radius:5px;white-space:nowrap;position:relative;top:-2px}
.art-badge::before{content:"";display:inline-block;width:5px;height:5px;border-radius:50%;
  background:var(--paper);opacity:.7;margin-right:7px;vertical-align:middle;position:relative;top:-1px}
/* Card type marker. Same solid block as the article badge, sized for a card —
   it is the one thing on a card that says what kind of thing this is, so it
   leads the tag row and does not share the brand colour with the topic tag. */
.tag-type{font-family:var(--mono);font-size:9.5px;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;background:var(--accent);color:var(--paper);
  padding:5px 9px 4px;border-radius:4px;line-height:1;white-space:nowrap}
h1.art-title{font-family:var(--display);font-weight:500;font-size:clamp(28px,3.9vw,46px);line-height:1.08;letter-spacing:-.03em;max-width:none;margin:0}
.art-sub{margin-top:18px;font-size:clamp(16px,1.5vw,19px);line-height:1.55;color:var(--muted);max-width:52ch}
/* The meta strip sits under the hero, not above it, so the reader meets the piece
   before its paperwork. Values are kept to a single line each — a Method cell that
   wraps to three lines turns a footnote into a block that competes with the title. */
.art-meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:12px 28px;border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline);padding:14px 0;margin:30px 0 0}
.am-k{font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--pdeo-label);margin-bottom:5px}
.am-v{font-size:14px;line-height:1.5;color:var(--ink-2)}
/* The argument in four parts, carried over from the card. Two columns, because the
   header runs the full 1180px rather than the body's reading measure — stacked, the
   same text would push the article itself off the first screen. */
.art-summary{display:grid;grid-template-columns:repeat(2,1fr);gap:24px 44px;border-bottom:1px solid var(--hairline);padding:24px 0 28px}
.as-k{font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--pdeo-label);margin-bottom:6px}
.as-v{font-size:14.5px;line-height:1.6;color:var(--ink-2)}
@media(max-width:760px){.art-summary{grid-template-columns:1fr;gap:20px;padding:20px 0 24px}}

.art-body{max-width:80ch;margin:0 auto;padding:26px 0 72px}
.art-body h2{font-family:var(--display);font-weight:600;font-size:clamp(21px,2.3vw,29px);line-height:1.2;letter-spacing:-.02em;color:var(--ink);margin:52px 0 18px}
.art-body h2:first-child{margin-top:36px}
/* Sub-heads inside a section. Deliberately a long way below h2 in weight — they
   mark the four worked examples, not new arguments. */
.art-body h3{font-family:var(--display);font-weight:600;font-size:clamp(16.5px,1.6vw,19px);line-height:1.34;letter-spacing:-.01em;color:var(--ink);margin:40px 0 14px}
/* Inlined diagrams. They carry no background of their own, so they sit on the page
   and take their colours from it. */
.fig-diagram{margin:40px 0}
.fig-diagram svg{display:block;width:100%;height:auto}
.art-body p{font-size:17px;line-height:1.72;color:var(--ink-2);margin:0 0 21px}
.art-body ul,.art-body ol{margin:0 0 21px;padding-left:22px}
.art-body li{font-size:17px;line-height:1.72;color:var(--ink-2);margin:0 0 12px;padding-left:4px}
.art-body li::marker{color:var(--muted)}
.art-body li strong{color:var(--ink);font-weight:600}
.art-body a{color:var(--accent);text-decoration:none;border-bottom:1px solid var(--pill-line);padding-bottom:1px}
.art-body a:hover{border-color:var(--accent)}
.art-body strong{color:var(--ink);font-weight:600}
blockquote.pull{font-family:var(--display);font-weight:500;font-size:clamp(21px,2.5vw,30px);line-height:1.28;letter-spacing:-.02em;color:var(--ink);border-left:3px solid var(--accent);padding:2px 0 2px 24px;margin:40px 0}
figure{margin:40px 0}
figure img{display:block;width:100%;height:auto;border-radius:10px;border:1px solid var(--hairline)}
figcaption{font-size:13px;line-height:1.55;color:var(--muted);margin-top:11px}
/* Phone screenshots are portrait; at full measure they would be over a thousand
   pixels tall, so they sit centred at a limited width. */
.fig-phone{max-width:330px;margin:40px auto}
/* A phone bezel, for a screenshot that is a whole screen rather than a crop. The
   frame is what makes it read as a device instead of a rectangle floating on the
   page — and it is the same construction the preview cards use, so the article and
   the picture advertising it agree.

   Wrap the <img> only, never the <figure>, or the caption ends up inside the bezel.
   Use it only on a full screen capture, status bar and all: a cropped screen inside
   a phone frame reads as a mistake, which is why CREW's hero stays a plain rounded
   rectangle. */
.device{border-radius:34px;overflow:hidden;background:#0F0D11;border:7px solid #17141A;
  box-shadow:0 3px 8px rgba(0,0,0,.30),0 26px 60px rgba(0,0,0,.26)}
.device img{display:block;width:100%;height:auto;border:0;border-radius:0}
/* The desktop equivalent. Not everything worth showing was captured on a phone, and
   a browser screenshot squeezed into the narrow portrait hero slot is unreadable —
   it needs the width, and a window frame rather than a bezel. */
.browser{border-radius:12px;overflow:hidden;background:#17141A;
  box-shadow:0 3px 8px rgba(0,0,0,.26),0 26px 60px rgba(0,0,0,.22)}
.browser::before{content:"";display:block;height:30px;background:#17141A;
  background-image:radial-gradient(circle,#4A4550 4.5px,transparent 5px),
    radial-gradient(circle,#4A4550 4.5px,transparent 5px),
    radial-gradient(circle,#4A4550 4.5px,transparent 5px);
  background-position:16px 50%,36px 50%,56px 50%;background-repeat:no-repeat}
.browser img{display:block;width:100%;height:auto;border:0;border-radius:0}
/* A landscape hero. The portrait slot caps at 330px, which is right for a phone and
   useless for a browser window. */
.art-hero-wide{max-width:860px;margin:28px auto 0}

/* ---- EMBEDDED DECK ----
   A Google Slides deck published to the web, shown in place so the reader never
   leaves the page. Padding-bottom rather than a height: it holds a 16:9 box at any
   width, which is what keeps the slides from being letterboxed on a phone.

   The frame is served by Google, so it can fail in ways this page cannot detect —
   unpublish the deck and the box goes blank with no error. Hence the plain link
   underneath, which always works. Never remove it. */
.deck{margin:34px 0 0;border-radius:12px;overflow:hidden;border:1px solid var(--hairline-strong);
  background:var(--surface);position:relative;padding-bottom:61.8%;height:0}
.deck iframe{position:absolute;inset:0;width:100%;height:100%;border:0;display:block}
.deck-note{margin:14px 0 0;font-family:var(--mono);font-size:11.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted)}
.deck-note a{color:var(--accent);text-decoration:none;border-bottom:1px solid var(--pill-line)}
.deck-note a:hover{border-color:var(--accent)}
/* A landscape figure held to the reading measure rather than breaking past it. For
   screenshots that read fine at 745px, where fig-wide would spend a third more
   vertical space to say the same thing. */
.fig-body{margin:34px 0}
/* Review cards are landscape and dense, so they break out past the measure. */
.fig-wide{width:min(1124px,100vw - 56px);margin-left:50%;transform:translateX(-50%)}
.table-scroll{overflow-x:auto;margin:34px 0;width:min(1124px,100vw - 56px);margin-left:50%;transform:translateX(-50%)}
.art-body table{border-collapse:collapse;width:100%;font-size:14.5px}
.art-body th{font-family:var(--mono);font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--pdeo-label);text-align:left;padding:0 18px 10px 0;border-bottom:1px solid var(--hairline-strong);white-space:nowrap}
.art-body td{padding:14px 18px 14px 0;border-bottom:1px solid var(--hairline);color:var(--ink-2);line-height:1.6;vertical-align:top}
.art-body tr:last-child td{border-bottom:none}
@media(max-width:900px){
  .fig-wide,.table-scroll{width:auto;margin-left:0;transform:none}
}
@media(max-width:640px){
  .art-wrap{padding:0 16px}
  .art-head{padding:22px 0 0}
  .art-body{max-width:none;padding-bottom:48px}
  .art-body p{font-size:16px}
  .fig-phone{max-width:100%}
}
/* The hero is the largest single image on the page — it should not read as smaller
   than the evidence screenshots further down. Height is bought back by cropping the
   source rather than by shrinking the display width: trim the empty screen above and
   below the subject, never scale the whole thing down. */
.art-hero{max-width:330px;margin:26px auto 0}

/* ---- WRITING INDEX (/writing/) ----
   The list of long-form pieces. Entries that exist are links; entries still being
   written are listed but inert, so the page never promises a click it can't honour. */
.wr-list{list-style:none;margin:8px 0 0;padding:0 0 72px;max-width:820px}
.wr-item{border-top:1px solid var(--hairline)}
.wr-item:last-child{border-bottom:1px solid var(--hairline)}
.wr-in{display:block;padding:30px 0;text-decoration:none;color:inherit;transition:opacity .2s}
a.wr-in:hover .wr-title{color:var(--accent)}
.wr-meta{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:11px}
.wr-kicker{font-family:var(--mono);font-size:10.5px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--pdeo-label)}
.wr-date{font-family:var(--mono);font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--faint)}
.wr-flag{font-family:var(--mono);font-size:10px;letter-spacing:.09em;text-transform:uppercase;color:var(--muted);border:1px solid var(--pill-line);border-radius:20px;padding:3px 10px}
.wr-title{font-family:var(--display);font-weight:500;font-size:clamp(20px,2.4vw,28px);line-height:1.18;letter-spacing:-.02em;color:var(--ink);margin:0 0 9px;transition:color .2s}
.wr-dek{font-size:15.5px;line-height:1.62;color:var(--ink-2);margin:0;max-width:62ch}
.wr-item.pending .wr-title,.wr-item.pending .wr-dek{color:var(--muted)}
@media(max-width:640px){
  .wr-in{padding:22px 0}
  .wr-dek{font-size:14.5px}
}

/* A run of phone screenshots sits in one row rather than stacked. Three portrait
   shots in sequence cost ~1500px of height and read as one idea anyway, so the
   column carries them side by side with their own captions. The images must be
   cut to a common aspect ratio before they go in here — mismatched heights leave
   the captions on three different lines and the row reads as an accident. */
.fig-row.two{grid-template-columns:repeat(2,1fr)}
.fig-row{display:grid;grid-template-columns:repeat(3,1fr);gap:24px;align-items:start;
  margin:44px 0;width:min(1124px,100vw - 56px);margin-left:50%;transform:translateX(-50%)}
.fig-row figure{margin:0}
.fig-row figcaption{font-size:12.5px;margin-top:9px}
@media(max-width:760px){
  .fig-row{grid-template-columns:1fr;gap:32px;width:auto;margin-left:0;transform:none}
}

/* ---- READING PROGRESS ----
   CREW runs to twenty-one phone screens, YouTube to fifteen. A reader that deep
   in has no way to judge how much is left, and the honest fix is orientation
   rather than shortening the argument. A hairline across the very top, filling
   as you move through the article body — the body, not the document, so the
   footer and the nav don't get counted as reading you've done.

   It sits above the sticky nav (z-index 50) because it belongs to the page
   edge, not to the header. */
.read-progress{position:fixed;top:0;left:0;z-index:60;height:3px;width:0;
  background:var(--accent);border-radius:0 2px 2px 0;pointer-events:none}
/* No transition: this tracks the scroll position directly, and easing it would
   make the bar lag behind the thumb on a fast flick. */
@media print{.read-progress{display:none}}

/* ---- IMAGE LIGHTBOX ----
   The screenshots carry the evidence, and on a phone they are rendered far
   below the size that evidence can be read at — the Levi's size chart lands at
   163px from a 1400px source, which is a grey smudge where the numbers should
   be. Pairing those figures side by side was right for the page height; it just
   left the proof illegible. So every figure image opens at full size on tap.

   The trigger is the image itself rather than a wrapper, because .device and
   .browser both style `img` as a direct child and wrapping would break both. */
.zoomable{cursor:zoom-in}
.zoomable:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

/* The affordance. cursor:zoom-in says nothing on a phone, which is exactly where
   the images are least readable, so there has to be something you can see. The
   badge sits on the image's own frame — .device and .browser clip it neatly,
   and a bare figure gets it against the image edge. */
.has-zoom{position:relative}
.has-zoom::after{content:"\2921";position:absolute;right:8px;bottom:8px;z-index:1;
  width:28px;height:28px;display:flex;align-items:center;justify-content:center;
  font-size:13px;line-height:1;color:#F5F1EC;background:rgba(24,21,26,.62);
  border:1px solid rgba(245,241,236,.22);border-radius:7px;
  backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);
  opacity:.72;pointer-events:none;transition:opacity .18s,background .18s}
/* Always on. The first version hid it behind @media (hover:hover) and only
   showed it to touch devices, which is clever until a device reports the wrong
   thing and the badge shows to nobody — the exact failure this badge exists to
   fix. A 28px chip in the corner is not clutter worth risking that for. */
.has-zoom:hover::after,
.has-zoom:focus-within::after{opacity:1;background:rgba(24,21,26,.86)}
/* On a .device or .browser frame the badge sits at the bottom right of the image,
   because the frame is the image. On a bare <figure> the frame also contains the
   caption, so bottom:8px put the badge on top of the caption text. Anchor it to the
   top on those instead — same corner convention, never over the words. */
figure.has-zoom::after{top:8px;bottom:auto}
@media (prefers-reduced-motion:reduce){.has-zoom::after{transition:none}}

dialog.lightbox{padding:0;border:0;background:transparent;max-width:100vw;
  max-height:100dvh;width:100%;height:100%;overflow:auto;overscroll-behavior:contain}
dialog.lightbox::backdrop{background:rgba(18,16,20,.96);backdrop-filter:blur(4px);
  -webkit-backdrop-filter:blur(3px)}
.lb-scroll{min-height:100%;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:16px;padding:56px 16px 32px}
.lb-fig{margin:0;max-width:min(1100px,100%)}
/* width:auto, not 100% — a 795px screenshot blown up to fill the panel is
   blurrier than the page version it replaced, which defeats the point. Big
   images fill the panel; small ones stop at their own size. */
.lb-fig img{display:block;width:auto;max-width:100%;height:auto;border:0;border-radius:8px;
  background:var(--surface-2)}
.lb-cap{margin-top:12px;font-size:13.5px;line-height:1.6;color:#D9D4CE;
  max-width:70ch;text-align:left}
/* The close control is fixed to the viewport, not the scroller: on a tall
   screenshot you are several thousand pixels down and still need the way out. */
.lb-close{position:fixed;top:12px;right:12px;z-index:2;min-width:44px;min-height:44px;
  display:inline-flex;align-items:center;justify-content:center;
  font-family:var(--mono);font-size:18px;line-height:1;color:#F5F1EC;
  background:rgba(28,25,30,.82);border:1px solid rgba(245,241,236,.28);
  border-radius:10px;cursor:pointer;backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px)}
.lb-close:hover{background:rgba(48,44,52,.92);border-color:rgba(245,241,236,.5)}
.lb-close:focus-visible{outline:2px solid var(--accent);outline-offset:2px}
.lb-hint{position:fixed;top:18px;left:16px;z-index:2;font-family:var(--mono);
  font-size:10.5px;letter-spacing:.09em;text-transform:uppercase;
  color:rgba(245,241,236,.55);pointer-events:none}
@media(max-width:600px){
  .lb-scroll{padding:52px 8px 24px}
  .lb-hint{display:none}
}

/* ---- CURSOR ART ON INTERACTIVE ELEMENTS ----
   This replaces the JavaScript cursor that used to live at the top of this file.
   The difference that matters: the browser composites `cursor:` art itself, at the
   OS level, so it is always exactly on the hotspot. The old one was positioned by
   a script running an eased lerp, which meant it trailed the real pointer by about
   five frames while `cursor:none` hid the pointer it was trailing — you aimed with
   a marker that was never where the click would land.

   Scope is deliberate. Only links and buttons get the ring:
   - the default arrow stays native, because its tip is what tells a visitor which
     pixel is live, and a symmetrical dot throws that away everywhere at once;
   - `input` is deliberately absent from the selector, so the FitCheck slider keeps
     the plain `cursor:pointer` set on .widget-slider above. Dragging it is a
     precision gesture and a 24px ring centred on the thumb would cover the track
     either side of it — exactly the readout you are dragging against;
   - .zoomable keeps `zoom-in` further down, because that shape means something
     specific and a generic ring would say less.

   Two declarations, not one: the plain url() runs everywhere, and image-set adds
   the 2x art on retina. A browser that cannot parse image-set drops only the
   second line and keeps the first. A browser that cannot load either falls through
   to the `pointer` keyword after the comma, which is required for exactly that.

   `12 12` is the hotspot and must stay equal to half the image's CSS size. It is
   generated by tools/cursor-art.py — rerun that if the art or the accent changes,
   and do not hand-edit the PNGs.

   :is() takes the specificity of its most specific argument, a[href], which is what
   lets these win over the single-class `cursor:pointer` rules set earlier. */
:is(a[href], button, [role="button"], .card-link){
  cursor:url("/assets/cursor/ring-light.png") 12 12, pointer;
  cursor:image-set(url("/assets/cursor/ring-light.png") 1x,
                   url("/assets/cursor/ring-light@2x.png") 2x) 12 12, pointer;
}
/* Bitmap art cannot read a CSS variable, so unlike the masked signature the accent
   is baked in and the theme swap has to be a second declaration. Same three-way
   pattern the rest of this file uses: explicit choice, then system preference. */
[data-theme="dark"] :is(a[href], button, [role="button"], .card-link){
  cursor:url("/assets/cursor/ring-dark.png") 12 12, pointer;
  cursor:image-set(url("/assets/cursor/ring-dark.png") 1x,
                   url("/assets/cursor/ring-dark@2x.png") 2x) 12 12, pointer;
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) :is(a[href], button, [role="button"], .card-link){
    cursor:url("/assets/cursor/ring-dark.png") 12 12, pointer;
    cursor:image-set(url("/assets/cursor/ring-dark.png") 1x,
                     url("/assets/cursor/ring-dark@2x.png") 2x) 12 12, pointer;
  }
}
/* Touch and pen have no cursor to style, and a coarse pointer means the ring would
   only ever be paid for and never seen. */
@media (hover:none), (pointer:coarse){
  :is(a[href], button, [role="button"], .card-link){cursor:pointer}
}
/* Restated last so the meaningful shapes beat the decorative one where they overlap
   — a zoomable figure wrapped in a link should still say "this magnifies". */
.zoomable{cursor:zoom-in}
.lb-close{cursor:pointer}


/* ---- TAP TARGETS ----
   WCAG 2.5.8 asks for 24x24 CSS px on anything you have to hit. These are inline
   links, so their box was only as tall as the text — 17 to 20px. Padding is added
   without moving the text: a negative margin pulls the extra height back out of the
   layout, so the target grows but nothing shifts. */
.art-back,
.wk-teaser a,
.sec-head .sub a,
.nav-cta{
  display:inline-flex;
  align-items:center;
  min-height:24px;
}
.art-back,.wk-teaser a,.sec-head .sub a{padding-block:4px;margin-block:-4px}

/* Sits under the try-it / source links on a project card. Used for the one
   constraint someone needs before they click, not for general prose. */
.card-note{
  margin:8px 0 0;
  font-family:var(--mono);
  font-size:.72rem;
  line-height:1.5;
  color:var(--faint);
}


/* ============================================================
   FIRST-PAINT SEQUENCE
   ============================================================ */
/* 1 — the signature writes itself left to right. clip-path rather than a mask
   trick, because the mark is already painted through a mask and two masks
   cannot be composited reliably across engines. */
/* The hidden state lives in the keyframe, not on the element: if .lit never
   arrives because a script failed, the signature is simply visible rather
   than clipped to nothing. */
/* 1.5s, held back half a second after the curtain lifts.
   At 1s starting immediately it was over before the eye had finished landing
   on the page. The easing is close to steady on purpose — a pen moves at a
   fairly even pace, and an ease-in-out reads as a wipe rather than writing. */
html.lit .brand-mark{animation:sig-draw 1.5s cubic-bezier(.4,.05,.35,1) .55s both}
@keyframes sig-draw{from{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0 0 0 0)}}

/* 2 — the marquee starts still and eases up to speed, so nothing is already
   moving when the page appears.

   The BAR itself is no longer held back. It used to wait for the preloader and
   then a further 1.1s before fading in over 0.8s, which measured 1.53s after the
   headline had gone solid — on a local server, so worse on a real connection. A
   band of the page arriving that long after the rest does not read as staged; it
   reads as something still loading, and the reader starts wondering about their
   connection instead of about the work. It now arrives on the hero's own stagger
   with everything else. Only the scrolling is still held, so the text is not
   already mid-travel when the curtain lifts. */
.hm-track{animation-play-state:paused}
html.lit .hm-track{animation-play-state:running}

@media (prefers-reduced-motion:reduce){
  .brand-mark,html.lit .brand-mark{clip-path:none;animation:none}
  .hm-track{animation-play-state:running}
}

