/* ── fonts ─────────────────────────────────────────────────────────
   One sans everywhere, the same on every computer and phone: Inter (open
   licence, from Google Fonts), the closest free match to PP Neue Montreal,
   which the site was drawn in. It's tuned to it by measuring the outlines:
   Google's copy pinned at optical size 28 (Neue Montreal has one design
   for every size), size-adjust for its x-height, and the --w-* weights
   below (Book 322, Medium 492, Semibold 692, Bold 785). Its spacing is
   matched to Neue Montreal's on the site's own words (6 Oct 2026): Inter
   sets lowercase wider and capitals narrower, so the --tr-* tracking below
   carries the difference (e.g. caps +.012em, chapter heads -.010em) and
   plain text gets --tr-fit, so lines break where Neue Montreal's did. Its ascent
   and descent are Neue Montreal's (958 and 242 of 1000, divided by the 97.7%), so
   letters sit at the same height in every line. The masthead's
   italic twins are Newsreader at 430 (build.py asks Google for just those
   letters), matched to PP Editorial New. The Pangram files in fonts/ are
   personal-use only and are never used or deployed.                    */
@font-face{font-family:"Site Sans";font-style:normal;font-weight:100 900;font-display:swap;size-adjust:97.7%;ascent-override:98.05%;descent-override:24.77%;line-gap-override:0%;
  src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50yjIa25L7W0Q5n-wU.woff2) format("woff2");
  unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:"Site Sans";font-style:normal;font-weight:100 900;font-display:swap;size-adjust:97.7%;ascent-override:98.05%;descent-override:24.77%;line-gap-override:0%;
  src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50yjIa1ZL7W0Q5nw.woff2) format("woff2");
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}

/* ── tokens ────────────────────────────────────────────────────────
   One system for every page: the index, the project window and the case
   studies. Rules use these, never their own values.
   space   2 · 4 · 8 · 12 · 16 · 24 · 32 · 48 · 64              --sp-0…8
           one page gutter (--gut) and one grid gap (--gap) everywhere
   faces   sans (Inter, as "Site Sans") for everything but
           reading; Source Serif for the reading text, the password
           headline and pull quotes; the masthead's italic twins
   sizes   label 11 (caps, 500, +.04em) · small 12 · cta 13 (buttons,
           fields, errors, tips, controls) · s 14 (captions, specs) ·
           m 15 (bylines, lists, supporting lines) · lede 16 · title 14
           (index names); all step up on large screens (end of file)
   heads   700 · tracking d1 -.022 (titles) · d2 -.018 (chapters) ·
           title -.012 (sub-heads) · leading 1.15 · 1.2 · 1.25
   text    leading snug 1.35 (compact) · body 1.45 · lede 1.4 · read 1.6  */
:root.nm{--tr-caps:.04em;--tr-d1:-.022em;--tr-d2:-.018em;--tr-title:-.012em;--tr-lede:-.008em;--tr-ui:-.005em;--tr-num:-.022em;--tr-fit:0em}   /* with licensed PP Neue Montreal on the site (build.py, fonts-licensed/): its own spacing, as drawn */
:root{
  --bg:#FFFFFF;--fg:#0A0A0A;--fg-2:#6B6B6B;--line:#E4E4E1;--surface:#F4F4F2;
  --accent:#0000EE;--link:#0000EE;--accent-fg:#FFFFFF;--error:#C4001D;
  --hair:color-mix(in srgb,var(--fg) 7%,transparent);
  --f:"Site Sans","Helvetica Neue",Helvetica,Arial,sans-serif;
  --serif:"Source Serif 4","Source Serif Pro",Charter,Georgia,serif;
  --r-pill:999px;--gut:clamp(20px,4.4vw,80px);--maxw:1360px;
  --sp-0:2px;--sp-1:4px;--sp-2:8px;--sp-3:12px;--sp-4:16px;--sp-5:24px;--sp-6:32px;--sp-7:48px;--sp-8:64px;
  --fs-label:11px;--fs-small:12px;--fs-cta:13px;--fs-s:14px;--fs-m:15px;--fs-lede:16px;--fs-title:14px;
  --lh-d1:1.15;--lh-d2:1.2;--lh-tight:1.25;--lh-snug:1.35;--lh-lede:1.4;--lh-body:1.45;--lh-read:1.6;
  --w-regular:322;--w-medium:492;--w-semibold:692;--w-bold:785;   /* Neue Montreal Book/Medium/Semibold/Bold, as Inter weights (see fonts) */
  --tr-caps:.052em;--tr-d1:-.028em;--tr-d2:-.028em;--tr-title:-.019em;--tr-serif:-.01em;--tr-lede:-.015em;--tr-ui:-.016em;--tr-read:-.003em;
  --tr-num:-.019em;--tr-fit:-.008em;   /* figures; plain sans text. Neue Montreal's tracking was caps .04, d1 -.022, d2 -.018, title -.012, lede -.008, ui -.005, figures -.022, plain 0 */
  --gap:clamp(16px,2vw,32px);
  /* the glass of the floating bars: the case's top bar, its chapters, the window's foot */
  --glass:color-mix(in srgb,var(--bg) 82%,transparent);--glass-fx:blur(16px) saturate(1.5);
  --glass-edge:inset 0 0 0 1px var(--line),0 10px 28px -18px rgba(10,10,10,.22);
  --studio:radial-gradient(120% 90% at 50% 38%,#FFFFFF 0%,#FAFAFB 55%,#F1F1F3 100%);
  --ease:cubic-bezier(.22,1,.36,1);--ease-out:cubic-bezier(.16,1,.3,1);
  color-scheme:light}
@media (prefers-color-scheme:dark){:root{
  --bg:#0B0B0C;--fg:#F7F7F5;--fg-2:#9A9A98;--line:#262628;--surface:#151517;
  --accent:#6E8CFF;--link:#6E8CFF;--accent-fg:#0B0B0C;--error:#FF7183;
  --studio:radial-gradient(120% 90% at 50% 38%,#1E1E21 0%,#18181B 55%,#121214 100%);color-scheme:dark}}

/* ── base ── */
*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%;text-size-adjust:100%;-webkit-tap-highlight-color:transparent}
/* scrollbars: thin and soft grey everywhere; in Safari the thumb darkens on hover */
:root{scrollbar-width:thin;scrollbar-color:color-mix(in srgb,var(--fg) 20%,transparent) transparent}
.ix-v-card,.cs-chap,textarea{scrollbar-width:thin}
::-webkit-scrollbar{width:10px;height:10px}   /* Safari, which doesn't read the two above */
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--fg) 20%,transparent) content-box;border:3px solid transparent;border-radius:999px}
::-webkit-scrollbar-thumb:hover{background-color:color-mix(in srgb,var(--fg) 38%,transparent)}
/* smooth wheel scrolling (site.js smooth, with Lenis): the browser's own smoothing steps aside while it runs */
html.lenis,html.lenis body{height:auto}
.lenis.lenis-smooth{scroll-behavior:auto!important}
.lenis.lenis-stopped{overflow:clip}
.lenis.lenis-smooth [data-lenis-prevent]{overscroll-behavior:contain}
body{margin:0;background:var(--bg);color:var(--fg);letter-spacing:var(--tr-fit);
  font-family:var(--f);font-size:var(--fs-lede);font-weight:var(--w-regular);line-height:var(--lh-body);-webkit-font-smoothing:antialiased;overflow-x:hidden}
:where(b,strong,h1,h2,h3,h4,h5,h6,th){font-weight:var(--w-bold)}   /* the browser's "bold" is 700; the site's is --w-bold */
:where(*){word-spacing:-.02em}   /* Inter's word space is a little wider than Neue Montreal's */
:is(.cs-gate-h,.cs-sec .cs-lead,.cs-sec .cs-points li,.cs-moment),:is(.cs-gate-h,.cs-sec .cs-lead,.cs-sec .cs-points li,.cs-moment) :not(.cs-cite){word-spacing:normal}   /* the reading serif keeps its own */
a{color:inherit}
button,input,textarea,select{font:inherit;letter-spacing:inherit}   /* controls take the site's type, never the browser's */
:focus-visible{outline:2px solid var(--fg);outline-offset:3px}
.wrap{max-width:var(--maxw);margin:0 auto;padding:0 var(--gut)}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* ═══ actions: one button and one text link, everywhere ═══════════
   .btn               the shape, type and a solid fill
   .btn-o / .btn-line  outlined, as every action on the site is now
   Both are square, the same size and type, and swap fill instantly on
   hover or press. ↗ leaves the site, → stays.
   A text link (in a sentence, or a quiet action like Request access)
   carries a faint underline that turns blue on hover, like the index:
   always the site's blue (--link), never a case's own colour (--accent). */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);padding:var(--sp-2) var(--sp-3);border:0;border-radius:0;
  background:var(--fg);color:var(--bg);box-shadow:inset 0 0 0 1px var(--fg);
  font:var(--w-medium) var(--fs-cta)/var(--lh-tight) var(--f);letter-spacing:var(--tr-ui);white-space:nowrap;text-decoration:none;cursor:pointer;transition:none}
.btn svg{width:18px;height:auto}
.btn-o,.btn-line{background:var(--bg);color:var(--fg)}
@media(hover:hover){.btn:hover{background:var(--bg);color:var(--fg)}:is(.btn-o,.btn-line):hover{background:var(--fg);color:var(--bg)}}
.btn:active{background:var(--bg);color:var(--fg)}
:is(.btn-o,.btn-line):active{background:var(--fg);color:var(--bg)}
.btn .away{display:none;width:12px}
.btn[target="_blank"] .away{display:block}
.btn[target="_blank"] svg:not(.away){display:none}
:is(.cs-sec a:not(.btn),.ix-links a,.ix-v-step a,.cs-gate-req,.cs-gate-back){color:var(--fg);text-decoration:underline 1px;
  text-decoration-color:color-mix(in srgb,currentColor 30%,transparent);text-underline-offset:.22em;transition:color .15s,text-decoration-color .15s}
@media(hover:hover){:is(.cs-sec a:not(.btn),.ix-links a,.ix-v-step a,.cs-gate-req,.cs-gate-back):hover{color:var(--link);text-decoration-color:currentColor}}
footer .wrap{display:flex;justify-content:center;padding-block:var(--sp-4);color:var(--fg-2)}

/* ── arrival: nothing hides without JS; .go starts it, .settled is the failsafe ── */
@keyframes riseIn{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:none}}
.js [data-reveal]{opacity:0;transform:translateY(18px)}
.js [data-reveal].in{opacity:1;transform:none;transition:opacity 1.05s var(--ease-out) var(--d,0s),transform 1.05s var(--ease-out) var(--d,0s)}
.settled [data-reveal]{opacity:1!important;transform:none!important;transition:none!important}

