/* Shared chrome for every prototype page. Edit here, not in a page.
   Pages own: .header's own height, .hero, and their content. */

:root {
  --ground: #EFF0F0;
  --ink: #000000;
  --headline: #0B0B0C;
  --accent: #424AED;
  --hairline: #0000001A;
  --serif: "Instrument Serif", Georgia, serif;
  --sans: "Geist", system-ui, sans-serif;
  --sans-features: "ss03", "ss04", "ss08";
  --wordmark-features: "case", "ss01", "ss03", "ss04", "ss08";
  --nav-sans: "Inter", system-ui, sans-serif;
  --mono: "Geist Mono", ui-monospace, SFMono-Regular, monospace;
  --edge: 40px;
  --gutter: 16px;

  /* How wide the chrome is allowed to get. Below this the header and the footer
     run to --edge as they always have; past it they hold this measure and the
     ground opens up outside, so the wordmark and the theme control stop walking
     out to the corners of a 4K or 5K panel with the page's content stranded in
     the middle.

     3000px, not the gallery's 1920px grid — the chrome is the frame and the
     grid is the picture, and pulling the frame in to the picture's width would
     read as a boxed page rather than a wide one. On anything up to 3K nothing
     here changes at all.

     --chrome-inset is the side figure for the header's 2 edge pieces, the
     wordmark and the theme control. Both are fixed, so there is no container to
     cap them and the measure has to be arithmetic on their own insets. The menu
     needs none of it — it is centred on 50% already and stays centred at any
     width.

     A percentage rather than 100vw on purpose: % resolves against the viewport
     without counting the scrollbar and 100vw counts it, so the vw form would
     sit the chrome a scrollbar's width off centre from everything in flow. */
  --chrome-max: 3000px;
  --chrome-inset: max(var(--edge), calc(50% - var(--chrome-max) / 2 + var(--edge)));
  --panel-radius: 12px;
  --panel-glass: #FFFFFFD6;
  --switch-track: #1212120F;
  --switch-thumb: #FFFFFF;
  --switch-shadow: 0 4px 3px #0000001A;
  --panel-filter: none;

  /* Wide, low-opacity stack — four layers so the falloff stays smooth at size. */
  --shadow-surface:
    0px 9px 18px rgb(var(--shadow-color) / 0.12),
    0px 34px 33px rgb(var(--shadow-color) / 0.1),
    0px 77px 44px rgb(var(--shadow-color) / 0.06),
    0px 136px 52px rgb(var(--shadow-color) / 0.02);

  /* Text selection. Dark is the reference (#365273 sampled); light holds the
     same hue family, lifted so ink stays readable on a pale ground. */
  --select-bg: #BFD0E3;
  --select-ink: #0B0B0C;

  /* Preview veil. Portfolio/Rectangle 3655, node 2363:2404 — black at 16%, so
     the blur carries most of the separation. */
  --veil: rgba(0, 0, 0, 0.16);
  --shadow-color: 15 23 42;
  --hairline-ring: #0000000F;

  /* Menu — Portfolio/Nav, node 2231:2226. Both radii exceed half their height,
     so the track and the thumb are pills; the nested-radius rule below is for
     the theme control, which is not. */
  --nav-w: 263px;
  --nav-h: 39px;
  --nav-gap: 4px;
  --nav-border: 0.5px;
  --nav-top: 33px;
  --nav-block: calc(var(--nav-h) + 2 * var(--nav-gap) + 2 * var(--nav-border));
  --nav-bottom: calc(var(--nav-top) + var(--nav-block));
  --nav-outer: 61px;
  --nav-inner: 33px;
  --nav-bg: #FFFFFFCC;
  --nav-edge: #16161B1F;
  --nav-ink: #1212146B;
  --nav-ink-on: #121214F0;

  /* Menu thumb — Portfolio/Nav, node 2242:2194, thumb from node 2256:2202.
     A hairline over a diagonal bevel: shadow off the top-right, light off the
     bottom-left, so the thumb reads pressed into the pill rather than laid on
     top of it. The fill paints under the hairline, so the rim composites
     darker than the border alpha alone. */
  --nav-thumb: rgb(0 0 0 / 0.06);
  --nav-thumb-border: 0.3px;
  --nav-thumb-edge: rgb(0 0 0 / 0.13);
  --nav-thumb-inset:
    inset -1px 3px 3px 0px rgb(0 0 0 / 0.22),
    inset 2px -3px 3px 0px rgb(255 255 255 / 0.8);

  /* Nested radii — inner = outer - gap - border (umbra.nick.qa/radius)

     The border term was missing, so every segmented control was 0.5px too
     round. Sub-pixel and invisible, but the rule is the rule and the whole
     point of deriving the inner value is that it cannot drift.

     --nav-border is right for every parent that uses these: each one is
     border: var(--nav-border) solid var(--hairline). The exception is .seg in
     mobile-menu.html, which has no border and hardcodes its padding — it is
     0.5px too square now instead of too round. */
  --seg-gap: 3px;
  --seg-outer: 12px;
  --seg-inner: calc(var(--seg-outer) - var(--seg-gap) - var(--nav-border));

  /* Cursor — Portfolio, node 2239:2375 (light) and 2239:2327 / 2239:2329 (dark).
     Light is ink on a pale ground and picks up a drop shadow once it collapses;
     dark is white glass and needs none. */
  --cursor-size: 20px;
  --cursor-size-on: 10px;
  --cursor-ring: 1px;
  --cursor-blur: 25px;
  --cursor-rgb: 0 0 0;
  --cursor-line: 0.2;
  --cursor-fill: 0.06;
  --cursor-fill-on: 0.16;
  --cursor-lift: none;
  --cursor-lift-on: 0 2px 4px rgb(0 0 0 / 0.06);
  --cursor-ease: cubic-bezier(0.2, 0.8, 0.2, 1);

  color-scheme: light;
}

