/* Version 2 shell — tokens, page frame, background grid and nav, shared by
   every v2 page. Landing-specific rules (specimens, lede, wordmark) too.
   Base frame: Figma wC5383GSncabfPynmGpSzw node 723:1275 (1280 x 832). */

:root {
  --paper: #fff9ec;
  --ink: #1d4d4e;
  --terra: #904f3e;

  /* The design canvas. Everything below is positioned against it. */
  --canvas-w: 1280;
  --canvas-h: 832;
}

/* ── Page transitions ───────────────────────────────────────────────────
   Shell nav (Home / About / Playground) is driven by js/page-transitions.js.
   Keep MPA view transitions opted-in for non-intercepted navigations in
   browsers that actually fire them; the script skips VT when it already
   ran a leave animation. */

@view-transition {
  navigation: auto;
}

/* Pre-paint hold: tiny head sniff sets .pt-pending after shell leave so the
   next page does not flash full content before the enter fade starts. */
html.pt-pending .stage,
html.pt-pending .foot,
html.pt-pending .not-designing {
  opacity: 0;
  transform: translateY(14px);
}

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

html,
body {
  margin: 0;
  padding: 0;
}

/* Back to Top is a plain #top link, so the easing lives here. */
html {
  scroll-behavior: smooth;
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: Helvetica, 'Helvetica Neue', Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

.skip {
  position: absolute;
  left: -9999px;
}

.skip:focus {
  left: 1rem;
  top: 1rem;
  z-index: 100;
  padding: 0.6rem 1rem;
  background: var(--ink);
  color: var(--paper);
  border-radius: 6px;
}

/* ── Stage ──────────────────────────────────────────────────────────────
   A proportional recreation of the 1280x832 frame. Sizes are expressed in
   cqw so the whole composition scales as one piece instead of drifting
   out of register at other widths. */

.stage {
  position: relative;
  width: min(100vw, calc(var(--canvas-w) * 1px));
  aspect-ratio: var(--canvas-w) / var(--canvas-h);
  margin-inline: auto;
  container-type: inline-size;
  overflow: hidden;
}

/* Fixed to the viewport so the grid bleeds to every browser edge rather than
   stopping at the 1280px stage and leaving bare margins. */
.grid-canvas {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  display: block;
  pointer-events: none;
  z-index: 0;
  /* Named so it's carried across the navigation rather than cross-faded —
     the grid should look continuous while the content changes under it. */
  view-transition-name: grid;
}

.stage {
  position: relative;
  z-index: 1;
}

/* ── Nav (Figma 758:1827 — a 1174-wide row, mark left, links right) ───── */

/* The bar spans the whole window; the row inside it stays on the 1280 frame.
   The nav sits outside .stage for this — the stage clips to 1280, so anything
   in it (including a bleed layer) gets cut off at the sides on wider screens.
   Same split as the footer: full-width fill, capped inner. */
.nav {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  background: var(--paper);
  z-index: 3;
  /* Identical on every page, so it stays put instead of blinking. */
  view-transition-name: nav;
}

/* Caps the row at the 1280 frame and carries the container query its cqw
   sizes are measured against. It holds no padding of its own: container query
   units measure the content box, so padding here would shrink the container
   to 1180 and quietly scale the whole row down with it. */
.nav-inner {
  width: min(100vw, calc(var(--canvas-w) * 1px));
  margin-inline: auto;
  container-type: inline-size;
}

/* 30/50 puts the row at y=30 with the mark at x=50 and links ending at 1230. */
.nav-row {
  padding: 2.344cqw 3.906cqw; /* 30px 50px */
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nav-links {
  display: flex;
  align-items: center;
}

/* The monogram: a cropped sprig with the S and L set over it. All three
   overlap in the frame, so they're placed rather than laid out. */
.mark {
  position: relative;
  display: block;
  width: 2.891cqw; /* 37px — sprig plus the L's overhang */
  height: 2.578cqw; /* 33px */
  flex: none;
}

.mark-sprig {
  position: absolute;
  left: 0;
  top: 0;
  width: 1.797cqw; /* 23px */
  height: 2.578cqw; /* 33px */
  overflow: hidden;
}

.mark-sprig img {
  position: absolute;
  display: block;
  max-width: none;
  width: 387.02%;
  height: 265.45%;
  left: -147.33%;
  top: -83.77%;
}

.mark-s,
.mark-l {
  position: absolute;
  font-family: 'Text Me One', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-size: 1.5625cqw; /* 20px */
  line-height: normal;
  color: #000;
}

.mark-s {
  left: 1.797cqw; /* 23px */
  top: 0.234cqw; /* 3px */
}

.mark-l {
  left: 2.109cqw; /* 27px */
  top: 0.625cqw; /* 8px */
}

.nav a,
.nav .nav-soon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.9375cqw 1.5625cqw; /* 12px 20px */
  border-radius: 30px;
  font-size: 1.25cqw; /* 16px */
  letter-spacing: -0.0625cqw; /* -0.8px */
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
  transition: opacity 0.2s ease;
}

.nav a:hover,
.nav a:focus-visible {
  opacity: 0.62;
}

/* playground has no page yet — not clickable, but it reads as part of
   the same set, so no dimming. */
.nav .nav-soon {
  cursor: default;
}

/* Current page: home on the landing, about on the About page. */
.nav .is-current {
  background: var(--ink);
  color: var(--paper);
}

.nav .is-current:hover,
.nav .is-current:focus-visible {
  opacity: 1;
}

/* ── Specimens (five pressed flowers, each taped with a label) ────────
   The list is display: contents so each frame and tag keeps its own
   coordinates against the stage rather than nesting inside a list box.
   Every image is a windowed crop of a larger source, matching Figma. */

.specimens {
  display: contents;
  margin: 0;
  padding: 0;
  list-style: none;
}

.specimens li {
  display: contents;
}

.sp {
  position: absolute;
  display: block;
  overflow: hidden;
  z-index: 2;
  /* Pivots from the tape at the base, so it swings like a pressed stem
     rather than spinning about its middle. */
  transform-origin: bottom center;
  transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Alternating lean, with slightly uneven angles so the row doesn't read
   as a mechanical zig-zag. */
.sp--1:hover { transform: rotate(-3.2deg); }
.sp--2:hover { transform: rotate(2.6deg); }
.sp--3:hover { transform: rotate(-2.2deg); }
.sp--4:hover { transform: rotate(3deg); }
.sp--5:hover { transform: rotate(-2.8deg); }

.sp img {
  position: absolute;
  max-width: none;
  display: block;
  height: 100%;
  top: 0;
}

/* 60,238 — 184x259 */
.sp--1 { left: 4.688cqw; top: 18.594cqw; width: 14.375cqw; height: 20.234cqw; }
.sp--1 img { width: 140.76%; left: -20.11%; }

/* 321,236 — 126x258 */
.sp--2 { left: 25.078cqw; top: 18.438cqw; width: 9.844cqw; height: 20.156cqw; }
.sp--2 img { width: 205.01%; left: -49.58%; }

/* 517,227 — 179x278 */
.sp--3 { left: 40.391cqw; top: 17.734cqw; width: 13.984cqw; height: 21.719cqw; }
.sp--3 img { width: 152.69%; left: -23.91%; }

/* 759,232 — 203x262 */
.sp--4 { left: 59.297cqw; top: 18.125cqw; width: 15.859cqw; height: 20.469cqw; }
.sp--4 img { width: 195.07%; height: 151.15%; left: -40.39%; top: -25.95%; }

/* 1006,277 — 144x218 */
.sp--5 { left: 78.594cqw; top: 21.641cqw; width: 11.25cqw; height: 17.031cqw; }
.sp--5 img { width: 180.39%; height: 119.29%; left: -40.44%; top: -19.29%; }

.sp-tag {
  position: absolute;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.625cqw 0.781cqw; /* 8px 10px */
  border: 1px solid #ae8c82;
  border-radius: 30px;
  background: rgba(255, 255, 255, 0.7);
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue',
    Helvetica, Arial, sans-serif;
  font-size: 0.9375cqw; /* 12px */
  letter-spacing: -0.01875cqw; /* -0.24px */
  color: #ae8c82;
  white-space: nowrap;
  z-index: 3;
  /* Hidden until its specimen is hovered. Kept in the DOM and only faded
     out, so it still reaches assistive tech. */
  opacity: 0;
  transform: translateY(0.313cqw);
  pointer-events: none;
  transition:
    opacity 0.35s ease,
    transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}

/* The li is display: contents, so the frame and its label are adjacent
   siblings in the box tree and this reaches across. */
.sp:hover + .sp-tag {
  opacity: 1;
  transform: none;
}

.sp-tag--painting { left: 14.453cqw; top: 16.094cqw; }
.sp-tag--empathy { left: 32.578cqw; top: 37.656cqw; }
.sp-tag--accessibility { left: 48.438cqw; top: 15.547cqw; }
.sp-tag--craft { left: 68.359cqw; top: 37.969cqw; }
.sp-tag--design { left: 88.984cqw; top: 21.094cqw; }

/* ── Lede + wordmark ───────────────────────────────────────────────────
   Now one block at 69,591 (965x172): the lede at the top, the wordmark
   68px down. */

.intro-block {
  position: absolute;
  left: 5.391cqw; /* 69 */
  top: 46.172cqw; /* 591 */
  width: 75.391cqw; /* 965 */
  z-index: 3;
}

.lede {
  /* Figma's 488px assumes real SF Pro Display. The fallback face sets
     wider and pushes "products" onto a third line; 538px holds it to two
     with room if the font resolves differently. */
  width: 42cqw;
  margin: 0;
  font-family: Helvetica, 'Helvetica Neue', Arial, sans-serif;
  font-size: 1.5625cqw; /* 20px */
  line-height: normal;
  text-align: justify;
  color: var(--ink);
  white-space: pre-wrap;
}

.wordmark {
  margin: 1.563cqw 0 0; /* lands the wordmark 68 below the block top */
  font-family: 'Syncopate', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 7.813cqw; /* 100px */
  line-height: normal;
  letter-spacing: -0.547cqw; /* -7px */
  color: var(--ink);
  white-space: nowrap;
}

/* ══ Projects ═══════════════════════════════════════════════════════════
   Figma 759:1913. Four 832px frames below the hero. Each holds a colour
   band and, 30px under it, a row of title/description with a tag pushed
   right. Frame one repeats the nav, as the design does. */

/* Left transparent so the grid runs behind the projects. Filling it looked
   worse: the stage caps at 1280, so the fill stopped mid-window and cut a
   hard edge against the grid on either side. */
.stage--project {
  overflow: hidden;
}

/* Snap lives on the document scroller. mandatory settles clearly on each
   project; hero + footer are also targets so scroll can leave the project
   stack. Scoped with :has so case / playground keep free scrolling;
   about owns its own snap via :has(.stage--about) in about.css. */
html:has(.stage--project) {
  scroll-snap-type: y mandatory;
}

/* Intro stage (plain .stage), projects, and footer — so mandatory scroll
   can leave the project stack. stop: normal avoids trapping.
   Footer uses end: it's shorter than the viewport, so start can never
   reach (not enough scroll range); end seats it at the page bottom. */
html:has(.stage--project) body > .stage:not(.stage--project),
html:has(.stage--project) .stage--project {
  scroll-snap-align: start;
  scroll-snap-stop: normal;
}

html:has(.stage--project) .foot {
  scroll-snap-align: end;
  scroll-snap-stop: normal;
}

/* Frame one sits at 60,200; the other three at 53,126. */
.proj {
  position: absolute;
  left: 4.141cqw; /* 53 */
  top: 9.844cqw; /* 126 */
  width: 91.719cqw; /* 1174 */
  display: flex;
  flex-direction: column;
  gap: 2.344cqw; /* 30 */
  z-index: 2;
}

.proj--menuvo {
  left: 4.688cqw; /* 60 */
  top: 15.625cqw; /* 200 */
}

.band {
  position: relative;
  width: 100%;
  height: 38.125cqw; /* 488 */
  border-radius: 10px;
  /* Already clipping, so the artwork inside follows the rounded corners. */
  overflow: hidden;
  background: var(--band);
}

.proj-foot {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 2.344cqw;
}

/* The column takes whatever the tag leaves rather than a fixed 686/804 from
   the frame, so every description has room to stay on one line. The longest
   one measures 874px against 970px of space at the narrowest row. */
.proj-text {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.781cqw; /* 10 */
}

.proj-text h2 {
  margin: 0;
  font-family: 'Syncopate', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 2.344cqw; /* 30px */
  line-height: normal;
  letter-spacing: -0.047cqw;
  color: #000;
}

.proj-text p {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue',
    Helvetica, Arial, sans-serif;
  font-size: 1.406cqw; /* 18px */
  line-height: normal;
  letter-spacing: -0.028cqw;
  /* One line each. Justify only stretched the words apart now that nothing
     wraps, so the text is set flush left. */
  white-space: nowrap;
  text-align: left;
  color: #323232;
}

.proj-tag {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.625cqw 0.781cqw; /* 8px 10px */
  border: 1px solid #323232;
  border-radius: 30px;
  background: rgba(255, 255, 255, 0.7);
  font-family: 'Syncopate', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-size: 1.406cqw; /* 18px */
  letter-spacing: -0.028cqw;
  color: #323232;
  white-space: nowrap;
}

/* ── Project hover ─────────────────────────────────────────────────────
   Only the cover moves: it lifts off the paper and pushes in slightly. The
   title and the tag below it stay exactly as they are.

   The zoom is a scale on .band rather than on its contents. Each project
   pins its artwork to absolute frame coordinates — the two Menuvo phones,
   the five offset beli screens, the overlapping Rythmeet windows — so
   scaling those individually would grow each about its own centre and pull
   the composition out of register. Scaling the band moves the whole
   arrangement inside one coordinate space, and overflow: hidden crops it. */

/* The resting transform is written out rather than left at `none` so both
   directions interpolate between two explicit matrices, and so the band is
   promoted to its own compositor layer up front — the Menuvo covers hold two
   playing videos, and promoting them at the moment of hover is what makes the
   first frame hitch. The easing is a plain decelerate: the quintic curve this
   used to run threw most of the movement into the first fifth of the
   transition, which is what read as a snap. */
/* The transition list lives with the reveal rules further down, because it has
   to outrank them; only the resting state is set here. */
.band {
  transform: translateY(0) scale(1);
  will-change: transform;
}

/* The hover state itself lives with the reveal rules further down: it sets
   opacity, which the reveal also sets at equal specificity, so it has to come
   after to win. */

/* Menuvo is a link now; the block keeps the surrounding layout exactly as the
   div did, and the text keeps its own colours rather than turning into link
   blue. */
.proj--linked {
  color: inherit;
  text-decoration: none;
}

.proj--linked:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 6px;
}

/* ── Menuvo: two phones on #ffda9f ───────────────────────────────────── */

.shot {
  position: absolute;
  display: block;
}

.shot--menuvo-1,
.shot--menuvo-2 {
  top: 2.578cqw; /* 33 */
  overflow: hidden;
}

/* Measured off the poster: the phone occupies 740x1532 of the 812x1578
   frame, at 6.16% in from the left and 1.01% down. Its ratio is 0.483,
   the same as the slot, so these land it edge to edge with no squeeze
   and nothing trimmed off the device. */
.shot--menuvo-1 video,
.shot--menuvo-2 video {
  position: absolute;
  display: block;
  width: 109.73%;
  height: 103%;
  left: -6.76%;
  top: -1.04%;
}

.shot--menuvo-1 {
  left: 26.953cqw; /* 345 */
  width: 16.094cqw; /* 206 */
  height: 32.813cqw; /* 420 */
  border-radius: 31px;
}

.shot--menuvo-2 {
  left: 46.328cqw; /* 593 */
  width: 16.172cqw; /* 207 */
  height: 32.891cqw; /* 421 */
  border-radius: 32px;
}

/* ── Food Blend: beli — four screens on #3c5c62, two hanging off ─────── */

.beli-logo {
  position: absolute;
  left: 4.766cqw; /* 61 */
  top: 16.406cqw; /* 210 */
  width: 5.313cqw; /* 68 */
  height: 5.313cqw;
  object-fit: cover;
}

.beli {
  position: absolute;
  width: 15.625cqw; /* 200 */
  height: 33.906cqw; /* 434 */
  object-fit: cover;
}

/* Alternating: the odd ones ride high enough to crop at the band's top. */
.beli--4 { left: 17.656cqw; top: 8.203cqw; }
.beli--3 { left: 35.234cqw; top: -5.938cqw; }
.beli--2 { left: 52.813cqw; top: 8.203cqw; }
.beli--1 { left: 70.391cqw; top: -5.938cqw; }

/* ── Rythmeet — screens rotated -30deg on #755bb5 ────────────────────── */

.band--ryth {
  border: 1px solid #4320b8;
}

.ryth {
  position: absolute;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 31.799cqw; /* 407.035 */
  width: 33.696cqw; /* 431.301 */
}

.ryth img {
  flex: none;
  width: 26.563cqw; /* 340 */
  height: 21.383cqw; /* 273.705 */
  object-fit: cover;
  border-radius: 7px;
  box-shadow: 0 4px 12px rgba(255, 255, 255, 0.4);
  transform: rotate(-30deg);
}

.ryth--6 { left: 63.75cqw; top: -10.547cqw; }
.ryth--5 { left: 37.578cqw; top: 4.309cqw; }
.ryth--event { left: 10.781cqw; top: 19.632cqw; }

.ryth--2 {
  left: -3.359cqw;
  top: -13.438cqw;
  width: 45.866cqw; /* 587.087 */
  height: 41.896cqw; /* 536.268 */
}

.ryth--2 img {
  width: 37.547cqw; /* 480.596 */
  height: 26.700cqw; /* 341.757 */
}

.ryth-logo {
  position: absolute;
  left: 72.578cqw; /* 929 */
  top: 29.453cqw; /* 377 */
  width: 17.266cqw; /* 221 */
  height: 6.25cqw; /* 80 */
  object-fit: cover;
}

/* ── Cadence — the band exported whole ───────────────────────────────── */

.band--cadence img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ══ Footer (Figma 769:2901 — 1280 x 319) ═══════════════════════════════ */

/* The paper fill spans the whole window so the footer reaches both browser
   edges instead of stopping at 1280 and leaving the grid showing in the
   margins. The 1280 frame, and with it the cqw coordinate space every child
   is positioned against, moves to .foot-inner. */
.foot {
  position: relative;
  width: 100%;
  /* Same paper fill as the nav, so the kinetic grid stops at the footer
     rather than running through it. */
  background: var(--paper);
  z-index: 1;
  /* The flower hangs past the footer's bottom edge; the frame clips it. */
  overflow: hidden;
}

.foot-inner {
  position: relative;
  width: min(100vw, calc(var(--canvas-w) * 1px));
  aspect-ratio: 1280 / 319;
  margin-inline: auto;
  container-type: inline-size;
}

.foot-nav {
  position: absolute;
  left: 3.906cqw; /* 50 — match .nav-row horizontal padding */
  top: 4.844cqw; /* 62 */
  width: 12.344cqw; /* 158 */
}

/* Figma 815:16713 — a second column beside Navigation, same baseline. */
.foot-projects {
  position: absolute;
  left: 20.781cqw; /* 266 — 50 + same 216 gap as before */
  top: 4.844cqw; /* 62 */
  width: 8.203cqw; /* 105 */
}

/* Both columns share the list treatment and the animated underline. */
.foot-projects ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.781cqw; /* 10 */
}

