/* Frijole + Gaegu load via <link> in each page's <head> (parallel with this
   file) — the old @import here serialized the font fetch behind the CSS. */

:root {
  color-scheme: light;
  --ink: #050505;
  --paper: #ffffff;
  --paper-deep: #f7f7f7;
  --surface: #ffffff;
  --orange: #ff5d12;
  --orange-deep: #e94927;
  --purple: #7961a6;
  --muted: #615b55;
  --line: #111111;
  --shadow: #050505;
  --radius: 8px;
  --hand: "Gaegu", "Comic Sans MS", "Bradley Hand", "Marker Felt", "Segoe Print", cursive;
  --stamp: "Frijole", "Courier New", "American Typewriter", fantasy;
  --body: "Gaegu", "Trebuchet MS", "Avenir Next", Verdana, sans-serif;
  --home-title-size: clamp(28px, 3vw, 42px);
}

/* first-frame dark: an inline script in each page's <head> stamps
   <html data-theme> from localStorage/prefers-color-scheme BEFORE first paint,
   and the html[data-theme="dark"] alternates below make the very first frame
   render with the dark palette — no white flash, no light→dark fade (the
   body background never changes value when script.js later stamps <body>). */
html {
  /* fade the canvas/overscroll color together with body on a theme switch */
  transition: background-color 240ms ease;
}

html[data-theme="dark"] {
  background: #070707; /* = dark --paper, covers overscroll areas too */
  color-scheme: dark;
}

body[data-theme="dark"],
html[data-theme="dark"] body {
  color-scheme: dark;
  --ink: #f9f3eb;
  --paper: #070707;
  --paper-deep: #101010;
  --surface: #090909;
  --orange: #ff7a1a;
  --orange-deep: #ff9a3d;
  --purple: #b7a1f1;
  --muted: #c8bdb3;
  --line: #f6eee7;
  --shadow: #ff7a1a;
}

* {
  box-sizing: border-box;
}

html {
  min-height: 100%;
  scroll-behavior: smooth;
  background: var(--paper);
}

body {
  min-height: 100vh;
  margin: 0;
  color: var(--ink);
  background: var(--paper);
  font-family: var(--body);
  font-size: 24px;
  font-weight: 400;
  /* clip, NOT hidden: `overflow-x: hidden` forces the spec to compute
     `overflow-y: auto`, which turns <body> into a scroll container and breaks
     every `position: sticky` inside it on iOS Safari (the pinned intro scene
     scrolls away instead of holding). `overflow-x: clip` hides horizontal
     overflow identically WITHOUT establishing a scroll container, so sticky
     keeps working. */
  overflow-x: clip;
  transition:
    background-color 240ms ease,
    color 240ms ease;
}

body.home-opening-active {
  overflow: hidden;
}

a {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

button {
  color: inherit;
  cursor: pointer;
}

img,
video {
  display: block;
  max-width: 100%;
}

.site-header {
  --header-top-pad: 36px;
  --chain-gap: 18px;
  --chain-width: 34px;
  position: sticky;
  top: 0;
  z-index: 40;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
  width: min(100% - 112px, 1280px);
  margin: 0 auto;
  padding: var(--header-top-pad) 0 46px;
  transition:
    opacity 260ms ease,
    padding 180ms ease,
    transform 320ms ease,
    visibility 320ms ease;
  visibility: visible;
}

body[data-page="home"].home-opening-active:not(.home-gallery-active) .site-header {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-10px);
  visibility: hidden;
}

.site-header.is-auto-hidden:not(.nav-open) {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-10px);
  visibility: hidden;
}

.site-header.is-scrolled {
  --header-top-pad: 36px;
  --chain-width: 34px;
  padding: var(--header-top-pad) 0 46px;
}

.brand-mark {
  display: inline-flex;
  align-items: center;
  width: 70px;
  height: 54px;
  transform-origin: left center;
  transition: transform 180ms ease;
}

.site-logo {
  width: 64px;
  height: 64px;
  object-fit: contain;
  transform: scale(1.7);
}

.main-nav {
  display: flex;
  justify-content: center;
  gap: clamp(18px, 2.6vw, 38px);
  padding: 12px 30px;
  border: 2px solid rgba(255, 255, 255, 0.92);
  border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 84%, transparent);
  font-family: var(--hand);
  font-size: clamp(18px, 1.45vw, 23px);
  line-height: 1;
  white-space: nowrap;
  transform: translateY(0) scale(1);
  transition:
    background 180ms ease,
    border-color 180ms ease,
    padding 180ms ease,
  transform 180ms ease;
}

.main-nav .nav-connect {
  display: none;
}

.menu-socials {
  display: none;
}

.site-header.is-scrolled .brand-mark {
  transform: none;
}

.site-header.is-scrolled .main-nav {
  gap: clamp(18px, 2.6vw, 38px);
  padding: 12px 30px;
  border-radius: 999px;
  border-color: rgba(255, 255, 255, 0.92);
  background: color-mix(in srgb, var(--paper) 90%, transparent);
  transform: translateY(0) scale(1);
}

.site-header.is-scrolled .pull-chain {
  height: 168px;
  width: var(--chain-width);
}

.site-header.is-scrolled .connect-button {
  min-height: 44px;
  padding-inline: 24px;
}

.main-nav a {
  --nav-roll-distance: 148%;
  position: relative;
  display: inline-block;
  overflow: hidden;
  padding: 8px 2px;
  color: var(--ink);
  line-height: 1.12;
}

.main-nav a .nav-link-label {
  display: block;
  transition: transform 260ms cubic-bezier(0.45, 0.02, 0.12, 1);
}

.main-nav a::after {
  content: attr(data-nav-label);
  position: absolute;
  top: 8px;
  left: 2px;
  right: 2px;
  display: block;
  color: var(--muted);
  text-align: center;
  pointer-events: none;
  transform: translateY(var(--nav-roll-distance));
  transition: transform 260ms cubic-bezier(0.45, 0.02, 0.12, 1);
}

.main-nav a:hover,
.main-nav a:focus-visible {
  color: var(--ink);
}

.main-nav a:hover .nav-link-label,
.main-nav a:focus-visible .nav-link-label {
  transform: translateY(calc(-1 * var(--nav-roll-distance)));
}

.main-nav a:hover::after,
.main-nav a:focus-visible::after {
  transform: translateY(0);
}

.header-actions {
  position: relative;
  justify-self: end;
  display: inline-flex;
  align-items: center;
  gap: 18px;
  min-height: 50px;
  padding-right: calc(var(--chain-width) + var(--chain-gap));
}

.pill-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: max-content;
  min-height: 50px;
  padding: 0 28px;
  border: 2px solid var(--line);
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--hand);
  font-size: 20px;
  line-height: 1;
  box-shadow: 0 0 0 var(--shadow);
  transition:
    transform 180ms ease,
    box-shadow 180ms ease,
    background 180ms ease,
    color 180ms ease;
}

.connect-button {
  min-height: 44px;
  padding-inline: 24px;
  font-size: clamp(16px, 1.15vw, 18px);
}

.pill-button:hover {
  background: var(--paper);
  color: var(--ink);
  transform: translateY(-2px);
  box-shadow: none;
}

.pill-button.light {
  background: var(--surface);
  color: var(--ink);
}

.pill-button.light:hover {
  background: var(--ink);
  color: var(--paper);
}

.pull-chain {
  position: absolute;
  appearance: none;
  -webkit-appearance: none;
  top: calc(-1 * var(--header-top-pad));
  right: 0;
  display: inline-flex;
  align-items: flex-start;
  justify-content: center;
  width: var(--chain-width);
  height: 168px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: grab;
  transform-origin: top center;
  transition:
    transform 220ms ease,
    opacity 180ms ease;
}

.pull-chain:hover {
  opacity: 0.68;
  transform: translateY(3px);
}

.pull-chain:focus-visible {
  outline: none;
  box-shadow: none;
}

.pull-chain:active {
  cursor: grabbing;
}

.pull-chain:focus-visible .chain-image {
  outline: none;
}

.pull-chain.is-pulled {
  animation: chain-pull 720ms cubic-bezier(0.2, 0.9, 0.2, 1);
}

.chain-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center top;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
  filter: contrast(1.06);
  transform-origin: 50% 0;
}

@keyframes chain-pull {
  0% {
    transform: translateY(0) scaleY(1) rotate(0deg);
  }
  22% {
    transform: translateY(28px) scaleY(1.16) rotate(-7deg);
  }
  44% {
    transform: translateY(14px) scaleY(0.97) rotate(6deg);
  }
  70% {
    transform: translateY(-4px) scaleY(1.02) rotate(-3deg);
  }
  100% {
    transform: translateY(0) scaleY(1) rotate(0deg);
  }
}

.menu-toggle {
  display: none;
}

.brand-mark:focus-visible,
.main-nav a:focus-visible,
.pill-button:focus-visible,
.menu-toggle:focus-visible,
.text-link:focus-visible,
.work-card:focus-visible {
  outline: 3px solid var(--orange);
  outline-offset: 5px;
}

.home-intro-screen {
  position: fixed;
  inset: 0;
  z-index: 34;
  display: grid;
  place-items: center;
  min-height: 100svh;
  padding: 120px clamp(20px, 5vw, 84px);
  background: #ffffff;
  color: #000000;
  font-family: var(--hand);
  filter: blur(12px);
  opacity: 0;
  pointer-events: none;
  transform: translateY(10px) scale(0.992);
  transition:
    opacity 560ms ease,
    filter 640ms ease,
    transform 640ms ease,
    visibility 640ms ease;
  visibility: hidden;
  will-change: opacity, filter, transform;
}

.home-intro-screen.is-active {
  filter: blur(0);
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0) scale(1);
  visibility: visible;
}

body.home-opening-complete .home-intro-screen {
  display: none;
}

.intro-line {
  min-height: 1.25em;
  margin: 0;
  color: #000000;
  font-family: var(--hand);
  /* Floor raised 18→25: this base rule owns every width >820px, but 1.72vw only
     reaches 25px at ~1450px, so the whole 821–1450 band (iPad, small laptops)
     used to sit at the 18px floor — tiny, and a jarring 27→18 drop across the
     820px breakpoint where the ≤820 rule caps at 27. 25px keeps that band at the
     desktop size; the max (27) still governs very wide screens unchanged. */
  font-size: clamp(27px, 1.9vw, 30px);
  font-weight: 300;
  font-synthesis: none;
  line-height: 1.12;
  text-align: center;
  letter-spacing: 0;
  text-shadow: none;
  -webkit-text-stroke: 0 transparent;
}

#intro-2 .intro-line,
#intro-3 .intro-line {
  /* same fix as .intro-line above: floor 17→22 so the longer lines 2/3 don't
     shrink to the floor across the iPad / small-laptop band */
  font-size: clamp(24px, 1.7vw, 27px);
}

.intro-line::after {
  display: inline-block;
  width: 0.48em;
  color: #000000;
  content: "|";
  opacity: 0;
  transform: translateY(-0.03em);
}

.intro-line.is-typing::after {
  animation: intro-caret-blink 780ms steps(1) infinite;
  opacity: 1;
}

.intro-line.is-done::after {
  opacity: 0;
}

@keyframes intro-caret-blink {
  50% {
    opacity: 0;
  }
}

.home-gallery-screen {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  min-height: 100svh;
  padding: 0 clamp(34px, 7vw, 140px);
  background: #ffffff;
  color: #000000;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity 500ms ease,
    filter 620ms ease,
    transform 620ms ease;
}

body.home-gallery-active .home-gallery-screen {
  opacity: 1;
  pointer-events: auto;
}

body.home-opening-complete .home-gallery-screen {
  display: none;
  pointer-events: none;
}

.home-gallery-stage {
  position: relative;
  display: grid;
  place-items: center;
  width: min(1540px, 100%);
  height: 100svh;
  min-height: 620px;
  isolation: isolate;
}

.home-photo {
  position: absolute;
  overflow: hidden;
  border-radius: 8px;
  background:
    repeating-linear-gradient(90deg, #dddddd 0 22px, #eeeeee 22px 44px),
    #e8e8e8;
  opacity: 0.86;
  transform: translate3d(0, 0, 0) scale(1);
  transform-origin: center;
  transition:
    opacity 160ms linear,
    filter 160ms linear,
    transform 160ms linear;
  will-change: opacity, filter, transform;
}

body[data-theme="dark"] .home-photo {
  background:
    repeating-linear-gradient(90deg, #202020 0 22px, #151515 22px 44px),
    #111111;
}

body[data-theme="dark"] .home-photo::after {
  color: rgba(255, 255, 255, 0.44);
}

.home-photo-tall {
  width: clamp(118px, 10vw, 170px);
  height: clamp(158px, 13.4vw, 226px);
}

.home-photo-wide {
  width: clamp(170px, 16vw, 260px);
  height: clamp(90px, 8vw, 132px);
}

.home-photo-small {
  width: clamp(96px, 9vw, 146px);
  height: clamp(64px, 6vw, 98px);
}

.home-photo-main {
  bottom: 7%;
  left: 50%;
  z-index: 3;
  width: clamp(250px, 25vw, 430px);
  height: clamp(132px, 13.5vw, 218px);
  opacity: 1;
  /* Centering lives in the `translate` PROPERTY, never in `transform`:
     the reveal scrub writes inline `transform` on this element, and the
     data-page/tier rules center via `translate: -50% 0` — a transform
     translateX(-50%) here would COMPOSE with that translate and shift the
     video left by half its width (the 2026-07-15 regression: a now-removed
     duplicate rule had been silently neutralizing exactly that). */
  translate: -50% 0;
}

.home-photo-a {
  top: 9%;
  left: 15%;
}

.home-photo-b {
  top: 12%;
  right: 13%;
}

.home-photo-c {
  top: 44%;
  right: 7%;
}

.home-photo-d {
  top: 45%;
  left: 8%;
}

.home-photo-e {
  top: 69%;
  left: 17%;
}

.home-orange-wrap {
  z-index: 5;
  display: grid;
  justify-items: center;
  gap: 0px;
  transform: translateY(-2px);
  transition:
    opacity 160ms linear,
    filter 160ms linear,
    transform 160ms linear;
  will-change: opacity, filter, transform;
}

.home-gallery-screen .orange-stage {
  z-index: 5;
  min-height: 0;
  width: min(30vw, 360px);
  opacity: 1;
  transform: translateY(-4px) scale(0.72);
  transition:
    opacity 160ms linear,
    filter 160ms linear,
    transform 160ms linear;
}

.home-vibe-note {
  margin: 0;
  color: #000000;
  font-family: var(--hand);
  font-size: clamp(20px, 1.3vw, 25px);
  font-weight: 300;
  line-height: 1.1;
  text-align: center;
}

/* ---- hero (2026-07 redesign): handwritten title, playable orange, three
   draggable polaroids, a couple of ink doodles. Everything inherits the
   gallery screen's ink colour, so dark mode flips it for free. ---- */
.hh-title {
  position: absolute;
  top: clamp(108px, 20vh, 208px);
  left: 50%;
  translate: -50% 0;
  z-index: 4;
  margin: 0;
  color: inherit;
  font-family: var(--hand);
  font-size: clamp(10px, 2.6vw, 31px);
  font-weight: 300;
  letter-spacing: 0.01em;
  line-height: 1;
  white-space: nowrap;
}

/* a loose polaroid: paper frame, ink border, pin at the top. The resting spot
   comes from left/top + rotate(var(--tilt)); dragging writes the independent
   `translate`, so position and tilt never fight. */
.hh-photo {
  position: absolute;
  z-index: 6;
  width: var(--hh-photo-width, clamp(122px, 13.5vw, 198px));
  margin: 0;
  /* the bottom lip is only a touch deeper than the top — not the classic
     fat polaroid band */
  padding: 7px 7px 10px;
  border: 1.5px solid currentColor;
  border-radius: 0px;
  background: #ffffff;
  /* --mag-tilt / --mag-scale are the fridge-magnet drag channels, written per
     frame by the spring driver in script.js: a touch of velocity tilt while
     carried, a lift while held, a tiny press past 1 as it re-attaches. The
     resting --tilt is untouched. */
  transform: rotate(calc(var(--tilt, 0deg) + var(--mag-tilt, 0deg))) scale(var(--mag-scale, 1));
  cursor: grab;
  /* only the shadow eases via CSS — position/tilt/scale are spring-driven */
  transition: box-shadow 200ms ease;
  touch-action: none; /* a finger dragging a photo must not scroll/page */
  user-select: none;
}

.hh-photo::before {
  /* the drawn pin holding it to the page — it dips past the frame's top lip
     and presses onto the photo itself, like it's actually pinned through */
  content: "";
  position: absolute;
  top: -7px;
  left: 50%;
  translate: -50% 0;
  z-index: 2;
  width: 12.5px;
  height: 12.5px;
  border: 1.5px solid currentColor;
  border-radius: 50%;
  background: #ffffff;
}

.hh-photo img,
.hh-photo video {
  display: block;
  width: 100%;
  aspect-ratio: var(--hh-photo-aspect, 1 / 1.04);
  object-fit: cover;
  /* Moves only the image inside its frame; the frame and pin stay put. */
  position: relative;
  top: var(--hh-photo-image-y, 0px);
  pointer-events: none;
  -webkit-user-drag: none;
}

.hh-photo.is-dragging {
  cursor: grabbing;
  /* peeled off the fridge: floats a little above the page */
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.16), 0 3px 7px rgba(0, 0, 0, 0.1);
}

/* Per-photo controls: change only the variables for the photo you want to tune. */
.hh-photo-1 {
  --hh-photo-width: clamp(132px, 14.5vw, 212px);
  --hh-photo-width-mobile: clamp(92px, 24vw, 116px);
  --hh-photo-aspect: 4 / 2.9;
  --hh-photo-image-y: -2px;
  left: 7%;
  top: 30%;
}

.hh-photo-2 {
  --hh-photo-width: clamp(102px, 11vw, 178px);
  --hh-photo-width-mobile: clamp(78px, 21vw, 100px);
  --hh-photo-aspect: 1 / 1.14;
  --hh-photo-image-y: -2px;
  right: 6.5%;
  top: 13%;
}

.hh-photo-3 {
  --hh-photo-width: clamp(115px, 13vw, 190px);
  --hh-photo-width-mobile: clamp(80px, 21vw, 104px);
  --hh-photo-aspect: 1 / 1.2;
  --hh-photo-image-y: -2px;
  right: 9%;
  top: 57%;
}

.hh-annot-text {
  position: relative;
  left: -2px; /* 往左：负数更大 */
  top: 8px;    /* 往下：正数更大 */
}

/* "play with me!" + a curly arrow aimed at the dome */
.hh-annot {
  position: absolute;
  z-index: 4;
  left: calc(50% + clamp(125px, 14vw, 205px));
  top: 39%;
  display: grid;
  justify-items: start;
  color: inherit;
  font-family: var(--hand);
  font-size: clamp(15px, 1.25vw, 19px);
  font-weight: 300;
  pointer-events: none;
}

/* Reading order is text → arrow → orange: the label sits up-right, then the
   spiral arrow drops BELOW and to the LEFT (toward the orange) with a clear
   gap, so it reads as a caption pointing at the creature — never touching the
   text. margin-top opens the gap; the negative margin-left carries the arrow
   out toward the orange. */
.hh-annot-arrow {
  width: clamp(34px, 3.7vw, 52px);
  margin-top: -7px;
  margin-left: clamp(-64px, -4.6vw, -42px);
}

.hh-flowers {
  position: absolute;
  z-index: 3;
  left: 9%;
  bottom: 9%;
  width: clamp(60px, 7vw, 96px);
  color: inherit;
  pointer-events: none;
}

body[data-theme="dark"][data-page="home"] .hh-photo,
body[data-theme="dark"][data-page="home"] .hh-photo::before {
  background: #121212;
}

/* ---- hero entrance: the screen fades in (rule above), then its furniture
   arrives in soft steps — title, orange, photos, then the margin notes.
   Photos enter via a ONE-SHOT keyframe, not a translate transition: dragging
   writes inline `translate`, and any lingering transition on it would make
   every drag frame lag behind the pointer. ---- */
body[data-page="home"]:not(.home-gallery-active) .hh-title,
body[data-page="home"]:not(.home-gallery-active) .home-gallery-screen .home-orange-wrap,
body[data-page="home"]:not(.home-gallery-active) .hh-annot,
body[data-page="home"]:not(.home-gallery-active) .hh-flowers {
  opacity: 0;
}

body[data-page="home"]:not(.home-gallery-active) .hh-title {
  transform: translateY(14px);
}

body[data-page="home"]:not(.home-gallery-active) .home-gallery-screen .home-orange-wrap,
body[data-page="home"]:not(.home-gallery-active) .hh-annot,
body[data-page="home"]:not(.home-gallery-active) .hh-flowers {
  translate: 0 14px;
}

.hh-title {
  transition: opacity 520ms ease 60ms, transform 600ms cubic-bezier(0.22, 1, 0.36, 1) 60ms;
}

.home-gallery-screen .home-orange-wrap {
  transition:
    opacity 540ms ease 140ms,
    translate 620ms cubic-bezier(0.22, 1, 0.36, 1) 140ms,
    filter 160ms linear,
    transform 160ms linear;
}

.hh-annot {
  transition: opacity 520ms ease 480ms, translate 600ms cubic-bezier(0.22, 1, 0.36, 1) 480ms;
}

.hh-flowers {
  transition: opacity 520ms ease 540ms, translate 600ms cubic-bezier(0.22, 1, 0.36, 1) 540ms;
}

body.home-gallery-active .hh-photo {
  animation: hh-photo-in 620ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

body.home-gallery-active .hh-photo-1 { animation-delay: 260ms; }
body.home-gallery-active .hh-photo-2 { animation-delay: 340ms; }
body.home-gallery-active .hh-photo-3 { animation-delay: 420ms; }

@keyframes hh-photo-in {
  from { opacity: 0; transform: translateY(14px) rotate(var(--tilt, 0deg)); }
  to { opacity: 1; transform: rotate(var(--tilt, 0deg)); }
}

/* ---- handoff exit: the hero leaves like scrolled content — it glides UP and
   fades while Projects slides in from below (both on the same ease-out ride
   the JS drives). No transition while the class is on: the scrub must track
   the wheel 1:1. When the class drops (reverse, or the release swap) the
   stage eases back through the base transition. ---- */
.home-gallery-stage {
  transition: opacity 220ms ease, translate 220ms ease;
}

body.home-work-handoff .home-gallery-stage {
  transition: none;
  translate: 0 var(--home-hero-exit-y, 0px);
  opacity: var(--home-hero-exit-o, 1);
}

@media (prefers-reduced-motion: reduce) {
  body[data-page="home"]:not(.home-gallery-active) .hh-title,
  body[data-page="home"]:not(.home-gallery-active) .home-gallery-screen .home-orange-wrap,
  body[data-page="home"]:not(.home-gallery-active) .hh-annot,
  body[data-page="home"]:not(.home-gallery-active) .hh-flowers {
    opacity: 1;
    transform: none;
    translate: none;
  }

  body.home-gallery-active .hh-photo {
    animation: none;
  }

  .hh-title,
  .home-gallery-screen .home-orange-wrap,
  .hh-annot,
  .hh-flowers,
  .home-gallery-stage {
    transition: none;
  }
}

@media (max-width: 660px) {
  .hh-title {
    top: clamp(64px, 12vh, 110px);
    font-size: clamp(26px, 7.6vw, 34px);
  }

  .hh-photo {
    width: var(--hh-photo-width-mobile, var(--hh-photo-width));
    padding: 5px 5px 7px;
  }

  .hh-photo::before {
    top: -4px; /* thinner lip here — keep the pin's press on the photo ~2px */
  }

  .hh-photo-1 { left: 3%; top: 20%; }
  .hh-photo-2 { right: 3%; top: 14%; }
  .hh-photo-3 { right: 4%; top: 66%; }

  .hh-annot {
    left: auto;
    right: 7%;
    top: 41%;
    font-size: 14px;
  }

  .hh-flowers {
    left: 7%;
    bottom: 5%;
    width: 54px;
  }
}

body.home-gallery-revealing .site-header {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-10px);
  visibility: hidden;
}

body[data-theme="dark"] .home-gallery-screen {
  background: #000000;
  color: #ffffff;
}

.home-hero {
  display: grid;
  grid-template-columns: minmax(0, 1.38fr) minmax(320px, 0.62fr);
  align-items: center;
  gap: clamp(28px, 4vw, 56px);
  width: min(100% - 112px, 1280px);
  min-height: 560px;
  margin: 0 auto;
  padding: clamp(24px, 2.4vw, 38px) 0 28px;
}

.role-line,
.student-line,
.eyebrow {
  font-family: var(--hand);
  font-size: clamp(18px, 1.45vw, 23px);
  font-weight: 400;
}

.role-line {
  margin: 0 0 54px;
}

.role-line .nowrap {
  white-space: nowrap;
}

.student-line {
  margin: 52px 0 42px;
}

.stamp-title {
  display: grid;
  gap: 42px;
  margin: 0;
  font-family: var(--stamp);
  font-size: clamp(28px, 3vw, 38px);
  font-weight: 400;
  line-height: 1.32;
  text-transform: uppercase;
  letter-spacing: 0;
}

.stamp-title span {
  display: block;
  color: var(--ink);
}

.stamp-title mark {
  display: inline-block;
  margin-left: 12px;
  padding: 0 12px 4px;
  color: var(--purple);
  background: color-mix(in srgb, var(--purple) 14%, transparent);
  transform: rotate(-1deg);
}

.stamp-title em {
  color: var(--orange-deep);
  font-style: normal;
  white-space: nowrap;
}

.button-arrow {
  position: relative;
  display: inline-block;
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
  border-radius: 1px 3px 1px 0;
  background:
    linear-gradient(
      135deg,
      transparent calc(50% - 1px),
      currentColor calc(50% - 1px) calc(50% + 1px),
      transparent calc(50% + 1px)
    );
  transform: translateY(-1px);
}

.orange-stage {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 360px;
  isolation: isolate;
  overflow: visible;
  /* One number scales the whole eye rig with the dome. script.js measures the
     stage's layout width against the 360px desktop reference and writes
     --eye-scale; the pupils' size and spacing follow it here, their travel range
     and resting offset follow it in JS. So the eyes stay the same fraction of the
     orange on a 1440px desktop and on a 390px phone. The fallback of 1 keeps the
     desktop geometry if the script never runs. */
  --eye-scale: 1;
  --eye-size: calc(22px * var(--eye-scale));
  --eye-gap: calc(82px * var(--eye-scale));
  --eye-top: 50%;
  --eye-left: 41%;
}

.orange-body {
  position: relative;
  width: min(100%, 430px);
  mix-blend-mode: multiply;
  z-index: 1;
}

body[data-theme="dark"] .orange-body {
  mix-blend-mode: normal;
}

.orange-eye-group {
  position: absolute;
  z-index: 2;
  left: var(--eye-left);
  top: var(--eye-top);
  display: flex;
  gap: var(--eye-gap);
  pointer-events: none;
  transform: translateZ(0);
  backface-visibility: hidden;
}

.orange-eye {
  --eye-translate: translate3d(0, 0, 0);
  display: block;
  width: var(--eye-size);
  height: var(--eye-size);
  border-radius: 50%;
  background: #111111;
  animation: orange-natural-blink 6.4s ease-in-out infinite;
  transform: var(--eye-translate) scaleY(1);
  transform-origin: 50% 50%;
  will-change: transform, border-radius;
}

.orange-eye-right {
  animation-delay: 0.04s;
}

.orange-stage.has-random-blink .orange-eye:not(.is-blinking) {
  animation: none;
}

.orange-stage.has-random-blink .orange-eye.is-blinking,
.orange-eye.is-blinking {
  animation: orange-blink-once 230ms cubic-bezier(0.42, 0, 0.28, 1) both;
}

@keyframes orange-natural-blink {
  0%,
  92%,
  96%,
  100% {
    border-radius: 50%;
    transform: var(--eye-translate) scaleY(1);
  }

  93.1% {
    border-radius: 999px;
    transform: var(--eye-translate) scaleY(0.08) scaleX(1.12);
  }

  94% {
    border-radius: 999px;
    transform: var(--eye-translate) scaleY(0.035) scaleX(1.16);
  }

  95.1% {
    border-radius: 50%;
    transform: var(--eye-translate) scaleY(0.72) scaleX(0.98);
  }
}

@keyframes orange-blink-once {
  0%,
  100% {
    border-radius: 50%;
    transform: var(--eye-translate) scaleY(1);
  }

  30% {
    border-radius: 999px;
    transform: var(--eye-translate) scaleY(0.18) scaleX(1.08);
  }

  46%,
  58% {
    border-radius: 999px;
    transform: var(--eye-translate) scaleY(0.035) scaleX(1.16);
  }

  78% {
    border-radius: 50%;
    transform: var(--eye-translate) scaleY(0.7) scaleX(0.98);
  }
}

/* ---- 面饼 interactions: a shy, soft little creature ------------------------
   The cursor is an invisible finger; the dome notices it, leans into it and
   deforms like dough when touched. EVERY deformation lives on .orange-dough
   (an inner wrapper stretched to the stage's exact box), because the stage's
   own transform belongs to the opening reveal scrub — the two can never fight.
   The dough's transform-origin sits low so squashes read as "the base stays
   put, the top gives way": a soft bun, not a scaling button.
   JS (script.js) toggles the classes; nothing here loops forever. */
.orange-stage {
  cursor: pointer;
  touch-action: manipulation; /* taps shouldn't double-tap-zoom the dome */
  -webkit-touch-callout: none; /* long-press = squish, not the iOS image sheet */
}

.orange-body {
  user-select: none;
  -webkit-user-drag: none;
}

.orange-dough {
  position: relative;
  display: grid;
  place-items: center;
  align-self: stretch;
  justify-self: stretch;
  transform-origin: 50% 86%;
  /* the ever-present way home: any released state (pet lean, held squish,
     rapid-click flatten) springs softly back to rest through this */
  transition: transform 300ms cubic-bezier(0.34, 1.45, 0.5, 1);
  will-change: transform;
}

/* hover — a finger pressing into soft dough: brief squash (top gives, base
   holds), two soft wobbles, done in ~560ms. Never loops. */
.orange-dough.is-pressing {
  animation: dough-press 560ms cubic-bezier(0.33, 1.2, 0.55, 1) both;
}

@keyframes dough-press {
  0% { transform: none; }
  20% { transform: scaleX(1.04) scaleY(0.96); }
  44% { transform: scaleX(1.03) scaleY(0.97) rotate(-1.5deg); }
  68% { transform: scaleX(1.015) scaleY(0.99) rotate(1.1deg); }
  86% { transform: rotate(-0.4deg); }
  100% { transform: none; }
}

/* petting (rubbing the top): the dome leans a touch toward the hand and dips —
   JS steers --pet-lean/--pet-dip from the pointer, the short transition keeps
   the follow doughy rather than rigid. Class off => the base spring above
   carries it back to rest in ~300ms. */
.orange-dough.is-petted {
  transition: transform 170ms ease-out;
  transform: rotate(var(--pet-lean, 0deg)) scaleY(var(--pet-dip, 1));
}

/* happy-but-bashful ∩ eyes while petted: slower than a blink on purpose —
   they ease shut, not snap (random blinks pause in JS meanwhile). Keeping the
   JS-written --eye-translate carries the gaze into the squint smoothly. */
.orange-stage.is-petted .orange-eye {
  border-radius: 999px 999px 10px 10px;
  transform: var(--eye-translate) translateY(-16%) scaleY(0.55);
  transition:
    transform 380ms ease,
    border-radius 380ms ease;
}

/* click reaction A — startled: eyes pop wide for a beat while the dough
   compresses and hops 12px, landing with a small wobble */
.orange-stage.is-startled .orange-eye {
  transform: var(--eye-translate) scale(1.22);
  transition: transform 90ms ease-out;
}

.orange-dough.is-hop {
  animation: dough-hop 640ms cubic-bezier(0.3, 1.15, 0.5, 1) both;
}

@keyframes dough-hop {
  0% { transform: none; }
  18% { transform: scaleX(1.05) scaleY(0.9); }
  46% { transform: translateY(-12px) scaleX(0.985) scaleY(1.03); }
  68% { transform: translateY(0) scaleX(1.03) scaleY(0.97) rotate(1.1deg); }
  85% { transform: rotate(-0.7deg); }
  100% { transform: none; }
}

/* click reaction B — shy: pupils sneak toward each other while the dough does
   a small embarrassed twist (the note briefly says `hehe :)` via JS) */
.orange-stage.is-shy .orange-eye-left {
  transform: var(--eye-translate) translateX(calc(5px * var(--eye-scale, 1)));
  transition: transform 260ms ease;
}

.orange-stage.is-shy .orange-eye-right {
  transform: var(--eye-translate) translateX(calc(-5px * var(--eye-scale, 1)));
  transition: transform 260ms ease;
}

.orange-dough.is-twist {
  animation: dough-twist 900ms ease-in-out both;
}

@keyframes dough-twist {
  0% { transform: none; }
  26% { transform: rotate(-2.6deg); }
  56% { transform: rotate(2deg); }
  80% { transform: rotate(-0.8deg); }
  100% { transform: none; }
}

/* click reaction C — playing dead: eyes flatten to two lines while the dough
   slowly deflates... then it snaps back like nothing happened (is-pop) */
.orange-stage.is-dead .orange-eye {
  border-radius: 999px;
  transform: var(--eye-translate) scaleY(0.12);
  transition: transform 140ms ease-in, border-radius 140ms ease-in;
}

.orange-dough.is-flat {
  transform: scaleX(1.04) scaleY(0.9);
  transition: transform 620ms ease-out;
}

.orange-dough.is-pop {
  animation: dough-pop 280ms cubic-bezier(0.3, 1.6, 0.5, 1) both;
}

@keyframes dough-pop {
  0% { transform: scaleX(1.04) scaleY(0.9); }
  55% { transform: scaleX(0.98) scaleY(1.04); }
  100% { transform: none; }
}

/* long-press (touch): the dough slowly gives under the finger; release lets
   the base spring (or is-pop) carry it back */
.orange-dough.is-held {
  transform: scaleX(1.06) scaleY(0.86);
  transition: transform 900ms cubic-bezier(0.25, 0.6, 0.35, 1);
}

/* a polaroid parked on top ("Hey! You're squishing me!"): a quick protest
   squash with two tiny wobbles, then it HOLDS the flattened pose — the
   keyframe fills `both`, so the pose lasts exactly as long as the class.
   Freed, the JS swaps this for .is-pop and it bounces straight back. */
.orange-dough.is-photo-squish {
  animation: dough-photo-squish 600ms cubic-bezier(0.3, 1.1, 0.5, 1) both;
}

@keyframes dough-photo-squish {
  0% { transform: none; }
  30% { transform: scaleX(1.09) scaleY(0.82); }
  52% { transform: scaleX(1.05) scaleY(0.88) rotate(-1.2deg); }
  74% { transform: scaleX(1.08) scaleY(0.84) rotate(0.8deg); }
  100% { transform: scaleX(1.07) scaleY(0.85); }
}

/* the eyes squeeze into a put-upon squint under the weight */
.orange-stage.is-photo-squish .orange-eye {
  transform: var(--eye-translate) translateY(12%) scaleY(0.35);
  transition: transform 220ms ease;
}

/* double-poke — "hey! :)": squashed flat for a beat, then it POPS 16px into
   the air with a little mid-air tilt, lands, and jiggles softly twice.
   User-specified rhythm: 按扁100ms → 弹起220ms → 落地180ms → 软晃300ms (800ms). */
.orange-dough.is-pounce {
  animation: dough-pounce 800ms cubic-bezier(0.3, 0.9, 0.4, 1) both;
}

@keyframes dough-pounce {
  0% { transform: none; }
  12.5% { transform: scaleX(1.06) scaleY(0.88); }                              /* 按扁 */
  40% { transform: translateY(-16px) rotate(-2deg) scaleX(0.98) scaleY(1.04); } /* 弹起+空中歪 */
  62.5% { transform: translateY(0) scaleX(1.05) scaleY(0.95); }                 /* 落地 */
  76% { transform: rotate(1.6deg); }                                            /* 软晃 */
  89% { transform: rotate(-1.1deg); }
  100% { transform: none; }
}

/* third rapid poke — it leaps properly awake ("okay okay, I'm awake :)");
   the flatten intro is baked in so it works from any state */
.orange-dough.is-spring {
  animation: dough-spring 780ms cubic-bezier(0.3, 1.5, 0.5, 1) both;
}

@keyframes dough-spring {
  0% { transform: none; }
  14% { transform: scaleX(1.08) scaleY(0.8); }
  40% { transform: translateY(-14px) scaleX(0.97) scaleY(1.06); }
  62% { transform: translateY(0) scaleX(1.04) scaleY(0.96); }
  80% { transform: rotate(1deg); }
  100% { transform: none; }
}

/* idle life — an occasional single breath (scheduled sparsely by JS; this is
   deliberately NOT an infinite float, which would read as page decoration) */
.orange-dough.is-breath {
  animation: dough-breath 1700ms ease-in-out both;
}

@keyframes dough-breath {
  0%, 100% { transform: none; }
  50% { transform: translateY(-2.5px) scaleY(1.012); }
}

/* the caption gets gently knocked about by the dome's bigger moves — rotate is
   the independent property, so it composes with any base transform */
.home-vibe-note.is-nudged {
  animation: note-nudge 560ms ease both;
}

@keyframes note-nudge {
  0% { rotate: 0deg; translate: 0 0; }
  35% { rotate: -2.4deg; translate: 0 1.5px; }
  100% { rotate: 0deg; translate: 0 0; }
}

/* a single bashful heart — only after the petting has gone on a while */
.orange-heart {
  position: absolute;
  z-index: 3;
  top: 12%;
  color: var(--orange);
  font-size: calc(26px * var(--eye-scale, 1));
  line-height: 1;
  pointer-events: none;
  animation: orange-heart-float 980ms ease-out both;
}

@keyframes orange-heart-float {
  0% { opacity: 0; translate: 0 6px; scale: 0.5; }
  22% { opacity: 1; scale: 1; }
  100% { opacity: 0; translate: 0 calc(-46px * var(--eye-scale, 1)); scale: 0.92; }
}

@media (prefers-reduced-motion: reduce) {
  .orange-dough,
  .orange-dough.is-pressing,
  .orange-dough.is-petted,
  .orange-dough.is-hop,
  .orange-dough.is-twist,
  .orange-dough.is-flat,
  .orange-dough.is-pop,
  .orange-dough.is-held,
  .orange-dough.is-pounce,
  .orange-dough.is-spring,
  .orange-dough.is-breath,
  .orange-dough.is-photo-squish,
  .home-vibe-note.is-nudged {
    animation: none;
    transition: none;
    transform: none;
  }

  .orange-heart {
    display: none;
  }
}

.work-section,
.story-strip,
.about-teaser,
.site-footer,
.page-shell {
  width: min(100% - 112px, 1280px);
  margin: 0 auto;
}

.work-section {
  min-height: min(980px, 100vh);
  padding: clamp(84px, 7.5vw, 116px) 0 96px;
  border-top: 0;
}

.section-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 30px;
}