body { font-feature-settings: var(--sans-features); }

:root[data-theme="dark"] {
  --ground: #070808;
  --ink: #F2F2F0;
  --headline: #F2F2F0;
  --accent: #8E95FF;
  --hairline: #FFFFFF1F;
  --nav-bg: #1A1B1D99;
  --nav-edge: #FFFFFF1F;
  --nav-ink: #F2F2F06B;
  --nav-ink-on: #F2F2F0F0;
  --nav-thumb: rgb(255 255 255 / 0.08);
  --nav-thumb-edge: rgb(255 255 255 / 0.28);
  --nav-thumb-inset:
    inset -1px 3px 3px 0px rgb(0 0 0 / 0.45),
    inset 2px -3px 3px 0px rgb(255 255 255 / 0.14);
  --panel-glass: #1A1B1DD6;
  --switch-track: #FFFFFF14;
  --switch-thumb: #2C2E31;
  --switch-shadow: 0 4px 3px #00000059;
  --panel-filter: brightness(0.82) saturate(0.92);
  --select-bg: #365273;
  --select-ink: #F2F2F0;

  --shadow-color: 0 0 0;
  --hairline-ring: #FFFFFF1A;

  --cursor-rgb: 255 255 255;
  --cursor-line: 1;
  --cursor-fill: 0.1;
  --cursor-fill-on: 0.65;
  --cursor-lift-on: none;

  color-scheme: dark;
}

* { box-sizing: border-box; }

::selection {
  background: var(--select-bg);
  color: var(--select-ink);
}

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  transition: background-color 240ms ease, color 240ms ease;
}

/* ── Scroll chrome ────────────────────────────────────────
   All 3 pieces are fixed from the start — never swapped from absolute, which
   would re-anchor them to the viewport mid-scroll and flash them back into
   view before animating out. All 3 tuck on scroll down; scroll up returns the
   menu alone, at any depth. The wordmark and the theme control return only at
   the top of the page. Same at every width — the narrow rules move the menu to
   the right edge but keep it fixed.

   Fixed is also what holds them still through an overscroll. A rubber-band at
   the top of the page, or a pull-to-refresh on a phone, drags the document
   under the viewport; anything in the flow rides down with it while the menu
   stays put, and the header comes apart for the length of the pull. The
   wordmark and the theme control now sit in the same frame the menu does. */