.foot-heading {
  margin: 0 0 0.938cqw;
  font-family: 'Syncopate', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 1.25cqw; /* 16px */
  letter-spacing: -0.063cqw;
  color: var(--ink);
}

.foot-nav ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.781cqw; /* 10 */
}

.foot-nav li a,
.foot-nav li .nav-soon,
.foot-projects li a,
.foot-projects li .nav-soon {
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue',
    Helvetica, Arial, sans-serif;
  font-size: 1.406cqw; /* 18px */
  color: #000;
  text-decoration: none;
}

/* The rule is drawn rather than using text-decoration, so it can wipe in from
   the left instead of appearing all at once. inline-block gives the pseudo a
   box to measure against; on a bare inline it would be laid out per line box.
   currentColor carries it along with the text, so the word and its underline
   warm to --terra together — the same colour the icons below take. */
.foot-nav li a,
.foot-projects li a {
  position: relative;
  display: inline-block;
  transition: color 0.3s ease;
}

.foot-nav li a::after,
.foot-projects li a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px; /* the offset the text-decoration used */
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.4s cubic-bezier(0.2, 0.5, 0.25, 1);
}

.foot-nav li a:hover,
.foot-nav li a:focus-visible,
.foot-projects li a:hover,
.foot-projects li a:focus-visible {
  color: var(--terra);
}

