/* ==========================================================================
   Moo Jun Jie — Portfolio (v2, "Swiss")
   Big type, a quiet grid, one signal colour.
   Type: Geist (everything) + Geist Mono (labels), self-hosted, OFL.
   No frameworks, no build step. Case-study components further down read
   the same tokens, so changing a value here restyles every page.
   ========================================================================== */

/* ---------- Fonts ---------- */
@font-face {
  font-family: "Geist";
  src: url("../fonts/Geist-Variable.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "Geist Mono";
  src: url("../fonts/GeistMono-Variable.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}

/* ---------- Tokens ---------- */
:root {
  --paper: #efefec;          /* page */
  --paper-raised: #ffffff;   /* floating cards, figure mats */
  --paper-sunken: #e4e4e0;   /* image placeholders, quiet panels */
  --ink: #111111;            /* primary text */
  --ink-soft: #3a3a37;       /* secondary body text (lede, captions in prose) */
  --ink-faint: #858581;      /* second half of a two-tone headline — 24px+ only */
  --muted: #5e5e5a;          /* labels, metadata — 4.5:1 on paper */
  --rule: #cfcfca;           /* decorative hairlines, the grid overlay */
  --rule-strong: #858581;    /* borders that must be seen — 3:1 on paper */
  --accent: #2a3cff;         /* the one signal colour */
  --accent-ink: #ffffff;
  --live: #1f8a4c;           /* "available for work" dot only */

  --font-display: "Geist", "Helvetica Neue", Arial, sans-serif;
  --font-body: "Geist", "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;

  --step--1: 0.875rem;                                    /* 14 */
  --step-0: 1rem;                                         /* 16 body, never smaller */
  --step-1: clamp(1.125rem, 1.05rem + 0.37vw, 1.25rem);   /* 18–20 lede */
  --step-2: clamp(1.375rem, 1.2rem + 0.8vw, 1.75rem);     /* 22–28 title */
  --step-3: clamp(1.75rem, 1.3rem + 2vw, 3rem);           /* 28–48 heading */
  --step-4: clamp(2rem, 1.2rem + 3.6vw, 4rem);            /* 32–64 */
  --step-5: clamp(3rem, 1.2rem + 7.5vw, 6.5rem);          /* 48–104 display */

  --gutter: clamp(1rem, 2vw, 1.5rem);   /* page margin: 16 → 24 */
  --col-gap: 1rem;
  --measure: 34rem;
  --shell: 90rem;                       /* 1440 */
  --radius: 4px;                        /* small UI */
  --radius-md: 12px;                    /* images, cards */
  --radius-lg: 24px;                    /* big panels */
  --section: clamp(4rem, 10vw, 10rem);
  --float: 0 12px 32px rgba(17, 17, 17, 0.10), 0 1px 2px rgba(17, 17, 17, 0.06);

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
img, video, svg { display: block; max-width: 100%; height: auto; }
h1, h2, h3, h4 { font-family: var(--font-display); font-weight: 500; line-height: 1.05; margin: 0; letter-spacing: -0.02em; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; }
a { color: inherit; }
button { font: inherit; color: inherit; }
::selection { background: var(--accent); color: var(--accent-ink); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--radius);
}

/* ---------- Layout helpers ---------- */
.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.section { padding-block: clamp(3.5rem, 8vw, 7rem); }
.rule { height: 1px; background: var(--rule); border: 0; margin: 0; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--ink); color: var(--paper); padding: 0.75rem 1.25rem;
  font-size: var(--step--1);
}
.skip:focus { left: 1rem; top: 1rem; }

/* ---------- Header ---------- */
.site-head {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--rule);
}
.site-head__inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem; min-height: 4.25rem;
}
.brand {
  display: inline-flex; align-items: baseline; gap: 0.55rem;
  font-family: var(--font-display); font-size: var(--step-0);
  font-weight: 500; letter-spacing: -0.01em; text-decoration: none;
}
.brand__role {
  font-family: var(--font-body); font-size: var(--step--1);
  color: var(--muted); letter-spacing: 0;
}

.nav { display: flex; align-items: center; gap: clamp(1rem, 2.4vw, 2rem); }
.nav__link {
  display: inline-flex; align-items: center; min-height: 44px;
  font-size: var(--step--1); text-decoration: none; color: var(--ink-soft);
  padding-block: 0.35rem; position: relative;
}
.nav__link::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: currentColor; transform: scaleX(0); transform-origin: left;
  transition: transform 0.35s var(--ease);
}
.nav__link:hover { color: var(--ink); }
.nav__link:hover::after { transform: scaleX(1); }
.nav__link[aria-current="page"] { color: var(--ink); }
.nav__link[aria-current="page"]::after { transform: scaleX(1); background: var(--accent); }

.nav-toggle {
  display: none; align-items: center; gap: 0.5rem;
  background: none; border: 1px solid var(--rule-strong);
  padding: 0.45rem 0.7rem; cursor: pointer;
  font-size: var(--step--1); border-radius: var(--radius);
}

/* ---------- Typographic primitives ---------- */

/* inline code — values, filenames, attributes quoted in body copy */
code, kbd {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.88em;
  background: var(--paper-raised);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.06em 0.34em;
  overflow-wrap: anywhere;
}
kbd { border-color: var(--rule-strong); }

/* ---------- Hero ---------- */
.hero { padding-block: clamp(3.5rem, 9vw, 8rem) clamp(2.5rem, 5vw, 4rem); }
.dot {
  width: 6px; height: 6px; border-radius: 50%; background: #2f7d4f;
  box-shadow: 0 0 0 3px color-mix(in srgb, #2f7d4f 18%, transparent);
}
.hero h1 {
  font-size: var(--step-5);
  line-height: 0.94;
  letter-spacing: -0.04em;
  font-weight: 500;
  max-width: 18ch;
  text-wrap: balance;
}
.hero h1 em { font-style: normal; color: var(--accent); }

/* ---------- Work cards (large) ---------- */

.tag {
  font-size: 0.75rem; letter-spacing: 0.04em;
  color: var(--ink-soft);
  border: 1px solid var(--rule-strong);
  padding: 0.15rem 0.5rem; border-radius: 100px;
  white-space: nowrap;
}

/* ---------- Contact ---------- */
.contact { border-top: 1px solid var(--rule); }
.contact h2 { font-size: var(--step-4); letter-spacing: -0.035em; max-width: 16ch; text-wrap: balance; }

/* ---------- Footer ---------- */
.site-foot {
  border-top: 1px solid var(--rule);
  padding-block: 2.5rem 3rem;
  font-size: var(--step--1); color: var(--muted);
}
.site-foot__inner { display: flex; flex-wrap: wrap; gap: 1rem 2rem; justify-content: space-between; align-items: baseline; }
.site-foot a {
  display: inline-flex; align-items: center; min-height: 44px;
  padding-block: 0.4rem; margin-block: -0.4rem;
}

/* ==========================================================================
   Case study layout
   ========================================================================== */

figure { margin: 0; }
.fig-cap {
  font-size: var(--step--1);
  color: var(--muted);
  margin-top: 0.7rem;
  max-width: 56ch;
  line-height: 1.5;
}

/* phone screens row */
.screens {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(11rem, 1fr);
  gap: clamp(0.75rem, 1.6vw, 1.25rem);
  overflow-x: auto;
  padding-bottom: 1rem;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}
.screens figure { scroll-snap-align: start; }
.screens img {
  width: 100%;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper-raised);
}

/* ==========================================================================
   Interactive device mockup — Nudge flow player
   The screens inside are the original Figma exports at exact iPhone geometry
   (440 x 956pt). Nothing is redrawn; the shell only crops and frames them, so
   the pixels stay 1:1. Interaction = a state machine over real screens.
   ========================================================================== */
.flow {
  --flow-w: clamp(238px, 30vw, 306px);
  /* Geometry is derived from the hardware, not guessed:
     screen 440x956pt, bezel 8.49pt  ->  outer 456.98 x 972.98pt.
     bezel fraction of outer width = 8.49/456.98 = 0.01858
     outer aspect                   = 972.98/456.98 = 2.12914
     The bezel must sit AROUND the screen, so the shell is outer-sized and the
     screen is what remains; if you size the shell to 440:956 the screen ends up
     at the wrong ratio and every frame gets silently cropped. */
  --flow-bezel: calc(var(--flow-w) * 0.01858);
  --flow-r: calc(var(--flow-w) * 0.139);
  display: grid;
  gap: clamp(1.25rem, 3vw, 2.25rem);
  align-items: start;
  grid-template-columns: 1fr;
}
@media (min-width: 44rem) {
  .flow {
    grid-template-columns: var(--flow-w) minmax(0, 1fr);
    gap: clamp(1.75rem, 4vw, 3.25rem);
  }
}

