:root {
  --gumi-ar: 1230 / 963; /* Gumizoo head renders: shared crop aspect, see the build note in CLAUDE.md */
  --bg: #FAF8F2;
  --fg: #111111;
  --muted: #6B6B6B;
  --line: rgba(17, 17, 17, .12);

  --dark-bg: #0B0B0E;
  --dark-fg: #F4F0DF;
  --dark-muted: #A9A493;
  --dark-line: rgba(244, 240, 223, .16);

  --pink: #FF3DAC;
  --cobalt: #3154FF;
  --lime: #B8FF38;
  --orange: #FF5C2B;
  --cyan: #26E1DD;

  --gutter: clamp(20px, 5.5vw, 80px);
  --max: 1400px;
  --nav-h: 64px;

  --display: "Bebas Neue", Impact, "Arial Narrow", sans-serif;
  --sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --ease: cubic-bezier(.2, .7, .2, 1);
  --fade: .8s;
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

body.is-dark {
  --bg: var(--dark-bg);
  --fg: var(--dark-fg);
  --muted: var(--dark-muted);
  --line: var(--dark-line);
}

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

html { -webkit-text-size-adjust: 100%; }
html.is-locked { overflow: hidden; }

@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 400 16px/1.6 var(--sans);
  -webkit-font-smoothing: antialiased;
  transition: background-color var(--fade) var(--ease), color var(--fade) var(--ease);
}

h1, h2, h3, h4, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, video { display: block; max-width: 100%; }
a { color: inherit; }
button { font: inherit; color: inherit; }

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

.skip {
  position: absolute;
  left: var(--gutter);
  top: -100px;
  z-index: 100;
  padding: 10px 16px;
  background: var(--fg);
  color: var(--bg);
}
.skip:focus { top: 12px; }

.wrap {
  width: 100%;
  max-width: calc(var(--max) + 2 * var(--gutter));
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.label {
  font: 500 12px/1.4 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  transition: color var(--fade) var(--ease);
}

.display {
  font: 400 clamp(52px, 10vw, 144px)/.86 var(--display);
  text-transform: uppercase;
}

/* Pills: nav links and buttons. The fill colour is set per hover by main.js. */
.pill {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 20px;
  border: 1px solid var(--fg);
  border-radius: 999px;
  background: transparent;
  font: 500 12px/1 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .25s var(--ease), color .25s var(--ease), border-color .25s var(--ease);
}
.pill--quiet { border-color: transparent; padding: 0 12px; }

@media (hover: hover) {
  .js-fill:hover { background: var(--fill); color: var(--on-fill); border-color: var(--fill); }
}
.js-fill:focus-visible { background: var(--fill); color: var(--on-fill); border-color: var(--fill); }

/* Nav */
.nav {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  transition: background-color var(--fade) var(--ease), border-color var(--fade) var(--ease);
}
.nav .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  height: var(--nav-h);
}
.nav__brand {
  --logo-h: 30px;
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: 10px;
  text-decoration: none;
}
/* The mark is the SVG used as a mask, so its colour is just background-color. */
.nav__logo {
  width: calc(var(--logo-h) * 1526 / 998);
  height: var(--logo-h);
  background-color: currentColor;
  -webkit-mask: url("../media/logo/raycrayon-mark.svg") center / contain no-repeat;
          mask: url("../media/logo/raycrayon-mark.svg") center / contain no-repeat;
  transition: background-color .25s var(--ease);
}
@media (hover: hover) {
  .nav__brand:hover .nav__logo { background-color: var(--fill); }
}
.nav__brand:focus-visible .nav__logo { background-color: var(--fill); }
.nav__mark {
  font: 400 28px/1 var(--display);
  letter-spacing: .02em;
  text-transform: uppercase;
  padding-top: 3px;
}
.nav__links { display: flex; gap: 2px; margin-right: -12px; }

@media (max-width: 400px) {
  .nav__brand { --logo-h: 22px; gap: 6px; }
  .nav__mark { font-size: 22px; }
  .nav .pill--quiet { padding: 0 7px; }
  .nav__links { gap: 0; margin-right: -7px; }
}
/* 360px phones with a visible scrollbar leave ~345px: tighten further so the lockup and links still fit. */
@media (max-width: 380px) {
  .nav .pill--quiet { padding: 0 5px; letter-spacing: .06em; }
  .nav__links { margin-right: -5px; }
}

/* Hero: RAYCRAYON as a window. The letters are filled with a duotoned still (accent + off-white, the same
   screen-then-multiply duotone the site uses elsewhere); hovering, focusing or pressing the word moves on to
   the next piece. .has-fill is added by main.js only when background-clip: text is supported. */
.hero .wrap {
  /* The wordmark's font-size, shared so the tagline can scale as a fixed fraction of it. Left unresolved
     here on purpose: cqi resolves where it's used, against this .wrap container. */
  --wordmark: min(calc(100cqi / 3.55), calc(var(--max) / 3.55));
  padding-block: clamp(24px, 3.5vw, 52px) clamp(64px, 8vw, 128px);
  container-type: inline-size;
}
/* The divisor sets the word's width to ~98% of the content box (Bebas RAYCRAYON ≈ 3.48em wide). */
.hero__name {
  margin-top: clamp(14px, 2.2vw, 32px);
  font: 400 var(--wordmark)/.84 var(--display);
  letter-spacing: 0;
  text-transform: uppercase;
}
.hero__word {
  position: relative;
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: var(--fg);
  font: inherit;
  letter-spacing: inherit;
  text-align: center;
  text-transform: inherit;
  cursor: pointer;
}
.hero__word:focus-visible { outline-offset: 6px; }
.hero__fill {
  display: block;
  white-space: nowrap;
}
/* WebGL version (main.js adds .is-gl once it has drawn): a canvas over the text's line box, clipped to the
   letterforms by #wordmark-clip. The text stays in place, invisible, to hold the layout. */