.foot-nav li a:hover::after,
.foot-nav li a:focus-visible::after,
.foot-projects li a:hover::after,
.foot-projects li a:focus-visible::after {
  transform: scaleX(1);
}

/* The nav column is 158px wide; the button is wider than that, so it sizes
   to its own content and never wraps. */
.to-top {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.625cqw; /* 8 */
  /* Sized to its own content and left-aligned in the column, so its edge sits
     directly under Playground above it. */
  width: max-content;
  max-width: none;
  white-space: nowrap;
  margin-top: 2.344cqw; /* 30 */
  padding: 0.625cqw 1.25cqw; /* 8 16 */
  border: 1px solid var(--ink);
  border-radius: 30px;
  background: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue',
    Helvetica, Arial, sans-serif;
  font-size: 1.172cqw; /* 15 */
  color: #fff;
  text-decoration: none;
  transition:
    background-color 0.3s ease,
    color 0.3s ease,
    transform 0.3s cubic-bezier(0.22, 1, 0.36, 1);
}

.to-top:hover,
.to-top:focus-visible {
  background: transparent;
  color: var(--ink);
  transform: translateY(-2px);
}

/* Painted in currentColor through a mask, so the arrow is exactly the colour
   of the label beside it in both states — white on the filled button, --ink
   on hover. (It was an <img> tinted by an approximated invert/hue-rotate
   filter, which never quite landed on --ink.) */
