/* ==========================================================================
   Saranyaa Ramesh — portfolio styles

   Palette is sampled directly out of her Decoding Draupadi artwork:
     deep violet #5a3e91 · lavender #b9a6e3 · porcelain #f7f6fb · mist #ece8f6
   The page alternates cream and ink spreads like a magazine, and headings are
   split across two typefaces so nothing reads as a default card grid.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* A component that sets its own display (.block is a grid, .case is a block)
   would otherwise beat the browser default for [hidden], leaving sections
   that are meant to be switched off still on the page. */
[hidden] { display: none !important; }

:root {
  --fs-base: 17px;
  --font-body: 'Inter', system-ui, -apple-system, sans-serif;
  --font-display: 'Fraunces', Georgia, serif;
  --font-wide: 'Archivo', 'Helvetica Neue', sans-serif;
  --measure: 64ch;
  --page: 1180px;
  --radius: 4px;
  /* Radius scale. Containers take --r-lg, anything sitting inside them drops
     one step, which is what makes nested corners look machined. */
  --r-lg: 15px;
  --r-md: 10px;
  --r-sm: 6px;

  /* Motion. One pair of curves so nothing eases on the browser default:
     --ease for interface moves, --ease-out for things entering the page. */
  --ease: cubic-bezier(.32, .72, 0, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --gap: clamp(1rem, 2.6vw, 1.75rem);

  /* Her artwork colours, kept as fixed brand values. */
  --brand-red: #5a3e91;
  --brand-coral: #b9a6e3;
  --brand-cream: #f7f6fb;
  --brand-blush: #ece8f6;
}

/* ── Cream and ink token sets ─────────────────────────────────────────────
   Declared as reusable blocks so a section can adopt the opposite palette. */
@supports (color: color-mix(in srgb, red, blue)) { /* grouping only */ }

html[data-theme="light"],
html[data-theme="dark"] .ink {
  --bg: #f7f6fb;
  --bg-card: #ffffff;
  --bg-soft: #ece8f6;
  --border: #dfdaea;
  --text: #1c1a24;
  --text-muted: #5c5869;
  --text-dim: #6a6578;
  --accent: #5a3e91;
  --accent-ink: #f7f6fb;
  --accent-soft: #ece8f6;
  --shadow: 0 1px 2px rgba(28, 26, 36, .06), 0 18px 40px -20px rgba(28, 26, 36, .2);
  --hairline: rgba(28, 26, 36, .07);
  --highlight: rgba(255, 255, 255, .75);
  --shadow-tint: rgba(28, 26, 36, .17);
  --glow: rgba(90, 62, 145, .14);
  --case-mark-opacity: .07;
  --grain-blend: multiply;
  --grain-opacity: .34;
}

html[data-theme="dark"],
html[data-theme="light"] .ink {
  --bg: #15131c;
  --bg-card: #1d1a26;
  --bg-soft: #25212f;
  --border: #36313f;
  --text: #f3f1f8;
  --text-muted: #aaa4b9;
  --text-dim: #928ca2;
  --accent: #c7b6ee;
  --accent-ink: #15131c;
  --accent-soft: #2e2840;
  --shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 20px 44px -20px rgba(0, 0, 0, .8);
  --hairline: rgba(255, 255, 255, .055);
  --highlight: rgba(255, 255, 255, .05);
  --shadow-tint: rgba(0, 0, 0, .7);
  --glow: rgba(199, 182, 238, .15);
  --case-mark-opacity: .08;
  --grain-blend: screen;
  --grain-opacity: .12;
}

/* ── Base ─────────────────────────────────────────────────────────────── */
html { font-size: var(--fs-base); scroll-behavior: smooth; }
/* The Reduce motion toggle sets a class on body, which the rules for
   body.a11y-motion * cannot reach back up to html. */
html:has(body.a11y-motion) { scroll-behavior: auto; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  transition: background .3s var(--ease), color .3s var(--ease);
}

/* Film grain. Sits above the page but ignores pointer events, and stays below
   the nav and the floating panels. */
body::after {
  content: '';
  position: fixed;
  inset: -50%;
  z-index: 2;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)' opacity='.55'/%3E%3C/svg%3E");
}

img, svg { max-width: 100%; height: auto; display: block; }

a { color: inherit; text-decoration-color: var(--accent); text-underline-offset: 3px; }
a:hover { color: var(--accent); }

:focus-visible { outline: 2.5px solid var(--accent); outline-offset: 3px; }
/* Headings take focus only so a case study is announced when it opens; they
   are not controls, so they get no ring. */
h1[tabindex="-1"]:focus { outline: none; }

.skip-link {
  position: absolute; left: 1rem; top: 0; z-index: 100;
  padding: .7rem 1.1rem; background: var(--accent); color: var(--accent-ink);
  font-weight: 600;
  /* Moved with transform rather than `top` so showing it cannot cause reflow. */
  transform: translateY(-160%); transition: transform .2s var(--ease);
}
.skip-link:focus { transform: translateY(0); color: var(--accent-ink); }

.wrap { width: 100%; max-width: var(--page); margin: 0 auto; padding: 0 clamp(1.15rem, 4vw, 2.75rem); }

/* ── Split display headings ───────────────────────────────────────────────
   One title, two typefaces, second line indented so it breaks the margin.
   This is the main thing keeping the page from reading as generic. */
.dsplit { display: grid; line-height: .86; margin-bottom: clamp(1.5rem, 4vw, 2.5rem); }

.dsplit-a {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  font-size: clamp(2.7rem, 8.4vw, 5.7rem);
  letter-spacing: -.025em;
  font-variation-settings: 'SOFT' 40, 'WONK' 1;
}

.dsplit-b {
  font-family: var(--font-wide);
  font-style: italic;
  font-weight: 600;
  font-size: clamp(2.1rem, 6.6vw, 4.6rem);
  text-transform: uppercase;
  letter-spacing: -.015em;
  font-variation-settings: 'wdth' 125;
  margin-left: clamp(1.25rem, 9vw, 8rem);
  color: var(--accent);
}

/* Faded, but still 3:1 against the section background (WCAG large text).
   Opacity .3 measured under 2:1 in the light theme. */
.dsplit--muted .dsplit-b { color: var(--text-dim); color: color-mix(in srgb, var(--text) 52%, var(--bg)); }

.eyebrow {
  display: inline-flex; align-items: center; gap: .5rem;
  font-family: var(--font-body);
  font-size: .72rem; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 1rem;
}
.eyebrow::before {
  content: ''; width: 26px; height: 1px; background: currentColor;
}

.section-intro { color: var(--text-muted); max-width: var(--measure); font-size: 1.04rem; }

/* ── Nav ──────────────────────────────────────────────────────────────── */
.nav {
  position: sticky; top: 0; z-index: 40;
  background: var(--bg);
  border-bottom: 1px solid var(--border);
}
.nav-inner { display: flex; align-items: center; justify-content: space-between; gap: 1rem; min-height: 60px; }

/* The wordmark echoes the hero's two-typeface split at nav scale, and the
   mark is her own ten-lobed aster rather than a typed asterisk. */
.nav-logo {
  display: inline-flex; align-items: baseline; gap: .34em;
  text-decoration: none; color: var(--text);
  perspective: 600px;
}
.nav-logo-first {
  font-family: var(--font-display); font-style: italic; font-weight: 500;
  font-size: 1.12rem; letter-spacing: -.015em;
  font-variation-settings: 'SOFT' 60, 'WONK' 1;
}
.nav-logo-last {
  font-family: var(--font-wide);
  font-weight: 700; font-size: .72rem;
  letter-spacing: .16em; text-transform: uppercase;
}
.nav-mark {
  align-self: center; flex: 0 0 auto;
  color: var(--accent);
  transition: transform 1.1s var(--ease);
}
.nav-logo:hover { color: var(--text); }
.nav-logo:hover .nav-mark { transform: rotate(144deg); }
/* A full coin turn on hover or focus. The class is set from app.js and
   cleared on animationend, so leaving mid-turn never snaps it back. */
@keyframes name-flip { to { transform: rotateY(360deg); } }
@media (prefers-reduced-motion: no-preference) {
  .nav-logo-first.is-flipping { animation: name-flip 1.4s var(--ease); }
}
body.a11y-motion .nav-logo-first { animation: none !important; }

.nav-links { display: flex; align-items: center; gap: 1.5rem; }
.nav-links a {
  font-family: var(--font-wide);
  font-size: .72rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-muted); text-decoration: none;
}
.nav-links a:hover { color: var(--accent); }
@media (max-width: 780px) { .nav-links a { display: none; } }

/* The primary action keeps the pill treatment inside the nav. It needs the
   extra specificity because `.nav-links a` would otherwise win on colour,
   and it has to survive the rule above that hides the text links on a phone. */
.nav-links a.nav-cta {
  color: var(--accent-ink);
  padding: .4rem .45rem .4rem .8rem;
  letter-spacing: .1em;
}
.nav-links a.nav-cta:hover { color: var(--accent-ink); }
/* The shared .cta-go pulls itself outward by -.5em to sit flush inside the
   roomier hero pill. In the tighter nav pill that left about 1.5px, so the
   arrow nearly touched the edge. Less pull here only. */
