/* ─────────────────────────────────────────────
   BREAKPOINTS (site-wide convention)

   Mobile         max-width: 599px
   Tablet         600px – 839px     (stacked, like mobile)
   Compact split  840px – 1023px    (split view, light canvas images)
   Small laptop   1024px – 1439px
   Desktop        1440px – 1919px   ← the reference design (built ~1512)
   Display        min-width: 1920px

   The FUNCTIONAL layout split (stacked vs split view, home worklist
   machinery, project layout) sits at 840px: phones and portrait tablets
   stack; from 840 up everything is the split view. The 1200px "-m"
   canvas variants serve up to 1023 (the compact split's canvas is small
   enough). JS agrees on 839/840 (worklist.js) — change both together or
   the bar/picker handoff breaks.
───────────────────────────────────────────── */

/* ─────────────────────────────────────────────
   main — brutalist styles
───────────────────────────────────────────── */

@font-face {
  font-family: 'Rand Medium';
  src: url('../fonts/Rand-Medium.woff') format('woff');
  font-display: swap;
}
@font-face {
  font-family: 'Rand Regular';
  src: url('../fonts/Rand-Regular.woff') format('woff');
  font-display: swap;
}
@font-face {
  font-family: 'Rand Mono';
  src: url('../fonts/Rand-Mono-Regular.woff') format('woff');
  font-display: swap;
}
@font-face {
  font-family: 'Rand Mono Medium';
  src: url('../fonts/Rand-Mono-Medium.woff') format('woff');
  font-display: swap;
}

:root {
  --white:    #ffffff;
  --black:    #16161a;
  --line:     #e1e1e5;  /* borders + dividers */
  --gray-100: #f5f5f7;  /* subtle fills (pill buttons, thumbnails, footer, placeholders) */
  --gray-200: #828285;  /* lighter muted text (project-hero phrases) */
  --gray-300: #646466;  /* muted text */
  --gray-500: #49494d;  /* secondary body text */
  --orange:   #FC2803;
  --blob-radius: 0px;  /* blobs are square — the pixel/glyph language; 50% = the old circle */
  /* TRIAL: everything in Rand Mono. Revert by restoring the two lines below.
     --font-sans: 'Rand Regular', system-ui, sans-serif;
     --font-medium: 'Rand Medium', system-ui, sans-serif; */
  --font-sans: 'Rand Mono', ui-monospace, monospace;
  --font-medium: 'Rand Mono', ui-monospace, monospace;
  --font-mono: 'Rand Mono', ui-monospace, monospace;
  --font-mono-medium: 'Rand Mono Medium', ui-monospace, monospace;

  /* Sun & moon icons (as masks) for the theme toggle */
  --sun: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Ccircle%20cx='12'%20cy='12'%20r='6'%20fill='%23000'/%3E%3Cg%20stroke='%23000'%20stroke-width='2.6'%20stroke-linecap='round'%3E%3Cline%20x1='12'%20y1='1.4'%20x2='12'%20y2='2.6'/%3E%3Cline%20x1='12'%20y1='21.4'%20x2='12'%20y2='22.6'/%3E%3Cline%20x1='1.4'%20y1='12'%20x2='2.6'%20y2='12'/%3E%3Cline%20x1='21.4'%20y1='12'%20x2='22.6'%20y2='12'/%3E%3Cline%20x1='4.4'%20y1='4.4'%20x2='5.2'%20y2='5.2'/%3E%3Cline%20x1='18.8'%20y1='18.8'%20x2='19.6'%20y2='19.6'/%3E%3Cline%20x1='4.4'%20y1='19.6'%20x2='5.2'%20y2='18.8'/%3E%3Cline%20x1='18.8'%20y1='5.2'%20x2='19.6'%20y2='4.4'/%3E%3C/g%3E%3C/svg%3E");
  --moon: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20fill='%23000'%20d='M21%2012.79A9%209%200%201%201%2011.21%203%207%207%200%200%200%2021%2012.79z'/%3E%3C/svg%3E");

  /* Native cursor behavior (Kosuke, 2026-08-22): interactive things show
     the OS pointer hand, everything else the arrow — no forced default. */
  --cursor-pointer: pointer;
}

* { box-sizing: border-box; }

html {
  /* Always reserve the scrollbar gutter, so locking scroll when the menu
     opens doesn't change the page width (removes the jitter). */
  scrollbar-gutter: stable;
}
/* The page scrollbar is the BROWSER'S OWN (Kosuke, 2026-09-05) — no
   scrollbar-width, no scrollbar-color, no ::-webkit-scrollbar rules. Each OS
   draws the bar its user already knows, including the overlay bar macOS
   fades in and out. (scrollbar-gutter above is layout, not appearance: it
   reserves the gutter so locking scroll can't change the page width.)
   The one exception is .project__panel, which hides its inner bar on
   purpose — see project.css. */

