/* ============================================================
   LIGHT EDITORIAL — neutral container, content forward
   Motif: role chips (Hands-on / Coach / Enabler)
   Type: Hanken Grotesk (display) + Inter (body).
   Colour + shape follow Airbnb: Rausch accent, #222 ink, hairline #ebebeb,
   12px cards, pill CTAs, soft low-opacity shadows.
   ============================================================ */
:root{
  --paper:#ffffff;
  --panel:#f7f7f7;
  --ink:#222222;
  --ink-2:#717171;
  --ink-3:#b0b0b0;
  --line:#ebebeb;
  --line-strong:#dddddd;
  /* Rausch: --accent clears 5.1:1 on white for text and lines,
     --accent-brand is the fill colour, --accent-grad is the CTA sweep */
  --accent:#d70466;
  --accent-brand:#ff385c;
  --accent-soft:#fff4f6;
  --accent-glow:rgba(255,56,92,.12);
  --accent-dark:#ff5a76;     /* on the dark deck */
  --accent-grad:linear-gradient(90deg,#e61e4d 0%,#e31c5f 50%,#d70466 100%);
  --shadow-1:0 2px 8px rgba(0,0,0,.06);
  --shadow-2:0 6px 16px rgba(0,0,0,.12);
  --radius:12px;
  --display:"Hanken Grotesk", Inter, -apple-system, "Helvetica Neue", sans-serif;
  --sans:"Inter", -apple-system, "Segoe UI", sans-serif;
  --maxw:1080px;
  --textw:680px;
  /* shared width for the wide section blocks so their right edges stay flush */
  --blockw:880px;
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
body{font-family:var(--sans);background:var(--paper);color:var(--ink);
  font-size:17px;line-height:1.65;-webkit-font-smoothing:antialiased}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
a:focus-visible,button:focus-visible,.tile:focus-visible{outline:2px solid var(--ink);outline-offset:3px;border-radius:6px}
img{max-width:100%;display:block}

.wrap{max-width:var(--maxw);margin:0 auto;padding:0 24px}
.textcol{max-width:var(--textw)}

/* ---------- nav ---------- */
.nav{display:flex;justify-content:space-between;align-items:center;padding:22px 24px;border-bottom:1px solid var(--line)}
/* Mark carries the brand colour, the name stays ink. --accent-brand is a fill token
   (3.52:1, see the theme table): as 19px text it was the one place on the site using it
   for type, and it was thinnest exactly where the About plate turns magenta. */
.nav .logo{display:inline-flex;align-items:center;gap:9px;text-decoration:none}
.nav .logo .lmark{height:22px;width:auto;flex:none;color:var(--accent-brand)}
.nav .logo .lname{font-family:var(--display);font-weight:700;font-size:19px;
  color:var(--ink);letter-spacing:-.01em;line-height:1}
.nav .logo:hover{text-decoration:none}
.nav .links a{color:var(--ink);margin-left:26px;font-size:15px;font-weight:600;padding:8px 12px;border-radius:100px}
.nav .links a:hover{background:var(--panel);text-decoration:none}

/* ---------- role chips (site motif) ---------- */
.chip{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;font-weight:600;
  letter-spacing:0;color:var(--ink);
  border:1px solid var(--line-strong);border-radius:100px;padding:4px 12px;background:var(--paper)}
.chip::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--accent-brand)}

/* ---------- hero ---------- */
.hero{padding:96px 24px 64px}
.hero h1{font-family:var(--display);font-weight:700;font-size:clamp(36px,5vw,56px);
  line-height:1.14;letter-spacing:-.012em;max-width:880px}
.hero .sub{margin-top:26px;font-size:19px;color:var(--ink-2);max-width:620px}
.hero .sub strong{color:var(--ink);font-weight:600}

/* ---------- metric strip ---------- */
.metrics{display:flex;flex-wrap:wrap;gap:0;border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);margin:56px 0}
.metrics .m{flex:1 1 150px;padding:26px 20px 26px 0;min-width:140px}
.metrics .m + .m{padding-left:24px;border-left:1px solid var(--line)}
.metrics .v{font-family:var(--display);font-size:28px;font-weight:700;line-height:1;letter-spacing:-.02em}
.metrics .l{font-size:13px;color:var(--ink-3);margin-top:8px}
.metrics a.v{color:var(--ink)}

/* ---------- landing positioning ----------
   Three unlike things (a tag list, a trajectory, a self-assessment) in one shape, so the
   row still scans. Labels are sentence case, not the ALL-CAPS eyebrow this pattern
   usually reaches for — that is on the banned list. */
.pos{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px;margin:56px 0}
/* three tiers: what kind of fact (label) → the fact itself, at display size (terms) →
   what grounds it (note). The three tiers line up across the row so the cards read as a
   set even when the terms wrap to different depths. */
.pcard{display:flex;flex-direction:column;padding:24px 22px}
.plabel{font-size:12.5px;font-weight:600;color:var(--ink-3);margin-bottom:13px}
/* Flex, one item per term, because joining the terms straight onto the separators left
   the browser no break opportunity: "Strategist/Enabler/Builder" counted as a single word
   and ran past the card (clipped by overflow:hidden), while "E-commerce" could only break
   at its own hyphen. Each item carries its trailing separator and is nowrap, so terms stay
   whole, wrap between each other, and a separator never opens a line. */
.pterms{display:flex;flex-wrap:wrap;align-items:baseline;row-gap:.12em;
  font-family:var(--display);font-weight:700;font-size:clamp(18px,1.55vw,21px);
  line-height:1.28;letter-spacing:-.015em;color:var(--ink)}
.pterms .term{white-space:nowrap}
/* muted, not accent: at this size a coloured separator pulled the eye to the punctuation
   instead of the terms it separates */
.pterms .sep{color:var(--ink-3);font-weight:400;font-style:normal;margin:0 .3em}
.pnote{padding-top:14px;font-size:13.5px;line-height:1.55;color:var(--ink-2)}
/* Notes start on one line across the row, not end on one. That needs the block above them
   to be a fixed height, so `terms` reserves two lines — its measured maximum at every
   multi-column width. Single column has nothing to align to, so it keeps its natural
   height rather than carrying dead space in each card. */
@media (min-width:641px){.pterms{min-height:2.56em}}
@media (max-width:640px){.pos{margin-top:40px}.pcard{padding:18px}}

/* ---------- case cards ---------- */
.cases{padding:24px 24px 96px}
.cases h2, .section-title{font-family:var(--display);font-size:22px;font-weight:700;letter-spacing:-.01em;
  color:var(--ink);margin-bottom:22px}
/* Section break between the two card groups. 22px is what a heading gets before its OWN
   content (the About vertical-rhythm note codifies that), so a heading that opens a new
   group needs a step that is clearly larger, and 56px was below even the 64px block rhythm.
   96px matches .cases' own bottom padding, so the landing closes on the same interval it
   uses to separate. Was an inline margin-top in render.js: layout does not belong there. */
.cases h2:not(:first-of-type){margin-top:96px}
.card-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:20px}
.card{display:flex;flex-direction:column;border-radius:var(--radius);color:var(--ink);
  transition:opacity .22s ease}
.card:hover{opacity:.82;text-decoration:none}          /* image + type dim together */
.card .thumb{aspect-ratio:20/13;border-radius:var(--radius);background:var(--panel);
  display:flex;align-items:center;justify-content:center;color:#666;font-size:13px;
  position:relative;overflow:hidden}
/* the dark placeholder is its own layer on the label, shown only when the thumb has no
   image (render.js drops a broken img). Nothing is painted behind a real thumbnail, so
   the rounded corners no longer bleed a hairline arc of it. */
.card .thumb > span{position:absolute;inset:0;border-radius:inherit;
  display:flex;align-items:center;justify-content:center;text-align:center;padding:0 12px;
  background:linear-gradient(135deg,#1c1c1c,#333)}
.card .thumb:has(img) > span{display:none}
.card .thumb img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:left top;
  border-radius:inherit;                               /* clip the image itself, not just the box */
  transition:transform .4s cubic-bezier(.2,.6,.2,1)}

.card:hover .thumb img{transform:scale(1.045)}         /* slow zoom in, clipped by the rounded thumb */
.card .cbody{display:flex;flex-direction:column;flex:1;padding:14px 4px 20px}
.card .co{font-size:13px;color:var(--ink-3);margin-bottom:6px}
.card h3{font-family:var(--display);font-size:18px;font-weight:700;line-height:1.35;margin-bottom:10px;letter-spacing:-.01em}
/* margin-top:auto — a two-line title used to push its own chip row down, so the chips
   across a row sat at different heights. Now they share one baseline. */
.card .cmeta{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-top:auto;padding-top:4px}
.card .stat{font-size:13px;color:var(--ink-2)}
.card.compact .thumb{aspect-ratio:21/9}
.featured-grid{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}

/* The card behaves like every other one — hover, zoom, pointer — and the badge is the
   only signal; the click opens the dialog instead of the page. */
.revising{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;font-weight:600;
  color:var(--ink-2);border:1px dashed var(--line-strong);border-radius:100px;padding:4px 12px}
.revising::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--ink-3)}

/* ---------- case page ---------- */
.case-head{padding:72px 24px 40px}
.case-head .co{color:var(--ink-3);font-size:15px;margin-bottom:14px}
.case-head h1{font-family:var(--display);font-weight:700;letter-spacing:-.015em;font-size:clamp(30px,4.2vw,46px);
  line-height:1.15;letter-spacing:-.01em;max-width:820px;margin-bottom:22px}