.nav-links a.nav-cta .cta-go { margin-right: -.15em; }
@media (max-width: 780px) { .nav-links a.nav-cta { display: inline-flex; } }
/* Below this the logo and the pill start fighting for the same row. */
@media (max-width: 430px) { .nav-links a.nav-cta { display: none; } }

.icon-btn {
  display: grid; place-items: center; width: 36px; height: 36px; flex: 0 0 auto;
  border: 1px solid var(--border); border-radius: 50%;
  background: transparent; color: var(--text); cursor: pointer;
  transition: border-color .2s var(--ease), color .2s var(--ease);
}
.icon-btn:hover { border-color: var(--accent); color: var(--accent); }
.icon-btn svg { width: 16px; height: 16px; }
html[data-theme="dark"] .icon-sun, html[data-theme="light"] .icon-moon { display: none; }

/* ── Hero ─────────────────────────────────────────────────────────────── */
.hero { position: relative; overflow: hidden; }
.hero > .wrap { position: relative; z-index: 1; }
.hero { padding: clamp(2.5rem, 7vw, 5rem) 0 clamp(2rem, 5vw, 3.5rem); }

.hero-name { display: grid; line-height: .82; margin-bottom: clamp(1.5rem, 4vw, 2.25rem); }
.hero-name .n1 {
  font-family: var(--font-display); font-style: italic; font-weight: 500;
  font-size: clamp(3.4rem, 15vw, 9.5rem);
  letter-spacing: -.04em;
  font-variation-settings: 'SOFT' 60, 'WONK' 1;
}
.hero-name .n2 {
  font-family: var(--font-wide); font-style: italic; font-weight: 700;
  font-size: clamp(2.6rem, 11.5vw, 7.4rem);
  text-transform: uppercase; letter-spacing: -.02em;
  font-variation-settings: 'wdth' 125;
  margin-left: clamp(2rem, 16vw, 13rem);
  color: var(--accent);
}

.hero-grid {
  display: grid;
  grid-template-columns: 1.15fr .85fr;
  gap: clamp(1.75rem, 5vw, 3.5rem);
  align-items: end;
}
@media (max-width: 860px) { .hero-grid { grid-template-columns: 1fr; align-items: start; } }

.hero-body { display: grid; gap: .85rem; }
.hero-body p { max-width: 48ch; color: var(--text-muted); }
/* The opening line carries the whole introduction now, so it is set in the
   display face with the verbs picked out, the way she drew it. */
.hero-hook {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 2.6vw, 1.85rem);
  line-height: 1.28; letter-spacing: -.015em;
  color: var(--text) !important; max-width: 22ch;
  text-wrap: balance;
}
.hero-hook em { font-style: italic; color: var(--accent); font-variation-settings: 'SOFT' 60, 'WONK' 1; }

/* The pen note on "overthink". app.js positions every part in pixels
   relative to .hero-body and adds .is-on after a pause on the hero. Where
   there is no room beside the line (phones) only the circle is drawn. */
.hero-body { position: relative; }
.ponder { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
/* Until app.js has placed it, the loop is stretched over the whole block,
   and the dash offset does not fully hide a stroke stretched that far. */
.ponder:not(.is-on) { visibility: hidden; }
.ponder svg { position: absolute; overflow: visible; fill: none; stroke: var(--accent); stroke-linecap: round; stroke-linejoin: round; }
.ponder path { vector-effect: non-scaling-stroke; stroke-dasharray: 1; stroke-dashoffset: 1; }
.ponder-loop { stroke-width: 2; }
.ponder-arrow { inset: 0; width: 100%; height: 100%; stroke-width: 1.6; }
.ponder-note {
  position: absolute; white-space: nowrap;
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: clamp(.95rem, 1.35vw, 1.12rem); line-height: 1.2;
  font-variation-settings: 'SOFT' 100, 'WONK' 1;
  color: var(--accent);
  opacity: 0; transform: rotate(-4deg) translateY(4px);
}
.ponder.is-on .ponder-loop path { stroke-dashoffset: 0; transition: stroke-dashoffset .85s var(--ease-out); }
.ponder.is-on .ponder-arrow path { stroke-dashoffset: 0; transition: stroke-dashoffset .5s var(--ease-out) .7s; }
.ponder.is-on .ponder-note { opacity: 1; transform: rotate(-4deg); transition: opacity .35s var(--ease-out) 1s, transform .45s var(--ease-out) 1s; }
.ponder.no-room .ponder-arrow, .ponder.no-room .ponder-note { display: none; }

/* Arched portrait on a blush ground, framed like the editorial references. */
.portrait-frame { position: relative; justify-self: end; width: min(100%, 330px); }
@media (max-width: 860px) { .portrait-frame { justify-self: start; width: min(100%, 270px); } }

.portrait-frame::before {
  content: '';
  position: absolute; inset: -10px;
  transform: translate(14px, 14px);
  border-radius: 999px 999px var(--radius) var(--radius);
  background: linear-gradient(170deg, var(--brand-blush), var(--brand-coral));
}
.portrait {
  position: relative;
  width: 100%; aspect-ratio: 4 / 5; object-fit: cover; object-position: center 18%;
  border-radius: 999px 999px var(--radius) var(--radius);
}

/* Floating labels, echoing the pills in the reference. */
.tag {
  position: absolute; z-index: 3;
  padding: .34rem .72rem;
  background: var(--text); color: var(--bg);
  font-family: var(--font-wide);
  font-size: .62rem; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase;
  white-space: nowrap;
  box-shadow: var(--shadow);
}
.tag--1 { top: 8%; left: -18%; }
.tag--2 { top: 42%; right: -14%; background: var(--accent); color: var(--accent-ink); }
.tag--3 { bottom: 16%; left: -14%; }
@media (max-width: 860px) {
  .tag--1 { left: auto; right: -6%; top: 4%; }
  .tag--3 { left: auto; right: -6%; bottom: 8%; }
  .tag--2 { right: auto; left: 2%; }
}
@media (max-width: 460px) { .tag { display: none; } }

/* Decorative shapes drawn from her own artwork. */
.decor { position: absolute; z-index: 0; pointer-events: none; opacity: .9; }
.decor--burst { width: clamp(220px, 40vw, 520px); top: -12%; right: -4%; opacity: .1; }
.decor--aster { width: clamp(52px, 7vw, 96px); top: 11%; right: 30%; opacity: .9; }
@media (max-width: 860px) { .decor--aster { display: none; } }

.hero-cta { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1.6rem; }
/* Five links have to share one line on a laptop, so these run tighter than a
   default pill. They still wrap onto a second row on narrow screens. */
.hero-cta .pill { padding: .5rem .72rem; letter-spacing: .07em; }

.pill {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .5rem 1rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-card);
  font-family: var(--font-wide);
  font-size: .72rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  text-decoration: none;
  transition: border-color .2s var(--ease), color .2s var(--ease), transform .2s var(--ease);
}
.pill:hover { border-color: var(--accent); color: var(--accent); transform: translateY(-2px); }
.pill--solid { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.pill--solid:hover { color: var(--accent-ink); filter: brightness(1.1); }

/* Label with a rule running out to the right, like a legend. */
.strip-label {
  display: flex; align-items: center; gap: 1rem;
  font-family: var(--font-wide); font-size: .63rem; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; color: var(--text-dim);
  margin-bottom: clamp(.9rem, 2vw, 1.3rem);
}
.strip-label::after { content: ''; flex: 1 1 auto; height: 1px; background: var(--border); }

/* Capsules are taxonomy, not navigation. They carry no border and never
   respond to hover, which is what keeps them from reading as buttons next
   to the outlined nav pills and the solid call to action. */
.spaces { display: flex; flex-wrap: wrap; gap: .5rem; list-style: none; }
.space {
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .5rem .95rem;
  border-radius: 999px;
  background: var(--accent-soft);
  font-family: var(--font-wide); font-size: .67rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--text);
}
.space svg { color: var(--accent); flex: 0 0 auto; }

.doing { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); list-style: none; }
.doing-item { padding-inline: clamp(.9rem, 2.2vw, 1.75rem); border-left: 1px solid var(--border); }
.doing-item:first-child { padding-left: 0; border-left: 0; }
.doing-icon { color: var(--accent); margin-bottom: .55rem; }
.doing-item h3 {
  font-family: var(--font-wide); font-size: .71rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; margin-bottom: .35rem;
}
.doing-item p { color: var(--text-muted); font-size: .87rem; }