.nav,
.wordmark,
.theme-toggle {
  transform-origin: center top;
  will-change: transform;
}

.nav,
.wordmark {
  transition:
    transform 220ms cubic-bezier(0.4, 0, 0.2, 1),
    opacity 160ms ease;
}

.is-tucked {
  transform: translateY(-80%) scale(0.8);
  opacity: 0;
  pointer-events: none;
}

/* ── Wordmark ─────────────────────────────────────────── */

.wordmark {
  position: fixed;
  z-index: 2;
  left: var(--chrome-inset);
  top: 49px;
  color: inherit;
  text-decoration: none;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 30px;
  letter-spacing: -1px;
  line-height: 20px;
  font-feature-settings: var(--wordmark-features);
  display: inline-flex;
  align-items: baseline;
  white-space: nowrap;
}

.wordmark-swap {
  position: relative;
  display: inline-block;
  width: var(--tail-rest, auto);
  transition: width 420ms cubic-bezier(0.65, 0, 0.35, 1);
}

.wordmark.is-open .wordmark-swap { width: var(--tail-hover, auto); }

/* Restored from the previous page: land already-open, no enter animation. */
.wordmark-instant,
.wordmark-instant * { transition: none !important; }
.wordmark-tail { display: inline-flex; }
.wordmark-tail-hover { position: absolute; left: 0; top: 0; width: max-content; }

.wordmark-tail span {
  display: inline-block;
  white-space: pre;
  transition:
    opacity 240ms ease var(--d),
    transform 420ms cubic-bezier(0.33, 1.2, 0.6, 1) var(--d),
    filter 240ms ease var(--d);
}

.wordmark-tail-rest span { --d: calc(110ms + var(--i) * 28ms); }

.wordmark-tail-hover span {
  --d: calc(var(--i) * 28ms);
  opacity: 0;
  transform: translateY(0.62em);
  filter: blur(6px);
}

.wordmark.is-open .wordmark-tail-rest span {
  --d: calc(var(--i) * 28ms);
  opacity: 0;
  transform: translateY(-0.62em);
  filter: blur(6px);
}

.wordmark.is-open .wordmark-tail-hover span {
  --d: calc(90ms + var(--i) * 28ms);
  opacity: 1;
  transform: translateY(0);
  filter: blur(0px);
}

/* ── Top menu ─────────────────────────────────────────── */

.nav {
  position: fixed;
  z-index: 2;
  left: 50%;
  top: var(--nav-top);
  translate: -50%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  width: var(--nav-w);
  padding: var(--nav-gap);
  overflow: clip;
  border: var(--nav-border) solid var(--nav-edge);
  border-radius: var(--nav-outer);
  background: var(--nav-bg);
  backdrop-filter: blur(12px);
}

.nav a {
  position: relative;
  z-index: 1;
  display: flex;
  flex: 1 0 0;
  min-width: 0;
  align-items: center;
  justify-content: center;
  height: var(--nav-h);
  border-radius: var(--nav-inner);
  font-family: var(--nav-sans);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: -0.23px;
  line-height: 20px;
  color: var(--nav-ink);
  text-decoration: none;
  transition: color 160ms ease;
}

/* Home is a circle, not a pill — it holds one letter and its width is locked
   to the row height so the two match. Every other link splits what is left. */
.nav a[data-key="home"] {
  flex: 0 0 var(--nav-h);
  width: var(--nav-h);
  border-radius: 50%;
}

/* Bound to the thumb, not to :hover — the pointer sits in the pill's padding
   for the 4px band around the links, and the link the thumb is under should
   read as hovered the whole time it is lit. */
.nav a[data-on],
.nav a[aria-current="page"] { color: var(--nav-ink-on); }

/* The thumb is pinned by both edges rather than left+width so the cursor morph
   can hand it an exact rect without a width/left rounding seam. */