.section-heading h2,
.work-section h2,
.about-teaser h2,
.site-footer h2 {
  margin: 0;
  font-family: var(--stamp);
  font-size: clamp(34px, 4.8vw, 58px);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: 0;
  text-transform: uppercase;
}

.eyebrow {
  display: inline-flex;
  margin: 0;
  color: var(--orange-deep);
}

.work-section .eyebrow {
  color: var(--ink);
}

.work-section {
  width: min(100% - 112px, 1280px);
  background: #ffffff;
}

body[data-theme="dark"] .work-section {
  background: #000000;
}

/* home main paints the full-width backdrop of the pinned screens (opaque black
   in dark mode). On a theme switch it must fade COLOR→COLOR on the same 240ms
   clock as the sections — fading the dark bg to TRANSPARENT instead composites
   darker than the sections mid-fade (an alpha fade over the still-fading body),
   which is exactly the left/right side flash. */
body[data-page="home"] main {
  background: var(--paper);
  transition: background-color 240ms ease;
}

body[data-theme="dark"][data-page="home"] main,
body[data-theme="dark"][data-page="home"] .work-section {
  background: #000000;
}

.work-section .section-heading {
  justify-content: flex-start;
  margin-bottom: clamp(48px, 5.2vw, 84px);
}

.work-section .work-heading-title {
  color: var(--ink);
  font-family: var(--hand);
  font-size: clamp(18px, 1.45vw, 23px);
  font-weight: 400;
  line-height: 1.1;
  text-transform: none;
}

.work-board {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: clamp(84px, 9vw, 136px) clamp(58px, 6.6vw, 104px);
  width: min(100%, 1000px);
  margin: 0 auto;
  transform: translateX(clamp(22px, 2.1vw, 38px));
}

body[data-page="home"] .work-board {
  transform: none;
}

.work-row {
  display: contents;
}

.work-card {
  position: relative;
  display: grid;
  gap: clamp(14px, 1.35vw, 20px);
  overflow: visible;
  justify-self: start;
  width: min(100%, 400px);
  min-height: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  opacity: 0;
  transform: translateY(20px);
  transition:
    opacity 520ms ease,
    transform 220ms ease,
    color 220ms ease;
}

.work-card.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.work-card.is-visible:hover {
  transform: translateY(-2px);
}

.work-card:hover .work-media-full {
  filter: saturate(1.02) contrast(1.02);
}

.work-card-feature {
  min-height: 0;
}

.work-card:nth-child(even) {
  justify-self: end;
  margin-top: clamp(62px, 7vw, 104px);
}

.work-media {
  position: relative;
  display: grid;
  place-items: center;
  background: var(--paper-deep);
}

body[data-theme="dark"] .work-media {
  background: var(--paper-deep);
}

.work-media-full {
  --project-arrow-left: clamp(16px, 1.9vw, 28px);
  --project-arrow-bottom: clamp(16px, 1.9vw, 28px);
  --project-arrow-circle: clamp(30px, 3.2vw, 44px);
  --project-arrow-size: clamp(5px, 0.55vw, 8px);
  aspect-ratio: 16 / 10;
  height: auto;
  overflow: hidden;
  border-radius: 22px;
  background:
    repeating-linear-gradient(
      135deg,
      rgba(255, 255, 255, 0.55) 0 2px,
      transparent 2px 14px
    ),
    #e8e8e8;
  transition:
    filter 220ms ease,
    transform 220ms ease;
}

.work-card-feature .work-media-full {
  height: auto;
}

.work-media-full::after {
  content: "";
  position: absolute;
  z-index: 3;
  left: calc(var(--project-arrow-left) + (var(--project-arrow-circle) - var(--project-arrow-size)) / 2);
  bottom: calc(var(--project-arrow-bottom) + (var(--project-arrow-circle) - var(--project-arrow-size)) / 2);
  width: var(--project-arrow-size);
  height: var(--project-arrow-size);
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
  border-radius: 1px 3px 1px 0;
  background:
    linear-gradient(
      135deg,
      transparent calc(50% - 1px),
      currentColor calc(50% - 1px) calc(50% + 1px),
      transparent calc(50% + 1px)
    );
  color: rgba(255, 255, 255, 0.68);
  transform: translate3d(0, 0, 0);
  transition:
    color 220ms ease,
    transform 220ms ease;
}

.work-media-full::before {
  content: "";
  position: absolute;
  z-index: 2;
  left: var(--project-arrow-left);
  bottom: var(--project-arrow-bottom);
  width: var(--project-arrow-circle);
  height: var(--project-arrow-circle);
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.18);
  opacity: 0.72;
  transition:
    background 220ms ease,
    opacity 220ms ease,
    transform 220ms ease;
}

.work-card:hover .work-media-full::before {
  background: #ffffff;
  opacity: 1;
  transform: scale(1.02);
}

.work-card:hover .work-media-full::after {
  color: #050505;
  transform: translate3d(1px, -1px, 0);
}

.work-media-full picture,
.work-media-full img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
}

.work-media-full picture {
  display: block;
}

.work-media-full img {
  object-fit: cover;
  transform: scale(1);
  transition: transform 360ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.work-card:hover .work-media-full img {
  transform: scale(1.035);
}

.wentong-media {
  background: #f6f1ea;
}

body[data-theme="dark"] .wentong-media {
  background: #f6f1ea;
}

.wentong-media img {
  width: min(25%, 172px);
  height: auto;
  object-fit: contain;
  mix-blend-mode: normal;
}

.voderrn-media {
  background: #d8dde5;
}

body[data-theme="dark"] .voderrn-media {
  background: #d8dde5;
}

.voderrn-media img {
  object-fit: cover;
  object-position: center;
  transform: translateY(34px) scale(1);
}

.work-card:hover .voderrn-media img {
  transform: translateY(34px) scale(1.035);
}

.gown-media {
  background: #fff3e4;
}

.gown-media img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center bottom;
  border-radius: inherit;
}

.work-card:hover .gown-media img {
  transform: scale(1.02);
}

.placeholder-media {
  background:
    repeating-linear-gradient(90deg, rgba(220, 220, 220, 0.9) 0 24px, rgba(235, 235, 235, 0.9) 24px 48px),
    #eeeeee;
}

.placeholder-media span {
  color: #8e8e8e;
  font-family: var(--hand);
  font-size: clamp(20px, 1.8vw, 28px);
}

.placeholder-media span::before {
  content: "image soon";
}

body[data-theme="dark"] .placeholder-media {
  background:
    repeating-linear-gradient(90deg, rgba(24, 24, 24, 0.96) 0 24px, rgba(38, 38, 38, 0.96) 24px 48px),
    #111111;
}

body[data-theme="dark"] .placeholder-media span {
  color: rgba(255, 255, 255, 0.52);
}

.work-card-placeholder .work-media-full::before,
.work-card-placeholder .work-media-full::after {
  content: none;
}

.work-card-copy {
  position: relative;
  z-index: 1;
  align-self: start;
  padding: 0;
  display: grid;
  gap: 8px;
  justify-items: start;
  transition: padding 220ms ease;
}

.work-card-copy span,
.project-tag,
.play-card span {
  display: inline-flex;
  margin-bottom: 0;
  color: var(--orange-deep);
  font-family: var(--hand);
  font-size: clamp(15px, 1.2vw, 18px);
}

/* 标准字号 */
.work-card-copy h3 {
  margin: 0;
  font-family: var(--stamp);
  font-size: clamp(12px, 1.25vw, 17px);
  font-weight: 400;
  line-height: 1.08;
  letter-spacing: 0;
  text-transform: uppercase;
}

.work-card-wide,
.work-card-small {
  min-height: 0;
}

.story-strip {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 22px;
  padding: 0 0 88px;
}

.story-strip article,
.about-teaser,
.project-card,
.value-list article,
.timeline article,
.resume-links,
.play-card {
  border: 3px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: 6px 6px 0 var(--shadow);
}

.story-strip article {
  padding: 28px;
}

.story-strip span,
.timeline span {
  color: var(--orange-deep);
  font-family: var(--hand);
  font-size: 20px;
}

.story-strip h2,
.value-list h2,
.timeline h2,
.project-body h2 {
  margin: 10px 0 12px;
  font-family: var(--stamp);
  font-size: 23px;
  font-weight: 400;
  line-height: 1.1;
  text-transform: uppercase;
}

.story-strip p,
.about-teaser p,
.project-body p,
.value-list p,
.timeline p {
  margin: 0;
  color: var(--muted);
  font-size: 18px;
  line-height: 1.35;
}

.about-teaser {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 28px;
  padding: clamp(28px, 4vw, 46px);
  margin-bottom: 88px;
}

.about-teaser p {
  max-width: 760px;
  margin-top: 18px;
}

.site-footer {
  --footer-divider-offset: 0px;
  --footer-inline-pad: max(56px, calc((100vw - 1280px) / 2));
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: nowrap;
  gap: clamp(24px, 5vw, 72px);
  width: 100%;
  max-width: none;
  min-height: 118px;
  margin: 0;
  padding: 22px var(--footer-inline-pad) clamp(26px, 3vw, 40px);
  background: var(--paper);
  color: var(--ink);
  border-top: 0;
}

body[data-page="home"] .site-footer {
  --footer-inline-pad: max(56px, calc((100vw - 1280px) / 2));
}

.site-footer::before {
  content: "";
  position: absolute;
  top: var(--footer-divider-offset);
  left: var(--footer-inline-pad);
  right: var(--footer-inline-pad);
  height: 1px;
  background: color-mix(in srgb, var(--ink) 12%, transparent);
  pointer-events: none;
}

body[data-theme="dark"] .site-footer {
  background: #000000;
  color: #ffffff;
}

body[data-theme="dark"] .site-footer::before {
  background: #ffffff;
}

.footer-socials {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: clamp(22px, 3vw, 42px);
  font-family: var(--hand);
  font-size: clamp(18px, 1.45vw, 23px);
}

.footer-socials a {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  color: color-mix(in srgb, currentColor 88%, transparent);
  transition:
    color 180ms ease,
    transform 180ms ease;
}

.footer-socials a::after {
  content: "";
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
  border-radius: 1px 3px 1px 0;
  background:
    linear-gradient(
      135deg,
      transparent calc(50% - 1px),
      currentColor calc(50% - 1px) calc(50% + 1px),
      transparent calc(50% + 1px)
    );
  transform: translateY(-1px);
  transition: transform 180ms ease;
}

.footer-socials a:hover,
.footer-socials a:focus-visible {
  color: currentColor;
  transform: translateX(2px);
}

.footer-socials a:hover::after,
.footer-socials a:focus-visible::after {
  transform: translate(2px, -3px);
}

.footer-copy {
  margin: 0;
  color: color-mix(in srgb, currentColor 52%, transparent);
  font-family: var(--hand);
  font-size: clamp(16px, 1.4vw, 21px);
  text-align: right;
  white-space: nowrap;
}

.reveal-section {
  opacity: 0;
  transform: translateY(22px);
  /* background-color rides the same 240ms clock as body, so opaque sections
     (e.g. the home work section) fade with the page on a theme switch instead
     of snapping while the body is still mid-fade (the "side flash") */
  transition:
    opacity 520ms ease,
    transform 520ms ease,
    background-color 240ms ease;
}

.reveal-section.is-visible {
  opacity: 1;
  transform: translateY(0);
}

body[data-page="home"] .home-hero.is-visible .role-line,
body[data-page="home"] .home-hero.is-visible .stamp-title span,
body[data-page="home"] .home-hero.is-visible .student-line,
body[data-page="home"] .home-hero.is-visible .hero-button,
body[data-page="home"] .home-hero.is-visible .orange-stage {
  animation: home-soft-arrive 680ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

body[data-page="home"] .home-hero.is-visible .stamp-title span:nth-child(1) {
  animation-delay: 90ms;
}

body[data-page="home"] .home-hero.is-visible .stamp-title span:nth-child(2) {
  animation-delay: 170ms;
}

body[data-page="home"] .home-hero.is-visible .stamp-title span:nth-child(3) {
  animation-delay: 250ms;
}

body[data-page="home"] .home-hero.is-visible .student-line {
  animation-delay: 320ms;
}

body[data-page="home"] .home-hero.is-visible .hero-button {
  animation-delay: 390ms;
}

body[data-page="home"] .home-hero.is-visible .orange-stage {
  animation-delay: 190ms;
}

@keyframes home-soft-arrive {
  from {
    opacity: 0;
    transform: translateY(18px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.page-shell {
  padding: 34px 0 96px;
}

.page-intro {
  max-width: 880px;
}

.page-intro h1 {
  margin: 12px 0 22px;
  font-family: var(--stamp);
  font-size: clamp(28px, 3.4vw, 42px);
  font-weight: 400;
  line-height: 1.08;
  text-transform: uppercase;
}

.page-intro p {
  max-width: 720px;
  margin: 0 0 28px;
  color: var(--muted);
  font-size: clamp(17px, 1.35vw, 21px);
  line-height: 1.35;
}

.project-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 24px;
  margin-top: 54px;
}

.project-card {
  display: grid;
  overflow: hidden;
}

.project-media {
  display: block;
  height: 255px;
  overflow: hidden;
  border-bottom: 3px solid var(--line);
  background: var(--paper-deep);
}

.project-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 250ms ease;
}

.project-card:hover .project-media img {
  transform: scale(1.04);
}

.project-body {
  padding: 24px;
}

.text-link {
  font-family: var(--hand);
  font-size: 19px;
  text-decoration: underline;
  text-decoration-color: var(--orange);
  text-decoration-thickness: 5px;
  text-underline-offset: -2px;
}

body[data-page="case-study"] {
  background: #ffffff;
  color: #191919;
}

body[data-theme="dark"][data-page="case-study"] {
  background: #000000;
  color: var(--ink);
}

body[data-page="case-study"] strong {
  font-weight: 700;
}

body[data-theme="dark"][data-page="case-study"] .case-back-link,
body[data-theme="dark"][data-page="case-study"] .case-section-nav {
  border-color: rgba(255, 255, 255, 0.92);
  background: color-mix(in srgb, #000000 84%, transparent);
}

body[data-theme="dark"][data-page="case-study"] .case-back-link:hover,
body[data-theme="dark"][data-page="case-study"] .case-back-link:focus-visible {
  background: color-mix(in srgb, #ffffff 14%, #000000);
}

body[data-theme="dark"][data-page="case-study"] .case-back-link span {
  border-color: var(--ink);
}

body[data-theme="dark"][data-page="case-study"] .case-section-nav a {
  color: var(--ink);
}

body[data-theme="dark"][data-page="case-study"] .case-section-nav a::after {
  color: var(--muted);
}


.case-hover-zone {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 80;
  min-height: 150px;
  pointer-events: auto;
  transition: min-height 220ms ease;
}

.case-hover-zone.is-past-intro:not(:hover):not(:focus-within) {
  min-height: 42px;
}

.case-floating-nav {
  position: relative;
  display: grid;
  grid-template-columns: 64px minmax(0, auto) 64px;
  align-items: center;
  gap: clamp(18px, 3vw, 36px);
  width: min(100% - 112px, 1280px);
  margin: 36px auto 0;
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
  transition:
    opacity 220ms ease,
    transform 220ms ease;
}

.case-hover-zone.is-past-intro:not(:hover):not(:focus-within) .case-floating-nav {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-76px);
}

.case-hover-zone.is-past-intro:hover .case-floating-nav,
.case-hover-zone.is-past-intro:focus-within .case-floating-nav {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}

.case-back-link {
  display: grid;
  place-items: center;
  width: 54px;
  height: 54px;
  border: 2px solid rgba(255, 255, 255, 0.82);
  border-radius: 100px;
  background: color-mix(in srgb, var(--paper) 84%, transparent);
  transition:
    transform 180ms ease,
    background 180ms ease;
}

.case-back-link:hover,
.case-back-link:focus-visible {
  background: #ffffff;
  transform: translateY(-2px);
}

.case-back-link span {
  width: 13px;
  height: 13px;
  border-left: 3px solid #222222;
  border-bottom: 3px solid #222222;
  border-radius: 2px;
  transform: translateX(3px) rotate(45deg);
}

.case-section-nav {
  justify-self: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(18px, 2.6vw, 38px);
  width: max-content;
  max-width: 100%;
  padding: 12px 30px;
  border: 2px solid rgba(255, 255, 255, 0.92);
  border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 84%, transparent);
  overflow-x: auto;
  scrollbar-width: none;
  font-family: var(--hand);
  font-size: clamp(18px, 1.45vw, 23px);
  line-height: 1;
  white-space: nowrap;
  transform: translateY(0) scale(1);
  transition:
    background 180ms ease,
    border-color 180ms ease,
    padding 180ms ease,
    transform 180ms ease;
}

.case-section-nav::-webkit-scrollbar {
  display: none;
}

.case-section-nav a {
  --nav-roll-distance: 148%;
  position: relative;
  flex: 0 0 auto;
  display: inline-block;
  overflow: hidden;
  padding: 8px 2px;
  color: var(--ink);
  line-height: 1.12;
  transition:
    color 180ms ease;
}

.case-roll-nav a,
.case-roll-nav a:hover,
.case-roll-nav a:focus-visible {
  border-radius: 0;
  background: transparent !important;
  box-shadow: none;
}

.case-section-nav a .nav-link-label {
  display: block;
  transition: transform 260ms cubic-bezier(0.45, 0.02, 0.12, 1);
}

.case-section-nav a::after {
  content: attr(data-nav-label);
  position: absolute;
  top: 8px;
  left: 2px;
  right: 2px;
  display: block;
  color: var(--muted);
  text-align: center;
  pointer-events: none;
  transform: translateY(var(--nav-roll-distance));
  transition: transform 260ms cubic-bezier(0.45, 0.02, 0.12, 1);
}

.case-section-nav a:hover,
.case-section-nav a:focus-visible {
  color: var(--ink);
}

.case-section-nav a:hover .nav-link-label,
.case-section-nav a:focus-visible .nav-link-label {
  transform: translateY(calc(-1 * var(--nav-roll-distance)));
}

.case-section-nav a:hover::after,
.case-section-nav a:focus-visible::after {
  transform: translateY(0);
}

.case-theme-toggle.pull-chain {
  position: relative;
  top: -36px;
  right: auto;
  z-index: 1;
  justify-self: center;
  align-self: start;
  width: 34px;
  height: 170px;
  min-width: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  opacity: 1;
  transform-origin: top center;
  transition:
    opacity 220ms ease,
    transform 220ms ease;
}

.case-hover-zone.is-past-intro:not(:hover):not(:focus-within) .case-theme-toggle.pull-chain {
  opacity: 0;
  pointer-events: none;
  transform: translateY(-76px);
}

.case-theme-toggle.pull-chain:hover,
.case-theme-toggle.pull-chain:focus-visible {
  background: transparent;
  box-shadow: none;
}

.case-study-page {
  width: min(100% - 160px, 1888px);
  margin: 0 auto;
  padding: clamp(124px, 11vw, 168px) 0 clamp(86px, 10vw, 148px);
  font-family: var(--hand);
}

.case-section {
  scroll-margin-top: 120px;
}

.case-hero-banner {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(360px, 0.86fr);
  gap: clamp(30px, 4.4vw, 70px);
  min-height: clamp(190px, 15vw, 270px);
  overflow: hidden;
  padding: clamp(28px, 3.2vw, 48px);
  border-radius: clamp(28px, 3.8vw, 48px);
  background:
    radial-gradient(#b9b9b9 1px, transparent 1px) 0 0 / 24px 24px,
    #f1f1f1;
}

.case-hero-copy {
  position: relative;
  z-index: 2;
  display: grid;
  align-content: start;
  gap: clamp(38px, 4.2vw, 64px);
  padding-top: clamp(0px, 1vw, 10px);
}

.case-kicker {
  margin: 0;
  color: #777777;
  font-family: var(--hand);
  font-size: clamp(18px, 1.5vw, 23px);
  line-height: 1.1;
}

.case-hero-copy h1,
.case-text-block h2,
.case-placeholder-section h2 {
  margin: 0;
  color: #111111;
  font-family: var(--stamp);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: uppercase;
}

/* Voderrn title */
.case-hero-copy h1 {
  font-size: clamp(20px, 4vw, 40px);
  line-height: 0.98;
}

.case-year {
  position: absolute;
  top: clamp(26px, 3.2vw, 48px);
  right: clamp(30px, 3.6vw, 56px);
  z-index: 3;
  margin: 0;
  color: #8b8b8b;
  font-family: var(--hand);
  font-size: clamp(22px, 2.2vw, 32px);
  line-height: 1;
}

.case-meta-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(22px, 4vw, 58px);
  margin: 0;
}

.case-meta-grid div {
  min-width: 0;
}

.case-meta-grid dt,
.case-meta-grid dd {
  margin: 0;
  color: #222222;
  font-size: clamp(19px, 1.65vw, 27px);
  line-height: 1.15;
}

.case-meta-grid dd {
  white-space: nowrap;
}

.case-meta-grid dt {
  margin-bottom: 12px;
  font-weight: 700;
}

.case-hero-visual {
  position: relative;
  min-height: 190px;
  overflow: hidden;
}

.case-photo-card {
  position: absolute;
  right: clamp(34px, 5vw, 88px);
  bottom: clamp(14px, 1.8vw, 26px);
  width: clamp(280px, 22vw, 390px);
  aspect-ratio: 16 / 10;
  border: 8px solid #ffffff;
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0)),
    repeating-linear-gradient(90deg, #d9d9d9 0 12px, #c9c9c9 12px 24px);
  box-shadow: 0 14px 28px rgba(0, 0, 0, 0.1);
  pointer-events: none;
  transform: translateY(62%);
  transition:
    transform 280ms cubic-bezier(0.45, 0.02, 0.12, 1);
}

.case-photo-card::after {
  content: "image soon";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: rgba(0, 0, 0, 0.34);
  font-size: 18px;
}

.case-hero-banner:hover .case-photo-card,
.case-hero-banner:focus-within .case-photo-card {
  transform: translateY(0);
}

.case-two-column {
  display: grid;
  grid-template-columns: minmax(280px, 0.92fr) minmax(260px, 0.58fr);
  align-items: center;
  gap: clamp(42px, 4.8vw, 82px);
  width: min(calc(100% - clamp(260px, 22vw, 520px)), 1220px);
  margin-inline: auto;
  min-height: clamp(440px, 45vw, 620px);
}

.pain-point-section {
  grid-template-columns: minmax(280px, 0.58fr) minmax(300px, 0.9fr);
}

.case-text-block {
  max-width: 470px;
}

.case-text-block h2,
.case-placeholder-section h2 {
  margin-bottom: 18px;
  font-size: clamp(10px, 3.0vw, 20px);
  line-height: 1.04;
}


/* part title */
.case-text-block p,
.case-placeholder-section p {
  margin: 0;
  color: #6f6f6f;
  font-size: clamp(10px, 1.75vw, 20px);
  line-height: 1.25;
}

.case-text-block strong {
  color: #242424;
}

body[data-theme="dark"][data-page="case-study"] .case-study-page,
body[data-theme="dark"][data-page="case-study"] .case-kicker,
body[data-theme="dark"][data-page="case-study"] .case-hero-copy h1,
body[data-theme="dark"][data-page="case-study"] .case-text-block h2,
body[data-theme="dark"][data-page="case-study"] .case-placeholder-section h2,
body[data-theme="dark"][data-page="case-study"] .visual-style-section h2,
body[data-theme="dark"][data-page="case-study"] .iterations-section h2,
body[data-theme="dark"][data-page="case-study"] .journey-copy h2 {
  color: var(--ink);
}

body[data-theme="dark"][data-page="case-study"] .case-year,
body[data-theme="dark"][data-page="case-study"] .case-text-block p,
body[data-theme="dark"][data-page="case-study"] .case-placeholder-section p,
body[data-theme="dark"][data-page="case-study"] .journey-copy p,
body[data-theme="dark"][data-page="case-study"] .iterations-intro p,
body[data-theme="dark"][data-page="case-study"] .persona-copy,
body[data-theme="dark"][data-page="case-study"] .insight-item,
body[data-theme="dark"][data-page="case-study"] .style-label {
  color: var(--muted);
}

body[data-theme="dark"][data-page="case-study"] .case-meta-grid dt,
body[data-theme="dark"][data-page="case-study"] .case-meta-grid dd,
body[data-theme="dark"][data-page="case-study"] .case-text-block strong,
body[data-theme="dark"][data-page="case-study"] .type-scale-list dt,
body[data-theme="dark"][data-page="case-study"] .type-scale-list dd,
body[data-theme="dark"][data-page="case-study"] .type-sample span {
  color: var(--ink);
}

body[data-theme="dark"][data-page="case-study"] .case-hero-banner,
body[data-theme="dark"][data-page="case-study"] .case-image-placeholder,
body[data-theme="dark"][data-page="case-study"] .insights-banner,
body[data-theme="dark"][data-page="case-study"] .visual-style-board,
body[data-theme="dark"][data-page="case-study"] .case-placeholder-section {
  background:
    radial-gradient(rgba(255, 255, 255, 0.16) 1px, transparent 1px) 0 0 / 24px 24px,
    #111111;
}

body[data-theme="dark"][data-page="case-study"] .case-showcase,
body[data-theme="dark"][data-page="case-study"] .final-product-section,
body[data-theme="dark"][data-page="case-study"] .iterations-section {
  background: #000000 !important;
}

body[data-theme="dark"][data-page="case-study"] .case-photo-card,
body[data-theme="dark"][data-page="case-study"] .persona-card,
body[data-theme="dark"][data-page="case-study"] .mac-screen,
body[data-theme="dark"][data-page="case-study"] .mock-page-soft,
body[data-theme="dark"][data-page="case-study"] .mock-page-lines,
body[data-theme="dark"][data-page="case-study"] .mock-voderrn-page,
body[data-theme="dark"][data-page="case-study"] .final-mac-screen {
  background-color: #151515;
}

body[data-theme="dark"][data-page="case-study"] .case-photo-card,
body[data-theme="dark"][data-page="case-study"] .persona-card {
  border-color: rgba(255, 255, 255, 0.12);
}

body[data-theme="dark"][data-page="case-study"] .case-photo-card::after,
body[data-theme="dark"][data-page="case-study"] .placeholder-media span,
body[data-theme="dark"][data-page="case-study"] .persona-image::after {
  color: rgba(255, 255, 255, 0.48);
}

body[data-theme="dark"][data-page="case-study"] .wardrobe-ghost span,
body[data-theme="dark"][data-page="case-study"] .mock-sidebar,
body[data-theme="dark"][data-page="case-study"] .mock-main span,
body[data-theme="dark"][data-page="case-study"] .mock-closet-grid span,
body[data-theme="dark"][data-page="case-study"] .persona-line,
body[data-theme="dark"][data-page="case-study"] .persona-chip,
body[data-theme="dark"][data-page="case-study"] .iteration-side,
body[data-theme="dark"][data-page="case-study"] .iteration-hero-block,
body[data-theme="dark"][data-page="case-study"] .iteration-lines span,
body[data-theme="dark"][data-page="case-study"] .iteration-card-grid span {
  background: rgba(255, 255, 255, 0.15);
}

body[data-theme="dark"][data-page="case-study"] .closet-rail,
body[data-theme="dark"][data-page="case-study"] .journey-canvas::before,
body[data-theme="dark"][data-page="case-study"] .journey-canvas::after {
  background: rgba(255, 255, 255, 0.18);
}

body[data-theme="dark"][data-page="case-study"] .closet-tags span,
body[data-theme="dark"][data-page="case-study"] .journey-canvas span,
body[data-theme="dark"][data-page="case-study"] .journey-note {
  border-color: rgba(255, 255, 255, 0.2);
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.44);
}

body[data-theme="dark"][data-page="case-study"] .mock-browser-bar,
body[data-theme="dark"][data-page="case-study"] .iteration-mac-bar {
  background: #1d1d1d;
}

body[data-theme="dark"][data-page="case-study"] .case-placeholder-section {
  border-top-color: rgba(255, 255, 255, 0.14);
}

.case-image-placeholder {
  position: relative;
  min-height: clamp(230px, 24vw, 360px);
  overflow: hidden;
  border-radius: 30px;
  background:
    radial-gradient(#cccccc 1px, transparent 1px) 0 0 / 24px 24px,
    #f6f6f6;
}

.wardrobe-ghost,
.closet-tags {
  position: absolute;
  inset: 12%;
}

.wardrobe-ghost {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
  align-items: end;
}

.wardrobe-ghost span {
  display: block;
  height: 72%;
  border-radius: 999px 999px 18px 18px;
  background: rgba(0, 0, 0, 0.08);
}

.wardrobe-ghost span:nth-child(2) {
  height: 94%;
}

.wardrobe-ghost span:nth-child(3) {
  height: 62%;
}

.closet-rail {
  position: absolute;
  top: 28%;
  left: 13%;
  right: 13%;
  height: 4px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.16);
}

.closet-tags {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  align-items: center;
}

.closet-tags span {
  display: grid;
  place-items: center;
  min-height: 104px;
  border: 2px dashed rgba(0, 0, 0, 0.18);
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.56);
  color: rgba(0, 0, 0, 0.42);
  font-size: clamp(22px, 2.4vw, 34px);
  font-weight: 700;
  transform: rotate(var(--tag-tilt, -3deg));
}

.closet-tags span:nth-child(2) {
  --tag-tilt: 2deg;
}

.closet-tags span:nth-child(3) {
  --tag-tilt: -1deg;
}

.case-showcase {
  display: grid;
  place-items: center;
  min-height: clamp(420px, 46vw, 620px);
  overflow: hidden;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
  background: #f1f1f1 !important;
}

.mac-scroll-stack {
  position: relative;
  width: min(84%, 1040px);
  min-height: clamp(300px, 34vw, 470px);
}

.mac-frame {
  position: absolute;
  overflow: hidden;
  border: 7px solid #171717;
  border-radius: 22px 22px 12px 12px;
  background: #171717;
  box-shadow: 0 24px 46px rgba(0, 0, 0, 0.14);
}

.mac-screen {
  position: relative;
  height: 100%;
  overflow: hidden;
  border-radius: 14px 14px 7px 7px;
  background: #f7f7f7;
}

.mac-frame-back {
  top: 15%;
  width: 40%;
  aspect-ratio: 16 / 10;
  height: auto;
  opacity: 0.42;
  filter: grayscale(0.2);
}

.mac-left {
  left: -8%;
}

.mac-right {
  right: -8%;
}