@media (max-width: 880px) {
  .doing { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: clamp(1.4rem, 3vw, 2rem); }
  .doing-item:nth-child(odd) { padding-left: 0; border-left: 0; }
}
@media (max-width: 540px) {
  .doing { grid-template-columns: 1fr; }
  .doing-item { padding-inline: 0; border-left: 0; }
  /* Five capsules stacked would be a very tall column, so they scroll.
     Measured at 390px: 927px of capsules in a 351px window, so three of the
     five sit off-screen. The scrollbar is hidden, so without the mask below
     there was nothing at all to suggest they existed -- a phone visitor
     simply never saw three of the areas she has worked in.
     A mask rather than an overlay, so one declaration covers both themes
     instead of needing a gradient per background colour. The fade is on the
     trailing edge only: a visitor starts at scroll zero, so that is the edge
     that has to say "there is more". */
  .spaces {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    padding-bottom: .3rem; padding-right: 1.5rem;
    -webkit-mask-image: linear-gradient(to right, #000 0 84%, transparent 100%);
    mask-image: linear-gradient(to right, #000 0 84%, transparent 100%);
  }
  .spaces::-webkit-scrollbar { display: none; }
  .spaces::-webkit-scrollbar { display: none; }
  .space { flex: 0 0 auto; }
}

/* ── Cursor glow ──────────────────────────────────────────────────────────
   A soft light under the pointer. It sits at z-index 1, below the grain, so
   the grain textures it. Only the layer transform changes each frame, so the
   gradient itself never repaints. Desktop pointers only: a touch device has
   no cursor, which is also what keeps the cost off phones entirely. */
.cursor-glow { display: none; }
@media (hover: hover) and (pointer: fine) {
  .cursor-glow {
    display: block;
    position: fixed; top: 0; left: 0;
    width: 820px; height: 820px; margin: -410px 0 0 -410px;
    z-index: 1; pointer-events: none;
    will-change: transform;
    opacity: 0; transition: opacity .45s var(--ease);
    background: radial-gradient(circle closest-side,
      var(--glow),
      color-mix(in srgb, var(--glow) 80%, transparent) 22%,
      color-mix(in srgb, var(--glow) 50%, transparent) 42%,
      color-mix(in srgb, var(--glow) 22%, transparent) 62%,
      color-mix(in srgb, var(--glow) 6%, transparent) 82%,
      transparent);
  }
  .cursor-glow.is-on { opacity: 1; }
}
body.a11y-motion .cursor-glow { display: none !important; }
@media (prefers-reduced-motion: reduce) { .cursor-glow { display: none !important; } }

/* ── Sections ─────────────────────────────────────────────────────────── */
.section { position: relative; padding: clamp(2.75rem, 7vw, 5.5rem) 0; scroll-margin-top: 64px; }
/* The two intro strips read as one block with the hero, so they run on a
   tighter rhythm. This has to sit after .section to win on source order. */
.section--tight { padding: clamp(1.1rem, 2.6vw, 1.9rem) 0; }
.section.ink { overflow: hidden; }
.section > .wrap { position: relative; z-index: 1; }
.ink { background: var(--bg); color: var(--text); }
.section--hair { border-top: 1px solid var(--border); }
.section-head { margin-bottom: clamp(1.5rem, 4vw, 2.5rem); }

/* ── Marquee ──────────────────────────────────────────────────────────── */
.marquee {
  background: var(--bg); color: var(--text);
  padding: .85rem 0;
  overflow: hidden;
  display: flex;
  user-select: none;
}
.marquee-track {
  display: flex; flex: 0 0 auto; gap: 2.5rem; padding-right: 2.5rem;
  animation: marquee 34s linear infinite;
}
.marquee span {
  font-family: var(--font-wide);
  font-size: clamp(.82rem, 2vw, 1.08rem); font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase;
  white-space: nowrap;
  display: inline-flex; align-items: center; gap: 2.5rem;
}
.marquee span::after { content: '✳'; color: var(--accent); font-size: .9em; }

@keyframes marquee { to { transform: translateX(-100%); } }

/* ── Stat band ────────────────────────────────────────────────────────── */
/* By the numbers: a violet ribbon laid across the band, tilted, with a
   fainter echo crossing behind it, each carrying its own share of the
   figures. app.js sets --slide (0 to 1) as the band scrolls past; each row
   repeats its set enough times that the visible stretch never runs out. */
.ribbon-band { overflow: clip; padding-bottom: clamp(4rem, 9vw, 7rem); }
.ribbon {
  position: relative; z-index: 1;
  width: 120vw; margin: clamp(1.6rem, 4vw, 2.6rem) 0 0 -10vw;
  padding: clamp(.7rem, 1.6vw, 1.1rem) 0;
  background: var(--accent); color: var(--accent-ink);
  transform: rotate(-2.5deg); white-space: nowrap;
}
.ribbon--echo {
  z-index: 0; margin-top: clamp(.6rem, 1.6vw, 1.4rem);
  background: transparent; color: var(--accent);
  border-block: 1px solid var(--accent);
  transform: rotate(1.5deg);
}
/* Each set drifts --travel across the band, starting --lead in from the
   left, so on wide screens a whole set stays in view the entire time.
   Percentages are of one set, which is why the sets move rather than the
   row. Phones cannot fit a set, so there it travels far enough to cycle. */
.ribbon { --travel: 22vw; --lead: 3vw; }
.ribbon-row { display: inline-flex; }
.ribbon-set {
  display: inline-flex; align-items: baseline; gap: clamp(1rem, 2vw, 1.8rem); padding-right: clamp(1rem, 2vw, 1.8rem);
  transform: translate3d(calc(-200% + 10vw + var(--lead) + var(--travel) - var(--slide, 0) * var(--travel)), 0, 0);
  will-change: transform;
}
.ribbon--echo .ribbon-set { transform: translate3d(calc(-200% + 10vw + var(--lead) + var(--slide, 0) * var(--travel)), 0, 0); }
@media (max-width: 760px) { .ribbon { --travel: 150vw; } }
.ribbon-set > span { display: inline-flex; align-items: center; gap: .7rem; }
.ribbon b {
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  font-size: clamp(2rem, 3.8vw, 3.6rem); line-height: 1; letter-spacing: -.03em;
  font-variation-settings: 'SOFT' 60, 'WONK' 1;
}
.ribbon small {
  font-family: var(--font-wide); font-weight: 700; line-height: 1.2;
  font-size: clamp(.68rem, .95vw, .92rem); letter-spacing: .12em; text-transform: uppercase;
}
.ribbon i { font-style: normal; font-size: clamp(1rem, 1.8vw, 1.5rem); }
/* With motion off the ribbons hold still, so each shows its figures once,
   wrapped to fit. */
@media (prefers-reduced-motion: reduce) {
  .ribbon { white-space: normal; }
  .ribbon-set[aria-hidden], .ribbon i { display: none; }
  .ribbon-row, .ribbon-set { display: flex; flex-wrap: wrap; justify-content: center; row-gap: .3rem; column-gap: clamp(1.6rem, 3.6vw, 3rem); }
  .ribbon-row { padding: 0 calc(10vw + 1.5rem); }
  .ribbon .ribbon-set { transform: none; }
}
body.a11y-motion .ribbon { white-space: normal; }
body.a11y-motion .ribbon-set[aria-hidden], body.a11y-motion .ribbon i { display: none; }
body.a11y-motion .ribbon-row, body.a11y-motion .ribbon-set { display: flex; flex-wrap: wrap; justify-content: center; row-gap: .3rem; column-gap: clamp(1.6rem, 3.6vw, 3rem); }
body.a11y-motion .ribbon-row { padding: 0 calc(10vw + 1.5rem); }
body.a11y-motion .ribbon-set { transform: none; }

/* ── Selected work: the stack ───────────────────────────────────────────
   Every card sticks just under the nav, each a step lower than the last, and
   the next one slides up over it. app.js writes --cover (how much of a card
   the next one hides) and --enter (how far a card is from settling); with
   motion off neither is written and the cards are a plain list. */
.stack { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(1.25rem, 3vw, 2rem); }
.stack-item { --stick: calc(61px + clamp(.75rem, 2vw, 1.25rem) + var(--i) * 14px); }

/* An editorial sheet rather than a box: the page's own ground, square cut,
   the image flush to the edge, and one rule across the top. Stacked, the
   rules of the cards underneath read as page edges. The shadow falls upward
   only, so it shows where a card overlaps the one beneath and nowhere else. */
.stack-card {
  position: relative; isolation: isolate; overflow: hidden;
  display: grid; grid-template-columns: 1.05fr .95fr; align-items: center;
  gap: clamp(1.5rem, 4vw, 3.5rem);
  background: var(--bg); color: inherit; text-decoration: none;
  border-top: 1px solid var(--text);
  box-shadow: 0 -22px 28px -26px var(--shadow-tint);
  transform-origin: 50% 0;
}
.stack-card:hover { color: inherit; }
.stack-card:hover .stack-media img { transform: scale(1.015); }
.stack-card:hover .work-card-more { color: var(--accent); }
@media (max-width: 800px) { .stack-card { grid-template-columns: 1fr; align-content: start; gap: 0; } }

/* Dims a card as the next one covers it. A layer rather than a filter, so it
   stays on the compositor. */
.stack-card::after {
  content: ''; position: absolute; inset: 0; z-index: 2;
  background: var(--bg); opacity: 0;
  pointer-events: none;
}

.stack-media { position: relative; z-index: 1; overflow: hidden; align-self: start; }
.stack-media img {
  display: block; width: 100%; aspect-ratio: 16 / 11; height: auto; max-height: min(52vh, 440px);
  object-fit: cover; object-position: top center;
  transition: transform .4s var(--ease);
}
.stack-media--logo { display: grid; place-items: center; background: #000; aspect-ratio: 16 / 11; max-height: min(52vh, 440px); }
.stack-media--logo img { width: 52%; aspect-ratio: 1; max-height: none; }
@media (max-width: 800px) {
  .stack-media img, .stack-media--logo { aspect-ratio: 16 / 10; max-height: 34vh; }
  .stack-media--logo img { width: auto; height: 80%; max-height: none; }
}

.stack-body { position: relative; z-index: 1; display: flex; flex-direction: column; gap: .65rem; justify-content: center; padding: clamp(1.25rem, 3vw, 2rem) 0; }
@media (max-width: 800px) { .stack-body { padding: 1.1rem 0 1.4rem; } }
.stack-body h3 { font-family: var(--font-display); font-size: clamp(2rem, 4.2vw, 3.2rem); font-weight: 500; letter-spacing: -.02em; line-height: 1.05; }
.stack-body p { color: var(--text-muted); font-size: .96rem; }
.stack-body .brand-mark { width: 40px; height: 40px; border-radius: 11px; }

/* The brand's own mark, tinted and bleeding off the card edge like the case
   study headers. It trails the card as it settles, so it reads as further
   back than the card it sits in. */
.stack-mark {
  --size: clamp(220px, 30vw, 420px);
  position: absolute; z-index: 0; right: calc(var(--size) * -.32); top: 50%;
  width: var(--size); height: var(--size); margin-top: calc(var(--size) / -2);
  background: var(--accent); opacity: var(--case-mark-opacity);
  -webkit-mask: var(--mark) center / contain no-repeat;
  mask: var(--mark) center / contain no-repeat;
  pointer-events: none;
}

.feature-kicker { font-family: var(--font-wide); font-size: .66rem; font-weight: 700; letter-spacing: .15em; text-transform: uppercase; color: var(--accent); }
.feature-metrics { display: flex; gap: 1.5rem; margin-top: .3rem; }
.feature-metrics div b { font-family: var(--font-display); font-size: 1.5rem; display: block; line-height: 1; }
.feature-metrics div span { font-size: .76rem; color: var(--text-dim); }

/* Sticking needs a screen tall enough to hold a whole card under the nav, so
   short landscape phones and reduced motion keep the plain list. */
@media (prefers-reduced-motion: no-preference) and (min-height: 640px) {
  .stack { gap: 0; padding-bottom: clamp(.5rem, 3vh, 2rem); }
  .stack-item { position: sticky; top: var(--stick); margin-bottom: clamp(3rem, 12vh, 7rem); }
  .stack-item:last-child { margin-bottom: 0; }
  /* app.js sets --card-h to the tallest card, so a taller card underneath
     never shows its bottom edge below a shorter one on top. */
  .stack-card { min-height: var(--card-h, 0); transform: scale(calc(1 - var(--cover, 0) * .05)); }
  .stack-card::after { opacity: calc(var(--cover, 0) * .5); }
  .stack-mark { transform: translate3d(0, calc(var(--enter, 0) * 70px), 0) rotate(calc(var(--enter, 0) * -10deg)); }
}
body.a11y-motion .stack { gap: clamp(1.25rem, 3vw, 2rem); padding-bottom: 0; }
body.a11y-motion .stack-item { position: static; margin-bottom: 0; }
body.a11y-motion .stack-card { transform: none; }
body.a11y-motion .stack-card::after { opacity: 0; }
body.a11y-motion .stack-mark { transform: none; }

/* Client marks sit on their own brand colour, so they keep a light backing in
   both themes rather than taking on the page palette. */
.brand-title { display: flex; align-items: center; gap: .6rem; }
.brand-mark {
  flex: 0 0 auto; width: 32px; height: 32px;
  border-radius: 9px; background: #fff; object-fit: cover;
  box-shadow: 0 0 0 1px var(--border);
}
.brand-mark--case { display: block; width: 44px; height: 44px; border-radius: 12px; margin-bottom: 1rem; }

.work-card-more { font-family: var(--font-wide); font-size: .66rem; font-weight: 700; letter-spacing: .13em; text-transform: uppercase; color: var(--accent); margin-top: .4rem; }

.badge-soon {
  display: inline-block; padding: .1rem .45rem; vertical-align: .2em;
  background: var(--accent-soft); color: var(--accent);
  font-family: var(--font-wide); font-size: .58rem; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase;
}

.shots { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); gap: var(--gap); margin-top: .35rem; }
.shots--tight { grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr)); }
.shots--one { grid-template-columns: minmax(0, 460px); }