.nav-thumb {
  position: absolute;
  top: var(--nav-gap);
  height: var(--nav-h);
  border-radius: var(--nav-inner);

  /* Border-box is global, so the hairline sits inside the width the thumb is
     already given — the pill keeps its measured geometry. */
  border: var(--nav-thumb-border) solid var(--nav-thumb-edge);
  background: var(--nav-thumb);
  box-shadow: var(--nav-thumb-inset);
  pointer-events: none;
  opacity: 0;
  transition:
    left 320ms cubic-bezier(0.2, 0.8, 0.2, 1),
    right 320ms cubic-bezier(0.2, 0.8, 0.2, 1),
    opacity 200ms ease;
}

.nav-thumb.is-on { opacity: 1; }

/* While the pointer is inside the pill the cursor IS the thumb, so the real one
   steps aside rather than doubling it. */
.nav[data-cursor-in="true"] .nav-thumb { opacity: 0; transition: opacity 120ms ease; }

/* ── Theme toggle ─────────────────────────────────────────
   The menu's shell at one link's width: same glass, same hairline, same thumb
   arriving under the pointer, same ink lift. Sized off --nav-h so it matches
   the menu's height exactly and sits on its top line.

   One glyph, never two. A second disc crosses the sun and cuts a crescent out
   of it while the rays retract into the body — the control changes form rather
   than swapping icons, which is what a single setting with 2 values looks
   like. Replaced the 3-then-2 segment control on 2026-09-14. */

.theme-toggle {
  --toggle-size: calc(var(--nav-h) + (var(--nav-gap) + var(--nav-border)) * 2);

  position: fixed;
  z-index: 2;
  right: var(--chrome-inset);
  top: var(--nav-top);
  display: grid;
  place-items: center;
  width: var(--toggle-size);
  height: var(--toggle-size);
  padding: 0;
  border: var(--nav-border) solid var(--nav-edge);
  border-radius: 50%;
  background: var(--nav-bg);
  backdrop-filter: blur(12px);
  color: var(--nav-ink);
  cursor: pointer;
  transition:
    color 160ms ease,
    transform 220ms cubic-bezier(0.4, 0, 0.2, 1),
    opacity 160ms ease;
}

.theme-toggle:hover { color: var(--nav-ink-on); }

/* Inside the menu it stops being its own shell and becomes a slot, sized like
   the Home circle. The pill supplies the glass, the hairline and the thumb, so
   the toggle drops all 3 and lights the same way a link does. */
.nav .theme-toggle {
  position: static;
  flex: 0 0 var(--nav-h);
  width: var(--nav-h);
  height: var(--nav-h);
  border: 0;
  background: transparent;
  backdrop-filter: none;
}

.nav .theme-toggle[data-on] { color: var(--nav-ink-on); }

.nav .theme-toggle .theme-toggle-thumb { display: none; }

.theme-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* The menu's thumb, arriving the same way — opacity only, never a size change,
   so nothing about the button moves when the pointer lands on it. */
.theme-toggle-thumb {
  position: absolute;
  inset: var(--nav-gap);
  border: var(--nav-thumb-border) solid var(--nav-thumb-edge);
  border-radius: 50%;
  background: var(--nav-thumb);
  box-shadow: var(--nav-thumb-inset);
  opacity: 0;
  pointer-events: none;
  transition: opacity 200ms ease;
}

.theme-toggle:hover .theme-toggle-thumb { opacity: 1; }

/* Inside the menu the cursor becomes the thumb; the same happens here, so the
   real one steps aside rather than doubling it. This is the whole hover state
   on a fine pointer — the CSS one above is what a touch device gets. */
.theme-toggle[data-cursor-in="true"] .theme-toggle-thumb {
  opacity: 0;
  transition: opacity 120ms ease;
}

.theme-toggle svg {
  position: relative;
  width: 18px;
  height: 18px;
}

.theme-toggle .cut { transition: translate 520ms cubic-bezier(0.32, 0.72, 0, 1); }

.theme-toggle .rays {
  transform-origin: 12px 12px;
  transition: opacity 260ms ease, scale 420ms cubic-bezier(0.32, 0.72, 0, 1);
}