html, body {
  background: var(--white);
  color: var(--black);
  /* Default UI text is Rand Medium; paragraph/body copy opts back into
     Regular (--font-sans) per element. */
  font-family: var(--font-mono);   /* base = Regular; Medium is a deliberate rank marker */
  font-size: 13px;
  font-weight: 400;
  line-height: 1.75;
  letter-spacing: 0;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

body {
  margin: 0;
}

a { color: inherit; text-decoration: none; }
button { cursor: pointer; }   /* buttons read as clickable, same as links
                                 (the UA default for buttons is the arrow) */
/* No browser focus ring, ever (the workrow rule, promoted to site policy) —
   hover/active states carry all interactive feedback on this site. */
a:focus, a:focus-visible,
button:focus, button:focus-visible { outline: none; }
::selection { background: var(--orange); color: #fff; }


/* ───────── PAGE TRANSITIONS (View Transitions API) ─────────
   Same-origin navigations cross-dissolve in place. The background never
   changes — only the content swaps — with a whisper of scale so the incoming
   content seems to surface from behind the wall. No X/Y translation.
   Progressive enhancement — a no-op where the API isn't supported. */
@view-transition { navigation: auto; }

::view-transition-old(root) { animation: page-dissolve-out .5s ease both; }
::view-transition-new(root) { animation: page-dissolve-in .55s ease both; }
@keyframes page-dissolve-out { to   { opacity: 0; transform: scale(1.012); } }
@keyframes page-dissolve-in  { from { opacity: 0; transform: scale(0.985); } }

/* ── Top-bar morph (index ⇄ project, every direction) ──
   The two bars are positioned identically, so the LEFT wordmark is literally the
   same element: held still AND not cross-faded, so it never flickers (seamless).
   The CENTER (role ↔ title) and RIGHT (controls ↔ icons) rotate over like an iOS
   date-picker dial. */
::view-transition-group(nav-wordmark) { animation: none; }   /* held still — wordmark never moves */
/* The wordmark is byte-identical on both bars, so keep the NEW one fully opaque
   on top of (and exactly covering) the OLD one — it reads as a single element
   that never fades or doubles. The OLD fades out underneath as a safety net. */
::view-transition-new(nav-wordmark) { animation: none; }
::view-transition-old(nav-wordmark) { animation: vt-fade-out .2s ease both; }
@keyframes vt-fade-out { to { opacity: 0; } }

::view-transition-group(nav-center),
::view-transition-group(nav-controls) { animation: none; }
::view-transition-old(nav-center),
::view-transition-old(nav-controls) {
  animation: nav-dial-out .42s cubic-bezier(.33, 0, .25, 1) both;
  transform-origin: 50% 50%;
}
::view-transition-new(nav-center),
::view-transition-new(nav-controls) {
  animation: nav-dial-in .48s cubic-bezier(.22, .68, .25, 1) both;
  transform-origin: 50% 50%;
}
/* iOS picker dial — pure in-place rotation around the drum center (no
   translation, so the box never moves): the old option rotates down off the
   drum as it fades, the new one rotates in from above as it fades in. */
@keyframes nav-dial-out {
  from { opacity: 1; transform: perspective(340px) rotateX(0); }
  to   { opacity: 0; transform: perspective(340px) rotateX(90deg); }
}
@keyframes nav-dial-in {
  from { opacity: 0; transform: perspective(340px) rotateX(-90deg); }
  to   { opacity: 1; transform: perspective(340px) rotateX(0); }
}
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(nav-center),
  ::view-transition-old(nav-controls),
  ::view-transition-new(nav-center),
  ::view-transition-new(nav-controls) { animation-duration: .01ms; }
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) { animation-duration: .01ms; }
}

/* ── Foot "next project" vertical slide-up (see main.js) ──
   The current page slides up out of view; the next slides up in, starting as a
   solid fill of its color (painted pre-paint by the head script) that then
   fades to reveal the content. The pinned chrome (nav + wordmark) stays above
   the fill (z 91/92 > 80). */
html.vt-slideup::view-transition-old(root) { animation: page-up-out .7s cubic-bezier(.76, 0, .24, 1) both; }
html.vt-slideup::view-transition-new(root) { animation: page-up-in  .7s cubic-bezier(.76, 0, .24, 1) both; }
@keyframes page-up-out { to   { transform: translateY(-100%); } }
@keyframes page-up-in  { from { transform: translateY(100%); } }
html.vt-slideup::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 80;
  background: var(--slide-color, var(--black));
  pointer-events: none;
  opacity: 1;
}
html.vt-slideup.is-slide-clearing::before { opacity: 0; transition: opacity .55s ease; }
@media (prefers-reduced-motion: reduce) {
  html.vt-slideup::view-transition-old(root),
  html.vt-slideup::view-transition-new(root) { animation-duration: .01ms; }
  html.vt-slideup.is-slide-clearing::before { transition-duration: .01ms; }
}

/* ── Pull-back "previous project" vertical slide-down (see main.js) ──
   The foot slide-up mirrored: scrolling past the TOP of a project pulls the
   previous one down from above — the current page is pushed down out of view
   while the previous slides in under its color fill, which then fades. */
html.vt-slidedown::view-transition-old(root) { animation: page-down-out .7s cubic-bezier(.76, 0, .24, 1) both; }
html.vt-slidedown::view-transition-new(root) { animation: page-down-in  .7s cubic-bezier(.76, 0, .24, 1) both; }
@keyframes page-down-out { to   { transform: translateY(100%); } }
@keyframes page-down-in  { from { transform: translateY(-100%); } }
html.vt-slidedown::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 80;
  background: var(--slide-color, var(--black));
  pointer-events: none;
  opacity: 1;
}
html.vt-slidedown.is-slide-clearing::before { opacity: 0; transition: opacity .55s ease; }
@media (prefers-reduced-motion: reduce) {
  html.vt-slidedown::view-transition-old(root),
  html.vt-slidedown::view-transition-new(root) { animation-duration: .01ms; }
  html.vt-slidedown.is-slide-clearing::before { transition-duration: .01ms; }
}


/* ───────── LANDING INTRO (index) ─────────
   On a fresh visit / hard refresh the content glyph-glitches into place
   (JS-driven, see main.js) over a quick fade-in. Gated by .is-building on
   <html> (head script, first visit / reload only); skipped under reduced
   motion. */
/* No scrolling while the intro owns the layout — positionTopnav is paused
   during .is-building, so an early scroll would drag the content up under
   the parked middle bar (nav overlapping the grid/worklist). The class is
   set pre-paint in <head> and removed by the intro finale, which unlocks
   the page in the same state the nav logic expects (top of page). */