/* Images sit directly on the page. Framing every one in a bordered card was
   what made the layout feel boxy. */
figure { background: none; border: 0; }

.shot { width: 100%; cursor: zoom-in; border-radius: var(--r-md); transition: transform .4s var(--ease), opacity .2s var(--ease); }
.shot:hover { opacity: .94; }
.shot--cover { aspect-ratio: 16 / 10; object-fit: cover; object-position: top center; }
.shot--square { aspect-ratio: 1 / 1; object-fit: cover; }
.shot--tall { aspect-ratio: 3 / 4; object-fit: cover; object-position: top center; }
/* For screenshots that must not lose their edges to a crop. */
.shot--fit { aspect-ratio: 16 / 10; object-fit: contain; object-position: top center; background: none; }
.shot--fit-tall { aspect-ratio: 3 / 4; object-fit: contain; object-position: top center; background: none; }
.shot--wide { aspect-ratio: 2 / 1; object-fit: cover; object-position: top center; }
.shot--left { object-position: top left; }
.shot-link { display: block; border-radius: var(--r-md); }
.shot-link .shot { cursor: pointer; }

/* Screenshots of very different shapes sat at different heights, so the row
   looked ragged. A shared surface gives them one silhouette and lines the
   captions up, without cropping anything out of the picture. */
.shots--slides figure { background: var(--bg-card); border-radius: var(--r-lg); padding: .7rem .7rem .15rem; }
.shots--slides .shot { object-position: center; border-radius: var(--r-sm); }
.shots--slides figcaption { padding: .55rem .15rem .25rem; }

/* Hovering one launch tile lifts it while the others fall back, so the one
   being read is clearly the front-most. Hover-capable pointers only, so a
   phone never gets a stuck blurred state from a tap. */
@media (hover: hover) and (pointer: fine) {
  .shots--slides figure {
    position: relative;
    transition: transform .4s var(--ease), filter .4s var(--ease), opacity .4s var(--ease);
  }
  .shots--slides figure:hover { transform: translateY(-5px) scale(1.02); z-index: 1; }
  .shots--slides:hover figure:not(:hover) { filter: blur(2px); opacity: .55; }
}

figcaption {
  padding: .6rem 0 0;
  font-family: var(--font-wide); font-size: .66rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim);
}

/* ── Contact: a chat with Anya, and her channels in orbit ──────────────── */
/* Registered so the burst and the scroll spin can each transition on their
   own, without one restarting the other. */
@property --rad { syntax: '<length>'; inherits: true; initial-value: 0px; }
@property --burst { syntax: '<angle>'; inherits: true; initial-value: 0deg; }

.cx-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-end; gap: 1rem 3rem; }
.cx-head .section-intro { max-width: 20rem; margin: 0 0 .6rem; }

.cx {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  grid-template-areas: "top orbit" "thread orbit" "compose orbit";
  grid-template-rows: auto 1fr auto;
  background: var(--bg-card);
  border-radius: 30px;
  box-shadow: 0 0 0 1px var(--hairline), 0 50px 90px -50px var(--shadow-tint);
  overflow: hidden;
}

.cx-top { grid-area: top; display: flex; gap: .85rem; align-items: center; padding: 1.1rem 1.6rem; border-bottom: 1px solid var(--border); }
.cx-top img { width: 44px; height: 44px; border-radius: 50%; object-fit: cover; object-position: top; }
.cx-top p { margin: 0; }
.cx-top b { display: block; font-size: 1rem; }
.cx-top span { display: flex; align-items: center; gap: .4rem; font-size: .78rem; color: var(--text-dim); }
.cx-top span::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: #3fb36b; }

.cx-thread {
  grid-area: thread;
  display: flex; flex-direction: column; align-items: flex-start; gap: .6rem;
  margin: 0; padding: 1.6rem; list-style: none;
}
.cx-b {
  max-width: 82%;
  padding: .8rem 1.15rem;
  border-radius: 22px 22px 22px 6px;
  background: var(--bg-soft); color: var(--text);
  font-size: 1.02rem; line-height: 1.5;
}
.cx-b--cue { background: var(--accent); color: var(--accent-ink); font-weight: 500; }
.cx-b--me { align-self: flex-end; border-radius: 22px 22px 6px 22px; background: var(--accent); color: var(--accent-ink); white-space: pre-line; }
.cx-pt { display: inline-block; margin-left: .45rem; }
.cx-receipt { align-self: flex-end; margin: -.2rem .4rem 0 0; font-size: .74rem; color: var(--text-dim); }
.cx-send:disabled, .cx-send[aria-disabled="true"] { cursor: default; filter: none; transform: none; }
.cx-compose input:read-only, .cx-compose textarea:read-only { color: var(--text-dim); }
/* The placeholders are the only visible labels, so they need text contrast;
   the browser default grey fell to about 3.7:1 on the soft field background. */