/* ---------- the device ---------- */
.device {
  position: relative;
  width: var(--flow-w);
  height: calc(var(--flow-w) * 2.12914);
  padding: var(--flow-bezel);
  border-radius: var(--flow-r);
  background:
    linear-gradient(145deg, #3a3733 0%, #191714 18%, #2b2825 50%, #141210 82%, #322e2a 100%);
  box-shadow:
    0 1px 0 0 rgba(255, 255, 255, .14) inset,
    0 0 0 1px rgba(0, 0, 0, .55),
    0 18px 40px -12px rgba(25, 23, 20, .38),
    0 4px 10px -4px rgba(25, 23, 20, .28);
  justify-self: center;
}
@media (min-width: 44rem) { .device { justify-self: start; } }

/* side buttons — decorative, match the hardware silhouette */
.device::before, .device::after {
  content: ""; position: absolute; width: 2px; border-radius: 2px;
  background: linear-gradient(180deg, #4a4640, #211e1b);
  pointer-events: none;
}
.device::before { left: -2px; top: 21%; height: 6%; }
.device::after  { right: -2px; top: 26%; height: 11%; }

.device__screen {
  position: relative;
  width: 100%; height: 100%;
  min-height: 0;
  border-radius: calc(var(--flow-r) - var(--flow-bezel));
  overflow: hidden;
  background: #fff;
  isolation: isolate;
  cursor: pointer;
}
.device__screen:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ---------- in-frame scrolling ----------
   The tall captures are true scroll-captures: they only lose the baked-in chrome
   (the status bar / tab bar were stripped in the design files), and the real
   chrome PNGs are re-composited on top as fixed layers. So the screen is a
   viewport, the capture scrolls inside it, and the chrome does not move.
   Everything is still original artwork — nothing is redrawn. */
.device__scroller {
  position: absolute; inset: 0;
  overflow-y: auto; overflow-x: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* children are absolutely positioned stages, so the scroller needs an explicit
     coordinate space and must not grow with its content */
  contain: layout paint;
}
.device__scroller::-webkit-scrollbar { display: none; }
.device__scroller > img {
  display: block;
  width: 100%; height: auto;
  max-width: none;
}

/* A stage wraps a capture and is exactly as tall as it, so hotspots placed as
   percentages of the stage scroll WITH the artwork. Screen-space hotspots are
   only correct for captures that never scroll. */
.device__stage {
  position: relative;
  display: none;
  width: 100%;
}
.device__stage.is-active { display: block; }
.device__stage > img {
  display: block;
  width: 100%; height: auto;
  max-width: none;
}
/* hotspots inside a stage are % of the artwork, so they travel with it */
.device__stage .hotspot { position: absolute; }

/* a capture taller than one screen gets a hint that it scrolls */
.device__scrollhint {
  position: absolute; z-index: 7;
  left: 50%; transform: translateX(-50%);
  /* clear the floating tab bar when one is present; 0.115 alone overlaps it */
  bottom: calc(var(--flow-w) * var(--hint-bottom, 0.115));
  display: flex; align-items: center; gap: .35em;
  padding: .3em .7em .3em .55em;
  border-radius: 999px;
  font-family: var(--font-body);
  font-size: calc(var(--flow-w) * 0.033);
  font-weight: 500;
  letter-spacing: .01em;
  color: #fff;
  background: rgba(20, 18, 16, .62);
  backdrop-filter: blur(4px);
  opacity: 0; transition: opacity .3s var(--ease);
  pointer-events: none;
}
.device__scrollhint.is-on { opacity: 1; }
.device__scrollhint svg { width: 1em; height: 1em; }

/* ---------- fixed chrome layers ----------
   Re-composited from the real design assets (tagbar-status bar/). They sit
   above the scroller and never move, which is what makes the tall captures
   behave like a real phone instead of a long picture. */
.device__status, .device__tabbar, .device__chatbox {
  position: absolute; z-index: 4;
  pointer-events: none;
  display: block;
  max-width: none;
}
/* Status bar sits ABOVE the modal scrim: iOS keeps the status bar visible and
   crisp while an alert dims everything behind it.

   Sizing is derived from the artwork, not guessed: the exported assets are
   2x iPhone Pro Max, so 804px wide is 402pt on a 440pt screen = 91.36% of the
   screen width. The captured status bar occupies x 152..761 of the 880px export,
   i.e. it starts at 38/880 = 4.318% from the left. Using the inverse ratio here
   (880/804) renders the bar 20% too wide — measurable, and it looks wrong. */
.device__status {
  top: 0; left: 4.318%; transform: none;
  width: calc(100% * 804 / 880);
  height: auto;
  z-index: 9;
}
/* tab bar: the design asset is a floating pill, centred horizontally */
.device__tabbar {
  left: 50%; transform: translateX(-50%);
  bottom: calc(var(--flow-w) * 0.0286);
  width: 78%;
  height: auto;
}

/* When the hint and the tab bar share a screen, lift the hint clear of it.
   The tab bar is 105/880 of the screen height and sits at 0.0286 * width, so
   0.185 * width clears it with ~13px to spare at a 306px shell. */
.device__screen:has(.device__tabbar) .device__scrollhint { --hint-bottom: 0.185; }
/* chat composer: the asset is 834px = 417pt, i.e. 94.77% of the 440pt screen,
   and it is flush with the screen edges (unlike the inset status bar). */
.device__chatbox {
  left: 0; transform: none;
  bottom: 0;
  width: calc(100% * 834 / 880);
  height: auto;
}
.device__chatbox.is-hidden { display: none; }

/* author display rules outrank the UA [hidden] rule, so state it explicitly:
   without this, per-step chrome switching silently does nothing. */
.device__status[hidden],
.device__tabbar[hidden],
.device__chatbox[hidden] { display: none; }

/* the modal scrim + card (vault force-unlock warning).
   The card artwork itself is translucent by design (alpha ≈ 204), which is what
   makes it read as frosted glass — so the scrim behind it must be light, not a
   dark overlay, or the artwork underneath bleeds through and the text fights it. */
.device__modal {
  position: absolute; inset: 0; z-index: 8;
  display: none;
  align-items: center; justify-content: center;
  padding: 0 7%;
  background: rgba(120, 116, 108, .30);
  backdrop-filter: blur(2.5px);
  -webkit-backdrop-filter: blur(2.5px);
}
.device__modal.is-open { display: flex; }
.device__modal img {
  display: block; width: 100%; height: auto;
  border-radius: calc(var(--flow-w) * 0.035);
  box-shadow: 0 12px 28px -10px rgba(0, 0, 0, .45);
  pointer-events: none;
}
.device__modal-btn {
  position: absolute; left: 10%; width: 80%;
  border: 0; background: transparent; cursor: pointer;
  border-radius: 999px;
}
.device__modal-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Each screen is a stack of exported frames; only the active one paints.
   Base state: the FIRST frame is visible and the rest are not. That way a
   visitor with no JavaScript still sees a real screen rather than a blank shell
   — the site's rule is that content must survive a failed script. JS then adds
   [data-ready] to the screen and drives .is-active itself.

   Two layouts share this:
     * fixed frames  -> absolutely stacked, `object-fit: cover`, cross-fade
     * stage frames  -> each in its own .device__stage inside .device__scroller,
                        laid out in normal flow so the capture can be taller than
                        the screen and scroll, with hotspots travelling with it */
.device__frame {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  object-fit: cover; object-position: top center;
  opacity: 0;
  transition: opacity .28s var(--ease);
  pointer-events: none;
}
.device__frame:first-of-type { opacity: 1; }
.device__screen[data-ready] .device__frame { opacity: 0; }
.device__screen[data-ready] .device__frame.is-active { opacity: 1; }

/* inside a stage the frame is a normal-flow image at its natural ratio */
.device__stage .device__frame {
  position: static; inset: auto;
  width: 100%; height: auto;
  object-fit: unset;
  opacity: 1;
}
.device__screen[data-ready] .device__stage .device__frame { opacity: 1; }

/* Dynamic island + home indicator, aligned to the empty band in the exports */
.device__island {
  position: absolute; z-index: 5;
  left: 50%; transform: translateX(-50%);
  top: calc(var(--flow-w) * 0.0246);
  width: calc(var(--flow-w) * 0.285);
  height: calc(var(--flow-w) * 0.0848);
  border-radius: 999px;
  background: #000;
  pointer-events: none;
}
.device__island::after {
  content: ""; position: absolute; right: 12%;
  top: 50%; transform: translateY(-50%);
  width: 15%; aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle at 38% 34%, #26313f 0%, #0b0d10 62%, #05070a 100%);
}
.device__home {
  position: absolute; z-index: 5; left: 50%; transform: translateX(-50%);
  bottom: calc(var(--flow-w) * 0.0142);
  width: calc(var(--flow-w) * 0.318);
  height: 3px; border-radius: 99px;
  background: rgba(25, 23, 20, .45);
  pointer-events: none;
}
.device__home--light { background: rgba(255, 255, 255, .82); }

/* ---------- tap hotspots ----------
   Positioned in % of the 440x956 screen, measured from the actual exports.
   Invisible by default: an outline drawn over the design would fight the
   design. Affordance lives in the player chrome instead. */
.hotspot {
  position: absolute; z-index: 6;
  border: 0; padding: 0; margin: 0;
  background: transparent;
  cursor: pointer;
  border-radius: 16px;
  -webkit-tap-highlight-color: transparent;
}
.hotspot:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* Without JS these buttons would all be active at once and swallow clicks on a
   stack they cannot change. JS reveals them; the no-JS view is a still image. */
.js .hotspot { display: block; }
.hotspot { display: none; }
.hotspot[hidden] { display: none; }

/* desktop precision: a faint ring on hover only, so the target is checkable */
.hotspot::after {
  content: ""; position: absolute; inset: -2px;
  border-radius: inherit;
  border: 1.5px solid var(--accent);
  opacity: 0; transition: opacity .18s var(--ease);
  pointer-events: none;
}
@media (hover: hover) {
  .hotspot:hover::after { opacity: .55; }
}

/* The whole-screen target used by steps whose export has no button in it. */
.hotspot--screen { inset: 0; width: 100%; height: 100%; border-radius: 0; }
.hotspot--screen::after { content: none; }

/* ---------- player chrome: "tap to continue" ----------
   Clearly player furniture, not part of the design: a scrim and a small label
   along the bottom of the screen. */
.device__tap {
  position: absolute; z-index: 7;
  left: 0; right: 0; bottom: 0;
  padding: calc(var(--flow-w) * 0.20) 0 calc(var(--flow-w) * 0.085);
  display: flex; align-items: center; justify-content: center;
  gap: .4em;
  font-family: var(--font-body);
  font-size: calc(var(--flow-w) * 0.037);
  font-weight: 500;
  letter-spacing: .02em;
  color: #fff;
  background: linear-gradient(to top, rgba(20, 18, 16, .58) 0%, rgba(20, 18, 16, .0) 100%);
  opacity: 0;
  transition: opacity .25s var(--ease);
  pointer-events: none;
}
.device__tap.is-on { opacity: 1; }
.device__tap svg { width: 1em; height: 1em; flex: none; }

/* ---------- flow chrome (step counter, restart, flow picker) ---------- */
.flow__meta { display: grid; gap: .85rem; align-content: start; min-width: 0; max-width: 34rem; }
.flow__step {
  font-family: var(--font-display);
  font-size: var(--step--1);
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--accent);
}
.flow__caption {
  font-family: var(--font-display);
  font-size: var(--step-1);
  letter-spacing: -0.015em;
  line-height: 1.35;
  color: var(--ink);
  min-height: 2.9em;
  max-width: 30ch;
  text-wrap: pretty;
}
.flow__hint {
  font-size: var(--step--1);
  color: var(--muted);
  line-height: 1.5;
  max-width: 40ch;
  padding-top: .2rem;
}
.flow__hint b {
  display: block; margin-bottom: .2rem;
  font-family: var(--font-display); font-weight: 500;
  font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.12em;
  color: var(--accent);
}
.flow__hint em { font-style: normal; color: var(--ink-soft); font-weight: 500; }

.flow__bar {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .6rem 1rem;
  padding-top: .35rem;
}
.flow__dots { display: flex; flex-wrap: wrap; gap: 0; align-items: center; margin-left: -8.5px; }
/* The visible dot stays 7px; the button around it is a 24px target (WCAG 2.5.8). */
.flow__dot {
  position: relative; width: 24px; height: 24px; padding: 0;
  border: 0; background: transparent; cursor: pointer;
}
.flow__dot::before {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 7px; height: 7px; transform: translate(-50%, -50%);
  border-radius: 50%; border: 1px solid var(--rule-strong); box-sizing: border-box;
  transition: background .2s var(--ease), border-color .2s var(--ease);
}
.flow__dot[aria-current="true"]::before { background: var(--accent); border-color: var(--accent); }
.flow__dot:hover::before { border-color: var(--accent); }

.flow__btn {
  font-family: var(--font-body);
  font-size: var(--step--1);
  color: var(--ink-soft);
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: .45rem .8rem;
  cursor: pointer;
  transition: border-color .2s var(--ease), color .2s var(--ease);
}
.flow__btn:hover { border-color: var(--rule-strong); color: var(--ink); }
.flow__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.flow__btn[disabled] { opacity: .4; cursor: default; }

@media (prefers-reduced-motion: reduce) {
  .device__frame { transition: none; }
  .device__tap { transition: none; }
}

/* two-up / three-up */
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr)); gap: clamp(1rem, 2.5vw, 2rem); }