html.is-building { overflow: hidden; }
@media (prefers-reduced-motion: no-preference) {
  /* Hero + top bar + below-fold sections fade in (the hero/role/about + icons
     also glyph-resolve on top of the fade). */
  html.is-building .topnav,
  html.is-building .topnav__anchors,
  html.is-building .worklist__rows,
  html.is-building .topnav__mute,
  html.is-building .hero,
  html.is-building .outro,
  html.is-building .foot,
  html.is-building .corner-blob {
    animation: intro-fade .35s ease both;
  }
  html.is-building .hero  { animation-delay: .025s; }
  html.is-building .topnav__anchors,
  html.is-building .topnav__mute,
  html.is-building .worklist__rows { animation-delay: .025s; }   /* with the hero, one breath */
  html.is-building .outro { animation-delay: .11s; }
  html.is-building .foot  { animation-delay: .15s; }
  /* The grid itself doesn't fade as a unit — its lines draw themselves in
     (drawGridLines) and the thumbnails reveal individually. */
  @keyframes intro-fade { from { opacity: 0; } to { opacity: 1; } }
  @keyframes thumb-reveal {
    from { clip-path: inset(var(--crop-t) var(--crop-r) 100% var(--crop-l) round var(--crop-radius)); opacity: 0; }
    to   { clip-path: inset(var(--crop-t) var(--crop-r) var(--crop-b) var(--crop-l) round var(--crop-radius)); opacity: 1; }
  }

}
/* One soft, gentle downward drift: the grid slides DOWN through the whole view at
   ~30% opacity and exits below the fold (out of view until scroll). --grid-in-y =
   -(gridHeight + 100vh), set by JS. Full opacity returns off-screen at rest. */
@keyframes grid-descend {
  0%   { transform: translateY(var(--grid-in-y, -320vh)); opacity: 0.3; }
  100% { transform: translateY(0); opacity: 0.3; }
}
/* Controls held back through the intro (they + the blob appear once the grid settles). */
.topnav__controls { transition: opacity .45s ease; }

.topnav__ctrl-glyph {
  display: block;
  width: 16px;
  height: 16px;
  font-family: var(--font-mono);
  font-size: 13px;
  line-height: 16px;
  text-align: center;
}
/* Inverted hand pointer on every clickable element, site-wide */
a, button, summary, label[for], [role="button"], [onclick] {
  cursor: var(--cursor-pointer);
}

/* The bar (pill) — fixed in place. It NEVER moves or resizes; only its pill
   styling fades out on open, so the contents stay exactly where they are. */
.nav {
  pointer-events: auto;
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  width: clamp(380px, 36vw, 454px);
  height: 56px;
  background: var(--white);
  border-radius: 100px;
  color: var(--black);
  z-index: 3;
  /* Figma default (573:20037): soft top drop-shadow + inset */
  box-shadow:
    0 -0.5px 0.5px 0 rgba(0, 0, 0, 0.15),
    -1px -2px 6px 0 rgba(0, 0, 0, 0.25) inset;
  transition:
    width .55s cubic-bezier(.65,0,.2,1),
    background .5s ease,
    border-color .5s ease,
    box-shadow .35s ease,
    color .2s ease;
}


/* ───────── TOP NAV — typographic bar ─────────
   Transparent top bar with page links on the left and icon controls centered. */
.topnav {
  position: fixed;
  top: calc(50vh - 24px);   /* fixed in the middle of the viewport */
  /* svh, not vh: JS (positionTopnav) measures innerHeight — the SMALL
     viewport, toolbar showing — so the pre-JS rest has to be measured the
     same way, or the bar shifts by half the toolbar when JS takes over.
     Ignored where svh is unknown; there vh above still stands. */
  top: calc(50svh - 24px);
  bottom: auto;
  left: 0;
  right: 0;
  z-index: 90;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  gap: 24px;
  padding: 8px 16px;
  background: transparent;
  border-bottom: 1px solid var(--line);
  pointer-events: none;
  transform: translateY(0);
  transition: transform .4s cubic-bezier(.4, 0, .2, 1), background .5s ease, border-color .5s ease;
  will-change: transform;
}
.topnav.is-hidden { transform: translateY(-100%); }
/* White bar at the top; transparent once you scroll (JS toggles .is-scrolled). */
.topnav.is-scrolled { background: transparent; border-bottom-color: transparent; }
/* Once the bar sticks at the very top, give it an opaque, blurred backdrop. */
.topnav.is-stuck {
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}
.topnav { min-height: 48px; }

/* Home wordmark — pinned to the left edge of the bar on pages without a hero
   (About, Contact). Mirrors the project pages' wordmark. */
/* Top-left identity — two lines (name + role), always visible. Replaces the old
   "Kosuke" wordmark and stays put as the grid scrolls under it. */