.cx-compose ::placeholder { color: var(--text-dim); opacity: 1; }
.cx-compose .compose-hint a { color: var(--accent); }
.copy-flag {
  display: inline-block; margin-left: .5em; padding: .1em .55em; border-radius: 999px;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--font-wide); font-size: .62rem; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
}
@media (prefers-reduced-motion: no-preference) { .copy-flag { animation: cx-pop .35s var(--ease-out) both; } }
.cx-typing { display: flex; gap: 5px; padding: 1rem 1.1rem; border-radius: 22px 22px 22px 6px; background: var(--bg-soft); }
.cx-typing i { width: 7px; height: 7px; border-radius: 50%; background: var(--text-muted); opacity: .45; }

.cx-compose { grid-area: compose; padding: 1rem 1.2rem 1.1rem; border-top: 1px solid var(--border); }
.cx-row { display: flex; gap: .6rem; align-items: flex-end; }
.cx-compose input, .cx-compose textarea {
  min-width: 0; padding: .72rem 1.1rem;
  border: 1px solid var(--border); border-radius: 22px;
  background: none; color: var(--text);
  font: inherit; font-size: .92rem; line-height: 1.4;
  transition: border-color .2s var(--ease);
}
.cx-compose input { flex: 0 1 9.5rem; background: var(--bg-soft); border-color: transparent; }
.cx-compose textarea { flex: 1; resize: none; height: calc(1.4em + 1.44rem + 2px); max-height: 9rem; overflow-y: auto; }
.cx-compose input:focus, .cx-compose textarea:focus { outline: 0; border-color: var(--accent); }
.cx-compose input:focus-visible, .cx-compose textarea:focus-visible { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent); }
.cx-send { flex: 0 0 auto; padding: .78rem 1.2rem; }
.cx-compose .compose-hint { margin: .7rem 0 0 .3rem; }
.cx-compose:not(.is-direct) .cx-email { display: none; }
.cx-compose.is-direct .cx-row {
  display: grid; align-items: end;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr) auto;
  grid-template-areas: "name email email" "note note send";
}
.cx-compose.is-direct #cName { grid-area: name; }
.cx-compose.is-direct #cEmail { grid-area: email; }
.cx-compose.is-direct #cNote { grid-area: note; }
.cx-compose.is-direct .cx-send { grid-area: send; }
.cx-compose.is-direct input { flex: none; }
.cx-compose input#cEmail { background: var(--bg-soft); border-color: transparent; }
.cx-compose .field-error { margin: .6rem 0 0 .3rem; }
.cx-compose .field-error a { color: inherit; }

/* The orbit. Every size comes off --w so the ring, the planets and their
   type shrink together on a phone instead of colliding. */
.cx-orbit {
  grid-area: orbit;
  display: grid; place-items: center;
  padding: 1.4rem;
  border-left: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg-soft) 55%, var(--bg-card));
  container-type: inline-size;
}
.mo { --w: min(25rem, calc(100cqw - 1rem)); --spin: 0deg; position: relative; width: var(--w); aspect-ratio: 1; }
.mo-ring { position: absolute; inset: 12.4%; border: 1.5px dashed var(--border); border-radius: 50%; transition: border-color .3s var(--ease); }
.mo:has(a:hover, a:focus-visible) .mo-ring { border-color: color-mix(in srgb, var(--accent) 50%, transparent); }
.mo-aster {
  position: absolute; inset: 33%; width: 34%;
  transform: translate(var(--ax, 0px), var(--ay, 0px)) rotate(calc(14deg - var(--spin) * 1.5 + var(--aim, 0deg)));
  transition: transform .8s cubic-bezier(.3, 1.3, .5, 1);
}
.mo-hi {
  position: absolute; left: 50%; top: 50%;
  translate: calc(-50% + var(--ax, 0px) * .6) calc(-50% + var(--ay, 0px) * .6);
  transition: translate .8s cubic-bezier(.3, 1.3, .5, 1);
  padding: .35rem .8rem; border-radius: 999px;
  background: var(--bg-card); box-shadow: 0 0 0 1px var(--hairline);
  font-family: var(--font-display); font-style: italic; font-size: 1.2rem; line-height: 1; white-space: nowrap; color: var(--text);
}
.mo-list { margin: 0; padding: 0; list-style: none; }
.mo-b {
  --a: calc(var(--i) * 60deg - 90deg + var(--spin) + var(--burst));
  --rad: calc(var(--w) * .376);
  position: absolute; left: 50%; top: 50%;
  transform: rotate(var(--a)) translateX(var(--rad)) rotate(calc(-1 * var(--a))) translate(-50%, -50%);
}
.mo-b a {
  display: grid; place-content: center; text-align: center;
  width: calc(var(--w) * .264); aspect-ratio: 1; border-radius: 50%;
  background: var(--bg-card); color: var(--text); text-decoration: none;
  box-shadow: 0 0 0 1px var(--hairline), 0 16px 30px -18px var(--shadow-tint);
  transition: transform .35s var(--ease), box-shadow .35s var(--ease), background-color .3s var(--ease), color .3s var(--ease);
}
.mo-b a:hover, .mo-b a:focus-visible { transform: scale(1.12); box-shadow: 0 0 0 1px var(--hairline), 0 22px 36px -16px var(--shadow-tint); }
.mo-b a:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; }
.mo-b b { font-family: var(--font-display); font-weight: 400; font-size: clamp(.8rem, calc(var(--w) * .041), 1.02rem); }
.mo-b small { font-size: .62rem; line-height: 1.3; color: var(--text-dim); padding: 0 .3rem; }
.mo-b.is-sel a, .mo-b a:hover { background: var(--accent); color: var(--accent-ink); }
.mo-b.is-sel small, .mo-b a:hover small { color: inherit; }
@container (max-width: 22rem) { .mo-b small { display: none; } .mo-hi { font-size: 1rem; } }

/* Armed by the script only, so without it the finished conversation shows.
   Bubbles wait their turn, and the planets sit folded inside the aster
   until the cue bubble points at them. */
@media (prefers-reduced-motion: no-preference) {
  body:not(.a11y-motion) .cx.is-armed .cx-thread > :not(.is-in) { display: none; }
  body:not(.a11y-motion) .cx.is-armed .cx-thread > .is-in { animation: cx-pop .45s var(--ease-out) both; }
  .cx-typing.is-in i { animation: cx-dot 1.1s ease-in-out infinite; }
  .cx-typing.is-in i:nth-child(2) { animation-delay: .15s; }
  .cx-typing.is-in i:nth-child(3) { animation-delay: .3s; }
  body:not(.a11y-motion) .cx.is-armed .mo-b {
    transition: --rad .9s cubic-bezier(.3, 1.35, .5, 1), --burst 1.1s var(--ease-out), opacity .4s var(--ease);
    transition-delay: calc(var(--i) * 70ms);
  }
  body:not(.a11y-motion) .cx.is-armed:not(.is-out) .mo-b { --rad: 0px; --burst: -70deg; opacity: 0; }
  body:not(.a11y-motion) .cx.is-armed:not(.is-out) .mo-b a { transform: scale(.4); }
  .cx-b--me { animation: cx-pop .45s var(--ease-out) both; }
  .cx-send { transition: filter .2s var(--ease), transform .2s var(--ease), translate .35s var(--ease); }
  @keyframes cx-pop { from { opacity: 0; transform: translateY(10px) scale(.94); transform-origin: left bottom; } }
  @keyframes cx-dot { 0%, 60%, 100% { transform: none; opacity: .45; } 30% { transform: translateY(-4px); opacity: .9; } }
}

@media (max-width: 860px) {
  .cx { grid-template-columns: minmax(0, 1fr); grid-template-areas: "top" "thread" "orbit" "compose"; grid-template-rows: none; }
  .cx-orbit { border-left: 0; border-top: 1px solid var(--border); }
  .cx-pt { rotate: 90deg; }
  .cx-thread { padding: 1.2rem; }
  .cx-b { max-width: 92%; font-size: .96rem; }
}
@media (max-width: 560px) {
  .cx-row { flex-wrap: wrap; }
  .cx-compose input, .cx-compose textarea { flex: 1 1 100%; }
  .cx-send { margin-left: auto; }
  .cx-compose.is-direct .cx-row { grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "name name" "email email" "note note" ". send"; }
}

.btn {
  appearance: none; border: 1px solid var(--accent); background: var(--accent); color: var(--accent-ink);
  padding: .7rem 1.4rem;
  font-family: var(--font-wide); font-size: .72rem; font-weight: 700;
  letter-spacing: .13em; text-transform: uppercase;
  cursor: pointer; justify-self: start;
  border-radius: 999px;
  display: inline-flex; align-items: center;
  transition: filter .2s var(--ease), transform .2s var(--ease);
}
.btn:hover { filter: brightness(1.1); transform: translateY(-2px); }

/* Button-in-button. The arrow never floats naked beside the label; it sits in
   its own disc, pulled flush with the button's right padding, and leans on
   hover so the whole control feels like it has internal tension. */
.cta-go {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.55em; height: 1.55em; flex: 0 0 auto;
  margin-left: .55em; margin-right: -.5em;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent-ink) 22%, transparent);
  transition: transform .3s var(--ease), background .3s var(--ease);
}
/* 12px against a viewBox of 24 halves the stroke, so this arrow lands on the
   same 1.5px optical weight as every other icon on the page. */