.theme-toggle .body {
  transform-origin: 12px 12px;
  transition: scale 420ms cubic-bezier(0.32, 0.72, 0, 1);
}

:root[data-theme="dark"] .theme-toggle .cut { translate: -8.5px 4.5px; }
:root[data-theme="dark"] .theme-toggle .rays { opacity: 0; scale: 0.3; }
:root[data-theme="dark"] .theme-toggle .body { scale: 1.45; }

/* ── Cursor ───────────────────────────────────────────── */

body[data-cursor="on"],
body[data-cursor="on"] * { cursor: none; }


/* A popover, so it shares the top layer with the preview dialog and stays
   visible over it. z-index is the fallback where popover is unsupported. */
.cursor {
  position: fixed;
  inset: 0 auto auto 0;
  z-index: 100;
  width: max-content;
  height: max-content;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  overflow: visible;
  pointer-events: none;
  will-change: transform;
}

.cursor[data-visible="false"] { visibility: hidden; }

/* Held for the first placement of a page only — see track() in site.js. Without
   it the cursor eases in from the top-left corner after every menu click. */
.cursor.is-placing,
.cursor.is-placing .cursor-dot { transition: none !important; }

.cursor[data-engaged="true"] {
  --cursor-size: var(--cursor-size-on);
  --cursor-fill: var(--cursor-fill-on);
  --cursor-lift: var(--cursor-lift-on);
}

/* Press collapses to the same size hover does, from any ground — so a press on
   something already collapsed holds at 10px instead of shrinking again. The
   fill and the lift stay whatever the state under the pointer already set. */
.cursor[data-pressed="true"] { --cursor-size: var(--cursor-size-on); }

/* ── Cursor tint ──────────────────────────────────────────
   Tapping empty ground cycles the dot through 8 hues. Fluo on purpose — the
   muted set read as one pale colour on the light ground once the alphas got
   this low, so the picked set trades restraint for hues that stay apart. Each
   carries a value tuned per ground: the lit column for #EFF0F0, the dim
   column for #070808.

   The tinted states also carry their own alphas — the ink default sits at
   0.2 line / 0.06 fill, far too faint to read as a colour at all. The engaged
   fill runs 30% above where it was: at 10px there is a third of the area to
   carry the hue, so it needs the density the resting size does not. */

.cursor[data-tint] {
  --cursor-line: 0.55;
  --cursor-fill: 0.16;
  --cursor-fill-on: 0.42;
}

.cursor[data-tint="1"] { --cursor-rgb: 132 186 0; }   /* Acid Lime */
.cursor[data-tint="2"] { --cursor-rgb: 224 72 48; }   /* Blaze */
.cursor[data-tint="3"] { --cursor-rgb: 0 168 190; }   /* Electric Cyan */
.cursor[data-tint="4"] { --cursor-rgb: 236 108 24; }  /* Neon Orange */
.cursor[data-tint="5"] { --cursor-rgb: 108 68 232; }  /* UV Violet */
.cursor[data-tint="6"] { --cursor-rgb: 0 176 132; }   /* Spring Mint */
.cursor[data-tint="7"] { --cursor-rgb: 224 62 150; }  /* Hot Magenta */
.cursor[data-tint="8"] { --cursor-rgb: 40 88 240; }   /* Ultramarine */

:root[data-theme="dark"] .cursor[data-tint] {
  --cursor-line: 0.9;
  --cursor-fill: 0.26;
  --cursor-fill-on: 0.68;
}

:root[data-theme="dark"] .cursor[data-tint="1"] { --cursor-rgb: 198 255 71; }   /* Acid Lime */
:root[data-theme="dark"] .cursor[data-tint="2"] { --cursor-rgb: 255 130 106; }  /* Blaze */
:root[data-theme="dark"] .cursor[data-tint="3"] { --cursor-rgb: 94 236 255; }   /* Electric Cyan */
:root[data-theme="dark"] .cursor[data-tint="4"] { --cursor-rgb: 255 158 92; }   /* Neon Orange */
:root[data-theme="dark"] .cursor[data-tint="5"] { --cursor-rgb: 168 140 255; }  /* UV Violet */
:root[data-theme="dark"] .cursor[data-tint="6"] { --cursor-rgb: 92 246 200; }   /* Spring Mint */
:root[data-theme="dark"] .cursor[data-tint="7"] { --cursor-rgb: 255 122 190; }  /* Hot Magenta */
:root[data-theme="dark"] .cursor[data-tint="8"] { --cursor-rgb: 122 156 255; }  /* Ultramarine */