/* next project */
.next {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 1rem 2rem;
  padding-block: clamp(2rem, 4vw, 3rem);
  border-top: 1px solid var(--rule);
  text-decoration: none;
}

/* timeline */
.timeline { display: grid; gap: 0; }

/* ---------- Inline text links (tap target, look unchanged) ---------- */
a[style*="border-bottom"] {
  /* Inline padding enlarges the tap target without moving the line box;
     the rule is drawn as an underline so it stays attached to the text. */
  display: inline;
  padding-block: 0.55rem;
  border-bottom: 0 !important;
  text-decoration: underline !important;
  text-decoration-color: var(--rule-strong);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.3em;
}
a[style*="border-bottom"]:hover { text-decoration-color: currentColor; }
/* ---------- Grid children must be allowed to shrink ----------
   Grid/flex items default to min-width:auto, so a wide child (the horizontal
   screen rails, long unbroken words) forces the whole page to overflow. */
.grid-2 > *,
.screens > * { min-width: 0; }

.screens { max-width: 100%; }

/* long words / URLs should wrap rather than widen the page */
.fig-cap { overflow-wrap: anywhere; }
.hero h1 { overflow-wrap: break-word; }
/* ---------- Reveal animation ----------
   Content is ALWAYS visible in the base state. The entrance is a one-shot
   animation applied when JS says the element has entered, so a failed script,
   a blocked resource or a text browser can never leave the page blank. */
.reveal { opacity: 1; transform: none; }

