/* ==========================================================================
   Portfolio — Stylesheet

   Sections:
   1. Custom properties & reset
   2. Deck & screens
   3. Payoff, CTA, work & closing
   4. Statements & reveal
   5. Chrome (border, blobs, grid, noise)
   6. Entrance & preloader
   7. Splash & navigation
   8. Intro sequence player
   9. Reduced motion overrides
   ========================================================================== */

/* --- 1. Custom properties & reset --- */

/* Animatable frame thickness — inset/padding track this as it springs
   between the thin chrome (18px) and the red statement (36px). */
@property --frame-width {
  syntax: "<length>";
  inherits: true;
  initial-value: 18px;
}

:root {
  --bg: #04251f;
  --accent: #fffca1;
  --blob-blur: 120px;
  --grid-size: 44px;
  --grid-color: 255, 255, 255;
  --grid-opacity: 0.02;
  --noise: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='5' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23noise)' opacity='1'/%3E%3C/svg%3E");

  /* Decorative frame (v2 deck) — content inset tracks border thickness + gap. */
  --frame-width: 18px;
  --frame-red-width: 36px;
  --frame-gap: clamp(10px, 2vw, 16px);
  --frame-inset: calc(var(--frame-width) + var(--frame-gap));
  --nav-zone: clamp(40px, 5.5vw, 64px);
  --menu-pad-y: clamp(28px, 4.6vw, 70px);
  --menu-pad-x: clamp(20px, 4.2vw, 72px);
  --frame-pad-duration: 0.85s;

  /* Cap layout width on ultra-wide screens; borders stay full-bleed. */
  --content-max: 1920px;
  --content-side: max(0px, calc((100vw - var(--content-max)) / 2));
}

* {
  box-sizing: border-box;
}

html {
  /* Root is the scroll container. A JS controller drives navigation one
     screen per gesture (see deck controller), so native snapping is off. */
  scroll-snap-type: none;
  /* Scroll stays locked until the preloader hands off (JS adds .is-ready). */
  overflow: hidden;
}

html.is-ready {
  overflow-y: auto;
}

html,
body {
  margin: 0;
  min-height: 100%;
  background: var(--bg);
  font-family: Inter, Arial, sans-serif;
}

/* Re-declare inset on body so var(--frame-width) resolves here (not frozen
   at :root), and transition thickness so padding eases with the border. */
body:not(.work-body) {
  --frame-inset: calc(var(--frame-width) + var(--frame-gap));
  transition: --frame-width var(--frame-pad-duration) ease;
}

/* --- 2. Deck & screens --- */

.deck {
  position: relative;
  z-index: 0;
}

.screen {
  position: relative;
  min-height: 100dvh;
}

/* v2 deck — keep readable clearance from the morphing border frame.
   Padding tracks --frame-width directly (no extra padding transition —
   that would fight the interpolated custom property). */
.deck .screen {
  box-sizing: border-box;
  padding:
    calc(var(--frame-inset) + env(safe-area-inset-top, 0px))
    calc(var(--frame-inset) + env(safe-area-inset-right, 0px))
    calc(var(--frame-inset) + env(safe-area-inset-bottom, 0px))
    calc(var(--frame-inset) + env(safe-area-inset-left, 0px));
}

.deck .screen--splash {
  padding-top: calc(var(--frame-inset) + var(--nav-zone) + env(safe-area-inset-top, 0px));
}

/* Red statement: thicken the frame. Exit classes animate back to 18px so
   inset restores with the border (instead of staying thick then snapping). */
body.is-red:not(.work-body) {
  --frame-width: 36px;
}

body.is-red-out:not(.work-body),
body.is-red-fade:not(.work-body) {
  --frame-width: 18px;
}

.screen--white {
  background: #fff;
  color: var(--bg);
  display: grid;
  place-items: center;
  perspective: 70rem;
}

.screen--green {
  background: var(--bg);
  color: #fff;
}

/* --- 3. Payoff, CTA, work & closing --- */

/* Green payoff screen (screen 3). Green backdrop (noise, grid) without blobs —
   blobs live on the splash only for performance. */
.screen--payoff {
  overflow: hidden;
  display: grid;
  place-items: center;
  color: #fff;
  perspective: 70rem; /* depth for the cursor tilt on the payoff line */
}

.screen--payoff .layer {
  z-index: 0;
}

/* Large, bold, centered white impact line. Reuses the statement scale but
   paints solid white instead of the teal gradient. */
.payoff-line {
  position: relative;
  z-index: 1;
  width: 100%;
  max-width: 100%;
  margin: 0;
  font-family: Inter, Arial, sans-serif;
  font-weight: 700;
  /* Sized off the viewport width so the whole line stays on ONE row at any
     width, capped at the white statements' scale on large screens. */
  font-size: clamp(20px, 5.4vw, calc(3.25rem * 1.21));
  line-height: 1.16;
  letter-spacing: -0.01em;
  text-align: center;
  white-space: nowrap;
  color: #fff;
  user-select: none;
  transform-style: preserve-3d;
  will-change: transform;
}

/* Hand-drawn marker circle around "best". The word is the positioning
   context; the SVG overflows it on every side so the loop wraps the word
   (and overshoots the start, like the Figma sketch). preserveAspectRatio
   "none" lets the loop stretch to the word's box at any font size. */
.circled {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}

.scribble {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 152%;
  height: 184%;
  transform: translate(-50%, -50%) rotate(-2.5deg);
  overflow: visible;
  pointer-events: none;
}

.scribble path {
  fill: none;
  stroke: var(--accent);
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
  /* --draw-len is the path's length (set by JS). The RESTING state is fully
     drawn (offset 0) so that when the line scrolls off, the marker fades +
     blurs out WITH the text (it rides the parent's reveal opacity/blur)
     instead of un-drawing. The draw-on-arrival starts from the full offset
     via the keyframe's `from` below. */
  stroke-dasharray: var(--draw-len, 460);
  stroke-dashoffset: 0;
}

/* Drawn only once the payoff line has revealed, and a beat AFTER the words
   land (delay), so it reads as "the line arrives, then I circle the word".
   `both` holds the hidden `from` during the delay; after it, the resting
   offset 0 keeps it drawn so the exit just fades. */
.payoff-line.is-visible .scribble path {
  animation: scribbleDraw 0.7s cubic-bezier(0.6, 0, 0.35, 1) 0.45s both;
}

@keyframes scribbleDraw {
  from { stroke-dashoffset: var(--draw-len, 460); }
  to   { stroke-dashoffset: 0; }
}

/* Reduced motion: no live drawing — just show the finished circle. */
@media (prefers-reduced-motion: reduce) {
  .scribble path { stroke-dashoffset: 0; animation: none; }
}

/* ----------------------------------------------------------------
   Green CTA screen (screen 4)
   Title + paragraph + two enlarged nav-style roll links, centered over
   the reused green backdrop. Sits above the backdrop layers.
---------------------------------------------------------------- */
/* The payoff line and the CTA block share ONE grid cell so they stack
   centered on top of each other; JS reveals one at a time as you scroll
   (same in-place swap as the white screen's statements). */
.screen--payoff > .payoff-line,
.screen--payoff > .cta-block {
  grid-area: 1 / 1;
}

/* Scroll-like swap: the title/paragraph block slides a full page vertically
   (driven by JS inline transforms) while it fades + blurs, so the content
   appears to scroll into place rather than crossfade. The backdrop, nav,
   and border never move. The links do NOT slide — they fade in (opacity)
   once the movement is done. transform timing wins over the global .reveal
   rule via the higher specificity here. */
.screen--payoff > .cta-block {
  transition: opacity 0.7s ease, filter 0.7s ease,
              transform 0.85s cubic-bezier(0.16, 1, 0.3, 1);
}

.cta-block {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: min(1100px, 100%);
  padding: 0 24px;
  text-align: center;
  perspective: 70rem; /* depth for the cursor tilt on the title/paragraph */
}

/* Inner wrapper that carries the cursor tilt (the slide transform lives on
   .cta-block, so the two never fight). Shared with the pattern screen. */
.cta-tilt,
.pattern-tilt,
.about-tilt,
.footer-tilt {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(10px, 1.1vw, 16px);
  width: 100%;
  transform-style: preserve-3d;
  will-change: transform;
}