.cursor-dot {
  width: var(--cursor-size);
  height: var(--cursor-size);
  translate: -50% -50%;
  border: var(--cursor-ring) solid rgb(var(--cursor-rgb) / var(--cursor-line));
  border-radius: 50%;
  background: rgb(var(--cursor-rgb) / var(--cursor-fill));
  box-shadow: var(--cursor-lift);
  backdrop-filter: blur(var(--cursor-blur));
  transition:
    width 160ms var(--cursor-ease),
    height 160ms var(--cursor-ease),
    background-color 160ms var(--cursor-ease),
    border-color 160ms var(--cursor-ease),
    box-shadow 160ms var(--cursor-ease);
}

/* ── Cursor → thumb morph ─────────────────────────────────
   Entering the menu used to hide the cursor, which read as the pointer falling
   down a hole. Instead the cursor keeps its identity and takes the thumb's
   shape: same rect, same fill, same hairline. Nothing appears or disappears —
   one object changes form. The three modes differ only in how the change is
   timed. */

.cursor[data-morph="on"],
.cursor[data-morph="leaving"] {
  transition: translate var(--morph-travel, 220ms) var(--morph-ease, cubic-bezier(0.2, 0.8, 0.2, 1));
}

.cursor[data-morph="on"] .cursor-dot {
  width: var(--cursor-w);
  height: var(--cursor-h);
  border-radius: var(--nav-inner);
  border: var(--nav-thumb-border) solid var(--nav-thumb-edge);
  background: var(--nav-thumb);
  box-shadow: var(--nav-thumb-inset);
  backdrop-filter: none;
  transition:
    width var(--morph-w, 260ms) var(--morph-ease, cubic-bezier(0.2, 0.8, 0.2, 1)) var(--morph-delay, 0ms),
    height var(--morph-h, 260ms) var(--morph-ease, cubic-bezier(0.2, 0.8, 0.2, 1)) var(--morph-delay, 0ms),
    border-radius 200ms ease var(--morph-delay, 0ms),
    background-color 200ms ease,
    border-color 200ms ease,
    box-shadow 200ms ease,
    scale 240ms var(--morph-ease, cubic-bezier(0.2, 0.8, 0.2, 1)),
    rotate 260ms var(--morph-ease, cubic-bezier(0.2, 0.8, 0.2, 1));
}

/* A round target hands the cursor a circle instead of the menu's pill radius.
   Everything else about the morph is shared, so the 2 run one path. Must sit
   after the rule above — same specificity, so source order decides. */
.cursor[data-morph="on"][data-round="true"] .cursor-dot { border-radius: 50%; }

/* Absorb. Position and shape resolve on one curve, so the dot swells into the
   pill exactly as it arrives — the quietest of the 4 that were tried, and the
   one that was picked. The other 3 and what separated them are written down in
   docs/nav-morph.md; nothing switches between them any more. */
.cursor {
  --morph-travel: 240ms;
  --morph-w: 260ms;
  --morph-h: 260ms;
  --morph-ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* Growing from the centre pushes past the pill's 4px padding at either end. The
   first and last links grow inward instead, so the shape stays in the frame. */
.cursor[data-edge="start"] .cursor-dot { transform-origin: left center; }
.cursor[data-edge="end"] .cursor-dot { transform-origin: right center; }

/* Entering onto the tab already under the thumb: lean toward the side the
   pointer came from, then settle. No travel to restate. */
.cursor[data-morph="on"][data-tilt="left"] .cursor-dot { rotate: -1.4deg; }
.cursor[data-morph="on"][data-tilt="right"] .cursor-dot { rotate: 1.4deg; }

/* ── Narrow: chrome falls into flow ───────────────────── */

/* Contact pill: email, LinkedIn, X. Borrows the top menu's shell and its sliding
   thumb — the fill travels between the three items instead of fading in under each
   one, and one tooltip slides and resizes between them, handing the label off in
   the direction of travel. A page opts in with <span class="contact-pill" data-contact>. */

.contact-pill {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--nav-gap);
  padding: var(--nav-gap);
  border: var(--nav-border) solid var(--nav-edge);
  border-radius: var(--nav-outer);
  background: var(--nav-bg);
  backdrop-filter: blur(12px);
}