/* ── the loading screen (build.py BOOT, site.js boot): frosted glass over the
   homepage, the same material as the site's floating bars, with my name in
   the small caps. The page arrives behind it, then the glass clears (the blur
   opens from the centre with a soft edge, slowly, as the frost thins) and
   the name lifts away first. The page carries .booting while
   it shows, and .booted after. ── */
#boot{display:none}
@property --clr{syntax:"<percentage>";inherits:false;initial-value:0%}
.js.booting #boot{position:fixed;inset:0;z-index:200;display:grid;place-content:center;color:var(--fg);
  background:color-mix(in srgb,var(--bg) 55%,transparent);-webkit-backdrop-filter:blur(28px) saturate(1.3);backdrop-filter:blur(28px) saturate(1.3);
  /* the clearing: a soft-edged opening that grows from the centre (--clr), the frost thinning as it goes */
  --clr:0%;-webkit-mask-image:radial-gradient(circle farthest-corner at 50% 50%,transparent calc(var(--clr) - 35%),#000 var(--clr));
  mask-image:radial-gradient(circle farthest-corner at 50% 50%,transparent calc(var(--clr) - 35%),#000 var(--clr));
  --clear:2.2s cubic-bezier(.45,.05,.25,1) .1s;   /* a slow start, a soft finish */
  transition:--clr var(--clear),background-color var(--clear),-webkit-backdrop-filter var(--clear),backdrop-filter var(--clear),opacity .5s ease 2s,visibility 0s 2.5s}
.booted #boot{display:none!important}
.js.booting #boot.out{--clr:140%;background-color:color-mix(in srgb,var(--bg) 20%,transparent);-webkit-backdrop-filter:blur(8px) saturate(1.1);backdrop-filter:blur(8px) saturate(1.1);
  opacity:0;visibility:hidden;pointer-events:none}
.boot-n{margin:0;animation:bootIn .9s cubic-bezier(.3,.7,.2,1) .1s backwards;transition:opacity .7s ease,filter .7s ease}   /* "Focus": the name sharpens out of the glass; backwards, so the fade-out below can take over */
.js.booting #boot.out .boot-n{opacity:0;filter:blur(5px)}   /* and softens back into it as the glass clears */
@keyframes bootIn{from{opacity:0;filter:blur(8px)}to{opacity:1;filter:blur(0)}}
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){   /* no glass: a soft white sheet that fades instead */
  .js.booting #boot{background:var(--bg);transition:opacity 1s ease,visibility 0s 1s}
  .js.booting #boot.out{opacity:0}}

/* a field that didn't take its answer (a wrong password, an email that won't do) gives a small shake */
.shake{animation:shake .34s ease}
@keyframes shake{10%,90%{transform:translateX(-2px)}30%,70%{transform:translateX(4px)}50%{transform:translateX(-4px)}}

/* ═══ homepage: the index ═════════════════════════════════════════ */
.wrap.ix{max-width:none;padding-top:var(--sp-6);padding-bottom:var(--sp-5)}
/* the one caps style: details, pills, the window's foot, the case's top
   bar, the footer */
:is(.ix-meta,.ix-filter button,.ix-v-nav,.cs-bar,.boot-n,footer .wrap){font:var(--w-medium) var(--fs-label)/var(--lh-snug) var(--f);
  letter-spacing:var(--tr-caps);text-transform:uppercase}

/* details row: four columns above the masthead */
.ix-intro{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--sp-4) var(--gap);margin:0 0 var(--sp-4)}
.ix-meta{margin:0}
.ix-k{display:block;margin-bottom:var(--sp-1);color:var(--fg-2)}
.ix-k.ix-me{color:var(--fg)}

/* the masthead: fitted by the script to --span of the width. Each letter
   keeps its sans glyph in the flow and draws its serif twin on
   top; [data-s] shows the twin. --sz1/--dy1 match cap height and baseline.
   The first paint is already the fitted size (--fs0: --span of the width,
   capped at 20vh on wide screens), so a reload doesn't snap.            */
.ix-name{--span:.6;--fs0:calc((100vw - 2 * var(--gut,20px)) * var(--span) * .2573);margin:0;font:var(--w-bold) var(--fs0)/.84 var(--f);letter-spacing:-.045em;white-space:nowrap;
  padding-bottom:.125em;touch-action:pan-y;cursor:default;user-select:none}
.ix-name.fit{font-size:var(--fs)}
.ix-name-in{display:block;position:relative}
.ix-name .ch{display:inline-block;position:relative}
.ix-name.spaced .g{letter-spacing:0}
.ix-name.spaced .ch{margin-right:var(--mr,0)}
/* the twin is Newsreader italic made to match PP Editorial New Italic, as measured from both outlines (6 Oct 2026):
   weight 500 at optical size 36 (build.py asks Google for that instance), drawn 90% as wide and leaning 2.5° more,
   which brings its width, stems, contrast and slant within about 2% of Editorial New's. --dx centres it on its letter
   by the ink, as an italic leans past its box (site.js). */
.ix-name .alt{position:absolute;left:50%;top:0;translate:calc(-50% + var(--dx,0px)) var(--dy1,0px);transform:skewX(-2.5deg) scaleX(.9);pointer-events:none;white-space:nowrap;letter-spacing:0;opacity:0;
  font-family:"Newsreader",Georgia,serif;font-style:italic;font-weight:500;font-size:calc(var(--sz1,1) * 1em)}
.ix-name .ch[data-s] .g{opacity:0}
.ix-name .ch[data-s] .alt{opacity:1}
:root.ed .ix-name .alt{font-family:"PP Editorial New","Newsreader",Georgia,serif;font-weight:400;transform:none}   /* with licensed PP Editorial New on the site: the twin is the real thing */
@keyframes chIn{from{opacity:0;translate:0 .45em}}
.js .ix-name .ch{opacity:0}
.js.go .ix-name .ch{opacity:1;animation:chIn .9s cubic-bezier(.2,.9,.2,1) calc(.06s + var(--i) * .035s) both}
.settled .ix-name .ch{animation:none!important;opacity:1!important}
@media(min-width:901px){.ix-name{font-size:min(var(--fs0),20vh)}}
@media(max-width:700px){.ix-name{--span:.8;line-height:.86}}

/* subheader: as wide as the masthead word (--mast-w, set by the script) */
.ix-lede{margin:0 0 var(--sp-5);max-width:var(--mast-w,44ch);font:var(--w-semibold) var(--fs-lede)/var(--lh-snug) var(--f);letter-spacing:var(--tr-lede);text-wrap:pretty}

/* filter pills, each with its count in a bubble */
.ix-filter{position:sticky;top:0;z-index:5;display:flex;align-items:center;flex-wrap:wrap;gap:var(--sp-2);
  margin:0 0 var(--sp-5);padding:var(--sp-4) 0 var(--sp-2);border-top:1px solid var(--hair);background:var(--bg)}
.ix-filter button{display:inline-flex;align-items:center;color:var(--fg);background:none;border:1px solid var(--line);border-radius:var(--r-pill);
  padding:var(--sp-2) var(--sp-3);cursor:pointer;white-space:nowrap;transition:background .15s,color .15s,border-color .15s}
@media(hover:hover){.ix-filter button:hover{border-color:var(--fg)}}
.ix-filter button[aria-pressed="true"]{background:var(--fg);color:var(--bg);border-color:var(--fg)}
.ix-filter .ct{display:inline-grid;place-items:center;min-width:18px;height:18px;margin-left:var(--sp-2);padding:0 5px;border-radius:var(--r-pill);
  font-size:var(--fs-label);line-height:1;letter-spacing:0;font-variant-numeric:tabular-nums;color:var(--fg-2);background:var(--surface)}
.ix-filter [aria-pressed="true"] .ct{color:var(--bg);background:color-mix(in srgb,var(--bg) 18%,transparent)}

/* the grid: four columns; a card's title sits over its tile, tags beneath */
.ix-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--sp-7) var(--gap)}
.ix-cell{min-width:0;display:flex;flex-direction:column}
.ix-cell[hidden]{display:none}
.ix-cell > *{transition:opacity .35s ease}
@media(hover:hover){.ix-grid:has(.ix-item:hover) .ix-cell > :not(:hover){opacity:.32}}
.ix-item{display:flex;flex-direction:column;flex:1;width:100%;padding:0;margin:0;border:0;background:none;color:inherit;font:inherit;text-align:left;cursor:pointer}
.ix-item:focus-visible{outline-offset:var(--sp-2)}
.ix-l{display:block;margin-bottom:var(--sp-3);font:var(--w-semibold) var(--fs-title)/var(--lh-tight) var(--f);letter-spacing:var(--tr-title);text-wrap:balance;transition:color .15s}
.ix-t{display:block;margin-top:var(--sp-0);font-size:var(--fs-small);font-weight:var(--w-medium);line-height:var(--lh-snug);letter-spacing:var(--tr-fit);color:var(--fg-2)}
@media(hover:hover){.ix-item:hover .ix-l,.ix-item:focus-visible .ix-l{color:var(--link)}}
/* the tile: a square-cornered white studio backdrop, always white */
.ix-thumb{margin-top:auto;display:grid;place-items:center;padding:5%;aspect-ratio:4/3;container-type:inline-size;position:relative;isolation:isolate;
  background:var(--studio);box-shadow:0 0 0 1px rgba(0,0,0,.05) inset}