@keyframes reveal-in {
  from { opacity: 0; transform: translate3d(0, 14px, 0); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .reveal.is-in { animation: reveal-in 0.65s var(--ease) both; }
}

/* ---------- Responsive ---------- */

@media (max-width: 47.99rem) {
  .nav { display: none; }
  .nav.is-open {
    display: flex;
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; align-items: flex-start; gap: 0;
    background: var(--paper);
    border-bottom: 1px solid var(--rule);
    padding: 0.5rem var(--gutter) 1.25rem;
  }
  .nav.is-open .nav__link { padding-block: 0.7rem; width: 100%; }
  .nav-toggle { display: inline-flex; }
  .site-head { position: relative; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
  .js .reveal { opacity: 1; transform: none; }
}

/* ---------- MyXR case study: UI components on a light field ---------- */
/* Dialogue bars, toggles and cut-out characters are transparent UI exports,
   not photographs. They get floated on a tinted mat so a low-contrast panel
   doesn't dissolve into the page background. Images are never scaled above
   their native pixel size -- blowing a 172px toggle up to 370px reads as mush. */
.ui-pad {
  border: 1px solid var(--rule);
  background:
    radial-gradient(120% 140% at 50% 0%, #ffffff 0%, #f4f9fd 55%, #eaf3fb 100%);
  border-radius: var(--radius-md);
  padding: clamp(.85rem, 2vw, 1.4rem);
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  overflow: hidden;
}
.ui-pad img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
/* rows stretch so every mat in a row is the same height and the captions line up */
.ui-row { align-items: stretch; }
.ui-row > figure { display: flex; flex-direction: column; }
.ui-row > figure .ui-pad { flex: 1 1 auto; height: auto; }

/* ---------- Print ---------- */
@media print {
  .site-head, .next, .contact, .site-foot, .nav-toggle { display: none !important; }
  body { background: #fff; color: #000; font-size: 10pt; }
  a { text-decoration: none; }
}

/* ==========================================================================
   Nudge key visual (work/nudge.html) — replaces the Behance board cover.
   One panel in the app's own brand gradient, three real screens fanned out,
   three floating cards built from the prototype's own numbers.
   Everything is sized in container units (cqi) so the composition scales as
   one picture from 320px to the 78rem shell.
   ========================================================================== */
@font-face {
  font-family: "Montserrat";
  src: url("../fonts/Montserrat-Variable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-display: swap;
}
.kv {
  --kv-font: "Montserrat", var(--font-body);
  font-family: var(--kv-font);
  --kv-blue: #7f93ec;
  --kv-lime: #b6df1c;
  --kv-orange: #f4874a;
  --kv-mist: #b2b8c8;
  --kv-ink: #141414;
  position: relative;
  container-type: inline-size;
  width: 100%; /* without it, min-height + aspect-ratio widen the figure past the page at 768–790px */
  margin: clamp(1.5rem, 3vw, 2.5rem) 0 0;
  aspect-ratio: 16 / 8.6;
  min-height: 26rem;
  overflow: hidden;
  border-radius: var(--radius-md);
  background: var(--kv-mist);
  isolation: isolate;
  color: var(--kv-ink);
}
.kv__mesh {
  position: absolute; inset: 0; z-index: -1;
  background:
    radial-gradient(48% 70% at 8% 18%, var(--kv-blue) 0%, transparent 70%),
    radial-gradient(42% 62% at 46% 30%, var(--kv-lime) 0%, transparent 72%),
    radial-gradient(46% 72% at 96% 78%, var(--kv-orange) 0%, transparent 70%),
    radial-gradient(40% 55% at 78% 8%, #d7e86a 0%, transparent 70%),
    radial-gradient(60% 60% at 12% 105%, #e9a27c 0%, transparent 70%),
    var(--kv-mist);
}
/* film grain, so the gradient reads as print rather than CSS */
.kv__mesh::after {
  content: ""; position: absolute; inset: 0; opacity: .22; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---- brand lock-up + line ---- */
.kv__brand {
  position: absolute; left: 3.4cqi; top: 3.4cqi; z-index: 6;
  display: flex; align-items: center; gap: 1cqi;
}
.kv__mark { width: 3.6cqi; height: auto; }
.kv__word { width: 7.4cqi; height: auto; }
.kv__line {
  position: absolute; left: 3.4cqi; bottom: 3.4cqi; z-index: 6; margin: 0;
  font-family: var(--kv-font); font-weight: 700;
  font-size: 2.2cqi; line-height: 1.02; letter-spacing: -0.03em;
  color: var(--kv-ink); /* white measured 2.14:1 on the mist; ink is 8.6:1 */
}
.kv__brand img { filter: drop-shadow(0 1px 8px rgba(20,18,30,.18)); }

/* ---- phones ---- */
.kv__phones { position: absolute; inset: 0; }
.kv-phone {
  --w: 22cqi;
  position: absolute; left: 50%; top: 11%;
  width: var(--w); height: calc(var(--w) * 2.12914);
  padding: calc(var(--w) * 0.01858);
  border-radius: calc(var(--w) * 0.139);
  background: linear-gradient(145deg, #3a3733 0%, #191714 18%, #2b2825 50%, #141210 82%, #322e2a 100%);
  box-shadow:
    0 1px 0 0 rgba(255,255,255,.16) inset,
    0 0 0 1px rgba(0,0,0,.5),
    0 3cqi 5cqi -1.5cqi rgba(20,18,30,.45),
    0 .6cqi 1.2cqi -.4cqi rgba(20,18,30,.35);
  transform: translateX(-50%);
}
.kv-phone::before { /* dynamic island */
  content: ""; position: absolute; z-index: 3; left: 50%; transform: translateX(-50%);
  top: calc(var(--w) * 0.0246); width: calc(var(--w) * 0.285); height: calc(var(--w) * 0.0848);
  border-radius: 999px; background: #000;
}
.kv-phone__screen {
  position: relative; width: 100%; height: 100%; overflow: hidden;
  border-radius: calc(var(--w) * (0.139 - 0.01858)); background: #fff;
}
.kv-phone__screen > img:first-child {
  width: 100%; height: auto; max-width: none;
}
.kv-phone__status {
  position: absolute; top: 0; left: 4.318%; width: calc(100% * 804 / 880); height: auto; max-width: none;
}
.kv-phone--c { z-index: 3; }
.kv-phone--l { --w: 18.5cqi; z-index: 2; top: 19%; transform: translateX(calc(-50% - 19cqi)) rotate(-7deg); }
.kv-phone--r { --w: 18.5cqi; z-index: 2; top: 19%; transform: translateX(calc(-50% + 19cqi)) rotate(7deg); }

/* ---- floating cards (the app's frosted-glass language) ---- */
.kv-card {
  position: absolute; z-index: 5;
  display: grid; gap: .35cqi;
  padding: 1.25cqi 1.4cqi;
  border-radius: 1.5cqi;
  background: rgba(255,255,255,.74);
  border: 1px solid rgba(255,255,255,.85);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  backdrop-filter: blur(14px) saturate(140%);
  box-shadow: 0 2cqi 4cqi -1.5cqi rgba(20,18,30,.35);
  font-family: var(--kv-font); line-height: 1.15;
}
.kv-card__tag {
  display: inline-flex; align-items: center; gap: .45cqi; justify-self: start;
  padding: .3cqi .7cqi; border-radius: 999px;
  background: var(--kv-lime); color: var(--kv-ink);
  font-size: .82cqi; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
}
.kv-card__tag svg { width: .95cqi; height: .95cqi; }
.kv-card__item { font-size: 1.05cqi; font-weight: 600; }
.kv-card__sub  { font-size: .85cqi; color: #555; }
.kv-card__timer, .kv-card__big {
  font-family: var(--kv-font); font-weight: 700; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; line-height: 1;
}
.kv-card__timer { font-size: 2.4cqi; }
.kv-card__big   { font-size: 3.6cqi; }
.kv-card__bar { display: block; height: .45cqi; border-radius: 999px; background: rgba(20,20,20,.12); overflow: hidden; }
.kv-card__bar i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--kv-blue), var(--kv-lime)); }
.kv-card__neg { font-family: var(--kv-font); font-weight: 600; font-size: 1.5cqi; color: #c41d14; letter-spacing: -0.02em; }

.kv-card__plates { display: flex; align-items: center; gap: .25cqi; margin-top: .2cqi; }
.kv-card__plates i {
  width: 1.2cqi; aspect-ratio: 1; border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, #f6c445 0 22%, #fff 23% 40%, #3f8a3a 41% 52%, #f2efe8 53% 100%);
  box-shadow: 0 0 0 1px rgba(0,0,0,.08);
}
.kv-card__plates b { font-size: .8cqi; font-weight: 600; color: #555; margin-left: .2cqi; }

.kv-card--grade { grid-auto-flow: column; align-items: center; gap: 1cqi; }
.kv-card--grade > span:last-child { display: grid; gap: .25cqi; }
.kv-card__ring {
  position: relative; display: block;
  width: 4.6cqi; aspect-ratio: 1; border-radius: 50%;
}
.kv-card__ring b {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--kv-font); font-size: 1.6cqi; font-weight: 700; color: #e0261c;
}
/* the ring is masked on its own layer so the label is not masked away */
.kv-card__ring::before {
  content: ""; position: absolute; inset: 0; border-radius: 50%;
  background: conic-gradient(#ff2d2d 0 64%, #27d3e6 64% 100%);
  -webkit-mask: radial-gradient(circle, transparent 54%, #000 55%);
          mask: radial-gradient(circle, transparent 54%, #000 55%);
}

.kv-card--vault { left: 3.4cqi; top: 37%; width: 14.5cqi; }
.kv-card--cost  { right: 6cqi;  top: 13%; width: 14cqi; }
.kv-card--grade { right: 4cqi; bottom: 7%; }

@media (prefers-reduced-motion: no-preference) {
  .kv-card { animation: kv-float 7s ease-in-out infinite alternate; }
  .kv-card--cost  { animation-delay: -2.4s; }
  .kv-card--grade { animation-delay: -4.6s; }
}
@keyframes kv-float {
  from { translate: 0 0; }
  to   { translate: 0 -0.8cqi; }
}

/* ---- narrow: taller panel, bigger phones, two cards ---- */
@media (max-width: 47.99rem) {
  .kv { aspect-ratio: 4 / 4.6; min-height: 0; }
  .kv__mark { width: 7cqi; }
  .kv__word { width: 15cqi; }
  .kv__brand { left: 5cqi; top: 5cqi; gap: 2cqi; }
  .kv__line { display: none; }
  .kv-phone { --w: 42cqi; top: 19%; }
  .kv-phone--l, .kv-phone--r { --w: 34cqi; top: 27%; }
  .kv-phone--l { transform: translateX(calc(-50% - 33cqi)) rotate(-7deg); }
  .kv-phone--r { transform: translateX(calc(-50% + 33cqi)) rotate(7deg); }
  .kv-card { padding: 2.4cqi 2.8cqi; border-radius: 3cqi; gap: .7cqi; }
  .kv-card__tag { font-size: 2cqi; padding: .6cqi 1.4cqi; gap: 1cqi; }
  .kv-card__tag svg { width: 2.2cqi; height: 2.2cqi; }
  .kv-card__item { font-size: 2.6cqi; }
  .kv-card__sub { font-size: 2.2cqi; }
  .kv-card__timer { font-size: 5.6cqi; }
  .kv-card__bar { height: 1cqi; }
  .kv-card__neg { font-size: 3.6cqi; }
  .kv-card--grade { gap: 2cqi; }
  .kv-card__ring { width: 10cqi; }
  .kv-card__ring b { font-size: 3.6cqi; }
  .kv-card--vault { left: 4cqi; top: auto; bottom: 7%; width: 36cqi; }
  .kv-card--grade { right: 4cqi; top: 4cqi; bottom: auto; }
  .kv-card--cost { display: none; }
}

/* ==========================================================================
   Swiss layer — the v2 direction applied to the shared page furniture.
   Kept last on purpose: it overrides the older primitives above without
   touching the case-study components (flow player, KV, MyXR mats).
   ========================================================================== */

body { letter-spacing: -0.005em; line-height: 1.6; }
h1, h2, h3, h4 { font-weight: 500; letter-spacing: -0.03em; }
::selection { background: var(--accent); color: var(--accent-ink); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; border-radius: var(--radius); }
code, kbd { font-family: var(--font-mono); border-radius: var(--radius); }

/* Mono label — the small half of the big/small contrast */
.label {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.label--ink { color: var(--ink); }

/* Two-tone headline: wrap the second half in <span class="soft"> */
.soft { color: var(--ink-faint); }

/* ---------- Header ---------- */
.site-head {
  background: color-mix(in srgb, var(--paper) 90%, transparent);
  border-bottom: 1px solid var(--rule);
}
.site-head__inner { min-height: 3.75rem; }
.brand { font-family: var(--font-display); font-size: 1.0625rem; font-weight: 600; letter-spacing: -0.03em; align-items: baseline; gap: 0.75rem; }
.brand__role { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); font-weight: 500; }
.nav { gap: clamp(1rem, 2.4vw, 2.25rem); }
.nav__link {
  font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--muted); font-weight: 500;
}
.nav__link:hover { color: var(--ink); }
.nav__link[aria-current="page"] { color: var(--accent); }
.nav__link[aria-current="page"]::after { background: var(--accent); }
.nav-toggle { font-family: var(--font-mono); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.04em; border-radius: 999px; border-color: var(--rule-strong); padding: 0.5rem 0.9rem; min-height: 40px; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.75rem;
  min-height: 44px; padding: 0 1.5rem; border-radius: 999px; border: 1px solid transparent;
  font-family: var(--font-body); font-size: 0.9375rem; font-weight: 500; letter-spacing: -0.01em;
  text-decoration: none; white-space: nowrap; cursor: pointer;
  transition: background-color 0.2s ease-out, color 0.2s ease-out, border-color 0.2s ease-out;
}
.btn .arrow { display: inline-block; transition: transform 0.2s ease-out; }
.btn:hover .arrow { transform: rotate(45deg); }
.btn--primary { background: var(--ink); color: var(--paper); }
.btn--primary:hover { background: var(--accent); color: var(--accent-ink); }
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--rule-strong); }
.btn--ghost:hover { border-color: var(--ink); }
.btn--icon { width: 44px; padding: 0; }

/* ---------- Typography in pages ---------- */
.hero h1 { font-size: var(--step-5); line-height: 0.92; letter-spacing: -0.045em; max-width: 16ch; }
.hero h1 em { color: var(--ink-faint); }
.rule { background: var(--rule); }
.section { padding-block: var(--section); }

/* ---------- Case study ---------- */
.screens img {
  border: 0;
  border-radius: var(--radius-md);
  background: var(--paper-raised);
}
.fig-cap { font-size: var(--step--1); line-height: 1.4; color: var(--muted); }
.tag { font-family: var(--font-mono); font-size: 0.75rem; border-radius: var(--radius); border-color: var(--rule-strong); }

.next { border-top: 1px solid var(--ink); }

/* ---------- Footer: the giant wordmark ---------- */
.site-foot { background: var(--ink); color: #a3a39e; border-top: 0; padding-block: 1.25rem 0; overflow: hidden; }
.site-foot a { color: #f2f2f0; }
.site-foot__inner a { text-decoration: none; }
.site-foot__inner a:hover { color: var(--accent); }
.site-foot__inner { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.04em; text-transform: uppercase; }
.site-foot::after {
  content: "Moo Jun Jie";
  display: block;
  max-width: var(--shell);
  margin: clamp(2rem, 5vw, 4rem) auto 0;
  padding-inline: var(--gutter);
  font-family: var(--font-display); font-weight: 500;
  font-size: clamp(3.5rem, 17.5vw, 16rem); line-height: 0.8; letter-spacing: -0.06em;
  color: #f2f2f0; white-space: nowrap;
  padding-bottom: 0.04em; transform: translateY(0.08em);
}
.site-foot :focus-visible { outline-color: #8f9bff; }

@media (max-width: 47.99rem) {
  .brand__role { display: none; }
  .nav.is-open .nav__link { font-size: var(--step--1); }
}

/* ==========================================================================
   Case study system (v2) — see the design system's "案例页写法".
   A 3-column label rail on the left, content from column 4.
   ========================================================================== */
.case-page { display: grid; gap: clamp(4rem, 9vw, 7.5rem); padding-block: clamp(2.5rem, 6vw, 4rem) clamp(4rem, 8vw, 6rem); }
.case-grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--col-gap); }

.case-hero { display: grid; gap: 2rem; }
.case-hero__top { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.case-hero__logo { height: 32px; width: auto; }
.case-hero__title { font-size: clamp(2.25rem, 1.2rem + 4.4vw, 4.5rem); line-height: 0.96; letter-spacing: -0.04em; font-weight: 500; max-width: 15em; text-wrap: balance; }
.case-hero__lede { font-size: var(--step-1); line-height: 1.45; letter-spacing: -0.01em; max-width: 34em; }
.case-meta { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); column-gap: var(--col-gap); row-gap: 1rem; margin: 0; padding-top: 0.75rem; border-top: 1px solid var(--rule); }
.case-meta dt { margin-bottom: 0.5rem; }
.case-meta dd { margin: 0; font-size: var(--step--1); line-height: 1.43; }

.case-sec { padding-top: 0.75rem; border-top: 1px solid var(--rule); }

/* ---------- Page rail: one line per section at the right edge; hover, focus or a deep link opens the labels ---------- */
.page-rail { display: none; }
@media (min-width: 75rem) and (hover: hover) and (pointer: fine) {
  .page-rail {
    display: block; position: fixed; z-index: 30; top: 50%; translate: 0 -50%;
    /* lines end 6px from the viewport edge, so even at 1440 they stay inside the 24px page margin */
    right: max(0px, calc((100vw - var(--shell)) / 2 - 32px));
    padding: 0.5rem 5px 0.5rem 0.75rem; border: 1px solid transparent; border-radius: var(--radius-md);
    transition: background-color .2s var(--ease), border-color .2s var(--ease), box-shadow .2s var(--ease);
  }
  .page-rail ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; }
  .page-rail a { display: flex; align-items: center; justify-content: flex-end; gap: 0.75rem; padding: 5px 0; color: var(--ink-faint); text-decoration: none; }
  .page-rail .label { color: inherit; white-space: nowrap; opacity: 0; translate: 6px 0; transition: opacity .2s var(--ease), translate .2s var(--ease); }
  /* the line's box is always 16px and grows by scale, so the label column never shifts */
  .page-rail i { flex: none; width: 16px; height: 2px; border-radius: 1px; background: currentColor; opacity: .55; transform: scaleX(.625); transform-origin: right; transition: transform .2s var(--ease), opacity .2s var(--ease), background-color .2s var(--ease); }
  .page-rail a[aria-current] { color: var(--ink); }
  .page-rail a[aria-current] i { transform: none; opacity: 1; background: var(--accent); }
  .page-rail a:hover { color: var(--ink); }
  .page-rail a:hover i { opacity: 1; }
  /* same frosted paper as .site-head, so both floating navs read as one material */
  .page-rail:hover, .page-rail:focus-within, .page-rail.is-open {
    background: color-mix(in srgb, var(--paper) 88%, transparent); border-color: var(--rule);
    -webkit-backdrop-filter: saturate(140%) blur(10px); backdrop-filter: saturate(140%) blur(10px);
    box-shadow: 0 12px 32px -12px rgba(17, 17, 17, .18);
  }
  .page-rail:hover .label, .page-rail:focus-within .label, .page-rail.is-open .label { opacity: 1; translate: 0 0; }
  .page-rail:hover a, .page-rail:focus-within a, .page-rail.is-open a { color: var(--ink-soft); }   /* one step darker over images */
  .page-rail a[aria-current], .page-rail a:hover { color: var(--ink); }
}
.case-sec__rail { grid-column: 1 / span 3; display: grid; gap: 0.25rem; align-content: start; }
.case-sec__body { grid-column: 4 / span 9; display: grid; gap: 1.75rem; min-width: 0; }
.case-sec__title { font-size: var(--step-3); line-height: 1; letter-spacing: -0.03em; font-weight: 500; max-width: 16em; text-wrap: balance; }
.case-prose { display: grid; gap: 1rem; max-width: 34em; }
.case-prose > p:first-child { font-size: var(--step-1); line-height: 1.45; letter-spacing: -0.01em; }

.case-fig { margin: 0; display: grid; gap: 0.75rem; min-width: 0; }
.case-fig__frame { border-radius: var(--radius-md); overflow: hidden; background: var(--paper-raised); }
.case-fig__frame img { width: 100%; height: auto; }
.case-cap { display: grid; grid-template-columns: 5rem minmax(0, 1fr); gap: 1rem; padding-top: 0.5rem; border-top: 1px solid var(--rule); }
.case-cap > span:last-child { font-size: var(--step--1); line-height: 1.4; color: var(--muted); max-width: 46em; }

.case-callout { margin: 0; padding-top: 0.75rem; border-top: 1px solid var(--ink); display: grid; gap: 1rem; }
.case-callout p { font-size: clamp(1.375rem, 1.1rem + 1vw, 1.75rem); line-height: 1.15; letter-spacing: -0.02em; font-weight: 500; max-width: 22em; text-wrap: balance; }

.case-role { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--col-gap); }
.case-role__col { padding-top: 0.75rem; border-top: 1px solid var(--rule); }
.case-role__col--me { border-top-color: var(--ink); }
.case-role__led { grid-column: 1 / -1; margin-top: 1.5rem; }
.case-role ul { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: 0.5rem; }
.case-role li { position: relative; padding-left: 1rem; line-height: 1.45; }
.case-role li::before { content: ""; position: absolute; left: 0; top: 0.72em; width: 8px; height: 1px; background: currentColor; }
.case-role__col:not(.case-role__col--me) li { color: var(--muted); }

.case-theory { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2rem var(--col-gap); }
.case-theory > div { padding-top: 0.75rem; border-top: 1px solid var(--rule); display: grid; gap: 0.5rem; align-content: start; }
.case-theory h3 { font-size: var(--step-1); line-height: 1.2; letter-spacing: -0.02em; margin-top: 0.75rem; }
.case-theory p { font-size: var(--step-0); color: var(--ink-soft); }

.case-boards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--col-gap); align-items: start; }
.case-boards img { width: 100%; border-radius: var(--radius-md); background: var(--paper-raised); }
.case-boards--match img { aspect-ratio: 16 / 10; object-fit: cover; object-position: center top; } /* before/after pairs: same height */

.case-next { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: 1rem; padding-top: 0.75rem; border-top: 1px solid var(--ink); text-decoration: none; color: inherit; }
.case-next__title { margin-top: 1rem; font-size: var(--step-3); line-height: 1; letter-spacing: -0.035em; font-weight: 500; max-width: 18em; }
.case-next__arrow { font-size: var(--step-3); line-height: 1; transition: transform 0.2s ease-out; }
.case-next:hover .case-next__title, .case-next:hover .case-next__arrow { color: var(--accent); }
.case-next:hover .case-next__arrow { transform: translateX(6px); }

/* the flow player inside the 9-column body */
.case-sec__body .flow { margin: 0; }
.case-sec__body .kv { margin: 0; }

@media (max-width: 63.99rem) {
  .case-meta { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 47.99rem) {
  .case-meta { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .case-sec__rail, .case-sec__body { grid-column: 1 / -1; }
  .case-sec__rail { margin-bottom: 1.5rem; }
  .case-role, .case-theory, .case-boards { grid-template-columns: 1fr; row-gap: 1.5rem; }
  .case-cap { grid-template-columns: 1fr; gap: 0.25rem; }
}
/* flow player labels follow the mono label style */
.flow__step, .flow__hint b { font-family: var(--font-mono); font-size: 0.75rem; letter-spacing: 0.04em; font-weight: 500; text-transform: uppercase; }
.flow__caption { font-family: var(--font-body); font-weight: 500; letter-spacing: -0.015em; }

/* ---------- Case study: personas, needs map, journey ---------- */
.case-personas { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2.5rem var(--col-gap); }
.persona { display: grid; gap: 1rem; align-content: start; }
.persona__photo { aspect-ratio: 4 / 3; border-radius: var(--radius-md); overflow: hidden; background: var(--paper-sunken); }
.persona__photo img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 30%; }
.persona__head { display: grid; gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid var(--ink); }
.persona__name { font-size: var(--step-2); line-height: 1.1; letter-spacing: -0.025em; font-weight: 500; }
.persona__quote { font-size: var(--step-1); line-height: 1.4; letter-spacing: -0.01em; font-weight: 500; text-wrap: pretty; }
.persona__quote::before { content: "“"; } .persona__quote::after { content: "”"; }
.persona__lists { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem var(--col-gap); }
.persona__lists > div { padding-top: 0.5rem; border-top: 1px solid var(--rule); }
.persona__lists ul { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; gap: 0.6rem; }
.persona__lists li { font-size: var(--step--1); line-height: 1.45; }
.persona__lists li b { display: block; font-weight: 500; }
.persona__lists li span { color: var(--muted); }

.case-needs { display: grid; }
.case-needs__row { display: grid; grid-template-columns: minmax(0, 1fr) 6rem minmax(0, 0.8fr); gap: 1rem var(--col-gap); align-items: baseline; padding-block: 0.75rem; border-top: 1px solid var(--rule); text-decoration: none; color: inherit; }
.case-needs__row:last-child { border-bottom: 1px solid var(--rule); }
.case-needs__head { padding-block: 0 0.5rem; border-top: 0; }
.case-needs__need { line-height: 1.45; }
.case-needs__to { font-weight: 500; letter-spacing: -0.01em; }
.case-needs__to .arrow { display: inline-block; margin-left: 0.35rem; transition: transform 0.2s ease-out; }
a.case-needs__row:hover .case-needs__to { color: var(--accent); }
a.case-needs__row:hover .arrow { transform: translateX(4px); }

.case-journey { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--col-gap); }
.case-journey > div { display: grid; gap: 0.5rem; align-content: start; padding-top: 0.75rem; border-top: 1px solid var(--ink); }
.case-journey h3 { font-size: var(--step-0); line-height: 1.2; letter-spacing: -0.02em; margin-top: 0.25rem; }
.case-journey p { font-size: var(--step--1); line-height: 1.45; }
.case-journey p.pain { color: var(--muted); }
.case-journey p.opp::before { content: "→ "; color: var(--accent); }
.case-link { color: inherit; text-decoration: underline; text-decoration-color: var(--rule-strong); text-underline-offset: 0.2em; }
.case-link:hover { color: var(--accent); text-decoration-color: currentColor; }

@media (max-width: 63.99rem) {
  .case-journey { grid-template-columns: repeat(3, minmax(0, 1fr)); row-gap: 1.5rem; }
}
@media (max-width: 47.99rem) {
  .case-personas, .persona__lists { grid-template-columns: 1fr; }
  .case-journey { grid-template-columns: 1fr; }
  .case-needs__row { grid-template-columns: 1fr auto; }
  .case-needs__row > :nth-child(2) { grid-row: 2; }
  .case-needs__head { display: none; }
}

/* Stacked UI exports on one mat (MyXR dialogue variants). Each image keeps
   its width relative to the widest export, so every bar shares one scale. */
.case-stack { display: grid; gap: clamp(0.5rem, 1.2vw, 0.85rem); justify-items: start; align-content: center; }
.case-stack img { height: auto; max-height: none; }

/* Metrics — up to three measured outcomes (design system: Metrics) */
.case-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--col-gap); }
.case-metric { display: grid; gap: 0.5rem; align-content: start; padding-top: 0.75rem; border-top: 1px solid var(--ink); }
.case-metric__value { font-size: clamp(3rem, 2.2rem + 2.4vw, 4rem); line-height: 1; font-weight: 500; letter-spacing: -0.045em; font-variant-numeric: tabular-nums; }
.case-metric__value small { font-size: 0.4em; letter-spacing: -0.02em; color: var(--muted); } /* under 24px, so --ink-faint is not allowed */
.case-metric__note { font-size: var(--step--1); line-height: 1.43; color: var(--muted); max-width: 22em; }
@media (max-width: 47.99rem) {
  .case-metrics { grid-template-columns: 1fr; row-gap: 1.5rem; }
}

/* About page: tool lists, timeline, contact rows — same rules as the case pages */
.case-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2rem var(--col-gap); }
.case-list { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; }
.case-list li { display: grid; gap: 0.25rem; padding-block: 0.75rem; border-top: 1px solid var(--rule); }
.case-list li:last-child { border-bottom: 1px solid var(--rule); }
.case-list b { font-weight: 500; }
.case-list span { color: var(--muted); font-size: var(--step--1); line-height: 1.45; }
.case-timeline { display: grid; gap: 2.5rem; }
.case-timeline > div { display: grid; grid-template-columns: 10rem minmax(0, 1fr); column-gap: var(--col-gap); row-gap: 0.5rem; padding-top: 0.75rem; border-top: 1px solid var(--ink); }
.case-timeline > div > .label { grid-row: span 2; }
.case-timeline h3 { font-size: var(--step-1); line-height: 1.25; letter-spacing: -0.02em; font-weight: 500; }
.case-timeline p { color: var(--ink-soft); max-width: 40em; }
@media (max-width: 47.99rem) {
  .case-cols { grid-template-columns: 1fr; }
  .case-timeline > div { grid-template-columns: 1fr; }
  .case-timeline > div > .label { grid-row: auto; }
}

/* Notes: real phone screenshots in a row */
.case-phones { --phones: 3; display: grid; grid-template-columns: repeat(var(--phones), minmax(0, 1fr)); gap: var(--col-gap); align-items: start; }
.case-phones--two { --phones: 2; max-width: 40rem; }
.case-phones--one { --phones: 1; max-width: 20rem; }
.case-phones img, .case-phones video { display: block; width: 100%; height: auto; border-radius: clamp(14px, 2.2vw, 26px); background: var(--paper-raised); box-shadow: 0 0 0 1px var(--rule); }
@media (max-width: 47.99rem) {
  .case-phones { --phones: 2; }
  .case-phones--one { --phones: 1; max-width: 16rem; }
}

/* Notes: a row of buttons (also used on the home page), and the list on notes/ */
.btn-row { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.note-list { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(2.5rem, 5vw, 4rem); }
.note-row { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--col-gap); row-gap: 1rem; padding-top: 0.75rem; border-top: 1px solid var(--ink); text-decoration: none; color: inherit; }
.note-row__date { grid-column: 1 / span 3; display: grid; gap: 0.25rem; align-content: start; }
.note-row__body { grid-column: 4 / span 5; display: grid; gap: 0.75rem; align-content: start; }
.note-row__title { font-size: var(--step-2); line-height: 1.1; letter-spacing: -0.025em; font-weight: 500; }
.note-row__line { color: var(--ink-soft); max-width: 34em; }
.note-row__media { grid-column: 9 / span 4; aspect-ratio: 16 / 10; border-radius: var(--radius-md); overflow: hidden; background: var(--paper-sunken); }
.note-row__media img { width: 100%; height: 100%; object-fit: cover; object-position: top left; transition: transform 0.3s ease-out; }
/* desktop screenshots: shown whole and centred with room around them, not cropped to the frame */
.note-row__media--inset { display: grid; place-items: center; padding: 7%; }
.note-row__media--inset img { width: auto; height: auto; max-width: 100%; max-height: 100%; object-fit: contain; border-radius: var(--radius); box-shadow: 0 0 0 1px var(--rule), 0 8px 24px -12px rgb(0 0 0 / 0.25); }
.note-row .arrow { display: inline-block; transition: transform 0.2s ease-out, color 0.2s ease-out; }
.note-row:hover .note-row__title, .note-row:hover .arrow { color: var(--accent); }
.note-row:hover .arrow { transform: rotate(45deg); }
.note-row:hover .note-row__media img { transform: scale(1.02); }
@media (max-width: 63.99rem) {
  .note-row__body { grid-column: 4 / -1; }
  .note-row__media { grid-column: 4 / -1; }
}
@media (max-width: 47.99rem) {
  .note-row__date, .note-row__body, .note-row__media { grid-column: 1 / -1; }
}

/* ==========================================================================
   Grid alignment (48rem and up)
   Every case-study component snaps to the page's 12 columns. A section body
   is a 9-column subgrid (columns 4-12), so anything inside it spans real
   columns instead of splitting the width at 4.5 columns:
     two equal items  4 | gap column | 4   both outer edges stay on the body edges
     figure captions  label in column 1, text from column 2
     needs table      4 | 2 | 3
     timeline         date 2 | text 7
   Hero meta row (64rem and up): 3 | 2 | 2 | 2 | 3, so the second item starts
   on column 4, the same line as every section body below it.
   Kept last so it overrides the single-column defaults above, which still
   apply on phones.
   ========================================================================== */
@media (min-width: 48rem) {
  .case-hero { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--col-gap); }
  .case-hero > * { grid-column: 1 / -1; }

  .case-sec__body { grid-template-columns: subgrid; column-gap: var(--col-gap); }
  .case-sec__body > * { grid-column: 1 / -1; }

  /* figures and captions */
  .case-hero > .case-fig,
  .case-sec__body > .case-fig { grid-template-columns: subgrid; column-gap: var(--col-gap); }
  .case-fig > * { grid-column: 1 / -1; }
  .case-hero > .case-fig > .case-cap,
  .case-sec__body > .case-fig > .case-cap { grid-template-columns: subgrid; column-gap: var(--col-gap); }
  .case-fig > .case-cap > span:last-child { grid-column: 2 / -1; }

  /* two equal items: 4 | 1 | 4 */
  .case-sec__body > .case-theory,
  .case-sec__body > .case-personas,
  .case-sec__body > .case-cols,
  .case-sec__body > .case-fig > .case-boards,
  .case-sec__body > .case-fig > .grid-2 { grid-template-columns: subgrid; column-gap: var(--col-gap); }
  .case-sec__body > .case-theory > :nth-child(odd),
  .case-sec__body > .case-personas > :nth-child(odd),
  .case-sec__body > .case-cols > :nth-child(odd),
  .case-sec__body > .case-fig > .case-boards > :nth-child(odd),
  .case-sec__body > .case-fig > .grid-2 > :nth-child(odd) { grid-column: 1 / span 4; }
  .case-sec__body > .case-theory > :nth-child(even),
  .case-sec__body > .case-personas > :nth-child(even),
  .case-sec__body > .case-cols > :nth-child(even),
  .case-sec__body > .case-fig > .case-boards > :nth-child(even),
  .case-sec__body > .case-fig > .grid-2 > :nth-child(even) { grid-column: 6 / span 4; }

  /* needs table / contact rows: 4 | 2 | 3 */
  .case-sec__body > .case-needs { grid-template-columns: subgrid; column-gap: var(--col-gap); }
  .case-needs > .case-needs__row { grid-column: 1 / -1; grid-template-columns: subgrid; column-gap: var(--col-gap); }
  .case-needs__row > :nth-child(1) { grid-column: 1 / span 4; }
  .case-needs__row > :nth-child(2) { grid-column: 5 / span 2; }
  .case-needs__row > :nth-child(3) { grid-column: 7 / span 3; }

  /* timeline: date 2 | text 7 */
  .case-sec__body > .case-timeline { grid-template-columns: subgrid; column-gap: var(--col-gap); }
  .case-timeline > div { grid-column: 1 / -1; grid-template-columns: subgrid; }
  .case-timeline > div > .label { grid-column: 1 / span 2; }
  .case-timeline > div > :not(.label) { grid-column: 3 / -1; }
}
@media (min-width: 48rem) and (max-width: 63.99rem) {
  /* a persona card is only four narrow columns here: stack its two lists */
  .persona__lists { grid-template-columns: 1fr; }
}
@media (min-width: 64rem) {
  .case-meta { grid-template-columns: subgrid; }
  .case-meta > :nth-child(1) { grid-column: 1 / span 3; }
  .case-meta > :nth-child(2) { grid-column: 4 / span 2; }
  .case-meta > :nth-child(3) { grid-column: 6 / span 2; }
  .case-meta > :nth-child(4) { grid-column: 8 / span 2; }
  .case-meta > :nth-child(5) { grid-column: 10 / span 3; }
  /* About: six facts, two rows of three */
  .case-meta--six > :nth-child(n) { grid-column: auto / span 4; }
}


/* ==========================================================================
   Logo mark — "J." The full stop hops on hover / keyboard focus.
   Geometry lives on a 64-unit grid (assets/img/logo/j-logo.svg); the
   animation is started by site.js so it always finishes once begun.
   ========================================================================== */
.brand__mark {
  flex: none; width: 1.625rem; height: 1.625rem;
  position: relative; top: 0.203rem; /* 8/64 of the box: drops the J onto the text baseline */
  margin-right: -0.25rem;
  overflow: visible;
}
.brand__j { fill: var(--ink); }
.brand__dot { fill: var(--accent); }
.brand__j, .brand__dot { transform-box: fill-box; transform-origin: 50% 100%; }

@media (prefers-reduced-motion: no-preference) {
  .brand.is-hopping .brand__dot { animation: brand-hop 0.85s both; }
  .brand.is-hopping .brand__j   { animation: brand-nod 0.85s both; }
}
/* crouch → jump to the top of the J → land → squash → settle */
@keyframes brand-hop {
  0%   { transform: translateY(0) scale(1, 1);        animation-timing-function: cubic-bezier(.3, 0, .5, 1); }
  14%  { transform: translateY(0) scale(1.3, .7);     animation-timing-function: cubic-bezier(.15, .6, .35, 1); }
  46%  { transform: translateY(-32px) scale(.9, 1.12); animation-timing-function: cubic-bezier(.6, 0, .85, .45); }
  70%  { transform: translateY(0) scale(.92, 1.1);    animation-timing-function: ease-out; }
  79%  { transform: translateY(0) scale(1.3, .72);    animation-timing-function: cubic-bezier(.3, 0, .3, 1); }
  90%  { transform: translateY(-3px) scale(.96, 1.04); animation-timing-function: ease-in-out; }
  100% { transform: translateY(0) scale(1, 1); }
}
/* the J gives a little under the landing */
@keyframes brand-nod {
  0%, 70% { transform: scale(1, 1); }
  80%     { transform: scale(1.03, .95); animation-timing-function: ease-out; }
  92%     { transform: scale(.99, 1.01); }
  100%    { transform: scale(1, 1); }
}

/* ---------- Page transitions ----------
   Cross-document View Transitions: the browser snapshots the old page and
   animates into the new one on same-origin navigations. Pure CSS, no JS.
   Unsupported browsers (and file:// previews) simply keep the normal cut.
   The header is named on desktop so it stays put while only the content
   moves; on mobile it scrolls away with the page, so it joins the fade. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}

@media (min-width: 48rem) {
  .site-head { view-transition-name: site-head; }
}

@keyframes vt-out {
  to { opacity: 0; transform: translate3d(0, -8px, 0); }
}
@keyframes vt-in {
  from { opacity: 0; transform: translate3d(0, 16px, 0); }
}

::view-transition-old(root) {
  animation: vt-out 0.18s cubic-bezier(0.4, 0, 1, 1) both;
}
::view-transition-new(root) {
  animation: vt-in 0.42s cubic-bezier(0.22, 0.61, 0.36, 1) 0.08s both;
}

/* the header itself: no slide, just a quick swap of the active-link state */
::view-transition-group(site-head) { animation-duration: 0.25s; }
::view-transition-old(site-head),
::view-transition-new(site-head) { animation-duration: 0.25s; }

/* ---------- Anchor offset ----------
   On desktop the header is sticky, so in-page and cross-page jumps
   (about.html#experience, index.html#work…) land below it instead of under it. */
@media (min-width: 48rem) {
  html { scroll-padding-top: calc(4.25rem + 1.5rem); }
}

/* ---------- PRD trace table (MyXR) ---------- */
.visually-hidden { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.case-trace { width: 100%; border-collapse: collapse; font-size: var(--step--1); line-height: 1.45; }
.case-trace th, .case-trace td { text-align: left; vertical-align: top; padding: 0.85rem 1rem 0.85rem 0; border-top: 1px solid var(--rule); }
.case-trace thead th { font-family: var(--font-mono); font-size: 0.75rem; font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); border-top: 1px solid var(--ink); padding-top: 0.75rem; }
.case-trace tbody th { font-weight: 500; color: var(--ink); width: 32%; }
.case-trace td.label { width: 18%; white-space: nowrap; }
.case-trace td:last-child { color: var(--ink-soft); padding-right: 0; }
.case-trace em { font-style: normal; color: var(--ink); font-weight: 500; }
.case-fig__frame--narrow { max-width: 30rem; }
@media (max-width: 47.99rem) {
  .case-trace thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
  .case-trace, .case-trace tbody, .case-trace tr, .case-trace th, .case-trace td { display: block; width: auto !important; }
  .case-trace tr { border-top: 1px solid var(--rule); padding: 0.85rem 0; }
  .case-trace th, .case-trace td { border-top: 0; padding: 0.15rem 0; }
  .case-trace td.label { white-space: normal; }
}

/* ---------- Exhibition voices (Nudge) ---------- */
.case-voices { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--col-gap); }
.case-voices blockquote { margin: 0; display: flex; flex-direction: column; justify-content: space-between; gap: 1rem; padding: 1.25rem; border-radius: var(--radius-md); background: var(--paper-raised); }
.case-voices p { font-size: var(--step-0); line-height: 1.5; letter-spacing: -0.005em; text-wrap: pretty; }
.case-voices p::before { content: "\201C"; } .case-voices p::after { content: "\201D"; }
.case-verdicts li { display: block; color: var(--ink-soft); }
@media (max-width: 63.99rem) { .case-voices { grid-template-columns: 1fr; } }