.hero__gl {
  position: absolute;
  inset: 0 0 auto;
  display: none;
  width: 100%;
  pointer-events: none;
  clip-path: url(#wordmark-clip);
}
.hero__word.is-gl .hero__gl { display: block; }
.hero__word.is-gl .hero__fill { visibility: hidden; }
/* Duotone: dark → accent, light → #FAF8F2. Chrome ignores background-blend-mode inside text-clipped
   backgrounds, so the letters use an SVG feColorMatrix filter (one per accent, built in main.js) on the
   clipped greyscale still; the standalone mobile block uses the CSS blend version. */
.hero__word.has-fill .hero__fill {
  background-image: var(--img);
  background-size: cover;
  background-position: center;
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  filter: var(--duo);
}
.hero__filters { position: absolute; }
.hero__swatch-img {
  background-color: var(--tint);
  background-image: linear-gradient(#FAF8F2, #FAF8F2), var(--img);
  background-blend-mode: multiply, screen;
  background-size: cover;
  background-position: center;
}
.hero__swatch { display: none; }

/* At phone size, text-clipped imagery turns to mush: solid ink type, and the still as its own block. */
@media (max-width: 699px) {
  .hero__word.has-fill .hero__fill {
    background: none;
    -webkit-text-fill-color: currentColor;
    color: var(--fg);
    filter: none;
  }
  .hero__gl { display: none !important; }
  .hero__word.is-gl .hero__fill { visibility: visible; }
  .hero__swatch {
    display: grid;
    gap: 8px;
    margin-top: 14px;
    text-align: left;
    font: 400 16px/1.4 var(--sans);
  }
  .hero__swatch-img {
    display: block;
    aspect-ratio: 16 / 5;
    transition: background-color .4s var(--ease);
  }
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.hero__foot {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 24px 40px;
  margin-top: clamp(36px, calc(var(--wordmark) * .2), 80px);
}
/* Uppercase and tracked so it reads as the wordmark's second line, sized as a fixed fraction of the wordmark.
   The 17px floor only engages on phones, where the pure ratio would get too small to read. */
.hero__line {
  font: 600 clamp(17px, calc(var(--wordmark) * .085), 34px)/1.2 var(--sans);
  letter-spacing: .06em;
  text-transform: uppercase;
  text-wrap: balance;
}
/* Focusable so keyboard users get the same accent moment; it fills like the pills (shared .js-fill cycling). */
.hero__line-text {
  margin-left: -.2em;
  padding: .04em .2em .06em;
  -webkit-box-decoration-break: clone;
          box-decoration-break: clone;
  cursor: default;
  transition: background-color .25s var(--ease), color .25s var(--ease);
}
.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}
.hero__actions .pill {
  min-height: 52px;
  padding: 0 26px;
  font-size: 14px;
  font-weight: 600;
}
@media (max-width: 699px) {
  .hero__actions { width: 100%; }
  .hero__actions .pill { flex: 1 1 150px; justify-content: center; padding: 0 18px; font-size: 13px; }
}

/* Grainy gradient hover for pills: the cycled accent (--fill) as a soft gradient with grain on top,
   instead of a flat fill. */
.pill--grain {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}
.pill--grain::before,
.pill--grain::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  pointer-events: none;
  transition: opacity .25s var(--ease);
}
.pill--grain::before {
  background-color: var(--fill);
  background-image:
    radial-gradient(120% 160% at 12% 0%, color-mix(in oklab, var(--fill), white 32%), transparent 60%),
    radial-gradient(100% 140% at 100% 100%, color-mix(in oklab, var(--fill), black 20%), transparent 65%);
}
.pill--grain::after {
  background-image: var(--grain);
  mix-blend-mode: soft-light;
}
@media (hover: hover) {
  .pill--grain.js-fill:hover { background: transparent; }
  .pill--grain:hover::before { opacity: 1; }
  .pill--grain:hover::after { opacity: .5; }
}
.pill--grain.js-fill:focus-visible { background: transparent; }
.pill--grain:focus-visible::before { opacity: 1; }
.pill--grain:focus-visible::after { opacity: .5; }

/* Section bands */
.band {
  padding-block: clamp(56px, 8vw, 112px);
  scroll-margin-top: var(--nav-h);
}
.band--intro { padding-bottom: 0; }
.band--rule > .wrap { border-top: 1px solid var(--line); padding-top: clamp(40px, 6vw, 80px); }
.band--rule { padding-top: 0; }

.section-head {
  display: grid;
  gap: 14px;
  margin-bottom: clamp(32px, 5vw, 64px);
}

/* Selected work intro: the sira_cow turntable loop plays as a background panel behind the heading, the page's
   one continuous motion at rest (plays only while in view, see main.js). A soft off-white wash at the bottom
   keeps the black heading legible over the red character. */
.work-intro {
  position: relative;
  isolation: isolate;
  display: grid;
  align-content: end;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  margin-bottom: clamp(32px, 5vw, 64px);
  padding: clamp(20px, 3vw, 44px);
  background: #F3F4D6;
}
.work-intro .section-head { position: relative; z-index: 1; margin-bottom: 0; }
.spotlight {
  position: absolute;
  inset: 0;
  z-index: 0;
}
.spotlight__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 35%;
}
.spotlight::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(to top, rgba(250, 248, 242, .82), rgba(250, 248, 242, .35) 38%, transparent 62%);
}
@media (min-width: 760px) {
  .work-intro { aspect-ratio: 16 / 8; }
}

.project-head {
  display: grid;
  gap: 14px 48px;
  margin-bottom: clamp(24px, 3vw, 36px);
}
.project-head p:not(.label) { max-width: 52ch; }
@media (min-width: 900px) {
  .project-head { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); align-items: end; }
}

.project-title {
  font: 400 clamp(44px, 7vw, 96px)/.88 var(--display);
  text-transform: uppercase;
}

.grid {
  display: grid;
  gap: clamp(16px, 2vw, 28px);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
}
.grid--wide { grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr)); }

/* Felines: the whole band takes the colour of the hovered piece */
.felines {
  background-color: var(--band, transparent);
  transition: background-color .5s var(--ease);
}
.felines.is-lit {
  --fg: #111111;
  --muted: #111111;
  --line: rgba(17, 17, 17, .22);
  color: #111111;
}

.strip {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(150px, 42vw);
  gap: 12px;
  margin-inline: calc(-1 * var(--gutter));
  padding: 4px var(--gutter) 12px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--gutter);
}
.strip > li { scroll-snap-align: start; }
@media (min-width: 1000px) {
  .strip {
    grid-auto-flow: row;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    margin-inline: 0;
    padding: 0;
    overflow: visible;
  }
}
.strip .card__label { flex-direction: column; align-items: start; }
.strip .card__title { font-size: 20px; }

/* Cards */
.card {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
}
.card:focus-visible { outline-offset: 4px; }

.card__media {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--accent);
  container-type: inline-size;
}
.card__media img,
.card__media video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.card__media video {
  opacity: 0;
  transition: opacity .35s var(--ease);
}
.card.is-playing .card__media video { opacity: 1; }