.to-top-arrow {
  display: block;
  flex: none;
  width: 1.25cqw; /* 16 */
  height: 1.25cqw;
  background: currentColor;
  -webkit-mask: url('../media/home/icon-arrow-up.svg') no-repeat center / 100% 100%;
  mask: url('../media/home/icon-arrow-up.svg') no-repeat center / 100% 100%;
}

/* A 250px image centred in the frame's 299.597 box at 808,76. */
.foot-flower {
  position: absolute;
  left: 65.063cqw;
  top: 7.875cqw;
  width: 19.531cqw; /* 250 */
  height: 19.531cqw;
  object-fit: contain;
  transform: rotate(-12.93deg);
  /* Pivot near the foot of the stem so the hover reads as the stem swaying
     rather than the whole picture spinning. */
  transform-origin: 50% 92%;
  transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}

.foot-flower:hover {
  transform: rotate(-5.5deg) scale(1.02);
}

.foot-cta {
  position: absolute;
  left: auto;
  right: calc(3.906cqw + 3px); /* 50 + 3 — flush the lead to the nav gutter */
  top: 3.203cqw; /* 41 */
  width: 20.469cqw; /* 262 */
  text-align: right;
  color: var(--ink);
}

.foot-kicker {
  margin: 0 0 0.391cqw;
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue',
    Helvetica, Arial, sans-serif;
  font-size: 1.406cqw; /* 18 */
}