.contact-pill-item {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--nav-h);
  height: var(--nav-h);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--nav-ink);
  cursor: pointer;
  transition: color 160ms ease;
}

.contact-pill-item svg { width: 18px; height: 18px; }

.contact-pill-item[data-on] { color: var(--nav-ink-on); }

.contact-pill-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.contact-pill-thumb {
  position: absolute;
  top: var(--nav-gap);
  height: var(--nav-h);
  border-radius: 50%;
  border: var(--nav-thumb-border) solid var(--nav-thumb-edge);
  background: var(--nav-thumb);
  box-shadow: var(--nav-thumb-inset);
  pointer-events: none;
  opacity: 0;
  transition:
    left 320ms cubic-bezier(0.2, 0.8, 0.2, 1),
    right 320ms cubic-bezier(0.2, 0.8, 0.2, 1),
    opacity 200ms ease;
}

.contact-pill-thumb.is-on { opacity: 1; }

.tip {
  position: absolute;
  left: var(--tip-x, 50%);
  bottom: calc(100% + 9px);
  width: var(--tip-w, 0);
  height: 24px;
  border-radius: 7px;
  background: var(--ink);
  overflow: hidden;
  opacity: 0;
  translate: -50% 4px;
  pointer-events: none;
  transition:
    opacity 160ms ease,
    translate 160ms ease,
    left 300ms cubic-bezier(0.32, 0.72, 0, 1),
    width 300ms cubic-bezier(0.32, 0.72, 0, 1);
}

.tip[data-jump] {
  transition: opacity 160ms ease, translate 160ms ease;
}

.tip[data-on] {
  opacity: 1;
  translate: -50% 0;
}

.tip-layer {
  position: absolute;
  left: 50%;
  top: 50%;
  width: max-content;
  padding: 0 9px;
  color: var(--ground);
  font-family: var(--mono);
  font-size: 10px;
  line-height: 24px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  translate: -50% -50%;
  transition: opacity 180ms ease, translate 260ms cubic-bezier(0.32, 0.72, 0, 1);
}

.tip-layer[data-state="in"] { opacity: 1; }

.tip-layer[data-state="pre"] {
  opacity: 0;
  translate: calc(-50% + var(--tip-shift)) -50%;
  transition: none;
}

.tip-layer[data-state="out"] {
  opacity: 0;
  translate: calc(-50% - var(--tip-shift)) -50%;
}

/* ── Footer ───────────────────────────────────────────────
   One footer on every page — name, contact pill, year — mounted from site.js
   the same way the header is, so the content has one home rather than three.

   It runs to --edge, which is the only width every page already agrees on: the
   wordmark and the theme control sit on it in the header above, and the
   homepage gallery sits on it in between. It takes no page's column as a
   measure either — a max-width tuned to one of those would line the footer up
   with that page and no other.

   --chrome-max is not that kind of measure. It is a far-viewport guard, the
   same one the header's fixed pieces take, and it binds at 3000px and nowhere
   below. Every window that exists today sees the --edge behaviour unchanged. */

.foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 28px;
  max-width: calc(var(--chrome-max) + var(--edge) * 2);
  margin-top: clamp(96px, 10vw, 168px);
  margin-inline: auto;
  padding: 22px var(--edge) 20px;
  font-size: 13px;
  color: color-mix(in oklab, var(--ink) 45%, transparent);
}