.ix-photo{padding:0;overflow:hidden;background:var(--surface)}
.ix-photo img{display:block;width:100%;height:100%;object-fit:cover;transition:transform .9s var(--ease)}
@media(hover:hover){.ix-item:hover .ix-photo img,.ix-item:focus-visible .ix-photo img{transform:scale(1.04)}}
@media(max-width:900px){.ix-grid,.ix-intro{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:700px){.ix-lede{max-width:none}}
@media(max-width:600px){.ix-mail{grid-column:1/-1}}
@media(max-width:560px){
  .ix-grid{grid-template-columns:1fr;row-gap:var(--sp-6)}
  .ix-filter{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;margin-inline:calc(var(--gut) * -1);padding-inline:var(--gut)}}

/* ── Safari window: the toolbar is drawn on Safari's 52-point grid and --u
   scales it to the window's width, so every size shows the same window ── */
.win{position:relative;z-index:2;display:block;width:100%;overflow:hidden;container-type:inline-size;border-radius:clamp(5px,2.2cqi,12px);background:#fff;
  box-shadow:0 0 0 .5px rgba(0,0,0,.22),0 2px 4px rgba(0,0,0,.04),0 12px 24px -8px rgba(0,0,0,.1),0 32px 60px -22px rgba(0,0,0,.2);
  transition:transform .7s var(--ease),box-shadow .7s var(--ease)}
@media(hover:hover){.ix-item:hover .win,.ix-item:focus-visible .win{transform:translateY(-4%) scale(1.03);
  box-shadow:0 0 0 .5px rgba(0,0,0,.08),0 4px 8px rgba(0,0,0,.05),0 20px 36px -10px rgba(0,0,0,.14),0 48px 80px -26px rgba(0,0,0,.26)}}
.win-bar{--u:calc(clamp(15px,6cqi,46px) / 52);position:relative;display:flex;align-items:center;gap:calc(14 * var(--u));height:calc(52 * var(--u));
  padding:0 calc(18 * var(--u));background:#F6F6F6;border-bottom:.5px solid rgba(0,0,0,.13);color:#8A8A8E}
.sf-tl{display:flex;gap:calc(8 * var(--u));margin-right:calc(6 * var(--u))}
.sf-tl i{width:calc(12 * var(--u));height:calc(12 * var(--u));border-radius:50%;background:#FF5F57;box-shadow:0 0 0 .5px rgba(0,0,0,.14) inset}
.sf-tl i:nth-child(2){background:#FEBC2E}
.sf-tl i:nth-child(3){background:#28C840}
.sf-i{display:block;flex:none;width:calc(16 * var(--u));height:calc(16 * var(--u))}
.sf-nav{width:calc(14 * var(--u))}
.sf-dim{opacity:.4}
.sf-url{position:absolute;left:50%;top:50%;translate:-50% -50%;width:42%;height:calc(30 * var(--u));display:flex;align-items:center;justify-content:center;
  gap:calc(5 * var(--u));border-radius:calc(8 * var(--u));background:rgba(0,0,0,.055);font:var(--w-medium) calc(12.5 * var(--u))/1 var(--f);color:#5E5E62;white-space:nowrap;overflow:hidden}
.sf-lock{width:calc(8 * var(--u));height:calc(10 * var(--u));flex:none;color:#8A8A8E}
.sf-right{display:flex;gap:calc(16 * var(--u));margin-left:auto}
.win-screen{position:relative;display:grid;place-items:center;aspect-ratio:16/10;overflow:hidden;container-type:inline-size;background:#fff}
.win-screen img{display:block;width:100%;height:100%;object-fit:cover;object-position:0 0}
@media (prefers-color-scheme:dark){   /* Safari's own dark mode: the toolbar goes dark, the pages inside stay as they are */
  .win{box-shadow:0 0 0 .5px rgba(255,255,255,.14),0 2px 4px rgba(0,0,0,.2),0 12px 24px -8px rgba(0,0,0,.4),0 32px 60px -22px rgba(0,0,0,.6)}
  .win-bar{background:#2B2B2E;border-bottom-color:rgba(255,255,255,.08);color:#98989D}
  .sf-url{background:rgba(255,255,255,.08);color:#C7C7CC}.sf-lock{color:#98989D}
  .win-screen,.dv-phone .dv-screen{background:#1C1C1E}
  .ix-thumb{box-shadow:0 0 0 1px rgba(255,255,255,.04) inset}}
/* no screen yet: the project's name in its own gradient */
.tw{font:var(--w-bold) 15cqi/.95 var(--f);letter-spacing:-.045em;text-align:center;text-wrap:balance;padding:0 8%;
  background:var(--tw);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
[data-scheme="ember"]{--tw:linear-gradient(100deg,#E23E57,#FF7A45 60%,#FFB05C)}
[data-scheme="lagoon"]{--tw:linear-gradient(100deg,#0A7CE0,#00B4D8 55%,#2FE0BC)}

/* desktop and mobile together: the window, with the phone over its corner */
.dv{position:relative;z-index:2;display:block;width:100%;aspect-ratio:16/11;container-type:inline-size;transition:transform .7s var(--ease)}
.dv > .win,.ix-v-stage .dv > .win,.cs-stage .dv > .win{position:absolute;left:0;top:0;width:84%}
/* the phone: iPhone 18 Pro (6.3in, 71.5 × 149.6mm body, the 17 Pro's shape) —
   thin even bezels, a metal edge, the smaller Dynamic Island, and the action,
   volume and side buttons on the frame. Sized in cqi of .dv (phone is 23cqi). */
.dv-phone{position:absolute;z-index:3;right:0;bottom:0;width:23%;aspect-ratio:71.5/149.6;padding:.62cqi;border-radius:4.3cqi;background:#0b0b0c;
  box-shadow:0 0 0 .2cqi #8e8e93,0 0 0 .34cqi #2c2c2e,0 2px 4px rgba(0,0,0,.08),0 14px 28px -8px rgba(0,0,0,.25),0 34px 60px -20px rgba(0,0,0,.3);
  transition:transform .8s var(--ease)}
.dv-phone::before,.dv-phone::after{content:"";position:absolute;width:.34cqi;border-radius:.3cqi}
.dv-phone::before{right:-.56cqi;top:27%;height:10%;background:#6e6e73}   /* side button */
.dv-phone::after{left:-.56cqi;top:18%;height:23%;   /* action button, volume up, volume down */
  background:linear-gradient(#6e6e73 0 13%,transparent 13% 27%,#6e6e73 27% 60%,transparent 60% 67%,#6e6e73 67% 100%)}
.dv-screen{position:relative;display:block;height:100%;overflow:hidden;border-radius:3.75cqi;background:#fff}
.dv-screen::before{content:"";position:absolute;z-index:1;left:50%;top:1.9%;translate:-50% 0;width:25%;aspect-ratio:3.3/1;border-radius:999px;background:#000}
.dv-screen img{display:block;width:100%;height:100%;object-fit:cover;object-position:top center}
.dv-word{display:grid !important;place-items:center;container-type:inline-size}
.dv-word .tw{font-size:24cqi;padding:0 6%}
@media(hover:hover){.ix-item:hover .dv-phone,.ix-item:focus-visible .dv-phone{transform:translateY(-6%)}}
.ix-v-stage .dv{width:min(100%,calc(100cqh * 16 / 11))}
.cs-stage .dv{max-width:1000px}

/* the design system card: a Figma canvas. A frame of Trint components, the
   Design panel, the floating toolbar and Annie's multiplayer cursor, which
   site.js (playground) moves; every length is in cqi of the board        */
.pg{letter-spacing:normal;--ui:"Inter",var(--f);--sel:#0D99FF;--comp:#9747FF;--lift:0 0 0 .15cqi rgba(0,0,0,.06),0 .8cqi 2.4cqi -.6cqi rgba(15,20,30,.16);
  position:relative;z-index:2;display:block;width:100%;aspect-ratio:4/3;container-type:inline-size;color:#19222E;
  font:400 2.6cqi/1.2 "Source Sans 3","Source Sans Pro",var(--f)}
.pg :is(span,i,b,em,small){display:block;font-style:normal}
/* the layout: frame and panel share a bottom edge, 3.5cqi margins either
   side and a 3cqi gutter between; the frame is an auto layout of two rows */
.pg-flabel{position:absolute;left:3.5cqi;top:8.25cqi;font:500 2cqi/1 var(--ui);color:#8B95A3}
.pg-frame{position:absolute;left:3.5cqi;top:11.35cqi;width:58cqi;height:45cqi;display:flex !important;flex-direction:column;justify-content:center;gap:7cqi;
  padding:0 4.5cqi;background:#fff;box-shadow:var(--lift)}
.pg-r{display:flex !important;align-items:center;justify-content:space-between;gap:3cqi}
.pg-r > *{position:relative;flex:none}
/* the Atomic button to spec: 40px high, 4px radius, 16px side padding,
   1px border, Body Small Semibold (14px); 1px of the spec is .185cqi here */
.pg-btn{height:7.4cqi;white-space:nowrap;display:flex !important;align-items:center;padding:0 2.96cqi;border:max(1px,.185cqi) solid #F7CC21;border-radius:.74cqi;
  background:#FFE522;color:#101517;font:600 2.6cqi/1 "Source Sans 3","Source Sans Pro",var(--f)}
.pg-btn[data-v=dark]{background:#2E3847;border-color:#4C5563;color:#fff}
.pg-tog{width:8.6cqi;height:4.8cqi;border-radius:4.8cqi;background:#AAB2BF;transition:background .3s}
.pg-tog::after{content:"";position:absolute;left:.6cqi;top:.6cqi;width:3.6cqi;height:3.6cqi;border-radius:50%;background:#fff;transition:translate .35s cubic-bezier(.3,1.4,.5,1)}
.pg-tog.on{background:#0E8728}.pg-tog.on::after{translate:3.8cqi 0}
.pg-chk{width:5cqi;height:5cqi;padding:.6cqi;border-radius:.9cqi;box-shadow:inset 0 0 0 .4cqi #2E3847;color:#fff;transition:background .2s}
.pg-chk svg{display:block;width:100%;height:100%;opacity:0;transition:opacity .2s}
.pg-chk.on{background:#2E3847}.pg-chk.on svg{opacity:1}
.pg-av{width:5.8cqi;height:5.8cqi;border-radius:50%;background:#2E3847;color:#fff;font:600 2.4cqi/5.8cqi "Source Sans 3",var(--f);text-align:center}
.pg-r > .pg-card{flex:1 1 0;min-width:0;display:grid !important;gap:1.3cqi;padding:2.6cqi;border-radius:1.2cqi;background:#fff;box-shadow:0 0 0 .25cqi #E6E8EB}
.pg-card b{font:700 2.6cqi/1.1 Montserrat,var(--f);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.pg-card i{height:1.3cqi;border-radius:1cqi;background:#E6E8EB}.pg-card i+i{width:70%}
.pg-tag{height:5.6cqi;padding:0 1.8cqi;border-radius:5.6cqi;box-shadow:inset 0 0 0 .3cqi #8B95A3;font-size:2.2cqi;line-height:5.6cqi;white-space:nowrap}
/* the selection: the blue box, handles, the component's name above, its size below */
.pg-sel{position:absolute;left:0;top:0;width:0;height:0;z-index:6;outline:.3cqi solid var(--sel);opacity:0;pointer-events:none;
  transition:left .3s var(--ease),top .3s var(--ease),width .3s var(--ease),height .3s var(--ease),opacity .2s}
.pg-sel.on{opacity:1}
.pg-sel > i{position:absolute;width:1.7cqi;height:1.7cqi;background:#fff;border:.3cqi solid var(--sel)}
.pg-sel > i:nth-of-type(1){left:-.95cqi;top:-.95cqi}.pg-sel > i:nth-of-type(2){right:-.95cqi;top:-.95cqi}
.pg-sel > i:nth-of-type(3){left:-.95cqi;bottom:-.95cqi}.pg-sel > i:nth-of-type(4){right:-.95cqi;bottom:-.95cqi}
.pg-sel em{position:absolute;left:-.3cqi;bottom:calc(100% + 1cqi);display:flex !important;gap:.6cqi;align-items:center;font:500 2.1cqi/1 var(--ui);color:var(--comp);white-space:nowrap}
.pg-sel small{position:absolute;left:50%;top:calc(100% + 1.3cqi);translate:-50% 0;padding:.6cqi 1.2cqi;border-radius:.6cqi;background:var(--sel);color:#fff;font:500 2cqi/1 var(--ui);white-space:nowrap}
.pg-sel small:empty{display:none}
.pg-dia{width:2.2cqi;height:2.2cqi;flex:none;color:var(--comp)}
/* the Design panel */
.pg-panel{position:absolute;left:64.5cqi;top:6.85cqi;width:32cqi;height:49.5cqi;display:grid !important;align-content:start;background:#fff;border-radius:1.2cqi;
  box-shadow:var(--lift);font:400 2cqi/1.2 var(--ui);color:#19222E;overflow:hidden;white-space:nowrap}
.pg-tabs{display:flex !important;gap:2.4cqi;padding:2.2cqi 2.2cqi 1.8cqi;border-bottom:.15cqi solid #E6E8EB;color:#8B95A3}.pg-tabs b{font-weight:600;color:#19222E}
.pg-name{display:flex !important;gap:.8cqi;align-items:center;padding:2.2cqi 2.2cqi 1.6cqi;font-weight:600}
.pg-name .pg-dia{display:none}.pg-name.comp{color:var(--comp)}.pg-name.comp .pg-dia{display:block}
.pg-props{display:grid !important;gap:1.4cqi;padding:0 2.2cqi}
.pg-row{display:grid !important;grid-template-columns:8cqi minmax(0,1fr);align-items:center;gap:1cqi;color:#657080}
.pg-row b{display:flex !important;align-items:center;justify-content:space-between;gap:.8cqi;min-width:0;padding:1cqi 1.2cqi;border-radius:.6cqi;background:#F0F2F2;color:#19222E;font-weight:500}
.pg-row b span{min-width:0;overflow:hidden;text-overflow:ellipsis}
.pg-row b::after{content:"";flex:none;width:.9cqi;height:.9cqi;border:solid currentColor;border-width:0 .25cqi .25cqi 0;rotate:45deg;translate:0 -.25cqi}
.pg-fill{margin-top:2cqi;padding:2cqi 2.2cqi 0;border-top:.15cqi solid #E6E8EB;display:grid !important;gap:1.2cqi;opacity:0;transition:opacity .3s}
.pg-fill.on{opacity:1}
.pg-fill small{display:flex !important;justify-content:space-between;font-weight:600}.pg-fill small em{font-weight:400;color:#657080}
.pg-fill small + .pg-tok + small{margin-top:.8cqi}
.pg-tok{display:flex !important;gap:1cqi;align-items:center;min-width:0}
.pg-tok span{min-width:0;overflow:hidden;text-overflow:ellipsis}
.pg-tok i{width:2.8cqi;height:2.8cqi;flex:none;border-radius:.5cqi;box-shadow:inset 0 0 0 .15cqi rgba(0,0,0,.12)}
.pg-tok.off{display:none !important}
/* a Figma dropdown menu, opened from a property in the panel */
.pg-menu{position:absolute;z-index:8;width:17cqi;white-space:nowrap;display:grid !important;padding:.8cqi;border-radius:1cqi;background:#1E1E1E;color:#fff;
  font:400 2cqi/1 var(--ui);box-shadow:0 1cqi 3cqi -.6cqi rgba(0,0,0,.35);opacity:0;visibility:hidden;translate:0 -.6cqi;
  transition:opacity .16s,translate .16s,visibility 0s .16s}
.pg-menu.on{opacity:1;visibility:visible;translate:0 0;transition:opacity .16s,translate .16s}
.pg-menu span{position:relative;padding:1cqi 1.2cqi 1cqi 3.6cqi;border-radius:.6cqi}
.pg-menu span.on::before{content:"✓";position:absolute;left:1.1cqi}
.pg-menu span.hi{background:var(--sel)}
/* the floating toolbar and the cursor */
.pg-tools{position:absolute;left:50%;translate:-50% 0;top:61.35cqi;display:flex !important;gap:.6cqi;padding:.9cqi;border-radius:1.4cqi;background:#fff;box-shadow:var(--lift)}
.pg-tools i{display:grid !important;place-items:center;width:5cqi;height:5cqi;border-radius:.9cqi;color:#19222E}
.pg-tools i.on{background:var(--sel);color:#fff}.pg-tools svg{width:2.8cqi;height:2.8cqi}
.pg-cur{position:absolute;left:34cqi;top:34cqi;z-index:9;pointer-events:none;
  transition:left .9s cubic-bezier(.55,.1,.3,1),top .9s cubic-bezier(.55,.1,.3,1)}
.pg-cur svg{display:block;width:4.2cqi;height:4.2cqi;filter:drop-shadow(0 .3cqi .4cqi rgba(0,0,0,.2));transition:transform .15s}
.pg-cur.click svg{transform:scale(.82)}
.pg-cur em{margin:-.8cqi 0 0 3.2cqi;padding:.6cqi 1.3cqi;border-radius:.3cqi 1cqi 1cqi 1cqi;background:#E23EB0;color:#fff;font:500 2.1cqi/1 var(--ui)}
@media (prefers-color-scheme:dark){   /* Figma's dark theme: dark panels and toolbar, the white artboard stays white */
  .pg{--comp:#B587FF}
  .pg-flabel{color:#9A9A9A}
  .pg-panel,.pg-tools{background:#2C2C2C;color:#F5F5F5;box-shadow:0 0 0 .15cqi rgba(255,255,255,.08),0 .8cqi 2.4cqi -.6cqi rgba(0,0,0,.55)}
  .pg-frame{box-shadow:0 0 0 .15cqi rgba(255,255,255,.06),0 .8cqi 2.4cqi -.6cqi rgba(0,0,0,.55)}
  .pg-tabs,.pg-fill{border-color:#3D3D3D}.pg-tabs{color:#9A9A9A}.pg-tabs b{color:#F5F5F5}
  .pg-row{color:#B3B3B3}.pg-row b{background:#383838;color:#F5F5F5}
  .pg-fill small em{color:#B3B3B3}.pg-tok i{box-shadow:inset 0 0 0 .15cqi rgba(255,255,255,.14)}
  .pg-tools i{color:#F5F5F5}
  .pg-menu{box-shadow:0 0 0 .15cqi rgba(255,255,255,.1),0 1cqi 3cqi -.6cqi rgba(0,0,0,.6)}}
/* in the viewer and the case study the canvas fits its stage */
.ix-thumb .pg{aspect-ratio:1.46}   /* the same shape as the browser windows in the other thumbnails, so every card lines up */
.ix-v-stage .pg{width:min(100%,calc(100cqh * 4 / 3))}
.cs-stage .pg{max-width:920px}
@media(max-width:900px){.ix-v-stage .pg{width:100%}}

/* cards arrive one after another (--d); the window settles into its card a beat later */
.js .ix-cell[data-reveal]{transform:translateY(34px);transition:none}
.js .ix-cell[data-reveal].in{transform:none;transition:opacity .9s cubic-bezier(.2,.7,.2,1) var(--d,0s),transform 1.25s var(--ease-out) var(--d,0s)}
.js .ix-cell[data-reveal] :is(.win,.pg,.ix-photo img,.ix-l){opacity:0}
.js .ix-cell[data-reveal] :is(.win,.pg){translate:0 16px;scale:.94;filter:blur(5px)}
.js .ix-cell[data-reveal] .ix-photo img{scale:1.08}
.js .ix-cell[data-reveal].in :is(.win,.pg,.ix-photo img,.ix-l){opacity:1;translate:0 0;scale:1;filter:none}
.js .ix-cell[data-reveal].in :is(.win,.pg){transition:opacity .8s cubic-bezier(.2,.7,.2,1) calc(var(--d,0s) + .14s),translate 1.3s var(--ease-out) calc(var(--d,0s) + .14s),
  scale 1.3s var(--ease-out) calc(var(--d,0s) + .14s),filter .8s ease calc(var(--d,0s) + .14s),transform .7s var(--ease),box-shadow .7s var(--ease)}
.js .ix-cell[data-reveal].in .ix-photo img{transition:opacity .8s cubic-bezier(.2,.7,.2,1) calc(var(--d,0s) + .14s),scale 1.4s var(--ease-out) calc(var(--d,0s) + .14s),transform .9s var(--ease)}
.js .ix-cell[data-reveal].in .ix-l{transition:opacity .7s ease calc(var(--d,0s) + .08s),color .15s}
.settled .ix-cell[data-reveal] :is(.win,.pg,.ix-photo img,.ix-l){opacity:1!important;translate:none!important;scale:none!important;filter:none!important}

/* ═══ project viewer ═══════════════════════════════════════════════
   A card that fills the screen, open at the bottom edge, over the blurred
   index. Two columns: the work on the left, the words on the right (held
   in view while a gallery scrolls past), and the category's projects in a
   bar along the foot. The card itself scrolls.                          */
.ix-view{--inset:clamp(8px,1.1vw,16px);position:fixed;inset:0;z-index:90;padding:var(--inset) var(--inset) 0;
  background:rgba(12,12,14,.38);-webkit-backdrop-filter:blur(14px) saturate(1.2);backdrop-filter:blur(14px) saturate(1.2)}
.ix-v-card:focus{outline:none}
.ix-v-card{position:relative;height:100%;display:flex;flex-direction:column;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;
  border-radius:clamp(14px,1.6vw,24px) clamp(14px,1.6vw,24px) 0 0;background:var(--bg);color:var(--fg);
  box-shadow:0 0 0 .5px rgba(0,0,0,.08),0 30px 80px -20px rgba(0,0,0,.4)}
@keyframes vIn{from{opacity:0}}
@keyframes vOut{to{opacity:0}}
@keyframes cIn{from{transform:translateY(64px);opacity:0}}
@keyframes cOut{to{transform:translateY(40px);opacity:0}}
.ix-view.opening{animation:vIn .6s ease both}
.ix-view.opening .ix-v-card{animation:cIn .6s var(--ease-out) both}
.ix-view.closing{animation:vOut .26s ease-in both}
.ix-view.closing .ix-v-card{animation:cOut .26s ease-in both}
/* the close button rides along the top of the card as it scrolls */
.ix-v-top{position:sticky;top:0;z-index:4;height:0}
.ix-v-x{position:absolute;top:var(--sp-3);right:var(--sp-3);display:grid;place-items:center;width:44px;height:44px;padding:0;
  border:0;background:none;color:var(--fg);font:var(--w-regular) 18px/1 var(--f);cursor:pointer;transition:opacity .15s}
@media(hover:hover){.ix-v-x:hover{opacity:.55}}
/* every project is laid out as a page (site.js project / glimpse), in one
   column at the page's own gutter; the head only feeds it and screen readers */
.ix-v-body{flex:1 0 auto;width:100%;max-width:1920px;margin-inline:auto;padding:clamp(64px,8vh,88px) 0 var(--sp-7);
  --stage:calc(100dvh - var(--inset) - clamp(64px,8vh,88px) - 120px)}
.ix-v-head{display:none}
/* the work: a screen on the studio backdrop sized to the window, or the set in rows */
.ix-v-work{min-width:0}
.ix-v-stage{height:max(360px,var(--stage));display:grid;place-items:center;padding:clamp(24px,5%,80px);container-type:size;
  background:var(--studio);box-shadow:0 0 0 1px rgba(0,0,0,.05) inset}
.ix-v-stage .win{width:min(100%,calc((100cqh - 46px) * 1.55))}
.ix-row{display:flex;gap:var(--sp-5)}
.ix-row > *{min-width:0}
.ix-v-work :is(.ix-row > img,video){display:block;width:100%;height:auto}
/* a gallery video: its still under a dark veil, with a Play video pill */
.ix-vid{position:relative}
.win-screen .ix-vid,.win-screen .ix-vid video{width:100%;height:100%;object-fit:cover}
.ix-vid::after{content:"";position:absolute;inset:0;background:rgba(10,10,10,.42);pointer-events:none;transition:opacity .3s}
.ix-play{position:absolute;left:50%;top:50%;translate:-50% -50%;z-index:1;display:inline-flex;white-space:nowrap;align-items:center;gap:var(--sp-2);height:44px;
  padding:0 var(--sp-5) 0 var(--sp-4);border:0;border-radius:var(--r-pill);cursor:pointer;background:#fff;color:#0A0A0A;
  font:var(--w-medium) var(--fs-cta)/1 var(--f);letter-spacing:var(--tr-ui);box-shadow:0 10px 30px -8px rgba(0,0,0,.45);transition:transform .3s var(--ease),opacity .2s}
.ix-play svg{width:16px;height:16px;fill:currentColor}
@media(hover:hover){.ix-play:hover{transform:scale(1.04)}}
.ix-play:focus-visible{outline-color:#fff}
.ix-vid.playing .ix-play,.ix-vid.playing::after{opacity:0;pointer-events:none}
/* the foot: previous, the category's projects by name, next */
.ix-v-nav{position:sticky;bottom:0;z-index:3;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);
  padding:var(--sp-3) max(var(--gut),calc((100% - 1920px) / 2 + var(--gut)));border-top:1px solid var(--hair);
  background:var(--glass);-webkit-backdrop-filter:var(--glass-fx);backdrop-filter:var(--glass-fx)}
.ix-view.solo .ix-v-nav{display:none}
/* reading an opened case: the card goes full screen, the chapter dock comes
   up (site.js caseNav), and the foot steps aside until the end of the case */
.ix-view{transition:padding .5s var(--ease)}
.ix-v-card{transition:border-radius .5s var(--ease)}
.ix-view.reading{padding:0}
.ix-view.reading .ix-v-card{border-radius:0}
.ix-v-nav{transition:translate .45s var(--ease),opacity .3s ease}
.ix-view.reading:not(.at-end) .ix-v-nav{translate:0 100%;opacity:0;pointer-events:none}
.ix-view.at-end .cs-dock.show{translate:-50% var(--sp-3);opacity:0;pointer-events:none}
/* the top bar in the window: fixed over the card, only while reading; ✕ steps aside for its Back */
.cs-bar.cs-bar-win{position:fixed;left:0;right:0;z-index:5;margin:0 auto}
.cs-bar button{padding:0;border:0;background:none;color:inherit;text-transform:inherit;cursor:pointer;transition:opacity .15s}
@media(hover:hover){.cs-bar button:hover{opacity:.55}}
.ix-view:not(.reading) .cs-bar-win{translate:0 calc(-100% - var(--sp-5));opacity:0;pointer-events:none}
.ix-view.reading .ix-v-x{opacity:0;pointer-events:none}
.ix-v-btn{font:inherit;letter-spacing:inherit;text-transform:inherit;color:var(--fg);background:none;border:0;
  padding:var(--sp-2) 0;cursor:pointer;white-space:nowrap;transition:opacity .15s}
@media(hover:hover){.ix-v-btn:hover{opacity:.55}}
/* one line of names at every size; when they outrun the space the line slides sideways, the current one kept in view */
.ix-v-dots{position:relative;flex:0 1 auto;min-width:0;display:flex;gap:var(--sp-5);overflow-x:auto;scrollbar-width:none;overscroll-behavior-x:contain;white-space:nowrap}
.ix-v-dots::-webkit-scrollbar{display:none}
.ix-v-dots.over{-webkit-mask-image:linear-gradient(to right,transparent,#000 24px,#000 calc(100% - 24px),transparent);mask-image:linear-gradient(to right,transparent,#000 24px,#000 calc(100% - 24px),transparent)}
.ix-v-dots .n{margin-right:var(--sp-2);font-variant-numeric:tabular-nums;opacity:.6}
.ix-v-dots [aria-current="true"] .n{opacity:1}
.ix-v-dots button{font:inherit;letter-spacing:inherit;text-transform:inherit;color:var(--fg-2);background:none;border:0;padding:var(--sp-1) 0;cursor:pointer;transition:color .15s}
@media(hover:hover){.ix-v-dots button:hover{color:var(--fg)}}
.ix-v-dots [aria-current="true"]{color:var(--fg)}
.ix-v-dots button{flex:none}
.ix-v-dots [aria-current="true"] .t{text-decoration:underline 1px;text-underline-offset:.22em}
.ix-v-body.swap > *{animation:riseIn .5s var(--ease) both}
@media(max-width:900px),(max-height:520px) and (orientation:landscape),(max-width:1100px) and (orientation:portrait){   /* phones either way up, and upright tablets: one column */
  .ix-v-body{padding-top:72px}
  .ix-v-stage{height:auto;container-type:inline-size}
  .ix-v-stage .win{width:100%}
  .ix-v-dots{gap:var(--sp-4)}}
@media(max-width:560px){.ix-row{flex-direction:column}.ix-row > *{flex:none!important}}

/* ═══ case study ══════════════════════════════════════════════════
   One template for every case study (cases/<name>/content.py), set like a
   Medium story: one 680px reading column, a serif body at 20px, sans
   headings, a byline with the read time, pictures stepping out past the
   text with captions centred beneath, a faint hairline between chapters,
   and the chapters in the top bar to jump between.                     */
.cs-bar,.cs{--col:680px;--wide:1000px;
  --read:clamp(18px,1.35vw,20px);--pic:880px;--s1:color-mix(in srgb,var(--accent) 80%,#fff);--s2:#B8791F;
  --accent-soft:color-mix(in srgb,var(--accent) 8%,var(--bg));--accent-line:color-mix(in srgb,var(--accent) 28%,var(--bg))}
/* the top bar: a pill in the portfolio's own vocabulary, a hairline edge
   like the filter pills, small caps like every label on the site, floating
   over the reading column. It slides away while you scroll down and comes
   back when you scroll up or pause (site.js caseBar).                      */
.cs-bar{position:sticky;top:var(--sp-4);z-index:20;display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);
  width:min(calc(var(--col) + 120px),calc(100% - 2 * var(--gut)));height:48px;margin:var(--sp-4) auto 0;padding:0 var(--sp-5);border-radius:var(--r-pill);color:var(--fg);
  background:var(--glass);-webkit-backdrop-filter:var(--glass-fx);backdrop-filter:var(--glass-fx);box-shadow:var(--glass-edge);
  transition:translate .5s var(--ease),opacity .35s ease}
.cs-bar.away{translate:0 calc(-100% - var(--sp-5));opacity:0;pointer-events:none}
.cs-bar a{text-decoration:none;transition:opacity .15s,color .2s}
@media(hover:hover){.cs-bar a:hover{opacity:.55}}
@media(max-width:600px){.cs-bar{top:var(--sp-3);width:calc(100% - 2 * var(--sp-3));margin-top:var(--sp-3);padding:0 var(--sp-4)}}

/* the chapters: a quiet glass pill at the bottom, in the same material as
   the top bar. It shows how far you've read, which chapter you're in and its
   name; tap it and it opens into every chapter, to jump to any of them.  */
.cs-dock{position:fixed;left:50%;bottom:max(var(--sp-4),calc(env(safe-area-inset-bottom) + var(--sp-3)));z-index:30;translate:-50% var(--sp-3);opacity:0;pointer-events:none;
  transition:translate .55s var(--ease),opacity .3s ease}
.cs-dock.show{translate:-50% 0;opacity:1;pointer-events:auto}
.cs-dock-btn{display:flex;align-items:center;gap:var(--sp-2);height:40px;max-width:calc(100vw - 2 * var(--sp-4));padding:0 var(--sp-4) 0 var(--sp-3);border:0;border-radius:var(--r-pill);
  background:var(--glass);-webkit-backdrop-filter:var(--glass-fx);backdrop-filter:var(--glass-fx);box-shadow:var(--glass-edge);
  color:var(--fg-2);font:var(--w-medium) var(--fs-cta)/1 var(--f);letter-spacing:var(--tr-ui);cursor:pointer;transition:color .2s}
@media(hover:hover){.cs-dock-btn:hover{color:var(--fg)}}
.cs-dock.open .cs-dock-btn{color:var(--fg)}
@media(pointer:coarse){.cs-dock-btn{height:44px}}
.cs-ring{flex:none;width:16px;height:16px;rotate:-90deg}
.cs-ring circle{fill:none;stroke-width:2.4}
.cs-ring circle:first-child{stroke:color-mix(in srgb,var(--fg) 12%,transparent)}
.cs-ring .p{stroke:var(--fg);stroke-linecap:round;stroke-dasharray:50.27;stroke-dashoffset:calc(50.27 * (1 - var(--p,0)))}
.cs-dock-n{flex:none;font-variant-numeric:tabular-nums;opacity:.7}
.cs-dock-t{display:block;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;color:var(--fg)}
.cs-dock-t.swap{animation:dockSwap .4s var(--ease) both}
@keyframes dockSwap{from{opacity:0;transform:translateY(55%)}}
.cs-dock-caret{flex:none;width:12px;height:12px;opacity:.6;transition:rotate .3s var(--ease)}
.cs-dock.open .cs-dock-caret{rotate:180deg}
.cs-dock .cs-chap{position:absolute;left:50%;bottom:calc(100% + var(--sp-2));min-width:max(100%,236px);padding:var(--sp-2);border-radius:20px;
  background:var(--glass);-webkit-backdrop-filter:var(--glass-fx);backdrop-filter:var(--glass-fx);box-shadow:var(--glass-edge);color:var(--fg);
  translate:-50% var(--sp-2);opacity:0;visibility:hidden;
  transition:opacity .2s ease,translate .3s var(--ease),visibility 0s .3s}
.cs-dock.open .cs-chap{translate:-50% 0;opacity:1;visibility:visible;transition:opacity .2s ease,translate .3s var(--ease)}
.cs-chap ol{display:grid;gap:var(--sp-0);margin:0;padding:0;list-style:none}
.cs-chap a{display:flex;align-items:center;gap:var(--sp-3);min-height:44px;padding:0 var(--sp-4);border-radius:12px;white-space:nowrap;text-decoration:none;
  font:var(--w-medium) var(--fs-s)/var(--lh-tight) var(--f);color:var(--fg-2);transition:background .15s,color .15s}
.cs-chap a span{min-width:1.1em;font-variant-numeric:tabular-nums;opacity:.6}
@media(hover:hover){.cs-chap a:hover{background:color-mix(in srgb,var(--fg) 5%,transparent);color:var(--fg)}}
.cs-chap a[aria-current="true"]{background:color-mix(in srgb,var(--fg) 7%,transparent);color:var(--fg)}
.cs-chap a[aria-current="true"] span{opacity:1}
.cs{padding:0 var(--gut) clamp(80px,12vh,140px)}
.cs-hero,.cs-by,.cs-specs,.cs-sec{max-width:var(--col);margin-inline:auto}
.cs-label{margin:0;font-size:var(--fs-m);line-height:var(--lh-body);color:var(--fg-2)}

/* the headline, the standfirst beneath it, then the byline */
.cs-hero{padding:clamp(40px,8vh,88px) 0 0}
.cs-title{margin:0;font:var(--w-bold) clamp(32px,4vw,44px)/var(--lh-d1) var(--f);letter-spacing:var(--tr-d1);text-wrap:balance}
.cs-lede{margin:var(--sp-3) 0 0;font-size:clamp(19px,1.6vw,22px);line-height:var(--lh-lede);letter-spacing:var(--tr-lede);color:var(--fg-2);text-wrap:pretty}
.cs.has-key:not(.open) :is(.cs-hero,.cs-glimpse,.cs-gate){visibility:hidden}   /* a key is already here: no flash of the glimpse while it opens */

/* the glimpse and the password (site.js caseLock), like a paywalled article:
   the specs and the opening of the first chapter fade out, then the gate */
.cs-gate{max-width:var(--col);margin-inline:auto}
.cs-teaser{max-height:min(150vh,1400px);overflow:hidden;pointer-events:none;user-select:none;
  -webkit-mask-image:linear-gradient(to bottom,#000 72%,transparent 98%);mask-image:linear-gradient(to bottom,#000 72%,transparent 98%)}
.cs-teaser{max-width:none}
.cs-gate{display:grid;justify-items:center;gap:var(--sp-3);margin:clamp(48px,16vh,200px) auto var(--sp-8);padding:0 var(--gut);text-align:center}   /* a quarter of the screen below the glimpse */
.cs-gate-h{margin:0;font:var(--w-regular) clamp(26px,3vw,34px)/var(--lh-d1) var(--serif);letter-spacing:var(--tr-serif);color:var(--fg);text-wrap:balance}
.cs-gate-sub{margin:0;max-width:42ch;font-size:var(--fs-m);line-height:var(--lh-body);color:var(--fg-2);text-wrap:pretty}
.cs-gate .pw-form{justify-items:center;margin-top:var(--sp-3)}
.cs-gate .pw-tip{left:50%;translate:-50% 0}
.cs-gate .pw-tip::before,.cs-gate .pw-tip::after{left:calc(50% - 6px)}
.cs-gate-req,.cs-gate-back{display:inline-flex;align-items:center;gap:var(--sp-2);margin-top:var(--sp-2);padding:0;border:0;background:none;font:var(--w-medium) var(--fs-cta)/var(--lh-tight) var(--f);letter-spacing:var(--tr-ui);cursor:pointer}   /* text links, in the buttons' type */
.cs-gate-req svg{width:18px;height:auto}
.cs-gate-or{margin:0;font-size:var(--fs-cta);line-height:var(--lh-tight);color:var(--fg-2)}   /* between the password and Request access */
.cs-gate-or + .cs-gate-req{margin-top:0}
.cs-gate-or:has(+ .cs-gate-req[hidden]){display:none}
.cs-gate-req[hidden]{display:none}
.cs-req{margin-top:var(--sp-2)}
.cs-gate-back[hidden]{display:none}
/* each change of the gate (password, request, sent) eases in, top to bottom */
.cs-gate.swap > :is(.cs-gate-h,.cs-gate-sub,.pw-form,.cs-gate-or,.cs-gate-req,.cs-gate-back){animation:gateIn .42s cubic-bezier(.2,.7,.2,1) both}
.cs-gate.swap > .cs-gate-sub{animation-delay:.05s}
.cs-gate.swap > .pw-form{animation-delay:.1s}
.cs-gate.swap > :is(.cs-gate-or,.cs-gate-req,.cs-gate-back){animation-delay:.15s}
@keyframes gateIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.cs-gate.swap > *{animation:none!important}}
.pw-form[hidden]{display:none}
/* a case study in the index window looks like its own page (site.js glimpse) */
.ix-v-body.is-page .ix-v-head{display:none}   /* every project is laid out as a page (site.js project / glimpse) */
.ix-v-body.is-page{grid-template-columns:minmax(0,1fr)}   /* no side column: the page takes the whole card and centres in it */
.ix-v-body.is-page .ix-v-work{grid-column:1}
.cs-in-viewer .ix-v-pics{max-width:var(--wide);margin-inline:auto}
.ix-v-pics{display:grid;gap:var(--sp-5);margin:var(--sp-7) 0}
.ix-v-pics .ix-row{margin:0}
.ix-v-step{max-width:var(--col);width:100%;margin:var(--sp-5) auto 0;font-size:var(--fs-m);line-height:var(--lh-body);color:var(--fg);text-wrap:pretty}   /* a gallery's line of text, e.g. a step in the process, above its pictures */
.cs-end .btn{margin-top:var(--sp-2)}
.cs-in-viewer{padding-bottom:0;text-align:left}   /* the window keeps its own foot room */
.cs-in-viewer.is-opening > :not(.cs-hero){visibility:hidden}   /* unlocked: the glimpse never flashes before the whole case lands */
.cs-in-viewer .cs-hero{padding-top:var(--sp-2)}
.cs-in-viewer .cs-hero-fig{width:auto;max-width:var(--wide);margin:var(--sp-7) auto;translate:none}
.cs-in-viewer .cs-gate{margin:var(--sp-7) auto var(--sp-5);padding:0}
.cs-gate-go{margin-top:var(--sp-2)}
.cs-gate-go[hidden]{display:none}
.cs-err{margin:0;font-size:var(--fs-cta);line-height:var(--lh-snug);color:var(--error)}
.cs-by{display:flex;align-items:center;gap:var(--sp-3);margin-top:var(--sp-6)}
.cs-av{flex:none;display:grid;place-items:center;width:44px;height:44px;border-radius:50%;background:var(--surface);color:var(--fg)}
.cs-av svg{width:20px;height:20px}
.cs-av img{width:100%;height:100%;border-radius:50%;object-fit:cover}
.cs-by p{margin:0;display:grid;gap:var(--sp-0);font-size:var(--fs-m);line-height:var(--lh-snug)}
.cs-by b{font-weight:var(--w-medium)}.cs-by span{color:var(--fg-2)}
/* the facts sit in the band Medium keeps for its actions: hairlines above and below */
.cs-specs{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--sp-4) var(--sp-5);margin:var(--sp-5) auto 0;
  padding:var(--sp-4) 0;border-block:1px solid var(--line)}
.cs-specs dt{font-size:var(--fs-cta);line-height:var(--lh-snug);color:var(--fg-2)}
.cs-specs dd{margin:var(--sp-0) 0 0;font-size:var(--fs-s);line-height:var(--lh-snug)}

/* pictures: the column's width, the opening one wider; captions centred */
.cs-fig{margin-block:0;width:min(var(--pic),100vw - 2 * var(--gut));margin-left:50%;translate:-50% 0}   /* pictures step out past the text */
.cs-hero-fig{width:min(var(--wide),100vw - 2 * var(--gut));margin:var(--sp-7) 0 var(--sp-7) 50%;translate:-50% 0}
.cs-fig figcaption,.cs-chart figcaption,.cs-note{margin:var(--sp-3) auto 0;max-width:var(--col);font-size:var(--fs-s);line-height:var(--lh-body);text-align:center;color:var(--fg-2);text-wrap:balance}
.cs-stage{display:grid;place-items:center;container-type:inline-size}
.cs-hero-fig .cs-stage{padding:clamp(20px,5%,64px) clamp(20px,7%,96px);background:var(--studio)}
.cs-hero-fig .cs-stage:has(.cs-hphoto){padding:0}
.cs-hphoto{display:block;width:100%;overflow:hidden}
.cs-hphoto img{display:block;width:100%;height:auto}
.cs-plate img{display:block;width:auto;max-width:100%;height:auto;max-height:640px;margin:0 auto;object-fit:contain}   /* never larger than the image itself, nor taller than a screen's worth */
.cs-small .cs-plate img{max-height:380px}
.cs-zoom-wrap{position:relative;display:block;width:fit-content;max-width:100%;margin:0 auto}
.cs-flow .win{box-shadow:0 0 0 1px var(--line)}
.cs-flow .cs-plate img{box-shadow:0 0 0 1px var(--line)}
/* a chart drawn like a Mixpanel report: a card with the report's name, a
   date pill, a line/bar switch, a legend, and a tooltip that follows you */
.mp .mp-card{position:relative;padding:16px 18px 12px;border-radius:12px;background:var(--bg);box-shadow:inset 0 0 0 1px var(--line),0 1px 2px rgba(10,10,10,.04);font-family:var(--f);text-align:left}
.mp-top{display:flex;flex-wrap:wrap;align-items:flex-start;justify-content:space-between;gap:10px 16px}
.mp-crumb{display:block;margin-bottom:3px;font:var(--w-medium) 11px/1.2 var(--f);color:var(--fg-2)}
.mp-title b{font:var(--w-semibold) 15px/1.25 var(--f);letter-spacing:-.01em}
.mp-tools{display:flex;align-items:center;gap:8px}
.mp-pill{display:inline-flex;align-items:center;gap:6px;height:28px;padding:0 10px;border-radius:7px;box-shadow:inset 0 0 0 1px var(--line);font:var(--w-medium) 12px/1 var(--f);color:var(--fg-2);white-space:nowrap}
.mp-pill svg{width:13px;height:13px}
.mp-seg{display:inline-flex;padding:2px;border-radius:8px;background:color-mix(in srgb,var(--fg) 6%,transparent)}
.mp-seg button{height:24px;padding:0 10px;border:0;border-radius:6px;background:none;font:var(--w-medium) 12px/1 var(--f);color:var(--fg-2);cursor:pointer}
.mp-seg button[aria-pressed="true"]{background:var(--bg);color:var(--fg);box-shadow:0 1px 2px rgba(10,10,10,.14)}
.mp-meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px 14px;margin:12px 0 6px;font:var(--w-medium) 12px/1.3 var(--f);color:var(--fg-2)}
.mp-metric{padding:3px 8px;border-radius:6px;background:color-mix(in srgb,#7856FF 10%,transparent);color:#6B4EF0}
.mp-key{display:inline-flex;align-items:center;gap:6px}.mp-key i{width:8px;height:8px;border-radius:2px}
.mp-plot svg{display:block;width:100%;height:auto;overflow:visible;font-family:var(--f)}
.mp-plot text{fill:var(--fg-2);font-size:11px}
.mp-plot .grid{stroke:var(--line)}
.mp-plot .hit{fill:transparent;cursor:crosshair}
.mp-tip{position:absolute;z-index:2;min-width:132px;padding:8px 10px;border-radius:8px;background:var(--bg);box-shadow:0 0 0 1px var(--line),0 10px 28px -10px rgba(10,10,10,.3);
  font:12px/1.35 var(--f);pointer-events:none;translate:-50% calc(-100% - 12px);white-space:nowrap}
.mp-tip b{display:block;margin-bottom:4px;font-weight:var(--w-semibold)}
.mp-tip span{display:flex;align-items:center;justify-content:space-between;gap:14px;color:var(--fg-2)}
.mp-tip span i{display:inline-block;width:8px;height:8px;margin-right:6px;border-radius:2px}
.mp-tip strong{color:var(--fg);font-weight:var(--w-semibold);font-variant-numeric:tabular-nums}
@media(prefers-color-scheme:dark){.mp-metric{color:#A996FF}}
/* a video set into a screen: it sits exactly over the screen's own picture, and plays in place */
.cs-vwrap{position:relative;width:fit-content;max-width:100%;margin:0 auto}
.win-screen .cs-vwrap{width:100%}.win-screen .cs-vwrap > img{display:block;width:100%;height:auto}
.cs-vbox{position:absolute;overflow:hidden;border-radius:clamp(3px,.6vw,8px);background:#000}
.cs-vbox video{display:block;width:100%;height:100%;object-fit:cover}
/* pictures that open full screen (site.js caseZoom): a zoom cursor, and an
   expand button in the corner on hover, always there on touch screens */
.cs-zoomable{position:relative}
.cs-zoomable img{cursor:zoom-in}
.cs-zoom-btn{position:absolute;top:var(--sp-2);right:var(--sp-2);z-index:2;display:grid;place-items:center;width:32px;height:32px;padding:0;border:0;border-radius:50%;
  background:rgba(10,10,10,.62);color:#fff;cursor:zoom-in;opacity:0;transition:opacity .2s}
.cs-zoom-btn svg{width:14px;height:14px}
.cs-zoomable:hover .cs-zoom-btn,.cs-zoomable:focus-within .cs-zoom-btn{opacity:1}
@media(hover:none){.cs-zoom-btn{opacity:.85}}
.cs-ms figure .cs-zoom-btn{width:28px;height:28px}
/* the full-screen view: the picture as large as the screen allows, its caption beneath */
.cs-zoom{position:fixed;inset:0;z-index:100;display:grid;place-items:center;padding:56px clamp(12px,5vw,88px) 40px;
  background:rgba(12,12,14,.86);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}
.cs-zoom[hidden]{display:none}
.cs-zoom.opening{animation:vIn .3s ease both}
.cs-zoom.closing{animation:vOut .2s ease-in both}
.cs-zoom-fig{margin:0;display:grid;justify-items:center;gap:var(--sp-3);max-width:100%}
.cs-zoom-fig img{display:block;max-width:min(1600px,100%);max-height:calc(100dvh - 150px);width:auto;height:auto;border-radius:6px;background:#fff;box-shadow:0 20px 60px -20px rgba(0,0,0,.6)}
.cs-zoom-fig figcaption{max-width:72ch;font:var(--w-regular) var(--fs-s)/var(--lh-body) var(--f);color:rgba(255,255,255,.82);text-align:center;text-wrap:balance}
.cs-zoom-fig.swap img{animation:riseIn .35s var(--ease) both}
.cs-zoom-n{position:absolute;top:var(--sp-4);left:50%;translate:-50% 0;font:var(--w-medium) var(--fs-small)/1 var(--f);letter-spacing:var(--tr-caps);color:rgba(255,255,255,.7);font-variant-numeric:tabular-nums}
.cs-zoom-x,.cs-zoom-go{position:absolute;display:grid;place-items:center;width:44px;height:44px;padding:0;border:0;border-radius:50%;background:rgba(255,255,255,.12);
  color:#fff;font:var(--w-regular) 18px/1 var(--f);cursor:pointer;transition:background .15s}
@media(hover:hover){.cs-zoom-x:hover,.cs-zoom-go:hover{background:rgba(255,255,255,.24)}}
.cs-zoom :focus-visible{outline-color:#fff}   /* the site's black ring would vanish on the dark backdrop */
.cs-zoom-x{top:var(--sp-3);right:var(--sp-3)}
.cs-zoom-go{top:50%;translate:0 -50%}
.cs-zoom-go.prev{left:var(--sp-3)}.cs-zoom-go.next{right:var(--sp-3)}
@media(max-width:600px){.cs-zoom{padding:56px var(--sp-2) 72px}.cs-zoom-go{top:auto;bottom:var(--sp-3);translate:none}.cs-zoom-go.prev{left:calc(50% - 56px)}.cs-zoom-go.next{right:calc(50% - 56px)}}

/* a slot for a picture still to come */
.cs-ph-box{display:grid;place-content:center;justify-items:center;gap:var(--sp-2);background:var(--surface);color:var(--fg-2);font-size:var(--fs-s);
  box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--line) 80%,var(--bg))}
.cs-ph-box svg{width:28px;height:28px;opacity:.7}

/* chapters: a small numbered label, a sans heading, a hairline between */
.cs-sec{scroll-margin-top:92px}
.cs-sec + .cs-sec{margin-top:var(--sp-8);padding-top:var(--sp-8);border-top:1px solid color-mix(in srgb,var(--line) 60%,var(--bg))}   /* a faint hairline between chapters */
@media(max-width:600px){.cs-sec{scroll-margin-top:80px}}
.cs-sec-h{display:grid;gap:var(--sp-1)}
.cs-kick{margin:0;font-size:var(--fs-m);line-height:var(--lh-body);color:var(--fg-2)}
.cs-sec-h h2{margin:0;font:var(--w-bold) clamp(24px,2.3vw,30px)/var(--lh-d2) var(--f);letter-spacing:var(--tr-d2);text-wrap:balance}

/* the reading text: Source Serif at 20px on a 1.6 measure */
.cs-sec :is(.cs-lead,.cs-points li){font:var(--w-regular) var(--read)/var(--lh-read) var(--serif);letter-spacing:var(--tr-read);color:var(--fg)}
.cs-lead{margin:0;text-wrap:pretty}
.cs-sec b{font-weight:var(--w-semibold)}
.cs-sec > * + *,.cs-split > div > * + *,.cs-split > div + div,.cs-idea > * + *,.cs-idea .cs-split{margin-top:var(--sp-5)!important}   /* one rhythm between blocks */
.cs-sec-h + *{margin-top:var(--sp-4)!important}
.cs-sec > .cs-fig,.cs-sec > .cs-grid,.cs-split .cs-fig,.cs-split .cs-chart{margin-block:var(--sp-6)!important}
.cs-points{margin:0;padding:0 0 0 1.4em;display:grid;gap:var(--sp-2)}
.cs-points li{padding-left:.2em}
.cs-points li::marker{color:var(--fg)}
.cs-h4{margin:0;font:var(--w-bold) clamp(18px,1.4vw,20px)/var(--lh-tight) var(--f);letter-spacing:var(--tr-title)}
.cs-sec .cs-h4{margin-top:var(--sp-6)!important}
.cs-h4 + *{margin-top:var(--sp-3)!important}
/* the turning point, as a pull quote: centred on the page. A real quote gets one large curly mark
   above it in the case's accent colour (build.py strips the typed marks) */
.cs-moment{margin:0 auto;max-width:34ch;padding:var(--sp-5) 0;text-align:center;font:italic var(--w-regular) clamp(24px,2.4vw,30px)/var(--lh-lede) var(--serif);letter-spacing:var(--tr-serif);text-wrap:balance}
.cs-moment .cs-cite{display:block;margin-top:var(--sp-3);font:normal var(--w-regular) var(--fs-s)/var(--lh-body) var(--f);letter-spacing:var(--tr-fit);color:var(--fg-2)}
.cs-moment.is-quote::before{content:"“";display:block;height:.5em;margin-bottom:var(--sp-3);font:var(--w-regular) 3.2em/1 var(--serif);font-style:normal;color:var(--fg)}   /* the quote mark in the text colour: black (white in dark mode) */
.cs-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--sp-5);width:min(var(--wide),100vw - 2 * var(--gut));margin-left:50%;translate:-50% 0}
.cs-grid .cs-chart,.cs-grid .cs-fig{width:auto;margin-left:0;translate:none}   /* two pictures side by side */
.cs-grid:has(> :nth-child(3)){grid-template-columns:repeat(4,minmax(0,1fr))}   /* phone screens, four across */
/* the charts: no panel, just the drawing and its caption */
.cs-chart{margin:0;display:flex;flex-direction:column}
.cs-chart figcaption{order:2}.cs-chart .cs-note{order:3;margin-top:var(--sp-0)}
.cs-chart svg{display:block;width:100%;height:auto;font-family:var(--f)}
/* the facts in a row, and the small lists, set as quiet tables */
.cs-stats{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:var(--sp-4);margin:0;padding:var(--sp-4) 0;border-block:1px solid var(--line)}   /* the figures share the row evenly, however many there are */
.cs-stats dt{font:var(--w-bold) clamp(28px,2.6vw,36px)/1 var(--f);letter-spacing:var(--tr-num)}
.cs-stats dd{margin:var(--sp-2) 0 0;font-size:var(--fs-s);line-height:var(--lh-snug)}
.cs-stats small{display:block;margin-top:var(--sp-0);font-size:var(--fs-cta);color:var(--fg-2)}
:is(.cs-ms,.cs-steps,.cs-deck){list-style:none;margin:0;padding:0}
.cs-ms li{display:grid;grid-template-columns:120px minmax(0,1fr);gap:0 var(--sp-4);padding:var(--sp-3) 0;border-top:1px solid var(--line);font-size:var(--fs-m);line-height:var(--lh-body)}
.cs-ms li:last-child{border-bottom:1px solid var(--line)}
.cs-ms span{grid-row:span 2;font-size:var(--fs-cta);color:var(--fg-2)}.cs-ms li.on span{color:var(--accent)}
.cs-ms b{font-weight:var(--w-semibold)}.cs-ms p{margin:0;color:var(--fg-2)}
.cs-ms li.has-pic{grid-template-columns:120px minmax(0,1fr) minmax(0,220px);grid-template-rows:auto 1fr;align-items:start}
.cs-ms figure{grid-column:3;grid-row:1 / span 2;margin:0;padding:6px;border-radius:6px;background:var(--surface);box-shadow:0 0 0 1px var(--line)}
.cs-ms figure img{display:block;width:auto;max-width:100%;height:auto;max-height:240px;margin:0 auto;border-radius:3px}   /* the whole picture, wide or tall, never cropped */
.cs-steps{display:flex;flex-wrap:wrap;gap:var(--sp-2) 0;font-size:var(--fs-s);line-height:var(--lh-body);color:var(--fg-2)}
.cs-steps li{display:flex;gap:var(--sp-1)}
.cs-steps li + li::before{content:"→";margin:0 var(--sp-3)}
.cs-steps span{color:var(--fg)}.cs-steps li.ai,.cs-steps li.ai span{color:var(--accent)}
.cs-deck{columns:2;column-gap:var(--sp-6);font-size:var(--fs-m);line-height:var(--lh-body)}
.cs-deck li{display:flex;gap:var(--sp-3);padding:var(--sp-1) 0;break-inside:avoid}
.cs-deck span{min-width:20px;color:var(--fg-2);font-variant-numeric:tabular-nums}
/* each idea: a small label, its heading, then the story */
.cs-idea{margin-top:var(--sp-7)!important}
.cs-idea h3{margin:var(--sp-1) 0 0;font:var(--w-bold) clamp(20px,1.8vw,24px)/var(--lh-tight) var(--f);letter-spacing:var(--tr-d2);text-wrap:balance}
@media(max-width:700px){
  .cs-specs{grid-template-columns:repeat(2,minmax(0,1fr))}
  .cs-stats{grid-auto-flow:row;grid-template-columns:repeat(2,minmax(0,1fr))}
  .cs-grid{grid-template-columns:minmax(0,1fr);width:100%;margin-left:0;translate:none}
  .cs-grid:has(> :nth-child(3)){grid-template-columns:repeat(2,minmax(0,1fr))}
  .cs-deck{columns:1}
  .cs-ms li,.cs-ms li.has-pic{grid-template-columns:minmax(0,1fr)}.cs-ms span{grid-row:auto}.cs-ms figure{grid-column:1;grid-row:auto;margin-top:var(--sp-3)}}

/* the viewer's password step for a protected case study, in the exact spot
   and at the exact height of the button it replaces (site.js copies the
   height into --h): a square field with its black outline, and the unlock
   arrow in its own cell, which site.js flies the button's arrow into.  */
.pw-form{position:relative;display:grid;gap:var(--sp-2);width:100%;max-width:380px}
.pw-form[hidden]{display:none}
.pw-field{--pw-line:var(--fg);display:flex;align-items:center;gap:var(--sp-1);width:min(340px,100%);height:var(--h,40px);padding:0 0 0 var(--sp-3);border-radius:0;background:var(--bg);
  position:relative;box-shadow:inset 0 0 0 1px var(--pw-line)}   /* always drawn active: white, with the black outline */
.pw-form.err .pw-field{--pw-line:var(--error)}
.pw-form .pw-field input{flex:1;min-width:0;height:100%;padding:0;border:0;border-radius:0;background:transparent;color:var(--fg);
  font:var(--w-regular) var(--fs-cta)/var(--lh-tight) var(--f);letter-spacing:var(--tr-ui);outline:0;box-shadow:none;caret-color:var(--fg)}
.pw-field input::placeholder{color:var(--fg-2);opacity:.85}
.pw-field input::selection{background:color-mix(in srgb,var(--fg) 14%,transparent);color:var(--fg)}   /* a wrong password is selected for retyping, in grey not blue */
.pw-field input[type=password]:not(:placeholder-shown){letter-spacing:.22em}
.pw-eye{flex:none;display:grid;place-items:center;width:28px;height:28px;padding:0;border:0;background:none;color:color-mix(in srgb,var(--fg) 72%,var(--bg));cursor:pointer;transition:color .15s}
@media(hover:hover){.pw-eye:hover{color:var(--fg)}}
.pw-eye svg{width:16px;height:16px}
.pw-eye .hide,.pw-eye[aria-pressed="true"] .show{display:none}
.pw-eye[aria-pressed="true"] .hide{display:block}
/* the arrow sits in its own square cell, set off from the eye by a divider
   in the outline's colour; it fills black the instant you hover, like the CTA */
.pw-go{flex:none;align-self:stretch;display:grid;place-items:center;width:var(--h,40px);margin:0 0 0 2px;padding:0;border:0;border-left:1px solid var(--pw-line);
  border-radius:0;background:none;color:var(--fg);cursor:pointer;transition:none}
.pw-field:has(input:not(:placeholder-shown)) .pw-go{background:var(--fg);color:var(--bg)}   /* active as soon as something's typed */
@media(hover:hover){.pw-go:hover{background:var(--fg);color:var(--bg)}}
.pw-go svg{width:16px;height:auto;transition:translate .25s var(--ease)}
@media(hover:hover){.pw-go:hover svg{translate:2px 0}}
.pw-go:active{background:var(--fg);color:var(--bg)}
.pw-go .spin{display:none;width:14px;height:14px;border-radius:50%;border:1.5px solid color-mix(in srgb,currentColor 30%,transparent);border-top-color:currentColor;
  animation:spin .7s linear infinite}
.pw-form.busy .pw-go .spin{display:block}
.pw-form.busy .pw-go svg:not(.spin){display:none}
.pw-form.busy input{opacity:.55}
@keyframes spin{to{transform:rotate(360deg)}}
/* the button stretching into the field: a frame that grows from the button's
   width while its black fades to white, and the button's label and arrow
   flying to their places; the field's own outline waits until it lands */
.pw-field > :not(.pw-frame,.pw-fly){position:relative;z-index:1}
.pw-field.morphing{box-shadow:none;background:transparent}
.pw-frame{position:absolute;left:0;top:0;bottom:0;z-index:0;pointer-events:none;will-change:width}
.pw-fly{position:absolute;z-index:2;display:flex;align-items:center;white-space:nowrap;pointer-events:none;will-change:transform,opacity;letter-spacing:var(--tr-ui)}
.pw-fly svg{display:block;width:100%;height:auto}
/* in place of the browser's "Please fill in this field": a small white tag
   with black text, in both modes, hanging off the field, square like the button, pointing up at the input */
.pw-tip{position:absolute;left:0;top:calc(100% + var(--sp-3));z-index:5;margin:0;padding:var(--sp-2) var(--sp-3);background:#FFFFFF;color:#0A0A0A;
  font:var(--w-medium) var(--fs-cta)/var(--lh-tight) var(--f);letter-spacing:var(--tr-ui);white-space:nowrap;box-shadow:inset 0 0 0 1px #0A0A0A,0 10px 24px -12px rgba(0,0,0,.3);pointer-events:none;
  animation:tipIn .28s var(--ease) both}
.pw-tip::before,.pw-tip::after{content:"";position:absolute;left:var(--sp-3);bottom:100%;border:6px solid transparent;border-bottom-color:#0A0A0A}
.pw-tip::after{margin-bottom:-1.5px;border-bottom-color:#FFFFFF}   /* the notch: a white point inside a black outline */
@media(prefers-color-scheme:dark){.pw-tip{box-shadow:0 10px 24px -12px rgba(0,0,0,.6)}}   /* white on a dark page needs no outline */
.pw-tip.out{animation:tipOut .2s ease both}
@keyframes tipIn{from{opacity:0;translate:0 -4px}}
@keyframes tipOut{to{opacity:0;translate:0 -4px}}

@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation:none!important;transition:none!important}
  .js [data-reveal],.js .ix-cell[data-reveal] :is(.win,.pg,.ix-photo img,.ix-l),.js .ix-name .ch{opacity:1;transform:none;translate:none;scale:none;filter:none}
}


/* ═══ every screen ════════════════════════════════════════════════
   One layer that keeps the site right from a 320px phone to a 2560px
   monitor: touch gets 44px targets and no sticky hover, phones get
   legible labels, large screens scale the type up instead of leaving
   laptop-sized text adrift in the space.                              */

/* touch: every control at least 44px, the size a fingertip needs */
@media(pointer:coarse){
  :root{--fs-cta:14px}
  .ix-filter button{min-height:44px;padding-inline:var(--sp-4)}
  .btn{min-height:44px;padding-inline:var(--sp-4)}
  .ix-v-btn,.ix-v-dots button{min-height:44px}
  .ix-v-x{min-width:44px;min-height:44px}
  .cs-bar :is(a,button){display:inline-flex;align-items:center;min-height:44px}
  .ix-links a{display:inline-block;padding-block:var(--sp-1)}
}

/* phones and tablets: labels up a step so small caps stay readable */
@media(max-width:900px){
  :root{--fs-label:12px}
  .ix-filter .ct{min-width:20px;height:20px}
}
/* the filters scroll sideways on phones: a soft edge says there's more */
@media(max-width:560px){
  .ix-filter::after{content:"";position:sticky;right:calc(var(--gut) * -1);flex:none;align-self:stretch;width:40px;margin:0 calc(var(--gut) * -1) 0 -40px;
    background:linear-gradient(90deg,color-mix(in srgb,var(--bg) 0%,transparent),var(--bg) 75%);pointer-events:none}
}

/* large screens: the type grows with the room */
@media(min-width:1680px){
  :root{--fs-label:12px;--fs-small:14px;--fs-cta:14px;--fs-s:15px;--fs-m:16px;--fs-title:16px;--fs-lede:19px}
  .btn{padding:var(--sp-3) var(--sp-4)}
  .cs-bar,.cs{--col:760px;--pic:1000px;--wide:1120px;--read:21px}
  .cs-title{font-size:52px}.cs-gate-h{font-size:38px}
  .cs-lede{font-size:24px}
  .cs-sec-h h2{font-size:34px}
  .cs-h4{font-size:22px}.cs-idea h3{font-size:26px}.cs-moment{font-size:32px}.cs-stats dt{font-size:40px}
}
@media(min-width:2200px){
  :root{--fs-label:14px;--fs-small:16px;--fs-cta:16px;--fs-s:17px;--fs-m:18px;--fs-title:19px;--fs-lede:23px;--gap:40px}
  .btn{padding:var(--sp-3) var(--sp-5)}
  .btn svg{width:22px}
  .cs-bar,.cs{--col:880px;--pic:1160px;--wide:1300px;--read:23px}
  .cs-title{font-size:64px}.cs-gate-h{font-size:44px}
  .cs-lede{font-size:28px}
  .cs-sec-h h2{font-size:42px}
  .cs-av{width:52px;height:52px}
  .cs-h4{font-size:25px}.cs-idea h3{font-size:30px}.cs-moment{font-size:38px}.cs-stats dt{font-size:46px}
}

/* case pictures load as the reader reaches them (site.js caseMedia): a faint
   tint holds each one's space until it arrives, then it fades in */
.cs img[data-enc]:not(.is-in){background:color-mix(in srgb,var(--fg) 5%,transparent)}
.cs img[data-enc].is-in{animation:cs-in .35s ease-out}
.cs-vbox.loading .ix-play{opacity:.5;pointer-events:none}
@keyframes cs-in{from{opacity:0}to{opacity:1}}
@media (prefers-reduced-motion:reduce){.cs img[data-enc].is-in{animation:none}}