/* ---------- Colour roles (Nudge) ---------- */
.case-swatches { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: var(--col-gap); }
.case-swatches li { display: grid; gap: 0.5rem; align-content: start; }
.case-swatch__chip { display: block; aspect-ratio: 4 / 3; border-radius: var(--radius-md); box-shadow: inset 0 0 0 1px rgba(17,17,17,.08); }
.case-swatch__name { font-weight: 500; line-height: 1.3; }
.case-swatch__name code { display: block; background: none; border: 0; padding: 0; margin-top: 0.15rem; font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.case-swatch__use { font-size: var(--step--1); line-height: 1.45; color: var(--ink-soft); }
@media (max-width: 63.99rem) { .case-swatches { grid-template-columns: repeat(2, minmax(0, 1fr)); row-gap: 1.5rem; } }

/* ---------- Condensed UX flow (Nudge) ---------- */
.uxf { display: grid; gap: 2.5rem; }
.uxf__part { display: grid; gap: 1rem; }
.uxf__head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0.25rem 1rem; padding-top: 0.75rem; border-top: 1px solid var(--ink); }
.uxf__row, .uxf__outcomes, .uxf__side { list-style: none; margin: 0; padding: 0; }
.uxf__row { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.75rem 2rem; }
.uxf__node, .uxf__out { position: relative; display: grid; gap: 0.35rem; align-content: start; padding: 1rem; border-radius: var(--radius-md); background: var(--paper-raised); }
.uxf__node b, .uxf__out b { font-size: var(--step-0); line-height: 1.25; letter-spacing: -0.015em; font-weight: 500; }
.uxf__node > span:last-child, .uxf__out > span:last-child { font-size: var(--step--1); line-height: 1.45; color: var(--ink-soft); }
.uxf__node em { font-style: normal; font-weight: 500; color: var(--ink); }
.uxf__row > .uxf__node:not(:last-child)::after { content: "\2192"; position: absolute; top: 1rem; right: -1.5rem; font-family: var(--font-mono); font-size: 0.875rem; line-height: 1rem; color: var(--muted); }
.uxf__node--hub { background: var(--ink); color: var(--paper); }
.uxf__node--hub .label, .uxf__node.uxf__node--hub > span:last-child { color: #b9b9b4; }
.uxf__node--decide { background: transparent; box-shadow: inset 0 0 0 1px var(--rule-strong); }
.uxf__node--decide .label::before { content: "\25C7\00a0"; }
.uxf__fork { display: flex; align-items: center; gap: 0.75rem; margin: 0.25rem 0 0; }
.uxf__fork::after { content: ""; flex: 1; border-top: 1px dashed var(--rule-strong); }
.uxf__outcomes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--col-gap); }
.uxf__out { border-top: 2px solid var(--ink); border-radius: 0 0 var(--radius-md) var(--radius-md); }
.uxf__out--cost { background: var(--accent); color: var(--accent-ink); border-top-color: var(--accent); }
.uxf__out--cost .label, .uxf__out.uxf__out--cost > span:last-child { color: rgba(255, 255, 255, 0.86); }
.uxf__return { margin: 0; font-family: var(--font-mono); font-size: 0.75rem; line-height: 1rem; font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
.uxf__side { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--col-gap); }
.uxf__side li { display: grid; grid-template-columns: 6.5rem minmax(0, 1fr); gap: 1rem; padding-block: 0.75rem; border-top: 1px solid var(--rule); }
.uxf__side b { font-weight: 500; letter-spacing: -0.01em; line-height: 1.45; }
.uxf__side span { font-size: var(--step--1); line-height: 1.45; color: var(--ink-soft); }
@media (max-width: 63.99rem) {
  .uxf__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .uxf__row > .uxf__node:nth-child(2n)::after { content: none; }
  .uxf__side { grid-template-columns: 1fr; }
}
@media (max-width: 47.99rem) {
  .uxf__row { grid-template-columns: 1fr; row-gap: 1.75rem; }
  .uxf__row > .uxf__node:not(:last-child)::after { content: "\2193"; top: auto; right: auto; left: 1rem; bottom: -1.4rem; }
  .uxf__outcomes { grid-template-columns: 1fr; }
  .uxf__side li { grid-template-columns: 1fr; gap: 0.25rem; }
}