/* Multi-clip cards: arrows and counter sit in a slim bar over the bottom of the media, just above the label. */
.card__ghost {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity .4s var(--ease);
}
.card__ghost.is-leaving { opacity: 0; }
.clip-card { position: relative; }
.clip-nav {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: calc(var(--label-h, 43px) + 8px);
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  pointer-events: none;
  opacity: 0;
  transition: opacity .2s var(--ease);
}
.clip-card:is(:hover, :focus-within) .clip-nav,
.clip-nav:focus-within { opacity: 1; }
@media (hover: none) {
  .clip-nav { opacity: 1; }
}
.clip-nav__btn {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid rgba(17, 17, 17, .5);
  border-radius: 50%;
  background: rgba(250, 248, 242, .92);
  color: #111111;
  cursor: pointer;
  pointer-events: auto;
  transition: background-color .2s var(--ease), border-color .2s var(--ease);
}
.clip-nav__btn svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.clip-nav__btn:is(:hover, :focus-visible) { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.clip-nav__btn:focus-visible { outline: 2px solid #111111; outline-offset: 2px; }
.clip-nav__count {
  padding: 5px 9px 4px;
  background: rgba(250, 248, 242, .92);
  color: #111111;
  font: 500 11px/1 var(--sans);
  letter-spacing: .1em;
  font-variant-numeric: tabular-nums;
}
@media (hover: hover) {
  .clip-card:hover .card__label { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
  .clip-card:hover .card__meta { color: inherit; }
}

.card__media--block {
  display: grid;
  place-items: center;
  padding: 8%;
  color: var(--on-accent);
}
.card__block-title {
  font: 400 clamp(22px, 13cqi, 96px)/.9 var(--display);
  text-align: center;
  text-transform: uppercase;
}

.card__label {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 2px 12px;
  padding: 10px 12px 8px;
  border: 1px solid var(--line);
  border-top: 0;
  transition: background-color .25s var(--ease), color .25s var(--ease), border-color .25s var(--ease);
}
.card__title {
  font: 400 24px/1 var(--display);
  letter-spacing: .02em;
  text-transform: uppercase;
}
.card__meta {
  font: 500 11px/1.2 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

@media (hover: hover) {
  .card:hover .card__label { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
  .card:hover .card__meta { color: inherit; }
}
.card:focus-visible .card__label { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.card:focus-visible .card__meta { color: inherit; }

/* Pangeo bento. Tiles never crop: each media box keeps its file's native ratio (or letterboxes inside a
   spanned tile). Spans are hand-set per tile role (data-tile in projects.js):
   row 1: two anchors side by side; rows 2–3: the portrait flagship on the left, the bear trio on the right
   (one wide lead over two). Projection-mapping details: calibration marks, a static top-down light wash,
   glowing edge-blend seams in the gutters, and a one-off light sweep on hover. */
.pangeo-stage {
  --seam: rgba(244, 240, 223, .1);
  --mark: rgba(244, 240, 223, .34);
  position: relative;
  isolation: isolate;
}
.pangeo-stage::before {
  content: "";
  position: absolute;
  inset: -14px;
  pointer-events: none;
  background:
    linear-gradient(var(--mark), var(--mark)) left top / 16px 1px,
    linear-gradient(var(--mark), var(--mark)) left top / 1px 16px,
    linear-gradient(var(--mark), var(--mark)) right top / 16px 1px,
    linear-gradient(var(--mark), var(--mark)) right top / 1px 16px,
    linear-gradient(var(--mark), var(--mark)) left bottom / 16px 1px,
    linear-gradient(var(--mark), var(--mark)) left bottom / 1px 16px,
    linear-gradient(var(--mark), var(--mark)) right bottom / 16px 1px,
    linear-gradient(var(--mark), var(--mark)) right bottom / 1px 16px;
  background-repeat: no-repeat;
}
.pangeo-stage::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: radial-gradient(75% 60% at 50% -12%, rgba(244, 240, 223, .1), transparent 75%);
  mix-blend-mode: screen;
}

.pangeo-grid {
  --g: 14px;
  display: grid;
  gap: var(--g);
  grid-template-columns: minmax(0, 1fr);
}
.pangeo-grid .card {
  position: relative;
  box-shadow: 0 0 14px 1px var(--seam);
  transition: box-shadow .35s var(--ease);
}
.pangeo-grid .card__media {
  background: #050507;
  isolation: isolate;
}
.pangeo-grid .card__media img,
.pangeo-grid .card__media video {
  object-fit: contain;
  transition: opacity .35s var(--ease), filter .35s var(--ease);
}
.pangeo-grid .card[data-tile="flagship"] {
  justify-self: center;
  width: min(100%, 400px);
}

/* Hover: one soft light-band sweep across the surface, then Pangeo's pink edge. No label fill here. */
.pangeo-grid .card__media::after {
  content: "";
  position: absolute;
  inset: -10% auto -10% 0;
  z-index: 1;
  width: 45%;
  opacity: 0;
  pointer-events: none;
  background: linear-gradient(105deg, transparent, rgba(244, 240, 223, .32) 50%, transparent);
  transform: translateX(-120%);
}
.pangeo-grid .card:is(:hover, :focus-visible) .card__media::after {
  animation: pangeo-sweep .9s cubic-bezier(.3, .1, .3, 1) 1;
}
@keyframes pangeo-sweep {
  0% { opacity: 0; transform: translateX(-120%); }
  15% { opacity: 1; }
  85% { opacity: 1; }
  100% { opacity: 0; transform: translateX(330%); }
}
.pangeo-grid .card:is(:hover, :focus-visible) {
  box-shadow: 0 0 16px 2px rgba(255, 61, 172, .32);
}
.pangeo-grid .card:is(:hover, :focus-visible) .card__media {
  box-shadow: inset 0 0 0 1px #FF3DAC;
}
.pangeo-grid .card:is(:hover, :focus-visible) .card__media :is(img, video) { filter: brightness(1.08); }
.pangeo-grid .card:is(:hover, :focus-visible) .card__label {
  background: transparent;
  border-color: var(--line);
  color: inherit;
  box-shadow: inset 0 -2px 0 #FF3DAC;
}
.pangeo-grid .card:is(:hover, :focus-visible) .card__meta { color: var(--muted); }

@media (min-width: 760px) {
  .pangeo-grid { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .pangeo-grid .card[data-tile="anchor"] { grid-column: span 6; }
  .pangeo-grid .card[data-tile="flagship"] {
    grid-column: 1 / span 4;
    grid-row: 2 / span 2;
    justify-self: stretch;
    display: flex;
    flex-direction: column;
    width: auto;
  }
  /* The flagship fills the height of the bear rows beside it and letterboxes its video inside (no crop). */
  .pangeo-grid .card[data-tile="flagship"] .card__media {
    flex: 1;
    min-height: 0;
    aspect-ratio: auto !important;
  }
  .pangeo-grid .card[data-tile="bear-lead"] { grid-column: 5 / -1; grid-row: 2; }
  .pangeo-grid .card[data-tile="bear"] { grid-column: span 4; grid-row: 3; }

  /* Registration crosses at three gutter junctions. */
  .pangeo-grid .card[data-tile="anchor"]:first-child::before,
  .pangeo-grid .card[data-tile="flagship"]::before,
  .pangeo-grid .card[data-tile="bear-lead"]::before {
    content: "";
    position: absolute;
    z-index: 3;
    width: 15px;
    height: 15px;
    pointer-events: none;
    background:
      linear-gradient(var(--mark, rgba(244, 240, 223, .34)), var(--mark, rgba(244, 240, 223, .34))) center / 100% 1px no-repeat,
      linear-gradient(var(--mark, rgba(244, 240, 223, .34)), var(--mark, rgba(244, 240, 223, .34))) center / 1px 100% no-repeat;
  }
  .pangeo-grid .card[data-tile="anchor"]:first-child::before {
    right: calc(var(--g) / -2);
    bottom: calc(var(--g) / -2);
    translate: 50% 50%;
  }
  .pangeo-grid .card[data-tile="flagship"]::before {
    right: calc(var(--g) / -2);
    top: calc(var(--g) / -2);
    translate: 50% -50%;
  }
  .pangeo-grid .card[data-tile="bear-lead"]::before {
    left: 50%;
    bottom: calc(var(--g) / -2);
    translate: -50% 50%;
  }
}

/* Gumizoo: a centered heading over the four heads (Blender renders, see main.js), 2×2 on phones and one row of
   four on desktop. Hovering/focusing a tile lifts its head out of the frame, shows its name, floods that one
   tile with its accent and turns the heading that accent (--title-accent, set by main.js). The section itself
   never floods. The event poster asset stays wired up in main.js (still fetched, still appended) but isn't
   part of this section's visible layout — see .gumizoo__poster below. */
.gumizoo {
  position: relative;
  container-type: inline-size;
}
.gumizoo .project-title {
  color: var(--title-accent, #111111);
  transition: color .3s var(--ease);
  /* Restrained extruded/letterpress depth: flat stepped layers in the brand's own ink tone, not tied to
     currentColor (a shadow derived from currentColor would nearly vanish at rest, since the resting color is
     already near-black) — so the same depth reads under every accent and at rest. Only text-shadow is added
     here; color/transition above are untouched, so the hover recolor keeps working exactly as before. */
  text-shadow:
    1px 1px 0 rgba(17, 17, 17, .35),
    2px 2px 0 rgba(17, 17, 17, .28),
    3px 3px 0 rgba(17, 17, 17, .20),
    4px 4px 8px rgba(17, 17, 17, .18);
}

/* Fragments: small motifs scattered on the detail panel. Slots (below) set position and size; the data only
   says which image and crop fills each one, so real PNG cut-outs can replace the crops. */
.gumi-frags {
  position: fixed;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
}
.gumi-frag {
  position: absolute;
  width: var(--s, 72px);
  aspect-ratio: 1;
  opacity: .7;
  rotate: var(--r, 0deg);
  background: var(--frag-img) var(--frag-pos, center) / var(--frag-size, contain) no-repeat;
}
.gumi-frag[data-shape="circle"] { clip-path: circle(46%); }
.gumi-frag[data-shape="star"] {
  clip-path: polygon(50% 0, 61% 32%, 95% 22%, 71% 50%, 95% 78%, 61% 68%, 50% 100%, 39% 68%, 5% 78%, 29% 50%, 5% 22%, 39% 32%);
}
.gumi-frag[data-shape="tooth"] { clip-path: polygon(8% 12%, 92% 12%, 50% 96%); }
.gumi-frags--panel .gumi-frag:nth-child(1) { top: 6%;  left: 4%;   --s: 150px; --r: -12deg; }
.gumi-frags--panel .gumi-frag:nth-child(2) { top: 3%;  left: 44%;  --s: 96px;  --r: 16deg; }
.gumi-frags--panel .gumi-frag:nth-child(3) { top: 14%; right: 12%; --s: 132px; --r: -6deg; }
.gumi-frags--panel .gumi-frag:nth-child(4) { top: 46%; left: 1%;   --s: 110px; --r: 22deg; }
.gumi-frags--panel .gumi-frag:nth-child(5) { top: 52%; right: 2%;  --s: 150px; --r: 9deg; }
.gumi-frags--panel .gumi-frag:nth-child(6) { bottom: 4%; left: 18%; --s: 128px; --r: -18deg; }
.gumi-frags--panel .gumi-frag:nth-child(7) { bottom: 8%; left: 52%; --s: 92px;  --r: 12deg; }
.gumi-frags--panel .gumi-frag:nth-child(8) { bottom: 2%; right: 16%; --s: 118px; --r: -10deg; }

/* Spacing is the brand grid's gutter (24px desktop, 12px phone) for the tile gaps. Frames: a 2px ink border
   and a soft shadow. justify-items: center centers the heading, tile row and materials line as independent
   blocks (each sized to its own content) within the section, at every width. */
.gumizoo__grid {
  --gutter-grid: 12px;
  --frame-border: 2px solid rgba(17, 17, 17, .78);
  --frame-shadow: 0 1px 2px rgba(0, 0, 0, .08), 0 14px 26px -10px rgba(0, 0, 0, .3);
  display: grid;
  justify-items: center;
  gap: 24px var(--gutter-grid);
  grid-template-areas: "head" "tiles" "materials";
}
.gumizoo__head { grid-area: head; display: grid; gap: 14px; text-align: center; }
.gumizoo__tagline {
  font: 400 clamp(22px, 2.4vw, 28px)/.95 var(--display);
  color: var(--muted);
  text-transform: uppercase;
}
.gumizoo__materials { grid-area: materials; text-align: center; }

.gumizoo__tiles {
  grid-area: tiles;
  /* justify-self: stretch overrides .gumizoo__grid's justify-items: center for this one item: its mobile
     1fr columns need a definite, full-width container to resolve against (fit-content sizing, which
     justify-items: center would otherwise give it, collapses fr tracks toward zero). Desktop's own explicit
     px-wide columns (repeat(4, var(--tile-w)), below) don't depend on this either way, so it's harmless there. */
  justify-self: stretch;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* justify-content: center positions the tile columns within this (now full-width, via justify-self: stretch
     above) box: a no-op on mobile's 1fr columns, which already fill it, but what actually centers desktop's
     explicit-width 4-column row (below) since that row is narrower than the stretched container. */
  justify-content: center;
  gap: var(--gutter-grid);
}
/* Tile texture: one grainy-gradient stack that goes from quiet to bold. At rest a light neutral gradient with a
   grain that reads on its own; on hover/focus the accent gradient fades in underneath the same grain. */
.gumi-tile {
  position: relative;
  isolation: isolate;
  display: block;
  width: 100%;
  /* The head renders' own crop (1230:963, wider than tall — see the build note in CLAUDE.md) rather than a
     forced square: matching the frame to the asset's real shape means no letterboxing and no cropping. */
  aspect-ratio: var(--gumi-ar);
  padding: 0;
  border: var(--frame-border);
  background-color: #F6F3EB;
  background-image:
    radial-gradient(120% 110% at 14% 0%, #FFFFFF, transparent 62%),
    radial-gradient(100% 100% at 100% 100%, #E4DFD3, transparent 66%);
  box-shadow: var(--frame-shadow);
  color: inherit;
  cursor: pointer;
}
.gumi-tile::before,
.gumi-tile::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.gumi-tile::before {
  z-index: 0;
  background-color: var(--accent);
  background-image:
    radial-gradient(120% 110% at 14% 0%, color-mix(in oklab, var(--accent), white 32%), transparent 62%),
    radial-gradient(100% 100% at 100% 100%, color-mix(in oklab, var(--accent), black 22%), transparent 66%);
  opacity: 0;
  transition: opacity .35s var(--ease);
}
.gumi-tile::after {
  z-index: 1;
  background-image: var(--grain);
  opacity: .42;
  /* soft-light, not multiply: multiply only ever darkens, which is what made the resting tone read heavier
     than the light-neutral one intended (same fix applies to the hover/active flood underneath, since it's
     the same grain layer at a higher opacity — one texture, quiet to bold, per the earlier build note). */
  mix-blend-mode: soft-light;
  transition: opacity .35s var(--ease);
}
/* .is-active is set by main.js (pointerover/focusin), not :hover/:focus-visible: a tile has to explicitly be
   told it's active so the click-to-open and close-return-focus paths can reliably switch it off again. */
.gumi-tile.is-active::before { opacity: 1; }
.gumi-tile.is-active::after { opacity: .5; }
/* The head render is cropped tight and normalised (same front width and centre for all four), so one rule
   places every head identically: 95% of the frame's width, centred. */
.gumi-tile__head {
  position: absolute;
  inset: 2.5%;
  width: 95%;
  height: 95%;
  z-index: 2;
  object-fit: contain;
  transform-origin: 50% 60%;
  transition: transform .45s var(--ease), filter .45s var(--ease);
}

/* Nameplate: a slim band across just the bottom of the frame — legible, but the head stays fully visible
   above it, not covered. Always shown on touch, where there's no hover. */
.gumi-tile__name {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 3;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: .3em;
  padding: .38em .5em .26em;
  border-top: 3px solid #111111;
  background: var(--accent);
  color: var(--on-accent);
  font: 400 clamp(14px, 1.7vw, 19px)/1 var(--display);
  letter-spacing: .02em;
  text-transform: uppercase;
  translate: 0 100%;
  opacity: 0;
  pointer-events: none;
  transition: opacity .2s var(--ease);
}
.gumi-tile__name span { font-size: .82em; letter-spacing: .05em; }
@keyframes gumi-band-in {
  0%   { opacity: 0; translate: 0 100%; }
  60%  { opacity: 1; translate: 0 -8%; }
  100% { opacity: 1; translate: 0 0; }
}
.gumi-tile.is-active { z-index: 2; }
.gumi-tile.is-active .gumi-tile__name { opacity: 1; animation: gumi-band-in .4s var(--ease) both; }
@media (hover: none) {
  .gumi-tile__name { opacity: 1; translate: 0 0; font-size: clamp(12px, 3.2vw, 15px); border-top-width: 2px; }
  .gumi-tile__head { translate: 0 -6%; }
}
/* The lift: the head scales toward you, rises and tilts, breaking past the frame's edge. Motion only. */
@media (prefers-reduced-motion: no-preference) {
  .gumi-tile.is-active .gumi-tile__head {
    transform: translateY(-7%) scale(1.14) rotate(-4deg);
    filter: drop-shadow(0 18px 20px rgba(0, 0, 0, .28));
  }
}

/* Not part of this section's visible layout (see the top-of-file Gumizoo note) — main.js still unconditionally
   fetches and appends the poster image here, so the element stays in the DOM for that; display:none just
   takes it out of the flow instead of removing it, since removing it would break that querySelector. */
.gumizoo__poster { display: none; }

/* Desktop: the centered heading sits above one row of four heads, sized to fit one viewport with no scroll —
   the same no-scroll budget approach as before (a min() of a width- and a height-derived size), just simpler
   with no poster term. Four-across is more width-hungry than the old 2×2 was, so this row ends up
   width-bound rather than height-bound: it hits the standard --max cap on wide/short screens (1920×1080) with
   a lot of spare height left over, which the section's own vertical centering (below) turns into calm
   whitespace rather than a cramped row. */
@media (min-width: 900px) {
  .gumizoo {
    --pad: 24px;
    display: grid;
    align-content: center;
    min-height: calc(100svh - var(--nav-h));
    padding-block: var(--pad);
  }
  .gumizoo__grid {
    --gutter-grid: 16px;
    gap: var(--gutter-grid);
    /* .gumizoo__head's real height: the "Character collection" label, an 8px gap, the GUMIZOO heading (capped
       lower here than .project-title's usual 96px, see below), another 8px gap, then the tagline (also capped
       lower than its usual 28px) — each term the exact font-size expression that field uses. */
    --head-h: calc(16.8px + 8px + clamp(44px, 7vw, 88px) * .88 + 8px + clamp(22px, 2.4vw, 26px) * .95);
    --materials-h: 16.8px; /* the materials note, one line of .label */
    /* --tile-w: the width of one tile (the row is 4 * tile-w + 3 * gutter-grid). .7829 = 963/1230, the head
       render's own height:width ratio (see --gumi-ar), used here since aspect-ratio's shorthand form isn't
       available inside a calc(). */
    --tile-w: min(
      (min(100cqi - 2 * var(--gutter), var(--max)) - 3 * var(--gutter-grid)) / 4,
      (100svh - var(--nav-h) - 2 * var(--pad) - var(--head-h) - var(--materials-h) - 2 * var(--gutter-grid) - 2px) / .7829
    );
  }
  .gumizoo__tiles { grid-template-columns: repeat(4, var(--tile-w)); }
  .gumizoo__head { gap: 8px; }
  /* Matches --head-h's caps above: Gumizoo-only, doesn't touch the shared .project-title/.gumizoo__tagline
     rules used elsewhere or on phones (this lives inside the 900px+ block already). */
  .gumizoo .project-title { font-size: clamp(44px, 7vw, 88px); }
  .gumizoo__tagline { font-size: clamp(22px, 2.4vw, 26px); }
}

/* Experience */
.xp { border-top: 1px solid var(--line); }
.xp li {
  display: grid;
  gap: 6px 32px;
  padding: 24px 0;
  border-bottom: 1px solid var(--line);
}
.xp__role { font: 600 18px/1.3 var(--sans); }
.xp__org { color: var(--muted); }
.xp__desc { max-width: 62ch; }
.xp__heading { margin: 56px 0 16px; }
@media (min-width: 900px) {
  .xp li { grid-template-columns: 180px minmax(0, 1fr) minmax(0, 1.4fr); }
  .xp__years { padding-top: 4px; }
}

/* Roadmap: one hairline runs down through Experience and Education. main.js places a node level with each
   entry's title and a segment from the previous node; both light up in turn (.is-lit) as entries scroll in. */
.roadmap {
  --rail: 7px;
  position: relative;
  padding-left: 32px;
}
.roadmap__track {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: calc(var(--rail) * 2);
  pointer-events: none;
}
.roadmap__track::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--rail) - .5px);
  width: 1px;
  background: var(--line);
}
.roadmap__seg {
  position: absolute;
  left: calc(var(--rail) - 1px);
  width: 2px;
  background: var(--node, transparent);
  transform: scaleY(0);
  transform-origin: top;
  transition: transform .7s var(--ease);
}
.roadmap__node {
  position: absolute;
  left: 0;
  width: calc(var(--rail) * 2);
  height: calc(var(--rail) * 2);
  translate: 0 -50%;
  border: 1px solid rgba(17, 17, 17, .28);
  border-radius: 50%;
  background: var(--bg);
  transition: background-color .4s var(--ease) .45s, border-color .4s var(--ease) .45s;
}
.roadmap__seg.is-lit { transform: none; }
.roadmap__node.is-lit { background: var(--node); border-color: var(--node); }
.roadmap.is-armed .xp li {
  opacity: 0;
  translate: 0 14px;
  transition: opacity .6s var(--ease), translate .6s var(--ease);
}
.roadmap.is-armed .xp li.is-lit { opacity: 1; translate: 0 0; }

/* About */
.about {
  display: grid;
  gap: 40px 72px;
}
.about__bio {
  max-width: 36em;
  font: 400 clamp(19px, 1.8vw, 23px)/1.55 var(--sans);
}
.about__lists { display: grid; gap: 32px; align-content: start; }
.about__lists .label { margin-bottom: 12px; }
.tags { display: flex; flex-wrap: wrap; gap: 8px; }
.tags li {
  padding: 7px 11px 6px;
  border: 1px solid var(--line);
  font: 500 12px/1.2 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
  transition: background-color .25s var(--ease), color .25s var(--ease), border-color .25s var(--ease);
}
/* Name heading: a thin tick in the cycled accent (--fill, set by the shared .js-accent handler) draws in
   beneath it on hover/focus. Focusable like the hero tagline so keyboard users get the same moment. */
.about__name {
  position: relative;
  justify-self: start;
  cursor: default;
}
.about__name::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -.1em;
  height: 4px;
  background: var(--fill);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .35s var(--ease);
}
@media (hover: hover) {
  .about__name:hover::after { transform: scaleX(1); }
}
.about__name:focus-visible::after { transform: scaleX(1); }

.about__photo {
  grid-area: photo;
  width: min(100%, 320px);
  aspect-ratio: 4 / 5;
  border: 1px solid var(--line);
  background: #EFEDE6;
  overflow: hidden;
}
.about__photo img { width: 100%; height: 100%; object-fit: cover; }
.about { grid-template-areas: "head" "photo" "bio" "lists"; }
.about > .about__head { grid-area: head; margin-bottom: 0; }
.about__bio { grid-area: bio; }
.about__lists { grid-area: lists; }
@media (min-width: 900px) {
  .about {
    grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
    grid-template-areas: "head head" "bio photo" "bio lists";
  }
  .about__photo { width: 100%; max-width: 320px; }
}
@media (min-width: 1100px) {
  .about {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, .95fr);
    grid-template-areas: "head head head" "bio lists photo";
    gap: clamp(32px, 5vw, 64px) 56px;
  }
  .about__photo { max-width: none; }
}
/* Wide desktop: the photo runs the full height of the section, from the top of the heading to the end of the
   bio/tags, cropped (cover) to that height. The image is taken out of flow so its own size never stretches
   the rows, and the heading scales to fit the two left columns on one line. Below 1240px the columns are too
   narrow (tall text, thin photo), so the photo stays 4:5 beside the tags. */
@media (min-width: 1240px) {
  .about { grid-template-areas: "head head photo" "bio lists photo"; }
  .about__head { container-type: inline-size; }
  .about__name { font-size: min(144px, 100cqi / 7.4); }
  /* Top edge lines up with the heading, below the small "About" label. */
  .about__photo { position: relative; aspect-ratio: auto; margin-top: calc(12px * 1.4 + 14px); }
  .about__photo img { position: absolute; inset: 0; }
}

/* Contact */
/* Instagram first, then email: two matching rows, same display size, an icon each, split by hairlines.
   The size is set so the (longer) email line fits the row, icon included. */
.contact .wrap { display: grid; gap: 28px; justify-items: start; container-type: inline-size; }
.contact__lead {
  font: 600 clamp(17px, 2.2vw, 28px)/1.2 var(--sans);
  letter-spacing: .06em;
  text-transform: uppercase;
}
.contact__list {
  justify-self: stretch;
  border-top: 1px solid var(--line);
  font-size: min(100cqi / 13.2, 76px);
}
.contact__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 24px;
  padding-block: .22em;
  border-bottom: 1px solid var(--line);
}
.contact__link {
  display: inline-flex;
  align-items: center;
  gap: .3em;
  margin-left: -.1em;
  padding: .1em .12em 0 .1em;
  font: 400 1em/1 var(--display);
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color .25s var(--ease), color .25s var(--ease);
}
/* Single-colour glyphs in the text colour, stroke weight matched to Bebas stems; never the brand gradient. */
.contact__icon {
  flex: none;
  width: .74em;
  height: .74em;
  margin-bottom: .1em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.3;
  stroke-linejoin: round;
}
.contact__icon-dot { fill: currentColor; stroke: none; }

/* Footer */
.footer .wrap {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 8px 24px;
  padding-block: 28px 36px;
  border-top: 1px solid var(--line);
}

/* Player */
.player {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--dark-bg);
  color: var(--dark-fg);
  --fg: var(--dark-fg);
  --muted: var(--dark-muted);
}
.player::backdrop { background: var(--dark-bg); }
.player[open] {
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
}
.player__bar {
  display: flex;
  justify-content: flex-end;
  padding: 16px var(--gutter);
}
.player__stage {
  display: grid;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  place-items: center;
  min-height: 0;
  padding-inline: var(--gutter);
}
.player__stage > video,
.player__stage > img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}
.player__stage > iframe {
  width: min(100%, calc((100svh - 280px) * 16 / 9));
  aspect-ratio: 16 / 9;
  height: auto;
  border: 0;
}
.player__block {
  display: grid;
  place-items: center;
  width: min(100%, 720px);
  max-height: 100%;
  aspect-ratio: 16 / 9;
  background: var(--accent);
  color: var(--on-accent);
  font: 400 clamp(40px, 8vw, 96px)/.9 var(--display);
  text-transform: uppercase;
  text-align: center;
}
.player__info {
  display: grid;
  gap: 6px;
  width: 100%;
  max-width: calc(var(--max) + 2 * var(--gutter));
  margin-inline: auto;
  padding: 20px var(--gutter) 28px;
}
.player__title {
  font: 400 clamp(36px, 5vw, 64px)/.9 var(--display);
  text-transform: uppercase;
}
.player__context { max-width: 60ch; }
.player__meta:empty, .player__context:empty { display: none; }

/* Felines board: a vision board on the piece's grainy accent background */
.board {
  --fg: #111111;
  --muted: #5E5E5E;
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  color: #111111;
  background-color: var(--accent);
  background-image:
    radial-gradient(110% 80% at 15% 0%, color-mix(in oklab, var(--accent), white 28%), transparent 60%),
    radial-gradient(90% 70% at 100% 100%, color-mix(in oklab, var(--accent), black 22%), transparent 65%);
}
/* While cycling between Felines (main.js), cards scatter well past the dialog's edge; since overflow-y: auto
   alone makes overflow-x effectively auto too (the CSS overflow-x/-y interaction), that transit briefly grows
   the dialog's scrollable area on both axes and shows scrollbars for space that's never actually visible
   (.board fills the viewport already). Suppressed for exactly the cycle. */
.board.is-cycling { overflow: hidden; }
.board::backdrop { background: var(--accent); }
.board [hidden] { display: none !important; }
.board::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  background-image: var(--grain);
  opacity: .38;
  mix-blend-mode: soft-light;
}