/* mac screen */
.mac-frame-front {
  left: 50%;
  top: 0;
  width: min(66%, 720px);
  aspect-ratio: 16 / 10;
  height: auto;
  transform: translateX(-50%);
}

.mac-base {
  position: absolute;
  left: 20%;
  right: 20%;
  bottom: -24px;
  height: 26px;
  border-radius: 0 0 20px 20px;
  background: linear-gradient(#d9d9d9, #b9b9b9);
}

.mock-page {
  height: 180%;
  animation: mac-page-scroll 9s linear infinite;
}

.mock-page-soft {
  background: #f7f7f7;
}

.mock-page-lines {
  background: #f7f7f7;
}

.mock-browser-bar {
  display: flex;
  gap: 7px;
  padding: 12px;
  background: #ffffff;
}

.mock-browser-bar span {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #d9d9d9;
}

.mock-voderrn-page {
  display: grid;
  grid-template-columns: 23% 1fr;
  grid-template-rows: auto 1fr;
  gap: 16px;
  height: calc(100% - 33px);
  padding: clamp(16px, 1.8vw, 26px);
  background: #fafafa;
}

.mock-sidebar {
  grid-row: 1 / span 2;
  border-radius: 18px;
  background: #e8e8e8;
}

.mock-main {
  display: grid;
  gap: 10px;
  align-content: start;
}

.mock-main span {
  display: block;
  height: 18px;
  border-radius: 999px;
  background: #dddddd;
}

.mock-main span:first-child {
  width: 48%;
  height: 30px;
  background: #cfcfcf;
}

.mock-main span:nth-child(2) {
  width: 70%;
}

.mock-main span:nth-child(3) {
  width: 56%;
}

.mock-closet-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}

.mock-closet-grid span {
  border-radius: 18px;
  background:
    linear-gradient(150deg, rgba(255, 255, 255, 0.48), rgba(255, 255, 255, 0)),
    #e1e1e1;
}

.case-placeholder-section {
  display: grid;
  align-content: center;
  min-height: clamp(380px, 48vw, 620px);
  border-top: 1px solid rgba(0, 0, 0, 0.08);
}

.visual-style-section {
  display: grid;
  align-content: center;
  gap: clamp(34px, 4.2vw, 60px);
  width: min(calc(100% - clamp(260px, 22vw, 520px)), 1220px);
  min-height: clamp(440px, 45vw, 620px);
  margin: 0 auto;
  padding: clamp(62px, 7vw, 94px) 0;
  font-family: var(--hand);
}

.visual-style-section h2,
.iterations-section h2 {
  margin: 0;
  color: #111111;
  font-family: var(--stamp);
  font-size: clamp(10px, 3vw, 20px);
  font-weight: 400;
  line-height: 1.04;
  letter-spacing: 0;
  text-transform: uppercase;
}

.visual-style-grid {
  display: grid;
  grid-template-columns: minmax(520px, 1fr) minmax(300px, 0.54fr);
  align-items: center;
  gap: clamp(48px, 5.2vw, 92px);
}

.style-label {
  margin: 0 0 clamp(22px, 2.4vw, 34px);
  color: #b4b4b4;
  font-family: var(--hand);
  font-size: clamp(16px, 1.45vw, 22px);
  font-weight: 400;
  line-height: 1;
}

.type-spec-panel {
  display: grid;
  grid-template-columns: minmax(142px, 0.34fr) minmax(360px, 1fr);
  column-gap: clamp(38px, 4.6vw, 74px);
  align-items: center;
}

.type-spec-panel .style-label {
  grid-column: 1 / -1;
}

.type-sample {
  display: grid;
  gap: 14px;
  align-content: end;
  min-height: 188px;
  font-family: "Satoshi", "Avenir Next", "Helvetica Neue", Arial, sans-serif;
}

.type-sample span {
  display: block;
  color: #242424;
  font-size: clamp(88px, 7.4vw, 118px);
  font-weight: 700;
  line-height: 0.78;
  letter-spacing: -0.055em;
}

.type-sample p {
  margin: 0;
  color: #6e6e6e;
  font-size: clamp(15px, 1.1vw, 18px);
  line-height: 1.3;
}

.type-scale-list {
  display: grid;
  gap: clamp(15px, 1.45vw, 22px);
  margin: 0;
  font-family: var(--hand);
}

.type-scale-list div {
  display: grid;
  grid-template-columns: minmax(148px, 1fr) minmax(86px, 0.56fr) minmax(58px, 0.34fr);
  gap: clamp(26px, 3vw, 52px);
  align-items: baseline;
}

.type-scale-list dt,
.type-scale-list dd {
  margin: 0;
  color: #1c1c1c;
  font-size: clamp(15px, 1.28vw, 20px);
  line-height: 1.08;
}

.type-scale-list dt,
.type-scale-list dd:first-of-type {
  font-weight: 700;
}

.type-scale-list dd:last-of-type {
  justify-self: end;
  font-weight: 400;
}

.color-spec-panel {
  display: grid;
  align-content: end;
}

.color-orbit {
  display: grid;
  grid-template-columns: repeat(3, minmax(96px, 1fr));
  gap: clamp(14px, 1.5vw, 22px);
}

.color-swatch {
  display: grid;
  place-items: center;
  aspect-ratio: 1;
  min-width: 0;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 50%;
  background: var(--swatch);
  color: #ffffff;
  font-family: var(--hand);
  font-size: clamp(14px, 1.2vw, 18px);
  font-weight: 700;
  line-height: 1;
}

.journey-map-section {
  grid-template-columns: minmax(520px, 1fr) minmax(300px, 0.5fr);
  width: min(calc(100% - clamp(260px, 22vw, 520px)), 1220px);
  min-height: clamp(440px, 45vw, 620px);
  gap: clamp(48px, 5.2vw, 92px);
}

.journey-map-board {
  position: relative;
  min-height: clamp(340px, 32vw, 460px);
  overflow: hidden;
  opacity: 0.92;
}

.journey-canvas {
  position: absolute;
  inset: 2% 0 0;
  background:
    linear-gradient(90deg, transparent 0 24%, rgba(0, 0, 0, 0.12) 24% 24.35%, transparent 24.35% 100%),
    linear-gradient(180deg, rgba(0, 0, 0, 0.11) 0 1px, transparent 1px 100%);
  background-size: 100% 100%, 100% 20%;
}

.journey-canvas span {
  position: absolute;
  display: block;
  border: 2px solid rgba(0, 0, 0, 0.24);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.32);
}

.journey-canvas span:nth-child(1) {
  left: 2%;
  top: 18%;
  width: 10%;
  height: 48%;
}

.journey-canvas span:nth-child(2) {
  left: 17%;
  top: 13%;
  width: 13%;
  height: 58%;
}

.journey-canvas span:nth-child(3) {
  left: 38%;
  top: 20%;
  width: 22%;
  height: 32%;
  border-radius: 20px;
  transform: rotate(-7deg);
}

.journey-canvas span:nth-child(4) {
  right: 22%;
  bottom: 8%;
  width: 18%;
  height: 24%;
  border-radius: 18px;
  transform: rotate(3deg);
}

.journey-canvas span:nth-child(5) {
  right: 5%;
  top: 14%;
  width: 12%;
  height: 54%;
}

.journey-canvas::before,
.journey-canvas::after {
  content: "";
  position: absolute;
  background: rgba(0, 0, 0, 0.16);
  transform-origin: left center;
}

.journey-canvas::before {
  left: 26%;
  top: 39%;
  width: 36%;
  height: 2px;
  transform: rotate(-16deg);
}

.journey-canvas::after {
  right: 18%;
  top: 27%;
  width: 22%;
  height: 2px;
  transform: rotate(24deg);
}

.journey-note {
  position: absolute;
  width: clamp(116px, 10vw, 160px);
  height: clamp(86px, 7vw, 116px);
  border: 2px solid rgba(0, 0, 0, 0.14);
  border-radius: 16px;
  background:
    repeating-linear-gradient(180deg, transparent 0 17px, rgba(0, 0, 0, 0.12) 17px 18px),
    rgba(255, 255, 255, 0.72);
}

.note-a {
  left: 12%;
  bottom: 10%;
  transform: rotate(-5deg);
}

.note-b {
  left: 47%;
  top: 4%;
  transform: rotate(4deg);
}

.note-c {
  right: 10%;
  bottom: 18%;
  transform: rotate(-2deg);
}

.journey-copy h2 {
  font-family: var(--stamp);
  font-size: clamp(10px, 3vw, 20px);
  font-weight: 400;
  line-height: 1.04;
  text-transform: uppercase;
}

.journey-copy p {
  font-family: var(--hand);
  color: #7a7a7a;
  font-size: clamp(10px, 1.75vw, 20px);
  line-height: 1.25;
}

.iterations-section {
  position: relative;
  display: grid;
  gap: clamp(34px, 4.2vw, 60px);
  min-height: 540vh;
  width: 100%;
  margin: 0 auto;
  padding: clamp(64px, 6.8vw, 96px) 0 0;
  font-family: var(--hand);
}

.iterations-intro {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  padding-left: clamp(52px, 7vw, 116px);
}

.iterations-intro h2,
.iterations-intro p {
  width: min(520px, 42vw);
}

.iterations-intro p {
  margin: 18px 0 0;
  color: #b0b0b0;
  font-family: var(--hand);
  font-size: clamp(10px, 1.75vw, 20px);
  line-height: 1.25;
}

.iterations-comparison {
  position: sticky;
  top: clamp(86px, 9vw, 118px);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  min-height: clamp(560px, 52vw, 760px);
  width: 100vw;
  margin-left: calc(50% - 50vw);
  overflow: hidden;
}

.iterations-comparison::after {
  content: none;
}

.iteration-panel::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 4;
  height: 26%;
  background: linear-gradient(180deg, rgba(247, 247, 247, 0) 0%, rgba(247, 247, 247, 0.72) 50%, rgba(247, 247, 247, 0) 100%);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  mask-image: linear-gradient(180deg, transparent 0%, #000000 34%, #000000 66%, transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000000 34%, #000000 66%, transparent 100%);
  pointer-events: none;
}

.iteration-panel {
  position: relative;
  display: grid;
  align-content: start;
  min-height: inherit;
  overflow: hidden;
  padding: clamp(46px, 4.8vw, 72px) clamp(52px, 7vw, 116px);
}

.iteration-before {
  background: #f7f7f7;
  color: #171717;
}

.iteration-after {
  background: #151515;
  color: #eeeeee;
}

.iteration-after::after {
  background: linear-gradient(180deg, rgba(21, 21, 21, 0) 0%, rgba(21, 21, 21, 0.76) 50%, rgba(21, 21, 21, 0) 100%);
}

.iteration-copy {
  position: relative;
  z-index: 2;
  width: min(100%, 520px);
  transition:
    opacity 260ms ease,
    transform 320ms ease;
}

.iteration-after .iteration-copy {
  width: min(76%, 600px);
  margin: 0 auto;
}

.iteration-copy h3 {
  margin: 0 0 18px;
  font-family: var(--hand);
  font-size: clamp(18px, 1.5vw, 23px);
  font-weight: 700;
  line-height: 1;
}

.iteration-copy p {
  margin: 0;
  color: #a7a7a7;
  font-family: var(--hand);
  font-size: clamp(10px, 1.75vw, 20px);
  line-height: 1.25;
}

.iteration-after .iteration-copy p {
  color: #bdbdbd;
}

.iterations-section.is-iteration-changing .iteration-copy {
  opacity: 0.28;
  transform: translateY(10px);
}

.iteration-mac {
  position: absolute;
  left: 50%;
  bottom: clamp(58px, 5.4vw, 92px);
  width: min(76%, 600px);
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border: clamp(8px, 0.9vw, 13px) solid #161616;
  border-radius: 22px;
  background: #161616;
  box-shadow: 0 28px 58px rgba(0, 0, 0, 0.24);
  transform: translateX(-50%);
}

.iteration-after .iteration-mac {
  bottom: clamp(58px, 5.4vw, 92px);
  transform: translateX(-50%);
}

.iteration-mac-bar {
  display: flex;
  gap: 8px;
  height: 34px;
  padding: 12px 14px;
  background: #f8f8f8;
}

.iteration-mac-bar span {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #d4d4d4;
}

.iteration-screen {
  display: grid;
  grid-template-columns: 26% 1fr;
  grid-template-rows: auto 1fr;
  gap: 16px;
  height: calc(100% - 34px);
  padding: clamp(18px, 2vw, 30px);
  background: #fbfbfb;
}

.iteration-after .iteration-screen {
  background: #f4f4f4;
}

.iteration-side,
.iteration-hero-block,
.iteration-lines span,
.iteration-card-grid span {
  border-radius: 18px;
  background: #dedede;
}

.iteration-side {
  grid-row: 1 / span 2;
  min-height: 100%;
}

.iteration-hero-block {
  grid-row: 1 / span 2;
  min-height: 100%;
  background: #d4d4d4;
}

.iteration-lines {
  display: grid;
  gap: 11px;
  align-content: start;
}

.iteration-lines span {
  display: block;
  height: 18px;
  border-radius: 999px;
}

.iteration-lines span:first-child {
  width: 64%;
  height: 30px;
  background: #cdcdcd;
}

.iteration-lines span:nth-child(2) {
  width: 84%;
}

.iteration-lines span:nth-child(3) {
  width: 58%;
}

.iteration-card-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 13px;
  align-self: end;
}

.iteration-after .iteration-card-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.iteration-card-grid span {
  min-height: clamp(116px, 10vw, 154px);
  background:
    linear-gradient(145deg, rgba(255, 255, 255, 0.58), rgba(255, 255, 255, 0)),
    #dddddd;
}

.iterations-scroll-steps {
  display: grid;
  gap: 0;
  margin-top: -100vh;
  pointer-events: none;
}

.iteration-step {
  min-height: 100vh;
}

.iterations-section[data-active-iteration="1"] .iteration-screen {
  grid-template-columns: 1fr;
}

.iterations-section[data-active-iteration="1"] .iteration-side,
.iterations-section[data-active-iteration="1"] .iteration-hero-block {
  grid-column: 1;
  grid-row: auto;
  min-height: clamp(88px, 8vw, 130px);
}

.iterations-section[data-active-iteration="2"] .iteration-card-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.iterations-section[data-active-iteration="3"] .iteration-screen {
  grid-template-columns: 32% 1fr;
}

.iterations-section[data-active-iteration="3"] .iteration-lines span:first-child {
  width: 86%;
}

.iterations-section[data-active-iteration="4"] .iteration-card-grid {
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

.final-product-section {
  display: grid;
  align-content: center;
  gap: clamp(46px, 5vw, 76px);
  min-height: clamp(620px, 58vw, 860px);
  width: 100vw;
  margin-left: calc(50% - 50vw);
  padding: clamp(78px, 8vw, 122px) 0 clamp(72px, 7vw, 112px);
  overflow: hidden;
  background: #ffffff;
}

.final-product-heading {
  display: grid;
  justify-items: center;
  gap: 10px;
  width: min(calc(100% - clamp(260px, 22vw, 520px)), 1220px);
  margin: 0 auto;
  text-align: center;
}

.final-product-heading p,
.final-product-heading h2 {
  margin: 0;
}

.final-product-heading p {
  color: #b5b5b5;
  font-family: var(--hand);
  font-size: clamp(10px, 1.75vw, 20px);
  line-height: 1;
}

.final-product-heading h2 {
  color: #171717;
  font-family: var(--stamp);
  font-size: clamp(10px, 3vw, 20px);
  font-weight: 400;
  line-height: 1;
  letter-spacing: 0;
  text-transform: uppercase;
}

.final-product-showcase {
  position: relative;
  min-height: clamp(330px, 34vw, 520px);
  overflow: hidden;
  isolation: isolate;
  mask-image: linear-gradient(90deg, transparent 0%, #000 11%, #000 89%, transparent 100%);
}

.final-page-strip {
  position: absolute;
  left: 50%;
  top: 11%;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: clamp(32px, 4vw, 72px);
  width: max-content;
  transform: translateX(-50%);
  animation: final-page-drift 24s linear infinite;
}

.final-page {
  display: grid;
  grid-template-columns: 24% 1fr;
  grid-template-rows: auto 1fr;
  gap: clamp(12px, 1.2vw, 18px);
  width: clamp(310px, 25vw, 480px);
  aspect-ratio: 16 / 10;
  padding: clamp(18px, 1.8vw, 28px);
  border: 0;
  border-radius: 18px;
  background: rgba(248, 248, 248, 0.82);
  opacity: 0.46;
  filter: grayscale(0.1);
}

.final-page:nth-child(2n) {
  opacity: 0.58;
}

.final-page:nth-child(3n) {
  width: clamp(340px, 28vw, 540px);
}

.final-page-hero,
.final-page-sidebar,
.final-page-lines span,
.final-page-cards span,
.final-setting-list span {
  display: block;
  border-radius: 999px;
  background: #dedede;
}

.final-page-hero {
  grid-column: 1 / -1;
  width: 54%;
  height: clamp(24px, 2.2vw, 36px);
  border-radius: 999px;
  background: #d2d2d2;
}

.final-page-sidebar {
  grid-row: 1 / span 2;
  min-height: 100%;
  border-radius: 20px;
  background: #e2e2e2;
}

.final-page-lines {
  display: grid;
  align-content: start;
  gap: clamp(8px, 0.8vw, 12px);
}

.final-page-lines span {
  height: clamp(12px, 1vw, 18px);
}

.final-page-lines span:first-child {
  width: 64%;
}

.final-page-lines span:nth-child(2) {
  width: 86%;
}

.final-page-lines span:nth-child(3) {
  width: 70%;
}

.final-page-cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 0.9vw, 13px);
  align-self: end;
}

.final-page-cards span {
  min-height: clamp(78px, 7vw, 132px);
  border-radius: 18px;
  background:
    linear-gradient(145deg, rgba(255, 255, 255, 0.62), rgba(255, 255, 255, 0)),
    #dddddd;
}

.final-setting-list {
  grid-column: 1 / -1;
  display: grid;
  gap: clamp(10px, 0.9vw, 14px);
  align-content: end;
}

.final-setting-list span {
  height: clamp(34px, 3vw, 48px);
  border-radius: 18px;
}

.final-mac-air {
  position: absolute;
  left: 50%;
  top: clamp(22px, 2.6vw, 42px);
  z-index: 3;
  width: min(58vw, 760px);
  aspect-ratio: 16 / 9.7;
  transform: translateX(-50%);
  pointer-events: none;
}

.final-mac-screen {
  height: calc(100% - clamp(12px, 1.3vw, 20px));
  overflow: hidden;
  border: clamp(7px, 0.75vw, 12px) solid #151515;
  border-radius: clamp(18px, 1.6vw, 25px);
  background: #151515;
  box-shadow: 0 24px 58px rgba(0, 0, 0, 0.2);
}

.final-mac-bar {
  display: flex;
  gap: 8px;
  height: clamp(28px, 2.4vw, 38px);
  padding: clamp(10px, 0.9vw, 14px);
  background: #fafafa;
}

.final-mac-bar span {
  width: clamp(8px, 0.75vw, 11px);
  height: clamp(8px, 0.75vw, 11px);
  border-radius: 50%;
  background: #d6d6d6;
}

.final-mac-page {
  display: grid;
  grid-template-columns: 22% 1fr;
  grid-template-rows: auto 1fr;
  gap: clamp(14px, 1.3vw, 20px);
  height: calc(100% - clamp(28px, 2.4vw, 38px));
  padding: clamp(18px, 2vw, 32px);
  background: #fbfbfb;
}

.final-mac-lip {
  width: 68%;
  height: clamp(10px, 1vw, 16px);
  margin: -1px auto 0;
  border-radius: 0 0 22px 22px;
  background: linear-gradient(180deg, #d8d8d8, #b9b9b9);
}

@keyframes final-page-drift {
  from {
    transform: translateX(-60%);
  }

  to {
    transform: translateX(-40%);
  }
}

.research-section {
  min-height: clamp(410px, 42vw, 580px);
}

.competitor-section {
  grid-template-columns: minmax(280px, 0.58fr) minmax(300px, 0.9fr);
}

.competitor-section .case-text-block {
  max-width: 470px;
}

.competitor-section .case-text-block h2 {
  white-space: nowrap;
}

.market-notes,
.competitor-grid {
  position: absolute;
  inset: 15%;
  display: grid;
  gap: 16px;
}

.market-notes {
  grid-template-columns: repeat(3, 1fr);
  align-items: end;
}

.market-notes span {
  display: block;
  height: 64%;
  border: 2px dashed rgba(0, 0, 0, 0.14);
  border-radius: 22px;
  background: rgba(255, 255, 255, 0.54);
}

.market-notes span:nth-child(2) {
  height: 88%;
}

.market-notes span:nth-child(3) {
  height: 54%;
}

.competitor-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.competitor-grid span {
  border-radius: 24px;
  background:
    linear-gradient(180deg, rgba(0, 0, 0, 0.12) 0 18%, transparent 18%),
    rgba(255, 255, 255, 0.58);
}

.persona-section {
  display: grid;
  justify-items: center;
  gap: clamp(22px, 2.2vw, 40px);
  width: min(calc(100% - clamp(260px, 22vw, 520px)), 1220px);
  margin: 0 auto;
  min-height: clamp(610px, 56vw, 820px);
  padding: clamp(72px, 8vw, 118px) 0;
}

.persona-copy {
  justify-self: start;
  width: min(100%, 560px);
}

.persona-reveal {
  width: min(100%, 680px);
  justify-self: center;
  overflow: visible;
  clip-path: inset(clamp(-120px, -7vw, -72px) 0 0 0);
  min-height: clamp(460px, 42vw, 620px);
  margin-top: 0;
  padding-top: 3px;
}

.persona-card {
  width: 100%;
  padding: clamp(18px, 2vw, 28px);
  border: 2px solid rgba(0, 0, 0, 0.06);
  border-radius: 8px;
  background: #ffffff;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.06);
  pointer-events: auto;
  transform: translateY(clamp(74px, 5.8vw, 108px));
  transition: transform 320ms cubic-bezier(0.45, 0.02, 0.12, 1);
}

.persona-reveal:hover .persona-card,
.persona-reveal:focus-within .persona-card {
  transform: translateY(clamp(-76px, -4.8vw, -42px));
}

.persona-photo {
  aspect-ratio: 16 / 9;
  border-radius: 8px;
  background:
    linear-gradient(135deg, rgba(255, 255, 255, 0.28), rgba(255, 255, 255, 0)),
    repeating-linear-gradient(90deg, #dedede 0 18px, #cfcfcf 18px 36px);
}

.persona-card-copy {
  display: grid;
  gap: 12px;
  padding: clamp(20px, 2.4vw, 32px) clamp(8px, 1.2vw, 18px) clamp(62px, 6vw, 92px);
}

.persona-card-copy h3 {
  margin: 0;
  color: #111111;
  font-family: var(--hand);
  font-size: clamp(25px, 2.7vw, 38px);
  font-weight: 700;
  line-height: 0.95;
  text-align: center;
}

.persona-card-copy p {
  margin: -6px 0 10px;
  color: #9a9a9a;
  font-size: clamp(16px, 1.3vw, 20px);
  line-height: 1;
  text-align: center;
}

.persona-lines {
  display: grid;
  gap: 9px;
}

.persona-lines span,
.persona-columns span,
.persona-detail-grid span {
  display: block;
  height: 10px;
  border-radius: 999px;
  background: #dddddd;
}

.persona-lines span:nth-child(1) {
  width: 82%;
}

.persona-lines span:nth-child(2) {
  width: 72%;
}

.persona-lines span:nth-child(3) {
  width: 56%;
}

.persona-lines span:nth-child(4) {
  width: 74%;
}

.persona-lines span:nth-child(5) {
  width: 48%;
}

.persona-columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 18px;
  margin-top: 14px;
}

.persona-columns span {
  height: 52px;
  border-radius: 14px;
}

.persona-detail-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-top: 4px;
}

.persona-detail-grid span {
  height: 42px;
  border-radius: 14px;
}

.research-insights-banner {
  position: relative;
  width: 100vw;
  min-height: clamp(420px, 48vw, 680px);
  margin: clamp(20px, 4vw, 64px) calc(50% - 50vw) 0;
  overflow: hidden;
  background: #f1f1f1;
  isolation: isolate;
}

.insight-text-field {
  position: absolute;
  inset: 0;
  display: block !important;
  overflow: hidden;
  font-family: var(--hand);
}

.insight-quote {
  position: absolute;
  width: clamp(130px, 10vw, 168px);
  margin: 0;
  color: rgba(74, 74, 74, 0.34);
  font-size: clamp(13px, 1vw, 17px);
  line-height: 1.32;
  opacity: 0.7;
}

.insight-quote.soft {
  color: rgba(82, 82, 82, 0.18);
  opacity: 0.56;
}

.insight-quote.edge {
  color: rgba(72, 72, 72, 0.12);
  opacity: 0.42;
}

.insight-quote.left-edge {
  transform: translateX(-46px);
}

.insight-quote.right-edge {
  transform: translateX(46px);
}

.insight-quote:nth-child(1) { left: 3%; top: 13%; }
.insight-quote:nth-child(2) { left: 14%; top: 16%; }
.insight-quote:nth-child(3) { left: 26%; top: 12%; }
.insight-quote:nth-child(4) { left: 39%; top: 14%; }
.insight-quote:nth-child(5) { left: 52%; top: 12%; }
.insight-quote:nth-child(6) { left: 65%; top: 14%; }
.insight-quote:nth-child(7) { left: 78%; top: 15%; }
.insight-quote:nth-child(8) { left: 91%; top: 17%; }
.insight-quote:nth-child(9) { left: 0%; top: 33%; }
.insight-quote:nth-child(10) { left: 14%; top: 31%; }
.insight-quote:nth-child(11) { left: 26%; top: 32%; }
.insight-quote:nth-child(12) { left: 39%; top: 31%; }
.insight-quote:nth-child(13) { left: 52%; top: 32%; }
.insight-quote:nth-child(14) { left: 65%; top: 31%; }
.insight-quote:nth-child(15) { left: 78%; top: 32%; }
.insight-quote:nth-child(16) { left: 91%; top: 31%; }
.insight-quote:nth-child(17) { left: 3%; top: 51%; }
.insight-quote:nth-child(18) { left: 14%; top: 51%; }
.insight-quote:nth-child(19) { left: 26%; top: 53%; }
.insight-quote:nth-child(20) { left: 65%; top: 51%; }
.insight-quote:nth-child(21) { left: 78%; top: 51%; }
.insight-quote:nth-child(22) { left: 91%; top: 51%; }
.insight-quote:nth-child(23) { left: 2%; top: 72%; }
.insight-quote:nth-child(24) { left: 14%; top: 72%; }
.insight-quote:nth-child(25) { left: 26%; top: 72%; }
.insight-quote:nth-child(26) { left: 39%; top: 73%; }
.insight-quote:nth-child(27) { left: 52%; top: 72%; }
.insight-quote:nth-child(28) { left: 65%; top: 73%; }
.insight-quote:nth-child(29) { left: 78%; top: 72%; }
.insight-quote:nth-child(30) { left: 91%; top: 72%; }

.insight-focus-veil {
  position: absolute;
  left: 50%;
  top: 51%;
  z-index: 2;
  width: min(54%, 760px);
  height: 56%;
  background: radial-gradient(
    circle,
    rgba(241, 241, 241, 0.98) 0%,
    rgba(241, 241, 241, 0.9) 30%,
    rgba(241, 241, 241, 0.52) 58%,
    rgba(241, 241, 241, 0) 80%
  );
  pointer-events: none;
  transform: translate(-50%, -50%);
}

.insight-center-stat {
  position: absolute;
  left: 50%;
  top: 51%;
  z-index: 3;
  display: grid;
  justify-items: center;
  color: #666666;
  text-align: center;
  pointer-events: none;
  transform: translate(-50%, -50%);
}

.insight-center-stat strong {
  margin: 0;
  color: #626262;
  font-family: var(--hand);
  font-size: clamp(25px, 2.45vw, 38px);
  font-weight: 700;
  line-height: 0.98;
  white-space: nowrap;
}

.insight-center-stat span {
  margin-top: 14px;
  color: rgba(92, 92, 92, 0.56);
  font-size: clamp(14px, 1.05vw, 18px);
  line-height: 1.08;
  white-space: nowrap;
}

.insight-edge-blur,
.insight-corner-fade {
  position: absolute;
  pointer-events: none;
}

