/* =====================================================================
   CLAY SHAPE + ORIGINAL COLORS  (experimental / reversible)
   Loaded AFTER style.css. Keeps Clay's *shape & type* — rounded warm
   display (Inter 500 + negative tracking), generous radii, compact
   rounded-rect buttons — but restores ALL of the site's ORIGINAL colors
   (soft-blue canvas, blue brand, gradient boards, blue buttons, the
   original textured canvas). No color overrides here, only geometry/type.

   To REVERT: delete this file and remove its <link> from the *.html files.
   Originals (style.css/projects.js/main.js) are untouched.
   ===================================================================== */

/* ---- Satoshi (body + UI + ALL headings) — self-hosted, Fontshare ----
   These three faces are the ONLY fonts this file loads. 17 unused .woff2 files
   (Open Runde, General Sans, Fraunces, Cal Sans, Satoshi 900, Instrument Serif)
   and their @font-face blocks were removed; they are in .claude/perf-backup/.
   WARNING: the literal "Open Runde" in the --sans/--display/--serif chains below
   is INTENTIONAL. Satoshi is first and covers every glyph the site uses, so that
   slot is never reached — leaving the string is a no-op, but removing it would
   change behaviour for a visitor with Open Runde installed locally. ---- */
@font-face { font-family: "Satoshi"; font-weight: 400; font-style: normal; font-display: swap;
  src: url("../fonts/satoshi/satoshi-400.woff2") format("woff2"); }
@font-face { font-family: "Satoshi"; font-weight: 500; font-style: normal; font-display: swap;
  src: url("../fonts/satoshi/satoshi-500.woff2") format("woff2"); }
@font-face { font-family: "Satoshi"; font-weight: 700; font-style: normal; font-display: swap;
  src: url("../fonts/satoshi/satoshi-700.woff2") format("woff2"); }

/* ----------------------------- Tokens ----------------------------- */
:root {
  /* Dwell-progress ring thickness. ONE token because two components draw the
     same ring -- the active process node and the testimonial avatar -- and they
     are meant to read as the same motif. It was 1px, which at DPR 1 is a single
     fully covered pixel between two partial ones: the same sub-pixel fault the
     hero avatar ring records, and the reason the arc looked faint and uneven at
     the top and bottom of the circle. 3px gives it a solid two-pixel core that
     survives any sub-pixel phase. 3px read heavy against these small circles,
     so it sits at 2px -- still a solid core at DPR 1, half the weight of 3. */
  --ring-w: 2px;
  /* The hairline that fades out at both ends. A flat 1px border stops dead at
     the container edge; this one dissolves, which is what keeps a centred block
     from looking boxed in.
     It used to say "two places draw it -- the hero and the footer's brand row".
     THE FOOTER ONE IS GONE (removed from .cta-final__foot on request), so the
     four remaining users are .trusted::before above 'Previously worked with',
     .contact-body, .contact-rail (<=900) and .bk-day (<=1100). */
  --rule-fade: linear-gradient(90deg, transparent, rgba(13, 26, 64, .14) 50%, transparent);
  /* the same hairline turned upright, for vertical separators (the project
     cards stat row). Same stops, so a horizontal and a vertical rule on the
     same page read as one material. */
  --rule-fade-y: linear-gradient(180deg, transparent, rgba(13, 26, 64, .14) 50%, transparent);
  /* Satoshi everywhere — body, UI, AND non-hero headings */
  --sans:  "Satoshi", "Open Runde", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* non-hero headings + the name wordmark also use Satoshi */
  --display: "Satoshi", "Open Runde", system-ui, sans-serif;
  /* ---- ORIGINAL colors restored (same as the very start) ---- */
  --bg:           #ffffff;
  --bg-soft:      #f6f7f9;
  --surface:      #ffffff;
  --surface-2:    #f6f9ff;

  --ink:          #0c0d12;
  --ink-2:        #232934;
  --muted:        #3a4150;   /* darker body/secondary — was #5b6473 */
  --faint:        #5a6273;   /* darker meta/tertiary — was #98a1b2 */

  --brand:        #1c1c1c;
  --brand-ink:    #000000;
  --brand-tint:   #e7eefc;
  --brand-soft:   rgba(28, 28, 28, .10);

  --line:         rgba(12, 13, 18, .08);
  --line-2:       rgba(12, 13, 18, .14);
  --shadow-sm:    0 1px 2px rgba(13, 26, 64, .04), 0 2px 8px rgba(13, 26, 64, .04);
  --shadow-md:    0 8px 30px rgba(13, 26, 64, .08);
  --shadow-lg:    0 24px 60px rgba(13, 26, 64, .12);

  /* ---- KEEP Clay shape + type ---- */
  --serif: "Satoshi", "Open Runde", system-ui, -apple-system, sans-serif;
  --radius:    16px;   /* content cards */
  --radius-lg: 24px;   /* feature cards */
  --radius-sm: 12px;   /* buttons + inputs */
}

/* Remove the dotted grid texture on the canvas */
body::before { display: none !important; }

/* --------------------------- Typography --------------------------- */
/* Clay rounded display: Inter weight 500, upright, negative tracking */
.display, .cta-band__title {
  font-family: var(--display);
  font-style: normal;
  font-weight: 500;
  letter-spacing: -0.02em;
}
.hero__title {
  font-family: "Averia Serif Libre", var(--display);   /* Averia Serif Libre display face */
  font-size: clamp(1.8125rem, 1.22rem + 2.2vw, 2.625rem);   /* 29px mobile → 42px cap (reached @1022): the page's clear apex */
  font-weight: 700;                          /* bold cut gives the serif heading presence */
  color: #1c1c1c;                            /* softer grey instead of near-black */
  line-height: 1.16;
  letter-spacing: -0.005em;
  max-width: 28em;                           /* wide enough that the column, not this, drives wrapping → 2 lines */
  text-wrap: balance;
}

/* Orbit removed — shrink the photo so the headline is the focal point */
.hero__visual { width: auto; min-height: 0; margin-bottom: clamp(16px, 2.2vw, 26px); }
.hero__photo { width: clamp(92px, 11vw, 124px); outline-width: 6px; }
.hero__ring { width: clamp(170px, 19vw, 230px); }

/* =====================================================================
   SIGNATURE "APPEAR" ENTRANCE (reverse-engineered from kirayanti.space)
   blur(5px)+fade+rise -> settle, 0.6s, cubic-bezier(.44,0,.56,1), staggered.
   ===================================================================== */
:root { --ease-appear: cubic-bezier(.44, 0, .56, 1); }

/* Project cards come alive on scroll: the card blurs-up + rises, then its
   content (logo -> title -> summary -> meta -> button) staggers in. */
#work .study {
  opacity: .001; filter: blur(6px); transform: translateY(48px) scale(.985);
  transition: opacity .6s var(--ease-appear), filter .6s var(--ease-appear), transform .68s var(--ease-appear);
  will-change: transform, opacity, filter;
}
/* filter: NONE, not blur(0). A zero blur is still a filter, and a filter makes
   the element a stacking context AND a containing block for absolutely
   positioned descendants. That is what broke the card twice: the click overlay
   was trapped inside .study__title (401x65 instead of 1080x448), and the globe
   button was trapped inside .study__brand below the overlay. CSS interpolates a
   filter list against  using identity values, so the blur still animates
   out — verified, not assumed. */
#work .study.is-in { opacity: 1; filter: none; transform: none; }
/* "View all projects" button — reveals after the cards it follows */
#work .work-more.reveal { opacity: 0; transform: translateY(24px); transition: opacity 1.4s ease, transform 1.4s cubic-bezier(.22, 1, .36, 1); transition-delay: .35s !important; }
#work .work-more.reveal.is-in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  #work .work-more.reveal { transition: none; opacity: 1; transform: none; }
}
/* image zoom-settle: starts gently scaled-in, eases to 1 as the card reveals
   (clipped to its own frame so it can't bleed past the card edge) */
#work .study__media { overflow: hidden; }
#work .study .study__media { transform: scale(1.12); transition: transform 1s var(--ease-appear); }
/* Cursor-tracked tilt on the screenshot.
   Composed INTO the reveal's own transform, not added to a wrapper: this
   element already animates scale(1.12) -> scale(1) for the zoom-settle
   entrance, and a second rule setting transform would simply win and kill
   it. The 1s entrance transition is left untouched -- .is-tilt / .is-untilt
   swap the duration only while the pointer is on the card, so the tilt can
   track tightly (.14s) and return softly (.55s) without touching the reveal. */
#work .study.is-in .study__media {
  transform: perspective(1100px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) scale(1);
}
#work .study__brand, #work .study__title, #work .study__sum,
#work .study__meta, #work .study__btn,
#work .study__chips, #work .study__impact, #work .study__stats {
  opacity: .001; filter: blur(4px); transform: translateY(12px);
  transition: opacity .75s var(--ease-appear), filter .75s var(--ease-appear), transform .75s var(--ease-appear);
}
#work .study.is-in .study__brand { transition-delay: .20s; }
#work .study.is-in .study__title { transition-delay: .34s; }
#work .study.is-in .study__sum   { transition-delay: .48s; }
#work .study.is-in .study__chips { transition-delay: .48s; }
#work .study.is-in .study__meta  { transition-delay: .62s; }
#work .study.is-in .study__impact { transition-delay: .62s; }
#work .study.is-in .study__btn   { transition-delay: .76s; }
#work .study.is-in .study__stats { transition-delay: .76s; }
#work .study.is-in .study__brand, #work .study.is-in .study__title,
#work .study.is-in .study__sum, #work .study.is-in .study__meta,
#work .study.is-in .study__btn, #work .study.is-in .study__chips,
#work .study.is-in .study__impact, #work .study.is-in .study__stats { opacity: 1; filter: none; transform: none; }

/* first featured card: animates like the rest, sequenced to land right after the
   hero cascade (hero logos rise at .70s) — card + inner stagger shifted by .45s */
#work .studies[data-featured-grid] .study:first-child { transition-delay: .45s !important; }   /* beats the inline reveal delay from main.js */
#work .studies[data-featured-grid] .study:first-child.is-in .study__media { transition-delay: .45s; }
#work .studies[data-featured-grid] .study:first-child.is-in .study__brand { transition-delay: .65s; }
#work .studies[data-featured-grid] .study:first-child.is-in .study__title { transition-delay: .79s; }
#work .studies[data-featured-grid] .study:first-child.is-in .study__sum,
#work .studies[data-featured-grid] .study:first-child.is-in .study__chips { transition-delay: .93s; }
#work .studies[data-featured-grid] .study:first-child.is-in .study__meta,
#work .studies[data-featured-grid] .study:first-child.is-in .study__impact { transition-delay: 1.07s; }
#work .studies[data-featured-grid] .study:first-child.is-in .study__btn,
#work .studies[data-featured-grid] .study:first-child.is-in .study__stats { transition-delay: 1.21s; }

/* Tilt timing. These MUST sit below the cascade above: that block sets a .45s
   transition-delay on the first featured card's media, which the tilt would
   otherwise inherit -- the card then lags half a second behind the cursor.
   The cascade selector is (1,5,0) and these are (1,4,0), so source order alone
   does NOT win it -- hence !important on the delay. It is safe: .is-tilt only
   exists while the pointer is on the card, so the entrance cascade is untouched. */
#work .studies .study.is-tilt .study__media,
#work .study.is-tilt .study__media {
  transition: transform .14s ease-out; transition-delay: 0s !important; will-change: transform;
}
#work .studies .study.is-untilt .study__media,
#work .study.is-untilt .study__media {
  transition: transform .55s cubic-bezier(.22, 1, .36, 1); transition-delay: 0s !important;
}
@media (prefers-reduced-motion: reduce) {
  #work .study.is-tilt .study__media, #work .study.is-untilt .study__media { transition: none; }
}

@media (prefers-reduced-motion: reduce) {
  .hero__projects, #work .study,
  #work .study__brand, #work .study__title, #work .study__sum,
  #work .study__meta, #work .study__btn,
  #work .study__chips, #work .study__impact, #work .study__stats {
    opacity: 1 !important; filter: none !important; transform: none !important; transition: none !important;
  }
}

/* ---- Featured grid (homepage): a clean spaced list — no sticky stacking, so a
   hovered/elevated card never covers the others. ---- */
/* "View all projects" button matches the hero "Let's chat" sizing + grey */
#work .studies + div .btn { height: 44px; padding: 0 24px; font-size: 1.125rem; background: linear-gradient(135deg, #3a3a3a 0%, #1c1c1c 60%, #0e0e0e 100%); gap: 0; }
#work .studies + div .btn:hover { background: linear-gradient(135deg, #454545 0%, #232323 60%, #121212 100%); }
/* arrow reveals + slides in on hover, exactly like the "Let's chat" CTA */
#work .studies + div .btn .ar {
  width: 0; opacity: 0; margin-left: 0; transform: translateX(-6px); overflow: hidden;
  transition: width .35s var(--ease), opacity .25s ease, margin-left .35s var(--ease), transform .35s var(--ease);
}
#work .studies + div .btn:hover .ar { width: 15px; opacity: 1; margin-left: 7px; transform: translateX(0); }
#work .studies .study {
  /* soft depth so each card reads as its own layer */
  box-shadow: 0 2px 6px rgba(13,26,64,.05), 0 24px 50px -30px rgba(13,26,64,.32);
}
/* hover: a clean pop — cards no longer overlap, so nothing gets hidden */
#work .studies .study.is-in:hover {
  transform: translateY(-7px) scale(1.012);
  box-shadow: 0 6px 14px rgba(28,28,28,.07), 0 36px 66px -26px rgba(28,28,28,.45);
  transition: transform .16s var(--ease), box-shadow .16s var(--ease);
  /* The first featured card carries transition-delay: .45s !important from the
     entrance cascade, so it sat still for nearly half a second before starting
     to lift. That delay is for the reveal; an interaction must not inherit it. */
  transition-delay: 0s !important;
  z-index: 2;
}
/* The RETURN was falling back to the entrance rule's .68s, so the card sank
   slowly after the pointer left -- that is what read as sluggish, not the pop.
   .is-tilt / .is-untilt are set by the tilt handler on pointer enter and leave,
   so they exist only during interaction: the entrance animation never sees
   them and keeps its own .68s zoom-settle. */
#work .studies .study.is-in.is-tilt {
  transition: transform .16s var(--ease), box-shadow .16s var(--ease);
  transition-delay: 0s !important;
}
#work .studies .study.is-in.is-untilt {
  transition: transform .26s var(--ease), box-shadow .26s var(--ease);
  transition-delay: 0s !important;
}

/* ---- Closing footer: hero-style box (light, transparent, bordered) ----------
   Was the dark CTA panel; now matched to the hero container (.hero-box). Holds the
   headline, the email + Book-a-Call control, and the brand/copyright bar. */
.cta-foot { padding-bottom: clamp(40px, 6vw, 76px); }   /* lands 76 @1280 (even) */
.cta-final {
  position: relative; overflow: visible;
  border: 1px solid #e7eaf1;
  border-radius: 24px;   /* matched to the project cards */
  /* 4-pt snap: the plain clamp is the fallback, round() quantises to the grid */
  padding: clamp(32px, 3.3vw, 44px) clamp(24px, 5vw, 64px) clamp(24px, 2vw, 28px);
  padding: round(clamp(32px, 3.3vw, 44px), 4px) round(clamp(24px, 5vw, 64px), 4px) round(clamp(24px, 2vw, 28px), 4px);   /* 44 · 64 · 24 @1280 */
  text-align: center; color: var(--ink);
  background: transparent;
  box-shadow: 0 1px 2px rgba(13, 26, 64, .04), 0 18px 50px -28px rgba(13, 26, 64, .22);
}
/* content cascade: heading → subtitle → actions → links → footer bar (hero-style) */
.cta-final.reveal .cta-final__title,
.cta-final.reveal .cta-final__sub,
.cta-final.reveal .cta-final__form,
.cta-final.reveal .cta-final__links,
.cta-final.reveal .cta-final__foot {
  opacity: 0; transform: translateY(16px);
  transition: opacity .9s ease, transform .9s cubic-bezier(.22, 1, .36, 1);
}
.cta-final.reveal.is-in .cta-final__title,
.cta-final.reveal.is-in .cta-final__sub,
.cta-final.reveal.is-in .cta-final__form,
.cta-final.reveal.is-in .cta-final__links,
.cta-final.reveal.is-in .cta-final__foot { opacity: 1; transform: none; }
.cta-final.reveal.is-in .cta-final__title { transition-delay: .12s; }
.cta-final.reveal.is-in .cta-final__sub   { transition-delay: .36s; }
.cta-final.reveal.is-in .cta-final__form  { transition-delay: .60s; }
.cta-final.reveal.is-in .cta-final__links { transition-delay: .84s; }
.cta-final.reveal.is-in .cta-final__foot  { transition-delay: 1.08s; }
@media (prefers-reduced-motion: reduce) {
  .cta-final.reveal .cta-final__title, .cta-final.reveal .cta-final__sub, .cta-final.reveal .cta-final__form,
  .cta-final.reveal .cta-final__links, .cta-final.reveal .cta-final__foot { transition: none; opacity: 1; transform: none; }
}

.cta-final__title {
  margin: 0 auto; max-width: 22ch;
  font-family: "Averia Serif Libre", var(--display); font-weight: 700;   /* matches every section heading */
  font-size: clamp(1.625rem, 1.325rem + 1.16vw, 2.25rem); line-height: 1.16; letter-spacing: -0.01em;   /* 36px cap — shared section-heading step */
  color: #1c1c1c; text-wrap: balance;
}
/* The section descriptions share this rule rather than copying it, so the
   footer and the three section heads cannot drift apart. */
.cta-final__sub,
.sec-sub {
  margin: 8px auto 0; max-width: 46ch;
  font-family: var(--sans); font-weight: 500;
  font-size: 1rem; line-height: 1.5;   /* 16px */
  color: var(--muted);
  text-wrap: balance;   /* even out the two lines — avoid a lone word on line 2 */
}
/* Book a Call → the site's prominent primary button (44px · 0 24px · 18px) */
.cta-final .btn { height: 44px; padding: 0 24px; font-size: 1.125rem; }

/* email + copy + Book-a-Call control (on the light footer) */
.cta-final__form {
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  gap: 12px; margin-top: 24px;   /* narrative → actions: the OUTER gap that opens the group */
}
.cta-mail {
  display: inline-flex; align-items: center; gap: 8px;
  /* The site grey, not a one-off. This was the only #f1f1f2 in the sheet and
     its #e4e4e6 edge the only one of those -- against the availability card,
     which is already on the system value, the two greys read as a mismatch.
     Both now use the tokens every other surface uses. */
  background: var(--bg-soft, #f6f7f9); border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 5px 5px 5px 14px; height: 44px;   /* same height as Book a Call, tighter */
}
.cta-mail__addr { color: var(--ink); font-weight: 500; font-size: 1rem; white-space: nowrap; }
.cta-mail__copy {
  height: 34px; padding: 0 12px; border-radius: var(--radius-sm); border: 1px solid #d5d7dd;   /* firmer edge so the control reads clickable */ cursor: pointer; flex: none;
  display: inline-flex; align-items: center; gap: 6px; background: #fff; color: var(--ink, #16181d);   /* was --faint: light grey on white read as a disabled control */
  font-family: var(--display); font-weight: 500; font-size: 0.875rem;
  transition: background .2s ease, color .2s ease;
}
/* hover: the same slide-up fill as the nav’s “Let’s chat” CTA. That one duplicates
   its label in ::before content; this cannot — the button has an icon and two
   swapping labels (Copy / Copied) — so the panel is a bare background behind the
   content instead, and the text simply inverts. Same motion, one source of truth. */
.cta-mail__copy { position: relative; overflow: hidden; isolation: isolate; }
.cta-mail__copy > * { position: relative; z-index: 2; }
.cta-mail__copy::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: var(--ink, #16181d); border-radius: inherit;
  transform: translateY(101%);
  transition: transform .7s cubic-bezier(.4, 0, .2, 1);
}
.cta-mail__copy:hover::before { transform: translateY(0); }
.cta-mail__copy:hover { color: #fff; border-color: var(--ink, #16181d); }
/* the confirmation is green on white — retract the panel so it stays legible
   while the pointer is still resting on the button after the click */
.cta-mail__copy.is-copied::before { transform: translateY(101%); }
.cta-mail__copy.is-copied:hover { color: #0f9d57; border-color: #bfe6cf; }
@media (prefers-reduced-motion: reduce) {
  .cta-mail__copy::before { transition: none; }
}
.cta-mail__copy svg { width: 15px; height: 15px; flex: none; }
.cta-mail__copy .ico-check { color: #0f9d57; }
.cta-mail__copy .ico-check, .cta-mail__copy .lbl-done { display: none; }
.cta-mail__copy.is-copied { color: #0f9d57; border-color: #bfe6cf; }
.cta-mail__copy.is-copied .ico-copy, .cta-mail__copy.is-copied .lbl-copy { display: none; }
.cta-mail__copy.is-copied .ico-check, .cta-mail__copy.is-copied .lbl-done { display: inline; }
/* full "Case studies" hover recipe: springy scale + gradient lift + glow, arrow reveals.
   Shared by Book a Call and the homepage "View all projects" button. */
.cta-book.btn,
#work .work-more .btn {
  gap: 0; transform-origin: center;
  transition: transform .4s cubic-bezier(.34, 1.56, .5, 1), box-shadow .35s ease, background .35s ease;
}
.cta-book.btn .ar,
#work .work-more .btn .ar {
  width: 0; opacity: 0; margin-left: 0; transform: translateX(-6px); overflow: hidden;
  transition: width .35s var(--ease), opacity .25s ease, margin-left .35s var(--ease), transform .35s var(--ease);
}
.cta-book.btn:hover,
.cta-book.btn:focus-visible,
#work .work-more .btn:hover,
#work .work-more .btn:focus-visible {
  background: linear-gradient(135deg, #454545 0%, #232323 60%, #121212 100%);
  transform: scale(1.06);
  box-shadow: 0 14px 28px -12px rgba(28, 28, 28, .7);
}
.cta-book.btn:hover .ar,
.cta-book.btn:focus-visible .ar,
#work .work-more .btn:hover .ar,
#work .work-more .btn:focus-visible .ar { width: 15px; opacity: 1; margin-left: 8px; transform: translateX(0); }
.cta-book.btn:active,
#work .work-more .btn:active { transform: scale(.99); }
@media (prefers-reduced-motion: reduce) {
  .cta-book.btn, .cta-book.btn .ar,
  #work .work-more .btn, #work .work-more .btn .ar { transition: none; }
  .cta-book.btn:hover, .cta-book.btn:focus-visible,
  #work .work-more .btn:hover, #work .work-more .btn:focus-visible { transform: none; }
}

/* ---------------------------------------------------------------
   "View all projects" — dark pill, white chip
   The chip is always visible, so the hide-and-reveal arrow above is
   switched off for this button only (.cta-book keeps it). Padding
   goes asymmetric — a circle needs only enough room to be inset, a label
   needs a proper text inset — so the chip side is 13 against the label's 24.
   It was 6, which put the circle 7 from the edge and read as crowding it.
   --------------------------------------------------------------- */
/* Specificity note: #work .studies + div .btn is (1,2,1) and sits ABOVE this
   in the sheet, so a (1,2,0) selector loses to it however late it comes. These
   match its shape so source order decides, which is why the sibling combinator
   is repeated here rather than simplified away -- and why the white fill has to
   be restated: that rule also sets the dark gradient. */
#work .work-more .btn--chip,
#work .studies + div .btn--chip {
  /* padding-right is 13, not 6: with the 1px border that insets the chip 14
     from the right edge against a vertical inset of 7 = (48 - 34) / 2, so the
     chip side is twice the vertical gap. That is the same 2:1 the .back-pill
     chip runs (12 against 6) — one rule, not two taste calls. At 6/7 the chip
     read as crowding the border; see .claude/viewall-trial.js, which renders
     the candidates at 3x and prints each one's right:vertical ratio. */
  gap: 14px; padding: 6px 13px 6px 24px; height: 48px;
  background: #fff; color: var(--ink, #0c0d12);
  border: 1px solid rgba(12, 13, 18, .12);
  box-shadow: 0 0 2px rgba(28, 28, 28, .05),
              0 0 7px rgba(28, 28, 28, .05),
              0 0 16px -2px rgba(28, 28, 28, .06);
}
/* the same even glow the cards lift on, so the button belongs to them */
#work .work-more .btn--chip:hover,
#work .work-more .btn--chip:focus-visible,
#work .studies + div .btn--chip:hover,
#work .studies + div .btn--chip:focus-visible {
  background: #fff;
  border-color: rgba(12, 13, 18, .18);
  box-shadow: 0 0 3px rgba(28, 28, 28, .06),
              0 0 12px rgba(28, 28, 28, .06),
              0 0 28px -4px rgba(28, 28, 28, .14);
  transform: translateY(-3px);
}
/* the old arrow is gone from the markup; this kills any inherited sizing
   should it ever come back */
#work .work-more .btn--chip .ar { display: none; }
#work .work-more .btn--chip .btn__chip {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: #fff; color: var(--ink, #0c0d12);
  box-shadow: inset 0 0 0 1px rgba(12, 13, 18, .18);   /* white on white needs an edge */
  transition: background .3s ease, color .3s ease, box-shadow .3s ease,
              transform .35s cubic-bezier(.34, 1.56, .5, 1);
}
#work .work-more .btn--chip .btn__chip svg { width: 15px; height: 15px; display: block; }
/* hover: the flip. The ring is what keeps a near-black circle legible against
   the pill's own #0e0e0e right end. */
#work .work-more .btn--chip:hover .btn__chip,
#work .work-more .btn--chip:focus-visible .btn__chip {
  background: var(--ink, #0c0d12); color: #fff;
  box-shadow: inset 0 0 0 1px transparent;
  transform: translateX(3px);
}
@media (prefers-reduced-motion: reduce) {
  #work .work-more .btn--chip .btn__chip { transition: none; }
  #work .work-more .btn--chip:hover .btn__chip { transform: none; }
}

/* secondary pill links — the site's card-CTA language: a quiet pill whose
   circular arrow chip fills ink on hover while the arrow flies out and a
   twin swoops back in (↗ for external, ↓ for the résumé download) */