/* Felines board: grain at the same strength as the grainy pills (Gumizoo's panel keeps its lighter grain). */
#board::before { opacity: .5; }

.board__close {
  position: fixed;
  top: 16px;
  right: var(--gutter);
  z-index: 5;
  background: #FAF8F2;
  border-color: #111111;
  color: #111111;
}

/* Cycling controls: round Prev/Next pills at the sides (a bottom bar on phones) and a "2 / 7" counter
   mirroring Close. They fill with the cycling accent like Close; the piece's own accent would vanish here. */
.board__arrow {
  position: fixed;
  z-index: 5;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  background: #FAF8F2;
  border-color: #111111;
  color: #111111;
}
.board__arrow svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.board__arrow--prev { left: var(--gutter); bottom: 16px; }
.board__arrow--next { right: var(--gutter); bottom: 16px; }
.board__count {
  position: fixed;
  top: 16px;
  left: var(--gutter);
  z-index: 5;
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 14px;
  background: #FAF8F2;
  color: #111111;
  font: 500 12px/1 var(--sans);
  letter-spacing: .1em;
  font-variant-numeric: tabular-nums;
}
@media (min-width: 900px) {
  .board__arrow { top: 50%; bottom: auto; margin-top: -24px; }
  .board__arrow--prev { left: max(8px, (var(--gutter) - 48px) / 2); }
  .board__arrow--next { right: max(8px, (var(--gutter) - 48px) / 2); }
}