/* The pill centres on the footer, not on what is left over between the 2
   labels. space-between splits the free space evenly, so the wider label
   pushes the middle off by half the difference between them — enough to see
   once the viewport is narrow. Equal flex on the outer 2 makes the centre the
   centre whatever they say. */
.foot > span:not(.contact-pill) { flex: 1 1 0; }

.foot > span:last-child { text-align: right; }

@media (max-width: 768px) {
  :root {
    --edge: 24px;
    --chrome-top: 24px;
  }

  /* Wraps on purpose. Row 1 is the wordmark's line and the pill's, both of
     them pinned over it; anything else the page hangs in the header — the
     homepage hero is flex: 1 1 100% — takes a row of its own below them.
     nowrap drags that onto the same line and runs the page off the screen. */
  .header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 28px 16px;
    padding: var(--chrome-top) var(--edge) 8px;
  }

  /* Row 1 holds the pill's height with nothing in it — the pill is pinned and
     so is the wordmark, so the row is empty and would otherwise collapse and
     pull the whole page up under them. Zero width, so it takes no space of
     its own. */
  .header::after {
    content: "";
    flex: 0 0 0;
    height: var(--nav-block);
  }

  /* Pinned here too, on the line row 1 would have given it: the header's own
     top padding, then half of what is left of the row above the 20px the
     wordmark stands. Centred against the pill, in other words, which is what
     align-items did while it was still in the flow. */
  .wordmark {
    left: var(--edge);
    right: auto;
    top: calc(var(--chrome-top) + (var(--nav-block) - 20px) / 2);
    translate: none;
  }

  /* The pill pins here as it does on a wide screen, so it can tuck away on
     the way down and come back on the way up — see wireScroll. Right rather
     than centred, which is where it sat when it was in the row, and at the
     header's own top padding, so it lands on the line it already held. */
  .nav {
    left: auto;
    right: var(--edge);
    top: var(--chrome-top);
    translate: none;
    width: auto;
    margin-inline: 0;
  }

  /* The wide pill divides --nav-w into equal columns. Auto width has no figure
     to divide, so the links hug their own labels instead and keep the right to
     compress when the viewport runs out. */
  .nav a {
    flex: 0 1 auto;
    padding-inline: 10px;
  }

  .nav a[data-key="home"] { padding-inline: 0; }

  /* Only the stem survives — DA, the prefix DAKIDA and DANILO RA already
     share, so the collapse reuses the swap the hover was built on rather than
     truncating the word. The hover open is off here; there is no room for it.

     overflow has to come with the width: the swap never needed to clip on the
     wide header, because both tails are sized to fit it. At zero it has to,
     or the tail paints straight out of a box with no width and the wordmark
     still reads DAKIDA. */
  .wordmark-swap,
  .wordmark.is-open .wordmark-swap {
    width: 0;
    overflow: hidden;
  }
}

/* The gutter's last step. 24px holds from 768px down, which on a 320px screen
   spends 15% of the width on margin — and the homepage headline is measured
   against what is left, so the gutter comes straight off the type.

   It sits after the 768px block on purpose: both set --edge, so the later one
   has to be the narrower. Pages that step down at their own breakpoint bound
   themselves above 600px rather than repeating this. */
@media (max-width: 600px) {
  :root { --edge: 16px; }
}

@media (prefers-reduced-motion: reduce) {
  .nav { transition: opacity 140ms linear; }
  .is-tucked { transform: none; }
  .wordmark-swap { transition-duration: 1ms; }
  .wordmark-tail span,
  .wordmark.is-open .wordmark-tail-rest span,
  .wordmark.is-open .wordmark-tail-hover span {
    transform: none;
    filter: none;
    transition: opacity 200ms linear var(--d);
  }
  .theme-toggle .cut,
  .theme-toggle .rays,
  .theme-toggle .body { transition-duration: 1ms; }
  .cursor-dot { transition-duration: 1ms; }
  .nav-thumb { transition-duration: 1ms, 1ms, 120ms; }
  .tip, .tip-layer { transition-duration: 1ms; }
  .contact-pill-thumb { transition-duration: 1ms, 1ms, 120ms; }
}