.foot-lead {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue',
    Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 2.344cqw; /* 30 */
  white-space: nowrap;
}

.foot-social {
  margin: 0.781cqw 0 0;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 1.172cqw; /* 15 */
}

/* Both icons are drawn in #1D4D4E, which is --ink exactly, so masking them and
   painting currentColor reproduces them unchanged while letting the hover
   recolour them. They lift and warm to --terra together. */
.foot-social a {
  display: inline-flex;
  color: var(--ink);
  transition:
    color 0.3s ease,
    transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}

.foot-social a:hover,
.foot-social a:focus-visible {
  color: var(--terra);
  transform: translateY(-3px);
}

.foot-icon {
  display: block;
  background: currentColor;
  -webkit-mask: var(--icon) no-repeat center / 100% 100%;
  mask: var(--icon) no-repeat center / 100% 100%;
}

.foot-icon--in {
  --icon: url('../media/home/icon-linkedin.svg');
  width: 1.719cqw; /* 22 */
  height: 1.719cqw;
}

.foot-icon--mail {
  --icon: url('../media/home/icon-email.svg');
  width: 2.422cqw; /* 31 */
  height: 2.422cqw;
}

.foot-wordmark {
  margin: 2.344cqw 0 0; /* 30 */
  font-family: 'Syncopate', 'Helvetica Neue', Helvetica, Arial, sans-serif;
  font-weight: 700;
  font-size: 7.813cqw; /* 100 */
  line-height: normal;
  letter-spacing: -0.391cqw; /* -5 */
  color: var(--ink);
}