.board__sheet {
  position: relative;
  display: grid;
  gap: 16px;
  perspective: 1800px; /* depth for the render's card-flip when cycling; origin set per flip in main.js */
  width: 100%;
  max-width: calc(var(--max) + 2 * var(--gutter));
  min-height: 100%;
  margin-inline: auto;
  padding: 76px var(--gutter) 88px; /* clears the phone Prev/Next bar */
}

/* Every piece on the board is pinned: title, render and each card get an ink pin at one top corner
   (alternating sides) and, on desktop, their own small tilt. */
.board__sheet > * { position: relative; }
.board__sheet > *::after {
  content: "";
  position: absolute;
  top: -6px;
  left: 18px;
  z-index: 3;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #5A5A5A, #111111 60%);
  box-shadow: 0 2px 3px rgba(0, 0, 0, .35), 1px 5px 6px rgba(0, 0, 0, .15);
}
.board__sheet > :nth-child(even)::after { left: auto; right: 18px; }
.board__sheet > .board__note::after { top: -4px; }

.board__title {
  justify-self: start;
  padding: 16px 20px 14px;
  background: #FAF8F2;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .1), 0 10px 24px rgba(0, 0, 0, .18);
  font: 400 clamp(40px, 10vw, 56px)/.9 var(--display);
  text-transform: uppercase;
}
.board__title span { display: block; }