.cta-final__links {
  display: flex; justify-content: center; align-items: center; gap: 12px;
  margin-top: 12px;   /* INNER gap — matches the 12px between the controls beside it,
                         so primary + secondary bind into one action group (24 out / 12 in) */
}
.cta-link {
  display: inline-flex; align-items: center; gap: 9px;
  height: 44px; padding: 0 5px 0 12px; border-radius: 999px;
  background: #fff; border: 1px solid #eef1f6;   /* softer than --line — reads secondary to Book a Call */
  font-family: var(--display); font-size: 1rem; font-weight: 500;   /* 16px */
  color: var(--ink, #0c0d12); text-decoration: none;
  transition: transform .35s cubic-bezier(.34, 1.56, .5, 1), box-shadow .3s ease, border-color .3s ease;
}
/* leading brand icon (LinkedIn glyph · résumé document) */
.cta-link__ico { display: inline-flex; flex: none; color: var(--ink, #0c0d12); }
.cta-link__ico svg { width: 16px; height: 16px; display: block; }
.cta-link:hover {
  transform: translateY(-2px);
  border-color: rgba(12, 13, 18, .22);
  box-shadow: 0 10px 22px -14px rgba(12, 13, 18, .4);
}
.cta-link:focus-visible { outline: 2px solid var(--ink, #0c0d12); outline-offset: 3px; }
.cta-link__chip {
  position: relative; overflow: hidden;
  width: 27px; height: 27px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  border: 1px solid rgba(12, 13, 18, .12); color: var(--ink, #0c0d12);
  transition: background .3s ease, border-color .3s ease, color .3s ease;
}
.cta-link:hover .cta-link__chip { background: #1c1c1c; border-color: #1c1c1c; color: #fff; }
.cta-link__chip svg {
  position: absolute; width: 13px; height: 13px;
  transition: transform .4s cubic-bezier(.34, 1.56, .5, 1);
}
/* external (↗): arrow exits top-right, its twin swoops in from bottom-left */
.cta-link--ext .cta-link__chip svg:nth-child(2) { transform: translate(-22px, 22px); }
.cta-link--ext:hover .cta-link__chip svg:first-child { transform: translate(22px, -22px); }
.cta-link--ext:hover .cta-link__chip svg:nth-child(2) { transform: translate(0, 0); }
/* download (↓): arrow drops out the bottom, its twin falls in from the top */
.cta-link--dl .cta-link__chip svg:nth-child(2) { transform: translateY(-30px); }
.cta-link--dl:hover .cta-link__chip svg:first-child { transform: translateY(30px); }
.cta-link--dl:hover .cta-link__chip svg:nth-child(2) { transform: translateY(0); }
@media (prefers-reduced-motion: reduce) {
  .cta-link, .cta-link__chip svg { transition: none; }
  .cta-link--ext .cta-link__chip svg:nth-child(2),
  .cta-link--dl .cta-link__chip svg:nth-child(2) { display: none; }
  .cta-link--ext:hover .cta-link__chip svg:first-child,
  .cta-link--dl:hover .cta-link__chip svg:first-child { transform: none; }
}
.cta-final__avail {
  margin: 18px auto 0; max-width: 60ch;
  font-family: var(--sans); font-size: 0.8125rem; line-height: 1.5;
  color: rgba(71, 85, 105, 0.72);
}

/* ≤560px: email control and booking button stack; links stay evenly spaced */
@media (max-width: 560px) {
  .cta-final__form { flex-direction: column; }
  .cta-mail { width: 100%; max-width: 340px; justify-content: space-between; }
  .cta-book.btn { width: 100%; max-width: 340px; justify-content: center; }
  .cta-final__links { gap: 10px; flex-wrap: wrap; }
}

/* brand + copyright bar at the bottom of the footer */
.cta-final__foot {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 28px; padding-top: 24px;   /* the 28/24 rhythm is kept deliberately */
  border-top: none; text-align: left;
  /* The faded hairline that used to sit here is REMOVED. It was drawn as a
     background-image at `100% 1px`, not a border-top (which is explicitly
     none), so looking for a border to delete finds nothing. The spacing is
     left alone on purpose: the rule was the only thing taken out, so the
     sign-off row keeps its position. --rule-fade still has FOUR other users
     (.trusted::before, .contact-body, .contact-rail, .bk-day), so the token
     stays. Do not confuse it with --rule-fade-y, which is a separate token
     with four users of its own. */
}
.cta-final__brand { display: flex; flex-direction: column; gap: 6px; }
/* footer-weight brand: a quiet sign-off, not a second navbar. The nav lockup is
   the loud one (28px mark · 18px name) — this steps down so it reads as a mark
   of authorship at the end of the page rather than a repeat of the header. */
.cta-final__brand .brand { gap: 8px; }
/* The nav mark is 28px and the SVG’s own rx=9 (of a 32 viewBox) resolves to
   ~7.9px — 28% of the box. Reusing --radius-sm (12px) here clipped a 22px box at
   55%, which read as a circle rather than the same squircle. 6px keeps the ratio. */
.cta-final__brand .brand__mark { width: 22px; height: 22px; border-radius: 6px; }
.cta-final__brand .brand__name { font-size: 0.875rem; color: var(--muted, #3a4150); }   /* 14px — on the small step, muted */
.cta-final__tag { margin: 0; color: var(--muted); font-size: 1rem; }
.cta-final__copy { margin: 0; color: var(--faint); font-size: 0.875rem; font-weight: 500; }
@media (max-width: 560px) {
  .cta-final__foot { justify-content: center; text-align: center; }
  .cta-final__brand { align-items: center; }
}

/* footer container: matched to the hero container (.hero-box) — transparent box,
   light border, soft two-layer shadow, same rounded corners */
.footer__card {
  background: transparent;
  border: 1px solid #e7eaf1;
  border-radius: 24px;   /* matched to the project cards */
  box-shadow: 0 1px 2px rgba(13, 26, 64, .04), 0 18px 50px -28px rgba(13, 26, 64, .22);
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}

/* ---- Skills radar + impact stats + projects-per-year line chart --------------
   Hand-drawn inline SVG (no chart lib), themed: brand-blue radar/line, the theme
   green for the positive series + arrows, --line gridlines, Satoshi labels. */
.skills { display: flex; flex-direction: column; gap: clamp(36px, 5vw, 60px); }
.skills__top {
  display: grid; grid-template-columns: 1.12fr .88fr; gap: clamp(28px, 5vw, 64px);
  align-items: center;
}
@media (max-width: 880px) { .skills__top { grid-template-columns: 1fr; gap: clamp(28px, 6vw, 44px); } }

/* radar */
.skills__radar { margin: 0; align-self: center; width: 100%; max-width: 560px; }
.skills__radar .rdr { width: 100%; height: auto; display: block; overflow: visible; }
.rdr__ring  { fill: none; stroke: var(--line); stroke-width: 1; }
.rdr__ring:nth-of-type(2n) { fill: rgba(12, 13, 18, .018); }   /* faint alternating bands */
.rdr__spoke { stroke: var(--line); stroke-width: 1; }
.rdr__tick  { fill: #aab2bf; font: 600 11px var(--sans); }
.rdr__area  { fill: rgba(28, 28, 28, .15); stroke: var(--brand); stroke-width: 2.5; stroke-linejoin: round; }
.rdr__dot   { fill: var(--brand); }
.rdr__label { fill: var(--ink-2); font: 600 13px var(--sans); }

/* stats */
.skills__stats { display: flex; flex-direction: column; gap: clamp(24px, 3.2vw, 38px); }
@media (max-width: 880px) and (min-width: 561px) { .skills__stats { flex-direction: row; gap: clamp(32px, 6vw, 56px); } }
.stat-cell__top { display: flex; align-items: center; gap: 10px; }
.stat-cell__num { font-family: var(--display); font-weight: 600; font-size: clamp(2rem, 3.4vw, 2.875rem); letter-spacing: -.02em; color: var(--ink); line-height: 1; }
.stat-cell__arw { width: 26px; height: 26px; color: #0f9d57; flex: none; }
.stat-cell__arw svg { width: 100%; height: 100%; display: block; }
.stat-cell__txt { margin: 12px 0 0; max-width: 32ch; color: var(--ink-2); font-size: clamp(1rem, 1.3vw, 1.0625rem); line-height: 1.5; }

/* line chart */
.skills__chart { padding-top: clamp(28px, 3.4vw, 44px); border-top: 1px solid var(--line); }
.skills__chart-title { font-family: var(--display); font-weight: 600; font-size: clamp(1.25rem, 1.9vw, 1.5rem); letter-spacing: -.015em; color: var(--ink); margin: 0; }
.skills__legend { display: flex; flex-wrap: wrap; gap: 20px; margin: 16px 0 6px; }
.lc__leg { display: inline-flex; align-items: center; gap: 8px; font-size: .9375rem; font-weight: 500; color: var(--ink-2); }
.lc__leg i { width: 18px; height: 3px; border-radius: 2px; flex: none; }
.lc__leg--blue  i { background: var(--brand); }
.lc__leg--muted i { background: #b3bac6; }
.lc__leg--green i { background: #0f9d57; }
.skills__plot { margin: 0; }
.skills__plot .lc { width: 100%; height: auto; display: block; }
.lc__grid { stroke: var(--line); stroke-width: 1; }
.lc__yt, .lc__xt { fill: #aab2bf; font: 600 12px var(--sans); }
.lc__yt { text-anchor: end; }
.lc__xt { text-anchor: middle; }
.lc__line { fill: none; stroke-width: 2.5; stroke-linejoin: round; stroke-linecap: round; }
.lc__line--blue  { stroke: var(--brand); stroke-width: 3; }
.lc__line--muted { stroke: #c2c8d2; }
.lc__line--green { stroke: #0f9d57; }
.skills__caption { margin: 18px 0 0; max-width: 72ch; color: var(--ink-2); font-size: clamp(.9375rem, 1.2vw, 1rem); line-height: 1.55; }

/* ---- Experience years bar ---- */
.reach-years { margin-top: clamp(30px, 3.6vw, 48px); padding-top: clamp(30px, 3.6vw, 48px); border-top: 1px solid var(--line); max-width: 780px; }
.reach-years__line { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }
.reach-years__num { font-family: var(--display); font-weight: 600; font-size: clamp(1.625rem, 2.7vw, 2.25rem); letter-spacing: -.02em; color: var(--ink); line-height: 1.1; }
.reach-years__lbl { color: var(--ink-2); font-size: clamp(1rem, 1.3vw, 1.0625rem); }
.reach-years__bar { height: 6px; border-radius: 999px; background: #e9dfce; margin: 14px 0; overflow: hidden; }
.reach-years__fill { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--brand), #5c8def); }

/* ---- 25+ products + domains  |  Markets & Platforms donut ---- */
.reach-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: clamp(28px, 5vw, 64px); align-items: center;
  padding-top: clamp(20px, 2.4vw, 32px);
}
@media (max-width: 880px) { .reach-grid { grid-template-columns: 1fr; gap: clamp(28px, 6vw, 40px); } }

.reach-prod__num { font-family: var(--display); font-weight: 600; font-size: clamp(1.75rem, 3vw, 2.5rem); letter-spacing: -.02em; color: var(--ink); margin: 0; line-height: 1.05; }
.reach-prod__txt { color: var(--ink-2); font-size: clamp(1rem, 1.3vw, 1.0625rem); margin: 8px 0 0; max-width: 40ch; }
.reach-domains { list-style: none; margin: clamp(20px, 2.6vw, 30px) 0 0; padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 14px 22px; }
.reach-domains__item { display: flex; align-items: center; gap: 9px; font-size: .9375rem; font-weight: 500; color: var(--ink-2); }
.reach-domains__ic { width: 18px; height: 18px; color: #9aa3b2; flex: none; }

/* bordered card to match the testimonial / process cards it now sits beside */
.reach-markets {
  display: flex; flex-direction: column;
  background: transparent;
  border: 1px solid #e7eaf1;
  box-shadow: 0 1px 2px rgba(28,28,28,.04), 0 10px 28px -22px rgba(28,28,28,.22);   /* shared container lift — same recipe as .exp-list */
  border-radius: var(--radius-lg, 24px);   /* on-system with every other card */
  padding: 6px clamp(24px, 2.4vw, 36px);   /* slim vertical rails — the donut fills the height */
  padding: 6px round(clamp(24px, 2.4vw, 36px), 4px);   /* 32 @1280 */
}
.reach-markets__title { font-family: var(--display); font-weight: 600; font-size: clamp(1.25rem, 1.9vw, 1.5rem); letter-spacing: -.015em; color: var(--ink); margin: 0 0 clamp(10px, 1.4vw, 16px); }
.reach-donut { margin: 0; flex: 1; display: grid; place-items: center; }   /* centre the donut in the card height */
.reach-donut .dn { width: 100%; max-width: clamp(330px, 38vw, 420px); height: auto; display: block; margin-inline: auto; overflow: visible; }   /* fills the same card height with slimmer rails */
.dn__lead   { fill: none; stroke: #c2c8d2; stroke-width: 1; }
.dn__lbl    { fill: var(--ink-2); font: 600 17px var(--sans); }
.dn__pct    { fill: #3a4150; font: 700 17px var(--sans); }      /* solid ink — segment colors washed the numbers out */
.dn__cen    { fill: var(--ink-2); font: 600 17px var(--sans); }   /* sized so "Desktop / Web 60%" clears the donut hole */
.dn__cenpct { fill: #3a4150; font-weight: 600; }

/* entrance motion: arcs draw in (staggered), labels + center fade up after */
.dn path { stroke-dasharray: 100; stroke-dashoffset: 100; transition: stroke-dashoffset .95s cubic-bezier(.42, 0, .2, 1); }
.dn.is-drawn path { stroke-dashoffset: 0; }
.dn .dn__lead, .dn .dn__lbl, .dn .dn__pct, .dn .dn__cen { opacity: 0; transition: opacity .5s ease; }
.dn.is-drawn .dn__lead, .dn.is-drawn .dn__lbl, .dn.is-drawn .dn__pct, .dn.is-drawn .dn__cen { opacity: 1; transition-delay: .55s; }
@media (prefers-reduced-motion: reduce) {
  .dn path { transition: none; stroke-dashoffset: 0; }
  .dn .dn__lead, .dn .dn__lbl, .dn .dn__pct, .dn .dn__cen { transition: none; opacity: 1; }
}

.h-1 { font-family: var(--display); font-size: clamp(1.5rem, 2.4vw, 1.75rem); font-weight: 500; letter-spacing: -0.02em; }  /* 28px */
.h-2 { font-family: var(--display); font-size: clamp(1.375rem, 2vw, 1.5rem); font-weight: 600; letter-spacing: -0.015em; }   /* 24px */
.h-3 { font-family: var(--display); font-size: 1.25rem; font-weight: 600; letter-spacing: -0.01em; }                          /* 20px */
.study__title { font-family: "Averia Serif Libre", var(--display); font-size: clamp(1.375rem, 2vw, 1.5rem); font-weight: 700; letter-spacing: -0.01em; }  /* 24px */
.board__title { font-family: var(--display); font-style: normal; font-size: 1.375rem; font-weight: 600; letter-spacing: -0.01em; }    /* 22px */
.cta-band__title { font-size: clamp(1.5rem, 2.2vw, 1.625rem); }                                       /* 26px */
.prose h2 { font-family: var(--display); font-style: normal; font-size: clamp(1.375rem, 2vw, 1.625rem); font-weight: 600; letter-spacing: -0.015em; }
.hero__lead-col .lead, .lead { font-size: 1rem; }                                                     /* 16px subhead */

/* The name wordmark — Fraunces italic, a signature feel */
.brand__name { font-family: "Averia Serif Libre", var(--display); font-style: normal; font-weight: 700; letter-spacing: 0; white-space: nowrap; }

/* Labels keep their original color, just switch mono → Inter */
.eyebrow { font-family: var(--serif); font-weight: 600; letter-spacing: .12em; }
.status  { font-family: var(--serif); letter-spacing: .10em; }
.serif-accent { font-style: normal; font-weight: 600; }

/* Logo mark before the wordmark in the nav. */
.brand { gap: 9px; }
.brand__mark {
  flex: none; display: block; width: 28px; height: 28px;
  border-radius: 8px; overflow: hidden; line-height: 0;
  box-shadow: 0 2px 6px rgba(13,26,64,.18);
  transition: transform .35s var(--ease, ease);
}
.brand__mark svg { width: 100%; height: 100%; display: block; }
.brand:hover .brand__mark { transform: rotate(-6deg); }   /* gentle tilt only — no scale, so the bar never looks taller */

/* logo mark = profile photo + rotating ring (replaces the "S" wordmark) */
.brand__mark.brand__mark--pfp {
  width: 46px; height: 46px; border-radius: 50%;
  background: transparent; box-shadow: none; overflow: visible; position: relative;
}
.brand__mark--pfp .brand__ring { position: absolute; inset: 0; width: 100%; height: 100%; display: block; overflow: visible; }
.brand__ring-band { fill: none; stroke: #1c1c1c; stroke-width: 20; }
.brand__ring-txt { fill: #fff; font-family: var(--display); font-weight: 700; font-size: 13.5px; letter-spacing: 1.5px; }
.brand__avatar {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: 30px; height: 30px; border-radius: 50%; overflow: hidden;
}
.brand__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.brand:hover .brand__mark--pfp { transform: scale(1.05); }   /* ring already spins — no tilt */

/* Profile cluster: avatar with a tooltip-style status bubble at its top-right. */
.hero__id {
  display: flex; flex-direction: column; align-items: center;
  gap: 14px;                                   /* avatar → status (nets 32 to the pill) */
  margin-top: 16px;                            /* lead offset (card padding-top → avatar) */
  margin-bottom: 16px;                         /* status → heading. The h1 is 42px on a
                                                  48.72px line-height, so ~3px of leading sits
                                                  inside the heading box on top of this. */
}
/* ---- Tooltip component, centered below the avatar, pointing up at it ---- */
.hero__id .status-slot {
  position: relative; width: auto; margin: 0; white-space: nowrap;
}
/* bare availability line — no chip; the dot alone carries the "live" signal */
.hero__id .status-slot .status {
  position: relative;
  background: transparent;
  color: var(--ink, #0c0d12);
  border: none;
  border-radius: 0;
  margin: 0;
  padding: 0;
  font-size: clamp(0.8125rem, 1.25vw, 1rem); font-weight: 600; letter-spacing: normal;   /* 16px cap, reached exactly @1280 */
  box-shadow: none;
}

/* live dot: a steady core with a radar ring pinging outward from under it.
   The ring is a ::after so the core stays crisp while the ring scales. */
.hero__id .status-slot .status__dot {
  position: relative;
  width: 8px; height: 8px;
  background: #22c55e;
  box-shadow: 0 0 0 2px rgba(34, 197, 94, .16);
  animation: status-glow 2.4s ease-in-out infinite;
}
.hero__id .status-slot .status__dot::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: 50%;
  background: #22c55e;
  animation: status-ping 2.4s cubic-bezier(0, 0, .2, 1) infinite;
}
@keyframes status-ping {
  0%        { transform: scale(1);   opacity: .5; }
  70%, 100% { transform: scale(3.4); opacity: 0; }
}
@keyframes status-glow {
  0%, 100% { box-shadow: 0 0 0 2px rgba(34, 197, 94, .16); }
  50%      { box-shadow: 0 0 0 4px rgba(34, 197, 94, .28); }
}
/* must match the selector specificity above, or the guard loses the cascade */
@media (prefers-reduced-motion: reduce) {
  .hero__id .status-slot .status__dot { animation: none; }
  .hero__id .status-slot .status__dot::after { animation: none; display: none; }
}

/* Small circular profile avatar, now sitting beside the chip. */
.hero__avatar {
  display: block; margin: 0; flex: none;
  position: relative;
  width: clamp(96px, 9vw, 112px); aspect-ratio: 1;
  border-radius: 50%; overflow: hidden;
  /* 4px, not 3, and the width is doing accessibility work rather than styling.
     At 1 device pixel per CSS pixel a 3px ring is one fully covered pixel
     between two partially covered ones, so how the antialiaser splits the ink
     depends on the subpixel phase of the curve -- which sweeps continuously
     around a circle. Total ink stays constant (coverage-integrated thickness
     sd 0.07px) but the PEAK darkness does not, and against a page this ring
     only clears by ~30 luminance levels that swing is visible as lumpiness.
     Measured contrast around the circumference, current 3px vs this 4px:
        DPR 1     cv 8.6%  (20.5-50.4)  ->  2.9%  (27.5-31.5)
        DPR 1.25  cv 6.4%  (26.5-44.4)  ->  3.6%
        DPR 1.5   cv 3.2%                   3.2%  unchanged
        DPR 2     cv 3.6%                   3.6%  unchanged
     So it was only ever broken on a 1x display -- an external monitor, not the
     retina laptop it was designed on. 4px gives the ring a solid two-pixel core
     that survives any phase; 5px measured no better, and the colour is
     deliberately unchanged. Re-measure with .claude/ring-trial2.js. */
  border: 4px solid rgba(234, 242, 255, .92);   /* very light blue ring */
  background: radial-gradient(circle at 50% 35%, #2c2f36, #0d0f13);
  /* SYMMETRIC. A +3px vertical offset darkens the page under the bottom arc
     and leaves the top clean, which is a real directional asymmetry around the
     circumference rather than rasterisation noise — measured, it was the only
     thing that varied by angle at all, and it took ring contrast from cv 1.8%
     to 3.2% at DPR 1. The ring ink itself is flat everywhere (sd 0.44,
     cv 0.19%, DPR 1 through 2.5). */
  box-shadow: 0 0 10px -6px rgba(13,26,64,.16);
}
.hero__avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hero__avatar.is-empty::after { content: "S"; position: absolute; inset: 0; display: grid; place-items: center; color: #fff; font-family: var(--display); font-weight: 600; font-size: 1.25rem; }

/* hero avatar wrapper (the old rotating curved-text ring has been removed) */
.hero__pfp {
  position: relative;
  width: 80px; aspect-ratio: 1; margin: 0 auto;
}
.hero__pfp .hero__avatar {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 1;
  width: 100%;
}

/* ---- hero entrance: badge pops, then pill / heading / sub / CTAs / logos rise in (staggered) ---- */
@keyframes heroRise { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
@keyframes heroPop    { from { opacity: 0; transform: translateY(14px) scale(.7); } to { opacity: 1; transform: translateY(0) scale(1); } }
.hero__pfp             { animation: heroPop  .8s cubic-bezier(.34, 1.32, .5, 1) both; }   /* clear scale-up pop */
.hero__id .status-slot { animation: heroRise .6s  cubic-bezier(.22, 1, .36, 1) .18s both; }
.hero__title           { animation: heroRise .7s  cubic-bezier(.22, 1, .36, 1) .30s both; }
.hero__sub             { animation: heroRise .7s  cubic-bezier(.22, 1, .36, 1) .44s both; }
.hero__cta             { animation: heroRise .7s  cubic-bezier(.22, 1, .36, 1) .58s both; }
.trusted               { animation: heroRise .7s  cubic-bezier(.22, 1, .36, 1) .70s both; }
@media (prefers-reduced-motion: reduce) {
  .hero__pfp, .hero__id .status-slot, .hero__title, .hero__sub, .hero__cta, .trusted { animation: none; }
}

@media (max-width: 620px) {
  .brand { gap: 8px; }
}

/* ----------------------------- Buttons --------------------------- */
/* Anything clickable uses the General Sans display face (matches headings) */
.btn, .nav__links a, .study__btn, .link-arrow, .chip, .back-pill,
.fun__title, .footer__col a, .footer__copy + a, .footer a {
  font-family: var(--display);
}

/* Clay shape only (12px radius, compact heights). Colors come from
   the original style.css rules (blue). */
.btn {
  height: 34px; padding: 0 16px;
  border-radius: var(--radius-sm);
  font-size: 0.875rem; font-weight: 500;
  line-height: 1; gap: 6px;
}
.btn .ar { width: 14px; height: 14px; }

.brand__mark { border-radius: var(--radius-sm); font-style: normal; background: #000000; }   /* text-S mark = pure black */

/* Narrow the nav pill to match the projects width (840px) below.
   Three-zone header: brand (left) · nav links (centred) · CTA (right).
   nav__links is flattened with display:contents so its links + CTA place
   directly into this grid; the two 1fr edge columns keep the 4 links
   optically centred on the bar regardless of logo / CTA widths. */
.nav__inner {
  max-width: 760px; margin-inline: auto; width: 100%;
  display: grid;
  grid-template-columns: 1fr auto auto auto auto 1fr;
  align-items: center;
  /* solid dark bar — same firm tone scrolled or not */
  border-radius: var(--pill);   /* fully rounded pill ends */
  background: rgba(28, 28, 28, .94);
  backdrop-filter: blur(8px) saturate(140%);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  border-color: rgba(255, 255, 255, .10);
}
.nav__inner > .brand { justify-self: start; }
.nav__links { display: contents; }
.nav__links a.nav__cta { justify-self: end; }
.nav.is-stuck .nav__inner {
  background: rgba(28, 28, 28, .94);   /* unchanged on scroll — only the shadow firms up */
  border-color: rgba(255, 255, 255, .10);
  box-shadow: 0 8px 28px -14px rgba(0, 0, 0, .55);
}
/* Selection underline: the same feathered thread of light, now parked under the
   active nav link and sliding to the next one when the selection changes.
   Position and width come from --nav-ink-*, measured in main.js. */
.nav__inner { position: relative; }
.nav__inner::after {
  content: ""; position: absolute; left: 0; bottom: 4px;
  width: var(--nav-ink-w, 0px); height: 2px; border-radius: 2px;
  transform: translateX(var(--nav-ink-x, 0px));
  opacity: var(--nav-ink-o, 0);
  /* #d6d6da, the grey the nav hamburger bars already use — same component
     family, and it lands in the right place in the hierarchy: brighter than the
     resting labels (#b7b7bb) so it still reads as the marker, softer than the
     selected label (#fff) it sits under. Pure white made the thread the
     brightest thing in the pill, brighter than the word it was marking.
     The feathered ends carry the same grey at alpha 0, not white, or the tips
     would tint back toward white as they fade. */
  background: linear-gradient(90deg,
    rgba(214, 214, 218, 0),
    #d6d6da 25%,
    #d6d6da 75%,
    rgba(214, 214, 218, 0));
  transition: transform .45s cubic-bezier(.22, 1, .36, 1),
              width     .45s cubic-bezier(.22, 1, .36, 1),
              opacity   .3s ease;
  pointer-events: none;
}
/* first paint places it without a slide in from x=0 */
.nav__inner.nav-ink-init::after { transition: none; }
@media (prefers-reduced-motion: reduce) {
  .nav__inner::after { transition: opacity .3s ease; }
}
/* the inline links collapse into the hamburger panel below 820px */
@media (max-width: 820px) {
  .nav__inner::after { display: none; }
}

/* ---- header motion: drop-in on load + hide-on-scroll-down / reveal-on-scroll-up ---- */
.nav { transition: transform .42s cubic-bezier(.22, 1, .36, 1); will-change: transform; }
.nav.nav--hidden { transform: translateY(calc(-100% - 80px)); }   /* slides the pill up off-screen */

@keyframes navDrop   { from { opacity: 0; transform: translateY(-120%); } to { opacity: 1; transform: translateY(0); } }
@keyframes navItemIn { from { opacity: 0; transform: translateY(-9px); }  to { opacity: 1; transform: translateY(0); } }
/* brand mark: springy bounce-in — pops past full size, squashes, then settles */
@keyframes brandPop {
  0%   { opacity: 0; transform: scale(0) rotate(-10deg); }
  55%  { opacity: 1; transform: scale(1.22) rotate(4deg); }
  75%  { transform: scale(.92) rotate(-1deg); }
  90%  { transform: scale(1.05); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}
.nav__inner { animation: navDrop .65s cubic-bezier(.22, 1, .36, 1) both; }
.nav__inner .brand { animation: navItemIn .5s ease .20s both; }
.nav__inner .brand__mark { animation: brandPop .75s cubic-bezier(.34, 1.56, .5, 1) .30s both; transform-origin: center; }
.nav__links a.nav__cta { animation: navItemIn .5s ease .40s both; }   /* only the CTA fades in; the text links stay static on load */
.nav__toggle { animation: navItemIn .5s ease .30s both; }
@media (prefers-reduced-motion: reduce) {
  .nav, .nav__inner, .nav__inner .brand, .nav__inner .brand__mark, .nav__inner .brand__name, .nav__links a, .nav__toggle { animation: none; transition: none; }
}

/* Resume CTA — outlined "ghost" button, rounded-rect corners (our button radius) */
.nav__links a.nav__cta {
  border-radius: var(--radius-sm);
  height: 38px; padding: 0 20px; gap: 6px;
  display: inline-flex; align-items: center; justify-content: center; white-space: nowrap;
  font-family: var(--sans); font-size: 1.125rem; font-weight: 500;
  background: #ffffff;
  color: #1a1a1c;
  border: 1px solid transparent;
  box-shadow: none;
  position: relative; overflow: hidden; isolation: isolate;   /* for the slide-up fill */
}
/* hover: a dark panel slides up from the bottom to cover it (ref clip) */
.nav__links a.nav__cta::before {
  content: "Let’s chat";
  position: absolute; inset: 0; z-index: 2;
  display: grid; place-items: center;
  background: #1c1c1c; color: #fff;
  border-radius: var(--radius-sm);
  font: inherit; letter-spacing: inherit; white-space: nowrap;
  transform: translateY(101%);
  transition: transform .7s cubic-bezier(.4, 0, .2, 1);
}
.nav__links a.nav__cta:hover::before { transform: translateY(0); }
.nav__links a.nav__cta:hover { background: #ffffff; color: #1a1a1c; box-shadow: 0 4px 14px -6px rgba(0,0,0,.5); }

/* Nav links: two-layer text flip (grey slides up & out, white rises in) */
.nav__links a:not(.nav__cta) {
  display: inline-flex; align-items: center;
  background: transparent; box-shadow: none; color: #b7b7bb;
}
.nav__links a:not(.nav__cta):hover,
.nav__links a:not(.nav__cta).is-active,
.nav__links a:not(.nav__cta).is-current {
  background: transparent; transform: none; box-shadow: none;
}
/* current section: the link's label simply rises to the brightest white */
.nav__links a:not(.nav__cta).is-current .nav__label::before,
.nav__links a:not(.nav__cta).is-active  .nav__label::before { color: #ffffff; }
/* the flip lives on the label so the icon stays put */
.nav__label {
  position: relative; overflow: hidden; display: inline-block;
  color: transparent;   /* real text only sizes the box; the two layers below are visible */
}
.nav__label::before,
.nav__label::after {
  content: attr(data-text);   /* per-link label so every nav item flips its own text */
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font: inherit; letter-spacing: inherit; white-space: nowrap;
  transition: transform .7s cubic-bezier(.4, 0, .2, 1);
}
.nav__label::after  { color: #b7b7bb; transform: translateY(0); }
.nav__label::before { color: #fff;    transform: translateY(120%); }
.nav__links a:not(.nav__cta):hover .nav__label::after,
.nav__links a:not(.nav__cta).is-active .nav__label::after,
.nav__links a:not(.nav__cta).is-current .nav__label::after { transform: translateY(-120%); }
.nav__links a:not(.nav__cta):hover .nav__label::before,
.nav__links a:not(.nav__cta).is-active .nav__label::before,
.nav__links a:not(.nav__cta).is-current .nav__label::before { transform: translateY(0); }
.nav__links a.nav__cta svg { width: 14px; height: 14px; }
/* dark-bar brand + hamburger go light */
.nav__inner .brand, .nav__inner .brand__name { color: #f3f3f4; }
.nav__toggle span { background: #d6d6da; }
/* collapse to a hamburger + dark dropdown before the 4-link inline nav can overflow
   the bar (~820px). Dark panel matches the bar so the light link text stays readable. */
@media (max-width: 820px) {
  /* revert the desktop three-zone grid → simple flex bar with dropdown */
  .nav__inner { display: flex; justify-content: space-between; }
  .nav__toggle { display: block; }
  .nav__links {
    display: flex;   /* restore box from desktop's display:contents so the dropdown renders */
    position: fixed; inset: 88px 16px auto; flex-direction: column; align-items: stretch;
    background: rgba(24, 24, 26, .98);
    backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
    border: 1px solid rgba(255, 255, 255, .12); border-radius: var(--radius);
    padding: 8px; gap: 2px;
    box-shadow: 0 20px 50px -22px rgba(0, 0, 0, .65);
    transform-origin: top; transform: scaleY(.9); opacity: 0;
    pointer-events: none; transition: opacity .25s, transform .25s;
  }
  .nav__links.is-open { opacity: 1; transform: none; pointer-events: auto; }
  .nav__links a:not(.nav__cta) { padding: 12px 16px; border-radius: var(--radius-sm); }
  .nav__links a.nav__cta { margin-top: 6px; justify-content: center; width: 100%; }
}
.study__btn {
  border-radius: var(--radius-sm);
  height: 34px; padding: 0 14px; font-size: 0.875rem;
}
.study__btn:hover { transform: none; }

.chip {
  height: 40px; padding: 0 20px;
  display: inline-flex; align-items: center;
  font-size: 1rem; font-weight: 500; letter-spacing: -0.01em;
  border-radius: 9999px;
}
/* Work page: centre the filter chips and tighten the gap below the header */
.section:has(.sec-head--filters) { padding-top: clamp(24px, 4vw, 56px); }

/* Every page that offers a way back opens at the SAME height. The pill is the
   first thing under the nav, so its top must not depend on what happens to
   follow it — and it did: the section clamp gave 51px on work.html, 115px on
   qualify/learnings/case-study and 26px on the four image-based studies (which
   set their own `.section` rule), so the button jumped by up to 90px between
   pages. This matches work.html's value; the 0,2,0 specificity of :has() is
   what lets it win over the case studies' own single-class overrides. */
.section:has(> .back-pill) { padding-top: clamp(24px, 4vw, 56px); }
/* DEAD as of the filter-chip removal: no page carries .sec-head--filters or
   .filters any more (checked across all 15). Kept rather than deleted because
   re-adding the chips is a markup-only change, and the base .chip rules below
   are still load-bearing for contact.html's .bk-chip. */
.sec-head--filters { max-width: none; text-align: center; }
.sec-head--filters .filters { justify-content: center; }

/* The chips were the ONLY thing between the back pill and the card list — the
   pill has no margin-bottom of its own, so removing them put the first card
   flush against it (gap 82 -> 0 at 1280). This restores the gap that used to
   sit BELOW the chips (48 at 1280, 32 at 375), i.e. the designed distance
   between the head area and the cards; only the chips' own height is gone. */
.section > [data-work-list] { margin-top: clamp(32px, 4vw, 48px); }

/* Filter chips get the nav CTA’s hover: a dark panel slides up from the bottom
   and covers the chip, plus a spring zoom borrowed from .back-pill. The CTA can
   hard-code its label in CSS because there is only one of it — these five carry
   their word in data-label (set in main.js) so ::before can render attr(). */
.filters .chip {
  /* one step down from the base .chip (16px/40px): 14px on the site’s small step,
     34px tall and 16px side padding — the same measurements as .back-pill, which
     now sits directly above this row. */
  height: 34px; padding: 0 16px;
  font-size: 0.875rem;
  position: relative; overflow: hidden; isolation: isolate;
  transform-origin: center;
  transition: transform .4s cubic-bezier(.34, 1.56, .5, 1), box-shadow .35s ease,
              border-color .35s ease, background .25s ease, color .25s ease;
}
.filters .chip::before {
  content: attr(data-label);
  position: absolute; inset: 0; z-index: 2;
  display: grid; place-items: center;
  background: #1c1c1c; color: #fff;
  border-radius: inherit;
  font: inherit; letter-spacing: inherit; white-space: nowrap;
  transform: translateY(101%);
  transition: transform .7s cubic-bezier(.4, 0, .2, 1);
}
.filters .chip:hover::before,
.filters .chip:focus-visible::before { transform: translateY(0); }
.filters .chip:hover,
.filters .chip:focus-visible {
  transform: scale(1.06);
  border-color: rgba(12, 13, 18, .30);
  box-shadow: 0 14px 28px -14px rgba(28, 28, 28, .35);
}
.filters .chip:active { transform: scale(.99); }
/* the selected chip is already dark — no panel to slide, just the zoom */
.filters .chip.is-active::before { content: none; }
@media (prefers-reduced-motion: reduce) {
  .filters .chip, .filters .chip::before { transition: none; }
  .filters .chip:hover { transform: none; }
  .filters .chip:hover::before { transform: translateY(101%); }
}
.hero__tags li { border-radius: 9999px; font-size: 0.875rem; }
/* "Back to work" — outline recipe, at the BASE .btn size (34px / 14px).
   It's a secondary way out of the page, not a primary CTA, so it sits one
   size step below the 44px "Next case study" button at the foot. */
/* Back to work is the same ghost button as the hero résumé CTA, and behaves
   the same on hover: spring zoom, firmer edge, lifted shadow. Its arrow is the
   homepage "View all projects" chip (.btn__chip) — a ringed circle that fills
   ink on hover — mirrored, because this arrow sits to the LEFT of the label
   and points back. */
.back-pill {
  height: 34px; padding: 0 16px 0 11px;
  display: inline-flex; align-items: center; gap: 10px;
  font-size: 0.875rem; font-weight: 500;
  border-radius: var(--radius-sm);
  background: transparent; color: var(--ink, #0c0d12);
  border: 1px solid rgba(12,13,18,.30);
  box-shadow: none;
  transform-origin: center;
  transition: transform .4s cubic-bezier(.34, 1.56, .5, 1), box-shadow .35s ease, border-color .35s ease;
}
/* The chip, taken from the 48px "View all projects" button and brought down to
   this pill's 34px. NOT a straight 34/48 scale: that gives a 24px circle at a
   5px inset, which is proportionally exact (5/24 = .208 against the reference's
   7/34 = .206) and still reads as crowding the border, because a 5px gap is
   simply small in absolute terms however correct the ratio. 22px at a 6px inset
   (.273) is the value that was chosen by LOOKING at it magnified — see
   .claude/backchip-trial.js, which renders the candidates at 3x.
   Transparent rather than #fff — identical on the white ground these pages
   have, and it cannot show a white disc if one ever gains a tint. The ring is
   what makes a circle visible on its own colour, exactly as on the original. */
.back-pill__chip {
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: transparent; color: var(--ink, #0c0d12);
  box-shadow: inset 0 0 0 1px rgba(12, 13, 18, .18);
  transition: background .3s ease, color .3s ease, box-shadow .3s ease,
              transform .35s cubic-bezier(.34, 1.56, .5, 1);
}
.back-pill .ar { width: 10px; height: 10px; display: block; }
.back-pill:hover,
.back-pill:focus-visible {
  transform: scale(1.06);
  color: var(--ink, #0c0d12);
  border-color: rgba(12,13,18,.48);
  box-shadow: 0 14px 28px -14px rgba(28, 28, 28, .35);
}
/* hover: the same flip the homepage chip does, flying LEFT because it goes back */
.back-pill:hover .back-pill__chip,
.back-pill:focus-visible .back-pill__chip {
  background: var(--ink, #0c0d12); color: #fff;
  box-shadow: inset 0 0 0 1px transparent;
  transform: translateX(-3px);
}
.back-pill:active { transform: scale(.99); }
@media (prefers-reduced-motion: reduce) {
  .back-pill, .back-pill__chip { transition: none; }
  .back-pill:hover { transform: none; }
  .back-pill:hover .back-pill__chip { transform: none; }
}

/* "Next case study" — homepage primary-button recipe ("View all projects"),
   scoped to the case-study footers so card "Explore" links stay text links */
.mt-l .link-arrow, .q-next .link-arrow {
  height: 44px; padding: 0 24px; gap: 0;
  display: inline-flex; align-items: center;
  font-size: 1.125rem; font-weight: 600; color: #fff;
  border-radius: var(--radius-sm);
  background: linear-gradient(135deg, #3a3a3a 0%, #1c1c1c 60%, #0e0e0e 100%);
}
.mt-l .link-arrow:hover, .q-next .link-arrow:hover { background: linear-gradient(135deg, #454545 0%, #232323 60%, #121212 100%); }
/* arrow reveals + slides in on hover, exactly like "View all projects" */
.mt-l .link-arrow .ar, .q-next .link-arrow .ar {
  width: 0; opacity: 0; margin-left: 0; transform: translateX(-6px); overflow: hidden;
  transition: width .35s var(--ease), opacity .25s ease, margin-left .35s var(--ease), transform .35s var(--ease);
}
.mt-l .link-arrow:hover .ar, .q-next .link-arrow:hover .ar { width: 15px; opacity: 1; margin-left: 7px; transform: translateX(0); }
.field input, .field textarea { border-radius: var(--radius-sm); }

/* Align neighbouring nav + link typography with the compact buttons */
.nav__links a:not(.nav__cta) { font-family: var(--sans); font-size: 1rem; font-weight: 500; padding: 8px 12px; gap: 6px; }   /* 16px, tighter so 4 links + Resume fit the bar */
.link-arrow { font-size: 0.875rem; }
/* hero CTAs — 18px text to match the header nav (Work / Resume), matching height */
.hero__cta .btn { height: 44px; font-size: 1.125rem; }

/* "Let's chat" hover: no colour change — it pops (springy scale + lift + glow) and reveals an arrow */
.cta-chat { gap: 0; background: linear-gradient(135deg, #3a3a3a 0%, #1c1c1c 60%, #0e0e0e 100%); transform-origin: center; transition: transform .4s cubic-bezier(.34, 1.56, .5, 1), box-shadow .35s ease; }   /* heading-grey gradient */
.cta-chat__ar {
  width: 0; opacity: 0; margin-left: 0;
  transform: translateX(-6px);
  transition: width .35s var(--ease, ease), opacity .25s, margin-left .35s var(--ease, ease), transform .35s var(--ease, ease);
}
.cta-chat:hover,
.cta-chat:focus-visible {
  background: linear-gradient(135deg, #454545 0%, #232323 60%, #121212 100%);   /* gradient lifts slightly on hover */
  transform: scale(1.06);   /* balanced zoom from the centre — no vertical bias */
  box-shadow: 0 14px 28px -12px rgba(28, 28, 28, .7);
}
.cta-chat:hover .cta-chat__ar,
.cta-chat:focus-visible .cta-chat__ar { width: 17px; opacity: 1; margin-left: 7px; transform: translateX(0); }
.cta-chat:active { transform: scale(.99); }
@media (prefers-reduced-motion: reduce) {
  .cta-chat, .cta-chat__ar { transition: none; }
  .cta-chat:hover { transform: none; }
}

.btn--outline { background: transparent; color: var(--ink); border: 1px solid rgba(12,13,18,.30); }   /* firmer than --line-2: the ghost CTA needs a readable edge */
.btn--outline:hover { background: transparent; color: var(--ink); border-color: rgba(12,13,18,.48); box-shadow: var(--shadow-sm); }

/* hero "Copy email" button — borderless; copies the address, swaps to a check + "Copied" */
.hero__copy { font-family: inherit; cursor: pointer; border: none; }
.hero__copy .ico-copy { transform-origin: center; transition: transform .7s cubic-bezier(.4, 0, .2, 1), color .3s var(--ease); }
.hero__copy:hover { background: transparent; box-shadow: none; transform: translateY(-1px); }

/* "Copy email" label: two-layer text flip like Work — ink slides up & out, blue rises in */
.hero__copy .lbl-copy {
  position: relative; overflow: hidden;
  display: inline-block; color: transparent;
  padding: 2px 0;   /* breathing room so each layer fully clears the box on flip */
}
.hero__copy .lbl-copy::before,
.hero__copy .lbl-copy::after {
  content: "Copy email";
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font: inherit; letter-spacing: inherit; white-space: nowrap;
  transition: transform .7s cubic-bezier(.4, 0, .2, 1);
}
.hero__copy .lbl-copy::after  { color: var(--ink, #0c0d12);   transform: translateY(0); }
.hero__copy .lbl-copy::before { color: #000; transform: translateY(120%); }
.hero__copy:hover .lbl-copy::after  { transform: translateY(-120%); }
.hero__copy:hover .lbl-copy::before { transform: translateY(0); }
.hero__copy:hover .ico-copy { transform: rotateX(-360deg) scale(1.06); color: #000; }   /* icon flips opposite to the label rise + turns pure black */
.hero__copy .ico-check, .hero__copy .lbl-done { display: none; }
.hero__copy .ico-check { color: #0f9d57; }
.hero__copy.is-copied { color: #0f9d57; border-color: #bfe6cf; }
.hero__copy.is-copied .ico-copy, .hero__copy.is-copied .lbl-copy { display: none; }
.hero__copy.is-copied .ico-check, .hero__copy.is-copied .lbl-done { display: inline; }
.hero__cta .link-arrow {
  height: 40px; padding: 0 18px;
  display: inline-flex; align-items: center; gap: 6px;
  border: 1px solid var(--brand, #1c1c1c);
  border-radius: var(--radius-sm, 12px);
  background: transparent; color: var(--brand, #1c1c1c);
  font-size: 1rem; font-weight: 600;
  transition: background .25s ease, color .25s ease, border-color .25s ease;
}
.hero__cta .link-arrow:hover { background: var(--brand-soft, rgba(23,24,28,.10)); }
.hero__cta .link-arrow .ar { width: 15px; height: 15px; }

/* ---- Typography floor = 14px: raise every 12px micro-label ---- */
.eyebrow, .status, .meta__k, .study__meta-k, .gcard__tag,
.work-item__cap .k, .contact-list .k, .cs-hero-media,
.hero__photo.is-empty::after, .portrait {
  font-size: 0.875rem;   /* 12px → 14px */
}

/* availability status — floating white pill */
.hero__lead-col .status { margin-top: clamp(24px, 3vw, 32px); margin-bottom: 0; }
.status, .hero__lead-col .status {
  font-family: var(--sans);
  font-size: clamp(0.875rem, 1.667vw, 1.5rem); font-weight: 600;   /* 24px at 1440 */
  text-transform: none; letter-spacing: -0.005em;
  gap: .5em;
  padding: 6px 14px 6px 11px;
  background: #fff;
  border: 1px solid rgba(13,26,64,.05);
  border-radius: var(--pill, 999px);
  box-shadow: 0 6px 20px -6px rgba(13,26,64,.16), 0 1px 2px rgba(13,26,64,.06);
  color: var(--ink, #0d1a40);
}
.status__label { white-space: nowrap; color: #0c0d12; }   /* matches the "View case study" ink */
/* larger dot with a soft halo ring — gentle breathing pulse */
.status .status__dot {
  width: 10px; height: 10px;
  background: #3fbf6b;
  box-shadow: 0 0 0 3px rgba(63,191,107,.18);
  animation: status-breathe 2.4s ease-in-out infinite;
}
@keyframes status-breathe {
  0%, 100% { transform: scale(1);    opacity: 1;   box-shadow: 0 0 0 4px rgba(63,191,107,.18); }
  50%      { transform: scale(.78);  opacity: .65; box-shadow: 0 0 0 6px rgba(63,191,107,.08); }
}
@media (prefers-reduced-motion: reduce) {
  .status .status__dot { animation: none; }
}
/* rotating last word — fades/swaps every 2s. Reserve the widest word's width
   (left-aligned) so the line length stays fixed and "Open to" never shifts. */
.status__txt {
  display: inline-block; text-align: left;
  transition: opacity .28s ease, transform .28s ease;
}
.status__txt.is-swapping { opacity: 0; transform: translateY(-3px); }

/* Plain meta labels — strip mono / uppercase / letter-spacing */
.study__meta-k {
  font-family: var(--sans);
  text-transform: none;
  letter-spacing: normal;
}

/* ---- Dario-style CTA: light card + big blue button w/ circular arrow ---- */
.cta-band {
  background: var(--bg-soft);
  color: var(--ink);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
}
.cta-band::before { display: none; }
.cta-band__title { color: var(--ink); }
.cta-band p { color: var(--muted); }

/* Dario-style tactile button — blue gradient + white frame + inset
   highlight + layered shadow + grain texture overlay */
.cta-band .btn--light {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  height: auto;
  color: #fff;
  padding: 14px 16px 14px 28px;
  font-size: 1.0625rem;                 /* 17px */
  font-weight: 600;
  gap: 16px;
  border: 4px solid #fff;               /* white outer frame */
  border-radius: 16px;
  background: linear-gradient(180deg, #4a86f5 0%, #1c1c1c 70%);
  box-shadow:
    inset 0 2px 0 0 rgba(255,255,255,.28),          /* glossy top edge */
    inset 0 0 0 1px rgba(13,26,64,.30),             /* inner border */
    0 0.5px 0.5px -0.7px rgba(13,26,64,.22),        /* layered soft shadow */
    0 1.8px 1.8px -1.3px rgba(13,26,64,.22),
    0 8px 12px -2px rgba(13,26,64,.30);
  transition: box-shadow .3s, transform .3s var(--ease, ease), filter .25s;
}
/* grain/noise texture overlaid on the gradient (inline SVG, no asset) */
.cta-band .btn--light::after {
  content: "";
  position: absolute; inset: 0;
  border-radius: inherit;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 150px 150px;
  opacity: .16;
  mix-blend-mode: overlay;
  pointer-events: none;
  z-index: -1;                 /* above the gradient, below the text */
}
.cta-band .btn--light:hover {
  transform: translateY(-2px);
  filter: brightness(1.04);
  box-shadow:
    inset 0 2px 0 0 rgba(255,255,255,.30),
    inset 0 0 0 1px rgba(13,26,64,.30),
    0 1px 1px -0.7px rgba(13,26,64,.22),
    0 3px 3px -1.3px rgba(13,26,64,.22),
    0 14px 22px -4px rgba(13,26,64,.36);
}
/* arrow in a white circle (above the texture), slides on hover */
.cta-band .btn--light .ar {
  position: relative; z-index: 1;
  width: 32px; height: 32px;
  padding: 8px;
  box-sizing: border-box;
  background: #fff;
  color: var(--brand);
  border-radius: 50%;
  transition: transform .3s var(--ease, ease);
}
.cta-band .btn--light:hover .ar { transform: translateX(4px); }

/* ============================================================
   "THINGS I DO FOR FUN" — fanned card arc
   Cards start stacked/flat, then fan out into the arc when the
   section scrolls into view (.reveal -> .is-in). Hover straightens
   and lifts the hovered card. Swap each card's `background` for a
   photo: background:url(assets/img/fun/xx.jpg) center/cover.
   ============================================================ */
/* sit tight under the hero (pull up over the section gap) */
#fun.section--tight { margin-top: 0; padding-top: clamp(48px, 6vw, 88px); }
/* The fan is pulled up under the hero; its (transparent) container would
   otherwise sit over the hero CTA buttons and swallow their clicks. Lift the
   hero (and its CTA) above the fun section so the buttons stay clickable. */
.hero { position: relative; z-index: 2; padding-top: clamp(18px, 2.4vw, 28px); }

/* push the hero buttons down a touch, closer to the companies row */
.hero__lead-col .hero__cta { margin-top: 20px; }   /* sub → buttons (20) */
.trusted { margin-top: 16px; }

/* ---- Hero feature row: three icon + title + description items ---- */
/* professional, descriptive subheading — two lines */
.hero__sub {
  margin: 12px auto 0;                     /* H1 → sub (12) */
  max-width: 84ch;                                  /* wider so the first line carries more */
  font-family: var(--sans);
  font-size: clamp(0.75rem, 1.6vw, 1.125rem);   /* 18px cap (reached @1125) */
  font-weight: 500; line-height: 1.55; letter-spacing: -0.005em;
  color: var(--ink, #0c0d12); text-align: center; text-wrap: normal;   /* matched to the project-card points */
}
.hero__id { position: relative; }
.hero__facts {
  display: flex; justify-content: center;
  gap: clamp(14px, 2vw, 30px);
  margin: clamp(20px, 2.6vw, 30px) auto 0;
  width: min(880px, 100%); max-width: 880px; flex-wrap: wrap;
  text-align: left;
}
.fact {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 8px; flex: 1 1 240px; max-width: 290px;
}
/* descriptions stay on one line on desktop */
.fact__d { white-space: nowrap; }
.fact__ic {
  flex: none; width: 34px; height: 34px; border-radius: var(--radius-sm);
  display: grid; place-items: center;
  background: #fff; color: var(--brand, #1c1c1c);
}
.fact__ic svg { width: 18px; height: 18px; }
.fact__t {
  font-family: var(--sans); font-size: 1.25rem; font-weight: 500;
  color: var(--ink, #0c0d12); margin: 0 0 3px; letter-spacing: -0.01em;
}
.fact__d {
  font-family: var(--sans); font-size: 1.125rem; font-weight: 400;
  color: #1a1d26; margin: 0; line-height: 1.42;
}
@media (max-width: 760px) {
  /* tablet: keep the 3 facts wrapping tidily, no vertical stretch */
  .hero__facts { gap: 16px 18px; max-width: 480px; }
  .fact { flex: 1 1 180px; }
  .fact__d { white-space: normal; }   /* allow wrap when columns get narrow */
}
@media (max-width: 620px) {
  .hero__facts { flex-direction: column; align-items: center; max-width: 300px; gap: 18px; }
  .fact { flex: 0 0 auto; max-width: none; width: 100%; }
  .hero__title { font-size: clamp(1.75rem, 7.4vw, 2.375rem); }   /* 28px floor — hero stays the apex on small screens */
  .hero__id { margin-top: 4px; margin-bottom: clamp(12px, 3vw, 16px); }
  .hero__id { margin-bottom: round(clamp(12px, 3vw, 16px), 4px); }   /* 12 @390 */
  .hero__cta { flex-wrap: wrap; justify-content: center; }
}
/* phones (≤430px): compact hero type — heading 28px, sub 14px */
@media (max-width: 430px) {
  .hero-box { padding-left: 14px; padding-right: 14px; }
  .hero__title { font-size: 1.75rem; line-height: 1.2; }
  .hero__sub { font-size: 0.875rem; line-height: 1.5; }
  .status__label, .status__txt { font-size: 0.875rem; }   /* Open to … : 14px */
  .cta-chat__lbl, .hero__cta .hero__resume { font-size: 1rem; }   /* CTAs: 16px */
  .trusted .trusted__cap { font-size: 0.75rem; }                  /* Previously worked with: 12px — the caption step is the floor */
}

/* ---- "Trusted by" social-proof row: overlapping logo cluster + caption ---- */
/* ---- "Previously worked with" — continuously scrolling logo marquee ---- */
.trusted {
  margin-top: 24px;   /* buttons → divider (centred in the 48 gap) */
  display: flex; flex-direction: column; align-items: center; gap: 16px; width: 100%;   /* caption → logos */
}
/* hairline divider — centred between the buttons and the caption */
.trusted::before {
  content: ""; display: block;
  width: min(100%, 620px); height: 1px;
  background: var(--rule-fade);
  margin-bottom: 7px;   /* divider → caption = 7 + 16 gap + 1px ≈ 24, matching the 24 above */
}
.trusted__cap {
  font-family: var(--sans); font-size: 0.75rem; font-weight: 600;   /* 12px — locked to the caption step */
  letter-spacing: .05em; text-transform: uppercase;
  color: var(--faint, #5a6273); margin: 0;
}
.marquee {
  width: 100%; max-width: 580px; overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
.marquee__track {
  display: flex; align-items: center; width: max-content;
  padding: 6px 0; margin: 0; list-style: none;
  will-change: transform; backface-visibility: hidden;
}
/* animation starts only once JS has measured the real set width (main.js sets
   --marquee-w / --marquee-dur). Driving it in px instead of -50% keeps the
   speed constant — a % translate re-resolves as the images load and made the
   scroll surge, stutter, and appear to restart. */
.marquee__track.is-ready { animation: marqueeScroll var(--marquee-dur, 26s) linear infinite; }
.marquee:hover .marquee__track { animation-play-state: paused; }
/* flat black-and-white wordmark logos (dummy companies) */
.marquee__logo {
  flex: none; display: inline-flex; align-items: center; gap: 8px;
  margin-right: clamp(36px, 6vw, 64px);   /* spacing lives inside each item so translateX(-50%) loops seamlessly (flex gap left the loop half-a-gap short → visible jump) */
  color: #aab0bb;                       /* muted grey at rest */
  transition: color .3s var(--ease, ease);
}
.marquee__logo:hover { color: #3a4150; }
.logo-mark { width: 30px; height: 30px; fill: currentColor; flex: none; display: block; }
.logo-wm {
  font-family: var(--sans); font-weight: 600; font-size: 1.3125rem;
  letter-spacing: -0.005em; color: rgba(12, 13, 18, 0.60); white-space: nowrap;
  transition: color .3s var(--ease, ease);
}
.marquee__logo:hover .logo-wm { color: rgba(12, 13, 18, 0.8); }
/* real company logos, rendered as uniform dark-grey silhouettes (white/colored originals normalize).
   Heights are tuned per logo so the wordmark cap-heights read the same optical size. */
.logo-img {
  height: 24px; width: auto; display: block; flex: none;
  filter: brightness(0); opacity: .80;
  transition: opacity .3s var(--ease, ease);
}
.logo-img--betacodes { height: 15px; }  /* mixed-case wordmark, cropped tight to its ink -- so the declared height IS the cap height, unlike the padded canvases above */
.logo-img--veroke    { height: 18px; }  /* light rounded wordmark — a touch larger */
.logo-img--sparkleo  { height: 22px; }  /* lowercase wordmark with star */
.logo-img--tradin      { height: 17px; }  /* bold wordmark */
.logo-img--fundingpips { height: 17px; }  /* long wordmark — kept under the others, but was reading too small */
.logo-img--piqla       { height: 19px; opacity: .94; }  /* wordmark (badge cropped — it flattened under brightness(0)); thin strokes read fainter than the others at the shared .80, so it carries more ink */
.logo-img--qualiphy    { height: 19px; }  /* wordmark */
.marquee__logo:hover .logo-img { opacity: .92; }
.marquee__logo:hover .logo-img--piqla { opacity: 1; }  /* already above the base .92, so it needs its own hover step */
@keyframes marqueeScroll {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(calc(-1 * var(--marquee-w, 50%)), 0, 0); }
}
@media (prefers-reduced-motion: reduce) { .marquee__track.is-ready { animation: none; } }

/* ---- Hero "box": bordered container, no background ---- */
.hero-box {
  position: relative;
  border: 1px solid #e7eaf1;
  border-radius: 24px;   /* matched to the project cards */
  background: transparent;
  box-shadow: 0 1px 2px rgba(13,26,64,.04), 0 18px 50px -28px rgba(13,26,64,.22);
  padding: clamp(16px, 3vw, 24px) clamp(24px, 5vw, 72px) clamp(24px, 3vw, 32px);   /* top 24 · bottom 32 */
  overflow: hidden;
}
.hero-box > * { position: relative; z-index: 1; }

@media (max-width: 620px) {
  .hero-box { padding: clamp(36px, 10vw, 56px) clamp(20px, 6vw, 28px); border-radius: 22px; }
  .hero-box { padding: round(clamp(36px, 10vw, 56px), 4px) round(clamp(20px, 6vw, 28px), 4px); }   /* 40 · 24 @390 */
}
.fun { text-align: center; }
/* ======================================================================
   HOW I WORK (auto-advancing process) + TESTIMONIAL carousel
   modelled on murynmukha.com — rose process wheel + blue testimonial
   ====================================================================== */
.approach-grid {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(10px, 1vw, 12px); align-items: stretch;   /* lands 12 @1280 (even, on-grid) */
}
/* entrance: chart card rises first, review card follows (hero-style stagger) */
.approach-grid.reveal { opacity: 1; transform: none; transition: none; }
.approach-grid.reveal .reach-markets,
.approach-grid.reveal .tst {
  opacity: 0; transform: translateY(22px);
  transition: opacity .85s cubic-bezier(.22, 1, .36, 1), transform .85s cubic-bezier(.22, 1, .36, 1);
}
.approach-grid.reveal.is-in .reach-markets { opacity: 1; transform: none; transition-delay: .06s; }
.approach-grid.reveal.is-in .tst           { opacity: 1; transform: none; transition-delay: .30s; }
/* inner cascade: review header rises, then the quotes */
.approach-grid.reveal .tst__head,
.approach-grid.reveal .tst__body {
  opacity: 0; transform: translateY(10px);
  transition: opacity .7s ease, transform .7s cubic-bezier(.22, 1, .36, 1);
}
.approach-grid.reveal.is-in .tst__head { opacity: 1; transform: none; transition-delay: .56s; }
.approach-grid.reveal.is-in .tst__body { opacity: 1; transform: none; transition-delay: .76s; }
@media (prefers-reduced-motion: reduce) {
  .approach-grid.reveal .reach-markets, .approach-grid.reveal .tst,
  .approach-grid.reveal .tst__head, .approach-grid.reveal .tst__body { transition: none; opacity: 1; transform: none; }
}
@media (max-width: 880px) { .approach-grid { grid-template-columns: 1fr; } }

/* ---- LEFT: process ---- */
.proc {
  position: relative; overflow: hidden;
  display: flex; align-items: center; gap: clamp(8px, 1.5vw, 22px);
  background: transparent;                        /* panel colour removed */
  border: 1px solid #e7eaf1;                      /* bordered box like the hero */
  box-shadow: 0 1px 2px rgba(13,26,64,.04), 0 18px 50px -28px rgba(13,26,64,.22);
  border-radius: clamp(28px, 3vw, 44px);
  padding: clamp(20px, 2.2vw, 28px) clamp(22px, 2.4vw, 34px);
  min-height: 0;
}
/* the wheel viewport — titles ride a rotating ring inside it */
.proc__list {
  list-style: none; margin: 0; padding: 0; flex: 0 0 auto; align-self: center;
  position: relative;
  --wheel-r: 130px;                              /* ring radius → keeps ~30px between titles at the full-ring angle */
  width: clamp(196px, 20vw, 244px);
  height: clamp(196px, 21vw, 232px);             /* shorter than the fan → outer titles clip at the corners */
}
/* the active-step arrow is FIXED at the column's left-centre (it never rotates) —
   the active title just rotates to horizontal beside it */
.proc__list::before {
  content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; background: #211b1c; z-index: 3; pointer-events: none;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") center/contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E") center/contain no-repeat;
}
/* the arrow kicks toward the title each time the active step changes (click or auto-advance) */
.proc__list.is-tick::before { animation: procArrowKick .6s ease-out; }
@keyframes procArrowKick {
  0%   { transform: translateY(-50%) translateX(0); }
  38%  { transform: translateY(-50%) translateX(8px); }
  100% { transform: translateY(-50%) translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .proc__list.is-tick::before { animation: none; }
}
/* Each title is a spoke on the ring: pivot at the column's LEFT-centre, pushed out to the
   right by the radius, rotated by --ang, and CENTRED on its spoke end (translate -50%,-50%)
   so the fan opens to the right and the titles lean the opposite way. */
.proc__item {
  position: absolute; left: 0; top: 50%; margin: 0;
  transform-origin: 0 0;
  transform: rotate(var(--ang, 0deg)) translateX(var(--wheel-r)) translate(-50%, -50%);
  white-space: nowrap; letter-spacing: -0.01em;
  font-family: var(--display); font-weight: 400; font-size: 1.25rem;   /* 20px Regular */
  color: #6b6666; opacity: .4;
  transition: transform .75s var(--ease, cubic-bezier(.34,.12,.2,1)),
              filter .55s ease, opacity .55s ease, color .5s ease;
  will-change: transform, filter, opacity;
}
.proc__item.is-active {
  color: #211b1c; opacity: 1; filter: none; font-weight: 600; z-index: 2;
}
.proc__label { white-space: nowrap; display: inline-block; }   /* inline-block so the push transform applies */
/* active title: the whole selected word is blue */
.proc__item.is-active .proc__label {
  color: #2f6ad0;
  -webkit-text-fill-color: #2f6ad0;
  background: none;
}
/* when a new title lands, the arrow's kick "shoves" it right then it settles back
   (peaks just after the arrow, so it reads as the arrow pushing the title) */
.proc__list.is-tick .proc__item.is-active .proc__label { animation: procTitlePush .6s ease-out; }
@keyframes procTitlePush {
  0%   { transform: translateX(0); }
  46%  { transform: translateX(5px); }
  100% { transform: translateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  .proc__list.is-tick .proc__item.is-active .proc__label { animation: none; }
}

/* light-blue description card — centred, with symmetric "echo" layers peeking above + below */
.proc__card {
  position: relative; flex: 1 1 auto; align-self: center; max-width: 280px;
  background: #e8f1ff; border-radius: 28px;
  padding: clamp(24px, 2.6vw, 34px) clamp(20px, 2vw, 26px);
  font-family: var(--sans); font-size: 1.125rem; font-weight: 500; line-height: 1.5; color: var(--ink, #0c0d12);
}
/* line icon above the description — swaps in sync with the words */
.proc__icon {
  display: block; width: 30px; height: 30px; margin-bottom: 16px; color: var(--brand);
  transition: opacity .22s ease, transform .4s cubic-bezier(.22,.61,.36,1), filter .22s ease;
}
.proc__icon svg { width: 100%; height: 100%; display: block; }
.proc__icon.is-swap  { opacity: 0; transform: translateY(-10px); filter: blur(2.5px); }
.proc__icon.is-enter { opacity: 0; transform: translateY(12px);  filter: blur(2.5px); transition: none; }
/* only the words animate on swap — they rise up + soft-blur out, the new line rises in from below */
.proc__d {
  display: block;
  transition: opacity .22s ease, transform .4s cubic-bezier(.22,.61,.36,1), filter .22s ease;
}
.proc__d.is-swap  { opacity: 0; transform: translateY(-10px); filter: blur(2.5px); }
.proc__d.is-enter { opacity: 0; transform: translateY(12px);  filter: blur(2.5px); transition: none; }
@media (prefers-reduced-motion: reduce) {
  .proc__d, .proc__d.is-swap, .proc__d.is-enter,
  .proc__icon, .proc__icon.is-swap, .proc__icon.is-enter { transition: none; filter: none; transform: none; }
}
/* the stacked echoes: narrower + taller than the card, centred, same hue fading back */
.proc__card::before, .proc__card::after {
  content: ""; position: absolute; left: 0; right: 0; border-radius: 22px;
  background: #dbe8ff;
}
.proc__card::before { left: 26px; right: 26px; top: -15px; bottom: -15px; z-index: -1; opacity: .5; }
.proc__card::after  { left: 48px; right: 48px; top: -29px; bottom: -29px; z-index: -2; opacity: .28; }

/* ---- RIGHT: testimonial ---- */
.tst {
  display: flex; flex-direction: column; gap: clamp(14px, 1.5vw, 18px);
  background: transparent; border-radius: var(--radius-lg, 24px);   /* on-system with every other card */
  border: 1px solid #e7eaf1;                                        /* bordered box like the hero */
  box-shadow: 0 1px 2px rgba(28,28,28,.04), 0 10px 28px -22px rgba(28,28,28,.22);   /* shared container lift — same recipe as .exp-list */
  padding: clamp(16px, 1.7vw, 24px) clamp(20px, 2.2vw, 28px);
  padding: round(clamp(16px, 1.7vw, 24px), 4px) round(clamp(20px, 2.2vw, 28px), 4px);   /* 20 · 28 @1280 */
  justify-content: center;
}
.tst__head { display: flex; align-items: center; gap: 12px; }
.tst__avatar {
  flex: none; width: 52px; height: 52px; border-radius: 50%;
  display: grid; place-items: center;
  /* NOT overflow:hidden — the dwell ring below is drawn 6px OUTSIDE this box and
     would be clipped away. The <img> carries its own border-radius instead,
     which crops it to the same circle. */
  position: relative;
  background: radial-gradient(circle at 50% 35%, #3a3a3a, #1c1c1c);
  color: #fff; font-family: var(--sans); font-weight: 700; font-size: 1.0625rem;
  box-shadow: 0 2px 8px -3px rgba(13,26,64,.3);
}
.tst__avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }

/* dwell progress — the same masked conic ring the active process node carries,
   driven by the same --ring 0→1. Here it counts out the 6s each quote holds
   for, so the reader can see the carousel is about to move rather than being
   surprised by it. */
.tst__avatar::before {
  content: ""; position: absolute; inset: -6px; border-radius: 50%;
  background: conic-gradient(#1c1c1c calc(var(--ring, 0) * 1turn), rgba(12, 13, 18, .10) 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring-w)), #000 calc(100% - var(--ring-w) + 0.5px));
          mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring-w)), #000 calc(100% - var(--ring-w) + 0.5px));
}
/* no autoplay under reduced motion, so nothing would ever drive it */
@media (prefers-reduced-motion: reduce) {
  .tst__avatar::before { display: none; }
}
.tst__who { display: flex; flex-direction: column; line-height: 1.3; }
.tst__name { font-family: var(--sans); font-weight: 600; font-size: 1.125rem; color: var(--ink, #0c0d12); }   /* was #1c1517 — a stray reddish near-black */
.tst__role { font-family: var(--sans); font-weight: 500; font-size: 1rem; color: var(--faint, #5a6273); }
.tst__nav {
  margin-left: auto; display: flex; align-items: center; gap: 8px;
  padding: 4px 6px 4px 18px; background: #f6f7f9; border-radius: var(--radius-sm);   /* chip grey */
  overflow: visible;
}
.tst__btn {
  width: 44px; height: 44px; border-radius: var(--radius-sm); border: none; cursor: pointer;
  display: grid; place-items: center;
  transition: background .25s ease, color .25s ease, transform .25s ease;
}
.tst__btn[data-tst-prev] { background: transparent; box-shadow: none; color: #1c1c1c; }
.tst__btn[data-tst-next] {
  width: 44px; height: 44px; border-radius: var(--radius-sm); background: #fff; color: #1c1517;   /* matches the 44px button system */
  box-shadow: 0 2px 8px -2px rgba(13,26,64,.22);
}
.tst__btn:hover { background: var(--brand, #1c1c1c); color: #fff; transform: translateY(-1px); }
.tst__body { margin: 0; transition: opacity .4s ease, transform .4s ease; }
.tst__body.is-swap { opacity: 0; transform: translateY(8px); }
.tst__body p {
  font-family: var(--sans); font-size: 1.125rem; font-weight: 500; line-height: 1.5;   /* 18px */
  color: var(--ink, #0c0d12); margin: 0 0 10px;
}
.tst__body p:last-child { margin-bottom: 0; }

/* tablet (single grid column, so the process panel is full-width): keep the wheel
   and description side-by-side like desktop and let the card fill the wide row
   instead of being capped to its narrow desktop chip width */
@media (max-width: 880px) and (min-width: 621px) {
  .proc__card { max-width: none; }
}

/* phone: a symmetric STACK of 5 titles. The active title sits flat at the
   bottom-centre with a down-arrow; its two coming/going pairs climb the left and
   right sides, each pair LEVEL with each other (same height). Each side stays in a
   near-vertical column (by --sgn, not off × step) so the outer pair can't fly off
   the card — with a slight outward fan toward the corners. Description card below. */
@media (max-width: 620px) {
  .proc { min-height: 0; flex-direction: column; align-items: stretch; gap: clamp(18px, 4vw, 26px); }
  .proc__list {
    --step-x: clamp(68px, 18.5vw, 82px);  /* how far each side sits from centre — wide enough that a levelled pair clears in the middle */
    --fan: 3px;                            /* slight extra outward shift for the 2nd (outer) pair, toward the corners */
    --step-y: 30px;                        /* vertical gap between the stacked rows */
    --active-y: 88px;                      /* the active title's distance from the top of the wheel area */
    align-self: stretch; width: 100%;
    height: 132px;
    margin: -4px 0 0;
  }
  /* side = --sgn (−1 / 0 / +1); row height = --dist. A pair shares its dist, so it sits
     level; the active (sgn 0, dist 0) is centred at the bottom. */
  .proc__item {
    left: 50%; top: 0; transform-origin: 0 0;
    transform:
      translate( calc(var(--sgn, 0) * (var(--step-x) + (var(--dist, 0) - 1) * var(--fan))),
                 calc(var(--active-y) - var(--dist, 0) * var(--step-y)) )
      rotate( calc(var(--off, 0) * 2.5deg) )
      translate(-50%, -50%);
    font-size: clamp(1.0625rem, 4.4vw, 1.25rem);
  }
  /* the indicator moves to the bottom-centre, under the active title, pointing DOWN */
  .proc__list::before {
    left: 50%; top: auto; bottom: 8px; transform: translateX(-50%);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14M6 13l6 6 6-6'/%3E%3C/svg%3E") center/contain no-repeat;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 5v14M6 13l6 6 6-6'/%3E%3C/svg%3E") center/contain no-repeat;
  }
  .proc__list.is-tick::before { animation: procArrowKickDown .6s ease-out; }
  .proc__list.is-tick .proc__item.is-active .proc__label { animation: procTitlePushDown .6s ease-out; }
  .proc__card { max-width: none; align-self: stretch; }
}
/* vertical variants of the kick/push so they read with the down-pointing arrow */
@keyframes procArrowKickDown {
  0%   { transform: translateX(-50%) translateY(0); }
  38%  { transform: translateX(-50%) translateY(6px); }
  100% { transform: translateX(-50%) translateY(0); }
}
@keyframes procTitlePushDown {
  0%   { transform: translateY(0); }
  46%  { transform: translateY(4px); }
  100% { transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .proc__list.is-tick::before,
  .proc__list.is-tick .proc__item.is-active .proc__label { animation: none; }
}

/* container framing the straight, overlapping card row */
.fun__fan {
  display: flex; justify-content: center; align-items: center;
  width: fit-content; max-width: 100%;
  margin: 0 auto;
  padding: clamp(20px, 2.6vw, 36px) clamp(24px, 3vw, 44px);
  border: 1px solid var(--line);
  border-radius: clamp(20px, 2vw, 28px);
  background: var(--surface-2, #f6f9ff);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.fun__card {
  position: relative;
  flex: 0 0 auto;
  width: clamp(120px, 11.8vw, 172px);
  margin: 0 clamp(-34px, -2.8vw, -18px);   /* overlapping fan */
  border-radius: var(--radius-lg);         /* match project cards (24px) */
  border: 1px solid var(--line);           /* match project hairline */
  box-shadow: var(--shadow-sm);            /* match project shadow */
  background: var(--surface);              /* white card body */
  overflow: hidden;
  display: flex; flex-direction: column;
  transform-origin: bottom center;
  cursor: pointer;
  /* pre-reveal: rise + faded (no rotation — straight) */
  opacity: 0;
  transform: translateY(40px) scale(.94);
  transition: transform .6s var(--ease, cubic-bezier(.22,1,.36,1)),
              opacity .5s ease, box-shadow .3s;
  transition-delay: calc(var(--i, 0) * 0.07s);
}
/* later cards stack on top, so each card's left edge (its title) stays
   visible above its left neighbour */
.fun__card:nth-child(1){z-index:1} .fun__card:nth-child(2){z-index:2}
.fun__card:nth-child(3){z-index:3} .fun__card:nth-child(4){z-index:4}
.fun__card:nth-child(5){z-index:5} .fun__card:nth-child(6){z-index:6}
.fun__card:nth-child(7){z-index:7} .fun__card:nth-child(8){z-index:8}

/* revealed state — straight, overlapping (no rotation) */
.fun.is-in .fun__card {
  opacity: 1;
  transform: none;
}
/* hover: lift straight to the front */
.fun.is-in .fun__card:hover {
  transform: translateY(-16px) scale(1.08);
  z-index: 9;
  box-shadow: var(--shadow-lg);
}

/* gradient "media" top + white text body (like the project cards) */
.fun__media {
  display: block; width: 100%;
  aspect-ratio: 1 / 1;
  background-size: cover; background-position: center;
}
.fun__body {
  position: relative;
  padding: 11px 11px;
  background: var(--surface);
  text-align: left;
}
/* hairline divider, inset from the card edges so it breathes (not full-bleed) */
.fun__body::before {
  content: "";
  position: absolute; top: 0; left: 11px; right: 11px;
  height: 1px; background: var(--line);
}
.fun__text { min-width: 0; }
.fun__title {
  font-family: var(--display);
  font-size: 0.875rem; font-weight: 600;
  color: var(--ink); line-height: 1.2; letter-spacing: -0.01em;
}
.fun__sub {
  font-size: 0.875rem; color: var(--muted);   /* 14px floor */
  line-height: 1.3; margin-top: 2px;
}
/* clickable affordance — arrow in a circle at the card's top-right
   (over the media, where the fan spreads open so it's always visible) */
.fun__go {
  position: absolute; top: 9px; right: 9px; z-index: 3;
  width: 28px; height: 28px;
  border-radius: var(--radius-sm);    /* rounded square (matches the icon chip) */
  display: grid; place-items: center;
  background: #fff;
  color: var(--ink, #1a1a1a);         /* dark filled triangle */
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  transition: transform .35s var(--ease, ease), background .3s, color .3s, border-color .3s;
}
.fun__card:hover .fun__go {
  background: var(--ink, #1a1a1a); color: #fff; border-color: transparent;
  transform: translate(3px, -3px);
}
/* Clear the lowest fanned cards (edge cards translate down ~80px and spill
   past the fan baseline) so the heading never sits under them */
.fun__head { margin-top: clamp(48px, 5vw, 72px); }
.fun__head .h-1 { margin-inline: auto; }
.fun__head .lead { margin-inline: auto; text-align: center; max-width: 42ch; margin-top: 12px; }

/* Featured projects: narrow the 3 cards to the hero-heading width so they
   read as a centered column rather than full-bleed, and make them compact
   (shorter, tighter type, smaller thumbnail). Fully responsive — see the
   breakpoint blocks below. */
/* Homepage: every section sits tight, matching the hero → first-project-card gap */
#work.section--tight, #snapshot.section--tight, #approach.section--tight,
#process.section--tight, #notes.section--tight, #stack.section--tight,
#experience.section--tight, #think.section--tight,
.cta-foot.section--tight { padding-top: clamp(16px, 2vw, 24px); }   /* footer joins the same stack rhythm */
/* A project with no cover yet falls back to .study__media-ph, which style.css
   colours rgba(255,255,255,.22) — written for the old dark accent stage. The
   stage is now #f6f7f9, so the placeholder rendered white on light grey and the
   card read as an empty box. Give it the stage's own ink instead. */
#work .study__media-ph { color: rgba(12, 13, 18, .16); }

/* nav scroll-targets: land the section so the PREVIOUS card's bottom edge sits
   on the nav's bottom line rather than 19px below it — a sliver of the card you
   just left reads as "the link did not scroll far enough".
   Flat 84px, not the old clamp(96px, 12vh, 124px): the nav's bottom is a fixed
   89px, so a viewport-height offset drifted — 7px of spill on an 800px screen,
   35px on a 1080px one. #notes is included because a section with no
   scroll-margin lands 65px UNDER the nav. */
#work, #experience, #process, #snapshot, #notes { scroll-margin-top: 40px; }
#work .studies { max-width: none; margin-inline: auto; gap: clamp(16px, 2.4vw, 18px); }
#work .study__body { padding: clamp(28px, 3.4vw, 48px); gap: 14px; }   /* base rhythm — header cluster. 14, not 18: it also feeds the chips>bullets gap, which adds .study__impact's own 12 on top (a flex column does not collapse margins into gap) -> 26. */
#work .study__body { padding: round(clamp(28px, 3.4vw, 48px), 4px); }   /* 44 @1280 */
/* grouped rhythm: more air before the bullet block, most before the metrics footer */
#work .study__impact { margin-top: 12px; }
#work .study__stats  { margin-top: 20px; }
#work .study__brand { margin-bottom: 0; gap: 12px; }
/* domain label → current typeface (Satoshi), no longer Space Mono */
#work .study__domain { font-family: var(--sans); font-weight: 600; font-size: 1rem; letter-spacing: normal; text-transform: none; color: var(--ink); }   /* 16px — on the body step */
#work .study__title { font-size: clamp(1.5rem, 2.3vw, 1.75rem); line-height: 1.2; margin-bottom: 0; }  /* 24→28px, typeface unchanged */
#work .study__sum {
  font-size: 1.0625rem; line-height: 1.6; max-width: 48ch;     /* 17px */
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
#work .study__meta { margin: 28px 0 0; padding-top: 24px; gap: clamp(32px, 4.5vw, 64px); }
#work .study__meta-k { font-size: 0.875rem; }    /* 14px label */
#work .study__meta-v { font-size: 1.125rem; }    /* 18px */

/* skill/category chips under the title */
/* project letter badge → monochrome navy (echoes the header logo / buttons), so the card
   complements the dark theme; the colourful product screenshot still carries the colour.
   !important beats the inline per-project cover colour set in studyCard(). */
.study__logo--mark {
  background: #1c1c1c !important;
  color: #fff !important;
  box-shadow: 0 4px 12px -5px rgba(28, 28, 28, .55);
}
.study__chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0; }   /* body gap owns the rhythm */
.study__chip {
  font-family: var(--sans); font-size: 0.875rem; font-weight: 500; color: #0c0d12;   /* 14px */
  letter-spacing: .02em;   /* slight breathing between characters */
  padding: 6px 14px; border: 1px solid var(--line); border-radius: 999px; background: #f6f7f9;
  white-space: nowrap;
}
/* Between the two-column card and the stacked one, the text column dips to
   ~395px while three chips need ~414px, which drops the last chip to a second
   row. Tightening the horizontal padding buys back the 24px and keeps the set
   on one line; the chips are still on the 4-pt grid. */
@media (max-width: 1100px) and (min-width: 621px) {
  .study__chip { padding: 6px 10px; }
  .study__chips { gap: 4px; }   /* at 960 the set overran its column by 0.3px */
}
/* impact rows — icon + sentence; one body size (16px), uniform weight.
   Uniform 20px icons keep every text edge on the same vertical line. */
.study__impact { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 16px; }
.study__impact-row {
  display: flex; align-items: flex-start; gap: 12px;
  font-family: var(--sans); font-size: 1rem; font-weight: 400; line-height: 1.5; color: #0c0d12;
}
.study__impact-ic { flex: none; width: 17px; height: 17px; margin-top: 3px; color: #767f8f; }   /* matched to the snapshot list icons (.snap-stat-list svg): 17px, #767f8f, stroke 1.7. The old comment claimed “same caption grey” but the value was near-black at a larger size, so the same 1.7 stroke read heavier here than in the About cards. */

/* impact parameters — three value/label stats */
.study__stats {
  list-style: none; margin: 0; padding: 0;
  display: flex; gap: clamp(20px, 2.5vw, 32px);   /* lands 32 @1280 (even) */
}
/* CTA — quiet text link at the right end of the brand row; the whole card is
   the click target, this is just the affordance (senior-portfolio pattern) */
.study__go {
  display: inline-flex; align-items: center; gap: 10px; margin-left: auto; margin-right: 24px;
  font-family: var(--sans); font-size: 1rem; font-weight: 500; white-space: nowrap;
  color: var(--ink, #16181d);
}
/* arrow lives in a circular chip that fills dark on card-hover */
.study__go-chip {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid var(--line-2, rgba(12, 13, 18, .18)); background: transparent;
  transition: background .3s var(--ease, ease), border-color .3s var(--ease, ease), transform .35s cubic-bezier(.34, 1.56, .5, 1);
}
.study__go-ar { width: 15px; height: 15px; flex: none; }
.study:hover .study__go-chip {
  background: var(--ink, #16181d); border-color: var(--ink, #16181d); color: #fff;
  transform: translateX(4px) scale(1.06);
}
@media (prefers-reduced-motion: reduce) {
  .study__go-chip { transition: none; }
  .study:hover .study__go-chip { transform: none; }
}
/* optional caption above the stat row — carries the same class so it inherits the
   #work reveal delays and margin-top, then overrides the flex/list presentation.
   Used when the numbers describe scope rather than outcome. */
.study__stats-lbl {
  display: block; font-family: var(--sans); font-size: 0.75rem; font-weight: 500;
  color: rgba(12, 13, 18, .52); text-transform: uppercase; letter-spacing: .09em;
}
.study__stats-lbl + .study__stats { margin-top: 10px; }
#work .study__stats-lbl + .study__stats { margin-top: 10px; }
.study__stat { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 8px; align-items: center; text-align: center; }   /* equal columns so a longer label wraps inside its own share instead of pushing the row wide */
/* Vertical breaker between metrics. Painted as a background rather than a
   border so it can fade out at both ends like the hero and footer rules -- a
   border is flat by definition. Size is 1px wide by the full height, pinned
   left, which is exactly where the border sat. */
.study__stat + .study__stat {
  padding-left: clamp(16px, 1.9vw, 24px);   /* trimmed so the bolder, wider-tracked labels still fit the column */
  background-image: var(--rule-fade-y);
  background-repeat: no-repeat;
  background-size: 1px 100%;
  background-position: left top;
}
.study__stat-v {
  font-family: "Averia Serif Libre", var(--display); font-weight: 700; font-size: 1.625rem;   /* 26px */
  color: var(--ink, #16181d); letter-spacing: -0.01em; line-height: 1.1;
}
.study__stat-l {
  font-family: var(--sans); font-size: 0.75rem; font-weight: 500;
  color: #0c0d12; text-transform: uppercase; letter-spacing: .09em;
  text-wrap: balance; line-height: 1.35;   /* outcome labels are phrases, not single words — nowrap overran the column by ~109px */
}


/* screenshot stage — soft cool-grey backdrop behind the product shot, so the
   (mostly white) dashboards read as framed screens instead of melting into
   the white card. Shared by the homepage grid and the Work index. */
.study .study__media { background-color: #f6f7f9; }   /* stage grey — same fill as the process card's .flow-panel */
.study .study__media::before { content: none; }       /* drop the hairline divider between details and screen */

/* Desktop / tablet-landscape (two-column card): collapse the 320px media
   floor so the tightened body drives the (shorter) card height. */
@media (min-width: 901px) {
  #work .study { grid-template-columns: 568px minmax(0, 1fr); }   /* details 568 · media fills the rest */
  /* alternating zig-zag: mirror the columns AND pull the media ahead of the
     body. style.css uses `order: 2`, which is a no-op because the media is
     already the last DOM child — so every card rendered identically. */
  #work .study.is-flip { grid-template-columns: minmax(0, 1fr) 568px; }
  #work .study.is-flip .study__media { order: -1; }
  #work .study__media { min-height: clamp(360px, 27vw, 432px); padding: clamp(12px, 1.4vw, 24px); }
  #work .study__media { padding: round(clamp(12px, 1.4vw, 24px), 4px); }   /* 16 @1280 — the stage was 40, which shrank the screenshot to the point the UI could not be read */
}

/* ≤900px — card stacks (thumbnail on top). Keep the thumbnail a calm,
   fixed-ratio band instead of the default 220px so it scales with width. */
@media (max-width: 900px) {
  #work .study__media { min-height: 0; aspect-ratio: 16 / 10; }
  /* `.study__media` sets `background-origin: content-box` so "padding insets the
     screen so it never touches the edges" — but the only padding rule lived in
     the ≥901px block, so below it the screenshot ran the full width of the stage
     and its square corners sat over the card's rounded ones. Same inset as
     desktop, scaled for the narrower card. */
  #work .study__media { padding: clamp(12px, 3vw, 16px); }
  /* the stats row's nowrap labels set a ~470px min-content that inflated the
     stacked card's single track past the phone width, pushing the title,
     chips, and stats off the right edge. Cap the track and let labels wrap. */
  .study { grid-template-columns: minmax(0, 1fr); }
  .study__body { min-width: 0; }
  .study__stat-l { white-space: normal; }
}

/* ≤620px — single narrow column, content already centers (style.css). Trim
   the thumbnail band a touch and let the summary breathe to ~3 lines. */
@media (max-width: 620px) {
  #work .study__media { aspect-ratio: 3 / 2; }
  #work .study__sum { -webkit-line-clamp: 3; max-width: 34ch; }
  .study__go { display: none; }   /* the whole card is the tap target on mobile */
}

/* Mobile: drop the fan, become a swipeable horizontal carousel */
@media (max-width: 720px) {
  /* On desktop the fan tucks up under the hero (negative margin). On a phone
     that pulls the carousel over the hero CTA — give it positive spacing. */
  #fun.section--tight { margin-top: 0; padding-top: clamp(28px, 7vw, 44px); }
  .fun__fan {
    display: flex;
    flex-wrap: nowrap;
    justify-content: flex-start;
    align-items: stretch;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    gap: 14px;
    padding: 6px 16px 14px;
    min-height: 0;
    scrollbar-width: none;              /* Firefox */
  }
  .fun__fan::-webkit-scrollbar { display: none; }   /* Chrome/Safari */
  .fun__card {
    flex: 0 0 auto;
    width: clamp(150px, 58vw, 220px);
    margin: 0;
    scroll-snap-align: center;
    transform: translateY(16px);        /* upright, no rotation */
    z-index: 1;
  }
  .fun.is-in .fun__card { transform: none; }
  .fun.is-in .fun__card:hover { transform: translateY(-4px) scale(1.02); }
  /* all tags bottom-left (no overlap in a carousel) */
  .fun__card .fun__tag,
  .fun__card:nth-child(n+6) .fun__tag { left: 8px; right: auto; bottom: 8px; }
  /* carousel cards don't spill downward, so the big desktop clearance
     would leave a dead gap — pull the heading back up */
  .fun__head { margin-top: clamp(24px, 6vw, 40px); }
}
/* Mobile hero: compact photo so the headline leads and a sliver of the next
   section shows without scrolling */
@media (max-width: 620px) {
  .hero__visual { width: auto; min-height: 0; margin-bottom: 4px; }
  .hero__photo { width: 104px; outline-width: 5px; }
  .hero__ring { width: clamp(150px, 46vw, 200px); }
}

/* ============================================================
   PROCESS + STACK — folder cards (hover/focus reveals contents)
   A back wall + front pocket; the items sit hidden behind the
   pocket, then rise and fan over the folder's top lip on hover.
   ============================================================ */
.folders {
  display: grid; grid-template-columns: repeat(2, 1fr);
  gap: clamp(20px, 3vw, 32px);
  max-width: 760px; margin-inline: auto;
  margin-top: clamp(40px, 5vw, 64px);   /* room for items that rise above the folders */
}
.folder {
  position: relative;
  height: clamp(210px, 24vw, 250px);
  cursor: pointer; outline: none;
}
/* back wall + tab */
.folder__back {
  position: absolute; left: 0; right: 0; bottom: 0; height: 74%;
  background: var(--surface-2); border: 1px solid var(--line);
  border-radius: 18px; z-index: 1;
}
.folder__back::before {
  content: ""; position: absolute; top: -13px; left: 22px;
  width: 38%; height: 16px;
  background: var(--surface-2); border: 1px solid var(--line); border-bottom: none;
  border-radius: 10px 10px 0 0; z-index: 1;
}
/* items: hidden behind the pocket at rest, rise + fan on hover */
.folder__items {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
}
.folder__items > * {
  position: absolute; left: 50%; bottom: 30%;
  --tx: 0%; --r: 0deg;
  transform: translateX(-50%) translateY(24%) scale(.96);
  transform-origin: bottom center;
  transition: transform .55s cubic-bezier(.22, 1, .36, 1), box-shadow .3s;
  transition-delay: calc(var(--i) * 0.045s);
}
.folder:hover .folder__items > *,
.folder:focus-visible .folder__items > * {
  transform: translateX(-50%) translateX(var(--tx)) translateY(var(--ty, -120%)) rotate(var(--r)) scale(1);
}
/* fan offsets — Stack (7 logos): tight cluster popping up out of the mouth,
   centre highest, edges lower + rotated (icons overlap like a spilling pile) */
.folder__items--logos > *:nth-child(1){--tx:-122%;--ty:-96%; --r:-24deg}
.folder__items--logos > *:nth-child(2){--tx:-82%; --ty:-132%;--r:-15deg}
.folder__items--logos > *:nth-child(3){--tx:-41%; --ty:-154%;--r:-7deg}
.folder__items--logos > *:nth-child(4){--tx:0%;   --ty:-162%;--r:0deg}
.folder__items--logos > *:nth-child(5){--tx:41%;  --ty:-154%;--r:7deg}
.folder__items--logos > *:nth-child(6){--tx:82%;  --ty:-132%;--r:15deg}
.folder__items--logos > *:nth-child(7){--tx:122%; --ty:-96%; --r:24deg}
/* fan offsets — Process (5 chips): arc so chips sit at different heights
   (center highest) and stay readable instead of overlapping flat */
.folder__items:not(.folder__items--logos) > *:nth-child(1){--tx:-190%;--ty:-178%;--r:-17deg}
.folder__items:not(.folder__items--logos) > *:nth-child(2){--tx:-95%; --ty:-238%;--r:-9deg}
.folder__items:not(.folder__items--logos) > *:nth-child(3){--tx:0%;   --ty:-272%;--r:0deg}
.folder__items:not(.folder__items--logos) > *:nth-child(4){--tx:95%;  --ty:-238%;--r:9deg}
.folder__items:not(.folder__items--logos) > *:nth-child(5){--tx:190%; --ty:-178%;--r:17deg}
/* process step chips */
.folder__chip {
  display: grid; place-items: center;
  height: 32px; padding: 0 13px;
  background: #fff; border: 1px solid var(--line); border-radius: var(--pill);
  font-family: var(--display); font-size: 0.875rem; font-weight: 600;
  color: var(--ink); white-space: nowrap; box-shadow: var(--shadow-sm);
}
/* stack tool logos */
.folder__logo {
  display: grid; place-items: center;
  width: 54px; height: 54px;
  background: #fff; border: 1px solid var(--line); border-radius: 15px;
  box-shadow: var(--shadow-md);
}
.folder__logo svg { width: 30px; height: 30px; display: block; }
/* front pocket + label */
.folder__front {
  position: absolute; left: 0; right: 0; bottom: 0; height: 60%;
  background: linear-gradient(180deg, #eef1f5, #e4e8ee);
  border: 1px solid var(--line); border-top-color: rgba(255,255,255,.7);
  border-radius: 18px; z-index: 3;
  display: flex; flex-direction: column; justify-content: flex-end;
  padding: 16px 18px;
  transition: transform .4s var(--ease, ease), box-shadow .4s;
}
.folder:hover .folder__front,
.folder:focus-visible .folder__front {
  transform: translateY(4px); box-shadow: var(--shadow-md);
}
.folder__name {
  font-family: var(--display); font-size: 1.0625rem; font-weight: 600;
  color: var(--ink); letter-spacing: -0.01em; line-height: 1.1;
}
.folder__meta { font-size: 0.875rem; color: var(--muted); margin-top: 3px; }

@media (max-width: 620px) {
  .folders { grid-template-columns: 1fr; max-width: 360px; gap: 16px; }
  .folder { height: clamp(190px, 56vw, 230px); }
}

@media (prefers-reduced-motion: reduce) {
  .fun__card { opacity: 1; transition: box-shadow .3s; transform: rotate(var(--r,0deg)) translateY(var(--y,0)); }
}

/* ---------- Custom cursor: Figma-style arrow + "You" name tag ---------- */
html:has(body.has-cursor), body.has-cursor, body.has-cursor * { cursor: none !important; }   /* hide the native cursor (incl. the <html> region / scrollbar gutter) */
.cursor-fig {
  position: fixed; top: 0; left: 0; z-index: 99999; pointer-events: none;
  opacity: 0; will-change: transform; transition: opacity .25s ease;
}
body.has-cursor.is-visible .cursor-fig { opacity: 1; }
.cursor-fig__arrow {
  display: block; margin: -2px 0 0 -2px;        /* nudge the tip onto the real pointer */
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .28));
  transition: transform .12s ease;
}
.cursor-fig.is-down .cursor-fig__arrow { transform: scale(.82); }   /* little click squeeze */
.cursor-fig__tag {
  position: absolute; left: 20px; top: 23px;
  background: var(--brand, #1c1c1c); color: #fff;
  font: 600 12px/1 var(--sans, system-ui, sans-serif); letter-spacing: .01em;
  padding: 5px 8px; border-radius: 8px; border-top-left-radius: 3px;
  white-space: nowrap;
  box-shadow: 0 4px 12px -3px rgba(28, 28, 28, .5);
}
/* never on touch / coarse pointers (the JS also bails, so has-cursor isn't set there) */
@media (hover: none), (pointer: coarse) {
  .cursor-fig { display: none !important; }
  html:has(body.has-cursor), body.has-cursor, body.has-cursor * { cursor: auto !important; }
}

/* ============================================================
   PROCESS bento — two vertical cards + one horizontal card
   ============================================================ */
.bento {
  display: grid;
  grid-template-columns: 1fr;           /* single full-width process card */
  gap: clamp(16px, 2vw, 24px);
}
.bento-card {
  border: 1px solid var(--line, rgba(13,26,64,.10));
  border-radius: var(--radius-lg, 24px);
  background: #fff;
  padding: clamp(24px, 3vw, 40px);
  padding: round(clamp(24px, 3vw, 40px), 4px);   /* 40 @1280 */
  box-shadow: 0 1px 2px rgba(28,28,28,.04), 0 10px 28px -22px rgba(28,28,28,.22);   /* shared container lift — was nearly flat beside its neighbours */
}
.bento-card--h { grid-column: 1 / -1; }   /* third card spans the full width → horizontal */

/* ---------------------------------------------------------------
   WORK-PAGE CARD ART
   The three cards (Design Process / Figma Proficiency / AI Tools) carried a
   bare gradient block. The gradient STAYS as the ground and the illustration
   sits centred on top at a fixed size, because the block's width is fluid
   while its height is pinned at 120px -- its aspect runs 2.3 at 1280, 4.2 at
   620 and 6.4 at 900, so no fixed-ratio artwork could fill it without
   distorting or cropping. The ground adapts for free; the art does not have to.
   Same language as the #notes covers: neutral gradient, chunky white objects.
   --------------------------------------------------------------- */
/* Matched to the article cards at BOTH states. The shadows are even (no
   offset, so they do not pool under the card) -- it was --shadow-sm at rest and
   --shadow-md at 4px on hover, both directional, plus a border-colour shift the
   article cards do not have. .card exists only on work.html, on these three. */
.card {
  box-shadow: 0 0 2px rgba(28, 28, 28, .05),
              0 0 7px rgba(28, 28, 28, .05),
              0 0 16px -2px rgba(28, 28, 28, .06);
}
.card:hover, .card:focus-visible {
  transform: translateY(-3px);
  box-shadow: 0 0 3px rgba(28, 28, 28, .06),
              0 0 12px rgba(28, 28, 28, .06),
              0 0 28px -4px rgba(28, 28, 28, .14);
  border-color: var(--line);
}
.card .card-art {
  position: relative; overflow: hidden;
  background: #f6f7f9;                       /* the stage colour .study__media paints */
}
.card .card-art::after {
  content: ''; position: absolute; inset: 0;
  background-image: var(--art);
  background-repeat: no-repeat;
  background-position: center;
  background-size: auto 88px;                /* the square tile, centred, not stretched */
}
.card .card-art[data-art="design-systems"]  { --art: url("../img/learnings/covers/design-systems.png?v=cov6"); }
.card .card-art[data-art="ai-ux"]           { --art: url("../img/learnings/covers/ai-ux.png?v=cov6"); }
.card .card-art[data-art="saas-guidelines"] { --art: url("../img/learnings/covers/saas-guidelines.png?v=cov6"); }
@media (max-width: 900px) {
  .card .card-art::after { background-size: auto 76px; }
}
}

/* ---------------------------------------------------------------
   COVER HERO TILT
   The same cursor-tracked tilt the project cards use, dialled up for the three
   real-HTML case studies: on those pages the cover IS the subject, so it takes
   a wider angle plus a small lift. Hover target is the <figure>; the transform
   lands on the <img>, which is why these are descendant selectors. Triply nests
   its image inside .tp-shot__pan -- also covered.
   None of the three pages set a transform on these images, so nothing is being
   overridden here.
   --------------------------------------------------------------- */
.q-cover img, .fp-shot--cover img, .tp-cover img {
  transform: perspective(1400px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg)) scale(var(--tz, 1));
  transition: transform .6s cubic-bezier(.22, 1, .36, 1);
}
.q-cover.is-tilt img, .fp-shot--cover.is-tilt img, .tp-cover.is-tilt img {
  transition: transform .16s ease-out; will-change: transform;
}
.q-cover.is-untilt img, .fp-shot--cover.is-untilt img, .tp-cover.is-untilt img {
  transition: transform .6s cubic-bezier(.22, 1, .36, 1);
}
@media (prefers-reduced-motion: reduce) {
  .q-cover img, .fp-shot--cover img, .tp-cover img { transform: none; transition: none; }
}

/* ---------------------------------------------------------------
   NO-JAVASCRIPT SAFETY NET
   The project cards are baked into the HTML (.claude/prerender-cards.js) so
   crawlers that do not execute JS can read them. But the scroll-reveal system
   paints them at opacity .001 until an IntersectionObserver adds .is-in, so
   without that observer they would be present in the source and invisible on
   screen -- arguably worse than missing.
   An inline script in <head> stamps html.js before first paint, so this rule
   only applies when JS is off (or when main.js failed to load, which the same
   inline script detects with a watchdog). Reveal-on-scroll is an enhancement;
   the content underneath it is not.
   --------------------------------------------------------------- */
html:not(.js) .reveal,
html:not(.js) #work .study,
html:not(.js) #work .study .study__media,
html:not(.js) #work .study__brand, html:not(.js) #work .study__title,
html:not(.js) #work .study__sum, html:not(.js) #work .study__meta,
html:not(.js) #work .study__btn, html:not(.js) #work .study__chips,
html:not(.js) #work .study__impact, html:not(.js) #work .study__stats,
html:not(.js) .notes__grid {
  opacity: 1 !important; filter: none !important; transform: none !important;
}

/* --- Card 1: philosophy --- */
.philo { display: flex; flex-direction: column; }
/* the display line under a section label -- one rule, so the process card and
   the Experience and Articles ledes cannot drift apart */
.philo__title,
.sec-lede {
  font-family: "Averia Serif Libre", var(--display);
  font-weight: 700; font-size: clamp(1.625rem, 1.325rem + 1.16vw, 2.25rem);   /* 36px cap (lands 36 @1280) */
  line-height: 1.16; letter-spacing: -0.005em; color: #1c1c1c;
  margin: 0;
}
.philo__label {
  font-family: "Caveat", cursive; font-size: 1.5rem; color: var(--brand, #1c1c1c);
  margin-top: clamp(20px, 3vw, 32px);
}
.philo__txt {
  margin: 12px 0 0; color: #2a3142;
  font-family: var(--sans); font-weight: 400;
  font-size: 1.0625rem; line-height: 1.45; max-width: 44ch;   /* matches the project-card impact points */
}
/* chips sit right under the description (not pinned to the card bottom) */
.philo__chips { list-style: none; display: flex; flex-wrap: wrap; gap: 8px; margin: 0; padding: clamp(18px, 2.2vw, 24px) 0 0; }
.philo__chips li {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 14px; border-radius: var(--pill, 999px);
  border: 1px solid var(--line-2, rgba(12,13,18,.14));
  font-size: 0.8125rem; font-weight: 600; letter-spacing: .02em;
  text-transform: uppercase; color: var(--ink-2, #232934);
}
.philo__chips svg { width: 15px; height: 15px; color: var(--brand, #1c1c1c); }

/* AI-assisted workflow strip — subtle tools mention between the paragraph and the process grid */
.philo__ai {
  margin-top: 30px;                                   /* ~28–32px gap after the paragraph */
  padding: 6px 18px;                                  /* slim — the strip is a footnote, not a feature */
  border: 1px solid rgba(15, 23, 42, 0.06);
  background: rgba(15, 23, 42, 0.015);
  border-radius: var(--radius, 16px);
  display: flex; flex-direction: column; gap: 4px;
}
.philo__ai-title {
  font-family: var(--sans);
  font-size: 0.75rem; font-weight: 500; letter-spacing: .08em;
  text-transform: uppercase; color: var(--faint, #5a6273);
}
.philo__ai-tools {
  font-family: var(--sans);
  font-size: 1rem; font-weight: 600; letter-spacing: .01em;
  color: var(--ink, #0c0d12);
}
.philo__ai-note {
  font-family: var(--sans);
  font-size: 1rem; font-weight: 400; line-height: 1.5;   /* 16px floor — same as the process-card descriptions */
  color: var(--muted, #3a4150);
  margin-top: 2px;                                       /* breathing room under the tools line */
}

/* --- process card content cascade: eyebrow → heading → timeline (steps have
   their own stagger) → detail stage, hero-style --- */
.bento.reveal .philo__eyebrow,
.bento.reveal .notes__title,
.bento.reveal .philo__title,
.bento.reveal .flow-nav,
.bento.reveal .flow-stage {
  opacity: 0; transform: translateY(12px);
  transition: opacity .75s ease, transform .75s cubic-bezier(.22, 1, .36, 1);
}
.bento.reveal.is-in .philo__eyebrow,
.bento.reveal.is-in .notes__title,
.bento.reveal.is-in .philo__title,
.bento.reveal.is-in .flow-nav,
.bento.reveal.is-in .flow-stage { opacity: 1; transform: none; }
.bento.reveal.is-in .philo__eyebrow { transition-delay: .10s; }
.bento.reveal.is-in .notes__title { transition-delay: .10s; }
.bento.reveal.is-in .philo__title   { transition-delay: .26s; }
.bento.reveal.is-in .flow-nav       { transition-delay: .46s; }
.bento.reveal.is-in .flow-stage     { transition-delay: .80s; }
/* inner cascade: after the grey panel box fades in, its content arrives piece by
   piece — same staggered feel as the study cards above. Only the active panel's
   content participates; step-to-step switching is still handled by flowPanelIn. */
.bento.reveal .flow-panel.is-on .flow-panel__num,
.bento.reveal .flow-panel.is-on .flow-panel__title,
.bento.reveal .flow-panel.is-on .flow-panel__what,
.bento.reveal .flow-panel.is-on .flow-panel__label,
.bento.reveal .flow-panel.is-on .flow-tools,
.bento.reveal .flow-panel.is-on .flow-panel__how {
  opacity: 0; transform: translateY(12px);
  transition: opacity .7s ease, transform .7s cubic-bezier(.22, 1, .36, 1);
}
.bento.reveal.is-in .flow-panel.is-on .flow-panel__num,
.bento.reveal.is-in .flow-panel.is-on .flow-panel__title,
.bento.reveal.is-in .flow-panel.is-on .flow-panel__what,
.bento.reveal.is-in .flow-panel.is-on .flow-panel__label,
.bento.reveal.is-in .flow-panel.is-on .flow-tools,
.bento.reveal.is-in .flow-panel.is-on .flow-panel__how { opacity: 1; transform: none; }
.bento.reveal.is-in .flow-panel.is-on .flow-panel__num   { transition-delay: .96s; }
.bento.reveal.is-in .flow-panel.is-on .flow-panel__title { transition-delay: 1.08s; }
.bento.reveal.is-in .flow-panel.is-on .flow-panel__what  { transition-delay: 1.20s; }
.bento.reveal.is-in .flow-panel.is-on .flow-panel__label { transition-delay: 1.14s; }
.bento.reveal.is-in .flow-panel.is-on .flow-tools        { transition-delay: 1.26s; }
.bento.reveal.is-in .flow-panel.is-on .flow-panel__how   { transition-delay: 1.40s; }
@media (prefers-reduced-motion: reduce) {
  .bento.reveal .philo__eyebrow, .bento.reveal .philo__title,
  .bento.reveal .flow-nav, .bento.reveal .flow-stage,
  .bento.reveal .flow-panel.is-on .flow-panel__num, .bento.reveal .flow-panel.is-on .flow-panel__title,
  .bento.reveal .flow-panel.is-on .flow-panel__what, .bento.reveal .flow-panel.is-on .flow-panel__label,
  .bento.reveal .flow-panel.is-on .flow-tools, .bento.reveal .flow-panel.is-on .flow-panel__how {
    transition: none; opacity: 1; transform: none;
  }
}

/* --- full-width process card: centered header --- */
.philo--full .philo__head--center { text-align: center; }
/* Kept for the capability chips' own eyebrow; the section heads no longer use
   it -- they carry the big serif line instead (see .notes__title below). */
.philo--full .philo__head--center .philo__eyebrow {
  display: block; margin: 0 0 12px;
  font-family: var(--sans); font-size: 0.875rem; font-weight: 500;   /* 14px, medium */
  letter-spacing: .12em; text-transform: uppercase; color: var(--faint, #5a6273);
}
.philo--full .philo__head--center .philo__title { max-width: 34ch; margin-inline: auto; text-wrap: balance; }
/* same measure and balancing for the two new ledes. Nothing later in the sheet
   targets .sec-lede, so this is safe from the source-order trap .exp-head hit. */
/* The section name, same line as the footer's "Let's talk". One rule, so the
   four big lines on the page cannot drift apart. */
.notes__title {
  margin: 0 auto; max-width: 22ch;
  font-family: "Averia Serif Libre", var(--display); font-weight: 700;
  font-size: clamp(1.625rem, 1.325rem + 1.16vw, 2.25rem); line-height: 1.16; letter-spacing: -0.01em;
  color: #1c1c1c; text-align: center; text-wrap: balance;
}
.sec-sub { text-align: center; }

/* services absorbed into the process card — a quiet chip row in the same
   language as the stage's tool pills; no divider, no heavy icon boxes */
.caps--row.caps--embed {
  display: block;
  margin-top: clamp(20px, 2.4vw, 28px);
  border-top: none;
  padding: 0;
}
.caps--row.caps--embed .caps__eyebrow { display: block; margin-bottom: 12px; }
.caps--row.caps--embed .caps__list { display: flex; flex-wrap: wrap; gap: 8px; }
.caps--row.caps--embed .caps__list li {
  flex: none; border-top: none;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 14px; border-radius: 999px;
  background: #f6f7f9; border: 1px solid var(--line, #e7eaf1);
  font-family: var(--sans); font-size: 0.875rem; font-weight: 500; color: var(--ink, #0c0d12);
  white-space: nowrap;
}
.caps--row.caps--embed .caps__ic {
  width: auto; height: auto; border-radius: 0;
  background: transparent; color: var(--ink, #0c0d12);
}
.caps--row.caps--embed .caps__ic svg { width: 15px; height: 15px; }

/* --- the workflow line: 6 numbered nodes on a continuous connector --- */
.flow {
  --flowY: 50px;                       /* number (12px) + 14px gap + half the 48px node */
  list-style: none; margin: clamp(24px, calc(2.2vw + 6px), 36px) auto 0; padding: 0;   /* heading → timeline 36px */
  margin-top: round(clamp(24px, calc(2.2vw + 6px), 36px), 4px);   /* 36 @1280 */
  width: 90%;                          /* slightly narrower than the card, centered */
  position: relative;
  display: grid; grid-template-columns: repeat(6, 1fr);
}
.flow::before {                        /* the connector line, drawn through the node centers */
  content: ""; position: absolute; z-index: 0;
  top: calc(var(--flowY) - 1px); height: 2px;
  left: calc(100% / 12); right: calc(100% / 12);
  background: rgba(12, 13, 18, 0.085);   /* ~15% lighter connector */
}
.flow__step { position: relative; padding: 0 10px; text-align: center; max-width: 170px; margin-inline: auto; }
.flow__step:not(:last-child)::after {  /* direction arrow on the line, at the column boundary */
  content: ""; position: absolute; z-index: 2;
  top: calc(var(--flowY) - 4.5px); right: -4.5px;
  width: 9px; height: 9px;
  border-top: 2px solid #d2d7df;    /* ~12% lighter arrows */
  border-right: 2px solid #d2d7df;
  transform: rotate(45deg);
}
.flow__num {
  display: block; margin-bottom: 14px;
  font-family: var(--sans); font-size: 0.875rem; font-weight: 600;   /* 14px */
  letter-spacing: .08em; line-height: 1; color: var(--faint, #5a6273);
  height: 1rem;                        /* the active step steps this up to 16px/700 — a fixed box keeps that from pushing its node and label 2px lower than the other five */
}
.flow__node {
  position: relative; z-index: 1;
  width: 48px; height: 48px; margin: 0 auto; border-radius: 50%;
  display: grid; place-items: center;
  background: #fff; border: 1px solid var(--line-2, rgba(12,13,18,.14));
  box-shadow: 0 2px 8px -4px rgba(13,26,64,.14);
  color: var(--ink, #0c0d12);
}
.flow__node svg { width: 20px; height: 20px; }
.flow__name {
  display: block; margin-top: 10px;    /* air between icon and title */
  min-height: 2.6em;                   /* two lines: “Build & Validate” and “Ship & Evolve” fit on one, the other four wrap, and without a reserved box the row read as ragged */
  font-weight: 600; font-size: 1rem; line-height: 1.3;   /* 16px */
  letter-spacing: -0.003em; color: var(--ink, #0c0d12);
  text-wrap: balance;
}
.flow__sub {
  display: block; margin-top: 11px;                    /* clear air between title and subtitle */
  font-size: 1rem; line-height: 1.4; color: #4b5568;   /* a touch more contrast than the numbers */
}

/* draw the flow in on scroll-reveal: line sweeps, steps rise in order, arrows follow */
.bento.reveal .flow::before { transform: scaleX(0); transform-origin: 0 50%; transition: transform .95s cubic-bezier(.55,0,.1,1) .25s; }
.bento.reveal.is-in .flow::before { transform: scaleX(1); }
.bento.reveal .flow__step { opacity: 0; transform: translateY(12px); transition: opacity .7s ease, transform .7s cubic-bezier(.2,.7,.3,1); }
.bento.reveal.is-in .flow__step { opacity: 1; transform: none; }
.bento.reveal .flow__step:nth-child(1) { transition-delay: .42s; }
.bento.reveal .flow__step:nth-child(2) { transition-delay: .60s; }
.bento.reveal .flow__step:nth-child(3) { transition-delay: .78s; }
.bento.reveal .flow__step:nth-child(4) { transition-delay: .96s; }
.bento.reveal .flow__step:nth-child(5) { transition-delay: 1.14s; }
.bento.reveal .flow__step:nth-child(6) { transition-delay: 1.32s; }
.bento.reveal .flow__step:not(:last-child)::after { opacity: 0; transition: opacity .35s ease; }
.bento.reveal.is-in .flow__step:not(:last-child)::after { opacity: 1; }
.bento.reveal .flow__step:nth-child(1)::after { transition-delay: .68s; }
.bento.reveal .flow__step:nth-child(2)::after { transition-delay: .86s; }
.bento.reveal .flow__step:nth-child(3)::after { transition-delay: 1.04s; }
.bento.reveal .flow__step:nth-child(4)::after { transition-delay: 1.22s; }
.bento.reveal .flow__step:nth-child(5)::after { transition-delay: 1.40s; }
@media (prefers-reduced-motion: reduce) {
  .bento.reveal .flow::before,
  .bento.reveal .flow__step,
  .bento.reveal .flow__step:not(:last-child)::after { opacity: 1; transform: none; transition: none; }
  .bento.reveal .flow::before { transform: scaleX(1); }
}

/* ---- live spotlight: the timeline runs itself — one step "on" at a time.
   The ink fill line grows to the active node, the active node inflates to a
   black chip with a white icon and a sweeping progress ring; the rest dim.
   Auto-advances (pauses on hover), click a step to jump. JS adds .is-live. ---- */
.flow::after {                          /* ink progress fill over the base line */
  content: ""; position: absolute; z-index: 0;
  top: calc(var(--flowY) - 1px); height: 2px;
  left: calc(100% / 12); right: calc(100% / 12);
  background: var(--ink, #0c0d12);
  transform: scaleX(var(--flow-fill, 0)); transform-origin: 0 50%;
  transition: transform .8s cubic-bezier(.22, 1, .36, 1);
  opacity: 0;
}
.flow.is-live::after { opacity: 1; }
/* each step is a real button (keyboard + AT); layout passes straight through */
.flow__stepbtn {
  display: block; width: 100%; margin: 0; padding: 0;
  background: none; border: 0; font: inherit; color: inherit;
  text-align: center; cursor: default;
}
.flow.is-live .flow__stepbtn { cursor: pointer; }
.flow__stepbtn:focus-visible { outline: 2px solid var(--ink, #0c0d12); outline-offset: 4px; border-radius: 14px; }
.flow.is-live .flow__num,
.flow.is-live .flow__name,
.flow.is-live .flow__sub { transition: opacity .45s ease, color .45s ease, transform .5s cubic-bezier(.34, 1.56, .5, 1); }
/* clear the inflated node + ring: label floats up, title drops down (desktop line only) */
@media (min-width: 901px) {
  .flow.is-live .flow__step.is-active .flow__num  { transform: translateY(-7px); }
  /* the name is NOT pushed down to match: scale(1.16) grows a 48px node by only
     ~3.9px below centre, and the label already carries 10px of margin, so the old
     translateY(7px) bought clearance that was not needed and left the active step
     sitting a line lower than the other five. */
}
/* three states — upcoming (default, lighter) · completed (.is-done, darker) · active */
.flow.is-live .flow__step .flow__num,
.flow.is-live .flow__step .flow__name { color: rgba(17, 24, 39, .42); }             /* upcoming: lightened ~10% */
.flow.is-live .flow__step .flow__node { color: rgba(17, 24, 39, .38); border-color: rgba(12, 13, 18, .12); }
.flow.is-live .flow__step.is-done .flow__num,
.flow.is-live .flow__step.is-done .flow__name { color: rgba(17, 24, 39, .74); }     /* completed: clearly darker than upcoming */
.flow.is-live .flow__step.is-done .flow__node { color: #2b313c; border-color: rgba(12, 13, 18, .26); }
.flow.is-live .flow__step.is-done:not(:last-child)::after { border-color: var(--ink, #0c0d12); }   /* completed arrows turn ink with the fill line */
.flow.is-live .flow__node {
  transition: background .45s ease, color .45s ease, transform .5s cubic-bezier(.34, 1.56, .5, 1),
              border-color .45s ease, box-shadow .45s ease;
}
.flow.is-live .flow__step:not(.is-active):hover .flow__node { transform: translateY(-2px); }
.flow.is-live .flow__step.is-active .flow__node {
  background: #1c1c1c; color: #fff; border-color: #1c1c1c;
  transform: scale(1.16);
  box-shadow: 0 10px 24px -10px rgba(12, 13, 18, .45);
}
.flow.is-live .flow__step.is-active .flow__num { color: var(--ink, #0c0d12); font-weight: 700; font-size: 1rem; }   /* 16px on active */
.flow.is-live .flow__step.is-active .flow__name { color: var(--ink, #0c0d12); }
/* dwell progress — a thin conic ring sweeping around the active node (JS drives --ring 0→1) */
.flow.is-live .flow__step.is-active .flow__node::before {
  content: ""; position: absolute; inset: -6px; border-radius: 50%;
  background: conic-gradient(#1c1c1c calc(var(--ring, 0) * 1turn), rgba(12, 13, 18, .10) 0);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring-w)), #000 calc(100% - var(--ring-w) + 0.5px));
          mask: radial-gradient(farthest-side, transparent calc(100% - var(--ring-w)), #000 calc(100% - var(--ring-w) + 0.5px));
}
@media (prefers-reduced-motion: reduce) {
  .flow::after { transition: none; }                       /* fill still shows progress, just without motion */
  .flow.is-live .flow__step.is-active .flow__node { transform: none; }
  .flow.is-live .flow__step.is-active .flow__node::before { display: none; }
  .flow.is-live .flow__step.is-active .flow__num,
  .flow.is-live .flow__step.is-active .flow__name { transform: none; }
}

/* ---- detail stage: the active step's full story below the timeline —
   ghost serif number + title + "what" on the left; tools chips + how-AI-carries-it
   on the right. Panels swap with the spotlight. ---- */
.flow-stage { margin-top: clamp(26px, 2.9vw, 36px); }   /* +10px separation from the step labels */
.flow-panel {
  display: none;
  background: #f6f7f9;                       /* same stage grey as the project-card media */
  border-radius: var(--radius-lg, 24px);     /* on-system with every other card */
  padding: clamp(16px, 2vw, 24px) clamp(24px, 2.8vw, 40px);   /* −12px vertical; width unchanged */
  padding: round(clamp(16px, 2vw, 24px), 4px) round(clamp(24px, 2.8vw, 40px), 4px);   /* 24 · 36 @1280 */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: clamp(24px, 3vw, 48px);
  gap: round(clamp(24px, 3vw, 48px), 4px);   /* 40 @1280 */
  align-items: stretch;                      /* divider spans full content height */
}
.flow-panel.is-on { display: grid; animation: flowPanelIn 1.8s cubic-bezier(.22, 1, .36, 1) both; }
@keyframes flowPanelIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: none; } }
.flow-panel__num {
  display: block; line-height: 1;
  font-family: "Averia Serif Libre", var(--display); font-weight: 700;
  font-size: clamp(2.25rem, 3.53vw, 2.75rem);   /* 44px cap @1280 */
  color: transparent; -webkit-text-stroke: 1.5px rgba(12, 13, 18, .30);
}
.flow-panel__title {
  margin: 10px 0 0;
  font-family: "Averia Serif Libre", var(--display); font-weight: 700;
  font-size: clamp(1.375rem, 1.9vw, 1.5rem); line-height: 1.2;   /* 24px cap @1280 */
  letter-spacing: -0.01em; color: var(--ink, #0c0d12);
}
.flow-panel__what {
  margin: 10px 0 0; max-width: 36ch;
  font-family: var(--sans); font-size: 1.125rem; line-height: 1.55;   /* 18px */
  color: #3a4150;
  text-wrap: pretty;   /* steps 01 and 05 ended on a single word — pretty pulls a word down from the line above rather than leaving the orphan */
}
.flow-panel__side { border-left: 1px solid rgba(12, 13, 18, .08); padding-left: clamp(24px, 3vw, 48px); }
.flow-panel__side { padding-left: round(clamp(24px, 3vw, 48px), 4px); }   /* 40 @1280 — matches the panel gap */
.flow-panel__label {
  display: block;
  font-family: var(--sans); font-size: 0.75rem; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--faint, #5a6273);
}
.flow-tools { list-style: none; margin: 12px 0 16px; padding: 0; display: flex; flex-wrap: nowrap; gap: 6px; }
.flow-tools li {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 8px; border-radius: 999px;
  background: #fff; border: 1px solid var(--line, #e7eaf1);
  font-family: var(--sans); font-size: 0.875rem; font-weight: 500; color: var(--ink, #0c0d12);
  white-space: nowrap;
}
.flow-tools li svg { width: 13px; height: 13px; flex: none; }
@media (max-width: 720px) { .flow-tools { flex-wrap: wrap; } }   /* stacked panel can wrap */
.flow-panel__how {
  margin: 0;
  font-family: var(--sans); font-size: 1rem; line-height: 1.55;
  color: #4b5568;
  text-wrap: pretty;   /* same orphan guard as .flow-panel__what — step 03 was ending on “implementation.” alone */
}
@media (max-width: 720px) {
  .flow-panel { grid-template-columns: 1fr; gap: 20px; padding: 22px 20px; }
  .flow-panel__side { border-left: none; padding-left: 0; border-top: 1px solid rgba(12, 13, 18, .08); padding-top: 18px; }
}
@media (prefers-reduced-motion: reduce) {
  .flow-panel.is-on { animation: none; }
}

/* ---- mobile flow header: one step at a time, stepped through with arrows ---- */
.flow-nav { display: none; }
/* ≤900px: the rail hides — the flow becomes one step card at a time, advanced
   by the arrows (or the auto-dwell), like a guided wizard. */
@media (max-width: 900px) {
  .flow { display: none; }
  .flow-nav {
    display: flex; align-items: center; justify-content: space-between;
    margin: 0 0 12px;
  }
  .flow-nav__count {
    font-family: var(--sans); font-size: 0.8125rem; font-weight: 600;
    letter-spacing: .08em; color: var(--faint, #5a6273);
    font-variant-numeric: tabular-nums; flex: none;
  }
  /* story-style progress: six thin segments — done = ink, active fills over the
     dwell (rAF drives --p each frame), upcoming stays light */
  .flow-nav__track { display: flex; flex: 1; gap: 5px; margin: 0 14px; }
  .flow-nav__track span {
    position: relative; overflow: hidden;
    flex: 1; height: 3px; border-radius: 999px;
    background: rgba(12, 13, 18, .08);
  }
  .flow-nav__track span::after {
    content: ""; position: absolute; inset: 0; border-radius: inherit;
    background: #1c1c1c;
    transform: scaleX(var(--p, 0)); transform-origin: 0 50%;
  }
  .flow-nav__track span.is-done::after { transform: scaleX(1); }
  .flow-nav__btns { display: flex; gap: 8px; }
  .flow-nav__btn {
    width: 40px; height: 40px; border-radius: 12px;
    border: 1px solid var(--line, #e7eaf1); background: #fff; color: var(--ink, #0c0d12);
    display: grid; place-items: center; cursor: pointer;
    transition: background .25s ease, color .25s ease;
  }
  .flow-nav__btn:active { background: #1c1c1c; color: #fff; border-color: #1c1c1c; }
  .flow-nav__btn:focus-visible { outline: 2px solid var(--ink, #0c0d12); outline-offset: 2px; }
}
@media (max-width: 900px) and (prefers-reduced-motion: reduce) {
  .flow-nav__track span.is-active::after { transform: scaleX(1); }   /* no dwell animation — show the current segment solid */
}

/* (legacy) old two-column process grid, kept for reference */
.philo__tools {
  margin-top: 22px;
}
.philo__tools .tools__title {
  font-family: "Averia Serif Libre", var(--display);
  font-weight: 700; font-size: clamp(1.1875rem, 1.7vw, 1.4375rem);
  line-height: 1.18; letter-spacing: -0.005em; color: #1c1c1c; margin: 0;
}
.philo__tools .tools__sub { margin: 8px 0 clamp(18px, 2.2vw, 24px); color: var(--muted, #3a4150); font-size: 1rem; line-height: 1.55; }
.philo__tools .tools__grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(2, 1fr); gap: clamp(10px, 1.2vw, 14px);
}
@media (max-width: 520px) {
  .philo__tools .tools__grid { grid-template-columns: 1fr; }
}

/* --- Card 2: capabilities --- */
.caps { display: flex; flex-direction: column; }
.caps__eyebrow {
  font-family: var(--sans); font-size: 0.8125rem; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase; color: var(--faint, #5a6273);
  margin-bottom: clamp(16px, 2vw, 22px);
}
.caps__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; flex: 1; }
.caps__list li {
  flex: 1;
  display: flex; align-items: center; gap: 14px;
  padding: clamp(10px, 1.4vw, 14px) 0;
  font-family: var(--display); font-weight: 600;
  font-size: clamp(1.0625rem, 1.6vw, 1.25rem); color: var(--ink, #0c0d12);
  border-top: 1px solid var(--line, rgba(13,26,64,.08));
}
.caps__list li:first-child { border-top: none; }
.caps__ic {
  flex: none; width: 38px; height: 38px; border-radius: 12px;
  display: grid; place-items: center;
  background: var(--ink, #0c0d12); color: #fff;
}
.caps__ic svg { width: 19px; height: 19px; }

/* --- horizontal services card (below the workflow): aligned 4-column grid --- */
.caps--row { padding: clamp(20px, 2.4vw, 28px) clamp(24px, 3vw, 40px); }
.caps--row .caps__list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);   /* 4 in row one, remaining 3 land on the same column lines */
  gap: 6px clamp(16px, 2.4vw, 32px);
}
.caps--row .caps__list li {
  flex: none;
  border-top: none;
  padding: 4px 0;
  gap: 12px;
  font-size: 1.0625rem;
}
.caps--row .caps__ic { width: 34px; height: 34px; border-radius: var(--radius-sm); }
.caps--row .caps__ic svg { width: 17px; height: 17px; }
.caps--row .caps__eyebrow { margin-bottom: 10px; }
@media (max-width: 900px) {
  .caps--row .caps__list { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .caps--row .caps__list { grid-template-columns: 1fr; }
}

/* --- Card 3: tools (horizontal) --- */
.tools {
  display: grid; grid-template-columns: minmax(220px, 0.8fr) 2fr;
  gap: clamp(24px, 3.4vw, 48px); align-items: start;
}
.tools__title {
  font-family: "Averia Serif Libre", var(--display);
  font-weight: 700; font-size: clamp(1.375rem, 2.2vw, 1.75rem);
  line-height: 1.14; letter-spacing: -0.01em; color: var(--ink, #0c0d12); margin: 0;
}
.tools__sub { margin: 12px 0 0; color: var(--muted, #3a4150); font-size: 1rem; line-height: 1.55; }
.tools__grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: clamp(12px, 1.4vw, 16px);
}
.btool {
  display: flex; align-items: center; gap: 12px;
  padding: 14px; border-radius: var(--radius, 16px);
  border: 1px solid var(--line, rgba(13,26,64,.10));
  transition: transform .25s var(--ease, ease), box-shadow .25s, border-color .25s;
}
.btool:hover { transform: translateY(-2px); box-shadow: 0 10px 22px -14px rgba(13,26,64,.4); border-color: var(--line-2, rgba(12,13,18,.16)); }
.btool__ic {
  flex: none; width: 36px; height: 36px; border-radius: var(--radius-sm);
  display: grid; place-items: center;
  background: #f6f7f9; border: 1px solid var(--line); color: var(--muted, #3a4150);
}
.btool__ic svg { width: 18px; height: 18px; }
.btool__txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.btool__name { font-weight: 600; font-size: 1.0625rem; letter-spacing: -0.003em; color: var(--ink, #0c0d12); }
.btool__tag {
  font-size: 1rem; font-weight: 400; letter-spacing: .01em;   /* 16px floor — matches the AI-strip note */
  color: var(--faint, #5a6273); line-height: 1.4;
}

@media (max-width: 860px) {
  .bento { grid-template-columns: 1fr; }      /* stack the two vertical cards on small screens */
  .tools { grid-template-columns: 1fr; }
  .tools__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 520px) {
  .tools__grid { grid-template-columns: 1fr; }
}

/* =====================================================================
   ON-LOAD HOVER TEASER  → each of the four interactive buttons replays
   its hover animation ONCE on landing (in → brief hold → out), so the
   motion is discoverable. Fires ~1.4s in, after the hero entrance.
   Pure CSS (no fill) so each element returns to rest and :hover still works.
   ===================================================================== */
@media (prefers-reduced-motion: no-preference) {
  /* shared timeline: rest → peak (hold) → rest */
  @keyframes demoRise    { 0%,18% { transform: translateY(101%); }            48%,60% { transform: translateY(0); }                       88%,100% { transform: translateY(101%); } }
  @keyframes demoFlipOut { 0%,18% { transform: translateY(0); }               48%,60% { transform: translateY(-120%); }                   88%,100% { transform: translateY(0); } }
  @keyframes demoFlipIn  { 0%,18% { transform: translateY(120%); }            48%,60% { transform: translateY(0); }                       88%,100% { transform: translateY(120%); } }
  @keyframes demoIcoFlip { 0%,18% { transform: rotateX(0); }                  48%,60% { transform: rotateX(-360deg); }                    88%,100% { transform: rotateX(0); } }
  @keyframes demoCopyIco { 0%,18% { transform: rotateX(0) scale(1); }         48%,60% { transform: rotateX(-360deg) scale(1.06); }        88%,100% { transform: rotateX(0) scale(1); } }
  @keyframes demoPop     { 0%,18% { transform: translateY(0) scale(1); }      48%,60% { transform: translateY(-3px) scale(1.05); box-shadow: 0 14px 28px -12px rgba(23,24,28,.7); }  88%,100% { transform: translateY(0) scale(1); } }
  @keyframes demoArrow   { 0%,18% { width: 0; opacity: 0; margin-left: 0; transform: translateX(-6px); }   48%,60% { width: 17px; opacity: 1; margin-left: 7px; transform: translateX(0); }   88%,100% { width: 0; opacity: 0; margin-left: 0; transform: translateX(-6px); } }

  /* Resume — the dark panel rises up and back (quick, finishes during the hero load) */
  .nav__links a.nav__cta::before     { animation: demoRise 1s cubic-bezier(.4,0,.2,1) .2s 1; }
  /* nav text labels (Work/Experience/Process/About) no longer replay the flip on load */
}

/* =====================================================================
   HOW I THINK  → left: serif heading + an accordion of principles;
   right: a sticky LinkedIn connect card. Black/grey, on-system tokens.
   ===================================================================== */
.think {
  display: grid; grid-template-columns: minmax(0, 1.12fr) minmax(0, 0.88fr);
  gap: clamp(20px, 3vw, 48px); align-items: start;
}
.think__title {
  font-family: "Averia Serif Libre", var(--display);
  font-weight: 700; line-height: 1.16; letter-spacing: -0.01em;
  font-size: clamp(1.875rem, 3.4vw, 2.6rem);
  color: #1c1c1c; margin: 0 0 clamp(28px, 4vw, 52px);
}

/* left column: heading + accordion live in one card */
.think__main {
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: 0 1px 2px rgba(28, 28, 28, .04), 0 10px 28px -22px rgba(28, 28, 28, .22);
  overflow: hidden;
}
.think-head { padding: clamp(24px, 3vw, 34px) clamp(18px, 2vw, 26px) clamp(16px, 2vw, 22px); }
.think-head .think__title { margin: 0; }
.think-head .sec-box-head__sub { margin-top: clamp(10px, 1.4vw, 14px); }

/* ---- accordion: flat rows divided by hairlines inside the card ---- */
.faq { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0; }
.faq__item {
  border: none; border-top: 1px solid var(--line); border-radius: 0;
  background: transparent; overflow: hidden;
  transition: background .25s var(--ease);
}
.faq__item.is-open { box-shadow: none; }
.faq__item:hover { background: rgba(13, 26, 64, .02); }
.faq__q {
  width: 100%; display: flex; align-items: center; gap: 16px;
  padding: clamp(16px, 1.8vw, 22px) clamp(18px, 2vw, 26px);
  background: none; border: none; cursor: pointer; text-align: left;
  font-family: var(--display); font-weight: 600; color: var(--ink);
}
.faq__num { font-family: var(--mono, var(--display)); font-weight: 700; font-size: 0.9375rem; color: rgba(31, 41, 55, .42); flex: none; }
.faq__item.is-open .faq__num { display: none; }
.faq__t { flex: 1; font-size: clamp(1.0625rem, 1.4vw, 1.1875rem); }
.faq__q:focus-visible { outline: 2px solid var(--ink, #0c0d12); outline-offset: -2px; border-radius: 10px; }
.faq__ic { position: relative; flex: none; width: 22px; height: 22px; color: var(--ink); }   /* box (hit area) unchanged */
.faq__ic::before, .faq__ic::after {
  content: ""; position: absolute; background: currentColor; border-radius: 2px;
  transition: transform .3s var(--ease), opacity .3s var(--ease);
}
.faq__ic::before { left: 4px; right: 4px; top: 10px; height: 2px; }            /* horizontal bar — ~12% shorter */
.faq__ic::after  { top: 4px; bottom: 4px; left: 10px; width: 2px; }            /* vertical bar (hidden when open → minus) */
.faq__item.is-open .faq__ic::after { transform: scaleY(0); opacity: 0; }
/* answer: animate grid-rows 0fr → 1fr so it expands smoothly with no JS measuring */
.faq__a { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .4s var(--ease); }
.faq__item.is-open .faq__a { grid-template-rows: 1fr; }
.faq__a-in { overflow: hidden; }
.faq__a-in p {
  margin: 0; padding: 0 clamp(18px, 2vw, 26px) clamp(18px, 2vw, 24px);
  font-family: var(--sans); font-weight: 400; font-size: 1.0625rem; line-height: 1.45; color: #2a3142;
}

/* ---- LinkedIn connect card ---- */
.connect {
  position: sticky; top: 96px;
  border: 1px solid var(--line); border-radius: var(--radius-lg); background: #fff;
  padding: clamp(28px, 3vw, 40px);
  box-shadow: 0 2px 6px -4px rgba(13,26,64,.10);
  display: flex; flex-direction: column; justify-content: center;   /* content centres in the card height */
}
.connect__avatar {
  display: block; width: 92px; height: 92px; border-radius: 50%; overflow: hidden;   /* +9% */
  background: radial-gradient(circle at 50% 35%, #3a3a3a, #1c1c1c);
  margin-bottom: clamp(12px, 1.6vw, 18px);   /* tighter to the heading */
}
.connect__avatar img { width: 100%; height: 100%; object-fit: cover; filter: grayscale(1); }
.connect__avatar.is-empty { display: block; }
.connect__title {
  font-family: "Averia Serif Libre", var(--display);
  font-weight: 700; line-height: 1.2; letter-spacing: -0.01em;
  font-size: clamp(1.375rem, 2vw, 1.75rem); color: #1d1f24; margin: 0 0 12px;
}
.connect__txt { font-family: var(--sans); font-weight: 400; font-size: 1.0625rem; line-height: 1.45; color: #2a3142; margin: 0 0 clamp(20px, 2.4vw, 28px); max-width: 30ch; }
.connect__btn.btn { height: 48px; padding: 0 22px; gap: 10px; font-size: 1.0625rem; }
.connect__li { width: 19px; height: 19px; flex: none; }

@media (max-width: 860px) {
  .think { grid-template-columns: 1fr; }
  .connect { position: static; }
}

/* =====================================================================
   EXPERIENCE  → timeline list: company badge · role + dates · blurb
   ===================================================================== */
.exp-title {
  font-family: "Averia Serif Libre", var(--display);
  font-weight: 700; line-height: 1.16; letter-spacing: -0.01em;
  font-size: clamp(1.625rem, 1.325rem + 1.16vw, 2.25rem); color: #1c1c1c;   /* 36px cap — shared section-heading step */
  margin: 0 0 clamp(28px, 4vw, 52px);
}

/* section intro card: eyebrow + heading + subtitle, so the title doesn't float on the page bg */
.sec-box-head {
  background: var(--surface, #fff);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg, 24px);
  padding: clamp(18px, 2vw, 24px) clamp(28px, 3.4vw, 44px);
  box-shadow: 0 1px 2px rgba(28, 28, 28, .04), 0 10px 28px -22px rgba(28, 28, 28, .22);
  margin: 0 0 clamp(14px, 1.8vw, 20px);
}
.sec-box-head .exp-title,
.sec-box-head .think__title { margin: 0; }
.sec-box-head__eyebrow {
  display: block;
  font-family: var(--sans); font-size: 0.8125rem; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase; color: var(--faint, #5a6273);
  margin-bottom: clamp(10px, 1.4vw, 14px);
}
.sec-box-head__sub {
  margin: clamp(12px, 1.6vw, 16px) 0 0;
  font-family: var(--sans); font-weight: 400; font-size: 1.125rem;   /* 18px */
  line-height: 1.5; color: #2a3142; max-width: 62ch;
  text-wrap: normal;   /* greedy: fill line 1 as far as it goes */
}
/* one card holding the heading + every role, separated by hairline dividers */
.exp-list {
  display: flex; flex-direction: column;
  background: var(--surface, #fff);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg, 24px);
  box-shadow: 0 1px 2px rgba(28, 28, 28, .04), 0 10px 28px -22px rgba(28, 28, 28, .22);
  overflow: hidden;
}
.exp-head { padding: clamp(24px, 3vw, 36px) clamp(24px, 3vw, 40px) clamp(20px, 2.4vw, 28px); }
.exp-head { padding: round(clamp(24px, 3vw, 36px), 4px) round(clamp(24px, 3vw, 40px), 4px) round(clamp(20px, 2.4vw, 28px), 4px); }   /* 36 · 40 · 28 @1280 */
/* content cascade: head first, then the roles top-to-bottom (hero-style) */
.exp-list.reveal .exp-head,
.exp-list.reveal .exp-row {
  opacity: 0; transform: translateY(16px);
  transition: opacity .9s ease, transform .9s cubic-bezier(.22, 1, .36, 1);
}
.exp-list.reveal.is-in .exp-head,
.exp-list.reveal.is-in .exp-row { opacity: 1; transform: none; }
/* slower, wider stagger — the list is tall, so each row gets a longer, more
   visible arrival as the cascade travels down */
.exp-list.reveal.is-in .exp-head { transition-delay: .10s; }
.exp-list.reveal.is-in .exp-row:nth-of-type(1) { transition-delay: .34s; }
.exp-list.reveal.is-in .exp-row:nth-of-type(2) { transition-delay: .56s; }
.exp-list.reveal.is-in .exp-row:nth-of-type(3) { transition-delay: .78s; }
.exp-list.reveal.is-in .exp-row:nth-of-type(4) { transition-delay: 1.00s; }
.exp-list.reveal.is-in .exp-row:nth-of-type(5) { transition-delay: 1.22s; }
.exp-list.reveal.is-in .exp-row:nth-of-type(6) { transition-delay: 1.44s; }
@media (prefers-reduced-motion: reduce) {
  .exp-list.reveal .exp-head, .exp-list.reveal .exp-row { transition: none; opacity: 1; transform: none; }
}
.exp-head .exp-title { margin: 0; }
/* the shared 62ch cap wrapped this one-sentence summary onto a second line even
   though the card is wide enough to hold it — lift the cap here only */
.exp-head .sec-box-head__sub { margin-top: clamp(10px, 1.4vw, 14px); max-width: none; }

/* Centred variant: the heading is PAINTED as the section label (type comes from
   the shared rule above), the summary stays at its own size below it. The h2 is
   still the h2 -- it is the section's name in the document outline and what the
   #experience anchor and the scroll-spy target.
   This block has to stay BELOW the .exp-head rules above: they carry the same
   0,2,0 specificity, so written higher it would silently lose on source order.
   Same trap as .qcs .q-cover. */
.exp-head--center { text-align: center; }
.exp-head--center .exp-title { margin: 0 auto; max-width: 22ch; }
.exp-head--center .sec-box-head__sub { margin-top: 0; margin-inline: auto; }
/* The head's 28px bottom padding was sized for a heading PLUS a deck. With only
   the label left it opened a 40px gap to the first role, against the notes
   card's 20px doing the same job. 8px brings the two sections level. */
/* Deck -> first row. Was clamp(6px,.7vw,8px) = 8px, left from before this
   section became a timeline; against five tall rows it read as the deck being
   stuck to the list. This is the SAME expression .flow uses for the process
   card heading -> timeline gap, so the two sections track together across
   viewports: 36px @1280, 32 @1100, 24 at the floor. */
.exp-head--center { padding-bottom: clamp(24px, calc(2.2vw + 6px), 36px); }
.exp-head--center { padding-bottom: round(clamp(24px, calc(2.2vw + 6px), 36px), 4px); }
.exp-row {
  display: grid;
  /* The node's CENTRE, measured from the row's top. The year's padding, the
     node's offset and both ends of the rail are all derived from it below, so
     moving the dot is a one-number change and the four cannot drift apart.
     24px is the 48px logo tile's centre. */
  --exp-node: 24px;
  /* year | rail | logo | role | description */
  grid-template-columns: 56px 24px 48px minmax(200px, 320px) 1fr;
  gap: 24px; align-items: start;
  padding: 0 clamp(24px, 3vw, 40px) 40px;
  padding: 0 round(clamp(24px, 3vw, 40px), 4px) 40px;   /* 40 @1280 */
  /* No separator: in this design the spine IS the divider, and running both
     would draw two rules down the same list. */
  border-top: none;
  background-image: none;
  transition: background-color .25s var(--ease, ease);
}
/* the last row keeps its 40px, which IS the card bottom inset -- the card
   itself has no padding, so zeroing it puts the closing sentence on the
   border. Its spine is already capped above, so nothing dangles. */

/* the year, tracked out so a column of digits reads as a scale rather than a
   list of numbers. 12px is the caption step -- the Figma sets 14 but the column
   is only 56 wide and 14 crowds it at the narrow end of the clamp. */
.exp-year {
  font-family: var(--sans); font-size: 0.75rem; font-weight: 700;
  letter-spacing: .1em; line-height: 1.3;
  color: var(--faint, #5a6273); text-align: right;
  /* 16px. The numerals ink centroid sits 7.8px below the line box top --
     measured by COVERAGE INTEGRATION, weighting each pixel by how far below
     white it sits. An earlier threshold scan said 7.25 and was wrong: a hard
     threshold clips an antialiased glyph edge asymmetrically and is worth
     about a pixel of ruler noise, the same trap recorded for the hero avatar
     ring. Keep this a WHOLE number -- rows are 91.297px tall, so a fractional
     padding rounds up on some rows and down on others and staggers the year
     column by one device pixel at DPR 1. */
  padding-top: calc(var(--exp-node) - 8px);
  font-variant-numeric: tabular-nums;   /* so the column edge stays straight */
}
.exp-row:first-child .exp-year { color: #1c1c1c; }

/* the rail: a 2px line with a node sitting on it. The node carries the row's
   own background so it masks the line behind it rather than being drawn over a
   gap -- a gap would have to be re-measured every time the rhythm changes. */
.exp-spine { position: relative; align-self: stretch; min-height: 28px; }
.exp-spine::before {
  content: ''; position: absolute; left: 50%; transform: translateX(-1px);
  width: 2px; top: 0; bottom: -40px; background: var(--line, #e7eaf1);
}
/* Both ends of the rail terminate ON a node rather than running past it.
   The tail was always capped; the head needs it now that the node has moved
   down, or an 18px stub reads as "the timeline continues above 2026". */
.exp-row:first-child .exp-spine::before { top: var(--exp-node); }
.exp-row:last-child:not(:first-child) .exp-spine::before {
  bottom: auto; height: calc(var(--exp-node) + 6px);
}
.exp-spine::after {
  content: ''; position: absolute; left: 50%; transform: translateX(-50%);
  top: calc(var(--exp-node) - 6px);   /* the node is 12px, so this centres it on --exp-node */
  width: 12px; height: 12px; border-radius: 50%;
  background: #fff; box-shadow: inset 0 0 0 2px var(--line-2, rgba(12, 13, 18, .18));
}
.exp-row:first-child .exp-spine::after { background: #1c1c1c; box-shadow: none; }

.exp-row:hover { background-color: rgba(13, 26, 64, .025); }   /* -color, not the shorthand: that would drop the separator above */
.exp-logo {
  width: 48px; height: 48px; border-radius: var(--radius-sm); flex: none;   /* matches the 48px process nodes */
  display: grid; place-items: center;
  background: #1c1c1c; color: #fff;
  font-family: var(--display); font-weight: 700; font-size: 1.125rem; line-height: 1;
  box-shadow: 0 4px 12px -5px rgba(28, 28, 28, .5);
}
/* image variant — real brand marks (extracted square avatars) instead of a letter */
.exp-logo--img { background: #fff; overflow: hidden; }
.exp-logo--img img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; display: block; }
.exp-role__title {
  font-family: var(--sans); font-weight: 700; line-height: 1.25;
  font-size: 1.125rem; color: #1c1c1c;                           /* 18px — one step above the 17px step names */
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
/* how long the role ran. The Figma sets 11px; this site's type scale has a
   12px floor and the audit flags anything under it, so it ships at the caption
   step instead. */
.exp-dur {
  font-size: 0.75rem; font-weight: 700; letter-spacing: .04em;
  line-height: 1.2; text-transform: uppercase;
  color: #1c1c1c; background: #f6f7f9;
  border-radius: 999px; padding: 4px 8px;
}
.exp-role__meta { font-family: var(--sans); font-size: 1rem; font-weight: 500; color: var(--muted); margin-top: 4px; }   /* 16px */
.exp-sep { margin: 0 8px; color: var(--faint); }
.exp-desc {
  font-family: var(--sans); font-weight: 400; font-size: 1rem; line-height: 1.5;   /* 16px — matches the process how-lines */
  color: #2a3142; max-width: 56ch; margin: 0;
}

/* wrapper is invisible on desktop — rows keep flowing in the .exp-list column */
.exp-rows { display: contents; }

/* 721-1024: the description stops competing for the last column.
   The timeline spends 128px on the year, the rail and their gaps, so below
   about 1024 the 1fr description column starves -- measured 218px at 900 and
   86px at 760, against 346px before the timeline existed. It drops under the
   role instead, aligned to the role text rather than to the year, so the rail
   survives on a tablet. */
@media (max-width: 1024px) and (min-width: 721px) {
  .exp-row { grid-template-columns: 56px 24px 48px 1fr; row-gap: 12px; }
  .exp-desc { grid-column: 4 / -1; }
  /* The description takes a SECOND grid track here, and align-self:stretch only
     stretches the spine over the track it occupies -- so the rail stopped 60px
     (84px at 721-760) short of the next dot and the timeline rendered as five
     disconnected segments. Span it across both tracks. */
  /* span 2, NOT 1 / -1: these row tracks are implicit (no grid-template-rows),
     and -1 resolves against the EXPLICIT grid, which has no row lines -- so
     1 / -1 silently collapses to a single track and the spine does not span. */
  /* The COLUMN must be pinned too. Giving the spine a definite row but an
     auto column moves it into grid placement pass 2, where it claimed column 1
     and pushed the year into the 24px rail column. */
  .exp-spine { grid-column: 2; grid-row: 1 / span 2; }
}

@media (max-width: 720px) {
  .exp-row { grid-template-columns: 48px 1fr; row-gap: 14px; }
  .exp-desc { grid-column: 1 / -1; }
  /* the year rail is a desktop device. Below 720 the row is already two
     columns, so a year and a spine would take the logo and role slots and
     push everything onto its own line. The year is not lost: every row meta
     line already reads "Company · May 2026 - Present". */
  .exp-year, .exp-spine { display: none; }
}

@media (max-width: 620px) {
  /* experience on mobile: the six roles as a horizontal swipe row of cards
     inside the box — one card of scroll height instead of six stacked
     (same pattern as the snapshot stats) */
  .exp-rows {
    display: flex; gap: 12px;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    padding: 4px 16px 20px;
  }
  .exp-rows::-webkit-scrollbar { display: none; }
  .exp-row {
    flex: 0 0 78%; max-width: 300px;
    scroll-snap-align: center;
    display: flex; flex-direction: column; align-items: flex-start; gap: 12px;
    background: #fff; border: 1px solid var(--line); border-radius: 16px;
    padding: 18px 16px;
  }
  .exp-row, .exp-row:first-child { background-image: none; }   /* cards keep a full border; no separator between them */
  /* the year rail is a desktop device: these are separate cards in a swipe
     row, so there is no continuous column for a spine to run down */
  .exp-year, .exp-spine { display: none; }
  .exp-head { padding-bottom: 12px; }
  /* ...and the centred variant keeps its tighter value here: this block is later
     in the sheet at the same 0,1,0, so the clamp above would otherwise be lost. */
  .exp-head--center { padding-bottom: 24px; }   /* the clamp floor, so the swipe row is not welded to the deck */
}

/* STACK: title + a single small row of tool tiles, all in one card */
.stack-card {
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius-lg, 24px);
  box-shadow: 0 1px 2px rgba(28, 28, 28, .04), 0 10px 28px -22px rgba(28, 28, 28, .22);
  padding: clamp(22px, 2.6vw, 32px) clamp(24px, 3vw, 40px);
}
.stack-card__title {
  font-family: "Averia Serif Libre", var(--display); font-weight: 700;
  line-height: 1.16; letter-spacing: -0.01em; color: #1c1c1c;
  font-size: clamp(1.875rem, 3.4vw, 2.6rem);
  margin: 0 0 clamp(18px, 2.2vw, 26px);
}
.stack-grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(16, 1fr); gap: clamp(8px, 1vw, 12px);
}
.stack-ic {
  aspect-ratio: 1; border-radius: 12px;
  background: #f6f7f9; border: 1px solid var(--line);
  display: grid; place-items: center; color: #1c1c1c;
  transition: transform .25s var(--ease, ease), box-shadow .25s, border-color .25s, background .25s;
}
.stack-ic:hover {
  transform: translateY(-3px); background: #fff;
  border-color: var(--line-2, rgba(28, 28, 28, .14));
  box-shadow: 0 2px 4px rgba(28, 28, 28, .05), 0 14px 28px -18px rgba(28, 28, 28, .4);
}
.stack-ic svg { width: clamp(20px, 1.9vw, 24px); height: clamp(20px, 1.9vw, 24px); display: block; }
@media (max-width: 900px) { .stack-grid { grid-template-columns: repeat(8, 1fr); } }
@media (max-width: 480px) { .stack-grid { grid-template-columns: repeat(6, 1fr); } }

/* =====================================================================
   SHIPPING  → two columns of icon + title + blurb (posts | plugins)
   ===================================================================== */
.ship { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(28px, 5vw, 72px); align-items: start; }
.ship-col { display: flex; flex-direction: column; gap: clamp(14px, 1.8vw, 22px); }
.ship-item { display: flex; gap: 16px; align-items: flex-start; }
.ship-ico {
  width: 46px; height: 46px; flex: none; border-radius: var(--radius-sm);
  background: var(--c, #e7ebf3); background-size: cover;
  box-shadow: 0 3px 10px -5px rgba(13,26,64,.3);
  display: grid; place-items: center; color: #fff; font-size: 1.1rem;
}
.ship-ico--dark { background: #1c1c1c; }
.ship-ico--fig  { background: #1c1c1c; }
.ship-title { font-family: var(--sans); font-weight: 700; font-size: 1.0625rem; line-height: 1.3; color: #1c1c1c; }
.ship-title--sm { font-size: 1rem; }
.ship-tag { font-weight: 500; color: var(--faint); }
.ship-desc { font-family: var(--sans); font-size: 0.9375rem; line-height: 1.45; color: var(--muted); margin-top: 4px; }


/* =====================================================================
   BIO  → about + stats + portrait
   ===================================================================== */
.bio { display: grid; grid-template-columns: 1.25fr 1fr; gap: clamp(32px, 5vw, 80px); align-items: start; }
.bio__label { font-family: var(--mono); font-size: 0.75rem; letter-spacing: .14em; text-transform: uppercase; color: var(--faint); }
.bio__title { font-family: "Averia Serif Libre", var(--display); font-weight: 700; font-size: clamp(1.625rem, 2.8vw, 2.25rem); color: #1c1c1c; line-height: 1.18; letter-spacing: -0.01em; margin: 12px 0 18px; }
.bio__p { font-family: var(--sans); font-size: 1.0625rem; line-height: 1.6; color: var(--muted); margin-bottom: 16px; max-width: 56ch; }
.bio__p:last-child { margin-bottom: 0; }
.bio__side { display: flex; flex-direction: column; gap: clamp(20px, 2.4vw, 30px); }
.bio__num { font-family: "Averia Serif Libre", var(--display); font-weight: 700; font-size: clamp(1.5rem, 2.2vw, 1.875rem); color: #1c1c1c; display: block; }
.bio__stat-txt { font-family: var(--sans); font-size: 1rem; line-height: 1.5; color: var(--muted); margin-top: 6px; max-width: 36ch; }
.bio__photo { display: block; width: 100%; aspect-ratio: 16 / 11; border-radius: var(--radius); background: linear-gradient(135deg, #c9ced9, #8b93a5); }

/* =====================================================================
   WRITING  → article list with thumbnails
   ===================================================================== */
.writing__head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; border-bottom: 1px solid var(--line); padding-bottom: 18px; margin-bottom: clamp(8px, 1.4vw, 16px); }
.writing__title { font-family: "Averia Serif Libre", var(--display); font-weight: 700; font-size: clamp(1.875rem, 3.2vw, 2.5rem); color: #1c1c1c; letter-spacing: -0.01em; }
.writing__all { display: inline-flex; align-items: center; gap: 6px; font-family: var(--sans); font-size: 0.9375rem; font-weight: 500; color: var(--muted); }
.writing__all:hover { color: #1c1c1c; }
.writing__list { display: flex; flex-direction: column; }
.wr-row { display: grid; grid-template-columns: 40px 1fr 132px; gap: clamp(16px, 2.4vw, 40px); align-items: center; padding: clamp(20px, 2.4vw, 30px) 0; border-bottom: 1px solid var(--line); transition: background .25s var(--ease); }
.wr-row:hover { background: rgba(28,28,28,.02); }
.wr-num { font-family: var(--mono); font-size: 0.8125rem; color: var(--faint); }
.wr-cat { font-family: var(--mono); font-size: 0.6875rem; letter-spacing: .12em; text-transform: uppercase; color: var(--faint); }
.wr-title { font-family: var(--sans); font-weight: 700; font-size: clamp(1.0625rem, 1.5vw, 1.25rem); color: #1c1c1c; line-height: 1.3; margin: 6px 0; }
.wr-desc { font-family: var(--sans); font-size: 0.9375rem; line-height: 1.45; color: var(--muted); max-width: 60ch; }
.wr-thumb { display: block; width: 132px; height: 80px; border-radius: 12px; background: var(--c, #d7dbe4); box-shadow: 0 6px 16px -8px rgba(13,26,64,.3); }

/* =====================================================================
   SNAPSHOT  → dark intro card + headline stats grid
   ===================================================================== */
.snap { display: grid; grid-template-columns: minmax(280px, 0.9fr) 1.6fr; gap: clamp(20px, 2.6vw, 32px); align-items: stretch; }
.snap-intro { background: #1c1c1c; color: #fff; border-radius: var(--radius-lg); padding: clamp(22px, 2.2vw, 28px); display: flex; flex-direction: column; justify-content: space-between; height: 100%; }   /* space-between splits any slack from the taller stat column into the two gaps between signature, lead and list instead of pooling under the list */
/* signature image (white ink on transparent — sits directly on the dark card) */
.snap-intro__sign { display: block; margin-bottom: clamp(10px, 1.2vw, 14px); }
.snap-intro__sign img { width: clamp(106px, 12.3vw, 132px); height: auto; display: block; }   /* −12% */
/* ---- snapshot entrance: cards rise one by one (hero-style stagger) ----
   The wrapper stops animating as a single block; each card gets its own rise. */
.snap.reveal { opacity: 1; transform: none; transition: none; }
.snap.reveal .snap-intro,
.snap.reveal .snap-stat {
  opacity: 0; transform: translateY(22px);
  transition: opacity .85s cubic-bezier(.22, 1, .36, 1), transform .85s cubic-bezier(.22, 1, .36, 1);
}
.snap.reveal.is-in .snap-intro,
.snap.reveal.is-in .snap-stat { opacity: 1; transform: none; }
.snap.reveal.is-in .snap-intro { transition-delay: .06s; }
.snap.reveal.is-in .snap-pair .snap-stat:nth-of-type(1) { transition-delay: .28s; }
.snap.reveal.is-in .snap-pair .snap-stat:nth-of-type(2) { transition-delay: .46s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(1) { transition-delay: .64s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(2) { transition-delay: .78s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(3) { transition-delay: .92s; }
/* inner cascade — hero-style: each card lands, then its number rises, then its
   lines follow one by one (delays = card delay + .14 / .24 / .34) */
.snap.reveal .snap-num,
.snap.reveal .snap-stat-list li {
  opacity: 0; transform: translateY(10px);
  transition: opacity .7s ease, transform .7s cubic-bezier(.22, 1, .36, 1);
}
.snap.reveal.is-in .snap-num,
.snap.reveal.is-in .snap-stat-list li { opacity: 1; transform: none; }
.snap.reveal.is-in .snap-pair .snap-stat:nth-of-type(1) .snap-num { transition-delay: .54s; }
.snap.reveal.is-in .snap-pair .snap-stat:nth-of-type(1) .snap-stat-list li:nth-child(1) { transition-delay: .70s; }
.snap.reveal.is-in .snap-pair .snap-stat:nth-of-type(1) .snap-stat-list li:nth-child(2) { transition-delay: .86s; }
.snap.reveal.is-in .snap-pair .snap-stat:nth-of-type(2) .snap-num { transition-delay: .72s; }
.snap.reveal.is-in .snap-pair .snap-stat:nth-of-type(2) .snap-stat-list li:nth-child(1) { transition-delay: .88s; }
.snap.reveal.is-in .snap-pair .snap-stat:nth-of-type(2) .snap-stat-list li:nth-child(2) { transition-delay: 1.04s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(1) .snap-num { transition-delay: .90s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(1) .snap-stat-list li:nth-child(1) { transition-delay: 1.06s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(1) .snap-stat-list li:nth-child(2) { transition-delay: 1.22s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(2) .snap-num { transition-delay: 1.04s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(2) .snap-stat-list li:nth-child(1) { transition-delay: 1.20s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(2) .snap-stat-list li:nth-child(2) { transition-delay: 1.36s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(3) .snap-num { transition-delay: 1.18s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(3) .snap-stat-list li:nth-child(1) { transition-delay: 1.34s; }
.snap.reveal.is-in .snap-impacts .snap-stat:nth-of-type(3) .snap-stat-list li:nth-child(2) { transition-delay: 1.50s; }
@media (prefers-reduced-motion: reduce) {
  .snap.reveal .snap-intro, .snap.reveal .snap-stat,
  .snap.reveal .snap-num, .snap.reveal .snap-stat-list li { transition: none; opacity: 1; transform: none; }
}

/* "signed by hand" entrance — signature writes in left-to-right, lead rises, bullets cascade */
.snap.reveal .snap-intro__sign img {
  clip-path: inset(0 100% 0 0);
  transition: clip-path .9s cubic-bezier(.55, 0, .1, 1) .15s;
}
.snap.reveal.is-in .snap-intro__sign img { clip-path: inset(0 -8% 0 0); }
.snap.reveal .snap-intro__lead,
.snap.reveal .snap-intro__list li {
  opacity: 0; transform: translateY(10px);
  transition: opacity .75s ease, transform .75s cubic-bezier(.22, 1, .36, 1);
}
.snap.reveal.is-in .snap-intro__lead { opacity: 1; transform: none; transition-delay: .70s; }
.snap.reveal.is-in .snap-intro__list li { opacity: 1; transform: none; }
.snap.reveal.is-in .snap-intro__list li:nth-child(1) { transition-delay: 1.00s; }
.snap.reveal.is-in .snap-intro__list li:nth-child(2) { transition-delay: 1.16s; }
.snap.reveal.is-in .snap-intro__list li:nth-child(3) { transition-delay: 1.32s; }
@media (prefers-reduced-motion: reduce) {
  .snap.reveal .snap-intro__sign img { transition: none; clip-path: none; }
  .snap.reveal .snap-intro__lead, .snap.reveal .snap-intro__list li { transition: none; opacity: 1; transform: none; }
}
.snap-intro__lead {
  font-family: "Averia Serif Libre", var(--display); font-style: normal; font-weight: 700;   /* matches headings */
  font-size: clamp(1.125rem, 1.563vw, 1.25rem); line-height: 1.35; color: #fff;   /* 20px cap (lands 20 @1280) */
  margin: 0 0 clamp(20px, 2vw, 28px); text-wrap: balance;
  margin-bottom: round(clamp(20px, 2vw, 28px), 4px);   /* 24 @1280 */
}
.snap-intro__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: clamp(8px, 1vw, 10px); }
.snap-intro__list li {
  position: relative; padding-left: 22px;
  font-family: var(--sans); font-weight: 400; font-size: 1rem; line-height: 1.45;   /* 16px */
  color: rgba(255,255,255,.72);
}
.snap-intro__list li::before {
  content: ""; position: absolute; left: 2px; top: .55em;
  width: 7px; height: 7px; border-radius: 50%; background: rgba(255,255,255,.55);
}
.snap-intro__btn.btn { margin-top: auto; align-self: flex-start; background: #fff; color: #1c1c1c; height: 44px; padding: 0 22px; }
/* ONE box, not five. gap goes to 0 so the cells sit flush and the fading rules
   below read as dividers inside a single card rather than gutters between
   cards. The cells' own padding keeps content clear of those rules. */
/* TWO boxes: the top pair and the bottom three. The container carries no card
   look -- it just stacks them and keeps the rows equal height, so the stat
   column still ends level with the dark card beside it. */
.snap-stats {
  display: grid; grid-template-columns: 1fr; grid-template-rows: 1fr 1fr;
  gap: clamp(12px, 1.4vw, 16px);
}
.snap-stat { background: none; border: 0; border-radius: 0; padding: clamp(16px, 1.9vw, 24px); box-shadow: none; display: flex; flex-direction: column; justify-content: center; gap: clamp(12px, 1.4vw, 18px); }   /* lands 24 @1280 (even). The rows stretch to meet the dark card; centring the number + line as one cluster splits the slack evenly, where space-between read as a hole in the middle. */
.snap-stat--wide { grid-column: 1 / -1; }
.snap-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 0; background: #fff; overflow: hidden; }
.snap-impacts { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; background: #fff; overflow: hidden; }
/* Fading dividers, same hairline as the hero rule and the project-card stats.
   Backgrounds rather than borders: a border is flat and cannot fade out at the
   ends. 1px wide x full height for the uprights, full width x 1px for the one
   between the rows. */
.snap-pair .snap-stat + .snap-stat,
.snap-impacts .snap-stat + .snap-stat {
  background-image: var(--rule-fade-y);
  background-repeat: no-repeat;
  background-size: 1px 100%;
  background-position: left top;
}
.snap-impacts .snap-num { font-size: clamp(1.75rem, 2.2vw, 2rem); }   /* 32px @1280 — a step under the top row so three-up cards keep breathing room */
/* Three-up cards are only ~214px wide. Inline, the icon + its gap ate 26 of the
   166px content box, leaving 138px — narrow enough that “adopted shared tokens,”
   could not hold together on one line. Stacking the icon and trimming the padding
   returns the full 174px to the text. */
.snap-impacts .snap-stat { padding: clamp(14px, 1.6vw, 20px); }
.snap-impacts .snap-stat-list li { display: block; }
.snap-impacts .snap-stat-list svg { float: left; margin: 2px 9px 0 0; }   /* floated, not a flex column: the icon shares the first line, but only that line pays for it — every line below runs the full 174px */
@media (max-width: 560px) { .snap-impacts { grid-template-columns: 1fr; } }
.snap-num { font-family: "Averia Serif Libre", var(--display); font-weight: 700; font-size: clamp(1.875rem, 2.5vw, 2.25rem); color: #1c1c1c; display: block; margin-bottom: 10px; line-height: 1.1; }   /* 36px @1280 — the number carries the card; the single support line stays 16px */
/* label on its own line under the number — consistent alignment for all five, no wrapping */
.snap-num em {
  display: block; margin: 4px 0 0;
  font-family: var(--sans); font-style: normal; font-weight: 600;
  font-size: 0.75rem; letter-spacing: 0.07em; text-transform: uppercase;
  color: #0c0d12; line-height: 1.2;   /* matches the project-card stat label ink */
}
.snap-stat-txt { font-family: var(--sans); font-weight: 400; font-size: 1.0625rem; line-height: 1.45; color: #2a3142; max-width: 48ch; }
/* stat bullets — icon + line, two per card (mirrors the project-card impact rows) */
.snap-stat-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.snap-stat-list li {
  display: flex; align-items: flex-start; gap: 9px;
  font-family: var(--sans); font-weight: 400; font-size: 1rem; line-height: 1.4; color: #2a3142;   /* 16px */
}
.snap-stat-list svg { flex: none; width: 17px; height: 17px; margin-top: 2px; color: #767f8f; }   /* ~10% darker for stronger icon presence */

@media (max-width: 860px) {
  .ship, .bio, .snap { grid-template-columns: 1fr; }
  .snap-stats { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 620px) {
  .wr-row { grid-template-columns: 28px 1fr; }
  .wr-thumb { display: none; }
  /* snapshot stats on mobile: full cards, but as a horizontal swipe row —
     one card of scroll height instead of five stacked (site's mobile pattern) */
  .snap-stats {
    display: flex; gap: 12px;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    margin: 0 calc(-1 * var(--gutter));                   /* bleed to screen edge */
    padding: 2px var(--gutter) 6px;
  }
  .snap-stats::-webkit-scrollbar { display: none; }
  /* Both wrappers dissolve so all five stats join the swipe row.
     display:contents generates no box, so their border/shadow/background never
     paint -- no reset needed, and nothing here can fight the row's overflow-x.
     The previous reset set overflow:visible and beat that, which made the
     container 720px wide inside a 375px viewport. */
  .snap-pair, .snap-impacts { display: contents; }
  .snap-pair .snap-stat + .snap-stat,
  .snap-impacts .snap-stat + .snap-stat { background-image: none; }
  .snap-stat {
    flex: 0 0 78%; max-width: 300px;
    scroll-snap-align: center;
    background: #fff;
    border: 1px solid rgba(12, 13, 18, .08);
    border-radius: var(--radius-lg, 24px);
    box-shadow: 0 1px 2px rgba(28, 28, 28, .04), 0 10px 28px -22px rgba(28, 28, 28, .22);
  }
}

/* =====================================================================
   HERO — static two-line heading + two-line sub · résumé ghost CTA
   ===================================================================== */
.hero__em { font-style: italic; }   /* AI phrase: quiet italic accent, no animation */
.hero__i  { font-style: italic; font-weight: 600; color: var(--ink, #1c1c1c); }  /* B2B/B2C + AI emphasis */
.hero__b  { font-weight: 600; color: inherit; }  /* B2B/B2C + AI: light bold, same color */
.hero__b--strong { font-weight: 700; }           /* "7+ years": a touch heavier */
/* secondary CTA: quiet ghost résumé button */
.hero__resume { gap: 0; transform-origin: center; transition: transform .4s cubic-bezier(.34, 1.56, .5, 1), box-shadow .35s ease, border-color .35s ease; }
/* The footer's download ARROW (M12 5v14M6 13l6 6 6-6), hidden at rest and
   revealed on hover — the SAME reveal .cta-chat__ar runs in the button beside
   it (width/opacity/margin-left/translateX on one transition), so the pair
   animates as one system rather than two.
   It is a single glyph: the footer's two-glyph drop swap was dropped with the
   ring, because a swap needs something visible to swap OUT and this starts
   hidden.
   `gap: 0` is load-bearing, not a leftover. A flex gap applies either side of a
   zero-width child, so any gap here would hold dead space open while the icon
   is hidden and the button would not close up. The spacing is margin-left on
   the icon instead, which collapses to 0 with it — the same reason .cta-chat
   sets gap: 0. */
.hero__resume-ic {
  flex: none; width: 0; height: 16px; opacity: 0; margin-left: 0;
  transform: translateX(-6px);
  transition: width .35s var(--ease, ease), opacity .25s, margin-left .35s var(--ease, ease), transform .35s var(--ease, ease);
}
.hero__resume:hover,
.hero__resume:focus-visible {
  transform: scale(1.06);   /* same spring zoom as the primary CTA */
  border-color: rgba(12, 13, 18, .48);
  box-shadow: 0 14px 28px -14px rgba(28, 28, 28, .35);
}
.hero__resume:hover .hero__resume-ic,
.hero__resume:focus-visible .hero__resume-ic { width: 16px; opacity: 1; margin-left: 8px; transform: translateX(0); }
.hero__resume:active { transform: scale(.99); }
@media (prefers-reduced-motion: reduce) {
  .hero__resume, .hero__resume-ic { transition: none; }
  .hero__resume:hover { transform: none; }
}
/* the hard line breaks only make sense on wide screens; let text wrap naturally on mobile */
@media (max-width: 620px) {
  .hero__title br, .hero__sub br, .philo__title br, .sec-lede br { display: none; }
}

/* ---- Container system ---------------------------------------------------
   Every page-level container paints the same box. Before this there were four
   competing recipes: hero/footer at 18px-50px .22, approach + process at
   10px-28px .22, the project cards at 24px-50px .32, and the snapshot tiles
   nearly flat at 2px-8px .04 — plus two different border colours (#e7eaf1 and
   rgba(12,13,18,.08)) that read as the same grey until you put them side by side.

   .study is deliberately NOT in this list: the project cards are the only
   clickable containers, and their heavier lift is what separates “this is a
   link” from “this is a panel”. */
.hero-box,
.cta-final,
.footer__card,
.reach-markets,
.tst,
.bento-card,
.exp-list,
.snap-pair,
.snap-impacts {
  border: 1px solid rgba(12, 13, 18, .08);
  border-radius: var(--radius-lg, 24px);
  box-shadow: 0 1px 2px rgba(28, 28, 28, .04), 0 10px 28px -22px rgba(28, 28, 28, .22);
}

/* =====================================================================
   CONTACT PAGE  ->  one container card: head + (form | rail)
   Same box, heading step and micro-labels as the homepage sections; the
   rail reuses the footer's cta-mail / cta-link components verbatim so
   the copy control and the chip animations are one source of truth.
   ===================================================================== */
/* contact.html carries no footer (the form IS the call to action), so the
   section supplies the page-closing gap the footer normally provides — same
   clamp as .cta-foot's padding-bottom. */
/* Contact opens at the SAME height as every other page. It carries no
   .back-pill, so it fell through to the base .section clamp and started 115px
   under the nav where work.html, qualify.html and triply.html all start at 51 --
   it read as a page that had been scrolled. This is the clamp that rule uses. */
/* Tighter than the shared page opening (clamp(24px,4vw,56px)) because this
   page has to hold a 984px booking card without scrolling. It no longer matches
   work.html exactly -- that is the trade, and it is deliberate. */
.section:has(.contact-card) { padding-top: clamp(16px, 2vw, 28px); }
.section:has(.contact-card) { padding-bottom: clamp(24px, 2.4vw, 36px); }
.contact-card {
  background: var(--surface, #fff);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg, 24px);
  box-shadow: 0 1px 2px rgba(28, 28, 28, .04), 0 10px 28px -22px rgba(28, 28, 28, .22);
  overflow: hidden;
}
.contact-card__head {
  padding: clamp(24px, 2.6vw, 32px) clamp(24px, 3vw, 40px) clamp(16px, 1.8vw, 24px);
  padding: round(clamp(24px, 2.6vw, 32px), 4px) round(clamp(24px, 3vw, 40px), 4px) round(clamp(16px, 1.8vw, 24px), 4px);   /* 32 - 40 - 24 @1280 */
}
.contact-title {
  font-family: "Averia Serif Libre", var(--display);
  font-weight: 700; line-height: 1.16; letter-spacing: -0.01em;
  font-size: clamp(1.625rem, 1.325rem + 1.16vw, 2.25rem); color: #1c1c1c;   /* shared section-heading step (36px cap) */
  margin: 0; text-wrap: balance;
}
/* one-line head like .exp-head: the card is wide enough, so no ch caps */
.contact-card__head .sec-box-head__sub { max-width: none; }

.contact-body {
  display: grid; grid-template-columns: 1.08fr .92fr;
  /* Faded rule, the same hairline as the footer and the Experience rows. A
     background rather than a border, because a border is flat and cannot fade
     out at its ends. */
  border-top: none;
  background-image: var(--rule-fade);
  background-repeat: no-repeat;
  background-size: 100% 1px;
  background-position: top left;
}

/* ---- form (left) ---- */
/* The column inset now belongs to the WRAPPER, because two panels share the
   column: the message form and the booking panel. Same values, moved up one
   level, so neither panel can drift from the other. */
.contact-main {
  padding: clamp(20px, 2.2vw, 28px) clamp(24px, 3vw, 40px) clamp(20px, 2.4vw, 32px);
  padding: round(clamp(20px, 2.2vw, 28px), 4px) round(clamp(24px, 3vw, 40px), 4px) round(clamp(20px, 2.4vw, 32px), 4px);
  min-width: 0;
}
.contact-form { padding: 0; }
.contact-main .frow { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
.contact-main .field { margin-bottom: 20px; }
.contact-main .field label {
  font-family: var(--sans); font-size: 0.8125rem; font-weight: 600;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-2, #232934);
  margin-bottom: 8px;   /* homepage micro-label voice (snap-num em / crail labels) */
}
.contact-main .field input,
.contact-main .field textarea {
  border-color: var(--line-2);   /* firmer than the card hairline so the wells read as controls */
  border-radius: var(--radius-sm);
  background: var(--surface, #fff);
}
.contact-main .field input:hover,
.contact-main .field textarea:hover { border-color: rgba(12, 13, 18, .24); }
.contact-main .field input::placeholder,
.contact-main .field textarea::placeholder { color: #9aa1af; }
.contact-main .field textarea { min-height: 196px; }   /* sized so form and rail bottom out together at 1280 */
/* send: the site's prominent primary step (44 - 0 24 - 18) with the
   cta-book spring hover + arrow reveal, exactly like Book a Call */
.contact-send-row { display: flex; align-items: center; flex-wrap: wrap; gap: 16px; margin-top: 4px; }
.contact-send.btn { height: 44px; padding: 0 24px; font-size: 1.125rem; }
.contact-send-note { margin: 0; font-family: var(--sans); font-size: 0.875rem; color: var(--faint, #5a6273); }
/* result of a send. One line, in the flow under the button, so it cannot be
   missed the way a corner toast can. */
/* The icon sits on its own flex item rather than inline in the sentence, so a
   status that wraps to two lines keeps its second line aligned with the first
   word and not under the tick. align-items: flex-start pins it to the first
   line for the same reason. */
.contact-status {
  margin: 8px 0 0; font-family: var(--sans); font-size: 0.875rem; line-height: 1.5;
  display: flex; align-items: flex-start; gap: 8px;
}
.contact-status .contact-status__ic {
  flex: none; width: 16px; height: 16px; margin-top: 2px;   /* 2px optically centres a 16px glyph on a 21px line */
}
.contact-status.is-sending { color: var(--faint, #6b7280); }
.contact-status.is-ok      { color: #0f7a43; font-weight: 500; }
.contact-status.is-err     { color: #b42318; }
/* the bot trap must be unreachable, not merely invisible: display:none would
   keep it out of the tab order too, but some browsers skip validating and
   submitting hidden fields, and the service needs it POSTed. */
.contact-form input[name="_honey"] {
  position: absolute; left: -9999px; width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}


/* ---- rail (right) ---- */
.contact-rail {
  /* The vertical twin of the rule above -- 1px wide by the full height, pinned
     left, exactly where the border sat. */
  border-left: none;
  background-image: var(--rule-fade-y);
  background-repeat: no-repeat;
  background-size: 1px 100%;
  background-position: left top;
  padding: clamp(24px, 3vw, 36px) clamp(24px, 3vw, 40px) clamp(28px, 3.2vw, 40px);
  padding: round(clamp(24px, 3vw, 36px), 4px) round(clamp(24px, 3vw, 40px), 4px) round(clamp(28px, 3.2vw, 40px), 4px);
  display: flex; flex-direction: column; gap: clamp(20px, 2.4vw, 28px);
}

/* The rail rides up so the "Response" row baseline meets the "Send message"
   label baseline -- the pairing the eye makes at the foot of the two columns.
   Scoped to >=901px because that is exactly where they ARE two columns; below
   it the rail stacks under the form and keeps its own generous top.
   Measured: worst baseline gap across 901-1440 goes 23-42px -> 2.3px.
   It costs the TOP: the availability card now starts 8px under the divider
   instead of level with the NAME label. That is the trade, and the bottom
   pairing is the one that reads. */
@media (min-width: 901px) {
  .contact-rail { padding-top: 8px; }
  /* The Response row is pinned to the rail bottom (.crail-meta margin-top:auto),
     so once the BOOKING TOGGLE made the form the taller column -- it now drives
     the card height and the rail stretches -- the padding-TOP above stopped
     reaching it and the pairing drifted a uniform 4.8px. The bottom inset is
     what positions it in that regime. */
  .contact-rail { padding-bottom: 27px; }
  .contact-rail { padding-bottom: calc(round(clamp(28px, 3.2vw, 40px), 4px) - 13px); }
  /* The availability card then sits lower INSIDE that space. The negative
     bottom margin is the point: it cancels the top one, so the card moves down
     on its own and the EMAIL block -- and everything under it, including the
     meta row whose baseline is aligned to the Send button -- does not move at
     all. Net: 8/28 above/below becomes 24/12.
     The gap below it needed closing because the card is a FILLED block with its
     own 20px bottom padding, so its text sat 49px off the EMAIL label where
     every other section gap is 28px edge to edge. */
  .crail-avail { margin-top: 16px; margin-bottom: -16px; }
}
/* Below 1060 the availability sentence takes a THIRD line, adding 24px to the
   rail. No padding can recover that -- aligning there would need a NEGATIVE
   padding-top -- so the rail own gap absorbs the extra line instead. */
@media (min-width: 901px) and (max-width: 1060px) {
  .contact-rail { gap: 18px; }
  /* ...and because the gap is already tightened here, the 16px nudge above
     would leave only 2px under the availability card. 6px off an 18px gap
     lands the same ~12px the wide range gets. */
  .crail-avail { margin-top: 6px; margin-bottom: -6px; }
}
/* availability: soft panel opener - hero status pill + one support line */
.crail-avail {
  background: var(--bg-soft, #f6f7f9);
  border: 1px solid var(--line);
  border-radius: var(--radius, 16px);
  padding: 18px 20px 20px;
}
.crail-avail .status { font-size: 0.875rem; padding: 6px 12px 6px 10px; }   /* the hero pill scales to 24px; step it down to UI size here */
.crail-avail .status .status__dot { width: 9px; height: 9px; }
.crail-avail__txt {
  margin: 12px 0 0; font-family: var(--sans); font-weight: 400;
  font-size: 1rem; line-height: 1.5; color: #2a3142;
}
.crail-label {
  display: block; margin-bottom: 10px;
  font-family: var(--sans); font-size: 0.8125rem; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase; color: var(--faint, #5a6273);   /* same voice as .sec-box-head__eyebrow */
}
/* footer components, stretched to the rail column */
.contact-rail .cta-mail { display: flex; width: 100%; justify-content: space-between; }
.contact-rail .cta-mail__addr { overflow: hidden; text-overflow: ellipsis; }
.crail-links { display: flex; flex-direction: column; gap: 10px; }
.contact-rail .cta-link { width: 100%; padding: 0 6px 0 14px; border-color: var(--line-2); }   /* #eef1f6 was tuned for the footer card; on white it vanished */
.contact-rail .cta-link .cta-link__chip { margin-left: auto; }
/* meta: ruled rows pinned to the rail foot so the two columns bottom out together */
.crail-meta { list-style: none; margin: auto 0 0; padding: 0; }
.crail-meta li {
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px;
  padding: 13px 2px; border-top: 1px solid var(--line);
}
.crail-meta .k {
  font-family: var(--sans); font-size: 0.8125rem; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase; color: var(--faint, #5a6273);
}
.crail-meta .v { font-family: var(--sans); font-size: 1rem; font-weight: 500; color: var(--ink-2, #232934); }

/* content cascade on scroll-in: head, then form, then rail (exp-list pattern) */
.contact-card.reveal .contact-card__head,
.contact-card.reveal .contact-main,
.contact-card.reveal .contact-rail {
  opacity: 0; transform: translateY(16px);
  transition: opacity .9s ease, transform .9s cubic-bezier(.22, 1, .36, 1);
}
.contact-card.reveal.is-in .contact-card__head,
.contact-card.reveal.is-in .contact-main,
.contact-card.reveal.is-in .contact-rail { opacity: 1; transform: none; }
.contact-card.reveal.is-in .contact-card__head { transition-delay: .10s; }
.contact-card.reveal.is-in .contact-form      { transition-delay: .30s; }
.contact-card.reveal.is-in .contact-rail      { transition-delay: .50s; }
@media (prefers-reduced-motion: reduce) {
  .contact-card.reveal .contact-card__head, .contact-card.reveal .contact-main,
  .contact-card.reveal .contact-rail { transition: none; opacity: 1; transform: none; }
}

@media (max-width: 900px) {
  .contact-body { grid-template-columns: 1fr; }
  /* stacked, so the same rule turns through 90 degrees */
  .contact-rail {
    border-left: 0; border-top: none;
    background-image: var(--rule-fade);
    background-size: 100% 1px;
    background-position: top left;
  }
}
@media (max-width: 560px) {
  .contact-main .frow { grid-template-columns: 1fr; }
  .contact-send-row { align-items: flex-start; flex-direction: column; gap: 12px; }
  .contact-send.btn { width: 100%; max-width: 340px; justify-content: center; }
}

/* ============================================================
   MY ARTICLES — two tiers, one grid
   Layout follows the Figma frame "Option 1 · Two tiers, one grid"
   (file qhP9xsRThEE56LLb6ro58E, node 17:15649): three long reads
   across the top, then the six quick guides as a 3 x 2 grid.
   It replaces two columns held level by align-content, which is
   why the old delta-0 alignment rule is gone -- a grid does that
   by construction.

   The Figma is a reference for LAYOUT only. Its own tokens are
   mapped onto this site's:
     fill      rgb(245,245,243) -> #f6f7f9, the stage grey already
               used behind card media
     radius    24 / 20          -> 16 / 12, --radius-sm and the
               step the notes cards already used
     padding   28 / 20          -> 24 / 16, the 8-pt grid; 28 and
               20 are off it
     type      Fraunces + DM Sans -> Averia + Satoshi, unchanged
               sizes, so the section still matches the page
     CTA       plain arrow      -> the shared circular chip, which
               is this site's card-CTA component
   ============================================================ */
.notes.bento-card { padding: clamp(20px, 2.2vw, 28px); }
.notes__tiers { display: grid; gap: 40px; margin-top: clamp(24px, 3vw, 32px); }

/* ---- tier 1: the three long reads ---- */
.notes__reads { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.note-read {
  position: relative;   /* the chip is corner-anchored, see below */
  display: flex; flex-direction: column; gap: 24px;
  padding: 24px; border-radius: 16px;
  background: #f6f7f9;
  text-decoration: none; color: inherit;
  transition: box-shadow .45s ease, transform .5s cubic-bezier(.34, 1.56, .5, 1);
}
a.note-read:hover, a.note-read:focus-visible {
  box-shadow: 0 0 3px rgba(28, 28, 28, .06),
              0 0 12px rgba(28, 28, 28, .06),
              0 0 28px -4px rgba(28, 28, 28, .14);
  transform: translateY(-3px);
}
/* the cover is a square sitting on the card, not a full-bleed header: the
   artwork is drawn as a tile and cropping it to a banner would cut the
   illustration, which is only three or four shapes to begin with */
.note-read__tile {
  width: 152px; height: 152px; flex: none;
  /* 24, not 12. The cover PNG already draws its own rounded outer shape -- 71px
     of a 528px canvas, which lands at ~20px once the tile renders it at 152.
     Clipping that at 12 left a white band between the tile edge and the drawn
     curve, so the corner showed TWO edges a few pixels apart. At 24 the clip
     takes the redundant outline off entirely and the tile edge is the only one.
     Measured at 6x, not guessed. The 56px tip tiles need no change: their 336px
     covers work out at 8.2px against the 8px they already carry. */
  border-radius: var(--radius-lg, 24px); overflow: hidden; background: #fff;
  box-shadow: 0 1px 2px rgba(28, 28, 28, .05), 0 3px 8px -3px rgba(28, 28, 28, .10);
}
.note-read__tile img { width: 100%; height: 100%; display: block; }
.note-read__body { display: flex; flex-direction: column; gap: 8px; min-width: 0; flex: 1 1 auto; }
/* the eyebrow is the line that reaches the corner; keep it clear of the chip */
.note-read__meta { padding-right: 38px; }
.note-read__meta { color: var(--faint, #6b7280); }
.note-read__title {
  font-family: var(--sans); font-weight: 700; font-size: 1.125rem;   /* 18px */
  line-height: 1.3; color: #1c1c1c; letter-spacing: -0.01em;
}
.note-read__desc {
  font-family: var(--sans); font-weight: 400; font-size: 0.9375rem;  /* 15px */
  line-height: 1.5; color: #2a3142;
}
/* Two lines, never three -- but ONLY while the cards sit in a row, which is the
   whole reason it matters: a third line on one card makes three side-by-side
   cards read as ragged. Below 620 they stack, uneven line counts cost nothing,
   and a phone has the vertical room -- so the clamp comes off rather than
   cutting a sentence mid-phrase ("survives a real..."). The copy above is
   trimmed so nothing is actually truncated anywhere the clamp is active; it is
   the guarantee that a later copy edit cannot quietly break the row again. */
@media (min-width: 621px) {
  .note-read__desc {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
    line-clamp: 2; overflow: hidden;
  }
}
/* The card-CTA chip. Shared with the Work-page cards, so the circle, its
   transition and its fill-on-hover are ONE definition. This rule used to sit in
   the old notes block and was lost when that block was replaced -- which broke
   work.html's chip too, since it joins here. */
.card .card-chip,
.note-tip__chip,
.note-read__chip {
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  color: var(--ink, #16181d);
  border: 1px solid var(--line-2, rgba(12, 13, 18, .18)); background: transparent;
  transition: background .3s ease, border-color .3s ease, color .3s ease,
              transform .35s cubic-bezier(.34, 1.56, .5, 1);
}
.card .card-chip svg,
.note-tip__chip svg,
.note-read__chip svg { width: 15px; height: 15px; display: block; }
.card:hover .card-chip,
.card:focus-visible .card-chip,
.note-tip__link:hover .note-tip__chip,
.note-tip__link:focus-visible .note-tip__chip,
a.note-read:hover .note-read__chip,
a.note-read:focus-visible .note-read__chip {
  background: var(--ink, #16181d); border-color: var(--ink, #16181d); color: #fff;
  transform: translateX(4px) scale(1.06);
}
@media (prefers-reduced-motion: reduce) {
  .note-read__chip, .note-tip__chip, .card .card-chip { transition: none; }
  a.note-read:hover .note-read__chip,
  .note-tip__link:hover .note-tip__chip,
  .card:hover .card-chip { transform: none; }
}

/* The chip sits in the card's top-right corner rather than on a labelled row
   at the foot. Absolute, because this card is a flex COLUMN -- left in flow it
   would take a row of its own and push the copy up. The offsets match the
   card's own padding, so it lands exactly in the corner and level with the top
   of the cover tile beside it. */
.note-read__chip { position: absolute; top: 24px; right: 24px; }

/* ---- tier 2: the six quick guides ---- */
.notes__tips {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px;
}
.note-tip { display: block; }
.note-tip__link {
  /* third track is the go-chip. A column rather than an absolute position, so
     the description can never run under it. */
  display: grid; grid-template-columns: 56px minmax(0, 1fr) 30px; gap: 16px;
  align-items: start;
  padding: 16px; border-radius: 12px;
  background: #f6f7f9;
  text-decoration: none; color: inherit; height: 100%;
  transition: box-shadow .45s ease, transform .5s cubic-bezier(.34, 1.56, .5, 1);
}
.note-tip__link:hover, .note-tip__link:focus-visible {
  box-shadow: 0 0 3px rgba(28, 28, 28, .06),
              0 0 12px rgba(28, 28, 28, .06),
              0 0 28px -4px rgba(28, 28, 28, .14);
  transform: translateY(-3px);
}
.note-tip__ic--img {
  width: 56px; height: 56px; border-radius: 8px;
  overflow: hidden; background: #fff;
  box-shadow: 0 1px 2px rgba(28, 28, 28, .05), 0 3px 8px -3px rgba(28, 28, 28, .10);
}
.note-tip__ic--img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.note-tip__body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.note-tip__kind { color: var(--faint, #6b7280); }
.note-tip__name {
  font-family: var(--sans); font-weight: 700; font-size: 1rem;       /* 16px */
  line-height: 1.3; color: #1c1c1c; letter-spacing: -0.01em;
}
.note-tip__desc {
  font-family: var(--sans); font-weight: 400; font-size: 0.875rem;   /* 14px */
  line-height: 1.5; color: #2a3142;
}
/* Two lines while the cards are in a row, for the same reason the read cards
   carry it: a third line on one card makes the row ragged. Below 620 they
   stack, so the clamp comes off rather than cutting a sentence short. The copy
   is trimmed so nothing is truncated anywhere the clamp is live. */
@media (min-width: 621px) {
  .note-tip__desc {
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
    line-clamp: 2; overflow: hidden;
  }
}
/* top of its own track: level with the thumbnail, in the card's top-right
   corner -- the same place the large cards now carry it */
.note-tip__chip { align-self: start; }

/* reveal — joins the staggered entrance the process card uses */
.bento.reveal .notes__tiers { opacity: 0; transform: translateY(14px); transition: opacity .7s ease, transform .7s cubic-bezier(.2,.7,.3,1); }
.bento.reveal.is-in .notes__tiers { opacity: 1; transform: none; transition-delay: .42s; }

/* 1180, not 1100: measured, three columns give the description box 278px at
   1181 and 275px at 1150 -- and 275 is where the longest of the three needs a
   third line. The old 1100 left an 80px band where the row was three-up and
   ragged. The quick guides are shorter and were fine either way; they keep the
   same breakpoint so the two tiers still turn together. */
@media (max-width: 1180px) {
  .notes__reads, .notes__tips { grid-template-columns: repeat(2, 1fr); }
}
/* The read cards stop being two-up at 820, not 620. Between those the second
   column squeezes the description box to 217-269px, where the copy needs a
   third line and the two cards in a row read as ragged -- the exact squeeze
   band this stylesheet already documents for the project cards at 940-1090.
   One column there gives them the full width instead. The quick-guide cards
   are shorter and hold two-up all the way down, so they are left alone. */
@media (max-width: 820px) {
  /* Both tiers stop being two-up here. Measured, the second column squeezed the
     quick-guide description to 96-131px between 621 and 800 -- three to six
     lines on a 14px sentence. Same band that was starving the read cards. */
  .notes__reads, .notes__tips { grid-template-columns: 1fr; }
}
@media (max-width: 620px) {
  .notes__reads, .notes__tips { grid-template-columns: 1fr; gap: 16px; }
  .notes__tiers { gap: 24px; }
  .note-read { padding: 16px; gap: 16px; }
  .note-read__chip { top: 16px; right: 16px; }
  /* the cover drops to the row scale so a phone does not spend half the card
     on artwork */
  .note-read__tile { width: 96px; height: 96px; }
}
@media (prefers-reduced-motion: reduce) {
  .note-read, .note-tip__link { transition: none; }
  a.note-read:hover, .note-tip__link:hover { transform: none; }
  .bento.reveal .notes__tiers { opacity: 1; transform: none; }
}

/* ============================================================
   ARTICLE PAGES (.artcs) — icon-types, text-inputs, cards
   Real text, not slices, same as the case studies. Lifted here
   from the pages themselves once a third article arrived: the
   block had been copied verbatim into each one.
   Long-form reading, so the measure is narrower than a case
   study — the Figma articles set 680 of an 880 column and that
   proportion is kept at 760 of the site's 1160 wrap.
   ============================================================ */
.artcs {
      --a-ink:   #1c1c1c;
      --a-body:  #43464e;
      --a-label: #6b7280;
      --a-line:  #e7eaf1;
      --a-surf:  #f6f7f9;
      --a-serif: "Averia Serif Libre", Georgia, serif;
      background: #fff; color: var(--a-ink); font-family: var(--sans);
    }
    .art-wrap { max-width: 760px; margin-inline: auto; }

    /* ---- header ---- */
    .art-head { padding-top: clamp(8px, 1.6vw, 16px); }
    .art-kicker {
      font-size: 0.75rem; font-weight: 700; letter-spacing: .1em;
      text-transform: uppercase; color: var(--a-label); margin: 0 0 16px;
    }
    .art-h1 {
      font-family: var(--a-serif); font-weight: 700; color: var(--a-ink);
      font-size: clamp(1.875rem, 1.15rem + 2.6vw, 2.875rem);
      line-height: 1.14; letter-spacing: -.015em; margin: 0; text-wrap: pretty;
    }
    .art-deck {
      font-size: 1.0625rem; line-height: 1.65; color: var(--a-body);
      margin: 18px 0 0;
    }
    .art-by {
      display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
      margin: 24px 0 0; padding-top: 20px; border-top: 1px solid var(--a-line);
      font-size: 0.875rem; color: var(--a-label);
    }
    .art-by strong { font-weight: 700; color: var(--a-ink); }
    .art-by span { color: var(--a-line); }

    /* ---- section ---- */
    .art-sec { padding-top: clamp(44px, 5.5vw, 72px); }
    .art-sec__head { display: flex; align-items: center; gap: 14px; margin: 0 0 16px; }
    .art-num {
      flex: none; width: 36px; height: 36px; border-radius: 10px;
      display: grid; place-items: center; background: var(--a-ink); color: #fff;
      font-size: 0.8125rem; font-weight: 700;
    }
    .art-h2 {
      font-family: var(--a-serif); font-weight: 700; color: var(--a-ink);
      font-size: clamp(1.5rem, 1.15rem + 1.4vw, 2rem); line-height: 1.2; margin: 0;
    }
    .art-p { font-size: 1rem; line-height: 1.7; color: var(--a-body); margin: 0; }
    /* the three long-form references run 2-3 paragraphs a section; the first five
       articles are one paragraph each, so this only ever fires on the long ones */
    .art-p + .art-p { margin-top: 16px; }

    /* ---- contents, long-form articles only ----
       An 18-section reference is unusable without a way in. Bordered rather than
       filled, same as the homepage note cards — the page already spends its one
       grey surface on the figures. */
    .art-toc {
      margin: clamp(28px, 3.4vw, 40px) 0 0;
      padding: clamp(20px, 2.4vw, 28px);
      border: 1px solid var(--a-line); border-radius: 20px; background: #fff;
    }
    .art-toc__k {
      font-size: 0.75rem; font-weight: 700; letter-spacing: .1em;
      text-transform: uppercase; color: var(--a-label); margin: 0 0 16px;
    }
    /* multi-column, not a 2-col grid: a numbered contents has to read DOWN the
       left column then down the right (01-09, 10-18). A grid flows across, which
       puts 02 beside 01 and makes the numbering look shuffled. */
    .art-toc__list {
      list-style: none; margin: 0; padding: 0;
      columns: 2; column-gap: 28px;
    }
    .art-toc__list li { break-inside: avoid; }
    .art-toc__list a {
      display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 8px;
      padding: 7px 8px; margin-left: -8px; border-radius: 10px;
      text-decoration: none; color: var(--a-ink);
      font-size: 0.9375rem; line-height: 1.4;
      transition: background .25s ease;
    }
    .art-toc__list a:hover { background: var(--a-surf); }
    .art-toc__n { color: var(--a-label); font-variant-numeric: tabular-nums; font-weight: 500; }
    @media (max-width: 720px) {
      .art-toc__list { grid-template-columns: minmax(0, 1fr); }
    }
    /* ---- key takeaways, long-form articles only ----
       Authored in Figma with the article and rendered by build-article.js only
       when the JSON carries them, so the original five articles are untouched.
       Deliberately the same shell as .art-toc: contents at the top and
       takeaways at the foot bookend the piece, and giving them two different
       card treatments would read as two unrelated components rather than the
       open and close of one page. Single column, unlike the contents — these
       are sentences, not a lookup list, so they read straight down. */
    .art-takeaways {
      margin: clamp(48px, 6vw, 76px) 0 0;
      padding: clamp(20px, 2.4vw, 28px);
      border: 1px solid var(--a-line); border-radius: 20px; background: #fff;
    }
    .art-takeaways__k {
      font-size: 0.75rem; font-weight: 700; letter-spacing: .1em;
      text-transform: uppercase; color: var(--a-label); margin: 0 0 16px;
    }
    .art-takeaways__list {
      list-style: none; margin: 0; padding: 0;
      display: grid; gap: 12px;
    }
    .art-takeaways__list li {
      display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 8px;
      align-items: start;
      font-size: 0.9375rem; line-height: 1.55; color: var(--a-body);
    }
    .art-takeaways__n {
      color: var(--a-label); font-variant-numeric: tabular-nums; font-weight: 500;
      line-height: inherit;
    }

    .art-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 20px 0 0; }
    .art-tags__k { font-size: 0.8125rem; font-weight: 500; color: var(--a-label); margin-right: 2px; }
    .art-pill {
      font-size: 0.8125rem; font-weight: 500; color: var(--a-ink);
      background: var(--a-surf); border: 1px solid var(--a-line);
      border-radius: 999px; padding: 6px 12px;
    }
    .art-shot { margin: clamp(20px, 2.6vw, 28px) 0 0; }
    .art-shot img {
      display: block; width: 100%; height: auto;
      border-radius: 16px; border: 1px solid var(--a-line);
    }

    /* Figma-panel screenshots carry baked 12px numerals (redlines, Auto Layout
       values). Squeezed from 1360 native into a 335px column those land at ~3px
       and the figure becomes a smudge, so below 720 the marked figures pan
       horizontally at a fixed 760 instead — the same trade the case studies make.
       Opt-in: the icon and input specimens are large-featured and read fine
       scaled down, so they are left alone. */
    @media (max-width: 720px) {
      .artcs .art-shot--pan {
        overflow-x: auto;
        margin-inline: calc(50% - 50vw);
        padding-inline: 20px;         /* matches .wrap's inset at 375 */
        -webkit-overflow-scrolling: touch;
      }
      .artcs .art-shot--pan img { width: 760px; max-width: none; }
    }

    .art-end { padding: clamp(48px, 7vw, 88px) 0 clamp(8px, 2vw, 24px); }

    .artcs .back-pill { margin-bottom: clamp(16px, 2.4vw, 24px); }

    @media (prefers-reduced-motion: reduce) { .artcs * { animation: none !important; transition: none !important; } }


/* ---------------------------------------------------------------------------
   Back to top — the long-document affordance
   The case studies run 14,700–32,400px. The nav stays sticky the whole way down,
   so getting back to WORK was never the missing action; getting back UP was.
   Reuses the circular-button language .study__go-chip already sets: a hairline
   ring at rest, filling --ink on hover, on the ghost button's spring curve.
   The ring doubles as a reading-progress arc, the same round-progress motif the
   process section and the testimonial avatar already use — on a thirty-screen
   page that is information, not decoration.
   Injected by main.js, so there is no markup in any page to keep in sync.
--------------------------------------------------------------------------- */
.totop {
  position: fixed;
  right: clamp(16px, 2.5vw, 32px);
  bottom: clamp(16px, 2.5vw, 32px);
  z-index: 60;
  width: 46px; height: 46px;
  display: grid; place-items: center;
  padding: 0; border: 0; border-radius: 50%;
  background: #fff;
  color: var(--ink, #0c0d12);
  cursor: pointer;
  /* it floats over body copy, so it needs its own lift to stay legible */
  box-shadow: 0 10px 24px -14px rgba(28, 28, 28, .45);
  opacity: 0; transform: translateY(10px) scale(.92);
  pointer-events: none;
  transition: opacity .28s ease,
              transform .34s cubic-bezier(.34, 1.56, .5, 1),
              background-color .2s ease, color .2s ease, box-shadow .2s ease;
}
.totop.is-on { opacity: 1; transform: none; pointer-events: auto; }
.totop.is-on:hover,
.totop.is-on:focus-visible {
  background: var(--ink, #0c0d12); color: #fff;
  transform: scale(1.06);
  box-shadow: 0 14px 28px -14px rgba(28, 28, 28, .45);
}
.totop:focus-visible { outline: 2px solid var(--ink, #0c0d12); outline-offset: 3px; }

/* the ring IS the border — a 1px border plus a stroked circle would read as the
   doubled edge the case-study covers already had to have removed */
.totop__ring {
  position: absolute; inset: 0; width: 100%; height: 100%;
  transform: rotate(-90deg);           /* progress starts at twelve o'clock */
  pointer-events: none;
}
.totop__ring circle { fill: none; stroke-width: 2; }
.totop__track { stroke: rgba(12, 13, 18, .12); transition: stroke .2s ease; }
.totop__bar { stroke: currentColor; stroke-linecap: round; }
.totop.is-on:hover .totop__track,
.totop.is-on:focus-visible .totop__track { stroke: rgba(255, 255, 255, .28); }
.totop__ar { position: relative; }

@media (prefers-reduced-motion: reduce) {
  .totop { transition: opacity .01s linear; }
  .totop.is-on:hover { transform: none; }
}

/* Visually hidden, still in the accessibility tree and still indexed.
   Five pages carry their title inside exported images (the four image-based
   case studies) or have no page heading at all (work.html), so they shipped
   with zero <h1>. A hidden h1 gives assistive tech and crawlers the document
   title without adding a heading the design never had. Standard clip pattern:
   width/height 1px rather than display:none, which would remove it from the
   tree entirely and defeat the point. */
.u-vh {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}


/* ============================================================
   BOOK A CALL
   Layout from the Figma frame "section card selected"
   (qhP9xsRThEE56LLb6ro58E, 25:17004); every token from this site.
   ============================================================ */

/* ---- mode switch: message | book ---- */
.mode-toggle {
  position: relative;
  display: inline-flex; align-items: center; gap: 2px;
  /* 24, not 28: the toggle is new height above the form, and at 28 it pushed
     the Send button out of the baseline pairing with the rail Response row. */
  padding: 4px; margin-bottom: 20px;
  background: var(--bg-soft, #f6f7f9);
  border-radius: var(--pill, 999px);
}
/* The sliding pill. Only paints once the script has measured the active segment
   and added .has-slider -- until then .is-on carries the selected state, so the
   control is still correct with no JS at all. */
.mode-toggle.has-slider::before {
  content: ''; position: absolute; left: 0; top: 4px;
  width: var(--seg-w, 0px); height: var(--seg-h, 36px);
  transform: translateX(var(--seg-x, 0px));
  background: var(--surface, #fff);
  border: 1px solid var(--line-2, rgba(12, 13, 18, .14));
  border-radius: var(--pill, 999px);
  box-shadow: 0 1px 2px rgba(28, 28, 28, .05);
  transition: transform .45s cubic-bezier(.22, 1, .36, 1),
              width .45s cubic-bezier(.22, 1, .36, 1);
  pointer-events: none;
}
.mode-toggle__btn { position: relative; z-index: 1; }
/* with the slider running the button paints no background of its own -- two
   white pills would be visible mid-slide */
.mode-toggle.has-slider .mode-toggle__btn.is-on {
  background: transparent; border-color: transparent; box-shadow: none;
}
@media (prefers-reduced-motion: reduce) {
  .mode-toggle.has-slider::before { transition: none; }
}
.mode-toggle__btn {
  height: 36px; padding: 0 20px; border: 1px solid transparent;
  border-radius: var(--pill, 999px);
  font-family: var(--sans); font-size: 0.9375rem; font-weight: 500;
  color: var(--faint, #5a6273); background: transparent; cursor: pointer;
  white-space: nowrap;
  transition: background .25s var(--ease, ease), color .25s var(--ease, ease),
              border-color .25s var(--ease, ease);
}
.mode-toggle__btn:hover { color: #1c1c1c; }
/* the selected segment is a raised white pill on the track -- the same
   figure/ground the site's other selected states use */
.mode-toggle__btn.is-on {
  background: var(--surface, #fff); color: #1c1c1c;
  border-color: var(--line-2, rgba(12, 13, 18, .14));
  box-shadow: 0 1px 2px rgba(28, 28, 28, .05);
}

/* ---- the booking panel ---- */
.bk { display: flex; flex-direction: column; gap: 20px; }
/* [hidden] is an ATTRIBUTE selector, so the UA rule that gives it display:none
   carries the same 0,1,0 weight as .bk and loses on origin -- the author sheet
   wins. Without this the panel renders under the message form even while its
   hidden property is true, and the card measures 1481px instead of 763. Any
   rule that sets display on a toggled element needs this pair. */
.bk[hidden] { display: none; }
.bk-block { display: flex; flex-direction: column; }
.bk-block .crail-label { margin-bottom: 12px; }

/* topic chips: the site's .chip at its small step, the one the Work filters use */
.bk-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.bk-chip {
  height: 34px; padding: 0 16px; font-size: 0.875rem; font-weight: 500;
  background: var(--surface, #fff); color: #1c1c1c;
  border: 1px solid var(--line-2, rgba(12, 13, 18, .14));
  cursor: pointer;
  transition: background .25s var(--ease, ease), color .25s var(--ease, ease),
              border-color .25s var(--ease, ease);
}
.bk-chip:hover { border-color: rgba(12, 13, 18, .30); }
.bk-chip.is-on {
  background: #1c1c1c; color: #fff; border-color: #1c1c1c;
}

/* ---- scheduler: calendar | day, split by the faded rule the card uses ---- */
.bk-sched {
  display: grid;
  /* The day column is PINNED to what its content needs, not to a ratio: three
     "15/30/45 min" segments and two "11:30 AM" slots per row. The Figma
     scheduler is 639 wide; this site gives the contact column ~502, so the
     frame ratio (1 : .78) starved both -- measured -0.5px on the slots and
     -8.4px on the segments, i.e. text past its own padding. The calendar takes
     whatever is left. */
  grid-template-columns: minmax(0, 1fr) 196px;
  gap: 16px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius, 16px);
  background: var(--surface, #fff);
}
/* the divider is the site's fading hairline, not a flat border -- the same
   treatment the contact card's own two rules carry */
.bk-day {
  padding-left: 16px;
  background-image: var(--rule-fade-y);
  background-repeat: no-repeat;
  background-size: 1px 100%;
  background-position: left top;
  display: flex; flex-direction: column; gap: 16px; min-width: 0;
}

/* ---- calendar ---- */
.bk-cal { min-width: 0; }
.bk-cal__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.bk-cal__month { font-family: var(--sans); font-size: 1rem; font-weight: 700; color: #1c1c1c; }
.bk-cal__nav { display: inline-flex; gap: 6px; }
.bk-navbtn {
  width: 28px; height: 28px; border-radius: 50%; flex: none;
  display: grid; place-items: center; cursor: pointer;
  background: var(--surface, #fff); color: #1c1c1c;
  border: 1px solid var(--line-2, rgba(12, 13, 18, .14));
  transition: border-color .25s var(--ease, ease), background .25s var(--ease, ease), opacity .2s ease;
}
.bk-navbtn svg { width: 14px; height: 14px; display: block; }
.bk-navbtn:hover:not(:disabled) { border-color: rgba(12, 13, 18, .30); }
.bk-navbtn:disabled { opacity: .35; cursor: default; }

.bk-cal__wd, .bk-cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.bk-cal__wd { margin-bottom: 4px; }
.bk-cal__wd span {
  font-family: var(--sans); font-size: 0.75rem; font-weight: 700;   /* 12px — the floor; the frame's 11 does not come across */
  letter-spacing: .06em; text-transform: uppercase;
  color: var(--faint, #5a6273); text-align: center;
}
.bk-day-btn {
  /* NO min-height beside aspect-ratio: the pair fights. A 36px floor on a
     ~31px grid column makes the box 36 wide and every row overflows to the
     right, which reads as the last column being clipped by the divider.
     The column sizes the cell; max-width keeps it from ballooning once the
     scheduler stacks and the calendar goes full width. */
  aspect-ratio: 1; width: 100%; max-width: 44px; margin-inline: auto;
  display: grid; place-items: center; padding: 0;
  border: 1px solid transparent; border-radius: 50%;
  font-family: var(--sans); font-size: 0.875rem; font-weight: 500;
  color: #1c1c1c; background: transparent; cursor: pointer;
  font-variant-numeric: tabular-nums;
  transition: background .2s var(--ease, ease), color .2s var(--ease, ease), border-color .2s var(--ease, ease);
}
.bk-day-btn:hover:not(:disabled):not(.is-on) { background: var(--bg-soft, #f6f7f9); }
.bk-day-btn:disabled { color: #9aa1af; cursor: default; }        /* the placeholder grey already on this page */
.bk-day-btn.is-today { border-color: var(--line-2, rgba(12, 13, 18, .14)); }
.bk-day-btn.is-on { background: #1c1c1c; color: #fff; border-color: #1c1c1c; font-weight: 700; }
.bk-day-btn--blank { visibility: hidden; }

/* ---- day panel ---- */
.bk-day__head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.bk-day__title { font-family: var(--sans); font-size: 1rem; font-weight: 700; color: #1c1c1c; }
.bk-day__tz { font-family: var(--sans); font-size: 0.75rem; color: var(--faint, #5a6273); white-space: nowrap; }

.bk-seg {
  display: flex; align-items: center; gap: 2px; padding: 4px;
  background: var(--bg-soft, #f6f7f9); border-radius: var(--pill, 999px);
}
.bk-seg__btn {
  /* 6px, not 8: "15 min / 30 min / 45 min" at 12px needs every pixel of a
     196px column, and 8 left the labels 0.4px past their own padding. */
  flex: 1 1 0; min-width: 0; height: 28px; padding: 0 6px;
  border: 1px solid transparent; border-radius: var(--pill, 999px);
  font-family: var(--sans); font-size: 0.75rem; font-weight: 500;
  color: var(--faint, #5a6273); background: transparent; cursor: pointer;
  white-space: nowrap;
  transition: background .25s var(--ease, ease), color .25s var(--ease, ease), border-color .25s var(--ease, ease);
}
.bk-seg__btn:hover { color: #1c1c1c; }
.bk-seg__btn.is-on {
  background: var(--surface, #fff); color: #1c1c1c;
  border-color: var(--line-2, rgba(12, 13, 18, .14));
  box-shadow: 0 1px 2px rgba(28, 28, 28, .05);
}

.bk-slots { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.bk-slot {
  height: 40px; padding: 0 8px;
  display: grid; place-items: center;
  border: 1px solid var(--line-2, rgba(12, 13, 18, .14));
  border-radius: var(--radius-sm, 12px);
  font-family: var(--sans); font-size: 0.875rem; font-weight: 500;
  color: #1c1c1c; background: var(--surface, #fff); cursor: pointer;
  white-space: nowrap;
  transition: background .25s var(--ease, ease), color .25s var(--ease, ease), border-color .25s var(--ease, ease);
}
.bk-slot:hover:not(.is-on) { border-color: rgba(12, 13, 18, .30); }
.bk-slot:disabled { color: #9aa1af; border-color: var(--line); background: var(--bg-soft, #f6f7f9); cursor: default; }
.bk-empty { grid-column: 1 / -1; margin: 0; font-family: var(--sans); font-size: 0.875rem; color: var(--faint, #5a6273); }
.bk-slot.is-on { background: #1c1c1c; color: #fff; border-color: #1c1c1c; }

/* ---- who + confirm ---- */
/* the panel gap already separates this row; .field's own 20px was doubling it */
.bk-who .field { margin-bottom: 0; }
.bk-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 24px; justify-content: space-between; }
.bk-summary { min-width: 0; }
.bk-summary__line {
  margin: 0; font-family: var(--sans); font-size: 0.9375rem; font-weight: 500; color: #1c1c1c;
}
.bk-summary__sub {
  margin: 4px 0 0; font-family: var(--sans); font-size: 0.8125rem; color: var(--faint, #5a6273);
}
/* Same button as Send message. It was inheriting the base .btn (34px/14px)
   while .contact-send.btn overrides to 44/24/18 -- two primary actions on one
   page at two different sizes. */
.bk-confirm.btn { flex: none; height: 44px; padding: 0 24px; font-size: 1.125rem; }

/* ---- switching panels ----
   The site's own entrance, at interaction speed: the same translateY(16px) +
   fade on cubic-bezier(.22, 1, .36, 1) that .contact-card.reveal and
   .exp-list.reveal use, run over .5s instead of .9s because this answers a
   click. An ANIMATION rather than a transition, so the resting state stays
   visible and a script failure cannot leave the form blank. */
@keyframes bk-swap-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}
.contact-main .is-swapping { animation: bk-swap-in .9s cubic-bezier(.22, 1, .36, 1) both; }

/* The GRID CONTAINER carries the height change, not the column. .contact-main
   and .contact-rail are both stretched grid items, so their height IS the row
   height -- pinning one cannot drive it, and an inline height there animates
   nothing. .contact-body is content-sized, so it can. */
/* .7s against the content's .9s: the card is at rest slightly before the
   content finishes arriving, which reads as one settling movement rather than
   two that stop together. */
.contact-body.is-resizing { overflow: hidden; transition: height .7s cubic-bezier(.22, 1, .36, 1); }

@media (prefers-reduced-motion: reduce) {
  .contact-main .is-swapping { animation: none; }
  .contact-body.is-resizing { transition: none; }
}

/* ---- keyboard ---- */
/* None of these are <a> or .btn, so nothing in the sheet was giving them a
   focus ring. Offset so it reads outside a filled selected state. */
.mode-toggle__btn:focus-visible,
.bk-chip:focus-visible,
.bk-seg__btn:focus-visible,
.bk-slot:focus-visible,
.bk-day-btn:focus-visible,
.bk-navbtn:focus-visible {
  outline: 2px solid var(--brand, #1b5fe0);
  outline-offset: 2px;
}

/* ---- responsive ---- */
@media (max-width: 1100px) {
  .bk-sched { grid-template-columns: 1fr; gap: 20px; padding: 20px; }
  /* stacked, so the rule turns through 90 degrees, exactly like the card's own */
  .bk-day {
    padding-left: 0; padding-top: 20px;
    background-image: var(--rule-fade);
    background-size: 100% 1px;
    background-position: top left;
  }
  .bk-slots { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 620px) {
  .bk { gap: 20px; }
  .mode-toggle { display: flex; width: 100%; }
  .mode-toggle__btn { flex: 1 1 0; padding: 0 12px; }
  .bk-slots { grid-template-columns: 1fr 1fr; }
  .bk-foot { gap: 16px; }
  .bk-confirm { width: 100%; max-width: 340px; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
  .mode-toggle__btn, .bk-chip, .bk-seg__btn, .bk-slot, .bk-day-btn, .bk-navbtn { transition: none; }
}

/* ---- "See all articles", at the right end of the section ----------------
   Deliberately the SAME affordance as `.study__go` on a project card: a quiet
   text link with a ringed circular chip that fills dark and flies right on
   hover. The two are the same promise — "there is more of this elsewhere" — so
   they should not be two different-looking controls. The values are copied
   rather than shared because `.study__go` carries a card-specific
   `margin-left:auto` and its hover is keyed on `.study:hover`, not on itself. */
/* no margin-top: this is the second child of .notes__tiers, whose 40px gap is
   already the spacing — adding one stacked to ~66px and read as a stray block */
.notes__all { display: flex; justify-content: flex-end; }
.notes__all-link {
  display: inline-flex; align-items: center; gap: 10px;
  font-family: var(--sans); font-size: 1rem; font-weight: 500; white-space: nowrap;
  color: var(--ink, #16181d); text-decoration: none;
}
.notes__all-chip {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid var(--line-2, rgba(12, 13, 18, .18)); background: transparent;
  transition: background .3s var(--ease, ease), border-color .3s var(--ease, ease),
              transform .35s cubic-bezier(.34, 1.56, .5, 1);
}
.notes__all-chip svg { width: 15px; height: 15px; display: block; }
.notes__all-link:hover .notes__all-chip,
.notes__all-link:focus-visible .notes__all-chip {
  background: var(--ink, #16181d); border-color: var(--ink, #16181d); color: #fff;
  transform: translateX(4px) scale(1.06);
}
@media (prefers-reduced-motion: reduce) {
  .notes__all-chip { transition: none; }
  .notes__all-link:hover .notes__all-chip { transform: none; }
}

/* ---- articles.html: the same card, the same grid, more rows -------------
   `.notes__reads` is unscoped and already carries the 3/2/1 breakpoints, so the
   articles page reuses it rather than growing a second grid that would drift. */
.arts { margin-top: clamp(22px, 3vw, 34px); }
/* the back-pill has no margin of its own, so the page title supplies the gap */
.arts__head { margin-top: clamp(26px, 3.4vw, 40px); }
.arts__head .sec-sub { margin-top: 10px; }