.foot-copy {
  position: absolute;
  left: 3.906cqw; /* 50 — match .foot-nav */
  top: 21.719cqw; /* 278 — dropped 11 in the updated frame */
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Helvetica Neue',
    Helvetica, Arial, sans-serif;
  font-size: 0.938cqw; /* 12 */
  letter-spacing: -0.047cqw;
  color: #898989;
}

/* ══ Page transitions ═══════════════════════════════════════════════════
   Soft paper dissolve: content fades and drifts a little while the named
   nav + grid hold still. Duration is long enough to read as intentional. */

::view-transition-old(root),
::view-transition-new(root) {
  mix-blend-mode: normal;
}

::view-transition-old(root) {
  animation: page-out 0.48s cubic-bezier(0.4, 0, 0.2, 1) both;
}

::view-transition-new(root) {
  animation: page-in 0.5s cubic-bezier(0.33, 0, 0.2, 1) both;
}

@keyframes page-out {
  to {
    opacity: 0;
    transform: translateY(-10px);
  }
}

@keyframes page-in {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
}

/* Shared chrome: hold still — no group morph, no old/new crossfade.
   show only the destination snapshot so the active pill can update without
   sliding the bar around. */
::view-transition-group(nav),
::view-transition-group(grid) {
  animation: none;
}

::view-transition-old(nav),
::view-transition-old(grid) {
  display: none;
}

::view-transition-new(nav),
::view-transition-new(grid) {
  animation: none;
}

/* Transforming scroll-snap targets re-seats the scroller mid-leave, which
   drags absolute .nav with it. Kill snap for the transition window. */
html.pt-anim,
html.pt-pending {
  scroll-snap-type: none !important;
}

/* Pin shell nav to the viewport while content fades so scroll / snap cannot
   move it. JS adds .pt-nav-pinned only when the bar is already on-screen. */
.nav.pt-nav-pinned {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
}

/* Primary shell path — js/page-transitions.js toggles html.pt-* classes.
   Animate stage + footer (and About's sticky caption) so nav/grid stay put. */
html.pt-anim .stage,
html.pt-anim .foot,
html.pt-anim .not-designing {
  transition:
    opacity 0.48s cubic-bezier(0.33, 0, 0.2, 1),
    transform 0.48s cubic-bezier(0.33, 0, 0.2, 1);
  will-change: opacity, transform;
}

html.pt-anim.pt-leaving .stage,
html.pt-anim.pt-leaving .foot,
html.pt-anim.pt-leaving .not-designing {
  opacity: 0;
  transform: translateY(-12px);
}

html.pt-anim.pt-entering .stage,
html.pt-anim.pt-entering .foot,
html.pt-anim.pt-entering .not-designing,
html.pt-pending .stage,
html.pt-pending .foot,
html.pt-pending .not-designing {
  opacity: 0;
  transform: translateY(14px);
}

/* Soft paper veil under the nav (z-index 3) while content dissolves. */
html.pt-anim::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  background: color-mix(in srgb, var(--paper) 72%, transparent);
  transition: opacity 0.48s cubic-bezier(0.33, 0, 0.2, 1);
}

html.pt-anim.pt-leaving::after,
html.pt-anim.pt-entering::after {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  @view-transition {
    navigation: none;
  }

  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }

  html.pt-pending .stage,
  html.pt-pending .foot,
  html.pt-pending .not-designing,
  html.pt-anim .stage,
  html.pt-anim .foot,
  html.pt-anim .not-designing {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }

  html.pt-anim::after {
    display: none;
  }
}

/* Mirrors prefers-reduced-motion when html.reduce-motion is set. */
html.reduce-motion::view-transition-group(*),
html.reduce-motion::view-transition-old(*),
html.reduce-motion::view-transition-new(*) {
  animation: none !important;
}

html.reduce-motion.pt-pending .stage,
html.reduce-motion.pt-pending .foot,
html.reduce-motion.pt-pending .not-designing,
html.reduce-motion.pt-anim .stage,
html.reduce-motion.pt-anim .foot,
html.reduce-motion.pt-anim .not-designing {
  opacity: 1 !important;
  transform: none !important;
  transition: none !important;
}

html.reduce-motion.pt-anim::after {
  display: none;
}

/* ══ Custom cursor ══════════════════════════════════════════════════════
   Figma 813:16694 — a 40px circle, white at 49% behind a 2px #B9B5B5 ring.
   js/cursor.js adds .cursor-on only on a fine pointer, so touch devices keep
   their own behaviour and nothing here applies. */

.cursor-on * {
  cursor: none;
}

.cursor {
  position: fixed;
  left: 0;
  top: 0;
  width: 40px;
  height: 40px;
  /* Centres the circle on the pointer, so transform can carry raw client
     coordinates untouched. */
  margin: -20px 0 0 -20px;
  border: 2px solid #b9b5b5;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.49);
  pointer-events: none;
  opacity: 0;
  z-index: 999;
  will-change: transform;
  /* Only the fade in and out is animated. The circle is one fixed size on
     every page and over every element — nothing about what is underneath it
     changes its shape or position. */
  transition: opacity 0.25s ease;
}