.board__render {
  width: min(100%, 420px);
  justify-self: center;
}
.board__render img,
.board__render video {
  display: block;
  width: 100%;
  aspect-ratio: 9 / 16;
  object-fit: cover;
  background: #FFFFFF;
}
.board__render img { box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 22px 48px rgba(0, 0, 0, .3); }
/* The strip card's hover loop, laid over the render and faded in once it's actually playing. */
.board__render video {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s var(--ease);
}
.board__render.is-playing video { opacity: 1; }

.board__card {
  display: grid;
  gap: 10px;
  padding: 16px 18px 18px;
  background: #FAF8F2;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .1), 0 10px 24px rgba(0, 0, 0, .18);
}
.board__card p { max-width: 40ch; }

/* Palette swatches as paint chips: each its own little card with a shadow and an alternating tilt. */
.swatches {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(52px, 1fr));
  gap: 10px;
  padding-top: 2px;
}
.swatches li {
  display: grid;
  gap: 5px;
  padding: 4px 4px 6px;
  background: #FFFFFF;
  box-shadow: 0 1px 1px rgba(0, 0, 0, .12), 0 4px 8px rgba(0, 0, 0, .14);
  transform: rotate(-2.5deg);
}
.swatches li:nth-child(2n) { transform: rotate(2deg); }
.swatches li:nth-child(3n) { transform: rotate(-1deg); }
.swatches span {
  display: block;
  aspect-ratio: 1;
  box-shadow: inset 0 0 0 1px rgba(17, 17, 17, .08);
}
.swatches code {
  font: 500 11px/1 var(--sans);
  letter-spacing: .04em;
}

.board__tags { display: flex; flex-wrap: wrap; gap: 6px; }
.board__tags li {
  padding: 5px 9px 4px;
  border: 1px solid rgba(17, 17, 17, .22);
  font: 500 12px/1.3 var(--sans);
}

.board__note { filter: drop-shadow(0 10px 18px rgba(0, 0, 0, .22)); }
.board__paper {
  display: grid;
  gap: 10px;
  padding: 18px 20px 30px;
  background: #FFF5CC;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 10px), 96% 100%, 91% calc(100% - 7px), 85% 100%, 79% calc(100% - 9px),
    72% 100%, 66% calc(100% - 6px), 59% 100%, 52% calc(100% - 9px), 45% 100%, 39% calc(100% - 6px), 32% 100%,
    25% calc(100% - 9px), 18% 100%, 12% calc(100% - 7px), 6% 100%, 0 calc(100% - 10px));
}
.board__caption { margin: 0; }
.board__caption p {
  font: 500 19px/1.4 var(--sans);
  quotes: "“" "”";
}
.board__caption p::before { content: open-quote; }
.board__caption p::after { content: close-quote; }
.board__observation { font-size: 15px; }

/* One-time reveal on open: the render arrives with the View Transitions morph, then the title and cards
   fade and scale in one after another (60ms apart). Uses the standalone `scale` property so each piece keeps
   its own tilt. Reduced motion: the global rule drops the animation, so everything is simply in place. */