.case-head .cmeta{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
.case-head .stat{color:var(--ink-2);font-size:14px}

.summary4{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
  gap:20px;padding:44px 0;border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.summary4 .s h4{font-size:13px;font-weight:700;letter-spacing:.01em;text-transform:capitalize;
  color:var(--accent);margin-bottom:8px}
.summary4 .s p{font-size:15px;color:var(--ink-2);line-height:1.6}
/* company primer opener: sits under the Problem column, where a stranger first meets the
   company. A quiet inline link with a chevron, not a CTA — the case is what's being read. */
.whatlink{display:inline-flex;align-items:center;gap:5px;margin-top:12px;
  font:inherit;font-size:13.5px;font-weight:600;line-height:1.4;color:var(--accent);
  background:none;border:0;padding:0 0 3px;cursor:pointer;text-align:left;
  /* one continuous rule under label + chevron: text-decoration would underline the two
     flex children separately and break across the gap */
  border-bottom:1px solid transparent;transition:border-color .18s ease}
.whatlink::after{content:"›";font-size:15px;line-height:1}
.whatlink:hover{border-bottom-color:currentColor}

.gate{margin:56px 0;padding:36px;background:var(--panel);border-radius:var(--radius);text-align:center}
.gate p{color:var(--ink-2);margin-bottom:18px}
.btn{display:inline-block;background:var(--accent-grad);color:#fff;border:none;font-size:15px;
  font-weight:700;padding:14px 26px;border-radius:100px;cursor:pointer;transition:filter .15s}
.btn:hover{filter:brightness(1.06);text-decoration:none}
.btn.ghost{background:none;color:var(--ink);border:1px solid var(--ink);font-weight:600}
.btn.ghost:hover{background:var(--panel);filter:none}

.sections{padding:16px 24px 80px}
.sec{padding:56px 0;border-bottom:1px solid var(--line)}
.sec:last-child{border-bottom:none}
.sec .kind{font-size:13.5px;font-weight:600;letter-spacing:.01em;
  color:var(--ink-3);margin-bottom:14px}
.sec .kind.decision{color:var(--accent)}
.sec h2{font-family:var(--display);font-weight:700;letter-spacing:-.012em;font-size:clamp(23px,2.8vw,30px);
  line-height:1.2;margin-bottom:18px;max-width:720px}
.sec p{max-width:var(--textw);color:var(--ink-2)}
.sec .tradeoff{margin-top:18px;padding:16px 20px;background:var(--panel);
  border-radius:var(--radius);font-size:15px;max-width:var(--textw)}
.sec .tradeoff b{color:var(--accent)}
.sec .media{margin-top:32px;border:1px solid var(--line);border-radius:var(--radius);
  overflow:hidden;background:var(--panel)}
.sec .media img{display:block;max-width:100%;margin:0 auto}
.sec .media .ph-label{display:none}
/* click to enlarge (scroll view only, like the strips) — a dense diagram can't be read
   at article width. The placeholder box keeps the plain cursor: nothing to enlarge. */
.sec .media{cursor:zoom-in;transition:opacity .22s ease}
.sec .media:hover{opacity:.88}
.sec .media.ph,.sec .media.ph:hover{cursor:default;opacity:1}
/* enlarged: fill the viewport, but never below 980px — a diagram scaled to a narrow
   viewport gains nothing (measured: 1.05x), so past that it grows and the overlay pans.
   Capped at the artwork's natural 1760 so it is never upscaled into mush.
   margin:auto rather than align-items:center — centring a flex item that overflows
   clips its top out of the scroll range. */
.media-zoom{position:fixed;inset:0;z-index:60;background:rgba(17,17,17,.85);cursor:zoom-out;
  display:flex;overflow:auto;padding:4vh 3vw}
/* max-width:none is required: the base `img{max-width:100%}` would clamp the width below */
.media-zoom img{display:block;margin:auto;max-width:none;width:min(1760px,max(94vw,980px));height:auto;
  background:var(--paper);border-radius:var(--radius);box-shadow:var(--shadow-2)}
.sec .media.ph{aspect-ratio:16/9;background:linear-gradient(135deg,#1a1a1a,#2e2e2e);
  display:flex;align-items:center;justify-content:center;color:#777;font-size:14px}
.sec .media.ph .ph-label{display:block}
/* static screenshot lineup — iPhones on a plain card */
.shot-row{display:flex;gap:24px;flex-wrap:nowrap;justify-content:space-around;margin-top:32px;padding:36px 30px;
  border-radius:var(--radius);background:var(--paper);
  border:1px solid var(--line);box-shadow:var(--shadow-2)}
.shot{flex:1 1 0;min-width:0;max-width:238px}   /* ~110% of the previous size */
.shot figcaption{font-size:13px;font-weight:600;color:var(--ink-2);margin-bottom:12px;min-height:68px}
.slide .shot figcaption{min-height:62px}
/* who made it: neutral pill for inherited work, accent pill for my own */
.shot .tag,.proto .tag,.row-tag .tag{display:inline-flex;align-items:center;gap:6px;margin-top:7px;
  font-size:11px;font-weight:600;padding:3px 9px;border-radius:100px;
  border:1px solid var(--line-strong);color:var(--ink-2);white-space:nowrap}
.shot .tag.own,.proto .tag.own,.row-tag .tag.own{border-color:var(--accent);color:var(--accent)}
.shot .tag.own::before,.proto .tag.own::before,.row-tag .tag.own::before{content:"";width:5px;height:5px;border-radius:50%;
  background:var(--accent-brand)}
/* Panel-level attribution. The badge is a claim about the whole lineup, so it reads
   before the row instead of sitting under one phone's label as if it described only
   that shot. render.js hoists it whenever a row's tags do not tell the items apart. */
.row-tag{margin-top:32px}
.row-tag .tag{margin-top:0}
.row-tag + .shot-row{margin-top:10px}
/* the caption min-height reserves room for an inline badge so the frames still line up.
   Once the badge is hoisted every caption is a plain label, so give the height back. */
.row-tag + .shot-row .shot figcaption{min-height:34px}
.shot figcaption,.proto figcaption{display:flex;flex-direction:column;align-items:flex-start}
/* device: tall phone proportion, thin bezel, big corner radius */
.shot .frame{background:#0a0a0b;border-radius:12.5%/6.2%;padding:2.9%;
  box-shadow:inset 0 0 0 1.5px #2e2e30}
.shot .screen{position:relative;display:block;border-radius:10.5%/5.2%;overflow:hidden;
  aspect-ratio:375/772;background:var(--bg,#000)}
.shot .lay{position:absolute;left:0;right:0;overflow:hidden}
.shot .lay img,.shot .lay video{width:100%;height:auto;display:block}
.shot .solo{top:0;bottom:0}
/* screen height = 772 source rows, so a row count converts straight to a % */
.shot .top{top:0;height:calc(var(--split) * 100% / 772)}
.shot .bot{bottom:3.4%;height:calc((var(--srch) - var(--split)) * 100% / 772)}  /* home-indicator gutter */
.shot .bot img{margin-top:calc(var(--split) * -100% / 375)}
/* drawn iOS status bar covers the capture's own (and its "Figma" label) */
.shot .sbar{position:absolute;top:0;left:0;right:0;height:4.4%;background:var(--bg,#000);
  display:flex;align-items:center;justify-content:space-between;padding:0 7%}
.shot .sbar b{color:#fff;font-size:10px;font-weight:600;letter-spacing:.02em}
.shot .sbar .isl{position:absolute;left:50%;top:26%;transform:translateX(-50%);
  width:25%;height:52%;background:#111;border-radius:100px;box-shadow:inset 0 0 0 1px #1c1c1c}
.shot .sbar .stx{width:34px;height:9px}
.shot .solo::after{content:"";position:absolute;left:0;right:0;bottom:0;height:14%;
  background:linear-gradient(transparent,var(--bg,#000));pointer-events:none}
.shot .solo.nf::after{display:none}   /* source already matches 375:772, nothing to fade out */
.shot .hbar{position:absolute;bottom:1%;left:50%;transform:translateX(-50%);
  width:34%;height:4px;background:#fff;border-radius:4px;opacity:.92;z-index:2}
/* elevation grows with time, left to right */
.shot .frame{box-shadow:inset 0 0 0 1.5px #2e2e30,0 6px 16px rgba(0,0,0,.12)}

/* header teaser: the lineup as a blog-style hero + table of contents */
.case-head .teaser{margin-top:40px}
.case-head .teaser .shot-row{margin-top:0}
.case-head .teaser .tnote{font-size:13.5px;color:var(--ink-3);margin-top:14px}
.case-head .teaser .tnote strong{color:var(--ink-2);font-weight:600}
.shot.linked{cursor:pointer;transition:opacity .22s ease}
.shot.linked:hover{opacity:.82}     /* same dim language as the case cards */

/* prototype comparison row (scroll view) */
.proto-row{display:flex;gap:24px;flex-wrap:wrap;margin-top:32px}
.proto figcaption{font-size:13px;font-weight:600;color:var(--ink-2);margin-bottom:10px}
.phone-wrap{--s:.82;width:calc(375px*var(--s));height:calc(812px*var(--s));
  border-radius:calc(26px*var(--s));overflow:hidden;border:1px solid #2a2a2a;
  background:#000;box-shadow:0 10px 34px rgba(0,0,0,.16)}
.phone-wrap.active{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-soft),0 10px 34px rgba(0,0,0,.16)}
.phone-wrap iframe{width:375px;height:812px;border:0;display:block;
  transform:scale(var(--s));transform-origin:0 0}
@media (max-width:1100px){.phone-wrap{--s:.72}}
@media (max-width:640px){.proto-row{gap:18px}.phone-wrap{--s:.82}
  .shot-row{flex-wrap:wrap}.shot{max-width:none;min-width:190px}}
.sec .metric-row{display:flex;gap:14px;flex-wrap:wrap;margin-top:24px}
.sec .metric-row span{font-family:var(--display);font-size:20px;font-weight:700;letter-spacing:-.01em;
  padding:14px 20px;border:1px solid var(--line-strong);border-radius:var(--radius)}

.case-next{padding:48px 24px 96px;display:flex;justify-content:space-between;align-items:center;gap:16px;flex-wrap:wrap}

/* ---------- about ---------- */
/* 140px, not 150: with five tiles and a 10px gap that keeps them on one row down to
   a ~640px window instead of breaking to 4+1 at ~800. */
.dash{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:0;margin:0 0 64px;
  border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.dash .tile{border:none;padding:26px 20px 26px 0;
  font:inherit;color:inherit;background:transparent;text-align:left;width:100%}
.dash .tile + .tile{border-left:1px solid var(--line);padding-left:22px}
.dash .v{font-family:var(--display);font-size:34px;font-weight:700;line-height:1;letter-spacing:-.02em}
.dash .l{font-size:13px;color:var(--ink-3);margin-top:10px}
/* display:block — an inline <a> sits on a baseline while its <div> siblings are blocks,
   which put the linked tile's number a couple of pixels off the row. Same class of
   bug as the <button> tiles had: don't mix element types in one aligned row. */
.dash a.v{display:block;color:var(--ink)}
.dash a.v:hover{color:var(--accent);text-decoration:none}

.timeline{padding:32px 0 64px;max-width:760px}
.tl{display:grid;grid-template-columns:130px 1fr;gap:24px;padding:30px 0;border-bottom:1px solid var(--line)}
/* career logos as app icons: one rounded square per entry, sized off the h3 so the
   icon and the org name read as a single row. The hairline matters — Apple/Intuit/
   Samsung are white-on-white marks and would have no shape without it, while CMU and
   Flipster bleed their colour to the edge. box-sizing keeps the border inside 44px. */
.tl-main{display:flex;gap:16px;align-items:flex-start}
.tl-logo{width:44px;height:44px;flex:none;border-radius:10px;object-fit:cover;
  background:#fff;border:1px solid rgba(20,18,28,.08);
  box-shadow:0 1px 3px rgba(31,17,45,.12);margin-top:2px}
.tl .era{font-size:14px;color:var(--ink-3);padding-top:4px}
.tl h3{font-family:var(--display);font-size:20px;font-weight:700;margin-bottom:6px;letter-spacing:-.01em}
.tl .role{color:var(--ink-2);font-size:15px;margin-bottom:10px}
/* CV-style bullets. The dot is smaller and softer than the old single `learned` line
   carried: four full-strength accent dots per entry read as a warning list. */
.tl-points{list-style:none;margin-top:11px;display:grid;gap:8px}
.tl-points li{font-size:14.5px;line-height:1.55;color:var(--ink-2);
  display:flex;align-items:baseline;gap:10px}
.tl-points li::before{content:"";width:5px;height:5px;border-radius:50%;background:var(--accent);
  opacity:.5;flex:none;position:relative;top:-1px}
.tl-links{display:flex;flex-wrap:wrap;gap:16px;margin-top:13px}
.tl-links a{font-size:13px;font-weight:600;color:var(--accent)}
.tl-links a::after{content:"↗";margin-left:4px;font-weight:400}

/* ---------- About profile header ----------
   Portrait is cropped in CSS: no image tooling on this machine, and a percentage crop
   stays correct at every width. Source 1920×1280, window x 900–1700 / y 90–1090 puts
   the face at 42% across and 24% down, with the looking-room on the left where he is
   looking. width/left/top are that window expressed against the 4:5 frame. */
.profile{display:grid;grid-template-columns:168px 1fr;gap:32px;align-items:center;
  padding:64px 0 0;margin-bottom:64px;max-width:760px}
/* Circular thumbnail. Square window, so the crop had to be recomputed from the 4:5 one:
   source x 946–1533 / y 60–647 (587px square) puts the eyes at 40% from the top and the
   head between 12% and 75% of the frame, which is where a head reads as framed rather
   than floating or cropped. width/left/top are that window against a square frame. */
.portrait{position:relative;aspect-ratio:1;overflow:hidden;border-radius:50%;
  background:var(--panel)}
.portrait img{position:absolute;width:327%;max-width:none;height:auto;left:-161%;top:-10.2%}
.profile-txt h1{font-family:var(--display);font-size:clamp(26px,3.2vw,34px);font-weight:700;
  letter-spacing:-.015em;line-height:1.15}
.profile-txt .ptitle{margin-top:8px;font-size:15px;color:var(--ink-2)}
.profile-txt p{margin-top:16px;font-size:15px;line-height:1.6;color:var(--ink-2)}
@media (max-width:640px){
  /* stack: at 375px a side-by-side split leaves ~200px for the text, which folds the
     name onto two lines and strands the circle beside a tall block */
  .profile{grid-template-columns:1fr;gap:18px;padding-top:44px;margin-bottom:40px}
  .portrait{width:96px}
  .profile-txt p{font-size:14.5px}
}

.awards{padding:16px 0 64px;max-width:760px}
.personal{padding:0 0 96px;max-width:760px}
.personal p{color:var(--ink-2)}
.personal .interests{list-style:none;margin-top:22px;max-width:var(--textw)}
.personal .interests li{padding:16px 0;border-top:1px solid var(--line);color:var(--ink-2);font-size:15px;line-height:1.65}
.award{display:flex;gap:20px;padding:18px 0;border-bottom:1px solid var(--line);font-size:15px}
.award .y{color:var(--ink-3);flex:0 0 52px}
.award .note{color:var(--ink-3)}

/* ---------- recommendations: supporting evidence, not a pull-quote wall ----------
   Deliberately quiet — small type, no panel, sitting on the gradient the way the
   personal section does. These sit next to the awards as third-party evidence; if they
   were glazed and enlarged they would outshout the work, which is the opposite of the
   job they do. The rule is an accent tint rather than a neutral hairline so it reads as
   a citation marker and not as a leftover divider. */
/* One column, not two: at this size two columns give ~38 characters a line, which is
   what made the block feel cramped and left an orphan in row three. */
.saidgrid{display:grid;grid-template-columns:1fr;gap:36px;max-width:760px;margin-bottom:8px}
/* Glazed like the timeline and awards panels — one pane holding all five rather than
   five cards, which would read as a second dashboard and add five more backdrop-filter
   elements to a page that already carries fourteen. .64 for the same reason those two
   run thicker: body copy sits on it. */
body.fx.about .saidgrid{padding:34px 36px;border:1px solid var(--glass-line);border-radius:16px;
  background:rgba(255,255,255,.64);
  backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  box-shadow:var(--glass-sheen),var(--glass-lift-lg);margin-bottom:64px}
/* No left rule. A vertical bar beside a blockquote is the default every framework and
   every generator reaches for, and inside a panel it fights the panel's own edge.
   The quotation does the marking instead — real curly quotes, the same convention the
   personal section already uses for the Johnny Depp line.
   Hierarchy inverted too: the words are now the loudest thing here (display face, --ink),
   the attribution clearly subordinate. Before, quote and credit were near-identical. */
.said p{font-family:var(--display);font-size:17.5px;font-weight:400;line-height:1.52;
  letter-spacing:-.006em;color:var(--ink);max-width:60ch;text-wrap:pretty}
/* --ink-2, not --ink-3: this line carries the credential (who they were to me, what
   they do now), and at 12.5px on a tinted ground --ink-3 lands near 2.3:1. */
.said .who{display:block;margin-top:13px;font-size:12.5px;font-style:normal;
  line-height:1.5;color:var(--ink-2)}
.said .who b{color:var(--ink);font-weight:600}
@media (max-width:640px){
  .saidgrid{gap:28px}
  .said p{font-size:16.5px}
  body.fx.about .saidgrid{padding:24px 22px}
}

/* ---------- "being rewritten" dialog ----------
   Native <dialog>: Esc, backdrop click and focus return are the platform's job, not
   mine. Glass panel to match the rest of the surfaces. */
.revise-dlg{max-width:460px;margin:auto;border:1px solid var(--glass-line);border-radius:16px;
  padding:30px 32px;background:rgba(255,255,255,.82);
  backdrop-filter:saturate(180%) blur(24px);-webkit-backdrop-filter:saturate(180%) blur(24px);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.85),0 24px 60px -20px rgba(31,17,45,.4)}
.revise-dlg::backdrop{background:rgba(20,18,28,.34);backdrop-filter:blur(3px)}
.revise-dlg h2{font-family:var(--display);font-size:22px;font-weight:700;
  letter-spacing:-.015em;margin-bottom:12px}
.revise-dlg p{font-size:15px;line-height:1.6;color:var(--ink-2)}
/* app-dialog actions: split the full width, equal halves. `text-align:center` because
   .btn is inline-block for the anchor — as a flex child it gets the width but keeps
   start-aligned text without it. */
.revise-act{display:flex;gap:10px;margin-top:26px}
.revise-dlg .btn{flex:1 1 0;min-width:0;text-align:center;font-size:14.5px;padding:13px 16px}
/* company primer: same glass sheet, plus the platform figures */
.what-dlg{max-width:520px}
.what-stats{display:grid;grid-template-columns:1fr 1fr;gap:14px 18px;margin-top:22px;
  padding-top:20px;border-top:1px solid var(--line)}
.what-stat b{display:block;font-family:var(--display);font-size:22px;font-weight:700;
  letter-spacing:-.015em;line-height:1.15}
.what-stat .wk{display:block;font-size:13px;color:var(--ink-2);margin-top:3px;line-height:1.45}
.what-stat .wtag{display:inline-block;margin-top:7px;font-size:10.5px;font-weight:600;
  color:var(--ink-2);border:1px solid var(--line-strong);border-radius:100px;padding:2px 8px}
.what-stat .wtag.hi{color:var(--accent);border-color:var(--accent)}
.what-foot{font-size:11.5px;color:var(--ink-3);line-height:1.55;margin-top:16px}
@media (max-width:640px){.revise-dlg{padding:24px 22px;margin:auto 18px}
  .what-stats{grid-template-columns:1fr;gap:12px}}

/* ---------- footer ---------- */
/* Contact left the nav, so the footer is now the only way to reach him: the two actions
   lead, the colophon follows. Pills rather than text links because the last screen of a
   portfolio should have something obviously clickable on it. */
footer{border-top:1px solid var(--line);padding:56px 0 64px;margin-top:40px}
.foot-cta{display:flex;flex-wrap:wrap;gap:12px}
.fbtn{display:inline-flex;align-items:center;gap:10px;
  padding:13px 20px 13px 17px;border-radius:100px;
  border:1px solid var(--line-strong);background:var(--paper);
  font-size:15px;font-weight:600;color:var(--ink);
  transition:background .2s ease,border-color .2s ease,box-shadow .2s ease}
/* Ink, not accent. At 19px a saturated glyph was the most coloured thing in the pill —
   the eye went to the least informative element, and the LinkedIn and Telegram marks read
   as their brand marks in the wrong colour. Ink lets the pill read as one object.
   The accent survives as the reward for hovering, and as the mark of the one pill a
   hiring reader actually wants. --ink-2 rather than --ink-3: quiet, but still present. */
.fbtn svg{width:19px;height:19px;flex:none;color:var(--ink-2);transition:color .2s ease}
.fbtn:hover svg{color:var(--accent)}
/* no per-pill exception: a single accented glyph in the row read as an inconsistency
   rather than as hierarchy. One line to bring back if the emphasis is wanted. */
/* darker on hover, never lighter — the direction set for every other surface here */
.fbtn:hover{text-decoration:none;background:rgba(20,18,28,.05);
  border-color:rgba(20,18,28,.22);box-shadow:0 2px 8px rgba(31,17,45,.07)}
.fbtn:active{background:rgba(20,18,28,.09)}
/* One row: the sentence, a divider, the stack. No max-width — capping it at 520px is
   what forced the wrap in the first place. */
footer .built{display:flex;flex-wrap:wrap;align-items:center;gap:0 14px;margin-top:36px;
  font-size:13.5px;color:var(--ink-3);line-height:1.6}
footer .built .stack{white-space:nowrap}   /* the chain wraps as one unit or not at all */
.fdiv{width:1px;height:13px;flex:none;background:var(--line-strong)}
@media (max-width:700px){.fdiv{display:none}}   /* stacked, so the rule has nothing to divide */
@media (max-width:640px){
  .foot-cta{flex-direction:column;align-items:stretch}
  .fbtn{justify-content:center}
}

/* ---------- /present : slide renderer ---------- */
body.present{background:#1c1c1c;color:#f7f7f7;overflow:hidden}
.deck{position:relative;width:100vw;height:100vh}
.slide{position:absolute;inset:0;display:none;flex-direction:column;justify-content:center;
  padding:7vh 9vw;background:#1c1c1c}
.slide.active{display:flex}
.slide .kind{font-size:15px;font-weight:600;letter-spacing:.01em;
  color:var(--accent-dark);margin-bottom:22px}
.slide h2{font-family:var(--display);font-weight:700;letter-spacing:-.015em;font-size:clamp(30px,4.4vw,52px);
  line-height:1.14;max-width:880px;margin-bottom:26px}
.slide p{font-size:clamp(16px,1.6vw,21px);line-height:1.6;color:#b9b9b4;max-width:760px}
.slide .tradeoff{margin-top:24px;padding:18px 22px;background:#2a2224;border-left:none;
  border-radius:var(--radius);color:#ffc2ce;font-size:16px;max-width:720px}
.slide .shot-row{flex-wrap:nowrap;justify-content:space-around;margin-top:26px;padding:24px 22px 20px;
  background:
    #232323;border:1px solid #333;box-shadow:0 6px 18px rgba(0,0,0,.35)}
.slide .shot{max-width:166px}
.slide .shot figcaption{color:#a8a8a8}
.slide .shot .tag{border-color:#3a3a3a;color:#a8a8a8}
.slide .shot .tag.own,.slide .row-tag .tag.own{border-color:var(--accent-dark);color:var(--accent-dark)}
.slide .shot .tag.own::before,.slide .row-tag .tag.own::before{background:var(--accent-dark)}
.slide .row-tag{margin-top:26px}
.slide .row-tag + .shot-row{margin-top:8px}
.slide .row-tag + .shot-row .shot figcaption{min-height:32px}
.slide .shot .frame{box-shadow:inset 0 0 0 1.5px #333}
.slide .proto-row{margin-top:26px;flex-wrap:nowrap}
.slide .phone-wrap{--s:.47;box-shadow:none}
.slide .proto figcaption{color:#8a8a8a}
.slide .smedia{margin-top:28px}
.slide .smedia img{max-height:42vh;max-width:100%;width:auto;display:block;
  border:1px solid #2a2a2a;border-radius:10px}
.slide .metric-row{display:flex;gap:16px;flex-wrap:wrap;margin-top:30px}
.slide .metric-row span{font-family:var(--display);font-size:24px;font-weight:700;padding:16px 24px;
  border:1px solid #3a3a3a;border-radius:var(--radius);color:#f7f7f7}
.slide .case-label{position:absolute;top:5vh;left:9vw;display:flex;gap:12px;align-items:center;
  font-size:14px;color:#777}
.slide .case-label .chip{border-color:var(--accent-dark);color:var(--accent-dark);background:transparent}
.slide .case-label .chip::before{background:var(--accent-dark)}
.hud{position:fixed;bottom:22px;left:0;right:0;display:flex;justify-content:center;gap:18px;
  align-items:center;font-size:13px;color:#666;z-index:10}
.hud .bar{width:200px;height:2px;background:#2a2a2a;border-radius:2px;overflow:hidden}
.hud .bar i{display:block;height:100%;background:var(--accent-dark);width:0%}
.hud button{background:none;border:1px solid #3a3a3a;color:#b0b0b0;border-radius:100px;
  padding:6px 15px;font-size:12.5px;font-weight:600;cursor:pointer}
.hud button:hover{color:#fff;border-color:#666}

/* ---------- print → PDF (slides) ---------- */
@media print{
  body.present{overflow:visible;background:#fff}
  .hud{display:none}
  .deck{width:auto;height:auto}
  .slide{position:relative;display:flex !important;page-break-after:always;
    width:100%;height:100vh;background:#1c1c1c}
}

/* ============================================================
   STRATEGY PRIMITIVES — problem map, bets, criteria, cut,
   callouts, insight mapping, sequencing. Data lives in site.js;
   these render in both the scroll view and the dark deck.
   ============================================================ */

/* editorial illustration: the emotional half of the problem section (scroll view only) */
/* the mat colour and the crop window are per-illustration (set inline by render.js from
   site.js illo.bg / .ar / .pos). Defaults = the Order artwork: warm paper, uncropped. */
.sec .illo{margin-top:32px;max-width:var(--blockw);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;background:var(--illo-bg,#fdfcfa);padding:2.6%}
.sec .illo img{width:100%;aspect-ratio:var(--illo-ar,1760/1168);object-fit:cover;object-position:50% var(--illo-py,50%);display:block}
.sec .illo .ph-label{display:none}
.sec .illo.ph{aspect-ratio:1760/1168;background:linear-gradient(135deg,#f2efe9,#e7e2d8);padding:0;
  display:flex;align-items:center;justify-content:center;color:#9a938a;font-size:14px}
.sec .illo.ph .ph-label{display:block}

/* market diagram: two flows, the `own` row is ours */
.dgm-market{margin-top:32px;max-width:var(--blockw)}
.dgm-row{display:grid;grid-template-columns:1fr 170px 1fr;align-items:stretch;margin-bottom:14px}
.dgm-node{background:var(--panel);border-radius:var(--radius);padding:18px 20px}
.dgm-node b{font-family:var(--display);font-size:16.5px;font-weight:700;letter-spacing:-.01em;display:block}
.dgm-node span{display:block;font-size:13.5px;color:var(--ink-2);margin-top:5px;line-height:1.5}
.dgm-link{position:relative;display:flex;align-items:center;padding:0 14px}
.dgm-link i{display:block;width:100%;height:2px;background:var(--line-strong);position:relative}
.dgm-link i::after{content:"";position:absolute;right:-1px;top:-3px;
  border:4px solid transparent;border-right:none;border-left:6px solid var(--line-strong)}
.dgm-link .lbl{position:absolute;left:50%;top:calc(50% - 24px);transform:translateX(-50%);
  font-size:11.5px;font-weight:600;color:var(--ink-3);white-space:nowrap}
/* evidence strip inside a node: anonymized order-screen crops, one grammar.
   .dgm-node .dgm-strip needed: .dgm-node span otherwise wins on display */
.dgm-node .dgm-strip{display:flex;gap:5px;margin-top:14px}
.dgm-strip i{flex:1 1 0;min-width:0;display:block;aspect-ratio:10/16;border-radius:6px;overflow:hidden;
  background:#111;border:1px solid var(--line-strong)}
.dgm-strip img{width:100%;height:100%;object-fit:cover;object-position:50% var(--py,40%)}
.sec .dgm-strip{cursor:zoom-in;transition:opacity .22s ease}
.sec .dgm-strip:hover{opacity:.82}
/* the strip enlarged: five crops as one composite card */
.strip-zoom{position:fixed;inset:0;z-index:60;background:rgba(17,17,17,.85);cursor:zoom-out;
  display:flex;align-items:center;justify-content:center;padding:5vh 4vw}
.strip-zoom .row{display:flex;flex-wrap:wrap;justify-content:center;gap:14px;background:var(--paper);
  border-radius:var(--radius);padding:22px;max-width:1160px;box-shadow:var(--shadow-2)}
/* fixed cell size so one draft doesn't blow up to full width; many drafts wrap */
.strip-zoom i{flex:0 0 auto;width:min(230px,42vw);display:block;aspect-ratio:400/711;border-radius:10px;
  overflow:hidden;background:#111;border:1px solid var(--line-strong)}
/* show the whole screen, no tight crop (drafts fill exactly; taller shots letterbox) */
.strip-zoom img{width:100%;height:100%;object-fit:contain;object-position:50% 50%}
@media (max-width:700px){
  .strip-zoom{padding:4vh 12px}
  .strip-zoom .row{flex-wrap:wrap;gap:10px;padding:14px}
  .strip-zoom i{flex:1 1 30%}
}
/* `tall` strip item = one long app screenshot used as a reference shot (Earn S1),
   not the five-up composite the Order strips carry. It keeps a phone-shaped crop
   window in both sizes: contain would render a 1:3.7 capture as an unreadable sliver.
   Higher specificity than the base .dgm-strip i / .strip-zoom i rules on purpose. */
.dgm-node .dgm-strip i.tall{flex:0 0 auto;width:78px;aspect-ratio:10/19}
/* `shot` = one ordinary phone capture (375:667) as a node thumbnail. Same 78px width as
   `tall` so sibling nodes match, but it needs no zoom override: the default .strip-zoom
   cell is 400/711 + contain, which is already this aspect. */
.dgm-node .dgm-strip i.shot{flex:0 0 auto;width:78px;aspect-ratio:375/667}
/* enlarged, a single phone should use the height it has: the 230px composite cell is sized
   for five-up strips and leaves one screen unreadable. Viewport-capped — .strip-zoom gives
   90vh and .row eats 44px of padding, so 80vh of height always clears. Expressed as a
   width (45vh ~= 80vh x 375/667) because width:auto + aspect-ratio resolved to a stale
   238px inside the flex row. Source is the 2x capture (750x1334), so it never upscales. */
.strip-zoom i.shot{flex:0 0 auto;max-width:none;width:min(45vh,450px);height:auto;aspect-ratio:375/667}
/* height-driven so the enlarged crop always fits the viewport without scroll;
   the aspect lands the bottom edge just under the earnings chart, above the FAQ */
.strip-zoom.tallzoom i.tall{flex:0 0 auto;aspect-ratio:400/991;height:min(86vh,900px);width:auto}
.strip-zoom.tallzoom img{object-fit:cover;object-position:50% var(--py,6%)}
.dgm-row.own .dgm-node{background:var(--accent-soft)}
.dgm-row.own .dgm-node b{color:var(--accent)}
.dgm-row.own .dgm-link i{background:var(--accent)}
.dgm-row.own .dgm-link i::after{border-left-color:var(--accent)}
.dgm-row.own .dgm-link .lbl{color:var(--accent)}
.dgm-goal{margin-top:8px;padding:16px 20px;border:1px solid var(--line-strong);
  border-radius:var(--radius);font-size:15px;color:var(--ink-2)}
.dgm-goal strong{color:var(--ink)}

/* bet cards: open → held / broke */
.bet-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:20px;margin-top:32px;max-width:var(--blockw)}
.bet{background:var(--panel);border-radius:var(--radius);padding:22px 24px}
.bet .bet-top{display:flex;align-items:baseline;gap:12px;margin-bottom:10px}
.bet .idx{font-size:12px;font-weight:700;color:var(--accent);white-space:nowrap}
.bet .bet-top b{font-family:var(--display);font-size:18px;font-weight:700;letter-spacing:-.01em}
.bet p{font-size:15px;color:var(--ink-2);max-width:none}
.bet .verdict{margin-top:16px;padding-top:14px;border-top:1px solid var(--line-strong);
  font-size:14.5px;color:var(--ink-2);line-height:1.55}
.bet .verdict b{color:var(--ink)}
.bet .verdict .mark{display:inline-flex;width:20px;height:20px;border-radius:50%;align-items:center;
  justify-content:center;font-size:11px;font-weight:700;margin-right:8px;color:#fff;
  position:relative;top:-1px}
.bet.held .verdict .mark{background:var(--ink)}
.bet.broke .verdict .mark{background:var(--accent-brand)}
.bet.neutral .verdict .mark{background:var(--ink-3)}
.bet .verdict.pending{color:var(--ink-3);border-top-style:dashed}

/* success criteria rows */
.crit{margin-top:32px;max-width:var(--blockw);border-top:1px solid var(--line)}
.crit-row{display:grid;grid-template-columns:104px 1fr auto;gap:18px;align-items:baseline;
  padding:15px 0;border-bottom:1px solid var(--line)}
.crit .ctype{font-size:12px;font-weight:700;color:var(--accent)}
.crit .cmain b{font-size:15.5px;font-weight:600;display:block}
.crit .cmain span{font-size:13.5px;color:var(--ink-2)}
.crit .cstat{font-size:12.5px;color:var(--ink-3);white-space:nowrap}

/* the cut: struck list → rule gate → survivors */
.cut{display:grid;grid-template-columns:1fr 200px 1fr;align-items:stretch;margin-top:32px;max-width:var(--blockw)}
.cut-col{background:var(--panel);border-radius:var(--radius);padding:20px 22px}
.cut-h{font-size:12.5px;font-weight:700;color:var(--ink-3);margin-bottom:10px}
.cut-col.kept .cut-h{color:var(--accent)}
.cut-col ul{list-style:none}
.cut-col li{font-size:14.5px;padding:7.5px 0;border-top:1px solid var(--line-strong)}
.cut-col.removed li{text-decoration:line-through;color:var(--ink-3)}
.cut-col.kept li{font-weight:600}
.cut-gate{position:relative;display:flex;align-items:center;justify-content:center;padding:0 16px}
.cut-gate::before{content:"";position:absolute;left:0;right:0;top:50%;height:2px;background:var(--line-strong)}
.cut-gate span{position:relative;background:var(--paper);border:1.5px solid var(--accent);color:var(--accent);
  border-radius:14px;padding:12px 16px;font-size:12.5px;font-weight:700;text-align:center;line-height:1.4}

/* numbered design callouts */
.callouts{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:20px;margin-top:32px;max-width:var(--blockw)}
.co-card{background:var(--panel);border-radius:var(--radius);padding:20px 22px}
.co-card .n{font-family:var(--display);font-size:13px;font-weight:700;color:var(--accent);display:block;margin-bottom:8px}
.co-card b{font-family:var(--display);font-size:16.5px;font-weight:700;letter-spacing:-.01em;display:block;margin-bottom:6px}
.co-card p{font-size:14px;color:var(--ink-2);max-width:none}
/* the two questions every lever had to answer: set as lines, since bolding them inside a
   paragraph buried the one device the section is actually about */
.co-asks{list-style:none;margin:14px 0 0;padding:12px 0 0;border-top:1px solid var(--line-strong)}
.co-asks li{display:flex;gap:8px;font-family:var(--display);font-size:16px;font-weight:700;
  letter-spacing:-.01em;color:var(--ink);line-height:1.35}
.co-asks li + li{margin-top:7px}
.co-asks li::before{content:"";flex:none;width:6px;height:6px;margin-top:7px;border-radius:50%;
  background:var(--accent-brand)}

/* observation → decision mapping */
.maprows{margin-top:32px;max-width:var(--blockw)}
.map-h{display:grid;grid-template-columns:1fr 40px 1.2fr;gap:14px;
  font-size:12px;font-weight:700;color:var(--ink-3);padding-bottom:10px}
.map-row{display:grid;grid-template-columns:1fr 40px 1.2fr;gap:14px;align-items:baseline;
  padding:14px 0;border-top:1px solid var(--line)}
.map-row .obs{font-size:15px;color:var(--ink-2)}
.map-row .arr{color:var(--accent);font-weight:700;text-align:center}
.map-row .ui{font-size:15px;font-weight:600}
/* the explorations an insight triggered: thumbnail strip, zoomable */
.map-x{display:flex;align-items:flex-start;gap:14px;margin:2px 0 14px}
.map-x .mx-n{font-size:12px;font-weight:600;color:var(--ink-3);white-space:nowrap;padding-top:4px}
.map-x .mx-strip{display:flex;gap:5px;cursor:zoom-in;transition:opacity .22s ease}
.map-x .mx-strip:hover{opacity:.82}
.mx-strip i{display:block;width:42px;aspect-ratio:10/19;border-radius:5px;overflow:hidden;
  background:#111;border:1px solid var(--line-strong)}
.mx-strip img{width:100%;height:100%;object-fit:cover;object-position:50% 0}
/* a `wide` item is a web page capture: a phone-shaped thumb squeezes it into a sliver.
   Sized to be read without opening the lightbox — three across the full block width. */
.mx-strip i.wide{width:248px;aspect-ratio:16/9}
/* inside an exploration panel the wides share the row: flexible so three of them fill the
   full width (~320px each) instead of sitting at a fixed 248 with slack on the right.
   basis 260 keeps three per row and wraps a fourth rather than shrinking them all. */
.exp-t .mx-strip i.wide{flex:1 1 260px;min-width:0;width:auto;max-width:none}
.mx-strip i.wide img{object-position:50% 0}
.strip-zoom.widezoom .row{flex-direction:column;gap:14px}
/* one wide capture at a time (render.js opens only the clicked item), so it can have the
   room: bounded on both axes so a long page fits without the overlay needing to scroll.
   Sources are 1440-3456px wide, so the cap never upscales. */
.strip-zoom.widezoom i{width:auto;max-width:min(88vw,1400px);height:auto;aspect-ratio:auto;overflow:visible}
.strip-zoom.widezoom i img{display:block;width:auto;height:auto;
  max-width:min(88vw,1400px);max-height:84vh;object-fit:contain;border-radius:10px}

/* exploration evidence under the Release 2 prototype, grouped by month */
/* full section width, not --blockw: its neighbours in the column (a media figure, a
   proto row) are uncapped, so an 880px cap left the panel short of them on the right. */
.explos{margin-top:36px}
/* visual marker so skimmers know these are draft iterations, not the shipped screen */
.exp-kicker{display:inline-flex;align-items:center;gap:8px;font-family:var(--display);
  font-size:14px;font-weight:700;letter-spacing:-.01em;color:var(--accent);margin-bottom:12px}
.exp-kicker i{width:7px;height:7px;border-radius:50%;background:var(--accent-brand);flex:none}
.exp-note{font-size:14.5px;color:var(--ink-2);max-width:var(--textw)}
.exp-groups{display:grid;grid-template-columns:1fr 1fr;gap:20px;margin-top:18px;align-items:start}
.exp-groups:has(.exp-g:only-child){grid-template-columns:1fr}
.exp-g{background:var(--panel);border-radius:var(--radius);padding:18px 20px}
.exp-g .when{font-size:12px;font-weight:700;color:var(--accent);display:block}
.exp-g > b{font-family:var(--display);font-size:16.5px;font-weight:700;letter-spacing:-.01em;
  display:block;margin:2px 0 6px}
.exp-t{margin-top:12px}
.exp-t .mx-n{display:block;padding-top:0;margin-bottom:8px;white-space:normal;
  font-size:13.5px;font-weight:500;color:var(--ink-2)}
/* thumbnails run in a row (was stacking vertically → over-tall cards) */
.exp-t .mx-strip{display:flex;flex-wrap:wrap;align-items:center;gap:15px;cursor:zoom-in;transition:opacity .22s ease}
.exp-t .mx-strip:hover{opacity:.82}
/* a wide strip opens the item you click, so the hover has to answer per item too —
   dimming the whole row made it look like one target. Cancel the row-level dim there. */
.exp-t .mx-strip:has(i.wide):hover{opacity:1}
.exp-t .mx-strip i.wide{transition:opacity .22s ease}
.exp-t .mx-strip i.wide:hover{opacity:.82}
/* a→b = one continuous flow: tight gap + a small chevron, so it reads as one exploration,
   not two separate iterations (distinct drafts keep the wider strip gap) */
.mx-flow{display:inline-flex;align-items:center;flex:none}
.exp-t .mx-flow{gap:4px}
.exp-t .mx-seq{flex:none;color:var(--ink-3);font-weight:400;font-size:13px;line-height:1}
.strip-zoom .mx-flow{gap:10px}
.strip-zoom .mx-seq{flex:none;color:var(--ink-3);font-weight:400;font-size:30px;line-height:1;padding:0 2px}
/* vertical divider between DISTINCT explorations (never inside a flow) */
.mx-div{flex:none;align-self:stretch;width:1px;background:var(--line-strong)}
.strip-zoom .mx-div{margin:0 4px;background:var(--line)}

/* ---------- user voices: verbatim quotes from research I ran ----------
   No italics, no left rule, no dash-attribution (house rules). The curly quotes mark the
   speech; the kicker above carries provenance, so each card stays a quote and nothing else.
   Korean is kept verbatim — translating a user's phrasing makes it my phrasing. */
.voices{margin-top:30px;max-width:var(--blockw)}
.voi-kicker{display:inline-flex;align-items:center;gap:8px;font-family:var(--display);
  font-size:14px;font-weight:700;letter-spacing:-.01em;color:var(--accent);margin-bottom:10px}
.voi-kicker i{width:7px;height:7px;border-radius:50%;background:var(--accent-brand);flex:none}
.voi-note{font-size:14px;color:var(--ink-3);margin-bottom:16px;max-width:var(--textw)}
.voi-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(268px,1fr));gap:14px}
.voi{background:var(--panel);border-radius:var(--radius);padding:20px 22px;margin:0}
.voi p{font-size:15.5px;line-height:1.6;color:var(--ink);font-style:normal;max-width:none}
.voi[lang="ko"] p{line-height:1.72;word-break:keep-all}
@media (max-width:700px){ .voi-grid{grid-template-columns:1fr} }

/* metric scoreboard: the criteria come back scored, release by release */
.score{margin-top:28px;max-width:var(--blockw)}
.score-row{display:flex;justify-content:space-between;align-items:baseline;gap:20px;flex-wrap:wrap;
  padding:16px 20px;background:var(--panel);border-radius:var(--radius);margin-top:12px}
.score .sname b{font-size:15.5px;font-weight:600;display:block}
.score .sname span{font-size:13px;color:var(--ink-3)}
.score .svals{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap}
.score .sarr{color:var(--ink-3)}
.score .sval{display:inline-flex;flex-direction:column;align-items:flex-start}
.score .sval b{font-family:var(--display);font-size:21px;font-weight:700;letter-spacing:-.01em;color:var(--ink-2)}
.score .sval.last b{color:var(--ink);font-size:25px}
/* cross-section row: equal weight, hairline separators, no arrows */
.score .svals.cmp{gap:0}
.score .svals.cmp .sval{padding:0 18px;border-left:1px solid var(--line-strong)}
.score .svals.cmp .sval:first-child{padding-left:0;border-left:0}
.score .svals.cmp .sval:last-child{padding-right:0}
.score .svals.cmp .sval b{color:var(--ink)}
.score .sval i{font-style:normal;font-size:11.5px;color:var(--ink-3);margin-top:2px}
.score .sread{font-size:14px;color:var(--ink-2);margin-top:10px;max-width:var(--textw)}
.score .sfoot{font-size:12px;color:var(--ink-3);margin-top:12px;line-height:1.6;max-width:var(--textw)}

/* sequenced return of depth */
.seq{margin-top:32px;max-width:var(--blockw)}
.seq-gate{padding:14px 18px;background:var(--accent-soft);border-radius:var(--radius);
  font-size:14.5px;color:var(--ink-2);margin-bottom:26px}
.seq-gate strong{color:var(--accent)}
.seq-steps{display:grid;grid-template-columns:repeat(3,1fr);gap:24px}
.seq-step{position:relative;padding-top:20px}
.seq-step::before{content:"";position:absolute;top:4px;left:0;right:-24px;height:2px;background:var(--line-strong)}
.seq-step:last-child::before{right:0}
.seq-step::after{content:"";position:absolute;top:0;left:0;width:10px;height:10px;border-radius:50%;
  background:var(--accent-brand)}
.seq-step .when{font-size:12px;font-weight:700;color:var(--ink-3);display:block;margin-bottom:4px}
.seq-step b{font-family:var(--display);font-size:16.5px;font-weight:700;letter-spacing:-.01em;display:block}
.seq-step .note{display:block;font-size:13.5px;color:var(--ink-2);margin-top:4px}

/* appendix: timestamped platform scale, quieter than the case's own metrics */
.apx{margin-top:30px;max-width:var(--blockw,880px);border-top:1px solid var(--line);padding-top:16px}
.apx-h{font-size:12.5px;font-weight:600;color:var(--ink-3);margin-bottom:6px}
.apx-row{display:grid;grid-template-columns:112px auto 1fr;gap:14px;align-items:baseline;
  padding:11px 0;border-bottom:1px solid var(--line)}
.apx-tag{justify-self:start;align-self:center;   /* pill centres on the row; value + label stay baseline-aligned */
  font-size:11px;font-weight:600;color:var(--ink-2);
  border:1px solid var(--line-strong);border-radius:100px;padding:2px 9px;white-space:nowrap}
.apx-tag.hi{color:var(--accent);border-color:var(--accent)}
.apx-row b{font-family:var(--display);font-size:21px;font-weight:700;letter-spacing:-.01em;white-space:nowrap}
.apx-k{font-size:14.5px;color:var(--ink-2)}
.apx-foot{font-size:12px;color:var(--ink-3);line-height:1.6;margin-top:12px;max-width:var(--textw)}

/* --- strategy primitives on the dark deck --- */
.slide .dgm-market,.slide .bet-row,.slide .crit,.slide .cut,.slide .callouts,.slide .maprows,.slide .seq,.slide .score{margin-top:22px}
.slide .mx-strip{display:none}                          /* deck keeps the label, drops the thumbs */
.slide .explos{display:none}                            /* exploration evidence is a scroll-view artifact */
/* a slide carrying a diagram is already labelled by the diagram (the Earn lifecycle names
   01/02/03 on itself), so the callouts that restate it are scroll-view only. Dropping them
   is what keeps section = one slide = 100vh, and it buys the diagram the height it needs
   to stay readable from a room. */
.slide:has(.smedia) .callouts{display:none}
.slide:has(.smedia) .smedia img{max-height:58vh}
/* A slide can't carry a full-page figure and a framed screen at once — three visuals in
   one 100vh box. The framed screen wins on the deck, because a moving screen is what a
   presentation is for; the figure's full-page context stays in the scroll view, where a
   reader can sit with it. */
.slide:has(.smedia):has(.shot-row) .smedia{display:none}
.slide .map-x{margin:0 0 8px}
.slide .map-x .mx-n{color:var(--accent-dark)}
.slide .score-row{background:#232323;padding:12px 16px;margin-top:10px}
.slide .score .sname b{color:#f7f7f7}
.slide .score .sname span,.slide .score .sarr,.slide .score .sval i,.slide .score .sfoot{color:#8a8a8a}
.slide .score .sval b{color:#b9b9b4}
.slide .score .sval.last b{color:#f7f7f7}
.slide .score .sread{color:#b9b9b4;font-size:13.5px}
.slide .srow > .score{flex:1;min-width:0}
.slide .dgm-node,.slide .bet,.slide .co-card,.slide .cut-col{background:#232323}
.slide .dgm-node b,.slide .bet .bet-top b,.slide .co-card b,.slide .map-row .ui,
.slide .crit .cmain b,.slide .seq-step b,.slide .bet .verdict b,.slide .dgm-goal strong{color:#f7f7f7}
.slide .dgm-node span,.slide .bet p,.slide .co-card p,.slide .map-row .obs,.slide .crit .cmain span,
.slide .seq-step .note,.slide .bet .verdict,.slide .seq-gate,.slide .dgm-goal{color:#b9b9b4}
.slide .dgm-link i,.slide .cut-gate::before,.slide .seq-step::before{background:#3a3a3a}
.slide .dgm-link i::after{border-left-color:#3a3a3a}
.slide .dgm-link .lbl{color:#8a8a8a}
.slide .dgm-strip i{border-color:#3a3a3a}
.slide .dgm-row.own .dgm-node{background:#2a2224}
.slide .dgm-row.own .dgm-node b{color:var(--accent-dark)}
.slide .dgm-row.own .dgm-link i{background:var(--accent-dark)}
.slide .dgm-row.own .dgm-link i::after{border-left-color:var(--accent-dark)}
.slide .dgm-row.own .dgm-link .lbl{color:var(--accent-dark)}
.slide .dgm-goal{border-color:#3a3a3a}
.slide .bet .idx,.slide .crit .ctype,.slide .map-row .arr,.slide .co-card .n,
.slide .cut-col.kept .cut-h,.slide .seq-gate strong{color:var(--accent-dark)}
.slide .bet .verdict{border-top-color:#3a3a3a}
.slide .bet.held .verdict .mark{background:#f7f7f7;color:#1c1c1c}
.slide .bet.neutral .verdict .mark{background:#8a8a8a;color:#1c1c1c}
.slide .crit,.slide .crit-row,.slide .map-row,.slide .cut-col li,
.slide .score .svals.cmp .sval{border-color:#3a3a3a}
.slide .score .svals.cmp .sval b{color:#f7f7f7}
.slide .crit .cstat,.slide .cut-h,.slide .seq-step .when,.slide .map-h,
.slide .cut-col.removed li,.slide .bet .verdict.pending{color:#8a8a8a}
.slide .cut-gate span{background:#1c1c1c;border-color:var(--accent-dark);color:var(--accent-dark)}
.slide .seq-gate{background:#2a2224}
.slide .seq-step::after{background:var(--accent-dark)}
/* deck fit: tighter cards */
.slide .dgm-node,.slide .bet,.slide .co-card,.slide .cut-col{padding:16px 18px}
.slide .co-asks{border-top-color:#3a3a3a;margin-top:12px;padding-top:10px}
.slide .co-asks li{color:#f7f7f7;font-size:15px}
.slide .crit-row{padding:12px 0}
/* deck fit: a strategy block and a prototype share the slide side by side */
.slide .srow{display:flex;gap:48px;align-items:flex-start}
.slide .srow > .proto-row{flex:0 0 auto;margin-top:26px}
.slide .srow > .dgm-market,.slide .srow > .bet-row,.slide .srow > .crit,
.slide .srow > .cut,.slide .srow > .callouts,.slide .srow > .maprows,.slide .srow > .seq{flex:1;min-width:0}
/* Callouts squeeze into one column only when they sit beside a phone. In any other
   combination both blocks want the full width, so stack them and let the cards spread;
   left side by side the callout column collapses to one card per row and overruns. */
.slide .srow:has(.proto-row) .callouts{grid-template-columns:1fr;gap:14px}
.slide .srow:not(:has(.proto-row)):has(.callouts){display:block}
.slide .srow:not(:has(.proto-row)) .callouts{margin-top:22px}
.slide .srow .cut{grid-template-columns:1fr 150px 1fr}
.slide .srow .seq-steps{grid-template-columns:1fr;gap:16px}
.slide .srow .seq-step{padding-top:0;padding-left:22px}
.slide .srow .seq-step::before{display:none}
.slide .srow .seq-step::after{top:5px}

/* appendix on the dark deck */
.slide .voices{margin-top:18px}
.slide .voi{background:#232323;padding:14px 16px}
.slide .voi p{font-size:14px;color:#f0f0ee}
.slide .voi-note{color:#8a8a8a;margin-bottom:12px}
.slide .apx{margin-top:20px;border-top-color:#333;padding-top:12px}
.slide .apx-h{color:#8a8a8a}
.slide .apx-row{border-bottom-color:#333;padding:8px 0}
.slide .apx-row b{color:#f7f7f7;font-size:19px}
.slide .apx-tag{color:#8a8a8a;border-color:#3a3a3a}
.slide .apx-tag.hi{color:var(--accent-dark);border-color:var(--accent-dark)}
.slide .apx-k{color:#b9b9b4;font-size:13.5px}
.slide .apx-foot{display:none}          /* the caveat is spoken, not projected */
/* an appendix sharing a slide with phones does not fit 100vh: scroll view keeps it,
   the deck drops it and the presenter speaks the numbers instead */
.slide .shot-row ~ .apx{display:none}

/* --- strategy primitives, narrow screens --- */
@media (max-width:700px){
  .dgm-row,.cut{grid-template-columns:1fr}
  .dgm-link{padding:10px 2px;min-height:38px}
  .dgm-link i,.cut-gate::before{display:none}
  .dgm-link .lbl{position:static;transform:none}
  .dgm-link .lbl::before{content:"↓  "}
  .cut-gate{padding:12px 0}
  .cut-gate span{width:100%}
  .map-h{display:none}
  .map-row{grid-template-columns:1fr;gap:4px}
  .map-row .arr{display:none}
  .map-row .ui::before{content:"→ ";color:var(--accent)}
  .seq-steps{grid-template-columns:1fr;gap:18px}
  .seq-step{padding-top:0;padding-left:22px}
  .seq-step::before{display:none}
  .seq-step::after{top:5px}
  .crit-row{grid-template-columns:1fr;gap:4px}
  .crit .cstat{white-space:normal}
  .apx-row{grid-template-columns:1fr;gap:4px}
  .map-x{flex-direction:column;gap:8px}
  .score-row{flex-direction:column;align-items:flex-start;gap:10px}
  .exp-groups{grid-template-columns:1fr}
}

/* ---------- responsive ---------- */
@media (max-width:640px){
  .hero{padding:64px 24px 40px}
  .tl{grid-template-columns:1fr;gap:6px}
  .tl-logo{width:38px;height:38px;border-radius:9px}
  .tl-main{gap:13px}
  .metrics .m + .m{border-left:none;padding-left:0}
  /* 375px leaves the lockup ~115px beside the links and the name needs ~122px, so it
     folded to "Christian / Park". The mark carries the identity on its own here; the
     anchor has an aria-label, so the accessible name is unchanged. */
  .nav .logo .lname{display:none}
  .nav .links a{margin-left:16px}
}

/* ============================================================
   PAGE FX (test) — opt-in per page via body class
     body.fx.landing  → index.html   (full five-blob gradient)
     body.fx.about    → about.html   (quieter two-blob gradient, long-read page)
   iOS-style shallow mesh gradient + liquid glass + background parallax.
   /work and /present are untouched: remove the body class to revert.
   ============================================================ */
body.fx{
  background:#ffffff;                           /* keep the paper base; #bgfx (z-index:0) stacks above it */
  --bgfx-op:.6;
  --grain-op:.55;                               /* grain is its own quality, not a fraction of the gradient */                                 /* one dial for the whole gradient depth */
  --glass:rgba(255,255,255,.55);
  --glass-line:rgba(255,255,255,.7);
  --glass-blur:saturate(180%) blur(20px);
  --glass-sheen:inset 0 1px 0 rgba(255,255,255,.85);
  --glass-edge:1px solid rgba(34,34,34,.07);
  /* float, not a drop shadow: a soft cast (negative spread keeps it under the panel)
     plus a tight contact shadow that holds the edge. Alpha is well below the reference's
     rgba(0,0,0,.4) — this is a light editorial page, not a dashboard.
     TWO TIERS, because spread is an absolute length and the panels are not the same size:
     shrinking a 90px-tall tile by 28px a side leaves a stub that blurs into a blob under
     the middle. Keep |spread| under ~15% of the element's shorter side. */
  --glass-lift:0 12px 24px -12px rgba(31,17,45,.22), 0 2px 6px -3px rgba(31,17,45,.10);
  --glass-lift-h:0 16px 30px -12px rgba(31,17,45,.26), 0 3px 8px -3px rgba(31,17,45,.12);
  --glass-lift-lg:0 24px 48px -28px rgba(31,17,45,.26), 0 5px 14px -8px rgba(31,17,45,.10);
  --dim:rgba(20,18,28,.06);                     /* hover darkens, never lightens */
}
/* the gradient plate: taller than the viewport so the parallax drift never exposes an edge */
#bgfx{position:fixed;left:0;top:-14vh;width:100%;height:128vh;z-index:0;pointer-events:none;
  background-color:#ffffff;opacity:var(--bgfx-op);will-change:transform}
/* Landing runs a band, not a field. The headline owns the upper left, so colour stays
   out of it and enters from the upper right, sweeping down through the metric tiles and
   the card grid — colour where the glass is, quiet where the type is. Fewer, larger
   shapes than About's scatter, and anchored near Rausch rather than About's cool mesh:
   the front door should read as the brand, About as the person.
   Vertical positions stay inside the plate's visible 12–85% band. */
body.fx.landing #bgfx{background-image:
  radial-gradient(62% 50% at 96% 12%,
    rgba(255,92,138,.95) 0%, rgba(255,92,138,.5) 34%, rgba(255,92,138,0) 88%),
  radial-gradient(50% 40% at 74% 40%,
    rgba(255,179,122,.85) 0%, rgba(255,179,122,.44) 36%, rgba(255,179,122,0) 86%),
  radial-gradient(56% 46% at 40% 74%,
    rgba(143,123,255,.8) 0%, rgba(143,123,255,.42) 38%, rgba(143,123,255,0) 88%),
  radial-gradient(48% 40% at 4% 86%,
    rgba(124,220,255,.7) 0%, rgba(124,220,255,.36) 40%, rgba(124,220,255,0) 88%)}
body.fx.landing #bgfx{background-image:
  radial-gradient(62% 50% at 96% 12% in oklab,
    rgba(255,92,138,.95) 0%, rgba(255,92,138,.5) 34%, rgba(255,92,138,0) 88%),
  radial-gradient(50% 40% at 74% 40% in oklab,
    rgba(255,179,122,.85) 0%, rgba(255,179,122,.44) 36%, rgba(255,179,122,0) 86%),
  radial-gradient(56% 46% at 40% 74% in oklab,
    rgba(143,123,255,.8) 0%, rgba(143,123,255,.42) 38%, rgba(143,123,255,0) 88%),
  radial-gradient(48% 40% at 4% 86% in oklab,
    rgba(124,220,255,.7) 0%, rgba(124,220,255,.36) 40%, rgba(124,220,255,0) 88%)}
/* about: two blobs on one diagonal, no warm centre. It reads as a wash rather than
   a mesh, so a page of long-form text keeps a quiet ground. */
/* About runs hotter than the landing on purpose: this is the page where the glass
   panels are the whole composition, and they only refract what the plate gives them */
body.fx.about{--bgfx-op:.85}
/* the magenta corner sits right under the Rausch logo — thicken the bar's glass so the
   logo keeps a ground of its own at this depth */
body.fx.about #nav{background:rgba(255,255,255,.74)}
/* Each blob is core → shoulder → nothing instead of one long fade. The shoulder is a
   readable boundary, and a boundary is the only thing glass can visibly refract: the
   same edge reads soft inside the pane and crisp outside it. A third blob puts one of
   those boundaries through the middle band, where the panels actually sit.
   Size and edge sharpness are separate dials: coverage is size × last stop, edge width is
   size × (last stop − shoulder stop). The blobs were grown ~35% for coverage and their
   shoulder percentages narrowed by the same factor, so the boundary stays as tight in
   pixels as it was — more colour under the panels, no loss of the refraction cue.
   Placement: four blobs sitting inside the plate rather than clamped to its corners, so
   this reads as a field the page floats in and not as four corner vignettes. Only the
   middle ~11–89% of the plate is ever on screen (top:-14vh on a 128vh box), so nothing is
   parked outside that band, and the two inward blobs start below full alpha — an inward
   core lands over the text column, where a saturated one would cost contrast.
   Hues run magenta → lavender → cyan → pale blue around the frame so neighbours blend.
   sRGB first as the fallback, then the same thing interpolated in oklab — if a browser
   rejects the colour-interpolation syntax the earlier declaration still stands. */
body.fx.about #bgfx{background-image:
  radial-gradient(62% 46% at 100% 83%,
    #ee5f6e 0%, rgba(238,95,110,.5) 34%, rgba(238,95,110,0) 90%),
  radial-gradient(54% 40% at -4% 85%,
    #f8cf96 0%, rgba(248,207,150,.48) 36%, rgba(248,207,150,0) 92%),
  radial-gradient(56% 46% at 100% 14%,
    #4a6cf7 0%, rgba(74,108,247,.46) 32%, rgba(74,108,247,0) 88%),
  radial-gradient(46% 54% at 105% 46%,
    rgba(159,192,245,.8) 0%, rgba(159,192,245,.42) 38%, rgba(159,192,245,0) 94%),
  radial-gradient(74% 62% at 30% 50%,
    rgba(206,228,218,.62) 0%, rgba(206,228,218,.4) 40%, rgba(206,228,218,0) 86%),
  radial-gradient(58% 42% at 50% 12%,
    #5fd8ee 0%, rgba(95,216,238,.46) 34%, rgba(95,216,238,0) 90%)}
body.fx.about #bgfx{background-image:
  radial-gradient(62% 46% at 100% 83% in oklab,
    #ee5f6e 0%, rgba(238,95,110,.5) 34%, rgba(238,95,110,0) 90%),
  radial-gradient(54% 40% at -4% 85% in oklab,
    #f8cf96 0%, rgba(248,207,150,.48) 36%, rgba(248,207,150,0) 92%),
  radial-gradient(56% 46% at 100% 14% in oklab,
    #4a6cf7 0%, rgba(74,108,247,.46) 32%, rgba(74,108,247,0) 88%),
  radial-gradient(46% 54% at 105% 46% in oklab,
    rgba(159,192,245,.8) 0%, rgba(159,192,245,.42) 38%, rgba(159,192,245,0) 94%),
  radial-gradient(74% 62% at 30% 50% in oklab,
    rgba(206,228,218,.62) 0%, rgba(206,228,218,.4) 40%, rgba(206,228,218,0) 86%),
  radial-gradient(58% 42% at 50% 12% in oklab,
    #5fd8ee 0%, rgba(95,216,238,.46) 34%, rgba(95,216,238,0) 90%)}
/* Grain — the high-frequency detail a blur can visibly destroy. Inside a pane the
   speckle smooths out, outside it stays crisp, and that difference is what reads as
   glass. It also kills the banding wide radial gradients produce.
   It lives on body::after, NOT inside #bgfx: a pseudo-element is a child, so in there it
   was multiplied by --bgfx-op and turning the gradient down took the grain with it.
   Two scales, because real grain is not one frequency and a single 150px tile starts to
   repeat visibly on a wide screen. Desaturated (feColorMatrix) so it reads as film grain
   rather than chroma noise, which is what colour turbulence looks like once it is strong
   enough to notice. Fixed, so it never shifts under the reader.
   MUST stay behind the glass — as an overlay on top it would grain the panels too and
   destroy the very contrast it exists to create.
   Normal blending only: mix-blend-mode:overlay would cancel the grey wash desaturated
   noise leaves behind, but it puts the layer in a blending group that flattens the
   sticky nav's backdrop-filter into a solid grey bar. The wash is instead kept down by
   leaning on the fine layer — low frequencies are what read as veil, high as grain. */
body.fx::after{content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
  opacity:var(--grain-op);
  background-size:150px 150px,320px 320px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='150' height='150'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='150' height='150' filter='url(%23n)' opacity='.58'/%3E%3C/svg%3E"),url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='320'%3E%3Cfilter id='c'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.3' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='320' height='320' filter='url(%23c)' opacity='.15'/%3E%3C/svg%3E")}
/* content sits above the plate. No :not(#bgfx) here — :not() carries its argument's
   specificity, and an id inside it would outweigh the #nav sticky rule below. */
body.fx > div{position:relative;z-index:1}

/* nav: sticky so the glass actually reads while the gradient moves under it */
body.fx #nav{position:sticky;top:0;z-index:20;
  background:var(--glass);backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  /* light catches the top AND the bottom edge, the way a pane of glass ends. A 7%-black
     hairline here is a leftover from the opaque design: too faint to read as a rule, too
     present to read as nothing. */
  box-shadow:var(--glass-sheen),inset 0 -1px 0 rgba(255,255,255,.55)}
body.fx .nav{border-bottom:0}
body.fx .nav .links a:hover{background:var(--dim)}
body.fx .chip{background:rgba(255,255,255,.62);border-color:rgba(34,34,34,.12)}
/* on the gradient pages the pills join the glass system rather than sitting on it */
body.fx .fbtn{background:rgba(255,255,255,.55);border-color:var(--glass-line);
  backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  box-shadow:var(--glass-sheen),var(--glass-lift)}
body.fx .fbtn:hover{background:rgba(255,255,255,.55);border-color:rgba(255,255,255,.85);
  box-shadow:var(--glass-sheen),var(--glass-lift-h),inset 0 0 0 999px var(--dim)}
body.fx #footer footer{border-top:0;
  background:var(--glass);backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  box-shadow:var(--glass-sheen)}

/* ---------- stat tiles: one pane of glass each ----------
   Landing metric strip and About dashboard share the recipe: no tray, no rails,
   so the gradient refracts once per tile instead of once per strip. */
body.fx .metrics .m, body.fx .metrics .m + .m,
body.fx .dash .tile, body.fx .dash .tile + .tile{
  padding:22px 18px;border:1px solid var(--glass-line);border-radius:14px;
  background:var(--glass);backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  box-shadow:var(--glass-sheen),var(--glass-lift);
  /* no isolation:isolate here — it creates a backdrop root, which would cut the edge
     ring's backdrop-filter off from the page behind it */
  position:relative;overflow:hidden;
  transition:border-color .25s ease,box-shadow .25s ease}
/* grid, not the base flex row: flex breaks the line before it shrinks, so the
   fifth tile drops to its own row and stretches full width at mid widths.
   auto-fit always fills the row evenly and drops to 4 / 3 / 2 columns on its own. */
body.fx .metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr))}
/* specular: light catches the top-left edge and falls off, the way a lens does */
body.fx .metrics .m::after, body.fx .dash .tile::after{
  content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
  background:linear-gradient(133deg,rgba(255,255,255,.6),rgba(255,255,255,0) 46%)}
body.fx .metrics .m:hover{border-color:rgba(255,255,255,.85);
  box-shadow:var(--glass-sheen),var(--glass-lift-h),inset 0 0 0 999px var(--dim)}
body.fx .metrics .v, body.fx .metrics .l,
body.fx .dash .v, body.fx .dash .l{position:relative;z-index:1}
/* the strips themselves stop being containers */
body.fx .metrics, body.fx .dash{border:0;border-radius:0;background:none;box-shadow:none;
  backdrop-filter:none;-webkit-backdrop-filter:none;padding:0;gap:10px}

/* the positioning cards join the glass system (same recipe as the stat tiles) */
body.fx.landing .pcard{border:1px solid var(--glass-line);border-radius:14px;
  background:var(--glass);backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  box-shadow:var(--glass-sheen),var(--glass-lift);position:relative;overflow:hidden}
body.fx.landing .pcard::after{content:"";position:absolute;inset:0;pointer-events:none;
  border-radius:inherit;
  background:linear-gradient(133deg,rgba(255,255,255,.6),rgba(255,255,255,0) 46%)}
body.fx.landing .pcard > *{position:relative;z-index:1}

/* ---------- landing: case cards ---------- */
body.fx.landing .card{position:relative;padding:8px;background:var(--glass);
  backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  border:1px solid var(--glass-line);
  box-shadow:var(--glass-sheen),var(--glass-lift)}
/* hover: same dim-together intent as the base card, but darkening instead of fading —
   opacity .82 over a light gradient lightens the panel, which reads as the card leaving */
body.fx.landing .card::after{content:"";position:absolute;inset:0;border-radius:inherit;
  pointer-events:none;background:rgba(20,18,28,0);transition:background .22s ease}
body.fx.landing .card:hover{opacity:1}
body.fx.landing .card:hover::after{background:rgba(20,18,28,.1)}
body.fx.landing .card .thumb{border-radius:8px}   /* inner radius, nested inside the 12px panel */

/* the design-system strip joins the glass system (same recipe as the case cards).
   Its base ::after already carries the hover dim, so only the surface changes here;
   the dim deepens to the card value on the gradient. */
body.fx.landing .dslink{background:var(--glass);border-color:var(--glass-line);
  backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  box-shadow:var(--glass-sheen),var(--glass-lift)}
body.fx.landing .dslink:hover::after{background:rgba(20,18,28,.1)}
body.fx.landing .card .cbody{padding:14px 10px 12px}

/* ---------- about: structured blocks get glass, prose stays on the gradient ----------
   Timeline and awards are lists of records, so they read as panels. The personal
   section is long-form and stays unglazed — the page needs somewhere to breathe. */
/* These two carry the long text, so their glass runs thicker than the tiles': at the
   widened gradient's shoulder, .55 puts #717171 body copy at 4.5:1 — exactly on the AA
   line, and under it nearer the blob cores. .64 buys back the margin. The dashboard
   tiles stay at .55: short labels, and they are where the refraction is on show. */
body.fx.about .timeline, body.fx.about .awards{
  padding:2px 26px;border:1px solid var(--glass-line);border-radius:16px;
  background:rgba(255,255,255,.64);
  backdrop-filter:var(--glass-blur);-webkit-backdrop-filter:var(--glass-blur);
  box-shadow:var(--glass-sheen),var(--glass-lift-lg);margin-bottom:64px}   /* tall enough for the deep tier */
body.fx.about .tl:last-child, body.fx.about .award:last-child{border-bottom:none}
body.fx.about .tl, body.fx.about .award{border-bottom-color:rgba(34,34,34,.07)}
body.fx.about .personal .interests li{border-top-color:rgba(34,34,34,.09)}

@supports not ((backdrop-filter:blur(4px)) or (-webkit-backdrop-filter:blur(4px))){
  body.fx #nav, body.fx #footer footer, body.fx .metrics .m, body.fx .dash .tile,
  body.fx.landing .card, body.fx.landing .dslink, body.fx.about .timeline,
  body.fx.about .awards, body.fx.about .saidgrid{
    background:rgba(255,255,255,.92)}
}
@media (prefers-reduced-motion:reduce){#bgfx{transform:none !important}}
@media (max-width:640px){
  body.fx{--glass-blur:saturate(160%) blur(14px)}   /* blur is the expensive part on mobile */
  body.fx .metrics, body.fx .dash{gap:8px}
  body.fx .metrics .m, body.fx .dash .tile{padding:18px 16px}
  body.fx.about .timeline, body.fx.about .awards{padding:2px 18px}
}

/* ---------- C · edge refraction ring (About) ----------
   Real glass is thickest at its edge, so light bends hardest there. A masked inset band
   runs a heavier backdrop-filter than the panel body; the difference between the two
   zones is what reads as thickness.
   Tiles only. On the tall panels the same rim just multiplies its own haze along a much
   longer perimeter, and the effect is a small-element one to begin with.
   No brightness: blurring at a boundary averages the two sides, and brightening that
   average produces a pale fog that eats into the panel. A real thick edge *gathers*
   colour, so the rim is saturate-led. The blur delta also stays modest — the mask sets
   the band's opacity but not the blur's sampling reach, so a 40px blur behind a 18px
   band washes far wider than the band suggests. */
body.fx.about .dash .tile::before,
body.fx.landing .metrics .m::before{
  content:"";position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  backdrop-filter:blur(24px) saturate(130%);
  -webkit-backdrop-filter:blur(24px) saturate(130%);
  /* Top and left only. A band around all four sides is a closed outline, and a closed
     outline reads as a second rounded rectangle no matter how softly it fades. Light has
     a direction — the same top-left the specular sheen already uses — so an open L of it
     reads as an edge catching light instead of a drawn shape. */
  -webkit-mask-image:linear-gradient(to bottom,#000,rgba(0,0,0,.3) 12px,transparent 30px),
    linear-gradient(to right,#000,rgba(0,0,0,.3) 12px,transparent 30px);
  mask-image:linear-gradient(to bottom,#000,rgba(0,0,0,.3) 12px,transparent 30px),
    linear-gradient(to right,#000,rgba(0,0,0,.3) 12px,transparent 30px)}

/* ============================================================
   DESIGN-SYSTEM NARRATIVE LINK — full-width strip on the landing,
   between the featured grid and "Earlier". Hover follows the site
   rule (darker dim, no lift). Guarded in render.js by S.dsNarrative.
   ============================================================ */
.dslink{display:flex;align-items:center;gap:24px;margin-top:20px;
  border:1px solid var(--line);border-radius:var(--radius);
  padding:22px 26px;color:var(--ink);position:relative;overflow:hidden}
.dslink:hover{text-decoration:none}
.dslink::after{content:"";position:absolute;inset:0;background:rgba(20,18,28,.06);
  opacity:0;transition:opacity .18s}
.dslink:hover::after{opacity:1}
.dslink .dsbody{flex:1;min-width:0}
.dslink .dlabel{font-size:12px;font-weight:600;color:var(--accent);margin-bottom:6px}
.dslink h3{font-family:var(--display);font-weight:700;font-size:18px;
  letter-spacing:-.01em;line-height:1.3;margin:0 0 6px}
.dslink p{font-size:13.5px;line-height:1.55;color:var(--ink-2);margin:0;max-width:640px}
.dslink .darrow{font-size:22px;color:var(--ink-3);flex:none}
.dslink:hover .darrow{color:var(--accent)}
/* thumbnail slot: nested 8px radius inside the 12px panel, same slow zoom as case cards */
.dslink .dsthumb{width:216px;aspect-ratio:16/10;flex:none;border-radius:8px;
  overflow:hidden;background:var(--panel)}
.dslink .dsthumb img{width:100%;height:100%;object-fit:cover;display:block;
  transition:transform .4s ease}
.dslink:hover .dsthumb img{transform:scale(1.045)}
@media(max-width:640px){
  .dslink{padding:14px 14px 16px;gap:14px;flex-direction:column;align-items:stretch}
  .dslink .dsthumb{width:100%}
  .dslink .darrow{display:none}
}

/* ============================================================
   LANGUAGE SWITCHER — sits at the end of the nav links
   ============================================================ */
.langsw{display:inline-flex;align-items:center;margin-left:22px;
  border:1px solid var(--line-strong);border-radius:100px;overflow:hidden;vertical-align:middle}
.nav .links .langsw a{margin-left:0;border-radius:0;padding:5px 11px;
  font-size:12.5px;font-weight:600;color:var(--ink-2);letter-spacing:.02em}
.nav .links .langsw a + a{border-left:1px solid var(--line)}
.nav .links .langsw a:hover{background:var(--panel);color:var(--ink)}
.nav .links .langsw a.on{background:var(--ink);color:#fff}
.nav .links .langsw a.on:hover{background:var(--ink);color:#fff}
@media (max-width:720px){.langsw{margin-left:14px}}

/* ============================================================
   KOREAN TYPOGRAPHY — scoped to body.lang-ko (set by assets/i18n.js)
   Nothing here touches the English render: drop the class and it is gone.
   Must stay LAST in the file so these overrides outrank the base rules.

   Why one family for both display and body:
   Hanken Grotesk carries no Hangul, so a mixed heading ("Flipster를 위한 …")
   would fall back per glyph and put Hanken's Latin next to another face's
   Hangul on the same line. Pretendard's Latin is Inter-derived, so using it
   for both keeps one voice. Pretendard is loaded on demand by i18n.js —
   English visitors never request it.
   ============================================================ */
body.lang-ko{
  --display:"Pretendard Variable", Pretendard, "Apple SD Gothic Neo", "Noto Sans KR", Inter, sans-serif;
  --sans:"Pretendard Variable", Pretendard, "Apple SD Gothic Neo", "Noto Sans KR", Inter, sans-serif;
  /* Hangul carries more meaning per character, so the same measure reads longer.
     Pulling the text column in keeps lines near the same word count. */
  --textw:620px;
  line-height:1.78;
  /* keep-all stops breaks inside a 어절; break-word still rescues long URLs
     and unbroken Latin strings that genuinely cannot fit. */
  word-break:keep-all;
  overflow-wrap:break-word;
}

/* Negative tracking is a Latin device. Hangul squares are already tight —
   tightening them collides the strokes, so every display site resets to normal. */
body.lang-ko .logo .lname,
body.lang-ko .hero h1,
body.lang-ko .metrics .v,
body.lang-ko .cases h2,
body.lang-ko .section-title,
body.lang-ko .card h3,
body.lang-ko .case-head h1,
body.lang-ko .sec h2,
body.lang-ko .sec .metric-row span,
body.lang-ko .dash .v,
body.lang-ko .tl h3,
body.lang-ko .slide h2,
body.lang-ko .dgm-node b,
body.lang-ko .bet .bet-top b,
body.lang-ko .co-card b,
body.lang-ko .exp-kicker,
body.lang-ko .exp-g > b,
body.lang-ko .score .sval b,
body.lang-ko .seq-step b,
body.lang-ko .apx-row b{letter-spacing:normal}

/* Hangul has no descender relief, so headings set for Latin read cramped.
   Each value is the base leading plus roughly .18. */
body.lang-ko .hero h1{line-height:1.32}
body.lang-ko .case-head h1{line-height:1.34}
body.lang-ko .sec h2{line-height:1.4}
body.lang-ko .slide h2{line-height:1.24;margin-bottom:22px}
body.lang-ko .card h3{line-height:1.52}
body.lang-ko .cases h2,
body.lang-ko .section-title{line-height:1.4}
body.lang-ko .tl h3{line-height:1.45}

/* Body copy: the base 1.65 is a Latin setting; Hangul wants more air. */
body.lang-ko .sec p,
body.lang-ko .summary4 .s p,
body.lang-ko .textcol,
body.lang-ko .interests li{line-height:1.8}

/* The deck is the one place Hangul does NOT get the full leading lift: every
   slide is a hard 100vh budget (HANDOFF §4) and 1.8 pushed three slides over.
   Large display type needs proportionally less extra anyway, so the deck runs
   its own tighter setting and reclaims the label margins.
   Re-measure all 33 slides at 1440x900 with images eager after ANY copy change. */
body.lang-ko .slide p{line-height:1.68}
/* ⚠️ 아직 영문인 슬라이드는 KO 모드에서 EN보다 5~27px 더 넘친다 — 영문 텍스트가
   Pretendard와 한글용 자간 해제(음수 → normal)를 물려받아 줄이 늘기 때문.
   한글 자간을 다시 조여서 맞추려는 시도는 **금지**(한글 획이 붙음, 이 블록의 존재 이유를 부정).
   번역이 끝나면 사라지는 일시적 상태다 — 실측으로 확인됨:
   번역된 슬라이드는 KO가 EN보다 오히려 여유 있음(#3 Order 11px → 6px, #15 37px → 21px). */
body.lang-ko .slide .kind{margin-bottom:18px}

/* Small print stays readable rather than getting the same lift. */
body.lang-ko .sfoot,
body.lang-ko .apx-foot,
body.lang-ko .exp-note,
body.lang-ko .tnote{line-height:1.7}

/* Numerals and codes keep Latin proportions: these cells are figures, not prose. */
body.lang-ko .metrics .v,
body.lang-ko .dash .v,
body.lang-ko .score .sval b,
body.lang-ko .apx-row b,
body.lang-ko .era,
body.lang-ko .award .y{font-feature-settings:"tnum" 1}

/* The status-bar clock inside the phone frames is a rendered UI, not page copy —
   keep it on the Latin stack so 9:41 matches the screenshots it sits on. */
body.lang-ko .shot .sbar b{font-family:Inter, -apple-system, sans-serif;letter-spacing:.02em}