.cursor.is-on {
  opacity: 1;
}

/* ══ Motion ═════════════════════════════════════════════════════════════ */

/* Reveals are gated on the `js` class, which reveal.js adds. Without it the
   page renders fully visible — no chance of content stuck at opacity 0. */
/* The lift uses the independent `translate` property rather than `transform`.
   .band is both a reveal target and the thing that moves on hover, and when
   the reveal owned `transform` it also owned the transition on it — so the
   hover silently ran at the reveal's 0.7s quintic no matter what the hover
   rule asked for. Keeping the two on separate properties lets each animate
   on its own terms, and they compose instead of overwriting each other. */
.js [data-reveal] {
  opacity: 0;
  translate: 0 1.4rem;
  transition:
    opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1),
    translate 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}

.js [data-reveal].is-in {
  opacity: 1;
  translate: none;
}

/* .band reveals and responds to the cursor, so it carries one transition list
   covering both jobs. It has to sit at .js-level specificity to beat the
   reveal rule's shorthand above, which would otherwise drop transform and
   box-shadow from the list entirely and make the hover jump. */
.js .band,
.band {
  transition:
    opacity 0.9s cubic-bezier(0.2, 0.5, 0.25, 1),
    translate 0.7s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.9s cubic-bezier(0.2, 0.5, 0.25, 1),
    box-shadow 0.9s cubic-bezier(0.2, 0.5, 0.25, 1);
}

/* The cover scales up a touch, lifts, and lightens. This sits after the
   reveal rules on purpose — `.js [data-reveal].is-in` pins opacity to 1 at
   the same specificity as `.proj:hover .band`, so an earlier rule would lose
   the tie and the fade would never happen. */
.proj:hover .band,
.proj:focus-visible .band {
  transform: translateY(-10px) scale(1.02);
  opacity: 0.92;
  box-shadow: 0 1.4cqw 3.1cqw -1.4cqw rgba(29, 77, 78, 0.42);
}

/* The landing's three blocks cascade on load. */
.js .stage .specimens { transition-delay: 0.1s; }
.js .stage .lede { transition-delay: 0.26s; }
.js .stage .wordmark { transition-delay: 0.36s; }

/* Specimens breathe. The sway sits on the image so it composes with the
   hover lean on the frame rather than fighting it for the transform. */
@keyframes sway {
  0%, 100% { transform: rotate(-0.5deg); }
  50% { transform: rotate(0.5deg); }
}

.sp img {
  transform-origin: bottom center;
  animation: sway 9s ease-in-out infinite;
}

.sp--1 img { animation-duration: 8.5s; animation-delay: -1.2s; }
.sp--2 img { animation-duration: 10.5s; animation-delay: -3.4s; }
.sp--3 img { animation-duration: 9.4s; animation-delay: -0.6s; }
.sp--4 img { animation-duration: 11.2s; animation-delay: -2.1s; }
.sp--5 img { animation-duration: 9.9s; animation-delay: -4.3s; }

/* Hovering steadies the stem you're looking at. */
.sp:hover img {
  animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  html:has(.stage--project) {
    scroll-snap-type: none;
  }

  .to-top,
  .to-top:hover {
    transform: none;
    transition: background-color 0.2s ease, color 0.2s ease;
  }

  /* Colour still responds to hover; the movement does not. */
  .foot-social a,
  .foot-social a:hover {
    transform: none;
    transition: color 0.2s ease;
  }

  /* The rule still appears, it just does not wipe across. */
  .foot-nav li a::after,
  .foot-projects li a::after {
    transition: none;
  }

  .foot-flower {
    transition: none;
  }

  .foot-flower:hover {
    transform: rotate(-12.93deg);
  }

  /* Nothing moves, so there is no layer worth promoting either. */
  .band,
  .proj:hover .band {
    transform: none;
    box-shadow: none;
    transition: none;
    will-change: auto;
  }

  .js [data-reveal] {
    opacity: 1;
    transform: none;
    translate: none;
    transition: none;
  }

  .sp img {
    animation: none;
  }
}

/* ══ Small screens ══════════════════════════════════════════════════════
   The fixed 1280 composition stops being readable well before phone
   widths, so every frame unpins and stacks. */