.cta-go svg { width: 12px; height: 12px; }
.pill--solid:hover .cta-go, .btn:hover .cta-go {
  transform: translateX(2px);
  background: color-mix(in srgb, var(--accent-ink) 34%, transparent);
}
/* color-mix is recent; this keeps the disc visible on older browsers. */
@supports not (background: color-mix(in srgb, red 20%, transparent)) {
  .cta-go { background: rgba(255, 255, 255, .22); }
}
.compose-hint { font-size: .78rem; color: var(--text-dim); }

/* ── Footer ───────────────────────────────────────────────────────────── */
.footer { background: var(--bg); color: var(--text); padding: 2.25rem 0 2.75rem; border-top: 1px solid var(--border); }
.footer-inner {
  display: flex; flex-wrap: wrap; gap: .5rem 1.25rem; align-items: center; justify-content: space-between;
  font-family: var(--font-wide); font-size: .68rem; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--text-dim);
}

/* ── Case studies ─────────────────────────────────────────────────────── */
.case { position: relative; overflow-x: clip; padding: clamp(2rem, 6vw, 3.5rem) 0 1rem; }
/* The client's mark, oversized and tinted in the accent, half off the right
   edge behind the case header. A mask rather than an <img> so one colour
   token recolours all four marks in both themes. */
.case-mark {
  --size: clamp(320px, 46vw, 660px);
  position: absolute; top: clamp(1rem, 4vw, 2.5rem); right: calc(var(--size) * -.42);
  width: var(--size); height: var(--size);
  background: var(--accent); opacity: var(--case-mark-opacity);
  -webkit-mask: var(--mark) center / contain no-repeat;
  mask: var(--mark) center / contain no-repeat;
  pointer-events: none;
}
.case-mark--morphic { --mark: url("mark-morphic.svg"); }
.case-mark--headout { --mark: url("mark-headout.svg"); }
.case-mark--yaas { --mark: url("mark-yaas.png"); }
.case-mark--decoding-draupadi { --mark: url("mark-decoding-draupadi.png"); }
@media (max-width: 700px) { .case-mark { --size: 300px; right: -150px; top: .5rem; } }

.case-back {
  display: inline-flex; align-items: center; gap: .4rem;
  margin-bottom: 1.75rem; padding: .45rem .95rem .45rem 0;
  font-family: var(--font-wide); font-size: .66rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase;
  color: var(--text-muted); text-decoration: none;
}
.case-back:hover { color: var(--accent); }

.case-tagline { font-family: var(--font-wide); color: var(--accent); font-size: .7rem; font-weight: 700; letter-spacing: .15em; text-transform: uppercase; margin-bottom: .9rem; }
.case-summary { font-size: 1.16rem; color: var(--text-muted); max-width: var(--measure); }

.metrics { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 1.9rem; }
.metric { flex: 1 1 165px; padding: .1rem 0 .1rem 1rem; border-left: 2px solid var(--accent); min-width: 0; }
.metric b { display: block; font-family: var(--font-display); font-weight: 500; font-size: 2rem; line-height: 1; }
.metric span { font-size: .82rem; color: var(--text-muted); }

.case-section { margin-top: clamp(2rem, 5vw, 3rem); }
.case-section > h2 {
  font-family: var(--font-wide); font-size: .78rem; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--accent);
  margin-bottom: 1rem;
}
.case-section p { max-width: var(--measure); color: var(--text-muted); }
.case-section p + p { margin-top: .8rem; }
.case-section .shots + p, .case-section p + .shots { margin-top: 1.1rem; }
.case-section > ul { max-width: var(--measure); margin: .2rem 0 0 1.1rem; display: grid; gap: .35rem; color: var(--text-muted); }
/* Instagram's player is a header, a 4:5 video frame and a likes footer. The
   frame scales with the width, the header and footer do not, so the height
   is the width times 1.25 plus a fixed allowance; cqw keeps that true when a
   narrow phone squeezes the column below 340px. */
.reels { display: flex; flex-wrap: wrap; gap: var(--gap); margin-top: 1rem; }
.reel {
  width: min(100%, 340px); container-type: inline-size;
  border-radius: var(--r-lg); overflow: hidden; background: #fff;
  box-shadow: 0 0 0 1px var(--hairline), 0 16px 32px -26px var(--shadow-tint);
}
.reel iframe { display: block; width: 100%; height: 635px; height: calc(125cqw + 210px); border: 0; }
.case-section .reels-links { margin-top: .9rem; font-size: .88rem; color: var(--text-dim); }

/* Numbered points inside a case study — "01 | HOW I FOUND THE RIGHT CREATORS"
   and the question-and-answer blocks. Without this they ran together, because
   the heading and the paragraph had nothing separating one point from the next. */
.creator-partnerships { margin-top: .25rem; }
.creator-point + .creator-point,
.brief-point + .brief-point,
.creator-partnerships + .creator-point,
.brief-point + .creator-point { margin-top: clamp(1.6rem, 3.5vw, 2.4rem); }
.creator-point h3, .brief-point h3 {
  font-family: var(--font-wide); font-size: .8rem; font-weight: 700;
  letter-spacing: .11em; text-transform: uppercase;
  color: var(--text); margin-bottom: .55rem;
}
.creator-point p, .brief-point p { max-width: var(--measure); color: var(--text-muted); }

/* Prompts for the case studies Saranyaa has not written yet. Deleting the
   .todo block is all that is needed to publish a section. */
.todo { border: 1px dashed var(--accent); background: var(--accent-soft); padding: 1.1rem 1.25rem; margin-top: 1rem; max-width: var(--measure); }
.todo h3 { font-family: var(--font-wide); font-size: .64rem; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: var(--accent); margin-bottom: .5rem; }
.todo p, .todo li { color: var(--text); font-size: .92rem; }
.todo ul { margin: .4rem 0 0 1.1rem; display: grid; gap: .3rem; }

/* ── Lightbox ─────────────────────────────────────────────────────────── */
.lightbox {
  position: fixed; inset: 0; z-index: 60; display: none; place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(12, 10, 18, .9); backdrop-filter: blur(4px);
}
.lightbox[open] { display: grid; }
.lightbox img { max-width: 100%; max-height: 88vh; box-shadow: 0 24px 60px rgba(0, 0, 0, .5); }
.lightbox-close {
  position: absolute; top: clamp(.75rem, 3vw, 1.5rem); right: clamp(.75rem, 3vw, 1.5rem);
  width: 42px; height: 42px; display: grid; place-items: center;
  border: 1px solid rgba(255, 255, 255, .32); border-radius: 50%;
  background: rgba(0, 0, 0, .4); color: #fff; font-size: 1.25rem; cursor: pointer;
}
.lightbox-close:hover { background: rgba(0, 0, 0, .72); }

/* ── Accessibility panel ──────────────────────────────────────────────── */
.a11y-fab {
  position: fixed; right: clamp(.85rem, 3vw, 1.5rem); bottom: clamp(.85rem, 3vw, 1.5rem);
  z-index: 50; width: 46px; height: 46px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--bg-card); color: var(--text);
  box-shadow: var(--shadow); cursor: pointer; display: grid; place-items: center;
}
.a11y-fab:hover { border-color: var(--accent); color: var(--accent); }
.a11y-fab svg { width: 24px; height: 24px; }

.a11y-panel {
  position: fixed; right: clamp(.85rem, 3vw, 1.5rem); bottom: calc(clamp(.85rem, 3vw, 1.5rem) + 56px);
  z-index: 51; width: min(300px, calc(100vw - 2rem)); max-height: min(70vh, 560px); overflow-y: auto;
  padding: 1.1rem; border: 1px solid var(--border); background: var(--bg-card);
  box-shadow: var(--shadow); display: none;
}
.a11y-panel[open] { display: block; }
.a11y-panel h2 {
  font-family: var(--font-wide); font-size: .68rem; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; color: var(--text-muted); margin-bottom: .9rem;
}
.a11y-row {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .5rem 0; border-top: 1px solid var(--border); font-size: .88rem;
}
.a11y-row:first-of-type { border-top: 0; }
.a11y-row input[type="checkbox"] { width: 18px; height: 18px; accent-color: var(--accent); cursor: pointer; flex: 0 0 auto; }
.a11y-row input[type="range"] { width: 110px; accent-color: var(--accent); cursor: pointer; }
.a11y-reset {
  width: 100%; margin-top: .9rem; padding: .55rem;
  border: 1px solid var(--border); background: var(--bg); color: var(--text-muted);
  font: inherit; font-size: .84rem; cursor: pointer;
}
.a11y-reset:hover { border-color: var(--accent); color: var(--accent); }

/* ── Accessibility modes ──────────────────────────────────────────────── */
@font-face {
  font-family: 'OpenDyslexic';
  src: url('https://cdn.jsdelivr.net/npm/open-dyslexic@1.0.3/OpenDyslexic-Regular.otf') format('opentype');
  font-display: swap;
}
body.a11y-dyslexia, body.a11y-dyslexia * {
  font-family: 'OpenDyslexic', 'Comic Neue', sans-serif !important;
  letter-spacing: .01em; font-style: normal !important;
}