/* ---------- KV: legible floors ----------
   The KV scales with its container (cqi), which left card text at 6–9px on
   phones and tablets. Each size keeps its cqi value on large screens but never
   drops below a readable floor; cards grow to fit instead of clipping. */
.kv-card__tag   { font-size: max(.82cqi, 11px); }
.kv-card__tag svg { width: max(.95cqi, 12px); height: max(.95cqi, 12px); }
.kv-card__item  { font-size: max(1.05cqi, 13px); }
.kv-card__sub   { font-size: max(.85cqi, 12px); }
.kv-card__plates b { font-size: max(.8cqi, 11px); }
.kv-card__neg   { font-size: max(1.5cqi, 15px); }
.kv-card__ring  { width: max(4.6cqi, 44px); }
.kv-card__ring b { font-size: max(1.6cqi, 14px); }
.kv-card__timer { font-size: max(2.4cqi, 20px); }
.kv-card__big   { font-size: max(3.6cqi, 28px); }
.kv-card--vault { width: max-content; min-width: 14.5cqi; }
.kv-card--cost  { width: max-content; min-width: 14cqi; }
@media (max-width: 47.99rem) {
  .kv-card__tag   { font-size: max(2cqi, 11px); }
  .kv-card__tag svg { width: max(2.2cqi, 12px); height: max(2.2cqi, 12px); }
  .kv-card__item  { font-size: max(2.6cqi, 13px); }
  .kv-card__sub   { font-size: max(2.2cqi, 12px); }
  .kv-card__neg   { font-size: max(3.6cqi, 15px); }
  .kv-card__ring  { width: max(10cqi, 44px); }
  .kv-card__ring b { font-size: max(3.6cqi, 14px); }
  .kv-card__timer { font-size: max(5.6cqi, 20px); }
  .kv-card--vault { min-width: 36cqi; }
}