@keyframes board-pin-in {
  from { opacity: 0; scale: .94; }
}
.board[open] .board__sheet > :not(.board__render) {
  animation: board-pin-in .4s var(--ease) both;
  animation-delay: calc(.32s + var(--i, 0) * 60ms);
}
.board__title    { --i: 0; }
.board__palette  { --i: 1; }
.board__style    { --i: 2; }
.board__font     { --i: 3; }
.board__elements { --i: 4; }
.board__note     { --i: 5; }

/* Desktop collage: the render is larger and the cards hug it, overlapping its edges, so the flood colour
   isn't left sitting empty in the corners. Side cards spread down the render's full height. Tilts alternate
   between about -3 and +4 degrees, no two neighbours alike. The phone layout stays a flat stack. */
@media (min-width: 900px) {
  .board__sheet {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    grid-template-rows: repeat(3, auto);
    column-gap: 0;
    row-gap: 18px;
    align-content: center;
    max-width: 1240px;
    padding-block: 72px 56px;
  }
  .board__render  { grid-area: 1 / 2 / 4 / 3; width: calc(80svh * 9 / 16); max-width: 460px; min-width: 330px; align-self: center; z-index: 1; transform: rotate(-2deg); }
  .board__title   { grid-area: 1 / 1; align-self: end; justify-self: end; margin-right: 40px; transform: rotate(-3deg); }
  .board__palette { grid-area: 2 / 1; align-self: center; justify-self: end; width: min(100%, 390px); margin-right: -22px; z-index: 2; transform: rotate(2deg); }
  .board__style   { grid-area: 3 / 1; align-self: start; justify-self: end; width: min(100%, 320px); margin-right: 16px; transform: rotate(-1.5deg); }
  .board__font    { grid-area: 1 / 3; align-self: end; justify-self: start; width: min(100%, 300px); margin-left: -26px; z-index: 2; transform: rotate(3.5deg); }
  .board__elements{ grid-area: 2 / 3; align-self: center; justify-self: start; width: min(100%, 360px); margin-left: 18px; transform: rotate(-2.5deg); }
  .board__note    { grid-area: 3 / 3; align-self: start; justify-self: start; width: min(100%, 340px); margin-left: -18px; z-index: 2; transform: rotate(1.5deg); }
  .board__title span { font-size: .5em; letter-spacing: .02em; }
}
/* Wide screens: room for the palette chips on one row, and a slightly larger render. */
@media (min-width: 1200px) {
  .board__sheet { max-width: 1400px; }
  .board__render { max-width: 500px; }
  .swatches { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 8px; }
  .swatches code { letter-spacing: 0; }
}

/* Gumizoo detail panel: reuses .board's grainy accent background and close button */
.gumi-panel__sheet {
  position: relative;
  z-index: 1;
  display: grid;
  gap: 20px;
  width: 100%;
  max-width: calc(1280px + 2 * var(--gutter));
  min-height: 100%;
  margin-inline: auto;
  padding: 76px var(--gutter) 48px;
  align-content: center;
}
/* The head, now turnable: the still underneath until the turntable sprite is in, then the sprite's current
   frame (6×6 grid of 10° steps, positioned by main.js). No tilt or frame: it sits straight on the flood. */