/* Title: one row on desktop, with a horizontal white -> yellow gradient
   painted onto the text. ~36pt on large screens, scaling down below. */
.cta-title {
  margin: 0;
  font-family: Inter, Arial, sans-serif;
  font-weight: 700;
  font-size: clamp(26px, 3.6vw, 48px); /* 48px ~= 36pt */
  line-height: 1.15;
  letter-spacing: -0.01em;
  white-space: nowrap;
  background: linear-gradient(90deg, var(--accent) 0%, #ffffff 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}

.cta-copy {
  margin: 0;
  max-width: 56ch;
  font-size: clamp(17px, 1.9vw, 24px);
  line-height: 1.7;
  color: #fff;
  opacity: 0.9;
  text-align: center;
  text-wrap: balance;
}

/* Links pinned near the bottom of the green CTA (player is hidden there). */
.cta-links {
  position: absolute;
  left: 50%;
  bottom: calc(var(--frame-inset) + clamp(16px, 4vh, 48px));
  transform: translateX(-50%);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: clamp(16px, 1.6vw, 22px);
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.cta-link {
  font-weight: 700;
}

.cta-sep {
  padding: 0 0.25em;
}

/* Below the width where the title fits on one row, let it wrap + scale. */
@media (max-width: 1024px) {
  .cta-title { white-space: normal; text-wrap: balance; }
}

/* Reduced motion: no JS stepping on the green screen either — un-stack the
   payoff line and CTA block into normal flow so they don't overlap. */
@media (prefers-reduced-motion: reduce) {
  .screen--payoff { grid-auto-flow: row; gap: 14vh; padding: 16vh 0; }
  .screen--payoff > .payoff-line,
  .screen--payoff > .cta-block {
    grid-area: auto;
  }
  .cta-links { position: static; transform: none; margin-top: 24px; }
}

/* ----------------------------------------------------------------
   Work section (one white screen, content swapped in place) + closing
---------------------------------------------------------------- */
/* Work section: one white screen, grid fixed; 6 project states stacked and
   swapped in place (the grid never scrolls). */
.screen--work {
  overflow: hidden;
}

.screen--work .layer {
  z-index: 0;
}

/* Work screen decor — hidden unless .is-live (JS, per work step). */
.work-decor {
  display: block;
  z-index: 0;
  overflow: visible;
  pointer-events: none;
}

.work-food-frost {
  display: block;
  opacity: 0;
  z-index: 1;
  pointer-events: none;
  transition: opacity 0.75s ease;
}

.work-food__item,
.work-gaming__item {
  position: absolute;
  height: auto;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
  opacity: 0;
  filter: blur(10px);
  will-change: translate, opacity, filter;
  transition:
    opacity 0.75s ease,
    filter 0.75s ease;
}

.work-decor.is-live .work-food__item,
.work-decor.is-live .work-gaming__item {
  opacity: 1;
  filter: blur(0);
}

.work-decor.is-live .work-food__item:nth-child(1),
.work-decor.is-live .work-gaming__item:nth-child(1) { transition-delay: 0.04s; }
.work-decor.is-live .work-food__item:nth-child(2),
.work-decor.is-live .work-gaming__item:nth-child(2) { transition-delay: 0.09s; }
.work-decor.is-live .work-food__item:nth-child(3),
.work-decor.is-live .work-gaming__item:nth-child(3) { transition-delay: 0.14s; }
.work-decor.is-live .work-food__item:nth-child(4),
.work-decor.is-live .work-gaming__item:nth-child(4) { transition-delay: 0.19s; }
.work-decor.is-live .work-food__item:nth-child(5),
.work-decor.is-live .work-gaming__item:nth-child(5) { transition-delay: 0.24s; }
.work-decor.is-live .work-food__item:nth-child(6),
.work-decor.is-live .work-gaming__item:nth-child(6) { transition-delay: 0.29s; }
.work-decor.is-live .work-food__item:nth-child(7),
.work-decor.is-live .work-gaming__item:nth-child(7) { transition-delay: 0.34s; }
.work-decor.is-live .work-food__item:nth-child(8) { transition-delay: 0.39s; }
.work-decor.is-live .work-food__item:nth-child(9) { transition-delay: 0.44s; }
.work-decor.is-live .work-food__item:nth-child(10) { transition-delay: 0.49s; }

.work-decor:not(.is-live) .work-food__item,
.work-decor:not(.is-live) .work-gaming__item {
  opacity: 0;
  filter: blur(10px);
  transition-delay: 0s;
  transition-duration: 0.5s;
}

.work-decor.is-work-slide .work-food__item,
.work-decor.is-work-slide .work-gaming__item {
  opacity: 1;
  filter: blur(0);
  transition: none;
  transition-delay: 0s;
}

.work-decor.is-work-slide {
  transition:
    opacity 1.4s ease,
    filter 1.4s ease,
    transform 1.9s cubic-bezier(0.22, 1, 0.36, 1);
}

.work-food__item--potato {
  top: 9%;
  left: 5%;
  width: clamp(84px, 8.8vw, 128px);
  transform: rotate(-20deg);
}

.work-food__item--corn {
  top: 10%;
  left: 50%;
  width: clamp(213px, 17.6vw, 281px);
  transform: translate(-50%, -56%);
}

.work-food__item--tomato {
  top: 11%;
  right: 7%;
  width: clamp(126px, 13vw, 187px);
  transform: rotate(14deg);
}

.work-food__item--spinach {
  top: 40%;
  left: 4%;
  width: clamp(92px, 9.6vw, 144px);
  transform: rotate(-10deg);
}

.work-food__item--carrot {
  top: 53%;
  left: 24%;
  width: clamp(36px, 3.8vw, 56px);
  transform: rotate(28deg);
}

.work-food__item--radish {
  top: 41%;
  right: 11%;
  width: clamp(34px, 3.6vw, 54px);
  transform: rotate(-16deg);
}

.work-food__item--cucumber {
  top: 53%;
  right: -1%;
  width: clamp(184px, 15.5vw, 272px);
  transform: rotate(-45deg);
}

.work-food__item--cabbage {
  bottom: 1%;
  left: 2%;
  width: clamp(168px, 17.6vw, 261px);
  transform: rotate(-6deg);
}

.work-food__item--pumpkin {
  bottom: 6%;
  left: 75%;
  width: clamp(76px, 8vw, 116px);
  transform: translateX(-50%) rotate(6deg);
}

.work-food__item--turnip {
  bottom: -12%;
  right: 6%;
  width: clamp(90px, 9.4vw, 138px);
  transform: rotate(22deg);
}

/* Pragmatic Play gaming decor — step 1. */
.work-gaming__item--medal {
  top: -6%;
  left: -4%;
  width: clamp(200px, 22vw, 320px);
  transform: rotate(-12deg);
}

.work-gaming__item--cards {
  top: 6%;
  right: -4%;
  width: clamp(180px, 19vw, 280px);
  transform: rotate(22deg);
}

.work-gaming__item--cash-a {
  top: 30%;
  left: 6%;
  width: clamp(88px, 9.5vw, 140px);
  transform: rotate(38deg);
}

.work-gaming__item--cash-b {
  top: 26%;
  right: 10%;
  width: clamp(64px, 7vw, 104px);
  transform: rotate(-28deg);
}

.work-gaming__item--chips {
  bottom: 4%;
  left: -2%;
  width: clamp(168px, 18vw, 268px);
  transform: rotate(8deg);
}

.work-gaming__item--slot {
  bottom: 10%;
  left: 56%;
  width: clamp(104px, 10.8vw, 168px);
  transform: translateX(-50%) rotate(-6deg);
}

.work-gaming__item--goldcoin {
  bottom: -4%;
  right: -3%;
  width: clamp(188px, 20vw, 300px);
  transform: rotate(4deg);
}

/* Top + bottom frost — shared blur gradient for all work decor steps. */
.work-food-frost.is-live {
  opacity: 1;
  --frost-blur: 20px;
  --frost-band: 120px;
  backdrop-filter: blur(var(--frost-blur));
  -webkit-backdrop-filter: blur(var(--frost-blur));
  mask-image: linear-gradient(
    to bottom,
    #000 0,
    transparent var(--frost-band),
    transparent calc(100% - var(--frost-band)),
    #000 100%
  );
  -webkit-mask-image: linear-gradient(
    to bottom,
    #000 0,
    transparent var(--frost-band),
    transparent calc(100% - var(--frost-band)),
    #000 100%
  );
}

.work-stack {
  position: relative;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 100%;
}

.work-stack > .work-content {
  grid-area: 1 / 1;
  pointer-events: none;
  transition: opacity 1s ease, filter 1s ease,
              transform 1.3s cubic-bezier(0.22, 1, 0.36, 1);
}

.work-stack > .work-content.is-visible {
  pointer-events: auto;
}

.work-content {
  position: relative;
  z-index: 1; /* above the white screen's grid layer */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(12px, 1.6vw, 20px);
  width: 100%;
  max-width: min(880px, 100%);
  padding: 0 24px;
  text-align: center;
}

.work-eyebrow {
  margin: 0;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 20%;
  text-transform: uppercase;
  color: #17917D;
}

.work-title {
  margin: 0;
  font-weight: 800;
  font-size: clamp(28px, 4vw, 36px);
  line-height: 1.12;
  letter-spacing: 2%;
  color: var(--bg);
}

.work-copy {
  margin: 0;
  max-width: 52ch;
  font-size: clamp(16px, 1.6vw, 24px);
  line-height: 1.6;
  color: #2b3a36;
}

.work-soon {
  margin: clamp(8px, 1.2vw, 14px) 0 0;
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(4, 37, 31, 0.38);
}

.work-content--archive {
  gap: 0;
}

.work-archive-link {
  margin: 0;
  font-family: Inter, Arial, sans-serif;
  font-weight: 700;
  font-size: clamp(32px, 4.6vw, 52px);
  line-height: 1.12;
  letter-spacing: -0.01em;
  text-align: center;
  text-decoration: underline;
  text-decoration-color: #189883;
  text-underline-offset: 0.18em;
  background: linear-gradient(90deg, #0E6A5B 0%, #189883 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  transition: filter 0.25s ease, text-decoration-color 0.25s ease;
}

.work-archive-link:hover {
  text-decoration: none;
  filter: brightness(1.08);
  outline: none;
}

.work-archive-link:focus-visible {
  outline: none;
  filter: brightness(1.08);
}

/* Work dots — intro-player language, dots only, visible on the work screen. */
.work-dots {
  --work-dot: #1C937F;

  position: fixed;
  left: 50%;
  bottom: calc(var(--frame-inset) + clamp(12px, 2vw, 24px));
  z-index: 5;
  display: flex;
  justify-content: center;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transform: translateX(-50%);
  transition:
    opacity 0.55s ease,
    visibility 0.55s;
}

.work-dots.is-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.work-dots.is-entering {
  animation: introPlayerIn 0.65s cubic-bezier(0.16, 1, 0.3, 1) both;
}

.work-dots__list {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.work-dots__dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--work-dot);
  opacity: 0.5;
  cursor: pointer;
  transition:
    width 0.25s ease,
    height 0.25s ease,
    opacity 0.25s ease;
}

.work-dots__dot.is-done,
.work-dots__dot.is-active {
  width: 12px;
  height: 12px;
  opacity: 1;
}

.work-dots__dot:focus-visible {
  outline: 2px solid var(--work-dot);
  outline-offset: 3px;
}

/* Reduced motion: no JS stepping — un-stack the work states into flow. */
@media (prefers-reduced-motion: reduce) {
  .screen--work { grid-auto-flow: row; gap: 14vh; padding: 16vh 0; }
  .work-stack { display: block; }
  .work-stack > .work-content { grid-area: auto; margin: 8vh auto; }
  .work-decor,
  .work-food-frost { display: none !important; }
  .work-food__item,
  .work-gaming__item { will-change: auto; }
  .work-dots { display: none !important; }
}

/* Closing green screen: 3 placeholder states stacked over the splash
   backdrop, swapped in place (the backdrop never scrolls). */
.screen--closing {
  position: relative;
  overflow: hidden;
  display: grid;
  place-items: center;
  color: #fff;
}

.screen--closing .layer {
  z-index: 0;
}

/* The 3 placeholder lines share ONE grid cell so they stack centered; JS
   reveals one at a time as you scroll (in place). */
.closing-stack {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 100%;
}

.closing-stack > .closing-state {
  grid-area: 1 / 1;
  pointer-events: none;
  transition: opacity 1s ease, filter 1s ease,
              transform 1.3s cubic-bezier(0.22, 1, 0.36, 1);
}

.closing-stack > .closing-state.is-visible {
  pointer-events: auto;
}

/* "The pattern" — title + paragraph over the green backdrop (no links). */
.pattern-block {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  max-width: min(1100px, 100%);
  padding: 0 24px;
  text-align: center;
  perspective: 70rem;
}

/* "Companies usually bring me when…" — title + checklist; links pinned
   separately on the screen (see .about-links). */
.about-block {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  width: 100%;
  max-width: min(1100px, 100%);
  padding: 0 24px;
  text-align: center;
  perspective: 70rem;
  box-sizing: border-box;
}

.about-title {
  margin: 0 0 clamp(20px, 3vh, 36px);
}

.about-checklist {
  margin: 0 auto;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: clamp(16px, 2.4vh, 28px);
  width: 100%;
}

.about-item {
  width: 100%;
  font-size: clamp(11px, 0.92vw, 15px);
  line-height: 3.5;
  color: #fff;
  opacity: 0.92;
  text-align: center;
}

.about-text {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}

.about-check {
  position: absolute;
  right: calc(100% + 0.55em);
  top: 50%;
  transform: translateY(-50%);
  width: 1.25em;
  height: 1.25em;
  color: var(--accent);
  pointer-events: none;
  flex-shrink: 0;
}

/* Tablet: allow wrap, bump type — still keep the hanging-check layout. */
@media (max-width: 1024px) {
  .about-item {
    font-size: clamp(14px, 1.6vw, 18px);
    line-height: 1.45;
  }

  .about-text {
    white-space: normal;
  }

  .about-check {
    top: 0.15em;
    transform: none;
  }

  .about-checklist {
    gap: clamp(12px, 2vh, 20px);
    max-width: 42ch;
  }
}

/* Phone: flex rows, readable type, fit the viewport. */
@media (max-width: 860px) {
  .about-block {
    padding:
      calc(var(--nav-zone) + 8px)
      clamp(16px, 5vw, 28px)
      12px;
    max-height: 100%;
  }

  .about-tilt {
    gap: 14px;
    max-width: 100%;
  }

  .about-title {
    margin: 0;
    font-size: clamp(22px, 6.2vw, 32px);
    line-height: 1.2;
    letter-spacing: -0.02em;
    max-width: 14ch;
  }

  .about-checklist {
    gap: 10px;
    max-width: none;
    width: 100%;
  }

  .about-item {
    font-size: clamp(14px, 3.8vw, 16px);
    line-height: 1.4;
    text-align: left;
    opacity: 0.95;
  }

  .about-text {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    width: 100%;
    text-align: left;
    text-wrap: pretty;
  }

  .about-check {
    position: static;
    transform: none;
    width: 1.2em;
    height: 1.2em;
    margin-top: 0.12em;
  }
}

@media (max-width: 860px) and (max-height: 700px) {
  .about-block {
    padding-top: calc(var(--nav-zone) + 4px);
  }

  .about-tilt {
    gap: 10px;
  }

  .about-title {
    font-size: clamp(18px, 5vw, 24px);
  }

  .about-checklist {
    gap: 8px;
  }

  .about-item {
    font-size: 13px;
    line-height: 1.35;
  }

  .about-check {
    width: 1.05em;
    height: 1.05em;
  }
}

/* About links pinned to the screen bottom on that step. Hidden for now. */
.screen--closing > .about-links {
  display: none;
  position: absolute;
  left: 50%;
  bottom: calc(var(--frame-inset) + clamp(16px, 4vh, 48px));
  transform: translateX(-50%);
  z-index: 2;
}

.screen--closing > .cta-links.reveal:not(.is-visible) {
  pointer-events: none;
}

/* Footer closing state — CTA centered, links in flow, name pinned to bottom. */
.closing-stack > .footer-block {
  align-self: stretch;
  width: 100%;
  min-height: calc(100dvh - 2 * var(--frame-inset) - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px));
  box-sizing: border-box;
  perspective: 70rem;
}

.footer-inner,
.footer-center {
  transform-style: preserve-3d;
}

.footer-inner {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  width: 100%;
  position: relative;
  z-index: 1;
}

.footer-center {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(80px, 12vh, 140px) 0 clamp(24px, 4vh, 48px);
  text-align: center;
}

.footer-tilt {
  max-width: min(1100px, 100%);
  padding: 0 24px;
}

/* Links sit above the decorative wordmark (z-index), in normal document flow.
   Hidden for now. */
.footer-links {
  display: none;
  position: relative;
  left: auto;
  bottom: auto;
  transform: none;
  z-index: 2;
  width: 100%;
  align-items: center;
  justify-content: center;
  margin: 0 auto clamp(28px, 5vh, 56px);
  font-weight: 700;
  font-size: clamp(16px, 1.6vw, 22px);
  letter-spacing: 0.04em;
  text-align: center;
  white-space: nowrap;
}

.footer-splash {
  position: relative;
  z-index: 0;
  margin-top: auto;
  width: 100%;
  pointer-events: none;
  user-select: none;
}

.footer-splash .hero-name,
.footer-splash .footer-since .since-word {
  opacity: 1;
}

.placeholder-line {
  margin: 0;
  font-weight: 700;
  font-size: clamp(28px, 4vw, 56px);
  line-height: 1.15;
  letter-spacing: -0.01em;
  text-align: center;
  padding: 0 24px;
  color: #fff;
}

/* Mobile closing / "Let's talk": stabilize against URL-bar viewport jumps
   and drop blur-during-transform (a common mobile GPU flicker source). */
@media (max-width: 860px) {
  .screen--closing {
    height: 100svh;
    min-height: 100svh;
  }

  .closing-stack {
    height: 100%;
    min-height: 100%;
  }

  .closing-stack > .footer-block {
    min-height: 100%;
  }

  .closing-stack > .closing-state {
    transition: opacity 0.85s ease, transform 1.1s cubic-bezier(0.22, 1, 0.36, 1);
  }

  .closing-stack > .closing-state.reveal {
    filter: none;
  }

  .footer-center {
    padding:
      calc(var(--nav-zone) + clamp(20px, 4vh, 36px))
      0
      clamp(12px, 2vh, 24px);
  }

  .footer-tilt {
    padding: 0 clamp(16px, 5vw, 28px);
  }

  .footer-tilt .cta-title {
    font-size: clamp(28px, 8vw, 40px);
  }

  .footer-splash {
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
}

/* Reduced motion: no JS stepping — un-stack the closing states into flow. */
@media (prefers-reduced-motion: reduce) {
  .screen--closing { grid-auto-flow: row; gap: 14vh; padding: 16vh 0; }
  .closing-stack { display: block; }
  .closing-stack > .closing-state { grid-area: auto; margin: 8vh auto; }
  .pattern-block,
  .about-block,
  .footer-block { margin: 8vh auto; }
  .about-links { position: static; transform: none; margin-top: 24px; }
  .footer-links { margin-top: 24px; }
}

/* --- 4. Statements & reveal --- */

/* All statements live in one grid cell so they stack centered on top of
   each other; JS reveals one at a time as you scroll. */
.statements {
  display: grid;
  place-items: center;
  width: 100%;
  position: relative;
  z-index: 1; /* keep the copy above the white screen's grid layer */
  transform-style: preserve-3d;
  will-change: transform;
}

/* The grid lives inside the white screen now (above its background, below
   its text), matching how the splash layers its grid. */
.screen--white .layer {
  z-index: 0;
}

.statements > .statement {
  grid-area: 1 / 1;
}

/* Reduced motion: no JS stepping, so un-stack the statements into normal
   flow (each on its own, spaced out) and let the page scroll natively. */
@media (prefers-reduced-motion: reduce) {
  .statements { display: block; }
  .statements > .statement {
    grid-area: auto;
    margin: 22vh auto;
  }
}

/* Big centered statement (white screens). Inter bold, 52pt, with a
   left→right teal gradient painted onto the text via background-clip. */
.statement {
  --statement-size: 3.25rem; /* 52pt reduced 25% */
  margin: 0;
  font-family: Inter, Arial, sans-serif;
  font-weight: 700;
  font-size: var(--statement-size);
  line-height: 1.16;
  letter-spacing: -0.01em;
  text-align: center;
  white-space: nowrap; /* keep each half on one line → exactly two rows */
  background: linear-gradient(90deg, #0E6A5B 0%, #189883 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  user-select: none;
}

/* Stepped sizes, each compounding on the previous statement. */
.statement--s2 { font-size: calc(var(--statement-size) * 1.1); }    /* +10% */
.statement--s3 { font-size: calc(var(--statement-size) * 1.21); }   /* +10% again */
.statement--s4 { font-size: calc(var(--statement-size) * 1.3915); } /* +15% again */
.statement--s5 { font-size: calc(var(--statement-size) * 1.53065); } /* +10% again */

/* Red gradient variant (overrides only the gradient — background-image so
   the text clip set on .statement stays intact). It's a long sentence, so
   let it wrap into balanced lines instead of forcing one row. */
.statement--red {
  background-image: linear-gradient(90deg, #911719 0%, #D43E40 100%);
  white-space: normal;
  max-width: min(16ch, 100%);
  text-wrap: balance;
}

/* Below the width where two lines fit, allow normal wrapping + scale down.
   Scaling the base var keeps the stepped sizes proportional. */
@media (max-width: 1024px) {
  .statement {
    white-space: normal;
    --statement-size: clamp(26px, 6vw, 3.25rem);
  }
}

/* Scroll reveal: hidden + blurred until its screen scrolls into view, then
   eases in (opacity 0→1, blur 10px→0). Reverses on the way out. Toggled by
   an IntersectionObserver adding/removing .is-visible. */
.reveal {
  opacity: 0;
  filter: blur(10px);
  transition: opacity 0.65s ease, filter 0.65s ease;
}

.reveal.is-visible {
  opacity: 1;
  filter: blur(0);
}

/* Every statement after the first grows in with an elastic overshoot —
   reinforcing the "things keep scaling up as a company grows" read. The
   first statement just gets the plain fade above.
   Opacity + blur ride the 0.8s transition (the same length as the spring)
   so they resolve together with the elastic on the way IN and fade
   smoothly on the way OUT. The spring is a transform-only animation that
   only runs on the way IN, so on the way OUT the text stays at full size
   and simply fades — no shrink-back. */
.statements > .statement:not(:first-child).reveal {
  transition: opacity 0.8s ease, filter 0.8s ease;
}

.statements > .statement:not(:first-child).reveal.is-visible {
  animation: statementElastic 0.8s cubic-bezier(0.34, 1.45, 0.5, 1) both;
}

/* Single, smooth overshoot (the spring easing supplies the bounce) — same
   0.8s length as the blur/opacity transition so they resolve together. */
@keyframes statementElastic {
  from { transform: scale(0.9); }
  to   { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .reveal { opacity: 1; filter: none; transition: none; }
  .statements > .statement:not(:first-child).reveal { transform: none; animation: none; }
}

/* --- 5. Chrome (border, blobs, grid, noise) --- */

.chrome {
  position: fixed;
  inset: 0;
  z-index: 3;
  overflow: hidden;
  pointer-events: none;
}

.layer {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* ----------------------------------------------------------------
   Blobs — splash screen only. Paused + hidden when scrolled away
   (html[data-splash-blobs="paused"], set by JS IntersectionObserver).
---------------------------------------------------------------- */

/* TEST toggle (v2 only): data-blobs="off" on <html> hides the whole layer. */
html[data-blobs="off"] .blobs {
  display: none !important;
}

/*
  Lava-lamp blur: one filter on the jar, sharp blobs move inside.
  Legacy per-blob blur (heavy): data-blob-blur="per" on <html>.
*/
.blobs {
  filter: blur(var(--blob-blur));
  transform: translateZ(0);
}

html[data-blob-blur="per"] .blobs {
  filter: none;
  transform: none;
}

.blob {
  position: absolute;
  inset: -14%;
  width: 128%;
  height: 128%;
  display: block;
  filter: none;
  transform-origin: center;
  will-change: transform;
}

html[data-blob-blur="per"] .blob {
  filter: blur(var(--blob-blur));
  will-change: transform, rotate;
}

/* Drift (translate + scale) and spin (rotate) run together but desynced. */
.blob-1 { animation: drift1 26s ease-in-out -12s infinite alternate, spin1 22s ease-in-out -5s infinite alternate; }
.blob-2 { animation: drift2 30s ease-in-out -4s infinite alternate, spin2 19s ease-in-out -14s infinite alternate; }
.blob-3 { animation: drift3 23s ease-in-out -19s infinite alternate, spin3 25s ease-in-out -9s infinite alternate; }
.blob-4 { animation: drift4 35s ease-in-out -7s infinite alternate, spin4 20s ease-in-out -17s infinite alternate; }
.blob-5 { animation: drift5 29s ease-in-out -23s infinite alternate, spin5 27s ease-in-out -2s infinite alternate; }

/* Drift = translate + scale (each blob breathes at its own pace/size). */
@keyframes drift1 {
  0%   { transform: translate(0, 0) scale(1); }
  50%  { transform: translate(22%, -18%) scale(1.48); }
  100% { transform: translate(-18%, 24%) scale(0.64); }
}

@keyframes drift2 {
  0%   { transform: translate(0, 0) scale(1); }
  50%  { transform: translate(-27%, 13.5%) scale(0.49); }
  100% { transform: translate(18%, -22.5%) scale(1.45); }
}

@keyframes drift3 {
  0%   { transform: translate(0, 0) scale(1); }
  50%  { transform: translate(19.5%, 22.5%) scale(1.66); }
  100% { transform: translate(-22.5%, -13.5%) scale(0.7); }
}

@keyframes drift4 {
  0%   { transform: translate(0, 0) scale(1); }
  50%  { transform: translate(-18%, -25.5%) scale(0.46); }
  100% { transform: translate(25.5%, 18%) scale(1.54); }
}

@keyframes drift5 {
  0%   { transform: translate(0, 0) scale(1); }
  50%  { transform: translate(27%, 19.5%) scale(1.42); }
  100% { transform: translate(-13.5%, -27%) scale(0.58); }
}

/* Per-blob rotation — wider, individual ranges for more variety. */
@keyframes spin1 { 0% { rotate: -42deg; } 100% { rotate: 48deg; } }
@keyframes spin2 { 0% { rotate: 54deg; } 100% { rotate: -48deg; } }
@keyframes spin3 { 0% { rotate: -60deg; } 100% { rotate: 36deg; } }
@keyframes spin4 { 0% { rotate: 30deg; } 100% { rotate: -54deg; } }
@keyframes spin5 { 0% { rotate: -48deg; } 100% { rotate: 66deg; } }

html[data-splash-blobs="paused"] .screen--splash .blobs {
  visibility: hidden;
}

html[data-splash-blobs="paused"] .screen--splash .blob {
  animation-play-state: paused;
}

/* ----------------------------------------------------------------
   Grid
   Stroke-only cells. The lines ARE a radial gradient: white at
   --grid-opacity in the center, fading to 0 toward the edges
   (radial gradient = paint, grid lines = mask).
---------------------------------------------------------------- */
.grid,
.grid-light {
  -webkit-mask:
    linear-gradient(to right, #000 1px, transparent 1px) 0 0 / var(--grid-size) var(--grid-size),
    linear-gradient(to bottom, #000 1px, transparent 1px) 0 0 / var(--grid-size) var(--grid-size);
  mask:
    linear-gradient(to right, #000 1px, transparent 1px) 0 0 / var(--grid-size) var(--grid-size),
    linear-gradient(to bottom, #000 1px, transparent 1px) 0 0 / var(--grid-size) var(--grid-size);
}

.grid {
  background: radial-gradient(
    circle at 50% 50%,
    rgba(var(--grid-color), var(--grid-opacity)) 0%,
    rgba(var(--grid-color), 0) 55%
  );
}

/* Gray grid for white screens — stacked over the white grid and
   crossfaded in via opacity when body.is-light is set. */
.grid-light {
  background: radial-gradient(
    circle at 50% 50%,
    rgba(80, 80, 80, 0.18) 0%,
    rgba(80, 80, 80, 0) 55%
  );
  opacity: 0;
  transition: opacity 0.4s ease;
}

body.is-light .grid-light {
  opacity: 1;
}

/* ----------------------------------------------------------------
   Border — ONE gradient frame element that morphs between three states
   (default green/yellow, teal on white screens, red on the final
   statement). A raw gradient border-image can't be transitioned, so the
   gradient's three colour stops are registered @property custom props,
   which CAN be smoothly interpolated; thickness animates via border-width.
   So a single element transforms instead of crossfading stacked frames.
---------------------------------------------------------------- */
@property --bc1 { syntax: "<color>"; inherits: false; initial-value: rgba(72, 186, 167, 0); }
@property --bc2 { syntax: "<color>"; inherits: false; initial-value: rgba(255, 252, 161, 0.7); }
@property --bc3 { syntax: "<color>"; inherits: false; initial-value: rgba(106, 180, 164, 0); }
/* Gradient angle — registered so it can be animated (the red state spins it
   in a loop). Static at 254deg everywhere else. */
@property --bangle { syntax: "<angle>"; inherits: false; initial-value: 254deg; }

.border {
  border: 18px solid;
  border-image: linear-gradient(
    var(--bangle),
    var(--bc1) 0%,
    var(--bc2) 50%,
    var(--bc3) 100%
  ) 1;
  opacity: 0; /* hidden until the preloader hands off (see entrance) */
  /* Even inner glow, just inside the frame, over the screen behind and
     under the border. Transparent by default (green splash), tinted per
     state below. */
  box-shadow: inset 0 0 200px transparent;
  transition:
    --bc1 0.5s ease, --bc2 0.5s ease, --bc3 0.5s ease, box-shadow 0.5s ease,
    border-width 0.9s ease 0.3s, opacity 0.9s ease 0.3s;
}

/* White screens: the frame's gradient transitions to solid teal, and a
   matching green inner glow (40%) settles over the white background. */
body.is-light .border {
  --bc1: rgba(23, 145, 125, 1);
  --bc2: rgba(65, 163, 144, 1);
  --bc3: rgba(23, 145, 125, 1);
  box-shadow: inset 0 0 200px rgba(23, 145, 125, 0.4);
}

/* Final (red) statement: reddens (after is-light so it wins) and thickens
   via the elastic animation. The inner glow switches to the same red (40%).
   Colour reddens in parallel via the transition above, so colour + thickness
   move together on the same element. */
body.is-red .border {
  --bc1: #911719;
  --bc2: #D43E40;
  --bc3: #911719;
  box-shadow: inset 0 0 200px rgba(145, 23, 25, 0.4);
  /* Elastic thickness spring (one-shot) + a continuous gradient spin while
     stationed on the red statement. Separate properties, so they coexist. */
  animation: borderRedElastic 0.8s ease-out both, borderSpin 6s linear infinite;
}

@keyframes borderRedElastic {
  0%   { border-width: 16px; }
  40%  { border-width: 48px; } /* overshoot past the 36px target */
  60%  { border-width: 30px; }
  78%  { border-width: 40px; }
  90%  { border-width: 34px; }
  100% { border-width: 36px; }
}

/* One full turn of the gradient angle (254 → 254 + 360). */
@keyframes borderSpin {
  to { --bangle: 614deg; }
}

/* Stepping back within the white screen: thickness springs back with the
   same elastic feel, settling exactly at the 18px green/teal frame. */
body.is-red-out .border {
  animation: borderRedElasticOut 0.8s ease-out both;
}

@keyframes borderRedElasticOut {
  0%   { border-width: 36px; }
  40%  { border-width: 8px;  } /* undershoot past the thin target */
  60%  { border-width: 22px; }
  78%  { border-width: 14px; }
  90%  { border-width: 19px; }
  100% { border-width: 18px; }
}

/* Returning to the splash: no spring — thickness just eases down to 18px
   while the colour transitions back to green, all on the one element. */
body.is-red-fade .border {
  animation: borderRedFadeOut 0.85s cubic-bezier(0.4, 0, 0.2, 1) both;
}

@keyframes borderRedFadeOut {
  0%   { border-width: 36px; }
  100% { border-width: 18px; }
}

/* ----------------------------------------------------------------
   Payoff border morph (deck-driven, decoupled from the nav)
   The border colour is normally driven by is-light, which the
   IntersectionObserver flips abruptly part-way through the scroll — so the
   frame looked like it vanished (faint green) then a new one popped in.
   For the payoff transitions the deck instead forces the border's target
   colour directly (frame-green / frame-red) and stretches the colour fade
   to the scroll length (frame-anim), so the SAME frame morphs continuously
   across the whole scroll and settles exactly as you land. These selectors
   touch ONLY the border, so the nav keeps its own is-light timing/contrast.
---------------------------------------------------------------- */

/* Target = the green/splash frame (entering the payoff). */
body.frame-green .border {
  --bc1: rgba(72, 186, 167, 0);
  --bc2: rgba(255, 252, 161, 0.7);
  --bc3: rgba(106, 180, 164, 0);
  box-shadow: inset 0 0 200px transparent;
}

/* Target = the red frame's colour (leaving the payoff back to the red
   statement). Colour only — the is-red stamp (width spring + spin) still
   fires on arrival; this just pre-morphs the colour during the scroll. */
body.frame-red .border {
  --bc1: #911719;
  --bc2: #D43E40;
  --bc3: #911719;
  box-shadow: inset 0 0 200px rgba(145, 23, 25, 0.4);
}

/* While a payoff transition runs, stretch the colour fade to ~the scroll
   length so the morph tracks the motion and lands with it, and ease the
   gradient angle back from the red spin instead of letting it snap. */
body.frame-anim .border {
  transition:
    --bc1 0.9s ease, --bc2 0.9s ease, --bc3 0.9s ease, --bangle 0.9s ease,
    box-shadow 0.9s ease,
    border-width 0.9s ease 0.3s, opacity 0.9s ease 0.3s;
}

/* ----------------------------------------------------------------
   Noise
   Procedural SVG grain over the backdrop, no external asset.
---------------------------------------------------------------- */
.noise {
  mix-blend-mode: multiply;
  background-image: var(--noise);
  background-size: 200px 200px;
}

/* --- 6. Entrance & preloader --- */

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes riseIn {
  from { opacity: 0; transform: translateY(-12px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Word rises up from below into place. */
@keyframes riseUp {
  from { opacity: 0; transform: translateY(0.6em); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Name: opacity 0 + heavy blur → opacity 1 + sharp. */
@keyframes nameIn {
  from { opacity: 0; filter: blur(40px); }
  to   { opacity: 1; filter: blur(0); }
}

/* Border entrance: it starts thin + transparent before load, then its
   own transition (opacity + border-width, both delayed 0.3s) reveals it.
   This is a transition, not a keyframe animation, so the `animation`
   property stays free for the red enter/exit springs on the same element. */
body:not(.loaded) .border { border-width: 2px; }
body.loaded .border { opacity: 1; }

/* The grid fades in immediately — it's part of the loading screen,
   alongside the green background, while the preloader bar fills. */
.grid { animation: fadeIn 1.2s ease-out 0.1s both; }

/* Everything else is hidden until the preloader finishes (body.loaded). */
.screen--splash .blobs,
.noise,
.border,
.name-tag,
.nav-center,
.nav-right,
.menu-toggle,
.hero-tagline,
.screen--splash .hero-name,
.screen--splash .since-word {
  opacity: 0;
}

/* Blobs are hidden during preload — no blur/animation work until handoff. */
body:not(.loaded) .screen--splash .blobs {
  visibility: hidden;
}

body:not(.loaded) .screen--splash .blob {
  animation-play-state: paused;
}

body.loaded .screen--splash .blobs {
  visibility: visible;
}

body.loaded .screen--splash .blob {
  animation-play-state: running;
}

/* The since words keep their inline-block layout even while hidden. */
.since-word { display: inline-block; }

/* Entrance sequence — only runs once the preloader hands off. */
body.loaded .screen--splash .blobs  { animation: fadeIn 1.2s ease-out 0s   both; }
body.loaded .noise  { animation: fadeIn 1.2s ease-out 0.1s both; }

/* Name: starts early and runs long (slow de-blur) so it's the LAST
   thing to finish, resolving in the background while the rest pops in.
   Scoped to the splash — footer replays the same sequence on arrival. */
body.loaded .screen--splash .hero-name { animation: nameIn 3.4s ease-out 0.5s both; }

/* Nav, broken into its three groups with slightly overlapping tempo
   (each starts ~0.2s after the previous, while it's still animating). */
body.loaded .name-tag   { animation: riseIn 0.7s ease-out 0.9s both; }
body.loaded .nav-center { animation: riseIn 0.7s ease-out 1.1s both; }
body.loaded .nav-right  { animation: riseIn 0.7s ease-out 1.3s both; }

/* Tagline: opacity ONLY — its transform is driven live by JS. */
body.loaded .hero-tagline { animation: fadeIn 0.8s ease-out 1.6s both; }

/* Since line: each word rises up into place, staggered left → right. */
body.loaded .screen--splash .since-word { animation: riseUp 2.3s cubic-bezier(0.16, 1, 0.3, 1) both; }
body.loaded .screen--splash .since-word:nth-child(1) { animation-delay: 1.5s;  }
body.loaded .screen--splash .since-word:nth-child(2) { animation-delay: 1.64s; }
body.loaded .screen--splash .since-word:nth-child(3) { animation-delay: 1.78s; }
body.loaded .screen--splash .since-word:nth-child(4) { animation-delay: 1.92s; }
body.loaded .screen--splash .since-word:nth-child(5) { animation-delay: 2.06s; }

/* ================================================================
   PRELOADER
   Transparent full-screen overlay (green bg + grid show through),
   holding a centered thin track whose fill is the accent color.
================================================================ */
.preloader {
  position: fixed;
  inset: 0;
  z-index: 5;
  display: grid;
  place-items: center;
  opacity: 1;
  transition: opacity 0.5s ease;
}

.preloader.is-done {
  opacity: 0;
  pointer-events: none;
}

.preloader__track {
  width: 240px;
  max-width: 60vw;
  height: 3px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  /* visible so the fill's leading-edge glow isn't clipped */
  overflow: visible;
}

.preloader__fill {
  display: block;
  width: 0;
  height: 100%;
  background: var(--accent);
  border-radius: inherit;
  /* soft halo + a stronger glow pushed to the right (leading) edge */
  box-shadow:
    0 0 6px 0 rgba(255, 252, 161, 0.7),
    8px 0 14px -2px rgba(255, 252, 161, 0.55);
}

/* --- 7. Splash & navigation --- */

.screen--splash {
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font-family: Inter, Arial, sans-serif;
  color: #fff;
}

/* Blobs + noise paint behind the hero copy within the splash. */
.screen--splash .layer {
  z-index: 0;
}

.screen--splash .hero-center,
.screen--splash .hero-footer {
  position: relative;
  z-index: 1;
}

/* ----------------------------------------------------------------
   Menu bar
---------------------------------------------------------------- */
.menu-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 4;
  /* Never tighter than the frame inset — on large screens the design pad
     already clears the red border, so thickness changes don't pull the
     nav closer to the edge. Extra --content-side keeps nav inside 1920. */
  padding:
    max(var(--menu-pad-y), calc(var(--frame-inset) + env(safe-area-inset-top, 0px)))
    calc(var(--content-side) + max(var(--menu-pad-x), calc(var(--frame-inset) + env(safe-area-inset-right, 0px))))
    var(--frame-gap)
    calc(var(--content-side) + max(var(--menu-pad-x), calc(var(--frame-inset) + env(safe-area-inset-left, 0px))));
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  color: #fff;
  user-select: none;
}

.name-tag {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 700;
  font-size: 16px;
  letter-spacing: 0.5px;
  white-space: nowrap;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.name-tag .dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

/* Hover the name-tag → the dot bounces up and down forever, ping-pong
   style: ease-out rising (gravity slows it), ease-in falling (speeds up). */
.name-tag:hover .dot,
.name-tag:focus-visible .dot {
  animation: dotBounce 0.55s infinite;
}

@keyframes dotBounce {
  0%   { transform: translateY(0);    animation-timing-function: ease-out; }
  50%  { transform: translateY(-7px); animation-timing-function: ease-in; }
  100% { transform: translateY(0); }
}

.nav-center {
  font-weight: 700;
  font-size: 16px;
  text-align: center;
  white-space: nowrap;
}

.nav-right {
  font-weight: 700;
  font-size: 16px;
  text-align: right;
  white-space: nowrap;
}

.sep {
  color: var(--accent);
  vertical-align: top;
  cursor: default;
  user-select: none;
}

/* ----------------------------------------------------------------
   Roll link
   Slot-reel hover: a one-line clip box holds two stacked copies of
   the word (white + accent). On hover the stack slides straight up
   by one line, swapping white for accent. Pure vertical — no scale,
   no horizontal shift, so it reads as a single word rolling.
---------------------------------------------------------------- */
.roll {
  display: inline-block;
  height: 1.2em;
  overflow: hidden;
  vertical-align: top;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.roll__inner {
  display: flex;
  flex-direction: column;
  transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}

.roll__inner > span {
  display: block;
  line-height: 1.2;
}

/* The second (accent) copy waiting just below the clip line. */
.roll__inner > span:last-child {
  color: var(--accent);
}

.roll:hover .roll__inner,
.roll:focus-visible .roll__inner {
  transform: translateY(-50%);
}

/* ----------------------------------------------------------------
   Nav theme
   Default (green screens): white text, accent dot / separators /
   hover reveal. On white screens (body.is-light, toggled by the
   scroll observer) the nav flips to black text with teal accents.
---------------------------------------------------------------- */
.menu-bar,
.name-tag .dot,
.sep,
.roll__inner > span:last-child {
  transition: color 0.4s ease, background-color 0.4s ease;
}

body.is-light .menu-bar { color: #000; }
body.is-light .name-tag .dot { background: #17917D; }
body.is-light .sep { color: #17917D; }
body.is-light .roll__inner > span:last-child { color: #17917D; }

/* On the final (red) statement the frame turns red, so the nav accents —
   the dot, the separators, and the hover reveal — switch from teal to the
   matching deep red. Placed after the is-light rules so they win. */
body.is-red .name-tag .dot,
body.frame-red .name-tag .dot { background: #911719; }
body.is-red .sep,
body.frame-red .sep { color: #911719; }
body.is-red .roll__inner > span:last-child,
body.frame-red .roll__inner > span:last-child { color: #911719; }

/* Entering the green payoff: the accents return to the yellow splash accent.
   Driven by frame-green (active across that scroll) so they go straight from
   red to yellow instead of dipping through the is-light teal. Placed after
   the is-light + red rules so it wins on the accent elements. */
body.frame-green .name-tag .dot { background: var(--accent); }
body.frame-green .sep { color: var(--accent); }
body.frame-green .roll__inner > span:last-child { color: var(--accent); }

/* ----------------------------------------------------------------
   Mobile burger menu
---------------------------------------------------------------- */
.menu-toggle {
  display: none;
  position: relative;
  z-index: 8;
  width: 44px;
  height: 44px;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.menu-toggle__bars {
  display: grid;
  place-items: center;
  gap: 7px;
  width: 22px;
  margin: 0 auto;
}

.menu-toggle__bars > span {
  display: block;
  width: 100%;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
  transform-origin: center;
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1),
    opacity 0.2s ease;
}

body.menu-open .menu-toggle__bars > span:first-child {
  transform: translateY(4.5px) rotate(45deg);
}

body.menu-open .menu-toggle__bars > span:last-child {
  transform: translateY(-4.5px) rotate(-45deg);
}

.mobile-menu {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 7;
  padding:
    calc(var(--frame-inset) + var(--nav-zone) + env(safe-area-inset-top, 0px))
    calc(var(--frame-inset) + env(safe-area-inset-right, 0px))
    calc(var(--frame-inset) + env(safe-area-inset-bottom, 0px))
    calc(var(--frame-inset) + env(safe-area-inset-left, 0px));
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.97) 0%, rgba(248, 252, 250, 0.98) 100%);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity 0.35s ease,
    visibility 0.35s;
}

.mobile-menu.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.mobile-menu__nav {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(18px, 4vh, 28px);
  height: 100%;
  max-width: 20ch;
}

.mobile-menu__link {
  font-size: clamp(28px, 8vw, 40px);
  font-weight: 800;
  line-height: 1.1;
  letter-spacing: 0.02em;
  color: #04251f;
  text-decoration: none;
  transition: color 0.25s ease;
}

.mobile-menu__link.is-current {
  color: #17917d;
  pointer-events: none;
}

.mobile-menu__link:hover,
.mobile-menu__link:focus-visible {
  color: #17917d;
}

/*
  Open menu always uses the light chrome (white panel / black type / teal
  accents), regardless of splash, is-light, is-red, or frame-green. Closing
  removes .menu-open and the existing body theme classes take over again.
*/
body.menu-open .menu-bar,
body.menu-open.is-light .menu-bar,
body.menu-open.is-red .menu-bar,
body.menu-open.frame-red .menu-bar,
body.menu-open.frame-green .menu-bar {
  color: #000;
}

body.menu-open .menu-toggle {
  color: #000;
}

body.menu-open .name-tag .dot,
body.menu-open.is-light .name-tag .dot,
body.menu-open.is-red .name-tag .dot,
body.menu-open.frame-red .name-tag .dot,
body.menu-open.frame-green .name-tag .dot {
  background: #17917d;
}

body.menu-open .sep,
body.menu-open.is-light .sep,
body.menu-open.is-red .sep,
body.menu-open.frame-red .sep,
body.menu-open.frame-green .sep,
body.menu-open .roll__inner > span:last-child,
body.menu-open.is-light .roll__inner > span:last-child,
body.menu-open.is-red .roll__inner > span:last-child,
body.menu-open.frame-red .roll__inner > span:last-child,
body.menu-open.frame-green .roll__inner > span:last-child {
  color: #17917d;
}

body.menu-open .intro-player,
body.menu-open .work-dots {
  opacity: 0 !important;
  visibility: hidden !important;
  pointer-events: none !important;
}

html.menu-open,
body.menu-open,
html.is-ready.menu-open {
  overflow: hidden;
}

@media (max-width: 860px) {
  :root {
    /* Match the fixed gap used on about/work mobile chrome. */
    --frame-gap: 12px;
  }

  .menu-bar {
    z-index: 8;
    justify-content: space-between;
    align-items: center;
    flex-wrap: nowrap;
    gap: 16px;
  }

  .nav-center,
  .nav-right {
    display: none;
  }

  .menu-toggle {
    display: grid;
    place-items: center;
    opacity: 0;
  }

  .mobile-menu {
    display: block;
  }

  body.loaded .menu-toggle {
    animation: riseIn 0.7s ease-out 1.3s both;
  }

  /* All pages: sit name + burger inside the frame (tracks --frame-width). */
  .menu-bar {
    padding:
      calc(var(--frame-inset) + env(safe-area-inset-top, 0px))
      calc(var(--content-side) + var(--frame-inset) + env(safe-area-inset-right, 0px))
      var(--frame-gap)
      calc(var(--content-side) + var(--frame-inset) + env(safe-area-inset-left, 0px));
  }
}

/* ----------------------------------------------------------------
   Centered tagline
---------------------------------------------------------------- */
.hero-center {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  perspective: 56.25rem;
}

.hero-tagline {
  position: relative;
  width: 100%;
  min-height: 2.2em; /* two lines at line-height 1.1 — reserves the box */
  margin: 0;
  font-weight: 700;
  font-size: clamp(18px, 1.5vw, 28px);
  line-height: 1.1;
  letter-spacing: 0.09em;
  text-align: center;
  transform-style: preserve-3d;
  will-change: transform;
}

.hero-tagline .amp {
  font-style: italic;
  font-size: 1.46em;
  color: var(--accent);
  letter-spacing: 0.09em;
}

/* ----------------------------------------------------------------
   Tagline swap
   The whole tagline crossfades between three phrasings on a loop.
   Only the opacity lives here (on a child of .hero-tagline), so the
   parent keeps driving the 3D tilt + glow untouched. The cycle is
   started by JS only after the page entrance animations finish.
---------------------------------------------------------------- */
.tagline-swap {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  opacity: 0;
  filter: blur(6px);
  transition: opacity 1.5s ease, filter 1.5s ease;
}

/* The visible layer. Switching = activating one while deactivating the
   other, with the JS staggering their starts so they crossfade. */
.tagline-swap.is-active {
  opacity: 1;
  filter: blur(0);
}

/* Word connectors (TO / FOR) sit a touch smaller than the big "&". */
.hero-tagline .tag-b.is-word {
  font-size: 1.15em;
}

/* ----------------------------------------------------------------
   Footer name + tagline
---------------------------------------------------------------- */
.hero-footer {
  position: relative;
}

/*
  SVG text, two independent layers:
  - fill layer   : gradient faded to transparent at the bottom (via mask)
  - stroke layer : solid, full height, with stroke-linejoin="round" so the
                   sharp vertices of A / R / V don't spike (impossible in
                   CSS -webkit-text-stroke, which is miter-only).
  textLength locks the width so it always fills the container.
*/
.hero-name {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.hero-name text {
  font-family: Arial, Helvetica, sans-serif;
  font-weight: 700;
}

.hero-since {
  margin: 0;
  text-align: right;
  font-weight: 900;
  font-size: clamp(15px, 1.62vw, 22px);
  position: relative;
  top: -10px;
}

/* Hero copy is presented as artwork, not selectable text — show the
   normal arrow instead of the I-beam and disable text selection. */
.hero-tagline,
.hero-name,
.hero-since {
  cursor: default;
  user-select: none;
}

@media (max-width: 720px) {
  /* Burger layout owns the bar below 860px — don't re-center the name/toggle. */
  .hero-since {
    text-align: center;
    margin-top: 10px;
  }
}

/* --- 8. Intro sequence player --- */

/*
  Persistent control for the 6 intro steps after splash:
  white statements (0–4) + green payoff (5). Hidden on the green CTA.
  Theme vars: green (white bg), red (red statement), yellow (green bg).
*/
.intro-player {
  --player: #1C937F;
  --player-accent: #fffca1;

  position: fixed;
  left: 50%;
  bottom: calc(var(--frame-inset) + clamp(12px, 2vw, 24px));
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 16px;
  color: var(--player);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  /* Stay put while fading out — entrance motion is a one-shot animation. */
  transform: translateX(-50%);
  transition:
    opacity 0.55s ease,
    visibility 0.55s,
    color 0.35s ease;
}

.intro-player.is-visible {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* First reveal only: fade + slight lift. Exit removes .is-visible and
   fades in place (no transform reverse). */
.intro-player.is-entering {
  animation: introPlayerIn 0.65s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes introPlayerIn {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(18px);
  }
  to {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
}

.intro-player[data-theme="red"] {
  --player: #911719;
}

.intro-player[data-theme="yellow"] {
  --player: var(--accent);
}

.intro-player__controls {
  display: flex;
  align-items: center;
  gap: 18px;
}

.intro-player__btn {
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  cursor: pointer;
  opacity: 0.9;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.intro-player__btn:hover {
  opacity: 1;
}

.intro-player__btn:active {
  transform: scale(0.94);
}

.intro-player__btn:disabled {
  opacity: 0.35;
  cursor: default;
  transform: none;
}

.intro-player__btn svg {
  display: block;
}

/* Animatable fill strength for the play button gradients (20% → 40% on hover). */
@property --play-fill {
  syntax: "<percentage>";
  inherits: true;
  initial-value: 20%;
}

@property --play-glow {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}

/* Main 52px play/pause button with soft gradient frame. */
.intro-player__play {
  --play-fill: 20%;
  --play-glow: 0%;
  position: relative;
  width: 52px;
  height: 52px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background:
    linear-gradient(
      180deg,
      color-mix(in srgb, var(--player) var(--play-fill), transparent) 0%,
      color-mix(in srgb, var(--player) 0%, transparent) 100%
    );
  color: inherit;
  cursor: pointer;
  display: grid;
  place-items: center;
  isolation: isolate;
  box-shadow:
    0 10px 28px color-mix(in srgb, var(--player) var(--play-glow), transparent),
    0 0 18px color-mix(in srgb, var(--player) var(--play-glow), transparent);
  transition:
    --play-fill 0.35s ease,
    --play-glow 0.35s ease,
    box-shadow 0.35s ease,
    transform 0.25s ease;
}

.intro-player__play:hover,
.intro-player__play:focus-visible {
  --play-fill: 40%;
  --play-glow: 32%;
}

.intro-player__play:active {
  transform: scale(0.96);
}

/* 1px border with inverted linear gradient (stronger at bottom). */
.intro-player__ring {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  background:
    linear-gradient(
      0deg,
      color-mix(in srgb, var(--player) var(--play-fill), transparent) 0%,
      color-mix(in srgb, var(--player) 0%, transparent) 100%
    );
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  mask-composite: exclude;
  padding: 1px;
  transition: background 0.35s ease;
}

/* Inner 50px progress disc — radial fill revealed by a conic clip.
   Figma: Radial · #FFFCA1 @ 0% opacity · 50%
                 theme colour (@ --player) @ 100% opacity · 100%
   Color-space keyword must follow the geometry (`circle … in srgb`),
   otherwise the whole gradient is invalid and the fill disappears. */
.intro-player__progress {
  --progress: 0;
  position: absolute;
  inset: 1px;
  border-radius: 50%;
  pointer-events: none;
  background:
    radial-gradient(
      circle at center in srgb,
      rgba(255, 252, 161, 0) 50%,
      var(--player) 100%
    );
  -webkit-mask-image: conic-gradient(
    #000 0deg,
    #000 calc(var(--progress) * 360deg),
    transparent calc(var(--progress) * 360deg)
  );
  mask-image: conic-gradient(
    #000 0deg,
    #000 calc(var(--progress) * 360deg),
    transparent calc(var(--progress) * 360deg)
  );
}

.intro-player__icon {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
}

.intro-player__icon[hidden] {
  display: none;
}

.intro-player__icon svg {
  display: block;
}

.intro-player__dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.intro-player__dot {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--player);
  opacity: 0.5;
  cursor: pointer;
  transition:
    width 0.25s ease,
    height 0.25s ease,
    opacity 0.25s ease,
    background-color 0.3s ease;
}

.intro-player__dot.is-done,
.intro-player__dot.is-active {
  width: 12px;
  height: 12px;
  opacity: 1;
}

.intro-player__dot:focus-visible,
.intro-player__btn:focus-visible,
.intro-player__play:focus-visible {
  outline: 2px solid var(--player);
  outline-offset: 3px;
}

@media (max-width: 720px) {
  .intro-player {
    bottom: calc(var(--frame-inset) + clamp(12px, 3vw, 20px));
    gap: 12px;
  }

  .intro-player__controls {
    gap: 12px;
  }

  .work-dots {
    bottom: calc(var(--frame-inset) + clamp(12px, 3vw, 20px));
  }
}

/* --- Mobile performance (hide blobs; keep grid + noise) --- */

@media (hover: none), (pointer: coarse) {
  .blobs {
    display: none !important;
  }
}

@media (max-width: 900px) {
  :root {
    --grid-size: 36px;
  }
}

@media (max-width: 480px) {
  :root {
    --grid-size: 32px;
  }
}

/* --- 9. Reduced motion overrides --- */

@media (prefers-reduced-motion: reduce) {
  .intro-player,
  .work-dots {
    display: none !important;
  }

  .screen--splash .blobs {
    filter: none;
  }

  .screen--splash .blob,
  .screen--splash .blobs,
  .grid,
  .noise,
  .border,
  .name-tag,
  .nav-center,
  .nav-right,
  .menu-toggle,
  .hero-tagline,
  .hero-name,
  .since-word,
  body.loaded .screen--splash .blobs,
  body.loaded .noise,
  body.loaded .border,
  body.loaded .name-tag,
  body.loaded .nav-center,
  body.loaded .nav-right,
  body.loaded .menu-toggle,
  body.loaded .hero-tagline,
  body.loaded .screen--splash .hero-name,
  body.loaded .screen--splash .since-word {
    animation: none;
  }

  /* Elements hidden for the entrance must show without animating. */
  .screen--splash .blobs,
  .noise,
  .border,
  .name-tag,
  .nav-center,
  .nav-right,
  .menu-toggle,
  .hero-tagline,
  .screen--splash .hero-name,
  .screen--splash .since-word {
    opacity: 1;
  }

  /* No preloader — the page is shown immediately. */
  .preloader {
    display: none;
  }

  .roll__inner,
  .tagline-swap,
  .border,
  .menu-toggle__bars > span,
  .mobile-menu {
    transition: none;
  }

  body:not(.work-body) {
    transition: none;
  }

  .name-tag:hover .dot,
  .name-tag:focus-visible .dot {
    animation: none;
  }
}