@media (max-width: 43rem) {
  /* Stacked mobile stages are variable height — snap would feel sticky. */
  html:has(.stage--project) {
    scroll-snap-type: none;
  }

  .stage,
  .stage--project,
  .foot {
    aspect-ratio: auto;
    min-height: 0;
    padding: 1.5rem 1.25rem 2.5rem;
    display: flex;
    flex-direction: column;
    container-type: normal;
  }

  .stage {
    min-height: 100svh;
  }

  .nav {
    position: static;
    width: auto;
  }

  /* Don't pull the in-flow mobile bar out of flow during leave/enter. */
  .nav.pt-nav-pinned {
    position: static;
  }

  /* The nav is its own block above the stage now, so it carries the page
     gutter itself rather than inheriting the stage's. */
  .nav-inner {
    width: auto;
    container-type: normal;
  }

  .nav-row {
    padding: 1.5rem 1.25rem 0;
  }

  .nav a,
  .nav .nav-soon {
    padding: 0.45rem 0.7rem;
    font-size: 0.9rem;
    letter-spacing: -0.03em;
  }

  .mark { width: 37px; height: 33px; }
  .mark-sprig { width: 23px; height: 33px; }
  .mark-s { left: 23px; top: 3px; font-size: 20px; }
  .mark-l { left: 27px; top: 8px; font-size: 20px; }

  /* Specimens become a scrollable strip with their labels always shown. */
  .specimens {
    display: flex;
    gap: 0.5rem;
    margin: 1rem 0 2rem;
    overflow-x: auto;
    scrollbar-width: none;
  }

  .specimens::-webkit-scrollbar { display: none; }

  .specimens li {
    display: block;
    position: relative;
    flex: 0 0 7.5rem;
  }

  .sp {
    position: relative;
    left: auto;
    top: auto;
    width: 100%;
    height: 10rem;
  }

  .sp-tag {
    position: static;
    opacity: 1;
    transform: none;
    display: inline-flex;
    margin-top: 0.4rem;
    padding: 0.35rem 0.6rem;
    font-size: 0.7rem;
    letter-spacing: 0;
  }

  .intro-block,
  .lede,
  .wordmark {
    position: static;
    width: auto;
  }

  .intro-block { margin-top: auto; }
  .lede { font-size: 1rem; text-align: left; }

  .wordmark {
    margin-top: 0.75rem;
    font-size: clamp(1.6rem, 8vw, 2.6rem);
    letter-spacing: -0.03em;
    white-space: normal;
  }

  /* ── Projects ─────────────────────────────────────────────────────── */

  .proj,
  .proj--menuvo {
    position: static;
    width: auto;
    gap: 1rem;
  }

  .band {
    height: 14rem;
  }

  .proj-foot {
    flex-direction: column;
    align-items: flex-start;
    gap: 0.75rem;
  }

  .proj-text { width: auto; }
  .proj-text h2 { font-size: 1.4rem; letter-spacing: -0.02em; }
  /* Nothing fits on one line at phone widths, so wrapping comes back. */
  .proj-text p {
    font-size: 1rem;
    text-align: left;
    letter-spacing: -0.01em;
    white-space: normal;
  }
  .proj-tag { padding: 0.35rem 0.6rem; font-size: 0.8rem; }

  /* Each band keeps its composition but scales to the narrower frame. */
  .shot--menuvo-1, .shot--menuvo-2 { top: 1rem; height: 12rem; width: 5.8rem; }
  .shot--menuvo-1 { left: 18%; }
  .shot--menuvo-2 { left: 52%; }

  .beli-logo { left: 5%; top: 42%; width: 2.4rem; height: 2.4rem; }
  .beli { width: 5.4rem; height: 11.7rem; }
  .beli--4 { left: 16%; top: 12%; }
  .beli--3 { left: 38%; top: -14%; }
  .beli--2 { left: 60%; top: 12%; }
  .beli--1 { left: 82%; top: -14%; }

  .ryth { width: 16rem; height: 15rem; }
  .ryth img { width: 12rem; height: 9.7rem; }
  .ryth--2 { width: 18rem; height: 16rem; left: -12%; top: -18%; }
  .ryth--2 img { width: 14rem; height: 10rem; }
  .ryth--6 { left: 56%; top: -12%; }
  .ryth--5 { left: 30%; top: 8%; }
  .ryth--event { left: 4%; top: 30%; }
  .ryth-logo { left: 62%; top: 62%; width: 8rem; height: 2.9rem; }

  /* ── Footer ───────────────────────────────────────────────────────── */

  .foot-inner {
    position: static;
    width: auto;
    aspect-ratio: auto;
    container-type: normal;
    display: flex;
    flex-direction: column;
    gap: 2rem;
  }

  .foot-nav,
  .foot-cta,
  .foot-copy,
  .foot-flower {
    position: static;
    width: auto;
  }

  .foot-flower { display: none; }
  .foot-heading { font-size: 1rem; letter-spacing: -0.02em; margin-bottom: 0.75rem; }
  .foot-nav ul, .foot-projects ul { gap: 0.5rem; }
  .foot-nav li a, .foot-nav li .nav-soon,
  .foot-projects li a, .foot-projects li .nav-soon { font-size: 1rem; }

  .to-top {
    margin-top: 1.25rem;
    gap: 0.5rem;
    padding: 0.55rem 1rem;
    font-size: 1rem;
  }

  .to-top-arrow { width: 1rem; height: 1rem; }

  .foot-cta { text-align: left; }
  .foot-kicker { font-size: 1rem; }
  .foot-lead { font-size: 1.4rem; white-space: normal; }
  .foot-social { justify-content: flex-start; gap: 0.9rem; margin-top: 0.75rem; }
  .foot-icon--in { width: 22px; height: 22px; }
  .foot-icon--mail { width: 28px; height: 28px; }

  .foot-wordmark {
    margin-top: 1.25rem;
    font-size: clamp(2.5rem, 16vw, 4rem);
    letter-spacing: -0.03em;
  }

  .foot-copy { font-size: 0.75rem; letter-spacing: 0; }
}