html[data-theme="light"] body.a11y-contrast {
  --bg: #fff; --bg-card: #fff; --bg-soft: #fff;
  --text: #000; --text-muted: #000; --text-dim: #1a1a1a;
  --border: #000; --accent: #3f2370; --accent-soft: #fff; --accent-ink: #fff;
}
html[data-theme="dark"] body.a11y-contrast {
  --bg: #000; --bg-card: #000; --bg-soft: #000;
  --text: #fff; --text-muted: #fff; --text-dim: #e8e8e8;
  --border: #fff; --accent: #dccfff; --accent-soft: #000; --accent-ink: #000;
}
/* Ink spreads must not reintroduce their own palette over high contrast, and
   the grain would reduce contrast, so both are neutralised here. */
body.a11y-contrast .ink {
  --bg: inherit; --bg-card: inherit; --bg-soft: inherit;
  --text: inherit; --text-muted: inherit; --text-dim: inherit;
  --border: inherit; --accent: inherit; --accent-soft: inherit; --accent-ink: inherit;
}
body.a11y-contrast::after { display: none; }
body.a11y-contrast a { text-decoration: underline; }
body.a11y-contrast .tag { background: var(--text); color: var(--bg); }
/* At high contrast --bg-card collapses to the page colour and soft shadows
   carry no meaning, so containers need a real border to stay containers. */
body.a11y-contrast .space { border: 1px solid var(--border); }
body.a11y-contrast .cta-go { background: none; border: 1px solid currentColor; }

body.a11y-links a { background: #fff3a3 !important; color: #000 !important; text-decoration: underline !important; outline: 1px solid #c79a12; }

body.a11y-spacing, body.a11y-spacing * { letter-spacing: .07em !important; word-spacing: .16em !important; line-height: 1.9 !important; }

body.a11y-cursor, body.a11y-cursor * {
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 0 48 48'%3E%3Cpath d='M6 2l30 20-13 3 7 14-6 3-7-14-11 8z' fill='%23fff' stroke='%23000' stroke-width='2.5'/%3E%3C/svg%3E") 4 2, auto !important;
}

body.a11y-motion *, body.a11y-motion *::before, body.a11y-motion *::after {
  animation: none !important; transition: none !important; scroll-behavior: auto !important;
}
body.a11y-motion .reveal, body.a11y-motion .ink.reveal > .wrap { opacity: 1 !important; transform: none !important; filter: none !important; }
body.a11y-motion .shots--slides figure:hover { transform: none !important; }
body.a11y-motion .shots--slides:hover figure:not(:hover) { filter: none !important; opacity: 1 !important; }
body.a11y-motion .parallax { transform: none !important; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
  .reveal, .ink.reveal > .wrap { opacity: 1 !important; transform: none !important; filter: none !important; }
  .shots--slides figure:hover { transform: none !important; }
  .shots--slides:hover figure:not(:hover) { filter: none !important; opacity: 1 !important; }
  .marquee-track { animation: none !important; }
}

/* ── Scroll reveal ────────────────────────────────────────────────────────
   Scoped to html.js so a failed script leaves content visible, not blank. */
html.js .reveal {
  opacity: 0; transform: translateY(14px); filter: blur(4px);
  transition: opacity .55s var(--ease-out), transform .55s var(--ease-out), filter .42s var(--ease-out);
}
html.js .reveal.is-visible { opacity: 1; transform: none; filter: blur(0); }
/* A dark band fading as a whole showed the light page through it until the
   reveal fired, which read as numbers that had not loaded. The band itself
   stays solid; only its contents arrive. No blur either: filtering a
   full-width band is the costliest part of the effect on slower phones. */
html.js .ink.reveal { opacity: 1; transform: none; filter: none; }
html.js .ink.reveal > .wrap {
  opacity: 0; transform: translateY(14px);
  transition: opacity .45s var(--ease-out), transform .45s var(--ease-out);
}
html.js .ink.reveal.is-visible > .wrap { opacity: 1; transform: none; }

/* Children arrive one behind another instead of the whole section lifting as
   a single curtain. 8px and 30ms apart, following the Subtle stagger preset:
   far enough to read as a sequence, not so far it feels like a queue.
   Declared under no-preference so reduced motion never gets the hidden state
   in the first place -- there is nothing to un-freeze. */
@media (prefers-reduced-motion: no-preference) {
  html.js .reveal [data-stagger] > * {
    opacity: 0; transform: translateY(8px);
    transition: opacity .38s var(--ease-out), transform .38s var(--ease-out);
  }
  html.js .reveal.is-visible [data-stagger] > * { opacity: 1; transform: none; }
  html.js .reveal.is-visible [data-stagger] > :nth-child(1) { transition-delay: 0.00s; }
  html.js .reveal.is-visible [data-stagger] > :nth-child(2) { transition-delay: 0.03s; }
  html.js .reveal.is-visible [data-stagger] > :nth-child(3) { transition-delay: 0.06s; }
  html.js .reveal.is-visible [data-stagger] > :nth-child(4) { transition-delay: 0.09s; }
  html.js .reveal.is-visible [data-stagger] > :nth-child(5) { transition-delay: 0.12s; }
  html.js .reveal.is-visible [data-stagger] > :nth-child(6) { transition-delay: 0.15s; }
  html.js .reveal.is-visible [data-stagger] > :nth-child(7) { transition-delay: 0.18s; }
  html.js .reveal.is-visible [data-stagger] > :nth-child(8) { transition-delay: 0.21s; }
}

/* The manual toggle is not the media query, so it needs its own opt-out. */
body.a11y-motion [data-stagger] > * {
  opacity: 1 !important; transform: none !important;
  filter: none !important; transition-delay: 0s !important;
}

/* Her name is the one thing on this page that earns a real entrance: two
   beats, the surname half a step behind. Both words are already in the HTML
   as .n1 and .n2, so this stays CSS-only -- the text is selectable, readable
   with JavaScript off, and nothing is reconstructed at runtime. Transform and
   opacity only, so the display type never reflows. */
@keyframes name-in {
  from { opacity: 0; transform: translateY(.3em); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: no-preference) {
  /* Hold the name until the real typefaces have landed. The fonts load with
     display=swap, so without this the entrance starts on fallback text and is
     77% over by the time Fraunces arrives -- measured. The swap itself then
     carried no motion at all, which is why it read as barely happening.
     Script always adds .fonts-ready, on load or on a timeout, so the hold
     cannot strand the name. */
  html.js .hero-name .n1,
  html.js .hero-name .n2 { opacity: 0; }
  html.js.fonts-ready .hero-name .n1,
  html.js.fonts-ready .hero-name .n2 { animation: name-in 1.1s var(--ease-out) both; }
  html.js.fonts-ready .hero-name .n2 { animation-delay: .3s; }
}
/* The motion toggle kills animations, so the hold state above needs an
   explicit release or the name would simply never appear. */
body.a11y-motion .hero-name .n1,
body.a11y-motion .hero-name .n2 { opacity: 1 !important; animation: none !important; }

/* The mark tracks how far down the page you are. Driven by the independent
   `rotate` property rather than `transform`, so it composes with the hover
   rotation above instead of overriding it -- scroll and hover both read.
   Native scroll-driven animation, so this adds no scroll listener. Where it
   is unsupported the mark simply sits still and keeps its hover turn. */
@keyframes mark-track { to { rotate: 180deg; } }
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    .nav-mark {
      animation: mark-track linear both;
      animation-timeline: scroll(root block);
    }
  }
}