.insight-edge-blur {
  inset: 0;
  z-index: 4;
  background:
    linear-gradient(90deg, #f1f1f1 0%, rgba(241, 241, 241, 0.86) 7%, rgba(241, 241, 241, 0) 18%, rgba(241, 241, 241, 0) 82%, rgba(241, 241, 241, 0.86) 93%, #f1f1f1 100%),
    linear-gradient(180deg, #f1f1f1 0%, rgba(241, 241, 241, 0.78) 8%, rgba(241, 241, 241, 0) 22%, rgba(241, 241, 241, 0) 78%, rgba(241, 241, 241, 0.78) 92%, #f1f1f1 100%);
  backdrop-filter: blur(0.2px);
}

.insight-corner-fade {
  inset: -80px;
  z-index: 5;
  background:
    radial-gradient(circle at 0% 50%, #f1f1f1 0%, rgba(241, 241, 241, 0.72) 11%, rgba(241, 241, 241, 0) 26%),
    radial-gradient(circle at 100% 50%, #f1f1f1 0%, rgba(241, 241, 241, 0.72) 11%, rgba(241, 241, 241, 0) 26%);
  opacity: 0.96;
}

@keyframes mac-page-scroll {
  0%,
  18% {
    transform: translateY(0);
  }

  78%,
  100% {
    transform: translateY(-42%);
  }
}

@media (max-width: 1080px) {
  .case-hover-zone {
    min-height: 132px;
  }

  .case-floating-nav {
    grid-template-columns: 56px minmax(0, 1fr) 56px;
    width: min(100% - 56px, 900px);
    margin-top: 28px;
  }

  .case-theme-toggle.pull-chain {
    top: -28px;
    width: 30px;
    height: 150px;
  }

  .case-section-nav {
    justify-self: start;
  }

  .case-study-page {
    width: min(100% - 64px, 900px);
    padding-top: 128px;
  }

  .case-hero-banner,
  .case-two-column,
  .pain-point-section,
  .competitor-section,
  .persona-section,
  .journey-map-section {
    grid-template-columns: 1fr;
  }

  .case-hero-visual {
    min-height: 220px;
  }

  .case-meta-grid {
    gap: 24px;
  }

  .pain-point-section .case-text-block {
    order: 2;
  }

  .pain-point-section .case-image-placeholder {
    order: 1;
  }

  .persona-section {
    width: min(100% - 64px, 900px);
    min-height: auto;
    gap: 28px;
    padding: 74px 0;
  }

  .persona-copy {
    justify-self: start;
    width: min(100%, 620px);
  }

  .persona-reveal {
    justify-self: center;
    width: min(100%, 620px);
    min-height: 520px;
    margin-top: 0;
    clip-path: inset(-76px 0 0 0);
  }

  .persona-reveal:hover .persona-card,
  .persona-reveal:focus-within .persona-card {
    transform: translateY(-44px);
  }

  .research-insights-banner {
    min-height: 540px;
  }

  .insight-quote {
    width: 140px;
    font-size: 14px;
  }

  /* below ~1000px the 8-per-row scatter columns get closer than the quote
     width — thin the wall to every other quote so nothing overlaps */
  .insight-quote:nth-child(odd) {
    display: none;
  }

  .visual-style-section,
  .iterations-section {
    width: min(100% - 64px, 900px);
  }

  .visual-style-grid {
    grid-template-columns: 1fr;
    gap: 54px;
  }

  .type-spec-panel {
    grid-template-columns: minmax(160px, 0.42fr) minmax(320px, 1fr);
  }

  .color-orbit {
    width: min(100%, 620px);
    grid-template-columns: repeat(3, minmax(120px, 1fr));
  }

  .journey-map-section .journey-copy {
    max-width: 620px;
  }

  .journey-map-board {
    min-height: 420px;
  }

  .iterations-intro {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    padding-left: clamp(52px, 7vw, 116px);
  }

  .iterations-intro h2,
  .iterations-intro p {
    width: 100%;
  }

  .iterations-comparison {
    grid-template-columns: 1fr;
  }

  .iteration-panel {
    min-height: 560px;
  }

  .iteration-after .iteration-copy {
    margin-left: 0;
  }

  .iteration-mac,
  .iteration-after .iteration-mac {
    width: min(82%, 620px);
    bottom: 54px;
    transform: translateX(-50%);
  }

  .final-product-section {
    min-height: 640px;
    padding: 84px 0 76px;
  }

  .final-product-heading {
    width: min(100% - 64px, 900px);
  }

  .final-product-showcase {
    min-height: 390px;
  }

  .final-page-strip {
    top: 16%;
    gap: 34px;
  }

  .final-page {
    width: clamp(260px, 38vw, 380px);
  }

  .final-mac-air {
    width: min(70vw, 620px);
    top: 18px;
  }
}

@media (max-width: 660px) {
  .case-hover-zone {
    min-height: 118px;
  }

  .case-floating-nav {
    grid-template-columns: 46px minmax(0, 1fr) 34px;
    gap: 10px;
    width: min(100% - 28px, 540px);
    margin-top: 16px;
    transform: translateY(0);
  }

  .case-theme-toggle.pull-chain {
    top: -18px;
    width: 24px;
    height: 118px;
  }

  .case-back-link {
    width: 44px;
    height: 44px;
    border-radius: 100px;
  }

  .case-back-link span {
    width: 11px;
    height: 11px;
    border-width: 2px;
  }

  .case-section-nav {
    padding: 5px;
    gap: 2px;
  }

  .case-section-nav a {
    padding: 8px 12px;
    font-size: 18px;
  }

  .case-study-page {
    width: min(100% - 28px, 540px);
    padding-top: 112px;
  }

  .case-hero-banner {
    min-height: auto;
    padding: 26px;
    border-radius: 28px;
  }

  .case-meta-grid {
    grid-template-columns: 1fr;
    gap: 20px;
  }

  .case-meta-grid dt {
    margin-bottom: 5px;
  }

  .case-photo-card {
    width: clamp(240px, 70vw, 360px);
    border-width: 7px;
  }

  .case-photo-card {
    right: 12px;
    bottom: 14px;
    transform: translateY(58%);
  }

  .case-two-column {
    gap: 34px;
    width: 100%;
    min-height: auto;
    padding: 68px 0;
  }

  .case-showcase {
    min-height: 360px;
  }

  .persona-section {
    width: 100%;
    gap: 24px;
    padding: 62px 0;
  }

  .competitor-section .case-text-block h2 {
    white-space: normal;
  }

  .persona-reveal {
    width: 100%;
    min-height: 450px;
    margin-top: 0;
    clip-path: inset(-54px 0 0 0);
  }

  .persona-card {
    padding: 16px;
    transform: translateY(58px);
  }

  .persona-reveal:hover .persona-card,
  .persona-reveal:focus-within .persona-card {
    transform: translateY(-28px);
  }

  .research-insights-banner {
    min-height: 440px;
    margin-top: 18px;
  }

  /* Phone: keep only a sparse curated scatter of quotes in two aligned
     columns; the rest of the wall reads as noise at this width. */
  .insight-quote {
    display: none;
    width: 124px;
    font-size: 13px;
    line-height: 1.22;
  }

  .insight-quote:nth-child(2),
  .insight-quote:nth-child(6),
  .insight-quote:nth-child(10),
  .insight-quote:nth-child(14),
  .insight-quote:nth-child(18),
  .insight-quote:nth-child(20),
  .insight-quote:nth-child(24),
  .insight-quote:nth-child(28) {
    display: block;
    transform: none;
  }

  .insight-quote:nth-child(2),
  .insight-quote:nth-child(10),
  .insight-quote:nth-child(18),
  .insight-quote:nth-child(24) {
    left: 6%;
  }

  .insight-quote:nth-child(6),
  .insight-quote:nth-child(14),
  .insight-quote:nth-child(20),
  .insight-quote:nth-child(28) {
    left: 60%;
  }

  .insight-quote:nth-child(2) { top: 7%; }
  .insight-quote:nth-child(6) { top: 8%; }
  .insight-quote:nth-child(10) { top: 32%; }
  .insight-quote:nth-child(14) { top: 33%; }
  .insight-quote:nth-child(18) { top: 55%; }
  .insight-quote:nth-child(20) { top: 56%; }
  .insight-quote:nth-child(24) { top: 78%; }
  .insight-quote:nth-child(28) { top: 79%; }

  .insight-center-stat {
    width: min(76%, 320px);
  }

  .insight-center-stat strong,
  .insight-center-stat span {
    white-space: normal;
  }

  .insight-center-stat strong {
    font-size: clamp(24px, 8vw, 30px);
  }

  .visual-style-section,
  .iterations-section {
    width: 100%;
    min-height: auto;
    padding: 68px 0;
  }

  .visual-style-section h2,
  .iterations-section h2,
  .journey-copy h2 {
    font-size: clamp(10px, 3vw, 20px);
  }

  .visual-style-grid,
  .type-spec-panel {
    grid-template-columns: 1fr;
    gap: 36px;
  }

  .style-label {
    margin-bottom: 20px;
  }

  .type-sample {
    min-height: 0;
  }

  .type-sample span {
    font-size: clamp(92px, 24vw, 118px);
  }

  .type-scale-list div {
    grid-template-columns: minmax(120px, 1fr) minmax(72px, 0.6fr) auto;
    gap: 18px;
  }

  .type-scale-list dt,
  .type-scale-list dd {
    font-size: clamp(18px, 5.4vw, 22px);
  }

  .color-orbit {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px;
  }

  .color-swatch {
    font-size: clamp(11px, 3.2vw, 14px);
  }

  .journey-map-section {
    gap: 28px;
  }

  .journey-map-board {
    min-height: 300px;
  }

  .iterations-intro {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    padding-left: 26px;
    padding-right: 26px;
  }

  .iterations-intro h2,
  .iterations-intro p {
    width: 100%;
  }

  .iterations-comparison {
    gap: 0;
  }

  .iteration-panel {
    min-height: 480px;
    padding: 44px 26px;
  }

  .iteration-copy {
    width: 100%;
  }

  .iteration-copy p {
    font-size: clamp(18px, 5.5vw, 22px);
  }

  .iteration-mac,
  .iteration-after .iteration-mac {
    width: 88%;
    bottom: 42px;
    border-width: 7px;
    border-radius: 16px;
    transform: translateX(-50%);
  }

  .final-product-section {
    gap: 34px;
    min-height: 520px;
    padding: 70px 0 62px;
  }

  .final-product-heading {
    width: min(100% - 28px, 540px);
    justify-items: start;
    text-align: left;
  }

  .final-product-heading h2 {
    font-size: clamp(10px, 3vw, 20px);
  }

  .final-product-heading p {
    font-size: clamp(10px, 1.75vw, 20px);
  }

  .final-product-showcase {
    min-height: 320px;
    mask-image: linear-gradient(90deg, transparent 0%, #000 7%, #000 93%, transparent 100%);
  }

  .final-page-strip {
    top: 19%;
    gap: 22px;
    animation-duration: 18s;
  }

  .final-page {
    width: clamp(220px, 62vw, 320px);
    padding: 14px;
    opacity: 0.42;
  }

  .final-mac-air {
    width: min(82vw, 390px);
    top: 42px;
  }

  .final-mac-screen {
    border-width: 7px;
    border-radius: 16px;
  }

  .final-mac-page {
    gap: 10px;
    padding: 14px;
  }

  .iteration-screen {
    gap: 10px;
    padding: 14px;
  }

  .iteration-card-grid {
    gap: 8px;
  }

  .journey-copy p {
    font-size: clamp(20px, 6vw, 25px);
  }

  .case-text-block p,
  .case-placeholder-section p {
    font-size: clamp(20px, 6vw, 25px);
  }

  .case-image-placeholder {
    min-height: 280px;
    border-radius: 24px;
  }

  .case-showcase {
    min-height: 360px;
  }

  .mac-scroll-stack {
    width: min(100%, 520px);
    min-height: 300px;
  }

  .mac-frame-back {
    width: 54%;
    height: 48%;
  }

  .mac-left {
    left: -22%;
  }

  .mac-right {
    right: -22%;
  }

  .mac-frame-front {
    width: 92%;
    height: 70%;
  }

  .mock-voderrn-page {
    grid-template-columns: 1fr;
  }

  .mock-sidebar {
    display: none;
  }

  .mock-closet-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.about-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 340px;
  gap: 52px;
}

.about-copy {
  align-self: end;
}

.about-portrait {
  align-self: start;
  overflow: hidden;
  border: 3px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: 7px 7px 0 color-mix(in srgb, var(--line) 16%, transparent);
  transform: rotate(2deg);
}

.about-portrait img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

.note-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 28px;
}

.note-strip span,
.resume-links a {
  padding: 10px 14px;
  border: 3px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  font-family: var(--hand);
}

.value-list {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 22px;
  margin-top: 8px;
}

.value-list article {
  padding: 24px;
}

.resume-layout {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(360px, 0.75fr);
  gap: 52px;
}

.timeline {
  display: grid;
  gap: 20px;
}

.timeline article {
  display: grid;
  grid-template-columns: 90px 1fr;
  gap: 20px;
  padding: 22px;
}

.resume-links {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: 18px;
}

.resume-page {
  display: grid;
  grid-template-columns: minmax(400px, 0.54fr) minmax(460px, 1fr);
  align-items: stretch;
  /* wide gutter so the enlarged "Where I am now" tree sits deep in it, clear of
     the résumé column, which is itself pushed right. The vw term grows the gap on
     wide screens; the reduced content min (460) lets the grid still fit in the
     medium 1081–1200 range instead of the old overflow (was 560 min → overflowed). */
  gap: clamp(140px, 15.5vw, 250px);
  width: min(100% - 112px, 1380px);
  margin: 0 auto;
  padding: clamp(8px, 1.2vw, 18px) 0 clamp(54px, 6vw, 88px);
}

.resume-tree-panel {
  position: relative;
  align-self: stretch;
  min-height: 100%;
  overflow: visible;
}

.resume-tree-large {
  position: absolute;
  left: clamp(-54px, -2.8vw, -26px);
  top: 0;
  bottom: 0;
  width: auto;
  height: 100%;
  max-width: none;
  mix-blend-mode: normal;
  object-fit: contain;
  object-position: left top;
  transform-origin: top left;
}

.resume-tree-small {
  position: absolute;
  /* shifted ~135px further right than the old sapling — it sits deep in the
     widened gutter (the résumé column was pushed right to make room). Visible
     label still clears the column; image right edge is transparent +
     pointer-events:none, so any box that reaches the column can't block clicks. */
  right: clamp(-211px, calc(-3.4vw - 135px), -173px);
  pointer-events: none;
  /* -7px lands the baked-in grass line (6.1% of image height) on the SAME
     ground line as the large tree's (1.68% of its drawn height ≈ 22px above
     the panel bottom): -7 + 0.061×489 ≈ 22.4 ✓ */
  bottom: -7px;
  /* sized so the baked-in "Where I am now" label renders at the SAME size as
     the large tree's "Where I'm growing toward" (large label: 21px cap in a
     2086px-tall source drawn at ~1331px → ~13.4px; small label: 25px cap in a
     660px-wide source → needs ~354px width). The large tree's height is
     content-driven (≈ constant across viewports), so the match is a constant,
     not vw-based; narrow screens shrink gracefully. */
  width: min(27.7vw, 354px);
  max-width: none;
  mix-blend-mode: normal;
}

body[data-theme="dark"] .resume-tree-large,
body[data-theme="dark"] .resume-tree-small {
  filter: invert(1) contrast(1.04);
  mix-blend-mode: normal;
  opacity: 0.9;
}

.resume-content {
  display: grid;
  gap: clamp(44px, 5vw, 68px);
  max-width: 700px;
  padding-top: 26px;
  color: var(--ink);
  font-family: var(--body);
}

.resume-section {
  display: grid;
  gap: 20px;
  padding-bottom: clamp(34px, 4vw, 48px);
  border-bottom: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
}

.resume-section:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.resume-section h2 {
  margin: 0;
  color: var(--ink);
  font-family: var(--stamp);
  font-size: clamp(21px, 1.65vw, 29px);
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: 0;
  text-transform: none;
}

.education-entry,
.experience-list {
  display: grid;
  gap: 20px;
}

.school-name,
.degree-line,
.experience-entry p,
.experience-entry span {
  margin: 0;
}

.school-name,
.degree-line strong,
.experience-entry strong {
  color: var(--ink);
  font-weight: 760;
}

.school-name,
.degree-line,
.experience-entry p {
  font-size: clamp(20px, 1.55vw, 23px);
  line-height: 1.3;
}

.degree-line {
  color: color-mix(in srgb, var(--ink) 72%, transparent);
}

.minor-divider {
  display: inline-block;
  padding: 0 8px;
  color: color-mix(in srgb, var(--ink) 38%, transparent);
}

.experience-list {
  gap: 25px;
}

.experience-entry {
  display: grid;
  gap: 5px;
}

.experience-entry > span {
  color: color-mix(in srgb, var(--ink) 48%, transparent);
  font-size: clamp(16px, 1.25vw, 18px);
  line-height: 1.45;
}

.company-cjk {
  display: inline-block;
  color: #050505;
  font-family: "FZKai-Z03S", "Kaiti SC", "STKaiti", "KaiTi", "楷体", serif;
  font-size: 0.9em;
  font-weight: 400;
  line-height: 1;
  vertical-align: 0.02em;
}

body[data-theme="dark"] .company-cjk {
  color: var(--ink);
}

.skill-list,
.toolbox-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  padding: 0;
  margin: 0;
  list-style: none;
}

.skill-list li {
  padding: 9px 14px 10px;
  border: 1px solid color-mix(in srgb, var(--ink) 12%, transparent);
  border-radius: 999px;
  background: transparent;
  color: color-mix(in srgb, var(--ink) 76%, transparent);
  font-size: clamp(16px, 1.2vw, 18px);
  line-height: 1;
}

.toolbox-grid {
  display: flex;
  flex-wrap: nowrap;
  gap: clamp(12px, 1.25vw, 18px);
  max-width: none;
  overflow-x: auto;
  padding-bottom: 4px;
  scrollbar-width: none;
}

.toolbox-grid::-webkit-scrollbar {
  display: none;
}

.tool-tile {
  position: relative;
  display: grid;
  place-items: center;
  width: 74px;
  height: 74px;
  min-height: 0;
  padding: 0;
  border-radius: 16px;
  background: var(--paper-deep);
  color: #111111;
  box-shadow: none;
  transition: transform 180ms ease;
}

.tool-tile img {
  width: 100%;
  height: 100%;
  border-radius: inherit;
  object-fit: cover;
}

.tool-tile.wordpress {
  background: #ffffff;
}

.tool-tile span {
  display: grid;
  place-items: center;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  background: transparent;
  font-family: "Avenir Next", Avenir, "Helvetica Neue", Arial, sans-serif;
  font-size: 27px;
  font-weight: 800;
  line-height: 1;
}

.tool-tile small {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.tool-tile:hover {
  transform: none;
  box-shadow: none;
}

.tool-tile.ae {
  background: #0d0757;
  color: #aaa5ff;
}

.tool-tile.ps {
  background: #061e32;
  color: #31a8ff;
}

.tool-tile.ai {
  background: #360700;
  color: #ff9a00;
}

body[data-theme="dark"] .tool-tile:not(.figma, .codex, .claude, .ae, .ps, .ai, .wordpress) {
  color: #111111;
}

.playground-page {
  display: grid;
  gap: clamp(86px, 10vw, 142px);
  width: min(100% - 112px, 1900px);
  margin: 0 auto;
  padding: 0 0 clamp(54px, 6vw, 88px);
  transform: translateY(clamp(-8px, -0.6vw, -2px));
}

.game-showcase {
  /* Desktop controls: the Game and both arrows are one centred unit.
     Change only --game-nav-gap to move both arrows closer/farther equally. */
  --game-frame-width: 820px;
  --game-nav-size: 56px;
  --game-nav-gap: 30px;
  display: grid;
  grid-template-columns: var(--game-nav-size) minmax(0, var(--game-frame-width)) var(--game-nav-size);
  align-items: center;
  width: min(
    100%,
    calc(
      var(--game-frame-width) + var(--game-nav-size) + var(--game-nav-size) +
      var(--game-nav-gap) + var(--game-nav-gap)
    )
  );
  margin-inline: auto;
  gap: var(--game-nav-gap);
}

.game-feature {
  display: grid;
  justify-items: center;
  gap: clamp(16px, 2vw, 24px);
  min-width: 0;
  color: var(--ink);
}

.game-feature:focus,
.game-feature:focus-visible,
.game-feature:active {
  outline: none;
  box-shadow: none;
}

.game-kicker {
  margin: 0;
  color: color-mix(in srgb, var(--ink) 44%, transparent);
  font-family: var(--hand);
  font-size: clamp(18px, 1.55vw, 24px);
  line-height: 1;
}

.game-frame {
  position: relative;
  width: 100%;
  max-width: var(--game-frame-width);
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border: 0;
  border-radius: 24px;
  background: color-mix(in srgb, var(--paper-deep) 78%, var(--paper));
  box-shadow: 0 24px 58px color-mix(in srgb, #000000 9%, transparent);
  transition:
    transform 220ms ease,
    box-shadow 220ms ease;
}

.game-frame::before {
  content: none;
}

.game-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  outline: 0;
  pointer-events: none;
}

.game-open-cue {
  position: absolute;
  right: 22px;
  bottom: 20px;
  z-index: 3;
  padding: 8px 14px;
  border: 2px solid var(--line);
  border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  color: var(--ink);
  font-family: var(--hand);
  font-size: 18px;
  line-height: 1;
  opacity: 0;
  transform: translateY(8px);
  transition:
    opacity 180ms ease,
    transform 180ms ease;
}

.game-feature:hover .game-frame {
  box-shadow: 0 28px 62px color-mix(in srgb, #000000 13%, transparent);
  transform: translateY(-3px);
}

.game-feature:hover .game-open-cue {
  opacity: 1;
  transform: translateY(0);
}

.game-feature h1 {
  margin: 0;
  color: color-mix(in srgb, var(--ink) 44%, transparent);
  font-family: var(--hand);
  font-size: clamp(18px, 1.55vw, 24px);
  font-weight: 400;
  line-height: 1;
  text-align: center;
}

body[data-theme="dark"] .game-kicker,
body[data-theme="dark"] .game-feature h1 {
  color: var(--ink);
}

.game-nav {
  display: grid;
  place-items: center;
  width: var(--game-nav-size);
  aspect-ratio: 1;
  padding: 0;
  border: 0;
  background: transparent;
  color: color-mix(in srgb, var(--ink) 28%, transparent);
}

.game-nav span {
  width: 12px;
  height: 12px;
  border-left: 3px solid currentColor;
  border-bottom: 3px solid currentColor;
  transform: rotate(45deg) translate(2px, -2px);
}

.game-nav-next span {
  transform: rotate(225deg) translate(2px, -2px);
}

.game-nav:disabled {
  cursor: default;
  opacity: 0.72;
}

/* Playground game navigation has exactly three responsive tiers.
   Edit the variables here instead of searching through unrelated media blocks. */
@media (min-width: 661px) and (max-width: 1080px) {
  .game-showcase {
    --game-nav-size: 48px;
    --game-nav-gap: 18px;
  }
}

@media (max-width: 660px) {
  .game-showcase {
    grid-template-columns: 1fr;
    --game-nav-gap: 14px;
  }

  .game-nav {
    display: none;
  }
}

.art-archive {
  display: grid;
  gap: 0;
}

.art-year-group {
  display: grid;
  align-content: center;
  justify-items: center;
  gap: clamp(28px, 4vw, 48px);
  min-height: 0;
  padding: clamp(70px, 9vw, 118px) 0;
  opacity: 0.36;
  transform: translateY(30px);
  transition:
    opacity 420ms ease,
    transform 520ms cubic-bezier(0.22, 1, 0.36, 1);
}

.art-year-group.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.art-year-group h2 {
  margin: 0;
  font-family: var(--stamp);
  font-size: clamp(21px, 1.65vw, 29px);
  font-weight: 400;
  line-height: 1;
  text-align: center;
  text-transform: none;
  transform: rotate(-2.4deg);
}

.art-row {
  --art-unit: clamp(190px, 13.6vw, 300px);
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  align-items: start;
  justify-self: center;
  gap: clamp(20px, 1.7vw, 34px);
  width: 100%;
  max-width: 1900px;
  margin-inline: auto;
  perspective: 1200px;
}

.art-card {
  --tilt: -2deg;
  --lift: 0px;
  --delay: 0ms;
  position: relative;
  flex: 0 1 var(--art-unit);
  width: var(--art-unit);
  min-width: 0;
  overflow: hidden;
  border: 0;
  border-radius: 18px;
  background: color-mix(in srgb, var(--paper-deep) 72%, var(--paper));
  transform: translateY(calc(var(--lift) + 26px)) rotate(calc(var(--tilt) - 1deg));
  transform-origin: 50% 72%;
  opacity: 0;
  will-change: transform, opacity;
}

.art-card.landscape {
  flex-basis: calc(var(--art-unit) * 1.28);
  width: calc(var(--art-unit) * 1.28);
}

.art-year-2026 .art-row {
  display: grid;
  grid-template-columns: repeat(4, max-content);
  justify-content: center;
  align-items: center;
  gap: clamp(20px, 1.7vw, 34px);
  width: 100%;
  max-width: 1900px;
}

.art-year-2026 .art-card {
  flex: none;
}

.art-year-2026 .art-card:nth-child(5) {
  grid-column: 2;
}

.art-year-2026 .art-card:nth-child(6) {
  grid-column: 3;
}

.art-year-2025 .art-row {
  gap: clamp(20px, 1.7vw, 34px);
  max-width: 1900px;
}

.art-year-2025 .art-card:nth-child(1) {
  --tilt: -0.7deg;
  --lift: 2px;
}

.art-year-2025 .art-card:nth-child(2) {
  --tilt: 0.6deg;
  --lift: 0px;
}

.art-card.filled {
  box-shadow: 0 18px 44px color-mix(in srgb, #000000 13%, transparent);
  transition:
    opacity 420ms ease,
    transform 560ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 220ms ease;
  transition-delay: var(--delay);
}

.art-year-group.is-visible .art-card {
  opacity: 1;
  transform: translateY(var(--lift)) rotate(var(--tilt));
}

.art-card.filled:hover {
  transform: translateY(calc(var(--lift) - 10px)) rotate(0deg) scale(1.018);
  box-shadow: 0 26px 54px color-mix(in srgb, #000000 17%, transparent);
  z-index: 2;
}

.art-card img {
  width: 100%;
  height: auto;
  object-fit: contain;
  transition: transform 420ms ease;
}

.art-card.filled:hover img {
  transform: scale(1.035);
}

.art-card.landscape img {
  aspect-ratio: auto;
  object-fit: contain;
}

.art-row .art-card:nth-child(1) {
  --tilt: -2.3deg;
  --lift: 6px;
  --delay: 0ms;
}

.art-row .art-card:nth-child(2) {
  --tilt: 1.4deg;
  --lift: -8px;
  --delay: 70ms;
}

.art-row .art-card:nth-child(3) {
  --tilt: -0.9deg;
  --lift: 4px;
  --delay: 140ms;
}

.art-row .art-card:nth-child(4) {
  --tilt: 2deg;
  --lift: -4px;
  --delay: 210ms;
}

.art-row .art-card:nth-child(5) {
  --tilt: -1.5deg;
  --lift: 8px;
  --delay: 280ms;
}

.art-row .art-card:nth-child(6) {
  --tilt: 1.1deg;
  --lift: -4px;
  --delay: 350ms;
}

.art-year-before h2 {
  transform: rotate(-0.8deg);
}

.art-year-before .art-card:nth-child(1) {
  --tilt: -0.7deg;
  --lift: 2px;
}

.art-year-before .art-card:nth-child(2) {
  --tilt: 0.6deg;
  --lift: 0px;
}

.art-year-before .art-card:nth-child(3) {
  --tilt: -0.5deg;
  --lift: 2px;
}

.art-year-before .art-card:nth-child(4) {
  --tilt: 0.7deg;
  --lift: 0px;
}

.art-year-before.is-visible .art-card {
  transform: translateY(var(--lift)) rotate(var(--tilt));
}

.art-card.placeholder::before {
  content: "";
  position: absolute;
  inset: 14px 12px;
  border: 2px solid color-mix(in srgb, var(--line) 24%, transparent);
  border-radius: 14px 18px 15px 19px;
  transform: rotate(var(--r, -1deg));
}

.art-card.placeholder:nth-child(2)::before {
  --r: 1.4deg;
}

.art-card.placeholder:nth-child(3)::before {
  --r: -0.8deg;
}

.art-card.placeholder:nth-child(4)::before {
  --r: 1deg;
}

body[data-theme="dark"] .game-frame {
  background: #060606;
  box-shadow: 0 26px 64px color-mix(in srgb, #000000 42%, transparent);
}

body[data-theme="dark"] .art-card {
  background: #0f0f0f;
}

@media (max-width: 1320px) {
  .art-row {
    --art-unit: clamp(142px, 11.8vw, 154px);
    gap: clamp(12px, 1.4vw, 18px);
  }

  .art-year-2026 .art-row {
    --art-unit: clamp(142px, 11.8vw, 154px);
    grid-template-columns: repeat(4, max-content);
    gap: clamp(12px, 1.4vw, 18px);
    width: min(100vw - 56px, 1100px);
    max-width: 1100px;
  }

  .art-year-2025 .art-row {
    --art-unit: clamp(142px, 11.8vw, 154px);
    gap: clamp(12px, 1.4vw, 18px);
    width: min(100vw - 56px, 980px);
    max-width: 980px;
  }
}

.art-lightbox {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: grid;
  place-items: center;
  padding: clamp(54px, 6vw, 76px) clamp(18px, 5vw, 72px);
  background: rgba(0, 0, 0, 0.78);
  color: #ffffff;
}

.art-lightbox[hidden] {
  display: none;
}

.art-lightbox-count,
.art-lightbox-close,
.art-lightbox-nav {
  position: fixed;
  z-index: 2;
  /* was var(--body-font) — a variable that never existed, so the intended
     Gaegu silently fell back to the inherited font (fixed 2026-07-15) */
  font-family: var(--body);
  color: #ffffff;
}

.art-lightbox-count {
  top: clamp(18px, 2vw, 34px);
  left: clamp(18px, 2.4vw, 34px);
  font-size: clamp(20px, 1.8vw, 28px);
  line-height: 1;
  opacity: 0.82;
}

.art-lightbox-close,
.art-lightbox-nav {
  appearance: none;
  border: 0;
  background: transparent;
  cursor: pointer;
}

.art-lightbox-close {
  top: clamp(12px, 1.8vw, 24px);
  right: clamp(18px, 2.4vw, 34px);
  padding: 8px;
  font-size: clamp(42px, 4vw, 58px);
  line-height: 0.8;
  opacity: 0.86;
}

.art-lightbox-nav {
  top: 50%;
  width: clamp(40px, 4vw, 62px);
  height: clamp(58px, 6vw, 86px);
  transform: translateY(-50%);
  opacity: 0.86;
}

.art-lightbox-prev {
  left: clamp(18px, 5vw, 92px);
}

.art-lightbox-next {
  right: clamp(18px, 5vw, 92px);
}

.art-lightbox-nav span {
  position: absolute;
  top: 50%;
  left: 50%;
  width: clamp(16px, 1.8vw, 26px);
  height: clamp(16px, 1.8vw, 26px);
  border-top: 5px solid currentColor;
  border-left: 5px solid currentColor;
  border-radius: 3px;
}

.art-lightbox-prev span {
  transform: translate(-35%, -50%) rotate(-45deg);
}

.art-lightbox-next span {
  transform: translate(-65%, -50%) rotate(135deg);
}

.art-lightbox-figure {
  display: grid;
  place-items: center;
  width: min(78vw, 980px);
  max-height: 82vh;
  margin: 0;
}

.art-lightbox-image {
  display: block;
  max-width: 100%;
  max-height: 82vh;
  border-radius: 3px;
  object-fit: contain;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.32);
}

body.is-lightbox-open {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .art-year-group,
  .art-card,
  .art-card.filled,
  .art-card img {
    transition: none;
  }

  .art-year-group,
  .art-card {
    opacity: 1;
  }

  .art-year-group {
    transform: none;
  }

  .art-card {
    transform: translateY(var(--lift, 0)) rotate(var(--tilt, 0deg));
  }
}

.play-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-auto-rows: 230px;
  gap: 22px;
  margin-top: 56px;
}

.play-card {
  position: relative;
  overflow: hidden;
  color: #fff;
  background: #111;
}

.play-card img,
.play-card video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.86;
  transition:
    transform 260ms ease,
    opacity 260ms ease;
}

.play-card:hover img,
.play-card:hover video {
  transform: scale(1.05);
  opacity: 0.68;
}

.play-card div {
  position: absolute;
  left: 18px;
  right: 18px;
  bottom: 16px;
  display: grid;
  gap: 2px;
  text-shadow: 0 2px 14px rgba(0, 0, 0, 0.55);
}

.play-card strong {
  font-family: var(--stamp);
  font-size: 23px;
  font-weight: 400;
  line-height: 1.05;
  text-transform: uppercase;
}

.play-card.tall {
  grid-row: span 2;
}

.play-card.wide {
  grid-column: span 2;
}

.error-page {
  min-height: calc(100vh - 170px);
  display: grid;
  place-items: center start;
}

.error-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 28px;
}

.about-page {
  --about-card-width: clamp(178px, 15vw, 244px);
  --about-grid-gap: clamp(24px, 2.4vw, 34px);
  display: grid;
  gap: clamp(72px, 8.8vw, 128px);
  width: min(100% - 112px, 1280px);
  margin: -30px auto 0;
  padding: 0 0 clamp(86px, 9vw, 126px);
}

.about-hero {
  display: grid;
  grid-template-columns: minmax(300px, 0.86fr) minmax(0, 1.14fr);
  align-items: center;
  column-gap: clamp(96px, 10vw, 152px);
  row-gap: clamp(34px, 5vw, 64px);
  /* The hero box starts ~144px down (sticky header 150 − the page's -30 margin
     + reveal settle). Centring content in a box of height 100vh − 2×144 would put
     the midline exactly on 50vh; we subtract a little less (248 instead of 288) so
     the group sits a touch BELOW dead centre — the look the design wants.
     (Replaces the old min(620px, …) cap, which drifted high on tall screens and
     low on short ones.) */
  min-height: calc(100vh - 248px);
}

.about-collage {
  position: relative;
  width: min(100%, 400px);
  aspect-ratio: 3 / 4;
  isolation: isolate;
}

.about-hero.is-visible .about-collage,
.about-hero.is-visible .about-intro-copy h1,
.about-hero.is-visible .about-intro-copy p,
.about-hero.is-visible .intro-facts {
  animation: about-soft-arrive 680ms cubic-bezier(0.22, 1, 0.36, 1) both;
}

.about-hero.is-visible .about-intro-copy h1 {
  animation-delay: 90ms;
}

.about-hero.is-visible .about-intro-copy p {
  animation-delay: 170ms;
}

.about-hero.is-visible .intro-facts {
  animation-delay: 250ms;
}

@keyframes about-soft-arrive {
  from {
    opacity: 0;
    transform: translateY(18px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.about-collage-card {
  position: absolute;
  margin: 0;
  overflow: hidden;
  border: 0;
  border-radius: 20px;
  background: var(--paper-deep);
  box-shadow: 0 18px 45px color-mix(in srgb, #000000 30%, transparent);
}

.about-collage-card img,
.photo-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.collage-back {
  z-index: 1;
  top: 6%;
  left: 6%;
  width: 66%;
  height: 74%;
  opacity: 0.94;
  transform: rotate(-7deg);
}

/* The front card and its peeking mascot live in ONE rotated wrapper, so the
   mascot carries the exact same tilt as the photo. The wrapper owns the old
   .collage-front position/transform; the card fills it. */
.collage-front-wrap {
  position: absolute;
  z-index: 3;
  right: 6%;
  bottom: 15%;
  width: 60%;
  height: 62%;
  transform: translateX(60px) rotate(7deg);
}

.collage-front {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  transform: none;
}

/* shy mascot peeking out from BEHIND the card's top edge (upper-right):
   painted under the card (z 1 < 2), ~2/3 of the dome above the edge, the
   lower 1/3 covered by the photo */
.collage-peek {
  position: absolute;
  top: 0;
  right: 12%;
  z-index: 1;
  width: 26%;
  transform: translateY(-66%);
  /* squash/stretch grows from the BOTTOM (the part tucked behind the card stays
     anchored), so the dome bulges UP out of the card rather than scaling in place */
  transform-origin: 50% 100%;
  pointer-events: none;
}

/* Entrance: the orange stays HIDDEN behind the photo while the photo + text
   arrive, then — a beat later — it squeezes up out of the card's top edge with a
   little volume: pressed flat → stretches as it rises → overshoots & squashes as
   it clears → settles. Layering is unchanged (z1 under the z2 photo), so its
   lower third stays tucked behind the card the whole time. NOT a fade/slide. */
.about-hero.is-visible .collage-peek {
  animation: collage-peek-emerge 900ms linear 450ms both;
}

@keyframes collage-peek-emerge {
  0% {
    transform: translateY(8%) scaleX(1.07) scaleY(0.64);
  }
  30% {
    transform: translateY(-55%) scaleX(0.95) scaleY(1.11);
  }
  52% {
    transform: translateY(-71%) scaleX(1.05) scaleY(0.93);
  }
  72% {
    transform: translateY(-63.5%) scaleX(0.985) scaleY(1.035);
  }
  88% {
    transform: translateY(-67%) scaleX(1.004) scaleY(0.992);
  }
  100% {
    transform: translateY(-66%) scaleX(1) scaleY(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .about-hero.is-visible .collage-peek {
    animation: none;
  }
}

.collage-peek img {
  display: block;
  width: 100%;
  height: auto;
  mix-blend-mode: multiply;
  user-select: none;
}

body[data-theme="dark"] .collage-peek img {
  mix-blend-mode: normal;
}

/* eyes: the marquee peeker's face geometry — row 30% of the body, sitting
   higher on the dome (top 52%), pupils 19% of the row */
.collage-peek-eyes {
  position: absolute;
  left: 50%;
  top: 52%;
  width: 30%;
  display: flex;
  justify-content: space-between;
  transform: translate(-50%, -50%);
}

.collage-peek-eye {
  width: 19%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #111111;
  animation: collage-peek-blink 4s ease-in-out infinite;
  transform-origin: 50% 50%;
}

/* one gentle blink per cycle — brief scaleY squeeze, otherwise still;
   both eyes share the exact same timing so the blink is synchronized */
@keyframes collage-peek-blink {
  0%,
  90.5%,
  95%,
  100% {
    transform: scaleY(1);
  }

  92.7% {
    transform: scaleY(0.08);
  }
}

.about-intro-copy {
  max-width: 720px;
}

.about-intro-copy h1,
.photo-wall h2 {
  margin: 0;
  color: var(--ink);
  font-family: var(--stamp);
  font-weight: 400;
  line-height: 1.16;
  letter-spacing: 0;
  text-transform: none;
}

/* Intro 标准 */
.about-intro-copy h1 {
  display: flex;
  flex-wrap: nowrap;
  align-items: baseline;
  gap: 12px;
  font-size: clamp(21px, 1.65vw, 29px);
  white-space: nowrap;
}

.about-intro-copy .title-main {
  flex: 0 1 auto;
}

.about-intro-copy .title-name {
  flex: 0 0 auto;
  font-family: "FZKai-Z03S", "Kaiti SC", "STKaiti", "KaiTi", "楷体", serif;
  font-size: 1em;
  line-height: 1.05;
}

.about-intro-copy h1 em {
  color: var(--ink);
  font-family: "Kaiti SC", "STKaiti", "KaiTi", "楷体", serif;
  font-size: 0.76em;
  font-style: normal;
  white-space: nowrap;
  transform: translateY(-0.18em);
}

.about-intro-copy p {
  margin: clamp(24px, 3vw, 36px) 0 30px;
  color: var(--ink);
  font-size: clamp(18px, 1.45vw, 23px);
  line-height: 1.46;
}

.marker-highlight {
  --mark-bg: rgba(121, 97, 166, 0.1);
  display: inline;
  margin-inline: -0.04em;
  padding: 0 0.16em 0.02em;
  border-radius: 7px 5px 9px 4px;
  background:
    linear-gradient(
      176deg,
      transparent 0 10%,
      var(--mark-bg) 10% 88%,
      transparent 88% 100%
    );
  color: var(--ink);
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

.highlight-dreamer {
  --mark-bg: rgba(121, 97, 166, 0.1);
}

.highlight-ai {
  --mark-bg: rgba(234, 74, 38, 0.1);
}

.highlight-alive {
  --mark-bg: rgba(134, 219, 255, 0.2);
}

.highlight-play {
  --mark-bg: rgba(233, 73, 39, 0.1);
}

.intro-facts {
  display: grid;
  gap: 12px;
  margin: 0;
  padding: 0;
  list-style: none;
  color: var(--ink);
  font-size: clamp(18px, 1.45vw, 23px);
}

.intro-facts li {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  line-height: 1.24;
}

.intro-facts strong {
  font-weight: 700;
}

.emoji {
  flex: 0 0 28px;
  width: 28px;
  line-height: 1.2;
}

.photo-wall {
  color: var(--ink);
}

.photo-wall-wrap {
  max-width: min(100%, calc(var(--about-card-width) * 5 + var(--about-grid-gap) * 4));
  margin: 0 auto;
}

.photo-wall h2 {
  margin-bottom: clamp(34px, 5vw, 62px);
  text-align: center;
  font-size: clamp(21px, 1.65vw, 29px);
}

.photo-grid {
  display: grid;
  justify-content: center;
  gap: var(--about-grid-gap);
}

.grid-four {
  grid-template-columns: repeat(4, minmax(0, var(--about-card-width)));
}

.grid-five {
  grid-template-columns: repeat(5, minmax(0, var(--about-card-width)));
}

.photo-card {
  position: relative;
  margin: 0;
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--line) 10%, transparent);
  border-radius: 20px;
  background: var(--surface);
  box-shadow:
    0 16px 40px color-mix(in srgb, #000000 14%, transparent),
    inset 0 0 0 1px color-mix(in srgb, #ffffff 55%, transparent);
  opacity: 1;
  transform: rotate(var(--r, 0deg));
  transform-origin: 50% 30%;
  transition:
    opacity 420ms ease,
    transform 220ms ease,
    box-shadow 220ms ease;
  will-change: transform;
}

.about-page .photo-wall {
  opacity: 0;
  transform: translateY(46px);
  transition:
    opacity 560ms ease,
    transform 680ms cubic-bezier(0.22, 1, 0.36, 1);
}

.about-page .photo-wall.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* The reveal (syncVisibleAboutPhotoWall) lights only the wall whose CENTRE is
   nearest the viewport centre. At the bottom of the page the viewport centre
   stops at docHeight − 50vh, so a final wall sitting closer to the end than
   that could never win and stayed invisible forever — Favorite Podcasts never
   faded in, at ANY breakpoint. Sizing the last wall so its centre lands exactly
   50vh above the document end fixes that AND parks it dead centre on the final
   screen: scrolling comes to rest with it centred and nothing empty below.
   A plain 100vh was one --about-tail too tall, which is what pushed it high up
   the screen with dead space underneath. --about-tail = everything already
   below its centre (main's bottom padding + the footer); retune if those
   change. The ≤1080 block overrides this for iPad/iPhone. */
.about-page .photo-wall:last-of-type {
  --about-tail: 244px;
  min-height: max(240px, calc(100vh - 2 * var(--about-tail)));
  min-height: max(240px, calc(100svh - 2 * var(--about-tail)));
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.about-page .photo-wall .photo-card {
  --photo-delay: 0ms;
  opacity: 0;
  transform: translateY(26px) rotate(var(--r, 0deg));
  transition:
    opacity 420ms ease,
    transform 560ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 220ms ease;
  transition-delay: var(--photo-delay);
}

.about-page .photo-wall.is-visible .photo-card {
  opacity: 1;
  transform: translateY(0) rotate(var(--r, 0deg));
}

.about-page .photo-wall.is-visible .photo-card:hover {
  transform: translateY(-6px) rotate(calc(var(--r, 0deg) * 0.4));
}

.about-page .photo-wall.is-visible .photo-card.no-caption:hover {
  transform: translateY(-6px) rotate(calc(var(--r, 0deg) * 0.4));
}

.about-page .photo-wall .photo-card:nth-child(2) {
  --photo-delay: 70ms;
}

.about-page .photo-wall .photo-card:nth-child(3) {
  --photo-delay: 140ms;
}

.about-page .photo-wall .photo-card:nth-child(4) {
  --photo-delay: 210ms;
}

.about-page .photo-wall .photo-card:nth-child(5) {
  --photo-delay: 280ms;
}

.about-page .photo-wall .photo-card:nth-child(6) {
  --photo-delay: 350ms;
}

.about-page .photo-wall .photo-card:nth-child(7) {
  --photo-delay: 420ms;
}

.about-page .photo-wall .photo-card:nth-child(8) {
  --photo-delay: 490ms;
}

.photo-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: 18px;
  pointer-events: none;
  background: linear-gradient(135deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0) 55%);
  mix-blend-mode: screen;
  opacity: 0.9;
}

.photo-card:hover {
  transform: translateY(-6px) rotate(calc(var(--r, 0deg) * 0.4));
  box-shadow:
    0 20px 30px color-mix(in srgb, #000000 18%, transparent),
    inset 0 0 0 1px color-mix(in srgb, #ffffff 60%, transparent);
}

.photo-card.no-caption:hover {
  transform: translateY(-6px);
}

.photo-media {
  aspect-ratio: 8 / 9;
  background: var(--paper-deep);
}

.poster-media {
  aspect-ratio: 7 / 9;
}

.square-media {
  aspect-ratio: 1 / 1;
}

.photo-card figcaption {
  position: relative;
  z-index: 2;
  padding: 12px 12px 14px;
  color: var(--ink);
  font-family: var(--hand);
  font-size: clamp(17px, 1.35vw, 21px);
  line-height: 1.28;
}

body[data-theme="dark"] .photo-card,
body[data-theme="dark"] .about-collage-card {
  border-color: color-mix(in srgb, var(--line) 22%, transparent);
  background: #111111;
  box-shadow:
    0 16px 40px color-mix(in srgb, #000000 68%, transparent),
    inset 0 0 0 1px color-mix(in srgb, #ffffff 12%, transparent);
}

@media (max-width: 1080px) {
  .site-header,
  .home-hero,
  .work-section,
  .story-strip,
  .about-teaser,
  .about-page,
  .page-shell,
  .playground-page,
  .resume-page {
    width: min(100% - 56px, 900px);
  }

  .site-header {
    --header-top-pad: 28px;
    --chain-gap: 0px;
    --chain-width: 28px;
    grid-template-columns: auto 1fr auto auto;
    align-items: start;
    gap: 18px;
    padding-top: 28px;
  }

  .site-header.is-scrolled {
    --header-top-pad: 28px;
    --chain-width: 28px;
    padding-top: 28px;
  }

  .menu-toggle {
    display: none;
  }

  .main-nav {
    pointer-events: auto;
    position: static;
    translate: none;
    display: flex;
    gap: clamp(14px, 4vw, 28px);
    max-width: calc(100vw - 56px);
    margin-top: 0;
    padding: 11px 22px;
    border-radius: 999px;
    border-color: rgba(255, 255, 255, 0.92);
    background: color-mix(in srgb, var(--paper) 92%, transparent);
    opacity: 1;
    transform: translateY(0) scale(1);
  }

  .site-header.is-scrolled .main-nav {
    gap: clamp(14px, 4vw, 28px);
    padding: 11px 22px;
    transform: translateY(0) scale(1);
  }

  .header-actions {
    display: inline-flex;
    justify-self: end;
    gap: 10px;
    padding-right: var(--chain-width);
  }

  .header-actions .connect-button {
    display: none;
  }

  .header-actions .pull-chain {
    height: 116px;
    width: var(--chain-width);
  }

  .site-header.is-scrolled .header-actions .pull-chain {
    height: 116px;
    width: var(--chain-width);
  }

  .site-header.is-scrolled .pull-chain {
    height: 116px;
    width: var(--chain-width);
  }

  .site-header.nav-open .main-nav a {
    padding: 8px 2px;
  }

  .home-hero,
  .about-layout,
  .resume-layout {
    grid-template-columns: 1fr;
  }

  .home-hero {
    grid-template-columns: minmax(0, 1.35fr) minmax(220px, 0.65fr);
    gap: 24px;
    padding-top: 14px;
    min-height: auto;
  }

  .stamp-title {
    gap: clamp(34px, 3.6vw, 40px);
  }

  .stamp-title span:last-child {
    white-space: nowrap;
  }

  .orange-stage {
    min-height: 310px;
  }

  .orange-body {
    width: min(100%, 360px);
  }

  .work-card-feature,
  .about-teaser {
    grid-template-columns: 1fr;
  }

  .work-row {
    display: contents;
  }

  .voderrn-media img {
    transform: translateY(20px) scale(1);
  }

  .work-card:hover .voderrn-media img {
    transform: translateY(20px) scale(1.035);
  }

  .work-card-small .work-media-full {
    height: auto;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    background: #fff3e4;
    border-radius: 22px;
  }

  .gown-media img,
  .work-card:hover .gown-media img {
    width: 100%;
    height: 100%;
    margin: 0;
    object-fit: cover;
    object-position: center;
    border-radius: inherit;
    transform: scale(1);
  }

  .work-card-copy h3 {
    white-space: nowrap;
  }

  .site-footer {
    width: 100%;
    max-width: none;
    min-height: auto;
    gap: clamp(30px, 5vw, 58px);
  }

  .story-strip,
  .project-grid,
  .value-list {
    grid-template-columns: 1fr;
  }

  .about-portrait {
    width: min(100%, 320px);
  }

  .play-grid {
    grid-template-columns: 1fr 1fr;
  }

  .game-frame {
    border-radius: 22px;
  }

  .art-row {
    --art-unit: clamp(112px, 13vw, 160px);
    gap: clamp(10px, 1.6vw, 18px);
    width: min(100vw - 56px, 980px);
  }

  .art-year-2026 .art-row,
  .art-year-2025 .art-row {
    --art-unit: clamp(112px, 13vw, 160px);
    gap: clamp(10px, 1.6vw, 18px);
    width: min(100vw - 56px, 980px);
    max-width: 980px;
  }

  .art-year-group {
    padding-block: clamp(58px, 8vw, 88px);
    text-align: center;
  }

  .about-hero {
    grid-template-columns: 1fr;
    row-gap: 46px;
    min-height: auto;
  }

  .about-collage {
    justify-self: center;
    width: min(100%, 300px);
  }

  .about-intro-copy {
    justify-self: center;
    text-align: center;
  }

  .about-intro-copy h1 {
    justify-content: center;
  }

  /* iPad/tablet: the intro paragraph reads as a tidy justified block —
     both edges flush (两端对齐), last/short line centred (局中) so it stays
     balanced under the centred title. Only the <p>; h1 and facts unchanged. */
  .about-intro-copy p {
    text-align: justify;
    text-align-last: center;
  }

  .intro-facts {
    width: fit-content;
    margin-inline: auto;
    text-align: left;
  }

  /* Tablet: keep a composed multi-column wall (fluid tracks) instead of two
     narrow fixed-width columns swimming in the container. */
  .grid-four,
  .grid-five {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .grid-four.poster-grid,
  .grid-four.square-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .resume-page {
    grid-template-columns: minmax(300px, 0.47fr) minmax(390px, 1fr);
    gap: clamp(108px, 11.5vw, 148px);
  }

  .resume-tree-large {
    /* tucked ~25px further left so its bare trunk clears the enlarged small
       tree at these narrower widths (panel is only ~300px here) */
    left: clamp(-103px, calc(-5.8vw - 25px), -73px);
  }

  .resume-tree-small {
    /* +25px right to pair with the large tree's left nudge — separates the two
       trunks without pushing the tree into the résumé column */
    right: clamp(-121px, calc(-7vw - 25px), -87px);
    /* grass-line compensation at this size (see base rule) */
    bottom: -5px;
    /* ×2.36 of the old clamp — same scale factor as the label-size match */
    width: min(30.7vw, 321px);
  }

  .resume-content {
    max-width: none;
  }

  .resume-tree-panel {
    min-height: 1160px;
  }

  .tree-sticky {
    top: 112px;
  }

  .toolbox-grid {
    gap: 12px;
  }
}

/* iPad only — one clear control for the About introduction's reading width.
   Make this number smaller for a narrower paragraph, or larger for a wider one. */
@media (min-width: 661px) and (max-width: 1080px) {
  /* The tablet header uses a 1fr centre column. Without an explicit
     shrink-to-content width, the nav pill stretches across that whole column. */
  .main-nav {
    justify-self: center;
    width: fit-content;
    max-width: calc(100vw - 56px);
  }

  /* Change only this value to move the About introduction closer to/further
     from the collage: smaller = closer, larger = further apart. */
  .about-hero {
    --about-hero-ipad-gap: 18px;
    row-gap: var(--about-hero-ipad-gap);
  }

  .about-intro-copy {
    --about-intro-ipad-width: 680px;
    width: min(100%, var(--about-intro-ipad-width));
  }

  /* Only these requested iPad walls use wider rows. All other walls above
     continue to inherit the tablet's normal three-column layout. */
  .photo-wall-moments .grid-four {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .photo-wall[aria-labelledby="passion-title"] .grid-five,
  .photo-wall[aria-labelledby="screen-title"] .grid-five {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }

  /* Books and Podcasts keep four centred covers, but each cover uses exactly
     the same width as a Screen Picks cover (a five-column row). Their own
     media aspect ratios — book poster and square podcast cover — stay intact. */
  .photo-wall[aria-labelledby="books-title"] .grid-four,
  .photo-wall[aria-labelledby="podcasts-title"] .grid-four {
    --about-ipad-screen-card-width: calc(
      (100% - var(--about-grid-gap) - var(--about-grid-gap) - var(--about-grid-gap) - var(--about-grid-gap)) / 5
    );
    grid-template-columns: repeat(4, minmax(0, var(--about-ipad-screen-card-width)));
  }

  /* iPad Playground: same card width and 24px rhythm as About's five-up
     Screen Picks row. Artwork retains its natural image proportions. */
  .playground-page {
    --art-ipad-gap: clamp(24px, 2.4vw, 34px);
  }

  .art-year-2026 .art-row,
  .art-year-2025 .art-row,
  .art-year-2024 .art-row,
  .art-year-before .art-row {
    display: grid;
    justify-content: center;
    align-items: start;
    gap: var(--art-ipad-gap);
    width: 100%;
    max-width: none;
  }

  .art-year-2026 .art-row {
    grid-template-columns: repeat(4, minmax(0, calc(
      (100% - var(--art-ipad-gap) - var(--art-ipad-gap) - var(--art-ipad-gap) - var(--art-ipad-gap)) / 5
    )));
  }

  /* 2026: balance the two portrait pieces at the outer edges, with the
     landscapes forming a calm centre band. The second row stays centred. */
  .art-year-2026 .art-row .art-card {
    --lift: 0px;
  }

  .art-year-2026 .art-card:nth-child(5) {
    grid-row: 1;
    grid-column: 4;
  }

  .art-year-2026 .art-card:nth-child(4) {
    grid-row: 2;
    grid-column: 2;
  }

  .art-year-2026 .art-card:nth-child(6) {
    grid-row: 2;
    grid-column: 3;
  }

  .art-year-2025 .art-row {
    grid-template-columns: repeat(2, minmax(0, calc(
      (100% - var(--art-ipad-gap) - var(--art-ipad-gap) - var(--art-ipad-gap) - var(--art-ipad-gap)) / 5
    )));
  }

  .art-year-2024 .art-row {
    grid-template-columns: repeat(5, minmax(0, 1fr));
  }

  .art-year-before .art-row {
    grid-template-columns: repeat(4, minmax(0, calc(
      (100% - var(--art-ipad-gap) - var(--art-ipad-gap) - var(--art-ipad-gap) - var(--art-ipad-gap)) / 5
    )));
  }

  .art-year-2026 .art-card,
  .art-year-2025 .art-card,
  .art-year-2024 .art-card,
  .art-year-before .art-card,
  .art-year-2026 .art-card.landscape,
  .art-year-2025 .art-card.landscape,
  .art-year-2024 .art-card.landscape,
  .art-year-before .art-card.landscape {
    width: 100%;
    flex: none;
  }
}

@media (max-width: 820px) {
  .resume-page {
    grid-template-columns: 1fr;
    max-width: 660px;
    min-height: 0;
    padding-top: 4px;
  }

  .resume-tree-panel {
    display: none;
  }

  .resume-content {
    padding-top: 0;
    max-width: 100%;
  }

  .home-hero {
    grid-template-columns: 1fr;
    gap: 10px;
  }

  .orange-stage {
    min-height: 275px;
    order: 2;
  }

  .hero-copy {
    order: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-self: center;
    width: 100%;
    text-align: center;
  }

  .hero-copy .stamp-title {
    gap: clamp(30px, 4vw, 38px);
    justify-items: center;
  }

  .hero-copy .stamp-title span:last-child {
    white-space: normal;
  }

  .hero-copy .role-line,
  .hero-copy .student-line {
    text-align: center;
  }

  .work-card-copy {
    display: grid;
    justify-items: center;
    text-align: center;
  }

  .about-hero {
    gap: 42px;
  }

  .home-intro-screen {
    padding: 104px 22px;
  }

  .intro-line {
    width: min(92vw, 560px);
    font-size: clamp(20px, 5.6vw, 27px);
  }

  #intro-2 .intro-line,
  #intro-3 .intro-line {
    font-size: clamp(18px, 5vw, 24px);
  }

  /* (the pre-canvas ≤820 collage layout that lived here was removed
     2026-07-15: every rule was fully shadowed by the aspect-locked canvas
     tiers, which use higher-specificity body[data-page="home"] selectors.
     Only the vibe-note size below was still live.) */
  .home-vibe-note {
    font-size: 14px;
  }
}

/* This narrower iPad tier otherwise overrides the row-gap above with
   `gap: 42px`; keep it connected to the same editable iPad variable. */
@media (min-width: 661px) and (max-width: 820px) {
  .about-hero {
    gap: var(--about-hero-ipad-gap);
  }
}

@media (max-width: 660px) {
  body {
    font-size: 17px;
  }

  .site-header,
  .home-hero,
  .work-section,
  .story-strip,
  .about-teaser,
  .about-page,
  .page-shell,
  .playground-page,
  .resume-page {
    width: min(100% - 32px, 560px);
  }

  .site-footer {
    --footer-inline-pad: 36px;
    width: 100%;
    max-width: none;
    min-height: 0;
    margin: 0;
    gap: 12px;
    padding: 28px var(--footer-inline-pad) 22px;
  }

  .footer-socials {
    gap: 14px;
  }

  .footer-socials a {
    gap: 8px;
  }

  .site-footer::before {
    left: var(--footer-inline-pad);
    right: var(--footer-inline-pad);
  }

  .site-header {
    --header-top-pad: 22px;
    --chain-width: 24px;
    grid-template-columns: auto 1fr auto auto;
    align-items: start;
    gap: 14px;
    padding: 22px 0 38px;
  }

  .brand-mark {
    order: 1;
  }

  .header-actions {
    order: 4;
  }

  .site-header.is-scrolled {
    --header-top-pad: 22px;
    --chain-width: 24px;
    padding: 22px 0 38px;
  }

  .menu-toggle {
    position: relative;
    z-index: 72;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    justify-self: end;
    order: 3;
    width: 34px;
    height: 34px;
    min-height: 34px;
    padding: 0;
    overflow: hidden;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: var(--ink);
    font: 0 / 0 a;
  }

  body.nav-lock {
    position: fixed;
    inset: 0;
    width: 100%;
    overflow: hidden;
    touch-action: none;
  }

  .menu-toggle::before,
  .menu-toggle::after {
    content: "";
    position: absolute;
    left: 9px;
    width: 16px;
    height: 2px;
    border-radius: 999px;
    background: currentColor;
    transition:
      top 180ms ease,
      transform 180ms ease,
      opacity 180ms ease;
  }

  .menu-toggle::before {
    top: 12px;
    box-shadow: 0 5px 0 currentColor;
  }

  .menu-toggle::after {
    top: 22px;
  }

  .main-nav {
    pointer-events: none;
    position: fixed;
    z-index: 68;
    inset: 0;
    translate: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: clamp(22px, 6.4vw, 30px);
    width: 100%;
    height: 100dvh;
    min-height: 100vh;
    max-width: none;
    padding: 104px 28px max(28px, env(safe-area-inset-bottom));
    border: 0;
    border-radius: 0;
    /* Theme-aware: this full-screen sheet had a hardcoded #ffffff, so in dark
       mode it stayed white while its links took the dark --ink (#f9f3eb) —
       cream on white, i.e. an invisible menu. --paper is #ffffff in light, so
       light mode renders exactly as before. */
    background: var(--paper);
    font-family: var(--hand);
    font-size: clamp(20px, 5.6vw, 25px);
    line-height: 1.05;
    opacity: 0;
    transform: translateY(-10px);
    overflow: hidden;
    transition:
      opacity 180ms ease,
      transform 180ms ease;
  }

  .site-header.is-scrolled .main-nav {
    gap: clamp(22px, 6.4vw, 30px);
    padding: 104px 28px max(28px, env(safe-area-inset-bottom));
    transform: translateY(-10px);
  }

  .main-nav .nav-connect {
    display: inline-block;
  }

  .main-nav a {
    padding: 0;
  }

  .main-nav > a {
    flex: 0 0 auto;
  }

  .menu-socials {
    display: flex;
    justify-content: center;
    gap: clamp(24px, 9vw, 44px);
    width: 100%;
    margin-top: clamp(48px, 14vh, 96px);
    color: #8b8b8b;
    font-size: clamp(14px, 3.8vw, 17px);
    line-height: 1;
  }

  .menu-socials a {
    color: inherit;
  }

  .main-nav a::after {
    content: none;
  }

  .main-nav a .nav-link-label {
    transition: none;
  }

  .main-nav a:hover .nav-link-label,
  .main-nav a:focus-visible .nav-link-label {
    transform: none;
  }

  .site-header.nav-open {
    z-index: 75;
  }

  .site-header.nav-open .brand-mark {
    position: fixed;
    z-index: 72;
    top: 22px;
    left: 22px;
  }

  .site-header.nav-open .main-nav {
    pointer-events: auto;
    opacity: 1;
    transform: translateY(0);
  }

  .site-header.nav-open .menu-toggle {
    position: fixed;
    top: 22px;
    right: 22px;
  }

  .site-header.nav-open .menu-toggle::before {
    top: 16px;
    box-shadow: none;
    transform: rotate(45deg);
  }

  .site-header.nav-open .menu-toggle::after {
    top: 16px;
    transform: rotate(-45deg);
  }

  .site-header.nav-open .header-actions {
    visibility: hidden;
  }

  .header-actions .pull-chain {
    height: 96px;
    width: var(--chain-width);
  }

  .site-header.is-scrolled .header-actions .pull-chain {
    height: 96px;
    width: var(--chain-width);
  }

  .site-header.is-scrolled .pull-chain {
    height: 96px;
    width: var(--chain-width);
  }

  .brand-mark {
    z-index: 72;
    width: 58px;
    height: 46px;
  }

  .site-logo {
    width: 54px;
    height: 54px;
  }

  :root {
    --home-title-size: clamp(18px, 5vw, 28px);
  }

  .stamp-title {
    gap: clamp(24px, 6vw, 32px);
    font-size: var(--home-title-size);
    overflow-wrap: normal;
  }

  .stamp-title span {
    width: max-content;
    max-width: 100%;
    text-wrap: balance;
  }

  .stamp-title mark {
    width: max-content;
    max-width: 100%;
    margin-left: 8px;
    margin-top: 0;
    padding-inline: 9px;
  }

  .stamp-title em {
    white-space: nowrap;
  }

  .role-line {
    margin-bottom: 34px;
  }

  .orange-stage {
    min-height: 245px;
  }

  .orange-body {
    width: min(100%, 340px);
  }

  .work-section {
    width: min(100% - 56px, 900px);
    padding-top: 78px;
  }

  .work-board {
    grid-template-columns: 1fr;
    gap: clamp(54px, 12vw, 78px);
    transform: none;
  }

  .work-card:nth-child(even) {
    margin-top: 0;
  }

  .section-heading {
    gap: 12px;
  }

  .work-card-copy {
    display: grid;
    justify-items: start;
    padding: 0;
    text-align: left;
  }

  .work-card-feature,
  .work-card-wide,
  .work-card-small {
    min-height: 0;
  }

  .work-row {
    display: contents;
  }

  .work-media-full {
    height: auto;
    aspect-ratio: 16 / 10;
    border-radius: 18px;
  }

  .work-card-small .work-media-full {
    height: auto;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    background: #fff3e4;
    border-radius: 18px;
  }

  .work-card-feature .work-media-full {
    height: auto;
  }

  .work-card-copy h3 {
    max-width: none;
    font-size: clamp(17px, 4.8vw, 22px);
    line-height: 1.12;
    text-align: left;
    white-space: nowrap;
  }

  .work-card-copy span {
    margin-bottom: 9px;
  }

  .voderrn-media img,
  .work-card:hover .voderrn-media img {
    object-fit: contain;
    object-position: center;
    transform: translateY(8px) scale(0.96);
  }

  .gown-media img,
  .work-card:hover .gown-media img {
    width: 100%;
    max-width: none;
    height: 100%;
    margin: 0;
    object-fit: contain;
    object-position: center bottom;
    border-radius: inherit;
    transform: scale(1);
  }

  .floating-preview {
    position: relative;
    top: auto;
    right: auto;
    width: 150px;
    margin: 0 24px 24px auto;
  }

  .page-intro h1 {
    font-size: clamp(26px, 9vw, 40px);
  }

  .play-grid {
    grid-template-columns: 1fr;
    grid-auto-rows: 240px;
  }

  .playground-page {
    gap: 66px;
    padding: 2px 0 54px;
  }

  .game-feature {
    gap: 14px;
  }

  .game-frame {
    aspect-ratio: 4 / 3;
    border-radius: 18px;
  }

  .game-frame::before {
    content: none;
  }

  .game-open-cue {
    display: none;
  }

  .art-archive {
    gap: 8px;
  }

  /* Phone: let the art wall wrap into a centered 3-up mosaic with tappable
     thumbnails instead of six tiny cards squeezed onto one line. */
  .art-row {
    --art-unit: clamp(88px, 26vw, 108px);
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    row-gap: 12px;
    gap: 12px 10px;
    width: 100%;
  }

  .art-year-2026 .art-row,
  .art-year-2025 .art-row {
    --art-unit: clamp(88px, 26vw, 108px);
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 12px 10px;
    width: 100%;
    max-width: 100%;
  }

  .art-year-2026 .art-card,
  .art-year-2025 .art-card {
    grid-column: auto;
    flex: 0 1 var(--art-unit);
  }

  .art-year-group {
    padding: 30px 0 36px;
    gap: 22px;
  }

  .art-year-group h2 {
    transform: rotate(-1.4deg);
  }

  .art-card.landscape {
    flex-basis: calc(var(--art-unit) * 1.12);
  }

  /* iPhone artwork archive — use the same 12px rhythm and three-up card width
     as the About photo wall. Landscape artwork keeps its own aspect ratio, but
     no longer claims extra horizontal space. */
  .art-year-2026 .art-row,
  .art-year-2025 .art-row,
  .art-year-2024 .art-row {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 14px 12px;
    width: 100%;
  }

  .art-year-2026 .art-card,
  .art-year-2025 .art-card,
  .art-year-2024 .art-card,
  .art-year-2026 .art-card.landscape,
  .art-year-2025 .art-card.landscape,
  .art-year-2024 .art-card.landscape {
    grid-column: span 2;
    width: 100%;
    flex: none;
  }

  /* Cancel the desktop-only centring slots so 2026 flows as a clean 3 + 3. */
  .art-year-2026 .art-card:nth-child(5),
  .art-year-2026 .art-card:nth-child(6) {
    grid-column: span 2;
  }

  /* 2025 has two pieces: keep the pair centred on its single row. */
  .art-year-2025 .art-card:nth-child(1) {
    grid-column: 2 / span 2;
  }

  .art-year-2025 .art-card:nth-child(2) {
    grid-column: 4 / span 2;
  }

  /* 2024 has five pieces: its final pair occupies the middle four tracks. */
  .art-year-2024 .art-card:nth-child(4) {
    grid-row: 2;
    grid-column: 2 / span 2;
  }

  .art-year-2024 .art-card:nth-child(5) {
    grid-row: 2;
    grid-column: 4 / span 2;
  }

  .art-year-before .art-row {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 14px 12px;
    width: 100%;
  }

  .art-year-before .art-card,
  .art-year-before .art-card.landscape {
    grid-column: span 2;
    width: 100%;
    flex: none;
  }

  /* Before 2024: move the final photo into the upper-left gap. */
  .art-year-before .art-card:nth-child(4) {
    grid-row: 1;
    grid-column: 2 / span 2;
  }

  .art-year-before .art-card:nth-child(2) {
    grid-row: 1;
    grid-column: 4 / span 2;
  }

  .art-year-before .art-card:nth-child(3) {
    grid-row: 2;
    grid-column: 2 / span 2;
  }

  .art-year-before .art-card:nth-child(1) {
    grid-row: 2;
    grid-column: 4 / span 2;
  }

  .art-card.filled:hover {
    transform: translateY(var(--lift)) rotate(var(--tilt));
  }

  .play-card.tall,
  .play-card.wide {
    grid-row: span 1;
    grid-column: span 1;
  }

  .timeline article {
    grid-template-columns: 1fr;
  }

  .resume-page {
    padding: 6px 0 54px;
  }

  .resume-content {
    gap: 38px;
  }

  .resume-section {
    gap: 18px;
    padding-bottom: 34px;
  }

  .resume-section h2 {
    font-size: clamp(21px, 1.65vw, 29px);
  }

  .school-name,
  .degree-line,
  .experience-entry p {
    font-size: 19px;
  }

  .degree-line {
    display: grid;
    gap: 3px;
  }

  .minor-divider {
    display: none;
  }

  .experience-list {
    gap: 21px;
  }

  .skill-list {
    gap: 9px;
  }

  .skill-list li {
    font-size: 16px;
  }

  .toolbox-grid {
    gap: 10px;
  }

  .tool-tile {
    width: 66px;
    height: 66px;
    min-height: 0;
    border-radius: 14px;
  }

  .tool-tile span {
    font-size: 24px;
  }

  .about-page {
    --about-card-width: min(100%, 260px);
    --about-grid-gap: 18px;
    gap: 76px;
    padding-top: 12px;
  }

  .about-collage {
    aspect-ratio: 3 / 4;
    width: min(calc(100% - 28px), 280px);
  }

  .collage-back {
    top: 4%;
    left: 2%;
    width: 72%;
    height: 76%;
  }

  .collage-front-wrap {
    right: 0;
    bottom: 15%;
    width: 60%;
    height: 62%;
    transform: rotate(7deg);
  }

  .about-intro-copy h1 {
    flex-wrap: wrap;
    gap: 8px 12px;
    font-size: clamp(18px, 5vw, 24px);
    white-space: normal;
  }

  .about-intro-copy .title-name {
    font-size: 0.86em;
  }

  .about-intro-copy p {
    font-size: clamp(18px, 1.45vw, 23px);
    text-align: left;
  }

  .intro-facts {
    font-size: clamp(18px, 1.45vw, 23px);
  }

  /* Phone: a compact two-column polaroid mosaic — keeps the scattered-wall
     personality instead of one giant card per row. */
  .grid-five,
  .grid-four {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .photo-grid {
    gap: 14px 12px;
  }

  /* slight vertical stagger so the wall still reads hand-placed */
  .photo-grid .photo-card:nth-child(even),
  .about-page .photo-wall.is-visible .photo-card:nth-child(even) {
    transform: translateY(10px) rotate(var(--r, 0deg));
  }

  .photo-card figcaption {
    padding: 8px 10px 10px;
    font-size: clamp(13px, 3.6vw, 15px);
  }

  .photo-wall h2 {
    margin-bottom: 32px;
    font-size: clamp(18px, 5vw, 24px);
  }

  /* Phone: one section per screen. The walls collapse to ~0.2 of a screen here
     (a title plus one short row), so several shared the viewport at once and the
     "nearest centre wins" reveal thrashed — Favorite Books flashed in and out in
     ~320px of scroll and Podcasts never got its turn. A full screen each gives
     every wall its own beat, matching the one-at-a-time rhythm of the wider
     layouts. svh keeps it steady while the mobile URL bar collapses. */
  .about-page .photo-wall:not([aria-labelledby="books-title"]):not([aria-labelledby="podcasts-title"]) {
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}

/* ---- iPad + iPhone: Favorite Books / Favorite Podcasts follow the PLAYGROUND
   art-year-group standard instead of the About wall standard. Playground rests
   its groups at 0.36 rather than 0, so the heading stays faintly present and
   the section eases up to full instead of appearing out of nothing, and it
   spaces them with padding rather than a screen-tall box — which is why its
   rhythm reads right where the About versions felt mistimed and misplaced.
   Values mirror .art-year-group exactly (0.36 / 30px / 420ms / 520ms, and the
   same padding per tier). The inner .photo-card timing already matches
   .art-card.filled (420ms / 560ms), so only the section level changes here.
   Placed after the phone block so it wins the equal-specificity ties. ---- */
@media (max-width: 1080px) {
  .about-page .photo-wall[aria-labelledby="books-title"],
  .about-page .photo-wall[aria-labelledby="podcasts-title"] {
    display: block;
    min-height: 0;
    padding-block: clamp(58px, 8vw, 88px);
    opacity: 0.36;
    transform: translateY(30px);
    transition:
      opacity 420ms ease,
      transform 520ms cubic-bezier(0.22, 1, 0.36, 1);
  }

  .about-page .photo-wall[aria-labelledby="books-title"].is-visible,
  .about-page .photo-wall[aria-labelledby="podcasts-title"].is-visible {
    opacity: 1;
    transform: translateY(0);
  }

  /* Podcasts is the last thing on the page. Compact padding alone leaves its
     centre below the viewport centre at max scroll, so it could never win the
     "nearest centre" reveal — it would sit at the 0.36 rest state forever with
     its covers (which rest at 0) never fading in at all. This runway after it
     lets the viewport centre reach it, so it lights up AND comes to rest dead
     centre. --about-runway-trim = everything already below its centre (its own
     lower half + main's bottom padding + the footer); retune if those change. */
  .about-page .photo-wall[aria-labelledby="podcasts-title"] {
    --about-runway-trim: 325px;
    margin-bottom: max(0px, calc(50svh - var(--about-runway-trim)));
  }
}

@media (max-width: 660px) {
  /* phone tier of the same standard — .art-year-group's own phone padding */
  .about-page .photo-wall[aria-labelledby="books-title"],
  .about-page .photo-wall[aria-labelledby="podcasts-title"] {
    padding: 30px 0 36px;
  }

  /* the phone tier sits lower (smaller padding + shorter rows), so less of the
     page already falls below the podcasts centre — trim the runway to match */
  .about-page .photo-wall[aria-labelledby="podcasts-title"] {
    --about-runway-trim: 263px;
  }

  /* Books and Podcasts are compact four-cover rows on iPhone. This must come
     after the generic phone .grid-four two-column fallback. */
  .photo-wall[aria-labelledby="books-title"] .grid-four,
  .photo-wall[aria-labelledby="podcasts-title"] .grid-four {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* iPhone-only editorial grids. Six internal tracks make a standard card span
   two tracks: full rows show three cards, while final pairs sit centred. */
@media (max-width: 660px) {
  /* Those Moments: 3 + 3 + centred 2. */
  .photo-wall-moments .grid-four {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }

  .photo-wall-moments .photo-card {
    grid-column: span 2;
  }

  .photo-wall-moments .photo-card:nth-child(7) {
    grid-row: 3;
    grid-column: 2 / span 2;
  }

  .photo-wall-moments .photo-card:nth-child(8) {
    grid-row: 3;
    grid-column: 4 / span 2;
  }

  /* My Passion + Screen Picks: 3 + centred 2. */
  .photo-wall[aria-labelledby="passion-title"] .grid-five,
  .photo-wall[aria-labelledby="screen-title"] .grid-five {
    grid-template-columns: repeat(6, minmax(0, 1fr));
  }

  .photo-wall[aria-labelledby="passion-title"] .photo-card,
  .photo-wall[aria-labelledby="screen-title"] .photo-card {
    grid-column: span 2;
  }

  .photo-wall[aria-labelledby="passion-title"] .photo-card:nth-child(4),
  .photo-wall[aria-labelledby="screen-title"] .photo-card:nth-child(4) {
    grid-row: 2;
    grid-column: 2 / span 2;
  }

  .photo-wall[aria-labelledby="passion-title"] .photo-card:nth-child(5),
  .photo-wall[aria-labelledby="screen-title"] .photo-card:nth-child(5) {
    grid-row: 2;
    grid-column: 4 / span 2;
  }
}

@media (max-width: 380px) {
  :root {
    --home-title-size: clamp(18px, 5vw, 22px);
  }

  .stamp-title {
    gap: clamp(20px, 6vw, 26px);
  }

  .work-card-copy h3 {
    font-size: clamp(16px, 4.6vw, 20px);
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }

  .reveal-section {
    opacity: 1;
    transform: none;
  }

  .about-page .photo-wall {
    opacity: 1;
    transform: none;
  }

  .about-page .photo-wall .photo-card {
    opacity: 1;
    transform: rotate(var(--r, 0deg));
  }

  .orange-body {
    transform: none;
  }
}



/* Home gallery position fix only */
body[data-page="home"] .home-gallery-screen {
  position: fixed;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 0 clamp(34px, 7vw, 140px);
  background: #ffffff;
}

body[data-page="home"] .home-vibe-note {
  margin-top: -8px;
}

/* Desktop collage layout (consolidated 2026-07-15 from two historical blocks —
   "position fix" + "photo layout cleanup" — that redefined every selector; the
   effective values below are byte-identical to what the cascade produced). */

body[data-page="home"] .home-gallery-stage {
  position: relative;
  display: grid;
  place-items: center;
  width: min(1500px, 100%);
  height: min(610px, calc(100svh - 190px));
  min-height: 520px;
  margin-top: 0;
  transform: translateY(-4vh);
}

/* Keep orange centered and above the bottom photo */
body[data-page="home"] .home-orange-wrap {
  z-index: 6;
  transform: translateY(18px);
}

/* Top left vertical photo */ /*山*/
body[data-page="home"] .home-photo-a {
  top: 6%;
  left: 17%;
  width: clamp(118px, 10vw, 170px);
  aspect-ratio: 4 / 5;
  height: auto;
}

/* Top right wide photo */ /*坎昆*/
body[data-page="home"] .home-photo-b {
  top: 13%;
  right: 12%;
  width: clamp(200px, 16.5vw, 290px);
  aspect-ratio: 16 / 9;
  height: auto;
}

/* Middle right wide photo */
body[data-page="home"] .home-photo-c {
  top: 50%;
  right: 6%;
  width: clamp(205px, 17vw, 300px);
  aspect-ratio: 16 / 9;
  height: auto;
}

/* Middle left wide photo */ /*我-海*/
body[data-page="home"] .home-photo-d {
  top: 47%;
  left: 7%;
  width: clamp(170px, 15vw, 250px);
  aspect-ratio: 4 / 2.5;
  height: auto;
}

/* Small bottom-left photo, moved away from main bottom photo */ /*欣欣*/
body[data-page="home"] .home-photo-e {
  top: 82%;
  left: 15%;
  width: clamp(82px, 7vw, 112px);
  height: clamp(58px, 5vw, 82px);
}

/* Bottom center main photo (北海 video): smaller and lower, clear of the
   orange's note. (A byte-identical earlier duplicate of this rule was removed
   in the 2026-07-15 cleanup; its transform came from the .home-photo base.) */
body[data-page="home"] .home-photo-main {
  left: 50%;
  bottom: -15%;
  width: clamp(220px, 20vw, 340px);
  aspect-ratio: 16 / 9;
  height: auto;
  translate: -50% 0;
  overflow: hidden;
  border-radius: 8px;
  z-index: 2;
}

body[data-page="home"] .home-photo-main::after {
  display: none;
}

body[data-page="home"] .home-photo-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

body[data-theme="dark"][data-page="home"] .home-gallery-screen {
  background: #000000;
  color: #ffffff;
}

body[data-theme="dark"][data-page="home"] .home-gallery-stage {
  color: #ffffff;
}

body[data-theme="dark"][data-page="home"] .home-photo {
  border: 1px solid rgba(255, 255, 255, 0.08);
  background:
    repeating-linear-gradient(90deg, #171717 0 22px, #101010 22px 44px),
    #121212;
}

body[data-theme="dark"][data-page="home"] .home-photo::after {
  color: rgba(255, 255, 255, 0.46);
}

body[data-theme="dark"][data-page="home"] .home-photo-main {
  border-color: rgba(255, 255, 255, 0.12);
  background: #050505;
}

body[data-theme="dark"][data-page="home"] .home-vibe-note {
  color: #ffffff;
}

/* Landing-position probe: while the body carries this class, the opening JS is
   flipping layout classes for a single synchronous measurement — nothing may
   animate from the round trip. */
body.home-probe,
body.home-probe *,
body.home-probe *::before,
body.home-probe *::after {
  transition: none !important;
  animation: none !important;
}

body[data-page="home"].home-opening-active:not(.home-work-handoff) .work-section,
body[data-page="home"].home-opening-active .site-footer {
  opacity: 0;
  pointer-events: none;
  visibility: hidden;
}

body[data-page="home"].home-opening-active.home-work-handoff .work-section {
  position: fixed;
  /* JS drives this from the 64vh start DOWN TO the section's real landed
     position as the handoff progresses, so the moment the opening completes
     the fixed preview and the in-flow section occupy the SAME pixels — the
     old constant top left a ~250px gap that the FLIP glide then animated,
     which read as the page "bouncing" / projects appearing twice. */
  top: var(--home-work-fixed-top, clamp(58vh, 64vh, 700px));
  left: 0;
  right: 0;
  z-index: 6;
  /* NO width/padding/min-height overrides here: the fixed preview must use the
     exact same box geometry as the in-flow section (base rules + every media
     query), or the release swap visibly re-lays-out the content — heading and
     cards dropped by the padding-top delta on desktop, and the side margins
     jumped on phones (100%-112px vs the mobile 100%-56px width). */
  margin: 0 auto;
  opacity: var(--home-work-section-opacity, 0);
  transform: translateY(var(--home-work-section-y, 34px));
  transition: none;
  pointer-events: none;
  visibility: visible;
  will-change: transform, opacity;
}

body[data-page="home"].home-opening-active.home-work-handoff .work-card {
  opacity: var(--home-work-section-opacity, 0);
  transform: translateY(calc(var(--home-work-section-y, 34px) * 0.55));
}

body[data-page="home"].home-opening-active.home-work-handoff .work-card:nth-child(even) {
  transform: translateY(calc(var(--home-work-section-y, 34px) * 0.55));
}

body[data-page="home"].home-opening-complete .home-gallery-screen {
  display: none !important;
  opacity: 0 !important;
  pointer-events: none !important;
  transform: none !important;
  visibility: hidden !important;
}

body[data-page="home"]:not(.home-opening-active) .work-section {
  position: relative !important;
  top: auto !important;
  left: auto !important;
  z-index: auto !important;
  margin: 0 auto !important;
  pointer-events: auto !important;
  transform: translateY(0) !important;
  visibility: visible !important;
}

body[data-page="home"] .work-board,
body[data-page="home"].home-opening-complete .work-board {
  transform: none !important;
}

/* (two byte-duplicate rules removed here 2026-07-15: a repeat of the
   .home-gallery-screen lock above — the earlier copy also carries
   transform:none — and a repeat of the .work-board rule) */

body[data-page="home"].home-opening-complete .work-section {
  position: relative !important;
  left: auto !important;
  top: auto !important;
  transform: none !important;
  margin: 0 auto !important;
}

body[data-page="home"].home-opening-complete .work-card {
  transform: translateY(0) !important;
}

body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: #ffffff;
  opacity: 0;
  pointer-events: none;
  transition: opacity 320ms ease;
}

body.is-page-fading::before {
  opacity: 1;
}

/* 拉链 */
.zipper-wrapper {
  position: absolute; /* 或 fixed，看你要不要跟随页面固定 */
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  padding: 0;
  z-index: 20;
}

.zipper-wrapper img {
  display: block;
  width: 100%;
  height: auto;
  transform: translateY(0px);
}

/* 拉链贴边修复：只影响右上角拉链 */
.site-header .header-actions .pull-chain {
  top: calc(-1 * var(--header-top-pad) - 22px) !important;
  overflow: visible;
}

.site-header .header-actions .pull-chain .chain-image {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center top;
  transform: translateY(8px);
}

/* 黑暗模式下，拉链自动变白 */
body[data-theme="dark"] .site-header .header-actions .pull-chain .chain-image {
  filter: invert(1) brightness(1.15) contrast(1.06);
}

/* 黑暗模式下，logo 黑色部分变白 */
body[data-theme="dark"] .brand-mark .site-logo {
  filter: brightness(0) invert(1);
}

/* ===========================================================
   Studio block (home only) — two full-screen scroll pages:
   1) intro paragraph  2) "What we do" hover list.
   Pure-CSS hover; scoped to .studio-* so other pages are safe.
   =========================================================== */
.studio-block {
  position: relative;
}

/* keep it out of the way during the home opening animation */
body[data-page="home"].home-opening-active .studio-block {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.studio-screen {
  width: 100%;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 80px 0;
}

/* ---- Page 1: intro paragraph ---- */
.studio-intro-inner {
  width: min(100% - 112px, 1280px);
  margin: 0 auto;
}

.studio-intro-lead {
  max-width: 560px;
  margin: 0 0 0 clamp(0px, 12%, 200px);
  font-family: var(--body);
  font-size: clamp(17px, 1.5vw, 23px);
  line-height: 1.8;
  font-weight: 400;
  color: inherit;
}

.studio-intro-tag {
  max-width: 560px;
  margin: clamp(44px, 5.5vw, 88px) 0 0 clamp(0px, 12%, 200px);
  font-family: var(--body);
  font-size: clamp(17px, 1.5vw, 23px);
  line-height: 1.8;
  color: inherit;
}

/* ---- Page 2: "What we do" list ---- */
.studio-services-inner {
  --studio-col: 56%;   /* left edge of the service-name column */
  --studio-big: 80%;   /* left edge of the giant hover label */
  position: relative;
  width: min(100% - 112px, 1280px);
  margin: 0 auto;
}

.studio-services-eyebrow {
  margin: 0 0 clamp(40px, 6vh, 84px) var(--studio-col);
  font-family: var(--stamp);
  font-size: clamp(13px, 1vw, 16px);
  font-weight: 400;
  letter-spacing: 0.01em;
  color: inherit;
}

.studio-service-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.studio-service {
  position: relative;
  padding: clamp(13px, 1.5vw, 20px) 0;
}

/* the list label (Gaegu) — matches the intro paragraph size */
.studio-service-name {
  position: relative;
  display: inline-block;
  margin-left: var(--studio-col);
  font-family: var(--body);
  font-size: clamp(17px, 1.5vw, 23px);
  font-weight: 400;
  color: inherit;
  transition:
    transform 320ms cubic-bezier(0.22, 1, 0.36, 1),
    font-weight 320ms ease;
  will-change: transform;
}

.studio-service-name::before {
  content: "\2022"; /* bullet */
  position: absolute;
  left: -30px;
  top: 0;
  opacity: 0;
  transform: translateX(8px);
  transition:
    opacity 280ms ease,
    transform 280ms ease;
}

/* menu labels roll up into place when the section first appears */
.studio-name-roll {
  display: inline-block;
  overflow: hidden;
  line-height: 1.32;
  vertical-align: top;
}

.studio-name-inner {
  display: block;
  transition: transform 520ms cubic-bezier(0.76, 0, 0.24, 1);
}

/* labels stay visible by default; only hidden once JS arms the entrance, then revealed */
.studio-services.studio-roll-armed .studio-name-inner {
  transform: translateY(118%);
}

.studio-services.studio-roll-armed.is-revealed .studio-name-inner {
  transform: translateY(0);
}

.studio-service:nth-child(1) .studio-name-inner { transition-delay: 40ms; }
.studio-service:nth-child(2) .studio-name-inner { transition-delay: 85ms; }
.studio-service:nth-child(3) .studio-name-inner { transition-delay: 130ms; }
.studio-service:nth-child(4) .studio-name-inner { transition-delay: 175ms; }
.studio-service:nth-child(5) .studio-name-inner { transition-delay: 220ms; }
.studio-service:nth-child(6) .studio-name-inner { transition-delay: 265ms; }
.studio-service:nth-child(7) .studio-name-inner { transition-delay: 310ms; }
.studio-service:nth-child(8) .studio-name-inner { transition-delay: 355ms; }
.studio-service:nth-child(9) .studio-name-inner { transition-delay: 400ms; }

/* each row knows its index (0–8) so the tablet tier can place the active row's
   illustration (left) and giant result label (right) at that row's own height —
   illustration · item · label then read as one horizontal line. Inert on
   desktop and phone. */
.studio-service:nth-child(1) { --row-i: 0; }
.studio-service:nth-child(2) { --row-i: 1; }
.studio-service:nth-child(3) { --row-i: 2; }
.studio-service:nth-child(4) { --row-i: 3; }
.studio-service:nth-child(5) { --row-i: 4; }
.studio-service:nth-child(6) { --row-i: 5; }
.studio-service:nth-child(7) { --row-i: 6; }
.studio-service:nth-child(8) { --row-i: 7; }
.studio-service:nth-child(9) { --row-i: 8; }

/* ---- hover: directional vertical masked roll (overflow-hidden text swap) ----
   The center menu never moves. The left aside (thumb + caption) and the right
   big label sit at the active row's height; their content rolls vertically inside
   fixed overflow-hidden masks (old slides out, new slides in), driven by JS. */
.studio-service-aside {
  position: absolute;
  top: 50%;
  left: 0;
  display: flex;
  align-items: flex-start;
  gap: 16px;
  max-width: 34%;
  transform: translateY(-50%);
  pointer-events: none;
}

/* every dynamic block is a mask; its .studio-roll-inner is what translates */
.studio-service-media,
.studio-service-caption,
.studio-service-big {
  overflow: hidden;
}

.studio-roll-inner {
  display: block;
  transform: translateY(110%); /* hidden below the mask until the row is active */
  transition: transform 460ms cubic-bezier(0.76, 0, 0.24, 1);
  will-change: transform;
}

/* slight stagger so the three regions read as one linked motion */
.studio-service-caption .studio-roll-inner { transition-delay: 30ms; }
.studio-service-big .studio-roll-inner { transition-delay: 60ms; }

.studio-service-media {
  flex: 0 0 auto;
  width: 96px;
  height: 64px;
  border-radius: 4px;
  background: transparent;
}

.studio-service-media .studio-roll-inner {
  width: 100%;
  height: 100%;
  border-radius: 4px;
  background: rgba(0, 0, 0, 0.13);
  overflow: hidden;
  /* rows without an image (e.g. Film) keep this gray placeholder */
}

body[data-theme="dark"] .studio-service-media .studio-roll-inner {
  background: rgba(255, 255, 255, 0.16);
}

.studio-service-media .studio-roll-inner img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 4px;
}

.studio-service-caption {
  font-family: var(--body);
  font-size: clamp(15px, 1.05vw, 18px);
  line-height: 1.25;
  color: inherit;
}

/* giant label (Frijole), clipped at the viewport edge */
.studio-service-big {
  position: absolute;
  top: 50%;
  left: var(--studio-big);
  font-family: var(--stamp);
  font-size: clamp(24px, 2.9vw, 54px);
  line-height: 1.45;
  white-space: nowrap;
  color: inherit;
  transform: translateY(-50%);
  pointer-events: none;
}

/* active row: bold name + dot, shifted left — no layout shift for the other rows */
.studio-service.is-active .studio-service-name {
  transform: translateX(-48px);
  font-weight: 700;
}

.studio-service.is-active .studio-service-name::before {
  opacity: 1;
  transform: translateX(0);
}

/* reduced motion: swap instantly, no roll */
@media (prefers-reduced-motion: reduce) {
  .studio-roll-inner {
    transition: none;
  }
}

/* ---- the studio screens are NOT a panel ----
   This used to be a frosted-glass sheet: 32px top radii, three drop shadows,
   a bright lip highlight and a backdrop blur, sliding up over a pinned work
   section. Those three traits together read as a Bottom Sheet popping open
   instead of the page simply continuing.
   Now it IS the page — the same --paper as <body>, no radius, no shadow, no
   blur — so nothing "rises". The only thing visible at the seam is ::after:
   one hand-drawn horizon line, with the peeker orange poking above it. The
   solid background is also what hides the peeker's lower half (it is painted
   beneath this block: peeker z-index 2 < 3). */
.studio-block {
  z-index: 3;
  background: var(--paper);
}

/* The horizon: a 1.5px ink line with a gentle hand-drawn wobble, tiled so it
   spans any width seamlessly (the tile starts and ends at the same y, so the
   joins are invisible). Drawn as a MASK, so the colour comes from --ink and
   flips with the theme for free. Centred ON the block's top edge (top:-3px in
   a 6px box) so the orange reads as peeking from behind the line. */
.studio-block::after {
  content: "";
  position: absolute;
  top: -3px;
  left: 0;
  right: 0;
  height: 6px;
  pointer-events: none;
  background-color: var(--ink);
  -webkit-mask: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='360' height='6' viewBox='0 0 360 6'><path d='M0 3 C 40 2.4, 78 3.6, 120 3.05 C 162 2.5, 198 3.7, 240 3.1 C 282 2.55, 318 3.5, 360 3' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round'/></svg>") repeat-x left center / 360px 6px;
  mask: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='360' height='6' viewBox='0 0 360 6'><path d='M0 3 C 40 2.4, 78 3.6, 120 3.05 C 162 2.5, 198 3.7, 240 3.1 C 282 2.55, 318 3.5, 360 3' fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round'/></svg>") repeat-x left center / 360px 6px;
}

/* content sits higher than dead-center to trim the empty space above the text */
.studio-intro {
  justify-content: flex-start;
  padding-top: clamp(96px, 16vh, 200px);
}

/* Work scrolls normally (all cards pass) until its bottom reaches the viewport
   bottom, then it PINS and holds still while the studio screens rise over it.
   The pin offset (viewportHeight - workHeight, a negative number) is set by JS.
   It never engages near scrollY 0, so the opening glide is left untouched.
   What rises is no longer a panel: the studio block is the same white as the
   page, so the visitor sees only a hand-drawn horizon line sweeping up over the
   held scene, with the orange peeking over it. z-index keeps this under the
   studio block, which has to hide the peeker's lower half at the seam. */
body[data-page="home"].home-opening-complete .work-section {
  position: sticky !important;
  top: var(--work-sticky-top, 0px) !important;
  bottom: auto !important;
  z-index: 1;
}

/* Resize scroll-drift fix. The clothesline scene reflows with viewport width,
   so the browser's scroll anchoring re-pins onto a node inside it on every
   resize and nudges scrollY a few px each time. Resizing back and forth across
   breakpoints (phone → tablet → desktop → phone …) compounds those nudges into
   a visible cumulative downward drift of the whole section. The home page runs
   its own scroll choreography, so anchoring only ever fights it — disable it
   page-wide. overflow-anchor is not inherited, hence the descendant selector. */
body[data-page="home"],
body[data-page="home"] * {
  overflow-anchor: none;
}

/* Studio → mascot handoff (desktop): the mascot screen is pulled up by one screen
   (--home-end-pull, set by JS to the last gray screen's height) so it starts exactly
   overlapping — and hidden behind — that gray screen. Once scrolled that far, it
   sticks at top:0 and stops moving, while the gray screen (left as plain, unpinned
   content) keeps scrolling normally up and off, uncovering the now-static mascot.
   The pull cancels that gray screen's contribution to the page's scrollable height,
   so .home-reveal-spacer below adds an equal amount back — the runway the mascot
   needs to actually stay pinned while gray finishes scrolling away instead of
   running out of page to scroll. */
body[data-page="home"].home-opening-complete .home-end {
  position: sticky;
  top: 0;
  z-index: 1;
  margin-top: var(--home-end-pull, 0px);
}

body[data-page="home"].home-opening-complete .home-reveal-spacer {
  display: block;
  height: calc(-1 * var(--home-end-pull, 0px));
}

/* ---- mobile + touch tablets: simplify the hover-only card affordances (the
   revealed aside images / big labels are unreachable without a pointer). The
   work section KEEPS its cover pin here: it sticks and the frosted studio panel
   slides up over it, same as desktop. With min-height:auto below the section is
   shorter than the viewport, so --work-sticky-top resolves to 0 (a positive
   viewport−height min()s to 0) and it pins cleanly at the top — no negative
   offset to fight. ---- */
@media (max-width: 760px), (pointer: coarse) and (max-width: 1080px) {
  /* The clothesline scene is a wide desktop composition: scaled down by vw it
     only fills the top ~40% of a phone screen, and the section's min-height
     (a full viewport, meant for the desktop pin/reveal — which is disabled
     here anyway) left ~470px of dead white underneath it. Let the section hug
     its own content instead, so the scene reads as one balanced screen. */
  body[data-page="home"] .work-section {
    min-height: auto;
    padding: clamp(72px, 13vw, 104px) 0 clamp(40px, 8vw, 72px);
  }

  /* keep the simple stacked scroll on mobile — no pin/reveal handoff */
  body[data-page="home"].home-opening-complete .home-end {
    position: static;
    margin-top: 0;
  }

  body[data-page="home"].home-opening-complete .home-reveal-spacer {
    display: none;
  }

  .studio-screen {
    min-height: auto;
    padding: 64px 0;
  }

  .studio-intro-lead,
  .studio-intro-tag {
    margin-left: 0;
    max-width: none;
  }

  /* (the old static chip+name list that lived here is superseded by the
     tap-driven "What I like" tiers at the end of this file — every viewport
     this block serves is ≤1080 or a portrait tablet, so those tiers cover it) */
}

/* ===========================================================
   Bottom marquee — seamless infinite horizontal scroll of square
   cards above the footer. Pure CSS. Center cards stay sharp/opaque;
   only the left/right edges fade and softly blur into the background.
   =========================================================== */
/* The marquee and the footer share the last viewport (one screen). */
.home-end {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.home-end .marquee-section {
  flex: 1 1 auto;
  min-height: 0;
}

.marquee-section {
  background: var(--paper);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(56px, 8vh, 120px) 0;
  overflow: hidden;
}

.marquee-viewport {
  position: relative;
  width: 100%;
  overflow: hidden;
}

/* Always scrolling (runs from load on its own GPU layer) so it is already in
   motion the moment the section is reached — and it never pauses on hover. */
.marquee-track {
  position: relative;                   /* anchors the ::after frame overlay below */
  display: flex;
  align-items: center;                  /* frames centred in the strip */
  width: max-content;
  padding: clamp(21.08px, 2.081vw, 29.24px) 0;  /* matches the film's sprocket bands (≈68% of original: 85% ×0.8) */
  transform: translateZ(0);
  will-change: transform;
  animation: marquee-scroll 33s linear infinite;
}

/* The film frame is painted as an overlay ON TOP of the cards/mascots (not as the
   track's own background) so the mascots are always visually *behind* the tape —
   any sub-pixel gap between a card's clip edge and the frame artwork's window edge
   is simply covered by the frame's opaque black, instead of relying on the two
   boundaries lining up pixel-perfectly. Moves with the track for free since it's
   the track's own pseudo-element (inherits its transform/animation). */
.marquee-track::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* one cropped film cell (frame window + divider + sprockets) tiled across the
     strip. background-size x = 100%/18 = exactly one cell, so it aligns with each
     card and loops seamlessly on the -50% scroll. The tile is cropped mid-divider
     to mid-divider, so the half-dividers rejoin into one bar at every seam. */
  background-image: url("assets/system/film-strip-bold.png?v=20260707o");
  background-repeat: repeat-x;
  background-size: calc(100% / 18) 100%;
  background-position: left center;
}

/* Dark mode: swap to the white-frame tile (filter:invert can't be used here —
   it would also invert the mascot children of the track). */
body[data-theme="dark"] .marquee-track::after {
  background-image: url("assets/system/film-strip-bold-dark.png?v=20260707o");
}

/* duplicated set means -50% is exactly one set → seamless, no jump/gap */
@keyframes marquee-scroll {
  from { transform: translateZ(0) translateX(0); }
  to { transform: translateZ(0) translateX(-50%); }
}

.marquee-card {
  flex: 0 0 auto;
  width: clamp(129.2px, 12.92vw, 183.6px); /* the film window (natural landscape) — ≈68% of original (85% ×0.8) */
  aspect-ratio: 849 / 713;
  margin: 0 clamp(2.72px, 0.313vw, 4.42px); /* half the divider on each side → window centred in the cell (≈68%) */
  background: transparent;                 /* frame + sprockets come from the film image */
}

/* edge treatment: soft white fade + gradual blur (masked so it eases in toward
   the very edge and leaves the center untouched). Not a hard vertical cutoff. */
.marquee-edge {
  position: absolute;
  top: 0;
  bottom: 0;
  width: clamp(90px, 15%, 300px);
  z-index: 2;
  pointer-events: none;
  -webkit-backdrop-filter: blur(5px);
  backdrop-filter: blur(5px);
}

.marquee-edge-left {
  left: 0;
  background: linear-gradient(to left, transparent, var(--paper) 80%);
  -webkit-mask: linear-gradient(to left, transparent, #000 82%);
  mask: linear-gradient(to left, transparent, #000 82%);
}

.marquee-edge-right {
  right: 0;
  background: linear-gradient(to right, transparent, var(--paper) 80%);
  -webkit-mask: linear-gradient(to right, transparent, #000 82%);
  mask: linear-gradient(to right, transparent, #000 82%);
}

@media (prefers-reduced-motion: reduce) {
  .marquee-track {
    animation: none;
  }
}

@media (max-width: 760px) {
  /* The strip was still ~3.5× the desktop design's weight: a 187px card on a
     390px screen, where desktop uses 184px on a 1440px one.

     The film's three sizes are coupled — window (card), sprocket bands (track
     padding) and divider (card margin) — and must scale by ONE factor or the
     frame artwork stops matching its windows. So they are all derived here from
     a single card size, using the desktop ratios (padding = 0.1593 × card,
     margin = 0.024 × card each side). Card 48vw → 34vw, capped at 170px so the
     strip doesn't blow up again just under the 760px breakpoint.
     Strip height at 390px: 199px → 154px. */
  .marquee-section {
    --film-card: min(34vw, 170px);
  }

  .marquee-card {
    width: var(--film-card);
    margin: 0 calc(var(--film-card) * 0.024);
  }

  .marquee-track {
    padding: calc(var(--film-card) * 0.1593) 0;
  }

  .marquee-edge {
    width: clamp(48px, 18%, 140px);
  }
}

/* ===========================================================
   Marquee mascots — one orange mascot per card, 8 animated variants.
   Reuses the existing hand-drawn body PNG + black-dot eyes; props and
   motion are code-based. Carousel moves the .marquee-track (outer);
   mascot motion lives on inner elements only.
   =========================================================== */
.marquee-card {
  position: relative;
  overflow: hidden;
  display: grid;
  place-items: center;
}

.mascot {
  --m-orange: #ee7b2c;
  position: relative;
  width: 55%;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding-bottom: 5%;
  container-type: inline-size; /* lets prop/symbol sizes use cqw (% of mascot width) */
}

.mascot-body {
  position: relative;
  width: 100%;
  transform-origin: 50% 100%;
  will-change: transform;
}

.mascot-img {
  display: block;
  width: 100%;
  mix-blend-mode: multiply;
}

body[data-theme="dark"] .mascot-img {
  mix-blend-mode: normal;
}

/* eyes — two black dots positioned over the dome (matches the home mascot) */
.mascot-eyes {
  position: absolute;
  left: 50%;
  top: 62%;
  width: 30%;
  display: flex;
  justify-content: space-between;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.mascot-eye {
  position: relative;
  width: 19%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #111111;
  transform-origin: 50% 50%;
}

/* closed/relaxed eyes (music) → gentle downward curves */
.mascot-eyes-closed .mascot-eye {
  background: none;
  border-bottom: 2px solid #111111;
  border-radius: 0 0 60% 60%;
  height: 8%;
  aspect-ratio: auto;
  align-self: center;
}

/* ---- 1. Curious (eyes driven by JS in px, so up/down reach matches the home mascot) ---- */
.mascot--curious .mascot-eyes {
  transform: translate(-50%, -50%) translate(var(--lx, 0px), var(--ly, 0px));
  transition: transform 640ms cubic-bezier(0.45, 0, 0.25, 1);
}
.mascot--curious .mascot-eye {
  transition: transform 180ms ease;
}

/* ---- 2. Crying ---- */
.mascot--crying .mascot-eye-l { transform: rotate(10deg) scaleY(0.9); }
.mascot--crying .mascot-eye-r { transform: rotate(-10deg) scaleY(0.9); }
.mascot-tear {
  position: absolute;
  top: 70%;
  width: 6%;
  height: 9%;
  background: #8fd3f4;
  border-radius: 50% 50% 50% 50% / 70% 70% 40% 40%;
  opacity: 0;
}
.mascot-tear-l { left: 35%; animation: mascot-tear-fall 5.4s ease-in 0.3s infinite; }
.mascot-tear-r { left: 59%; animation: mascot-tear-fall 5.4s ease-in 3s infinite; }
@keyframes mascot-tear-fall {
  0% { opacity: 0; transform: translateY(0) scale(0.5); }
  14% { opacity: 0.9; transform: translateY(0) scale(1); }
  60% { opacity: 0.85; transform: translateY(140%) scale(1); }
  78% { opacity: 0; transform: translateY(220%) scale(0.9); }
  100% { opacity: 0; transform: translateY(220%) scale(0.9); }
}

/* ---- 3. Peeking (crouches low behind the card's bottom edge and peeks up) ---- */
/* push the whole mascot down so its lower half is clipped by the card edge;
   a touch more than half of the dome still shows. */
.mascot--peeking { transform: translateY(38%); }
.mascot--peeking .mascot-body {
  animation: mascot-peek 7s ease-in-out infinite;
  transform-origin: 50% 100%;
}
/* Synced with the eye look-around below (same 7s duration): rise to 3/4, hold while
   the eyes glance upper-left / upper-right and return to centre, then duck to half. */
@keyframes mascot-peek {
  0%, 6%    { transform: translateY(47%); }  /* start: about half (1/2) of the dome shows */
  16%, 82%  { transform: translateY(22%); }  /* peek up to three-quarters (3/4) and hold */
  92%, 100% { transform: translateY(47%); }  /* duck back down to half */
}
/* eyes sit higher and look upward */
.mascot--peeking .mascot-eyes { top: 52%; }
.mascot--peeking .mascot-eye {
  animation: mascot-peek-look 7s ease-in-out infinite;
}
/* Timed to the dome (same 7s): centred while it rises, then glance upper-left + blink →
   upper-right + blink → return to centre (正视) + blink, held centred while it ducks. */
@keyframes mascot-peek-look {
  0%, 16%   { transform: translate(0, -32%) scaleY(1); }       /* forward / centred while rising */
  22%, 30%  { transform: translate(-210%, -42%) scaleY(1); }   /* glance upper-left */
  33%       { transform: translate(-210%, -42%) scaleY(0.1); } /* blink */
  36%, 44%  { transform: translate(-210%, -42%) scaleY(1); }
  50%, 58%  { transform: translate(210%, -42%) scaleY(1); }    /* glance upper-right */
  61%       { transform: translate(210%, -42%) scaleY(0.1); }  /* blink */
  64%, 70%  { transform: translate(210%, -42%) scaleY(1); }
  76%, 82%  { transform: translate(0, -32%) scaleY(1); }       /* return to centre (正视) before ducking */
  85%       { transform: translate(0, -32%) scaleY(0.1); }     /* blink */
  88%, 100% { transform: translate(0, -32%) scaleY(1); }       /* hold centre through the duck + rest */
}

/* ---- 4. Working (round wire-frame glasses + temple arms, laptop to the left) ---- */
.mascot--working .mascot-eye { animation: mascot-work-scan 3.4s ease-in-out infinite; }
@keyframes mascot-work-scan {
  0%, 100% { transform: translateX(-20%); }
  20% { transform: translateX(20%); }
  40% { transform: translateX(-20%); }
  48% { transform: translateX(-20%) scaleY(0.12); }
  52% { transform: translateX(-20%) scaleY(1); }
  70% { transform: translateX(16%); }
  90% { transform: translateX(-8%); }
}
.mascot-glasses {
  position: absolute;
  left: 50%;
  top: 60%;
  width: 48%;
  aspect-ratio: 2.4 / 1;
  transform: translate(-50%, -50%);
}
.mg-lens {
  position: absolute;
  top: 0;
  width: 38%;
  aspect-ratio: 1;
  border: 3.2px solid #161616;
  border-radius: 50%;
  background: transparent; /* frame only — no lens fill */
}
.mg-lens-l { left: 4%; }
.mg-lens-r { right: 4%; }
.mg-bridge {
  position: absolute;
  top: 18%;
  left: 42%;
  right: 42%;
  height: 3.2px;
  background: #161616;
}
.mg-arm {
  position: absolute;
  top: 26%;
  width: 16%;
  height: 3.2px;
  background: #161616;
}
.mg-arm-l { left: -12%; transform: rotate(10deg); transform-origin: right center; }
.mg-arm-r { right: -12%; transform: rotate(-10deg); transform-origin: left center; }
/* glasses sit so the laptop's top edge cuts their lower half; pupils ride low
   (just above the edge) so the mascot reads as looking down at the screen */
.mascot--working .mascot-eyes { top: 43.5%; }
.mascot--working .mascot-glasses { top: 46%; }
/* MacBook seen from the back of the lid: white panel, hand-drawn outline, Apple logo */
.mascot-laptop {
  position: absolute;
  left: 50%;
  bottom: -3%;
  width: 70%;
  transform: translateX(-50%);
  z-index: 5;
}
.ml-lid {
  display: grid;
  place-items: center;
  width: 100%;
  aspect-ratio: 1.5 / 1;
  background: #ffffff;
  border: 2.5px solid #181818;
  border-radius: 12% 12% 5% 5% / 20% 20% 8% 8%;
}
.ml-apple {
  width: 18%;
  height: auto;
}
.ml-apple path {
  fill: #181818;
}

/* ---- 5. Sleepy (eyes stay shut, slow breathing, looping zzz) ---- */
.mascot--sleepy .mascot-body { animation: mascot-breathe 4.2s ease-in-out infinite; }
@keyframes mascot-breathe {
  0%, 100% { transform: scaleY(1) scaleX(1); }
  50% { transform: scaleY(1.04) scaleX(0.99); }
}
/* eyes blink and settle mostly shut — original dot-based doze form */
.mascot-lid { display: none; }
.mascot--sleepy .mascot-eye { animation: mascot-doze 7s ease-in-out infinite; }
@keyframes mascot-doze {
  0%, 8% { transform: scaleY(0.7); }    /* drowsy, only half-open */
  28% { transform: scaleY(0.07); }      /* slowly drifts shut */
  82% { transform: scaleY(0.07); }      /* stays shut a good while */
  100% { transform: scaleY(0.7); }      /* slowly opens again, loops */
}
/* small "zzz" drifting up the top-right corner, fading in and out in a loop */
.mascot-z {
  position: absolute;
  font-family: var(--body);
  font-weight: 700;
  color: #9a9a9a;
  opacity: 0;
}
.mascot-z1 { right: 14%; top: 22%; font-size: 12cqw; animation: mascot-zfloat 3.6s ease-in-out 0s infinite; }
.mascot-z2 { right: 6%;  top: 14%; font-size: 10cqw; animation: mascot-zfloat 3.6s ease-in-out 0.55s infinite; }
.mascot-z3 { right: -2%; top: 7%;  font-size: 8cqw;  animation: mascot-zfloat 3.6s ease-in-out 1.1s infinite; }
@keyframes mascot-zfloat {
  0%   { opacity: 0; transform: translateY(25%) rotate(-4deg); }
  35%  { opacity: 0.85; }
  65%  { opacity: 0.85; transform: translateY(-8%) rotate(5deg); }
  100% { opacity: 0; transform: translateY(-22%) rotate(8deg); }
}

/* ---- 6. Dizzy (woozy lean; left eye orbits clockwise, right counter-clockwise) ---- */
.mascot--dizzy .mascot-body { animation: mascot-dizzy-body 2.6s ease-in-out infinite; transform-origin: 50% 92%; }
@keyframes mascot-dizzy-body {
  0%, 100% { transform: rotate(-3.5deg); }
  50% { transform: rotate(3.5deg); }
}
.mascot--dizzy .mascot-eye-l { animation: mascot-orbit-cw 1.9s linear infinite; }
.mascot--dizzy .mascot-eye-r { animation: mascot-orbit-ccw 1.9s linear infinite; }
@keyframes mascot-orbit-cw {
  from { transform: rotate(0deg) translateX(95%) rotate(0deg); }
  to { transform: rotate(360deg) translateX(95%) rotate(-360deg); }
}
@keyframes mascot-orbit-ccw {
  from { transform: rotate(0deg) translateX(95%) rotate(0deg); }
  to { transform: rotate(-360deg) translateX(95%) rotate(360deg); }
}

/* ---- 7. Jumping (squash → stretch → land → rest) ---- */
.mascot--jumping .mascot-body { animation: mascot-jump 3s ease-in-out infinite; }
@keyframes mascot-jump {
  0%, 20% { transform: translateY(0) scaleX(1) scaleY(1); }
  30% { transform: translateY(0) scaleX(1.12) scaleY(0.84); }
  48% { transform: translateY(-40%) scaleX(0.93) scaleY(1.12); }
  64% { transform: translateY(0) scaleX(1.14) scaleY(0.82); }
  78%, 100% { transform: translateY(0) scaleX(1) scaleY(1); }
}

/* ---- 8. Listening to music (single composed mascot+headphones asset, sway, notes) ---- */
/* the composed art includes the head, closed eyes and worn headphones; widen
   the body so the whole headphone fills the card like the other mascots. */
.mascot--music .mascot-body { width: 140%; flex-shrink: 0; animation: mascot-sway 3.4s ease-in-out infinite; transform-origin: 50% 90%; }
@keyframes mascot-sway {
  0%, 100% { transform: rotate(-3deg) translateY(0); }
  25% { transform: rotate(0deg) translateY(-2%); }
  50% { transform: rotate(3deg) translateY(0); }
  75% { transform: rotate(0deg) translateY(-2%); }
}
/* powder-blue over-ear headphones (hand-drawn SVG, Bose-style): the band
   arcs over the crown, the ear cups sit at the sides of the head (ear level) */
/* two stacked copies of the same headphone asset create a worn front/back
   relationship: the band tucks behind the crown, the ear cups sit in front
   of the head's sides. */
.mascot-headphones {
  position: absolute;
  left: 50%;
  top: -24%;       /* band dips onto the crown so it tucks behind the head */
  width: 108%;     /* cups press onto the head's sides, overlapping in front */
  transform: translateX(-50%);
  pointer-events: none;
}
.mascot-headphones img {
  display: block;
  width: 100%;
  height: auto;
}
/* back copy: behind the head — the headband arcs over/behind the crown */
.mascot-headphones--back { z-index: -1; }
/* front copy: clipped to the two ear-cup columns so the cups sit on the
   head's sides while the band stays behind */
.mascot-headphones--front { z-index: 5; }
.mascot-headphones--front img {
  clip-path: polygon(0 0, 30% 0, 30% 50%, 70% 50%, 70% 0, 100% 0, 100% 100%, 0 100%);
}
.mascot-note {
  position: absolute;
  top: 15%;
  right: 14%;
  opacity: 0;
}
.mascot-note img { display: block; width: 100%; height: auto; }
/* widths are chosen so the *rendered heights* land close together (~1cqw apart) —
   the single-note art is tall/narrow (0.55 w/h) and the double-note art is near-
   square (1.01 w/h), so matching width alone left the single note rendering at
   roughly 2x the visual height of the double note. */
.mascot-note1 { width: 7.9cqw; animation: mascot-note-float 3.6s ease-in-out infinite; }
.mascot-note2 { width: 13.7cqw; right: 24%; animation: mascot-note-float 3.6s ease-in-out 1.8s infinite; }
@keyframes mascot-note-float {
  0% { opacity: 0; transform: translate(0, 0) rotate(-6deg); }
  25% { opacity: 0.9; }
  100% { opacity: 0; transform: translate(50%, -130%) rotate(10deg); }
}

/* ---- 9. Angry (inward-tilted brows + eyes, tense shake, comic anger mark) ---- */
.mascot--angry .mascot-body {
  animation: mascot-angry-shake 3.2s ease-in-out infinite;
  transform-origin: 50% 100%;
}
@keyframes mascot-angry-shake {
  0%, 58%, 100% { transform: translateX(0) scaleY(1); }
  62% { transform: translateX(-1.6%) scaleY(0.985); }
  66% { transform: translateX(1.6%); }
  70% { transform: translateX(-1.3%); }
  74% { transform: translateX(1.3%); }
  78% { transform: translateX(0) scaleY(1); }
}
/* eyes morph from calm round dots into angled, narrowed "glare" shapes and
   relax again — the anger is carried entirely by the eyes (no brows) */
.mascot--angry .mascot-eye-l { animation: mascot-angry-eye-l 3.2s ease-in-out infinite; }
.mascot--angry .mascot-eye-r { animation: mascot-angry-eye-r 3.2s ease-in-out infinite; }
@keyframes mascot-angry-eye-l {
  0%, 16% { border-radius: 50%; transform: rotate(0deg) scaleY(1); }                  /* calm */
  34%, 82% { border-radius: 16% 16% 50% 50%; transform: rotate(26deg) scaleY(0.58); } /* glare, inner-down */
  100% { border-radius: 50%; transform: rotate(0deg) scaleY(1); }                     /* relax + loop */
}
@keyframes mascot-angry-eye-r {
  0%, 16% { border-radius: 50%; transform: rotate(0deg) scaleY(1); }
  34%, 82% { border-radius: 16% 16% 50% 50%; transform: rotate(-26deg) scaleY(0.58); }
  100% { border-radius: 50%; transform: rotate(0deg) scaleY(1); }
}
/* hand-drawn red anger mark (image asset), top-right corner, static */
.mascot-anger {
  position: absolute;
  top: 16%;
  right: 8%;
  width: 11cqw;
  transform: rotate(-6deg);
}
.mascot-anger img { display: block; width: 100%; height: auto; }

/* pause mascot motion when the section is off-screen (set by JS) */
.marquee-section.is-mascot-paused .mascot-body,
.marquee-section.is-mascot-paused .mascot-eye,
.marquee-section.is-mascot-paused .mascot-eyes,
.marquee-section.is-mascot-paused .mascot-blush,
.marquee-section.is-mascot-paused .mascot-tear,
.marquee-section.is-mascot-paused .mascot-z,
.marquee-section.is-mascot-paused .mascot-note,
.marquee-section.is-mascot-paused .mascot-anger {
  animation-play-state: paused;
}

/* reduced motion: hold a calm static pose for every mascot */
@media (prefers-reduced-motion: reduce) {
  .mascot-body,
  .mascot-eye,
  .mascot-eyes,
  .mascot-blush,
  .mascot-tear,
  .mascot-z,
  .mascot-note,
  .mascot-anger {
    animation: none !important;
    transition: none !important;
  }
  .mascot-tear, .mascot-z, .mascot-note, .mascot-blush { opacity: 0; }
}

/* ===========================================================
   Home opening collage — phone + portrait-tablet rebuild.
   The late "Home gallery position fix / cleanup" rules above use
   body[data-page="home"] selectors that outrank the old ≤820 media
   block, so small screens were getting desktop photo sizes/offsets
   (clumped, overlapping the orange). This block sits later in the
   file with the same specificity, so it wins on small screens.
   Composition mirrors the desktop reference proportionally:
   orange dead-centre (same 0.72 scale; eye row 41%/50% of the dome,
   35% of its width — measured from desktop), note under the dome,
   photos orbiting around it, everything smaller.
   Portrait tablets (iPad up to 1180px) take this canvas too — the
   desktop orbit was tuned for wide screens and clumps when the
   viewport is taller than it is wide.
   =========================================================== */
@media (max-width: 900px), (min-width: 901px) and (max-width: 1180px) and (orientation: portrait) {
  body[data-page="home"] .home-gallery-screen {
    padding: 0 clamp(16px, 4vw, 48px);
  }

  /* ---- ASPECT-LOCKED DESIGN CANVAS ----
     The old rules sized the photos in vw but positioned them in % of a stage
     whose HEIGHT was capped in px. The two units drift apart: widen the screen
     (or shorten it) and the photos grow while their vertical slots do not, so
     past ~460px wide the bottom video landed on the beach and rock photos and
     the sand photo landed on the tree.

     Instead the stage is now a canvas with a FIXED aspect ratio, fitted to
     whichever axis runs out first, and everything inside it is expressed in
     units of that canvas: sizes in cqw (% of the canvas width), positions in %
     (of canvas width / height). Both axes then scale by the same number, so the
     composition is rigid — it can grow or shrink, but it can never collide. */
  body[data-page="home"] .home-gallery-stage {
    width: min(100%, calc((100svh - 150px) * 1.1)); /* 1.1 = the aspect below */
    aspect-ratio: 11 / 10;
    height: auto;
    min-height: 0;
    container-type: inline-size; /* the canvas IS the unit for everything inside */
    transform: translateY(-3vh);
  }

  /* orange: centred, desktop scale ratio. The eye rig needs NO overrides here —
     size, gap, resting offset and travel range all ride --eye-scale, which JS
     derives from this stage's own width (see .orange-stage). */
  body[data-page="home"] .home-gallery-screen .orange-stage {
    /* the old home-hero mobile rule (.orange-stage { order: 2 }) leaks in at
       ≤820px and would flip the dome below its caption — restore DOM order */
    order: 0;
    width: 35cqw;
    min-height: 0;
    transform: translateY(-4px) scale(0.72);
  }

  /* the caption's fixed -28px pull was tuned for the desktop stage's larger
     transform inset — scaled down it lands ~4px under the dome, as on desktop */
  body[data-page="home"] .home-vibe-note {
    margin-top: -2.55cqw;
  }

  /* 山 — tall, top-left */
  body[data-page="home"] .home-photo-a {
    top: 2%;
    left: 6%;
    width: 21cqw;
    aspect-ratio: 4 / 5;
    height: auto;
  }

  /* 坎昆 video — wide, top-right */
  body[data-page="home"] .home-photo-b {
    top: 7%;
    right: 4%;
    left: auto;
    width: 31.2cqw;
    aspect-ratio: 16 / 9;
    height: auto;
  }

  /* rocks — mid-right, hugs the edge beside the orange */
  body[data-page="home"] .home-photo-c {
    top: 48%;
    right: 2%;
    left: auto;
    width: 25cqw;
    aspect-ratio: 16 / 9;
    height: auto;
  }

  /* 我-海 — mid-left, hugs the edge beside the orange */
  body[data-page="home"] .home-photo-d {
    top: 45%;
    left: 2%;
    width: 24cqw;
    aspect-ratio: 4 / 2.5;
    height: auto;
  }

  /* 欣欣 — dropped on tablets and phones: the sketch keeps four photos +
     the bottom video, and the freed lower-left corner is the breathing
     room the composition needs */
  body[data-page="home"] .home-photo-e {
    display: none;
  }

  /* 北海 video — bottom centre */
  body[data-page="home"] .home-photo-main {
    left: 50%;
    bottom: -2%;
    top: auto;
    width: 42.5cqw;
    aspect-ratio: 16 / 9;
    height: auto;
    translate: -50% 0;
  }
}

/* ---- phones: per the sketch, only three media survive — tree top-left,
   rocks top-right, koi video at the base — with the orange + note between
   them. Anything more turns the airy composition back into a list. Same
   aspect-locked canvas as ≤900; eye-rig ratios carry over and are re-stated
   only because the dome is proportionally larger. ---- */
@media (max-width: 560px) {
  body[data-page="home"] .home-gallery-stage {
    width: min(100%, calc((100svh - 128px) * 0.64)); /* 0.64 = the aspect below */
    aspect-ratio: 16 / 25;
    transform: translateY(-2vh);
  }

  /* jetski video + beach photo leave the stage on phones (欣欣 already
     leaves at ≤900) */
  body[data-page="home"] .home-photo-b,
  body[data-page="home"] .home-photo-d {
    display: none;
  }

  /* the frames' desktop aspect ratios match each file's natural ratio, so
     nothing needed cover until now — here the rocks photo is re-cropped
     into a portrait card to mirror the tree, which needs a real crop */
  body[data-page="home"] .home-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  body[data-page="home"] .home-gallery-screen .orange-stage {
    width: 52cqw;
  }

  body[data-page="home"] .home-vibe-note {
    margin-top: -3.6cqw;
  }

  /* tree — top-left, tall */
  body[data-page="home"] .home-photo-a {
    top: 2%;
    left: 6%;
    width: 40cqw;
    aspect-ratio: 4 / 5;
  }

  /* rocks — top-right, portrait crop mirroring the tree */
  body[data-page="home"] .home-photo-c {
    top: 2%;
    right: 6%;
    left: auto;
    width: 40cqw;
    aspect-ratio: 4 / 5;
  }

  /* 北海 video — bottom centre, anchors the base */
  body[data-page="home"] .home-photo-main {
    left: 50%;
    bottom: 0%;
    top: auto;
    width: 62cqw;
    aspect-ratio: 16 / 9;
    translate: -50% 0;
  }
}

/* ===========================================================
   Selected projects — whimsical clothesline / cable-car carousel.
   Two rope systems: a FIXED decorative top rope (charms + the
   winking orange bun mascot) and a lower rope that travels with
   the four hanging project cards as one draggable track.
   Gaegu-flavoured: thin sketchy strokes, warm paper, uneven
   hand-drawn radii, no heavy shadows, no glass. Logic lives in
   clothesline.js (drag/snap/sway/depth/wink).
   =========================================================== */
.pc-scene {
  /* Near-black ink to match the hero's line-art clarity (photo borders + pins
     are 1.5px on pure ink). Was #333 gray — the clothesline scene read light. */
  --pc-line: #111111;
  --pc-line-soft: rgba(17, 17, 17, 0.6);
  --pc-card-bg: #fdfbf6;
  --pc-blue: #6ba8ff;
  /* The smaller, flatter notes stay right-weighted, preserving the generous
     lower-left breathing space for the pinwheel and clothesline scene. */
  --pc-inset: 55vw;                       /* front card's resting left edge (where the carousel snaps to rest). Bigger = the stopped card sits further RIGHT. */
  --pc-gap: clamp(48px, 7.5vw, 125px);    /* reference 0.075 of frame */
  --pc-w: clamp(205px, 17.5vw, 292px);    /* restrained card scale */
  --pc-fade-l: clamp(14px, 2.4vw, 44px);
  --pc-fade-r: 15vw;                      /* melts the preview card's right side */
  --pc-scene-lift: clamp(36px, 4vw, 58px);
  position: relative;
  font-family: var(--hand);
}

body[data-theme="dark"] .pc-scene {
  /* #eee = exactly invert(1) of the #111 top-rope SVG, so both clotheslines
     stay identical in dark mode too (the top rope flips via a CSS invert). */
  --pc-line: #eeeeee;
  --pc-line-soft: rgba(238, 238, 238, 0.6);
  --pc-card-bg: #121212;
}

/* section title kept for screen readers / the #work anchor only */
.pc-sr-title {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---- fixed decorative top rope: the user's line2 artwork ---- */
.pc-top {
  position: relative;
  /* dome top rides at 9.16% of the viewport width (measured from the art),
     plus the bun's own height */
  height: calc(9.16vw + clamp(78px, 8.2vw, 112px) * 0.66 + 10px);
  margin-top: -6px;
  transform: translateY(calc(var(--pc-scene-lift) * -1));
  pointer-events: none;
}

/* full-bleed, scales 1:1 with the viewport (2172px native, band 250/2172 =
   11.51% of its width tall) — crisp on any normal screen */
.pc-top-line {
  position: absolute;
  top: 0;
  left: calc(50% - 50vw);
  z-index: 2; /* above the bun so the drawn clip + rope strokes hold the dome */
  width: 100vw;
  max-width: none; /* the global img cap would clamp it to the content column */
  height: auto;
  user-select: none;
  transition: filter 240ms ease; /* invert crossfades with the theme switch */
}

body[data-theme="dark"] .pc-top-line {
  filter: invert(1);
}

.pc-rope path {
  stroke: var(--pc-line);
  stroke-width: 1.5; /* one weight with .pc-top-line — both clotheslines match */
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke 240ms ease; /* theme switch */
}

/* ---- the bun mascot, tucked under the artwork's drawn clothespin.
   Art is viewport-sized so both coordinates are in vw: clip centre at 28.75%
   of the image, dome top at 199px of its 2172px width (9.16vw) — the clip's
   lower strokes paint over the dome = visually caught in it. Both numbers are
   re-measured from the asset whenever the decorations are rescaled. ---- */
.pc-bun {
  position: absolute;
  left: calc(50% - 50vw + 28.75vw);
  top: 8.92vw; /* dome tip 0.74vw inside the enlarged clip's jaw (still 0.13vw
                  below the rope crossing, so it never pokes above the line) */
  z-index: 1;
  width: clamp(78px, 8.2vw, 112px);
  /* centre the dome under the clip; the 10° tilt is the natural lean of a
     single-clip hanging item, pivoting from the clip point above */
  transform: translateX(-50%) rotate(8deg);
  transform-origin: 50% -0.6vw; /* the clip's jaw, just above the dome */
}

.pc-bun img {
  display: block;
  width: 100%;
  height: auto;
  mix-blend-mode: multiply;
  user-select: none;
  mask-image: radial-gradient(ellipse 3.2% 10% at 50% 0%, transparent 97%, #000 103%);
}

body[data-theme="dark"] .pc-bun img {
  mix-blend-mode: normal;
}

/* both eyes open by default, sitting a touch right + low on the dome so the bun
   quietly gazes down-right toward the first/second cards — subtle, never extreme */
.pc-bun-eyes {
  position: absolute;
  left: 56%;
  top: 63%;
  width: 26%;
  display: flex;
  justify-content: space-between;
  transform: translate(-50%, -50%);
}

.pc-bun-eye {
  width: 19%;
  aspect-ratio: 1;
  border-radius: 50%;
  background: #111111;
  transform-origin: 50% 50%;
  transition: transform 90ms ease;
}

/* blink: BOTH eyes squeeze shut together for a beat (JS toggles .is-blink) */
.pc-bun.is-blink .pc-bun-eye {
  transform: scaleY(0.12);
}

/* ---- lower rope + card track (they move together) ---- */
.pc-viewport {
  position: relative;
  width: 100vw;
  margin-left: calc(50% - 50vw);
  padding: 6px 0 20px;
  transform: translateY(calc(var(--pc-scene-lift) * -1));
  overflow: hidden;
  cursor: grab;
  touch-action: pan-y;
  scrollbar-width: none;
}

.pc-viewport::-webkit-scrollbar {
  display: none;
}

.pc-viewport.pc-enhanced {
  overflow: hidden;
}

.pc-viewport.is-dragging {
  cursor: grabbing;
}

/* ---- desktop swipe hint, ONLY at the two ends, ONLY over the live image ----
   On the FIRST project a small right arrow says "keep going that way"; on the
   LAST a left arrow says "back the other way". Every card in between keeps the
   ordinary pointer — the hint belongs at the ends, where there is only one
   direction to travel.

   Scope matters more than the drawing here. An earlier version put the cursor
   on .pc-viewport, which is the full 100vw carousel strip, so the arrow
   appeared far away from any image and over the blurred neighbours too. It now
   sits on `.pc-card-active .pc-media` — the live card's image panel and nothing
   else — so it turns on exactly at the artwork's edge and off the moment the
   pointer leaves it. Blurred/slid-away cards are `pointer-events: none`, so
   they can never show it at all.

   • Pointer devices only; a custom cursor means nothing on touch.
   • :not(.is-dragging) lets `grabbing` keep winning while a drag is live.
   • A cursor is an IMAGE and cannot inherit --ink, hence a cream variant for
     dark mode, where these cards are black.
   • 12 12 = the hotspot, dead centre of the 24x24 drawing. ---- */
@media (hover: hover) and (pointer: fine) {
  .pc-viewport.pc-at-first:not(.is-dragging) .pc-card-active .pc-media {
    cursor: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><g fill='none' stroke='%23111111' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><path d='M3.4 12.6 C 8 11.9, 13.5 12.7, 18.4 12.1'/><path d='M13.6 8.1 C 15.6 9.6, 17.4 11, 18.9 12.2 C 17.2 13.5, 15.4 14.9, 13.9 16.6'/></g></svg>") 12 12, pointer;
  }

  .pc-viewport.pc-at-last:not(.is-dragging) .pc-card-active .pc-media {
    cursor: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><g fill='none' stroke='%23111111' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><path d='M20.6 12.6 C 16 11.9, 10.5 12.7, 5.6 12.1'/><path d='M10.4 8.1 C 8.4 9.6, 6.6 11, 5.1 12.2 C 6.8 13.5, 8.6 14.9, 10.1 16.6'/></g></svg>") 12 12, pointer;
  }

  body[data-theme="dark"] .pc-viewport.pc-at-first:not(.is-dragging) .pc-card-active .pc-media {
    cursor: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><g fill='none' stroke='%23f9f3eb' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><path d='M3.4 12.6 C 8 11.9, 13.5 12.7, 18.4 12.1'/><path d='M13.6 8.1 C 15.6 9.6, 17.4 11, 18.9 12.2 C 17.2 13.5, 15.4 14.9, 13.9 16.6'/></g></svg>") 12 12, pointer;
  }

  body[data-theme="dark"] .pc-viewport.pc-at-last:not(.is-dragging) .pc-card-active .pc-media {
    cursor: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><g fill='none' stroke='%23f9f3eb' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'><path d='M20.6 12.6 C 16 11.9, 10.5 12.7, 5.6 12.1'/><path d='M10.4 8.1 C 8.4 9.6, 6.6 11, 5.1 12.2 C 6.8 13.5, 8.6 14.9, 10.1 16.6'/></g></svg>") 12 12, pointer;
  }
}

.pc-track {
  position: relative;
  width: 100%;
  height: calc(var(--pc-w) * 0.66 + 72px);
  padding-left: var(--pc-inset); /* read by JS as the front card's inset */
  /* a real (resolved) property so JS can read the clamped gap — custom
     properties hand back the raw clamp() token, not its computed value */
  column-gap: var(--pc-gap);
  /* cards fade out at both edges — right fade covers the 2nd card's last third,
     hinting at more projects. The rope is outside the track, so it stays solid. */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--pc-fade-l), #000 calc(100% - var(--pc-fade-r)), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--pc-fade-l), #000 calc(100% - var(--pc-fade-r)), transparent 100%);
}

/* a small pinwheel planted in the lower-left breathing space — the user's own
   TWO-LAYER artwork: the rest layer (stick + pin + ribbon + grass) sizes the
   container and never moves; the head layer spins about the pin. Desktop only:
   below 1080px the left breathing space is where the cards live. */
.pc-pinwheel {
  position: absolute;
  /* viewport-anchored like the ropes, so it holds the lower-left zone from the
     user's reference at every width instead of drifting with the content column */
  left: calc(50% - 50vw + 12vw);
  bottom: 25px;
  z-index: 0;
  display: block;
  /* container = the rest layer (318 units); the head overflows it to ~1.47x,
     so the full drawing renders about the same width as the old pinwheel */
  width: clamp(44px, 4.5vw, 66px);
  pointer-events: none;
  user-select: none;
  transition: filter 240ms ease; /* invert crossfades with the theme switch */
}

body[data-theme="dark"] .pc-pinwheel {
  filter: invert(1); /* inverts both layers; the white sail fills become
                        background-black, so occlusion stays invisible */
}

.pc-pinwheel img {
  display: block;
  -webkit-user-drag: none;
}

.pc-pinwheel-rest {
  position: relative;
  z-index: 1;
  width: 100%;
  height: auto;
  transform: scale(3.5);
  transform-origin: 55% 0;
}

/* The head hangs on the rest layer's pin. Its source-space hub is aligned with
   the fixed pin, so the existing spin continues around the same visual axis. */
.pc-pinwheel-head {
  position: absolute;
  z-index: 2;
  width: 250%;            
  max-width: none;         /* the global img cap would clamp this back to 100% */
  height: auto;
  left: -85.3%;            /* hub remains on the static pin */
  top: -44.2%;
  /* pivot on the TRUE hub — the centre of the enclosed circle where the four
     sails converge, measured from the asset (hub interior bbox centre =
     50.65% / 49.02% of the 1254² source). The old 47.4% y sat ~1.6% above it,
     so the hub orbited ~2.3px each turn instead of spinning in place. Rotation
     coordinate only — rest position (left/top), size and art are unchanged. */
  transform-origin: 50.65% 49.02%;
  /* driven by a gentle-breeze curve, NOT a constant spinner: the head keeps
     turning forward but the SPEED undulates (soft gusts + lulls). The variation
     lives in the keyframes, so timing must stay linear. Pure rotate about the
     true hub ⇒ no centre drift / no positional wobble. */
  animation: pc-pinwheel-spin 15s linear infinite;
}

/* rotation(p) = 720°·p + gentle sine gusts — one seamless loop = two turns, the
   endpoints share value+slope so it never jumps. Monotonic ⇒ always spins forward,
   just faster (gust) and slower (lull), like a light wind. */
@keyframes pc-pinwheel-spin {
  0% { transform: rotate(11.78deg); }
  5% { transform: rotate(63.88deg); }
  10% { transform: rotate(109.28deg); }
  15% { transform: rotate(147.96deg); }
  20% { transform: rotate(181.71deg); }
  25% { transform: rotate(213.22deg); }
  30% { transform: rotate(244.82deg); }
  35% { transform: rotate(277.57deg); }
  40% { transform: rotate(310.90deg); }
  45% { transform: rotate(342.99deg); }
  50% { transform: rotate(371.78deg); }
  55% { transform: rotate(396.07deg); }
  60% { transform: rotate(416.38deg); }
  65% { transform: rotate(435.15deg); }
  70% { transform: rotate(456.12deg); }
  75% { transform: rotate(483.22deg); }
  80% { transform: rotate(519.23deg); }
  85% { transform: rotate(564.76deg); }
  90% { transform: rotate(618.00deg); }
  95% { transform: rotate(675.18deg); }
  100% { transform: rotate(731.78deg); }
}

@media (prefers-reduced-motion: reduce) {
  .pc-pinwheel-head {
    animation: none;
  }
}

@media (max-width: 1080px) {
  .pc-pinwheel {
    display: none;
  }
}

/* the project rope — a scene-spanning foreground curve (enters high at the far
   left, sags to the card zone, lifts right). Static in viewport space; the
   cards glide along it. Path + size are generated at 1:1 pixels by
   clothesline.js in SCENE coordinates (svg top = scene top). */
.pc-rope {
  position: absolute;
  top: 0;
  left: calc(50% - 50vw);
  z-index: 0; /* under the cards (viewport comes later in the DOM) */
  width: 100vw;
  transform: translateY(calc(var(--pc-scene-lift) * -1));
  overflow: visible;
  pointer-events: none;
}

/* ---- one hanging project: flat, front-facing, always the same size.
   JS only slides it horizontally and drops it to the rope's height. ---- */
.pc-card {
  position: absolute;
  left: 0;
  top: 24px;
  width: var(--pc-w);
  transform-origin: 50% -10px; /* the rope/jaw line — sway pivots at the clips */
  will-change: transform;
  container-type: inline-size; /* caption type scales with the card, every tier */
  /* only the current front project reacts to the mouse — slid-away / blurred
     cards take no hover, no "coming soon" note and no clicks. JS toggles
     .pc-card-active on the front card; the drag still works because it is bound
     to .pc-viewport (events pass through the non-interactive cards to it). */
  pointer-events: none;
}

.pc-card.pc-card-active {
  pointer-events: auto;
}

.pc-hang {
  position: relative;
}

/* two-part clothespin (inline SVG, drawn for this site): the BACK blade sits
   behind the hung object, the FRONT blade presses over it — rope → back blade
   → object → front blade. The object's top edge enters the jaw and its outline
   is genuinely hidden under the paper-filled front blade. */
.pc-clip {
  position: absolute;
  top: -20px;      /* rope crosses the card frame at −9.5px = the blade waist (35%) */
  width: 13px;
  height: 30px;
  pointer-events: none;
}

.pc-clip svg,
.pc-l1clip svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible; /* the back blade pokes above the box */
}

.pc-clip-l {
  left: 13%;
  transform: rotate(-2.5deg);
}

.pc-clip-r {
  right: 10%;
  transform: rotate(3deg);
}

.pc-clip-b {
  z-index: 0; /* behind the card */
}

.pc-clip-f {
  z-index: 2; /* over the card: its tip covers ~10px of the top border */
}

/* line1 clips (bun + tee): sized/positioned inline in vw so they scale 1:1
   with the artwork; back blade under the bun (z1) and the artwork (z2),
   front blade above both */
.pc-l1clip {
  position: absolute;
  pointer-events: none;
}

.pc-l1clip-b {
  z-index: 0;
}

.pc-l1clip-f {
  z-index: 3;
}

/* the framed cartoon drawing on line1 (replaces the drawn tee): hangs from the
   two existing clips at 78.41 / 81.17vw — its top edge enters both jaws (~0.5vw
   under each front blade) and the whole sheet tilts with the rope's local slope
   (−7.4°), pivoting from its own top edge so both clip bites stay symmetric. */
.pc-l1pic {
  position: absolute;
  left: calc(50% - 50vw + 79.79vw);
  top: 4.51vw; /* rope at this x ≈ 4.21vw — top edge 0.3vw below, inside the jaws */
  z-index: 1;  /* over the back blades (z0), under the artwork rope (z2) and
                  the front blades (z3) — the clip sandwich stays real */
  width: 6.0vw;
  max-width: none; /* the global img cap would clamp it to the content column */
  height: auto;
  transform: translateX(-50%) rotate(-7.4deg);
  transform-origin: 50% 0;
  pointer-events: none;
  user-select: none;
  transition: filter 240ms ease; /* invert crossfades with the theme switch */
}

body[data-theme="dark"] .pc-l1pic {
  filter: invert(1);
}

/* the user's star / heart / raindrop charms — crisp DOM line art hung from the
   rope. Each PNG is its own string + shape; the string-top sits just above the
   rope line and z1 (under the z2 top-line art) lets the drawn rope paint over
   the attachment, so the charm reads as tied to the line, not floating in front.
   Strokes are native-resolution + solid ink (no downscale-into-the-raster), so
   they carry the same weight as the rope instead of the old faint/thin look. */
.pc-l1charm {
  position: absolute;
  z-index: 1; /* over the back blades / behind the top-line rope art (z2) */
  height: auto;
  max-width: none; /* the global img cap would clamp these to the content column */
  transform-origin: 50% 0; /* hangs from its string-top */
  pointer-events: none;
  user-select: none;
  transition: filter 240ms ease; /* invert crossfades with the theme switch */
}

/* stacked light/dark pair per charm (same classes -> identical geometry).
   Light: the hand-drawn ink original. Dark: a BAKED variant — same strokes
   pre-inverted to cream, interior filled with a translucent gem colour
   (assets built by scripts/make-dark-charms.py; a raster interior cannot be
   filled from CSS). Atomic swap, no crossfade — the pair is opaque-on-
   transparent so strokes align exactly and nothing bleeds mid-toggle. */
.pc-charm-dark {
  opacity: 0;
}

body[data-theme="dark"] .pc-charm-dark {
  opacity: 1;
}

body[data-theme="dark"] img.pc-l1charm:not(.pc-charm-dark) {
  opacity: 0;
}

/* ---- night-time gem light (dark mode only) --------------------------------
   A CONSTANT soft halo — a tight bright core plus a wide faint one, drawn
   with drop-shadow so the light follows each drawing's own alpha (fill
   included, so the whole gem is lit, not just its outline). Stroke pixels
   are untouched: the shadow paints behind the artwork.
   Deliberately NOT animated. A gem does not pulse; making the whole shape
   breathe in and out read as a cheap neon sign. The gem simply sits there
   glowing, and all the sparkle comes from the glints below. */
body[data-theme="dark"] .pc-charm-dark.pc-l1star {
  filter: drop-shadow(0 0 2px rgba(255, 214, 130, 0.85)) drop-shadow(0 0 7px rgba(255, 196, 96, 0.34));
}

body[data-theme="dark"] .pc-charm-dark.pc-l1heart {
  filter: drop-shadow(0 0 2px rgba(255, 150, 175, 0.8)) drop-shadow(0 0 7px rgba(255, 122, 156, 0.3));
}

body[data-theme="dark"] .pc-charm-dark.pc-l1drop {
  filter: drop-shadow(0 0 2px rgba(140, 195, 255, 0.8)) drop-shadow(0 0 7px rgba(110, 175, 250, 0.3));
}

/* ---- the bling: small glints skipping across each gem --------------------
   One <span> per charm shares the charm's positioning classes, so it overlays
   the artwork exactly (media-query size changes included); aspect-ratio gives
   the span the PNG's proportions, since a span has no intrinsic height. Inside
   it, four <i> are four-point star glints sitting at different facets of the
   gem body (positions taken from the measured fill bounding boxes, so none can
   land on the hanging string).

   Each glint pops for a fraction of its cycle — scale 0 -> 1 -> 0 with a small
   rotation, ~8% of the period — and each has its OWN negative delay, so the
   four fire at four different moments and the gem twinkles irregularly instead
   of blinking as a unit. Nothing else moves: the gem's own light is constant.

   ▼ TUNING: --s = glint size (% of the charm's width), --x/--y = position on
     the gem, --d = when in the cycle it fires. Duration lives on the charm. ▼ */
.pc-gem-spark {
  opacity: 0;
  z-index: 2; /* glints sit over the charm art */
}

.pc-gem-spark.pc-l1star { aspect-ratio: 277 / 477; }
.pc-gem-spark.pc-l1heart { aspect-ratio: 272 / 566; }
.pc-gem-spark.pc-l1drop { aspect-ratio: 297 / 1199; }

body[data-theme="dark"] .pc-gem-spark {
  opacity: 1;
}

/* Soft light, not a pasted shape. These charms render at only ~11-16px wide,
   so a hard-edged geometric star cannot resolve at that size — it just reads as
   a crisp white blob stuck onto hand-drawn art, which is what made the earlier
   glints feel abrupt. A radial gradient fading to nothing gives a catch of
   light that is bright at its centre and has no edge to snag the eye. */
.pc-gem-spark i {
  position: absolute;
  width: var(--s, 20%);
  aspect-ratio: 1;
  left: var(--x, 50%);
  top: var(--y, 50%);
  background: radial-gradient(
    circle closest-side,
    rgba(255, 255, 255, 0.95) 0%,
    rgba(255, 255, 255, 0.6) 32%,
    rgba(255, 255, 255, 0) 72%
  );
  transform: scale(0);
}

/* star — four facets around its body (fill bbox x 15-85%, y 55-99%) */
.pc-gem-spark.pc-l1star i:nth-child(1) { --s: 26%; --x: 29%; --y: 65%; --d: -0.2s; }
.pc-gem-spark.pc-l1star i:nth-child(2) { --s: 18%; --x: 63%; --y: 71%; --d: -1.3s; }
.pc-gem-spark.pc-l1star i:nth-child(3) { --s: 22%; --x: 45%; --y: 86%; --d: -2.4s; }
.pc-gem-spark.pc-l1star i:nth-child(4) { --s: 15%; --x: 22%; --y: 82%; --d: -3.1s; }

/* heart — fill bbox x 11-89%, y 61-93% */
.pc-gem-spark.pc-l1heart i:nth-child(1) { --s: 26%; --x: 27%; --y: 68%; --d: -0.6s; }
.pc-gem-spark.pc-l1heart i:nth-child(2) { --s: 18%; --x: 61%; --y: 70%; --d: -1.9s; }
.pc-gem-spark.pc-l1heart i:nth-child(3) { --s: 22%; --x: 44%; --y: 83%; --d: -3.0s; }
.pc-gem-spark.pc-l1heart i:nth-child(4) { --s: 15%; --x: 70%; --y: 78%; --d: -4.1s; }

/* raindrop — its body is the lower part of a very tall canvas (y 58-99%) */
.pc-gem-spark.pc-l1drop i:nth-child(1) { --s: 30%; --x: 33%; --y: 70%; --d: -0.4s; }
.pc-gem-spark.pc-l1drop i:nth-child(2) { --s: 22%; --x: 62%; --y: 77%; --d: -1.8s; }
.pc-gem-spark.pc-l1drop i:nth-child(3) { --s: 26%; --x: 44%; --y: 87%; --d: -3.3s; }
.pc-gem-spark.pc-l1drop i:nth-child(4) { --s: 18%; --x: 29%; --y: 83%; --d: -4.6s; }

/* one glint per cycle, each on its own clock */
body[data-theme="dark"] .pc-gem-spark.pc-l1star i  { animation: pc-glint 3.8s ease-in-out var(--d) infinite; }
body[data-theme="dark"] .pc-gem-spark.pc-l1heart i { animation: pc-glint 4.6s ease-in-out var(--d) infinite; }
body[data-theme="dark"] .pc-gem-spark.pc-l1drop i  { animation: pc-glint 5.4s ease-in-out var(--d) infinite; }

/* a quick catch of light: in and out inside ~8% of the cycle, so each glint
   is a blink of light rather than a shape that fades up and down */
@keyframes pc-glint {
  0%, 8%, 100% { transform: scale(0) rotate(0deg); }
  4% { transform: scale(1) rotate(18deg); }
}

@media (prefers-reduced-motion: reduce) {
  .pc-gem-spark i {
    animation: none;
    transform: scale(0); /* no glints — the gems' constant light carries it */
  }
}

.pc-l1star {
  left: calc(50% - 50vw + 12.82vw);
  top: 6.65vw;
  width: 1.24vw;
}

.pc-l1heart {
  left: calc(50% - 50vw + 63.91vw);
  top: 5.96vw;
  width: 1.11vw;
}

.pc-l1drop {
  left: calc(50% - 50vw + 94.21vw);
  top: 1.93vw;
  width: 0.84vw;
}

.pc-card-link {
  position: relative;
  z-index: 1; /* between the clip's back and front blades */
  display: block;
  color: var(--ink);
  -webkit-user-drag: none;
}

.pc-card-link img {
  -webkit-user-drag: none;
  pointer-events: none;
}

/* the card itself: soft paper, thin sketchy border, uneven hand-drawn radii.
   Landscape; a small image panel on top, title/year below it — never inside it. */
.pc-card-body {
  position: relative;
  display: grid;
  grid-template-rows: 1fr auto;
  gap: clamp(8px, 4cqw, 16px);
  aspect-ratio: 8 / 5; /* flatter landscape notes */
  padding: clamp(10px, 5cqw, 24px);
  background: var(--pc-card-bg);
  /* Paper edge, deliberately finer than the 1.5px rope + clips: the card is a
     large shape, so its outline carries far more ink at the same width than a
     hairline rope does.
     CSS border-width SNAPS to whole device pixels — on a 2× display the only
     steps are 1 / 1.5 / 2px, and 1.5 read heavy while 1 read light. So the
     line is drawn in TWO parts: a crisp device-aligned 1px border plus a
     0.25px ring hugging it from outside (box-shadow spread is painted, not
     laid out, so it antialiases at fractional widths and follows the same
     uneven radii). Together they read as one ~1.25px stroke.
     --pc-card-edge is the knob: 0 = plain 1px, 0.5 = ~1.5px. */
  --pc-card-edge: 0.25px;
  border: 1px solid var(--pc-line);
  box-shadow: 0 0 0 var(--pc-card-edge) var(--pc-line);
  border-radius: 26px 21px 27px 19px / 21px 27px 20px 26px;
  /* paper + ink fade on the theme-switch clock (no hover lift — the cards
     hang still; only the destination hint responds to hover) */
  transition:
    background-color 240ms ease,
    border-color 240ms ease,
    box-shadow 240ms ease;
}

/* the project image sits in its own restrained panel — airy, never crowded */
.pc-media {
  display: grid;
  place-items: center;
  min-height: 0;
  overflow: hidden;
  border-radius: 16px 13px 17px 12px / 13px 17px 12px 16px;
  transition: background-color 240ms ease; /* theme switch */
}

.pc-media img {
  max-width: 58%;
  max-height: 35%;
  object-fit: contain;
  transition: transform 320ms ease;
}

/* pure-image cards: no lift, but the drawing zooms in slightly on hover
   (clipped by the card's overflow:hidden so it never spills past the border) */
.pc-card-cover .pc-card-link:not(.pc-card-static):hover .pc-media img,
.pc-card-cover .pc-card-link:not(.pc-card-static):focus-visible .pc-media img {
  transform: translate(var(--cover-x, 0px), var(--cover-y, 0px)) scale(calc(var(--cover-scale, 1) * 1.045));
}

/* full hand-drawn scene covers: they arrive on white paper, so the panel is
   white and the art is contained (never cropped) — the drawing's own margin
   blends into the panel, the letterbox bands are invisible. The image is taken
   out of flow (absolute) so its intrinsic square size can't blow out the card's
   grid row; it fills the panel box and object-fit keeps the whole scene visible. */
.pc-media-wentong,
.pc-media-voderrn,
.pc-media-health,
.pc-media-llm {
  position: relative;
  background: #fff;
}

.pc-media-wentong img,
.pc-media-voderrn img,
.pc-media-health img,
.pc-media-llm img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  /* per-card adjust knobs (see the block right below): size + nudge */
  transform: translate(var(--cover-x, 0px), var(--cover-y, 0px)) scale(var(--cover-scale, 1));
  transition: transform 320ms ease, opacity 240ms ease;
}

/* ════════════════════════════════════════════════════════════════════
   ▼ PER-IMAGE ADJUST KNOBS — edit these three numbers for each card ▼
     --cover-scale : size    (1 = default, 1.2 = 20% bigger, 0.85 = smaller)
     --cover-x     : move L/R (positive = right, negative = left, in px)
     --cover-y     : move U/D (positive = down,  negative = up,   in px)
   The drawings used to run edge to edge in the card; 0.8 pulls them back off
   the border on EVERY screen so each card reads as art pinned on paper. The
   card box itself is untouched, so no layout moves — desktop included.
   ════════════════════════════════════════════════════════════════════ */
.pc-media-wentong img { --cover-scale: 0.65;  --cover-x: -2px;  --cover-y: 0px; } /* Land of Wisdom (AI reading) */
.pc-media-voderrn img { --cover-scale: 0.65;  --cover-x: 0px;  --cover-y: 0px; } /* Voderrn (fashion mirror) */
.pc-media-health  img { --cover-scale: 0.55;  --cover-x: 0px;  --cover-y: 0px; } /* Healthcare (dotor + teddy) */
.pc-media-llm     img { --cover-scale: 0.7;  --cover-x: 0px;  --cover-y: 0px; } /* Coming soon (LLM scanner) */

/* quiet striped placeholder */
.pc-media-soon {
  background:
    repeating-linear-gradient(90deg, rgba(0, 0, 0, 0.04) 0 30px, transparent 30px 62px),
    var(--pc-card-bg);
}

/* DARK MODE: swap to the pre-baked dark variant of each drawing — WHITE line-art
   on BLACK with the orange bun kept AND the eyes/sunglasses left dark (a plain CSS
   invert whitened the eyes, which read as creepy). The light + dark <img>s are
   stacked and crossfade on the theme toggle. */
body[data-theme="dark"] .pc-media-wentong,
body[data-theme="dark"] .pc-media-voderrn,
body[data-theme="dark"] .pc-media-health,
body[data-theme="dark"] .pc-media-llm {
  background: #000;
}

/* Both covers are FULLY OPAQUE (verified: 0% transparent pixels; the light one
   has a baked #fff ground, the dark one a baked #000 ground) and the dark <img>
   is the later sibling, so it paints ON TOP of the light one.

   Two failure modes had to be dodged at once:
   • Cross-fading them in OPPOSITE directions (light 1→0 while dark 0→1) drops
     the combined coverage to 1-(1-.5)(1-.5) = 0.75 at the midpoint, so the
     panel behind bleeds through and each drawing flashes pale mid-toggle.
   • Simply parking the light cover at opacity 1 underneath fixes that, but the
     covers are scaled (--cover-scale), so the dark one's edge lands on
     fractional pixels and antialiases — letting the white ground beneath show
     as a hairline. Pure white on pure black reads as a hard white frame.

   So the light cover is a BASE LAYER that is only ever removed once the dark
   one is already solid: it snaps ON instantly when leaving dark (giving the
   fading dark cover something opaque to sit on) and snaps OFF only after the
   dark cover's 240ms fade has finished (so it can never halo through). Both
   switches are 0s — invisible, because at each moment the other layer fully
   covers it. Coverage is 1 throughout, and nothing white survives underneath. */
.pc-cover-dark {
  opacity: 0;
}

body[data-theme="dark"] .pc-cover-dark {
  opacity: 1;
}

img.pc-cover-light {
  opacity: 1;
}

body[data-theme="dark"] img.pc-cover-light {
  opacity: 0;
}

/* ATOMIC SWAP — do not cross-fade these covers.
   Both are opaque and tonally inverted, and they are scaled (--cover-scale), so
   the top one's edge antialiases. Any frame in which it is partially
   transparent shows the other cover's ground through that edge — pure white on
   pure black, which reads as a hard frame. Fading them in opposite directions
   instead drops coverage to 0.75 mid-way and the panel bleeds through. Both
   were tried; both flash. There is no crossfade of two opaque inverted images
   that avoids it, so the artwork and its ground switch in ONE frame together:
   no opacity transition on the covers, no background transition on the panel
   or on the pure-image card body. Nothing is ever partially transparent, so
   there is no intermediate state to flash. */
img.pc-cover-light,
img.pc-cover-dark {
  transition: transform 320ms ease; /* opacity deliberately not transitioned */
}

.pc-media-wentong,
.pc-media-voderrn,
.pc-media-health,
.pc-media-llm,
.pc-card-cover .pc-card-body {
  transition: none;
}

/* pure-image cards: the entire card IS the hand-drawn scene — the media panel
   fills the whole card body edge-to-edge (white ground, art contained so nothing
   crops), the text caption and corner doodle are dropped. The card stays a link
   (the whole drawing is clickable) and the coming-soon note still peeks from
   behind its bottom. Light: white card; dark: swaps to the baked dark image (see below). */
.pc-card-cover .pc-card-body {
  grid-template-rows: 1fr;
  padding: 0;
  overflow: hidden;
  background: #fff;
}

body[data-theme="dark"] .pc-card-cover .pc-card-body {
  background: #000;
}

.pc-card-cover .pc-caption,
.pc-card-cover .pc-mini {
  display: none;
}

.pc-card-cover .pc-media {
  border-radius: 0; /* the card body's rounded overflow does the clipping */
}

body[data-theme="dark"] .pc-media-soon {
  background:
    repeating-linear-gradient(90deg, rgba(255, 255, 255, 0.05) 0 30px, transparent 30px 62px),
    var(--pc-card-bg);
}

.pc-soon-note {
  color: color-mix(in srgb, var(--ink) 32%, transparent);
  font-size: clamp(14px, 1.15vw, 18px);
  letter-spacing: 0.24em;
}

/* a small clean "coming soon" paper tucked BEHIND each project card. On hover it
   slides down from behind the card's bottom edge and fades in; its top edge stays
   hidden behind the card so the whole text reads below it. White, thin black
   border, Gaegu, pure-black text — no shadow, icon or underline. It sits behind
   the card link (z1) and takes no layout space, so it never shifts anything. */
.pc-note {
  /* how far the note slides out on hover — bigger = more of it shows below the
     card (one place to tune the reveal amount) */
  --pc-note-peek: 18px;
  position: absolute;
  left: 50%;
  top: 100%;                          /* top edge anchored to the card's bottom */
  z-index: 0;                         /* behind the card link — the card covers its top */
  transform: translate(-50%, -100%);  /* rest: fully tucked up behind the card */
  /* top padding is the slice that stays hidden behind the card; the text sits
     clear of the card edge in the revealed band */
  padding: 9px 14px 3px;
  background: #ffffff;
  border: 1px solid #111111;
  border-radius: 5px;
  font-family: var(--hand);
  font-size: 14.5px;                   /* text size */
  line-height: 1;
  letter-spacing: 0.02em;
  color: #000000;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition:
    transform 250ms ease,
    opacity 250ms ease;
}

/* hover the card: the note peeks --pc-note-peek below the bottom edge, fades to full */
.pc-hang:hover .pc-note {
  transform: translate(-50%, calc(-100% + var(--pc-note-peek)));
  opacity: 1;
}

/* dark mode: keep it a refined dark paper with light ink instead of a stark
   bright rectangle against the dark cards */
body[data-theme="dark"] .pc-note {
  background: #1b1917;
  border-color: #e8e0d6;
  color: #f3efe9;
}

/* tiny sparse doodles inside cards */
.pc-mini {
  position: absolute;
  left: var(--mx, auto);
  right: var(--mr, auto);
  top: var(--my, auto);
  bottom: var(--mb, auto);
  width: 14px;
  height: 14px;
  opacity: 0.75;
}

.pc-mini-tag {
  width: 17px;
  height: 20px;
}

.pc-mini-tag path,
.pc-mini-tag rect {
  stroke: var(--pc-blue);
  stroke-width: 1.4;
  fill: none;
}

.pc-mini-tag circle {
  fill: var(--pc-blue);
}

.pc-mini-heart path {
  stroke: #7fae86;
  stroke-width: 1.5;
}

/* caption inside the card, under the image panel — a handwritten two-row label:
   title + year share the first line, subtitle + destination hint the second */
.pc-caption {
  display: grid;
  gap: 3px;
  padding: 0 2px;
}

.pc-cap-main,
.pc-cap-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
}

.pc-caption h3 {
  margin: 0;
  font-family: var(--hand);
  font-size: clamp(12px, 5.2cqw, 22px);
  font-weight: 700;
  letter-spacing: 0.14em;
  line-height: 1.15;
  text-transform: uppercase;
}

.pc-sub {
  color: color-mix(in srgb, var(--ink) 58%, transparent);
  font-size: clamp(10px, 4cqw, 17px);
  line-height: 1.2;
}

.pc-year {
  flex: none;
  color: var(--orange-deep);
  font-size: clamp(10px, 3.8cqw, 16px);
  letter-spacing: 0.12em;
}

/* quiet destination hint — says where the card leads; warms up on hover */
.pc-go {
  flex: none;
  color: color-mix(in srgb, var(--ink) 42%, transparent);
  font-size: clamp(9px, 3.5cqw, 15px);
  letter-spacing: 0.08em;
  white-space: nowrap;
  transition: color 200ms ease;
}

.pc-go-arr {
  display: inline-block;
  transition: transform 200ms ease;
}

.pc-card-link:not(.pc-card-static):hover .pc-go,
.pc-card-link:not(.pc-card-static):focus-visible .pc-go {
  color: var(--orange-deep);
}

.pc-card-link:not(.pc-card-static):hover .pc-go-arr,
.pc-card-link:not(.pc-card-static):focus-visible .pc-go-arr {
  transform: translateX(2.5px);
}

/* (the original ≤1080 carousel tier was removed 2026-07-15: the sketch-based
   661–1080 + ≤660 tiers later in this file re-set every variable it declared
   at the same specificity, so it had been fully shadowed for its whole range) */

@media (max-width: 660px) {
  .pc-scene {
    /* Phones — proportions measured from the user's sketch at 390px and
       expressed in ONE unit (vw) for every element, so the whole scene is a
       uniform zoom at any width in this tier. The old mix (vw card + px-frozen
       bun/pinwheel + vw rope art) meant a 640px window showed a huge card next
       to a frozen bun and speck-sized charms. */
    /* user pass 3: whole scene scaled down ~20% — 小巧精致, ratios unchanged */
    --pc-w: 35vw;                          /* main card */
    --pc-inset: calc(50vw - var(--pc-w) / 2); /* CENTERED, as drawn — the next
                                                card peeks at the right edge */
    --pc-gap: 16px;
    --pc-fade-l: 10px;
    --pc-fade-r: 9vw;
    --clothesline-drop: 36px;
  }

  .pc-top {
    height: calc(9.16vw + 14vw * 0.66 + 8px);
    margin-top: 0;
    top: var(--clothesline-drop);
  }

  /* mascot: pure vw, never frozen — keeps the sketch's mascot:card ratio */
  .pc-bun {
    width: 14vw;
  }

  /* the hanging drawing reads nearly as LARGE as the mascot in the sketch —
     it was 6vw (a third of the bun) here. Its clips sit at fixed rope spots
     (78.08 / 80.84vw), so the wider sheet just extends symmetrically around
     them; top edge stays inside the jaws. */
  .pc-l1pic {
    width: 12.5vw;
  }

  /* charms: clearly-visible shapes in the sketch, not specks */
  .pc-l1star { width: 3.1vw; }
  .pc-l1heart { width: 2.7vw; }
  .pc-l1drop { width: 2vw; }

  /* the clothespins grip items ~10× their width on desktop; keep that ratio
     for the enlarged bun/drawing (inline vw sizes need the !important).
     Growing in place: recenter by half the width delta, jaw top stays on
     the rope, blades reach deeper over the item — still "holding" it. */
  .pc-l1clip {
    width: 1.35vw !important;
    height: 2.3vw !important;
    margin-left: -0.31vw;
  }

  /* tall enough to hold the card at its resting drop (top + rope drop + body),
     so the scene can never bleed into the section below now that the section
     hugs its content */
  .pc-track {
    height: calc(var(--pc-w) / 1.6 + 96px);
  }

  /* phones keep the larger, legible caption type on the 72vw card */
  .pc-card-body {
    padding: 14px;
  }

  .pc-caption h3 {
    font-size: 17px;
  }

  .pc-sub {
    font-size: 13px;
  }

  .pc-year {
    font-size: 12px;
  }

  .pc-go {
    font-size: 12px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .pc-hang {
    transform: none;
  }

  .pc-card-link:hover .pc-media img,
  .pc-card-link:hover .pc-go-arr {
    transform: none;
  }
}

/* ===========================================================
   Clothesline work section — portrait tablet + phone (sketch 2).
   Desktop (landscape, >1180) never enters these queries: the wide
   composition there is final and must not move.

   The pinwheel is drawn by TWO layers whose rendered art is much bigger
   than the .pc-pinwheel box: the rest layer is scale(3.5) from its own
   top edge, so the drawing spans 3.5 × the box width and hangs 2.5 × the
   box height BELOW the box. Anchoring the box at 2.5 × its size above the
   scene floor therefore lands the drawing's feet exactly on that floor,
   and reserving 3.5 × its size of padding gives it the band to stand in —
   the "breathing room" under the cards in the sketch.
   =========================================================== */

/* ---- portrait tablets (iPad 768/834/1024 upright) ----
   The ≤1080 tier was tuned for short landscape windows: it leaves a small
   card pinned to the far LEFT with the preview stranded mid-screen. Per the
   sketch the card grows and sits centre-right, with the next card peeking in
   from the right edge. */
/* Applies to every mid-size viewport: portrait tablets up to 1180 AND any
   661–1080 window regardless of orientation — a resized ~850px browser window
   is landscape by the numbers but needs the same composition; the orientation
   gate alone let it fall through to the old left-pinned layout. 1081–1180
   landscape keeps the desktop look (same boundary the old tier used). */
@media (min-width: 661px) and (max-width: 1080px), (min-width: 661px) and (max-width: 1180px) and (orientation: portrait) {
  .pc-scene {
    /* user pass 3: whole scene scaled down ~20% — 小巧精致, ratios unchanged */
    --pc-w: 26vw;      /* main project card */
    --pc-inset: 43vw;  /* its resting left edge → card centre at 60% (sketch: 59%) */
    --pc-gap: 5vw;
    --pc-fade-l: 14px;
    /* the fade must MELT the peeking card's outer edge, not erase it — at 10vw
       it swallowed the whole 123px shoulder and the swipe affordance vanished */
    --pc-fade-r: 5vw;
    --pw-size: clamp(45px, 6.2vw, 55px);
    padding-bottom: calc(var(--pw-size) * 3.5 + 26px);
  }

  /* hold the card at its resting drop so the scene hugs its own content */
  .pc-track {
    height: calc(var(--pc-w) / 1.6 + 110px);
  }

  /* top rope, sketch proportions at a delicate scale — same one-unit (vw)
     rule as phones. Desktop's clamp floor (78px) freezes the bun below
     ~950px, so mid-size windows showed a frozen mascot beside vw rope art. */
  .pc-top {
    height: calc(9.16vw + 9vw * 0.66 + 10px);
  }

  .pc-bun {
    width: 9vw;
  }

  .pc-l1pic {
    width: 8.5vw;
  }

  .pc-l1star { width: 1.9vw; }
  .pc-l1heart { width: 1.7vw; }
  .pc-l1drop { width: 1.3vw; }

  .pc-l1clip {
    width: 0.9vw !important;
    height: 1.53vw !important;
    margin-left: -0.09vw;
  }

  /* the art also overhangs 1.375 × the box to the LEFT (same scale, 55% origin),
     so add that back — then the vw term IS the drawing's true left margin */
  .pc-pinwheel {
    display: block;
    left: calc(50% - 50vw + 8vw + var(--pw-size) * 1.375);
    bottom: calc(var(--pw-size) * 2.5);
    width: var(--pw-size);
  }
}

/* ---- phones: same idea, smaller. The cap tracks 6vw all the way to the
   tier edge (a fixed cap froze it mid-tier while the card kept growing) ---- */
@media (max-width: 660px) {
  .pc-scene {
    --pw-size: clamp(30px, 7.5vw, 48px);
    padding-bottom: calc(var(--pw-size) * 3.5 + 16px);
  }

  .pc-pinwheel {
    display: block;
    left: calc(50% - 50vw + 6vw + var(--pw-size) * 1.375);
    bottom: calc(var(--pw-size) * 2.5);
    width: var(--pw-size);
  }
}

/* ===========================================================
   "What I like" — TAP-to-switch on tablet & phone (sketch 4, revised).
   Desktop (>1080 landscape) keeps the hover scatter and is untouched.

   Two things from the first draft were wrong and are fixed here:
   1. The giant Frijole result label is anchored to the right edge, so a
      real word ("PHOTOGRAPHY", "ANIMATION") extends far enough left to
      land ON the list column — unavoidable overlap on any narrow screen.
      It is a desktop-only flourish and is DROPPED on these tiers.
   2. Scroll-stepping hijacked the page scroll. The interaction is now a
      plain TAP on a list item (JS), matching the user's request.

   Layout: a calm 2-column on tablet (active illustration+caption LEFT,
   list RIGHT) and a stacked column on phone (illustration+caption ABOVE,
   list BELOW). Only the active row's illustration/caption is shown — the
   existing roll-masks already clip the other eight to nothing, so all
   nine asides can overlap at one pinned spot.
   =========================================================== */
@media (max-width: 1080px), (max-width: 1180px) and (orientation: portrait) {
  .studio-services {
    min-height: 100svh;
    padding: clamp(64px, 10vh, 104px) 0;
  }

  .studio-services-inner {
    /* aside + big label are positioned against THIS box; the eyebrow is lifted
       out of flow so the list is the only centred child. --svc-row is a FIXED
       row pitch so `50% + (row-i − 4)·svc-row` lands exactly on each row's
       centre (index 4 = the middle of 9 items = the list's centre). */
    --svc-row: clamp(46px, 5.6vh, 58px);
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: center;
    width: min(100% - clamp(48px, 12vw, 128px), 880px);
    min-height: calc(100svh - clamp(128px, 20vh, 208px));
    margin: 0 auto;
  }

  .studio-services-eyebrow {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    margin: 0;
    text-align: center;
    font-size: clamp(15px, 1.8vw, 18px);
  }

  /* list is the centre column */
  .studio-service-list {
    margin-left: 34%;
  }

  .studio-service {
    position: static;
    display: flex;
    align-items: center;
    height: var(--svc-row);
    padding: 0;
    cursor: pointer;
  }

  .studio-service-name {
    margin-left: 0;
    font-size: clamp(22px, 2.3vw, 26px); /* sketch: 22–26px */
  }

  /* every item wears a quiet dot; the active one turns solid (sketch) */
  .studio-service-name::before {
    top: 50%;
    left: -1.15em;
    transform: translateY(-50%);
    opacity: 0.26;
  }

  .studio-service.is-active .studio-service-name {
    transform: none; /* the desktop -48px hover shift would jiggle the column */
    font-weight: 700;
  }

  .studio-service.is-active .studio-service-name::before {
    opacity: 1;
    transform: translateY(-50%);
  }

  /* active illustration + caption, stacked in the LEFT column, at the ACTIVE
     row's own height — so illustration · item · label read as one line */
  .studio-service-aside {
    position: absolute;
    top: calc(50% + (var(--row-i, 4) - 4) * var(--svc-row));
    left: 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 14px;
    width: calc(34% - clamp(16px, 3vw, 36px));
    max-width: none;
    transform: translateY(-50%);
  }

  .studio-service-media {
    width: clamp(128px, 18vw, 168px);  /* sketch: 140–180 square */
    height: clamp(128px, 18vw, 168px);
    border-radius: 12px;
  }

  .studio-service-media .studio-roll-inner,
  .studio-service-media .studio-roll-inner img {
    border-radius: 12px;
  }

  .studio-service-caption {
    max-width: 100%;      /* full column width — no more one-word-per-line wraps */
    font-size: clamp(15px, 1.7vw, 18px);
    line-height: 1.4;
  }

  /* giant result label — back on tablets (the right side reads too empty
     without it). LEFT-anchored just past the list so long words ("PHOTOGRAPHY")
     extend toward the edge instead of growing leftward INTO the list (the bug
     that made the first draft overlap). Tracks the active row's height. */
  .studio-service-big {
    display: block;
    top: calc(50% + (var(--row-i, 4) - 4) * var(--svc-row));
    left: 56%;
    right: auto;
    font-size: clamp(30px, 3.7vw, 46px); /* sketch: 36–48, trimmed so the
                                            longest word clears the edge at 768 */
    line-height: 1.1;
    white-space: nowrap;
    transform: translateY(-50%);
  }
}

/* phones: single centred column — illustration + caption ABOVE the list */
@media (max-width: 660px) {
  .studio-services {
    padding: clamp(56px, 8vh, 88px) 0;
  }

  .studio-services-eyebrow {
    font-size: 15px;
  }

  /* phones are too narrow for the giant word — the user confirmed dropping it
     here is fine; it stays only on tablet/desktop */
  .studio-service-big {
    display: none;
  }

  .studio-service-list {
    /* the aside anchors to the list here; reserve its height as top padding so
       the items sit BELOW the illustration */
    --aside-h: clamp(196px, 52vw, 232px);
    position: relative;
    margin-left: 0;
    margin-inline: auto;
    width: max-content;
    padding-top: calc(var(--aside-h) + clamp(16px, 3vh, 28px));
  }

  .studio-service {
    justify-content: flex-start;
  }

  .studio-service-name {
    font-size: clamp(18px, 5vw, 20px); /* sketch: 18–20px */
  }

  .studio-service-aside {
    top: 0;
    left: 50%;
    height: var(--aside-h);
    width: auto;
    align-items: center;
    justify-content: flex-start;
    gap: 12px;
    transform: translateX(-50%);
  }

  .studio-service-media {
    width: clamp(110px, 32vw, 130px);  /* sketch: 110–130 square */
    height: clamp(110px, 32vw, 130px);
  }

  .studio-service-caption {
    max-width: 82vw;
    text-align: center;
    font-size: clamp(16px, 4.3vw, 18px); /* sketch: 16–18px */
  }
}

/* ===========================================================
   Two hand-written Gaegu annotations (user request).
   Each block below starts with its ADJUST KNOBS — edit those
   numbers to move/resize the text; nothing else needs touching.
   =========================================================== */

/* ════ "Things I've worked on" under the project cards — pc-worked-note ADJUST KNOBS ════
   --pc-worked-x    : horizontal CENTRE of the note, in viewport coords.
                      Default = the front card's resting centre, so it stays
                      under the card on desktop / tablet / phone automatically.
                      Use any length, e.g. 50vw = screen centre, 63vw, 480px …
   --pc-worked-gap  : vertical gap between the cards and the arrow tip (px).
   --pc-worked-size : caption font size.
   --pc-worked-arrow: arrow height (width follows automatically).            */
.pc-scene {
  --pc-worked-x: calc(var(--pc-inset) + var(--pc-w) / 2);
  --pc-worked-gap: -10px;
  /* identical to the hero's "play with me!" label (.hh-annot) so the two
     hand-written annotations read as the same pen at every viewport */
  --pc-worked-size: clamp(15px, 1.25vw, 19px);
  --pc-worked-arrow: clamp(25px, 2vw, 35px);
}

/* in normal flow right below the carousel: the cards slide, this stays put.
   The viewport above rides translateY(-lift), so the note rides the same
   lift to keep the visual gap honest. */
.pc-worked-note {
  position: relative;
  z-index: 1;
  display: grid;
  justify-items: center;
  gap: 8px;
  width: max-content;
  margin: var(--pc-worked-gap) 0 0 calc(50% - 50vw + var(--pc-worked-x));
  font-family: var(--hand);
  transform: translateX(-50%) translateY(calc(var(--pc-scene-lift) * -1));
  pointer-events: none;
  user-select: none;
}

.pc-worked-arrow {
  height: var(--pc-worked-arrow);
  width: auto;
}

.pc-worked-arrow path {
  fill: none;
  stroke: var(--pc-line);
  /* User units, NOT screen px: this SVG scales its stroke (no
     vector-effect), viewBox 22x46 drawn at ~25.6px tall => scale ≈0.556.
     1.7 × 0.556 ≈ 0.95px on screen — the same rendered weight as the hero's
     "play with me!" arrow (1.6 user units at its own ≈0.592 scale). This is
     annotation weight and deliberately lighter than the 1.5px structural
     clothesline lines. */
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.pc-worked-text {
  font-size: var(--pc-worked-size); /* = the hero label's size (knob above) */
  line-height: 1.2;
  font-weight: 300;
  color: var(--pc-line);
}

/* ════ "Feel free to reach out :)" on the film-strip screen — reach-note ADJUST KNOBS ════
   The note FLOWS right beneath the strip (it sits in the same centred flex
   column as the strip), so the gap below the strip is CONSTANT at every screen
   size — it tracks the strip instead of drifting off a fixed section-bottom
   offset. The strip + note centre together as one block.
   --reach-note-x    : distance from the RIGHT edge of the screen.
   --reach-note-gap  : vertical gap between the strip and the note.
   --reach-note-size : font size.                                            */
.marquee-section {
  position: relative;
  --reach-note-x: 50px;
  --reach-note-gap: 12px;
  --reach-note-size: clamp(16px, 1.15vw, 20px);
  font-weight: 300;
}

.reach-note {
  align-self: flex-end;                 /* right edge of the (column) section */
  margin: var(--reach-note-gap) var(--reach-note-x) 0 0;
  font-family: var(--hand);
  font-size: var(--reach-note-size);
  line-height: 1.2;
  color: var(--ink);
}