.gumi-panel__model {
  position: relative;
  justify-self: center;
  width: 100%;
  aspect-ratio: var(--gumi-ar);
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
  filter: drop-shadow(0 22px 26px rgba(0, 0, 0, .22));
}
.gumi-panel__model.is-ready { cursor: grab; }
.gumi-panel__model.is-dragging { cursor: grabbing; }
.gumi-spin__still,
.gumi-spin__turn {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.gumi-spin__turn { background: no-repeat 0 0 / 600% 600%; }
.gumi-panel__model.is-ready .gumi-spin__still { visibility: hidden; }
.gumi-panel__hint {
  position: absolute;
  left: 50%;
  bottom: 2%;
  translate: -50% 0;
  padding: 5px 10px 4px;
  background: #FAF8F2;
  color: #111111;
  white-space: nowrap;
}
.gumi-panel__model:not(.is-ready) .gumi-panel__hint { display: none; }
.gumi-panel__stamp {
  justify-self: start;
  padding: 12px 18px 6px;
  border: 3px solid var(--accent);
  background: #FAF8F2;
  color: var(--accent);
  font: 400 clamp(38px, 4.6vw, 62px)/.9 var(--display);
  text-transform: uppercase;
  box-shadow: 0 10px 24px rgba(0, 0, 0, .18);
}
.gumi-panel__trait {
  justify-self: start;
  padding: 7px 12px 6px;
  border: 1px solid rgba(17, 17, 17, .22);
  background: #FAF8F2;
  font: 500 13px/1.3 var(--sans);
  letter-spacing: .08em;
  text-transform: uppercase;
  box-shadow: 0 6px 14px rgba(0, 0, 0, .16);
}
.gumi-panel__bio {
  justify-self: start;
  max-width: 440px;
  filter: drop-shadow(0 10px 18px rgba(0, 0, 0, .22));
}
.gumi-panel__bio p { font: 400 16px/1.5 var(--sans); }
.gumi-panel__more:empty { display: none; }
.gumi-panel__more video {
  display: block;
  /* Kept modest and, below an 800px-tall viewport, self-shrinking: it's a bonus clip, not the panel's main
     draw, and its height is the one thing that made Nanju's panel (the only one with a video) run taller than
     the other three's and force a scroll. Untouched above 800px tall (max-width simply binds at 320px);
     between 650 and 800 it eases down; never smaller than 200px. See the sheet padding/columns tweak below,
     the other half of this fix. */
  width: clamp(200px, calc(320px - (800px - 100svh) * .8), 320px);
  aspect-ratio: 16 / 9;
  background: #111111;
  border: 6px solid #FFFFFF;
  box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 16px 32px rgba(0, 0, 0, .28);
}

@media (min-width: 900px) {
  /* Sized so all four panels (Nanju's, with its extra video slot, included) fit one screen without scrolling.
     Model and text column stay the same relative size on all four (nothing here is per-character): a touch
     less top/bottom padding, a touch more width for the text column (which also shortens the stamp/bio's
     wrapped line count), and a tighter row-gap. */
  .gumi-panel__sheet {
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    grid-template-rows: 1fr auto auto auto auto 1fr;
    padding-block: 64px 40px;
    column-gap: 44px;
    row-gap: 14px;
  }
  .gumi-panel__model { grid-area: 1 / 1 / 7 / 2; align-self: center; width: 100%; }
  .gumi-panel__stamp { grid-area: 2 / 2; transform: rotate(-2deg); }
  .gumi-panel__trait { grid-area: 3 / 2; transform: rotate(1.5deg); }
  .gumi-panel__bio { grid-area: 4 / 2; transform: rotate(1.2deg); }
  .gumi-panel__more { grid-area: 5 / 2; transform: rotate(-1.5deg); }
}

/* Per-character voice for the panel's three text pieces (stamp, personality tag, bio). Same structure for all
   four; the treatment carries the personality. main.js sets data-gumi on the panel. Gumizoo only. */
.gumi-panel .gumi-panel__stamp {
  padding: .2em .32em .08em;
  border-width: 4px;
  font-size: clamp(52px, 6.2vw, 96px);
}
.gumi-panel .gumi-panel__trait {
  padding: 10px 16px 9px;
  font: 600 clamp(15px, 1.25vw, 18px)/1.3 var(--sans);
  letter-spacing: .1em;
}
.gumi-panel .gumi-panel__bio { max-width: 520px; }
.gumi-panel .gumi-panel__bio p { font: 500 clamp(18px, 1.5vw, 22px)/1.45 var(--sans); }

/* Ashik, hard-headed: blocky and squared-off. Solid accent slab with a heavy ink outline and a hard square
   shadow, fattened lettering, no tilt anywhere; the bio is a flat, square-edged card. */
.gumi-panel[data-gumi="ashik-rancher"] .gumi-panel__stamp {
  border: 6px solid #111111;
  background: var(--accent);
  color: var(--on-accent);
  -webkit-text-stroke: 1.5px currentColor;
  letter-spacing: .03em;
  box-shadow: 10px 10px 0 #111111;
}
.gumi-panel[data-gumi="ashik-rancher"] .gumi-panel__trait {
  border: 0;
  background: #111111;
  color: #FAF8F2;
  box-shadow: none;
}
.gumi-panel[data-gumi="ashik-rancher"] .gumi-panel__bio { filter: none; }
.gumi-panel[data-gumi="ashik-rancher"] .gumi-panel__bio .board__paper {
  clip-path: none;
  padding-bottom: 22px;
  border: 4px solid #111111;
  box-shadow: 8px 8px 0 #111111;
}

/* Gia, gluttonous: glossy candy. A rounded, shiny accent stamp, and a bio note in a sugary tint whose bottom
   edge melts into drips. */
.gumi-panel[data-gumi="gia-tangerine"] .gumi-panel__stamp {
  border: 0;
  border-radius: 18px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, .38), rgba(255, 255, 255, 0) 48%),
    var(--accent);
  color: #FAF8F2;
  box-shadow: inset 0 -6px 0 rgba(0, 0, 0, .18), 0 12px 24px rgba(0, 0, 0, .2);
}
.gumi-panel[data-gumi="gia-tangerine"] .gumi-panel__trait { border-radius: 10px; }
.gumi-panel[data-gumi="gia-tangerine"] .gumi-panel__bio { --paper: color-mix(in oklab, var(--accent) 16%, #FFF8F0); padding-bottom: 34px; }
.gumi-panel[data-gumi="gia-tangerine"] .gumi-panel__bio .board__paper {
  position: relative;
  clip-path: none;
  padding-bottom: 20px;
  border-radius: 16px 16px 0 0;
  background: var(--paper);
}
.gumi-panel[data-gumi="gia-tangerine"] .gumi-panel__bio .board__paper::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 40px;
  background:
    radial-gradient(14px 30px at 9% 0, var(--paper) 98%, transparent 100%),
    radial-gradient(9px 17px at 24% 0, var(--paper) 98%, transparent 100%),
    radial-gradient(12px 38px at 41% 0, var(--paper) 98%, transparent 100%),
    radial-gradient(8px 14px at 57% 0, var(--paper) 98%, transparent 100%),
    radial-gradient(13px 26px at 72% 0, var(--paper) 98%, transparent 100%),
    radial-gradient(10px 20px at 90% 0, var(--paper) 98%, transparent 100%);
}

/* Nanju, gambling-type stupid: reckless. Cards thrown down at loose, mismatched angles and offsets; the tag
   is a torn-off raffle ticket with a dashed perforation. */
.gumi-panel[data-gumi="nanju-jam"] .gumi-panel__stamp {
  border-style: double;
  border-width: 8px;
  box-shadow: -8px 12px 0 color-mix(in oklab, var(--accent) 55%, #111111);
}
.gumi-panel[data-gumi="nanju-jam"] .gumi-panel__trait {
  border: 2px dashed #111111;
  background: #FFE36E;
  box-shadow: 0 8px 16px rgba(0, 0, 0, .2);
}

/* Rana, boy-crazy: soft and swoony. Rounded corners throughout, a soft blush shadow instead of a hard one,
   and a light, bouncy entrance when her panel opens. */
.gumi-panel[data-gumi="rana-ghee"] .gumi-panel__stamp {
  border-radius: 24px;
  box-shadow: 0 14px 30px color-mix(in oklab, var(--accent) 45%, transparent);
}
.gumi-panel[data-gumi="rana-ghee"] .gumi-panel__trait { border-radius: 14px; }
.gumi-panel[data-gumi="rana-ghee"] .gumi-panel__bio .board__paper {
  clip-path: none;
  padding-bottom: 22px;
  border-radius: 22px;
  background: color-mix(in oklab, var(--accent) 12%, #FFF8F4);
}
@keyframes gumi-swoon {
  0%   { opacity: 0; translate: 0 26px; scale: .92; }
  60%  { opacity: 1; translate: 0 -6px; scale: 1.02; }
  100% { opacity: 1; translate: 0 0; scale: 1; }
}
.gumi-panel[open][data-gumi="rana-ghee"] :is(.gumi-panel__stamp, .gumi-panel__trait, .gumi-panel__bio) {
  animation: gumi-swoon .7s var(--ease) both;
}
.gumi-panel[open][data-gumi="rana-ghee"] .gumi-panel__stamp { animation-delay: .25s; }
.gumi-panel[open][data-gumi="rana-ghee"] .gumi-panel__trait { animation-delay: .35s; }
.gumi-panel[open][data-gumi="rana-ghee"] .gumi-panel__bio   { animation-delay: .45s; }

@media (min-width: 900px) {
  .gumi-panel[data-gumi="ashik-rancher"] :is(.gumi-panel__stamp, .gumi-panel__trait, .gumi-panel__bio, .gumi-panel__more) { transform: none; }
  .gumi-panel[data-gumi="nanju-jam"] .gumi-panel__stamp { transform: rotate(-7deg) translateX(-14px); }
  .gumi-panel[data-gumi="nanju-jam"] .gumi-panel__trait { transform: rotate(6deg) translate(46px, 4px); }
  .gumi-panel[data-gumi="nanju-jam"] .gumi-panel__bio   { transform: rotate(-4deg) translateX(18px); }
  .gumi-panel[data-gumi="nanju-jam"] .gumi-panel__more  { transform: rotate(3.5deg) translateX(-10px); }
  .gumi-panel[data-gumi="rana-ghee"] .gumi-panel__stamp { transform: rotate(-1.5deg); }
  .gumi-panel[data-gumi="rana-ghee"] .gumi-panel__trait { transform: rotate(1deg); }
  .gumi-panel[data-gumi="rana-ghee"] .gumi-panel__bio   { transform: rotate(-.8deg); }
}

::view-transition-group(gumi-portrait) {
  animation-duration: .55s;
  animation-timing-function: cubic-bezier(.2, .7, .2, 1);
}

::view-transition-group(feline-render) {
  animation-duration: .55s;
  animation-timing-function: cubic-bezier(.2, .7, .2, 1);
}
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: .45s;
}

/* Phone polish (under 700px): 44px tap targets, nothing under 12px, tighter section rhythm, and caps on the
   two tallest media blocks so no single piece takes over the screen. Desktop is unaffected. */
html { -webkit-tap-highlight-color: transparent; }
@media (max-width: 699px) {
  .band { padding-block: 44px; }
  .band--intro { padding-bottom: 0; }
  .band--rule { padding-top: 0; }
  .band--rule > .wrap { padding-top: 40px; }
  .hero .wrap { padding-bottom: 44px; }
  .section-head { margin-bottom: 28px; }
  .project-head { margin-bottom: 20px; }
  .work-intro { margin-bottom: 28px; }

  .nav__brand { min-height: 44px; }
  .hero__line-text { display: inline-block; padding-block: .26em .28em; }
  .contact__link { min-height: 48px; }
  .contact__row { padding-block: 10px; }
  .contact__row .pill { min-height: 48px; }
  /* Phones: the email breaks before @gmail.com so both rows can share a much larger size. */
  .contact__list { font-size: min(100cqi / 8.6, 56px); }
  .contact__link { align-items: flex-start; line-height: 1.02; }
  .contact__icon { margin: .06em 0 0; }
  .contact__domain { display: block; }

  .card__meta,
  .clip-nav__count { font-size: 12px; }
  .clip-nav { left: 6px; right: 6px; bottom: calc(var(--label-h, 43px) + 6px); }
  .clip-nav__btn { width: 44px; height: 44px; }
  .clip-nav__btn svg { width: 20px; height: 20px; }

  .pangeo-grid .card[data-tile="flagship"] { width: min(100%, calc(64svh * 9 / 16)); }

  .xp li { padding: 20px 0; }
  .xp__heading { margin: 40px 0 12px; }
  .about { gap: 28px; }
  .about__photo { width: min(100%, 280px); }
  .footer .wrap { padding-block: 24px 28px; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition: none !important;
    animation: none !important;
  }
  .card__media video { display: none; }
}