.topnav__brand {
  position: absolute;
  left: 16px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
  pointer-events: auto;
  display: flex;            /* identical box to .project-wordmark so the VT never */
  align-items: center;      /* scales/jitters: same width (text) + same fixed height */
  height: 24px;
  line-height: normal;
  text-decoration: none;
  transition: color .2s ease;
  view-transition-name: nav-wordmark;
}
.topnav__brand .hero__name {
  margin: 0;
  font-family: 'Rand Medium', system-ui, sans-serif;
  font-size: 13px;
  line-height: normal;
  letter-spacing: 0;
  color: var(--black);
  transition: color .2s ease;
}
/* Role — inline, centered in the bar (mirrors the project pages' center title). */
.topnav .topnav__role {
  position: absolute;
  left: 50%;                   /* left edge at 50% screen width = center of the grid */
  top: 0;
  transform: none;
  width: 320px;                /* fixed box — matches the project title box (VT morph) */
  height: 48px;
  display: flex;
  align-items: center;
  justify-content: flex-start; /* left-aligned */
  z-index: 1;
  margin: 0;
  font-family: 'Rand Regular', system-ui, sans-serif;
  font-size: 13px;
  line-height: normal;
  letter-spacing: 0;
  color: var(--black);
  white-space: nowrap;
  pointer-events: none;
  view-transition-name: nav-center;
}
.topnav__brand:hover .hero__name { color: var(--orange); }
.topnav__brand.is-icon-light .hero__name { color: #fff; }
.topnav__role.is-icon-light { color: #fff; }

/* Slide blob — one orange circle that glides between the nav links (JS-driven),
   identical to the footer social links. Sits behind the link text. */
.topnav__blob {
  position: absolute;
  left: 0;
  top: 0;
  width: 1.25rem;
  height: 1.25rem;
  margin: -0.625rem 0 0 -0.625rem; /* center the box on its (x,y) point */
  border-radius: var(--blob-radius);
  background: var(--orange);
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transform: translate(var(--x, 50%), var(--y, 50%)) scale(var(--sx, 1), var(--sy, 1));
  transition:
    transform .38s cubic-bezier(.34, 1.25, .5, 1),
    opacity .2s ease;
}
.topnav__blob.is-on { opacity: 1; }
@keyframes theme-spin {
  from { transform: rotate(0); }
  to   { transform: rotate(360deg); }
}

/* ── View/contact/theme controls — horizontal icon group, right-docked. ── */
.topnav__controls {
  position: absolute;
  right: 0;
  left: auto;
  top: 0;
  bottom: auto;
  width: 116px;                /* fixed box so the VT box matches the project bar exactly */
  height: 48px;                /* same height as .project-nav — no size delta for the morph */
  display: flex;
  align-items: center;
  justify-content: flex-end;   /* icons right-aligned */
  gap: 0;
  padding: 0 6px 0 0;          /* right gutter → glyph optical edge at 16px */
  pointer-events: auto;
  view-transition-name: nav-controls;
}  .topnav__contact {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  aspect-ratio: auto;
  padding: 0;
  border: 0;
  background: transparent;
  color: #16161a;
  cursor: var(--cursor-pointer);
  transition: color .25s ease;
}
.topnav__contact {
  visibility: visible;
  opacity: 1;
  pointer-events: auto;
}
.topnav__contact:not(.is-suppress-hover):hover,
.topnav__contact:focus-visible { background: transparent; color: var(--orange); outline: none; }
.topnav__controls.is-icon-light .topnav__contact { color: #fff; }
.topnav__brand.is-icon-light { color: #fff; }
@keyframes orbit-fade { to { opacity: 0; } }
@keyframes nucleus-grow {
  0%   { transform: scale(1);     fill-opacity: 1; stroke-width: 0; }
  55%  { fill-opacity: 0; }                                  /* fill gone by mid-grow → outline */
  100% { transform: scale(3.125); fill-opacity: 0; stroke-width: .61; }  /* r2.4→r7.5, .61×3.125 ≈ 1.9px outline */
}
@keyframes grid-form {
  from { rx: 7.5px; }
  to   { rx: 1px; }
}
@keyframes edge-erase {
  from { stroke-dashoffset: 0; }      /* full square */
  to   { stroke-dashoffset: -7.5; }   /* retracted into the corners */
}
@keyframes grid-collapse {
  from { transform: scale(1); }
  to   { transform: scale(0); }      /* scales down toward the center */
}
@keyframes icon-enter {
  from { transform: scale(.4); opacity: 0; }
  to   { transform: scale(1); opacity: 1; }
}
@keyframes orbit-spin-cw  { from { transform: rotate(-200deg); } to { transform: rotate(45deg); } }
@keyframes orbit-spin-ccw { from { transform: rotate(200deg); }  to { transform: rotate(-45deg); } }
/* Full 360° spin so the new icon settles upright (ending at 360° ≡ 0°, the
   resting state) — no upside-down flip-back at the end. The mask swaps during
   the faint, scaled-down mid-point. */
@keyframes view-icon-swap {
  0%   { transform: rotate(0deg) scale(1); opacity: 1; }
  45%  { transform: rotate(180deg) scale(.62); opacity: .25; }
  55%  { transform: rotate(180deg) scale(.62); opacity: .25; }
  100% { transform: rotate(360deg) scale(1); opacity: 1; }
}
/* Contact uses an inline SVG (not a mask) so the flap can animate open. */
.topnav__contact-icon {
  width: 18px;          /* slightly larger so the envelope optically matches the others */
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: transparent;   /* override the shared mask rule */
}
.topnav__contact-icon svg {
  width: 18px;
  height: 18px;
  display: block;
  overflow: visible;               /* the flap lifts above the envelope when open */
}
/* The flap's apex lifts from inside the envelope (closed V) up above the mouth
   (open lid) when the menu opens. Animating the path `d` keeps the two top hinge
   corners fixed. Browsers without CSS `d` support simply keep the closed shape. */
.topnav__envelope-flap {
  d: path("M4 8 12 13 20 8");
  transition: d .35s cubic-bezier(.2, .8, .2, 1);
}
.topnav__contact[aria-expanded="true"] .topnav__envelope-flap {
  d: path("M4 8 12 2 20 8");
}
/* When open, the envelope's top edge disappears (the rectangle loses its top
   border) so the lifted flap reads as an opened lid. The body — left/bottom/
   right sides — stays drawn at all times. */
.topnav__envelope-top {
  transition: opacity .25s ease;
}
.topnav__contact[aria-expanded="true"] .topnav__envelope-top {
  opacity: 0;
}

/* Contact menu — a vertical list dropping below the controls, right-aligned. */
.topnav-contact {
  position: absolute;
  /* Sit clear below the controls: the first link (email) must never overlap the
     contact button, or the cursor grazes it on approach and the "Copy email"
     tooltip blinks. Open is a fade only (no slide), so the link never travels up
     into the button's hit area. */
  top: 52px;
  right: 6px;
  left: auto;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  padding: 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity .2s ease, visibility 0s .2s;
}
.topnav-contact.is-on {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition-delay: 0s;
}
/* When the bar sits at the bottom of the viewport, the list opens UPWARD so it
   never spills off-screen — and the items flip (column-reverse) so the email
   still lands nearest the button. JS toggles is-above from the button position. */
.topnav-contact.is-above {
  top: auto;
  bottom: 52px;
  flex-direction: column-reverse;
}
.topnav-contact__link {
  position: relative;
  z-index: 1;
  padding: 8px 14px;
  color: var(--black);
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: normal;
  letter-spacing: .02em;
  white-space: nowrap;
  transition: color .2s ease;
}
.topnav-contact__link:hover { color: var(--black); }
.topnav-contact__link span {
  display: inline-block;
  transition: transform .25s ease;
}
.topnav-contact__link:hover span { transform: rotate(45deg); }
.topnav-contact__blob {
  position: absolute;
  left: 0;
  top: 0;
  width: 1.25rem;
  height: 1.25rem;
  margin: -0.625rem 0 0 -0.625rem;
  border-radius: var(--blob-radius);
  background: var(--orange);
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transform: translate(var(--x, 50%), var(--y, 50%)) scale(var(--sx, 1), var(--sy, 1));
  transition:
    transform .38s cubic-bezier(.34, 1.25, .5, 1),
    opacity .2s ease;
}
.topnav-contact__blob.is-on { opacity: 1; }
@keyframes grid-return {
  from { opacity: 0; filter: blur(12px); }
  to   { opacity: 1; filter: blur(0); }
}
@keyframes thumb-collapse {
  from { clip-path: inset(var(--crop-t, 0) var(--crop-r, 0) var(--crop-b, 0) var(--crop-l, 0) round var(--crop-radius, 0)); opacity: 1; }
  to   { clip-path: inset(var(--crop-t, 0) var(--crop-r, 0) 100% var(--crop-l, 0) round var(--crop-radius, 0)); opacity: 0; }
}


/* ───────── HERO ───────── */

.hero {
  position: sticky;            /* pinned at its natural height; content scrolls up over it (parallax) */
  top: 0;
  z-index: 0;                  /* behind the scrolling content */
  isolation: isolate;          /* own stacking context for the hero's layers */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 280px 16px 256px;   /* original hero height */
  background: var(--white);
}
.hero__name,
.hero__role { position: relative; z-index: 2; }
/* Content sits above the image-cursor (which shows through the transparent
   margins); the OS cursor is hidden only where the image is shown. */
.about-content { position: relative; z-index: 2; background: var(--white); }

.hero__name {
  margin: 0;
  font-family: var(--font-mono-medium);
}
.hero__role {
  color: var(--gray-300);
  margin: 0;
}
/* Matches .outro__cta exactly — orange text that flips to --black as the
   JS-driven orange slide-blob (.hero__blob) pops in behind it. */
.hero__about {
  position: relative;
  z-index: 1;            /* above the slide blob */
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  margin-top: 16px;
  padding: 8px 14px;
  color: var(--orange);
  font-family: var(--font-mono);
  font-weight: 400;
  font-size: 13px;
  letter-spacing: .02em;
  line-height: normal;
  pointer-events: auto;
  transition: color .45s ease;
}
.hero__about:hover { color: var(--black); }
.hero__about span {
  display: inline-block;
  transition: transform .25s ease;
}
/* On hover the arrow rotates 90° so it points down. */
.hero__about:hover span { transform: rotate(90deg); }

/* Landing "ignite" — during the intro the button starts as plain text in --black;
   once the blob is sucked into its center, orange spreads radially from the middle
   outward. Active only for the intro (added/removed by introBounce); otherwise the
   button is its normal orange and hover behaves as usual. */
@property --about-lit { syntax: '<percentage>'; inherits: false; initial-value: 0%; }
.hero__about.is-igniting {
  background-image: radial-gradient(circle at 50% 50%, var(--orange) var(--about-lit), var(--black) 0);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  transition: --about-lit .6s cubic-bezier(.4, 0, .15, 1);
}
.hero__about.is-igniting.is-lit { --about-lit: 120%; }
/* Slide blob — same orange circle as the outro/topnav/footer (JS-driven) */
.hero__blob {
  position: absolute;
  left: 0;
  top: 0;
  width: 1.25rem;
  height: 1.25rem;
  margin: -0.625rem 0 0 -0.625rem; /* center the box on its (x,y) point */
  border-radius: var(--blob-radius);
  background: var(--orange);
  z-index: 0;
  pointer-events: none;
  opacity: 0;
  transform: translate(var(--x, 50%), var(--y, 50%)) scale(var(--sx, 1), var(--sy, 1));
  transition:
    transform .38s cubic-bezier(.34, 1.25, .5, 1),
    opacity .2s ease;
}
.hero__blob.is-on { opacity: 1; }
/* Intro float — a small orange blob (same size as the resident .hero__blob) that
   drifts around the hero on its own while the glyph cascade plays, then homes
   into the "About me" button and fades. All the motion (eased drift, velocity
   smear, breath) is the per-frame transform set in JS; this just styles the dot.
   z below the hero text so it never covers the glyphs. */
.intro-float {
  position: absolute;
  left: 0;
  top: 0;
  width: 1.25rem;                  /* same size as .hero__blob */
  height: 1.25rem;
  margin: -0.625rem 0 0 -0.625rem; /* center the dot on its (x, y) point */
  z-index: 0;                      /* behind the hero text (z2) — never covers the glyphs */
  pointer-events: none;
  opacity: 0;
  transition: opacity .45s ease;
  will-change: transform, opacity;
}
.intro-float.is-on { opacity: 1; }
/* The outer is positioned/smeared/sucked by JS; this pseudo is the visible body
   and breathes on its own — the same organic loops as the blob cursor. */
.intro-float::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--orange);
  border-radius: var(--blob-radius);
  animation: blob-pulse 3.8s ease-in-out infinite, blob-morph 6.7s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) { .intro-float { display: none; } }


/* ───────── OUTRO + FOOTER ───────── */

.outro {
  position: relative;
  z-index: 0;            /* contain the blob's negative z-index */
  text-align: left;
  padding: 256px 16px 256px 50%;
  background: var(--white);
  transition: background-color .45s ease;
}
/* Slide blob — same orange circle as the topnav/footer (JS-driven) */
.outro__blob {
  position: absolute;
  left: 0;
  top: 0;
  width: 1.25rem;
  height: 1.25rem;
  margin: -0.625rem 0 0 -0.625rem; /* center the box on its (x,y) point */
  border-radius: var(--blob-radius);
  background: var(--orange);
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  transform: translate(var(--x, 50%), var(--y, 50%)) scale(var(--sx, 1), var(--sy, 1));
  transition:
    transform .38s cubic-bezier(.34, 1.25, .5, 1),
    opacity .2s ease;
}
.outro__blob.is-on { opacity: 1; }

.foot {
  position: relative;
  z-index: 1; /* above the pinned hero; contains the blob's negative z-index */
  background: var(--white);   /* opaque over the pinned hero */
  display: flex;
  justify-content: flex-start;
  gap: 24px;
  padding: 0 16px 0 50%;       /* trail's left edge on 50%, aligned with the outro text; no top/bottom pad */
  color: var(--gray-300);
}
/* Location trail used in the contact footer instead of links */
html {
  overflow-x: clip;              /* never scroll horizontally */
  overflow-anchor: none;         /* the page owns its motion — Chrome's scroll
                                    anchoring fights the wrap transform and
                                    jitters the picker boundary */
}
.foot__trail {
  padding: 8px 0;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .02em;
  white-space: normal;         /* wrap the location trail instead of overflowing */
  overflow-wrap: anywhere;
}
/* City names in the trail — country flag pops up above on hover */
.foot__city {
  position: relative;
  cursor: default;
}
.foot__city::after {
  content: attr(data-flag);
  position: absolute;
  left: 50%;
  /* Sit one line-height above the city name (center-to-center = 1 line ≈ 19px;
     line-height:1 keeps the flag's box tight so it isn't floated ~1.5 lines up
     by the default tall line box). */
  bottom: 16px;
  transform: translate(-50%, 4px);
  font-size: 20px;
  line-height: 1;
  letter-spacing: .02em;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity .2s ease,
    transform .25s cubic-bezier(.2, .8, .2, 1);
}
.foot__city:hover::after {
  opacity: 1;
  transform: translate(-50%, 0);
}
.foot a {
  display: inline-flex;
  gap: 4px;
  align-items: center;
  padding: 8px 0;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .02em;
}
.foot a:hover { color: var(--black); }
.foot a span { display: inline-block; transition: transform .25s cubic-bezier(.2,.8,.2,1); }
.foot a:hover span { transform: rotate(45deg); }

/* Slime blob — one orange circle that slides between the social links,
   matching the top-menu behavior (JS-driven). Sits behind the text. */
.foot__blob {
  position: absolute;
  left: 0;
  top: 0;
  width: 1.25rem;
  height: 1.25rem;
  margin: -0.625rem 0 0 -0.625rem; /* center the box on its (x,y) point */
  border-radius: var(--blob-radius);
  background: var(--orange);
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  transform: translate(var(--x, 50%), var(--y, 50%)) scale(var(--sx, 1), var(--sy, 1));
  transition:
    transform .38s cubic-bezier(.34, 1.25, .5, 1),
    opacity .2s ease;
}
.foot__blob.is-on { opacity: 1; }


/* ───────── CURSOR PILL ───────── */

.cursor-pill {
  position: fixed;
  z-index: 2200;
  top: 0; left: 0;
  /* Tooltips are an exception to the consistent text style */
  background: var(--orange);
  color: var(--white);
  font-family: var(--font-mono-medium);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: .02em;
  padding: 4px 4px;
  border-radius: 0;
  pointer-events: none;
  white-space: nowrap;
  transform: translate(-50%, -160%) scale(0.5);
  opacity: 0;
  transition:
    transform .25s cubic-bezier(.2,.8,.2,1),
    opacity .2s ease,
    background .2s ease,
    color .2s ease,
    width .3s cubic-bezier(.2,.8,.2,1);
  will-change: transform, opacity;
}
@keyframes check-draw { to { stroke-dashoffset: 0; } }
.cursor-pill.is-on {
  opacity: 1;
  transform: translate(-50%, -160%) scale(1);
}
/* "View project" — no background; orange text always. */
.cursor-pill.is-view {
  background: transparent;
  border-radius: 0;
  padding: 4px 4px;
  line-height: 1;
  color: var(--orange);
  transition: opacity .2s ease, transform .25s cubic-bezier(.2, .8, .2, 1);
}
/* The "Copy email" tooltip — orange chip, white text. */
.cursor-pill.is-view.is-tip {
  background: var(--orange);
  color: #fff;
  padding: 4px 4px;
  line-height: 1;
}
.cursor-pill__text { display: inline-block; }


/* ───────── Organic blob breath ─────────
   Shared breathing morph + pulse — used by the hover-pop blobs (About/Experience).
   The blob cursor that replaced the pointer on project links has been removed;
   project links now use the standard --cursor-pointer like every other link. */
@keyframes blob-pulse {            /* the breath — soft squash/stretch in and out */
  0%, 100% { transform: scale(.92, 1.08); }
  50%      { transform: scale(1.08, .92); }
}
@keyframes blob-morph {            /* organic drift, off-period from the pulse — a living, liquid edge */
  0%, 100% { border-radius: var(--blob-radius); }
  50%      { border-radius: var(--blob-radius); }
}


/* ───────── Email copy interaction ─────────
   A blob behind the address: hover = a small orange circle (text turns black);
   click = the address glyph-scrambles while the blob rises straight up and
   morphs into a "Copied" tooltip. Resets on mouseleave. Geometry
   (left/top/width/height) is set inline by JS per state. */
.email-orb {
  position: absolute;
  z-index: -1;                  /* sits behind the email text */
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--orange);
  color: #fff;
  opacity: 0;
  transform: scale(.3);
  pointer-events: none;
  font-family: var(--font-mono-medium);
  font-size: 13px;
  letter-spacing: .02em;
  line-height: 1;
  white-space: nowrap;
  transition:
    left   .32s cubic-bezier(.3, .8, .2, 1),
    top    .34s cubic-bezier(.3, .8, .2, 1),   /* the straight rise into a tooltip */
    width  .32s cubic-bezier(.3, .8, .2, 1),
    height .32s cubic-bezier(.3, .8, .2, 1),
    border-radius .3s ease,
    transform .24s cubic-bezier(.3, .8, .2, 1),
    opacity .2s ease;
}
.email-orb.is-shown { opacity: 1; transform: scale(1); }
.email-orb.is-front { z-index: 6; }    /* tooltip phase: above the text */
.email-orb__label {
  display: inline-flex;
  align-items: center;
  text-transform: uppercase;
  letter-spacing: .08em; /* the one caps tracking */
  padding: 5px 6px;              /* snug to the caps label */
  opacity: 0;
  transform: scale(.7);
  transition: opacity .16s ease, transform .2s ease;
}
.email-orb.is-tip .email-orb__label { opacity: 1; transform: scale(1); }

.topnav-contact .topnav-contact__email.is-copy-active { color: #16161a; }
/* The email isn't a link anymore (no mailto) — it just copies — but keep the
   clickable hand cursor. */
.outro__email, .topnav-contact__email { cursor: var(--cursor-pointer); }


/* ───────── RESPONSIVE ───────── */

@media (max-width: 839px) {
  .hero { padding: 140px 16px; }
  .cursor-pill { display: none; }
  body { cursor: auto; }
  /* Restore centered columns on mobile (desktop left-aligns them at 50vw) */
  .about-block { padding-left: 16px; padding-right: 16px; }
  .about-block__inner { max-width: 100%; }
  .outro { text-align: center; padding-left: 16px; padding-right: 16px; }
  .foot { justify-content: center; padding-left: 16px; padding-right: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ─────────────────────────────────────────────
   ABOUT + EXPERIENCE  (moved from about.html / about.css)
   Component styles only — global resets/fonts stay in main.css.
───────────────────────────────────────────── */
.about-content {
  display: flex;
  flex-direction: column;
  overflow: clip;
}
/* Section block — a narrow column centered in the viewport */
.about-block { padding: 64px 16px 64px 50%; }  /* left edge at viewport center — aligns with the hero role (both scrollbar-excluded) */
/* Halved gaps: About→Experience = 104+104 = 208px; Experience→outro text =
   80 (Experience pad-bottom) + 128 (outro pad-top) = 208px. */
.about-block--first { padding-top: 160px; padding-bottom: 104px; }
.about-block--last  { padding-top: 104px; padding-bottom: 160px; }
.about-block__inner {
  position: relative;          /* anchor + stacking context for the hover-pop blobs */
  z-index: 0;
  max-width: 40vw;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* Mono section label (ABOUT / EXPERIENCE) */
.about-block__label {
  font-family: var(--font-mono-medium);
  font-size: 13px;
  line-height: 1.5;
  letter-spacing: .08em; /* the one caps tracking */
  text-transform: uppercase;
  color: var(--black);
}
/* Bio text */
.about-bio {
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.5;
  color: var(--black);
}
.about-bio p { margin: 0; }
.about-bio p + p { margin-top: 22px; }

/* ── Experience accordion ── */
.exp-list {
  position: relative;
  z-index: 0;
  display: flex;
  flex-direction: column;
  --date-w: 42px;
  --row-gap: 16px;
  --dot-size: 7px;
  --text-x: calc(var(--date-w) + var(--row-gap) * 2 + var(--dot-size));
}
.exp-list::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--date-w) + var(--row-gap) + var(--dot-size) / 2);
  width: 1px;
  margin-left: -0.5px;
  background: var(--line);
  z-index: 0;
  transition: opacity .25s ease;
}
.exp-row {
  display: flex;
  flex-direction: column;
  position: relative;
}
.exp-row__header {
  display: flex;
  align-items: center;
  gap: var(--row-gap);
  padding: 12px 0;
  width: 100%;
  text-align: left;
  border: 0;
  background: none;
  cursor: var(--cursor-pointer);
}
.exp-row__dot {
  position: relative;
  z-index: 1;
  flex-shrink: 0;
  width: var(--dot-size);
  height: var(--dot-size);
  border-radius: 50%;
  background: var(--black);
  transition: opacity .25s ease;
}
.exp-row__header:hover .exp-row__dot { opacity: 0; }
.exp-row__header:hover .exp-row__company,
.exp-row__header:hover .exp-row__role,
.exp-row__header:hover .exp-row__date,
.exp-row__header:hover .exp-row__plus { color: var(--orange); }
.exp-row__info {
  display: flex;
  align-items: center;
  gap: 16px;
  flex: 1;
  min-width: 0;
}
.exp-row__details {
  display: flex;
  flex-direction: column;
  gap: 1px;
  flex: 1;
  min-width: 0;
  transition: opacity 0.2s ease;
}
.exp-row__company {
  font-family: 'Rand Regular', system-ui, sans-serif;
  font-size: 13px;
  line-height: 1.5;
  color: var(--black);
  transition: color .25s ease;
}
.exp-row__role {
  font-family: 'Rand Regular', system-ui, sans-serif;
  font-size: 13px;
  line-height: 1.5;
  color: var(--gray-300);
  transition: color .25s ease;
}
.exp-row__date {
  width: var(--date-w);
  flex-shrink: 0;
  text-align: right;
  font-family: var(--font-medium);
  font-size: 13px;
  line-height: normal;
  color: var(--gray-300);
  white-space: nowrap;
  transition: color .25s ease, opacity 0.2s ease;
}
.exp-row__plus {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  color: var(--black);
  flex-shrink: 0;
  transition: color .25s ease, opacity .25s ease;
}
.exp-list__blob {
  position: absolute;
  left: 0;
  top: 0;
  width: 1.25rem;
  height: 1.25rem;
  margin: -0.625rem 0 0 -0.625rem;
  border-radius: var(--blob-radius);
  background: var(--orange);
  z-index: 2;
  pointer-events: none;
  opacity: 0;
  transform: translate(var(--x, 50%), var(--y, 50%)) scale(var(--sx, 1), var(--sy, 1));
  transition: transform .38s cubic-bezier(.34, 1.25, .5, 1), opacity .2s ease;
}
.exp-list__blob.is-on { opacity: 1; }
.exp-row__toggle { display: none; }
.exp-row.is-open .exp-row__plus { color: var(--black); }
.exp-row.is-open .exp-row__plus .exp-row__plus-v { display: none; }
.exp-row__body {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.35s ease;
}
.exp-row.is-open .exp-row__body { grid-template-rows: 1fr; }
.exp-row__body-wrap {
  min-height: 0;
  overflow: hidden;
}
.exp-row__content {
  overflow: hidden;
  padding-left: var(--text-x);
}
.exp-row__icon-lg,
.exp-row__inner-header { display: none; }
.exp-row__inner {
  display: flex;
  flex-direction: column;
  gap: 16px;
  flex: 1;
  min-width: 0;
  padding: 4px 0 16px 0;
  overflow: hidden;
}
.exp-row__desc {
  font-family: var(--font-sans);
  font-size: 13px;               /* BODY */
  line-height: 1.6;
  color: var(--gray-500);
  cursor: default;
  margin: 0;
}
.exp-row__links {
  position: relative;
  z-index: 0;
  color: var(--gray-300);
}
.exp-row__links a {
  position: relative;
  z-index: 1;
  display: inline-flex;
  gap: 8px;
  align-items: center;
  padding: 0;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .02em;
  color: inherit;
  transition: color .2s ease;
}
.exp-row__links a:hover { color: var(--black); }
.exp-row__links a span {
  display: inline-block;
  transition: transform .25s cubic-bezier(.2,.8,.2,1);
}
.exp-row__links a:hover span { transform: rotate(45deg); }
.exp-row__links-blob {
  position: absolute;
  left: 0;
  top: 0;
  width: 1.25rem;
  height: 1.25rem;
  margin: -0.625rem 0 0 -0.625rem;
  border-radius: var(--blob-radius);
  background: var(--orange);
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  transform: translate(var(--x, 50%), var(--y, 50%)) scale(var(--sx, 1), var(--sy, 1));
  transition: transform .38s cubic-bezier(.34, 1.25, .5, 1), opacity .2s ease;
}
.exp-row__links-blob.is-on { opacity: 1; }
.exp-row__thumbs {
  position: relative;
  margin-left: var(--text-x);
  margin-bottom: 32px;
  aspect-ratio: 8 / 5;
  background: var(--gray-100);
  overflow: hidden;
  cursor: default;
}
.exp-row__thumb {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity .5s ease;
}
.exp-row__thumb.is-active { opacity: 1; }
.exp-row__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.hero__name {
  font-family: 'Rand Medium', system-ui, sans-serif;
}
.hero__role,
.about-bio,
.exp-row__desc {
  font-family: 'Rand Regular', system-ui, sans-serif;
}
.hero__name,
.hero__role,
.about-bio {
  font-size: 15px;
  letter-spacing: 0;
  line-height: 1.65em;
}
/* Experience accordion descriptions */
.exp-row__desc {
  font-size: 13px;
  letter-spacing: .01em;
  line-height: 1.6em;
}


/* ===== TEMP — experimental layout (remove to restore) =====
   Hero elements live in the top bar; the hero area is left as empty white space
   (60% of the viewport) above the grid. ===== */
.hero { display: none; }    /* TEMP: hide grid lines */
.topnav { border-bottom: 0; }                    /* TEMP: hide top-nav border */
/* ===== /TEMP ===== */


/* ═══════════ CORNER BLOB (experimental) ═══════════ */

/* ── Corner blob — a shy, playful orange dot (16px, half the old size). Breathes
   in place, but when the cursor gets close it bolts: vanishing and reappearing in
   the opposite top corner (left <-> right, JS-driven, see cornerBlobEscape). Its
   left/right edge sits on the 16px gutter. Hidden during the build.
   pointer-events:none — it flees before you reach it, never clicked. ── */
.corner-blob {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 95;
  width: 16px;
  height: 16px;
  pointer-events: none;
  transform: translate(16px, 16px);   /* home; JS drives the slime move (translate + stretch) */
}
/* Middle layer owns reveal/vanish — it shrinks straight into its own center (its
   "hole"). Used for the scroll-away vanish and the build-time hide. */
.corner-blob__hole {
  display: block;
  width: 100%;
  height: 100%;
  transform: scale(1);
  transform-origin: 50% 50%;
  transition: transform .34s cubic-bezier(.5, 0, .32, 1);
}
.corner-blob.is-scrolled-away .corner-blob__hole,
.corner-blob.is-hiding .corner-blob__hole { transform: scale(0); }
.corner-blob__dot {
  display: block;   /* it is a direct <span> child now (no flex parent) — needs block to take width/height */
  width: 100%;
  height: 100%;
  border-radius: var(--blob-radius);
  background: var(--orange);
  animation: blob-breathe 3.6s ease-in-out infinite;
}
@keyframes blob-breathe {
  0%, 100% { transform: scale(.9);  border-radius: var(--blob-radius); }
  50%      { transform: scale(1.12); border-radius: var(--blob-radius); }
}
@media (prefers-reduced-motion: reduce) {
  .corner-blob__dot { animation: none; }
}

/* ── Semantic headings, unstyled ──
   The hero name is the page's <h1> and the section labels are <h2>s, so
   screen readers and search engines get a real outline. The UA stylesheet
   gives headings their own size/weight/margin — reset to inherit so the
   markup change is purely semantic and the design is untouched. ── */
/* Headings, neutralised. Split deliberately:
   - font-size/weight need REAL specificity, because normalize.css sets
     `h1 { font-size: 2em }` at (0,0,1) — a :where() rule loses to it and the
     name renders 15px instead of 13px.
   - margin/line-height go in :where() at ZERO specificity, so each component
     rule keeps its own spacing (`.outro__label`'s 12px bottom margin was being
     flattened by a plain `h2.outro__label` selector here). */
h1.hero__name,
h2.about-block__label,
h2.outro__label {
  font-size: inherit;
  font-weight: inherit;
}
:where(h1.hero__name, h2.about-block__label, h2.outro__label) {
  line-height: inherit;
  margin: 0;
}

/* ── Pixel veil — arrival half of the lock/404 pixel transition (see
   pxVeil in main.js). Fall parameters ride on each cell from birth. ── */
.pxveil {
  position: fixed;
  inset: 0;
  z-index: 200;
  width: 100vw;
  height: 100vh;
  display: block;
  pointer-events: none;
}