/* ── Print ────────────────────────────────────────────────────────────── */
@media print {
  .nav, .a11y-fab, .a11y-panel, .lightbox, .cx-compose, .case-back, .marquee, .decor, .ponder, .stack-mark { display: none !important; }
  .stack-item { position: static !important; }
  body { background: #fff; color: #000; }
  body::after { display: none; }
  .ink { background: #fff !important; color: #000 !important; }
  .section { page-break-inside: avoid; }
}

/* ── Off the clock: one of her postcards ───────────────────────────────
   The real 2023 card on the front; the back carries a short note and the
   three things as address lines, each one a link. The paper stays cream in
   both themes, the way a printed card would. */
.postcard {
  --paper: #f6f3ee; --ink: #2a2433; --stamp: #5a3e91;
  display: grid; grid-template-columns: 1fr 1fr;
  margin-top: clamp(1.25rem, 3vw, 2rem);
  background: var(--paper); color: var(--ink);
  transform: rotate(-1deg);
  box-shadow: 0 2px 4px -2px rgba(0,0,0,.25), 0 34px 54px -32px rgba(0,0,0,.6);
}
.pc-front { display: grid; align-content: center; padding: clamp(.2rem, .6vw, .5rem); }
.pc-front img { display: block; width: 100%; height: auto; aspect-ratio: 1592 / 1128; object-fit: cover; }
.pc-back {
  position: relative;
  margin: clamp(.6rem, 1.4vw, 1.1rem) 0;
  padding: clamp(1.25rem, 2.6vw, 2rem) clamp(1.25rem, 2.8vw, 2.2rem);
  border-left: 1px dashed rgba(42, 36, 51, .3);
}
.pc-stamp {
  position: absolute; top: 1.2rem; right: 1.5rem;
  width: 62px; height: 74px; display: grid; place-items: center;
  background: var(--stamp); outline: 2px solid var(--paper); outline-offset: -6px;
  box-shadow: 0 0 0 1px rgba(42, 36, 51, .25); transform: rotate(3deg);
}
.pc-stamp i {
  width: 34px; height: 34px; background: var(--paper);
  -webkit-mask: url(shape-aster.svg) center / contain no-repeat;
  mask: url(shape-aster.svg) center / contain no-repeat;
}
.pc-postmark {
  position: absolute; top: 2.2rem; right: 5.6rem;
  padding: .9rem .6rem; border: 1.5px solid var(--stamp); border-radius: 50%;
  font-family: var(--font-wide); font-size: .6rem; font-weight: 700;
  letter-spacing: .16em; text-transform: uppercase; color: var(--stamp);
  transform: rotate(-12deg);
}
.pc-msg, .pc-sign {
  font-family: var(--font-display); font-style: italic;
  font-size: clamp(1.2rem, 1.9vw, 1.45rem); line-height: 1.35;
  font-variation-settings: 'SOFT' 100, 'WONK' 1;
}
.pc-msg { margin-top: 4.6rem; max-width: 22ch; }
.pc-sign { margin-top: .3rem; color: var(--stamp); }
.pc-lines { list-style: none; padding: 0; margin: 1.6rem 0 0; }
.pc-lines a {
  display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: .15rem 1rem;
  padding: .7rem 0 .45rem; border-bottom: 1px solid rgba(42, 36, 51, .35);
  color: var(--ink); text-decoration: none;
}
.pc-lines b { font-family: var(--font-display); font-weight: 400; font-size: 1.15rem; }
.pc-lines span {
  font-family: var(--font-wide); font-size: .64rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--stamp);
  white-space: nowrap; transition: transform .18s var(--ease-out);
}
.pc-lines a:hover b { font-style: italic; }
.pc-lines a:hover span { transform: translateX(.3rem); }
.pc-lines a:active span { transform: translateX(.1rem); }
.postcard a:focus-visible { outline-color: var(--stamp); }
/* It arrives the way a card lands on a table: a little more turned, then
   settling. Transform only; with motion off it simply sits at rest. */
@media (prefers-reduced-motion: no-preference) {
  html.js .reveal .postcard { transform: rotate(-3.5deg) translateY(28px); transition: transform .9s var(--ease-out) .15s; }
  html.js .reveal.is-visible .postcard { transform: rotate(-1deg); }
}
/* Stacked on a phone the card is tall, so a steep turn swings its corners
   past the screen edge. */
@media (prefers-reduced-motion: no-preference) and (max-width: 760px) {
  html.js .reveal .postcard { transform: rotate(-1.2deg) translateY(28px); }
}
body.a11y-motion .postcard { transform: rotate(-1deg) !important; }
@media (max-width: 760px) {
  .postcard { grid-template-columns: 1fr; transform: rotate(-.6deg); }
  html.js .reveal.is-visible .postcard { transform: rotate(-.6deg); }
  .pc-back { border-left: 0; border-top: 1px dashed rgba(42, 36, 51, .3); margin: 0 1rem 1rem; padding-inline: .2rem; }
  .pc-msg { margin-top: 5rem; }
  .pc-stamp { right: .6rem; }
  .pc-postmark { right: 4.6rem; }
}
/* ── Other work panels ──────────────────────────────────────────────────
   On a desktop with a mouse the six sit side by side as slim violet
   spines, and the one under the pointer or keyboard focus opens into a
   card (app.js keeps the last one open). Everywhere else, including
   without JavaScript, they are plain picture cards so nothing hides
   behind hover. */
.panels {
  list-style: none; margin: clamp(1.25rem, 3vw, 2rem) 0 0; padding: 0;
  display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
}
.panel-in {
  display: flex; flex-direction: column; height: 100%;
  color: inherit; text-decoration: none;
  background: var(--bg-card); border-radius: var(--r-md); overflow: hidden;
  box-shadow: 0 0 0 1px var(--hairline);
}
a.panel-in:hover { color: inherit; }
.panel-in:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.panel-img { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; object-position: center top; background: var(--bg-soft); }
.panel-img--left { object-position: left top; }
.panel-spine { display: none; }
.panel-body { display: grid; align-content: start; gap: .35rem; padding: 1rem 1.15rem 1.25rem; }
.panel-kicker {
  font-family: var(--font-wide); font-size: .6rem; font-weight: 700;
  letter-spacing: .15em; text-transform: uppercase; color: var(--accent);
}
.panel-title {
  font-family: var(--font-display); font-size: 1.4rem; font-weight: 400;
  letter-spacing: -.01em; line-height: 1.1; text-wrap: balance;
}
.panel-line { color: var(--text-muted); font-size: .92rem; text-wrap: pretty; }
.panel-go {
  margin-top: .35rem;
  font-family: var(--font-wide); font-size: .64rem; font-weight: 700;
  letter-spacing: .12em; text-transform: uppercase; color: var(--accent);
}

@media (min-width: 861px) and (hover: hover) and (pointer: fine) {
  /* --open is the width of the open panel: six panels, five 8px gaps,
     flex 4.6 against five at 1. The picture and the text are fixed at
     that width, so opening a panel uncovers them instead of re-cropping
     and reflowing them every frame. Only flex-grow and opacity animate. */
  html.js .panels {
    --open: calc((100cqw - 40px) * 4.6 / 9.6);
    --glide: cubic-bezier(.45, 0, .15, 1);
    container-type: inline-size;
    display: flex; gap: 8px; height: clamp(28rem, 36vw, 32rem);
  }
  html.js .panel { flex: 1 1 0; min-width: 0; transition: flex-grow .75s var(--glide); }
  html.js .panel.is-open { flex-grow: 4.6; }
  html.js .panel-in { position: relative; display: block; background: var(--accent); box-shadow: none; contain: paint; }
  html.js .panel-img {
    position: absolute; left: 0; top: 0; width: var(--open); height: 52%; aspect-ratio: auto;
    opacity: .22; transition: opacity .6s var(--glide);
  }
  html.js .panel.is-open .panel-img { opacity: 1; }
  /* Kicker and title run up the spine as two lines, read bottom to top. */
  html.js .panel-spine {
    display: flex; flex-direction: column; gap: .35rem;
    position: absolute; left: 1rem; bottom: 1.25rem;
    writing-mode: vertical-rl; text-orientation: sideways; transform: rotate(180deg);
    color: var(--accent-ink); white-space: nowrap;
    transition: opacity .35s var(--glide) .3s, visibility 0s .3s;
  }
  html.js .panel-spine span { font-family: var(--font-wide); font-size: .58rem; font-weight: 700; letter-spacing: .16em; text-transform: uppercase; }
  html.js .panel-spine b { font-family: var(--font-display); font-size: 1.45rem; font-weight: 400; line-height: 1; }
  html.js .panel.is-open .panel-spine { opacity: 0; visibility: hidden; transition: opacity .25s var(--glide), visibility 0s .25s; }
  html.js .panel-body {
    position: absolute; left: 0; top: 52%; bottom: 0; width: var(--open);
    padding: 1.15rem 1.5rem; background: var(--bg-card);
    opacity: 0; transition: opacity .3s var(--glide);
  }
  html.js .panel-body > * { max-width: 30rem; }
  html.js .panel-title { font-size: clamp(1.6rem, 2.3vw, 2.1rem); font-style: italic; }
  html.js .panel.is-open .panel-body { opacity: 1; transition: opacity .5s var(--glide) .2s; }
}
@media (prefers-reduced-motion: reduce) {
  .panel, .panel-img, .panel-spine, .panel-body, .panel.is-open .panel-body { transition: none !important; }
}
body.a11y-motion .panel, body.a11y-motion .panel-img, body.a11y-motion .panel-spine, body.a11y-motion .panel-body { transition: none !important; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
/* ── Audit fixes ──────────────────────────────────────────────────────────
   Pressed feedback was missing everywhere: things lifted on hover but gave
   nothing back on click. */
.pill:active, .btn:active, .link-card:active { transform: translateY(1px); }

/* The nav gave no clue which section you were looking at. */
.nav-links a[aria-current="true"] { color: var(--accent); }
/* The CTA is already an accent pill, so accent text made it read blank.
   It keeps its ink and lights up with a halo instead. */
.nav-links a.nav-cta[aria-current="true"] {
  color: var(--accent-ink);
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--accent) 30%, transparent),
    0 0 22px color-mix(in srgb, var(--accent) 50%, transparent);
}

/* Figures read as data, so they should not jiggle as the digits change. */
.metric b, .feature-metrics div b { font-variant-numeric: tabular-nums; }

/* Stops a single word dangling on its own final line. */
p, .section-intro { text-wrap: pretty; }
.stack-body h3, .creator-point h3, .brief-point h3 { text-wrap: balance; }

/* Inline validation message for the composer. */
.field-error { margin-top: .4rem; font-size: .82rem; color: var(--accent); }
.cx-compose textarea[aria-invalid="true"], .cx-compose input[aria-invalid="true"] { border-color: var(--accent); }