/* ---------- Short pages (404): footer sits at the bottom of the viewport ---------- */
.page-short { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; }
.page-short > main { flex: 1 0 auto; width: 100%; align-content: start; }
.page-short > .site-head, .page-short > .site-foot { width: 100%; }

/* ---------- Lightbox: figure images open in an album-style viewer (site.js) ---------- */
.is-zoomable { cursor: zoom-in; }
html.lightbox-open { overflow: hidden; }
.lightbox {
  position: fixed; inset: 0; width: 100%; height: 100%; max-width: none; max-height: none;
  margin: 0; padding: 0; border: 0; background: rgba(14, 14, 13, .94); color: #fff; overflow: hidden;
}
.lightbox[open] { display: grid; grid-template-rows: minmax(0, 1fr) auto; }
.lightbox::backdrop { background: transparent; }   /* the dialog fills the viewport and carries the dark itself */
/* flex, not grid: a flex item's max-height:100% resolves against the stage; margin:auto centres it and still lets a zoomed image scroll from its top */
.lightbox__stage { display: flex; min-height: 0; overflow: auto; padding: 4rem 5rem 0.5rem; overscroll-behavior: contain; }
.lightbox__img { margin: auto; max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; border-radius: var(--radius-md); cursor: zoom-in; user-select: none; }
.lightbox__img.is-in { animation: lightbox-in .24s var(--ease); }
@keyframes lightbox-in { from { opacity: 0; transform: scale(.985); } }
.lightbox.is-zoomed .lightbox__img { flex: none; max-width: none; max-height: none; border-radius: 0; cursor: zoom-out; }
.lightbox__bar { display: flex; align-items: baseline; justify-content: center; gap: 1rem; padding: 0.75rem 5rem 1.5rem; }
.lightbox__count { color: rgba(255, 255, 255, .55); flex: none; }
.lightbox__cap { margin: 0; max-width: 60ch; font-size: var(--step--1); line-height: 1.45; color: rgba(255, 255, 255, .85); }
.lightbox__btn {
  position: absolute; display: grid; place-items: center; width: 2.75rem; height: 2.75rem; padding: 0;
  border: 1px solid rgba(255, 255, 255, .16); border-radius: 999px; background: rgba(255, 255, 255, .08);
  color: #fff; font: 400 1.25rem/1 var(--font-body); cursor: pointer; transition: background-color .2s var(--ease);
}
.lightbox__btn:hover { background: rgba(255, 255, 255, .18); }
.lightbox__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.lightbox__close { top: 1rem; right: 1rem; font-size: 1.5rem; }
.lightbox__prev, .lightbox__next { top: 50%; translate: 0 -50%; }
.lightbox__prev { left: 1rem; }
.lightbox__next { right: 1rem; }
.lightbox.is-single .lightbox__prev, .lightbox.is-single .lightbox__next, .lightbox.is-single .lightbox__count { display: none; }
@media (max-width: 47.99rem) {
  .lightbox__stage { padding: 3.75rem 0.75rem 0.5rem; }
  .lightbox__bar { padding: 0.75rem 4.25rem 1.25rem; flex-direction: column;   /* sides clear the corner arrows */ align-items: center; gap: 0.35rem; text-align: center; }
  .lightbox__prev, .lightbox__next { top: auto; bottom: 1rem; translate: none; width: 2.5rem; height: 2.5rem; }
}
@media (prefers-reduced-motion: reduce) { .lightbox__img.is-in { animation: none; } }
