/* ============================================================================
   BASE — reset, typography, layout primitives, and the two chrome elements
   (header, footer) that every page shares.

   Nothing here is page-specific. If a rule only applies to the home page it
   belongs in home-*.css; if it only applies to the archive it belongs in
   archive.css.
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Keep the content grid and fixed header on the same x-coordinate when
     navigating between a long page and a page that does not need a scrollbar. */
  scrollbar-gutter: stable;
  /* The hero pins the sticker field to the viewport, so the page must not
     rubber-band horizontally on trackpads. */
  overflow-x: hidden;
  /* Every anchor on this page lands underneath a header that is fixed over the
     top of it. Without this, following #characters parks the section's kicker
     behind the bar; worse, a Tab that walks focus off the bottom of the
     viewport scrolls it to the very top, which is the one place the bar
     covers — so the focus ring ends up somewhere the reader cannot see, with
     nothing on screen to say where.

     Sized off the stuck bar rather than the taller resting one: nothing
     follows an anchor without scrolling first, and by the time it lands the
     bar has already shrunk. --s3 on top so the target reads as landed rather
     than as jammed up against the rule. */
  scroll-padding-top: calc(var(--hd-h) + var(--s3));
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--body);
  font-size: var(--t-sm);
  line-height: var(--lh-normal);
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

/* The night panel and the modals lock the page. Doing it with a class rather
   than inline style means only one owner can lock and unlock it. */
body.is-locked { overflow: hidden; }

img, svg, video { display: block; max-width: 100%; }
img { -webkit-user-drag: none; user-select: none; }

button, input, textarea, select {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
}
button { cursor: pointer; }
a { color: inherit; text-decoration: none; }

/* Custom pointer set. Kept behind a fine-pointer query so touch devices never
   download or attempt to use cursor artwork. Text entry and drag surfaces set
   their own semantic cursors later in the cascade and remain unchanged. */
@media (hover: hover) and (pointer: fine) {
  html,
  body {
    cursor: url('../cursor/point.png?v=20260829-3') 2 2, auto;
  }

  a[href],
  button:not(:disabled):not([aria-disabled="true"]),
  [role="button"]:not([aria-disabled="true"]),
  summary,
  label[for],
  input:is([type="button"], [type="submit"], [type="reset"], [type="checkbox"], [type="radio"], [type="file"]) {
    cursor: url('../cursor/click.png?v=20260829-3') 2 2, pointer;
  }

  /* Empty space inherits the resting pointer, so briefly swap only that
     inherited cursor while the mouse is held. Controls with a semantic cursor
     of their own—text fields, disabled buttons, drag rails—still override it. */
  html.is-pointer-active,
  html.is-pointer-active body {
    cursor: url('../cursor/click.png?v=20260829-3') 2 2, pointer;
  }

}

/* Fine-pointer feedback stays native-fast. Cursor echoes answer a deliberate
   shake; the click burst marks the exact press without changing layout or
   holding another animation layer between interactions. */
.cursor-echo {
  position: fixed;
  width: 48px;
  height: 48px;
  max-width: none;
  pointer-events: none;
  z-index: var(--z-cursor);
  will-change: opacity, transform;
}
.cursor-burst {
  position: fixed;
  left: 0;
  top: 0;
  width: 1px;
  height: 1px;
  max-width: none;
  color: var(--accent);
  pointer-events: none;
  z-index: var(--z-cursor);
}
.cursor-burst__ray {
  position: absolute;
  display: block;
  background: currentColor;
  border-radius: var(--r-pill);
  left: -1.5px;
  top: -12px;
  width: 3px;
  height: 12px;
  transform-origin: 50% 100%;
}
@media (prefers-reduced-motion: reduce), (hover: none), (pointer: coarse) {
  .cursor-echo,
  .cursor-burst { display: none; }
}

/* One focus treatment for the entire site. Never removed, only restyled. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}
.on-night :focus-visible { outline-color: var(--marker-1); }

::selection { background: var(--accent); color: var(--on-accent); }

/* ------------------------------------------------------------------ layout */
.wrap      { width: 100%; max-width: var(--page);      margin-inline: auto; padding-inline: var(--gutter); }
.wrap-wide { width: 100%; max-width: var(--page-wide); margin-inline: auto; padding-inline: var(--gutter); }

/* Section rhythm. Sections set padding-block ONLY — the `padding` shorthand is
   banned in section rules because it silently wipes the container's horizontal
   padding and drags the content out of the grid. */
.sec {
  padding-block: var(--s8);
  position: relative;
}
.sec--tight { padding-block: var(--s7); }

/* Anchor scripts move focus into the destination for keyboard and assistive
   technology users. A section itself is not an interactive control, so the
   global focus ring would read as a stray full-width rule rather than useful
   feedback. Its heading and contents remain the visible landing cue. */
.sec[tabindex="-1"]:focus { outline: none; }

/* The rule between sections is drawn by the section itself, so a section that
   wants to sit on its own colour can simply opt out. */
.sec--ruled::before {
  content: '';
  position: absolute; inset-inline: var(--gutter); top: 0;
  height: 1px; background: var(--line);
}

/* --------------------------------------------------------------- typography */
h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 700;
  line-height: var(--lh-snug);
  letter-spacing: var(--tr-snug);
  text-wrap: balance;
}

.t-mega { font-size: var(--t-mega); line-height: var(--lh-tight); letter-spacing: var(--tr-tight); font-weight: 800; }
.t-xl   { font-size: var(--t-xl);   line-height: var(--lh-tight); letter-spacing: var(--tr-tight); font-weight: 800; }
.t-lg   { font-size: var(--t-lg);   line-height: var(--lh-snug);  letter-spacing: var(--tr-snug);  font-weight: 700; }
.t-md   { font-size: var(--t-md);   line-height: 1.25;            letter-spacing: var(--tr-snug);  font-weight: 600; }

.lead   { font-size: var(--t-md); line-height: 1.4; color: var(--ink-soft); font-weight: 400;
          letter-spacing: -.012em; max-width: 34ch; text-wrap: pretty; }
.prose  { max-width: var(--page-text); color: var(--dim); text-wrap: pretty; }
.prose strong { color: var(--ink); font-weight: 500; }

/* Quiet metadata in the same sans face as the body. Normal case and
   proportional figures keep labels from resembling terminal output. */
.mono {
  font-family: var(--mono);
  font-size: var(--t-mono);
  letter-spacing: var(--tr-mono);
  font-weight: 500;
  color: var(--dim);
  font-variant-numeric: proportional-nums;
}
.mono--ink { color: var(--ink); }

/* Handwriting. Used for at most one phrase per section — it is seasoning. */

/* ------------------------------------------------------- section openers
   Section titles and optional supporting information share one alignment. */
.sec-hd { display: flex; align-items: flex-end; gap: var(--s4);
          flex-wrap: wrap; margin-bottom: var(--s6); }
.sec-hd__t   { flex: 1 1 20ch; min-width: 0; }
.sec-hd__num { display: block; margin-bottom: var(--s3); }
.sec-hd__aside { margin-left: auto; padding-bottom: .4em; text-align: right; }

/* ---------------------------------------------------------------- controls */
.btn {
  display: inline-flex; align-items: center; gap: var(--s2);
  border-radius: var(--r-pill);
  padding: 11px 22px;
  font-size: var(--t-xs); font-weight: 500;
  border: 1px solid var(--line-2);
  transition: border-color var(--t-hover) var(--ease-out),
              color       var(--t-hover) var(--ease-out),
              background  var(--t-hover) var(--ease-out),
              transform   var(--t-hover) var(--ease-out);
}
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn:active { transform: scale(.97); }

.btn--solid { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.btn--solid:hover { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

.btn--ghost { border-color: transparent; padding-inline: 4px; }
.btn--ghost:hover { border-color: transparent; }

/* Placeholder controls — the social entries that have no URL yet.

   The markup already does most of the work: an <a> with no href and
   aria-disabled="true" does not navigate, does not push a `#` into history,
   takes the text cursor rather than the hand, and is skipped by the tab order
   and by the night panel's focus trap for free.

   What markup cannot do is stop button styling. `.btn:hover` and `.btn:active`
   fire regardless, so disabled buttons stay visually inert. The footer's
   standalone social glyphs are the deliberate exception: they may preview the
   site's accent colour without pretending to be filled controls. */
[aria-disabled="true"] { cursor: not-allowed; }
.btn[aria-disabled="true"]:hover  { border-color: var(--line-2); color: inherit;
                                    background: none; }
.btn[aria-disabled="true"]:active { transform: none; }

@media (hover: hover) and (pointer: fine) {
  button:disabled,
  [aria-disabled="true"] {
    cursor: url('../cursor/disabled.png?v=20260829-3') 24 24, not-allowed;
  }
}

/* An arrow that leans forward when its control is hovered. The arrow is a
   separate span so the label itself never moves — moving text is unreadable
   for the ~200ms it takes. */
.btn__arw { transition: transform var(--t-hover) var(--ease-out); }
.btn:hover .btn__arw, a:hover > .btn__arw { transform: translateX(4px); }

/* One solid compositor layer replaces the root View Transition's two raster
   snapshots. It is created lazily on the first theme change and never handles
   input. The explicit hidden rule keeps its fixed box out of layout at rest. */
.theme-veil {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-cursor) + 1);
  pointer-events: none;
  opacity: 0;
  contain: strict;
  will-change: opacity;
}
.theme-veil[hidden] { display: none; }
.theme-veil.is-covering {
  animation: theme-veil-in var(--t-theme-in) cubic-bezier(.4, 0, 1, 1) forwards;
}
.theme-veil.is-revealing {
  animation: theme-veil-out var(--t-theme-out) cubic-bezier(0, 0, .2, 1) forwards;
}
@keyframes theme-veil-in  { from { opacity: 0; } to { opacity: var(--theme-veil-peak); } }
@keyframes theme-veil-out { from { opacity: var(--theme-veil-peak); } to { opacity: 0; } }

/* The footer drawings advance in discrete frames, so pause them while the
   theme veil swaps the palette. */
html.theme-changing .ft__mark-frame,
html.theme-changing .ft__mark.is-animated::after {
  animation-play-state: paused;
}

/* ------------------------------------------------------------------ header
   Sits above the hero without a background at rest so the sticker field runs
   underneath it. It acquires an opaque surface as soon as the page leaves the
   top, before any content can pass behind its controls. */
site-header { display: contents; }
.hd {
  position: fixed; inset-inline: 0; top: 0;
  z-index: var(--z-sticky);
  transition: transform var(--t-move) var(--ease-out);
}
.hd__in {
  position: relative;
  width: 100%; max-width: var(--page-wide); margin-inline: auto;
  height: calc(var(--hd-h) - 1px);
  padding: var(--s3) var(--gutter);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--s5);
}
.hd.is-stuck {
  /* The header remains unpainted at the top. Once content travels underneath,
     a compact frosted surface separates the controls without turning the bar
     into a second solid section. */
  background: color-mix(in srgb, var(--paper) 84%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--line-2) 72%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.08);
          backdrop-filter: blur(14px) saturate(1.08);
}
/* Scrolling down is reading; the header gets out of the way and comes back the
   moment you scroll up. */
.hd.is-hidden { transform: translateY(-101%); }
.hd.has-contact-open,
.hd.has-mobile-open { transform: translateY(0); }

.hd__menu-trigger { display: none; }

.hd__id { display: flex; align-items: center; gap: var(--s3); justify-self: start; }
.hd__logo {
  width: var(--s5);
  height: var(--s5);
  flex: none;
  background: var(--ink);
  -webkit-mask: url('../nav-logo.svg?v=20260830-3') center / contain no-repeat;
          mask: url('../nav-logo.svg?v=20260830-3') center / contain no-repeat;
  transition: background var(--t-micro) linear,
              transform var(--t-hover) var(--ease-out);
}
.hd__name {
  font-family: var(--display);
  font-size: 22px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.025em;
}
.hd__wordmark {
  display: flex;
  align-items: center;
  gap: var(--s2);
}
.hd__status {
  position: relative;
  flex: none;
  padding: 2px 4px;
  color: var(--ink-soft);
  font-family: var(--display);
  font-size: var(--t-mono);
  font-weight: 700;
  line-height: 1;
  letter-spacing: .06em;
  transform: rotate(-2deg);
}
.hd__status::after {
  content: '';
  position: absolute;
  inset: -2px -4px;
  border: 1.5px solid var(--marker-1);
  border-radius: 52% 46% 55% 48%;
  transform: rotate(-3deg);
  pointer-events: none;
}
.hd__id:focus-visible .hd__logo { background: var(--accent); }
.hd__id:active .hd__logo { transform: scale(.965); }
@media (hover: hover) and (pointer: fine) {
  .hd__id:hover .hd__logo { background: var(--accent); }
}

.hd__nav { display: flex; align-items: center; justify-self: center; }
.hd__utils {
  display: flex;
  align-items: center;
  justify-self: end;
  gap: var(--s2);
}
.hd__utils > :is(.btn, .theme-toggle) {
  height: var(--control-h);
}
.hd__utils > .btn {
  position: relative;
  padding-block: 0;
}
/* The painted controls stay compact, but their elements still claim a 44px
   vertical pointer/touch target. This keeps Contact and the switch visually
   identical in height without making either one fiddly on a small screen. */
.hd__utils > :is(.btn, .theme-toggle)::before {
  content: '';
  position: absolute;
  inset-block: calc((var(--control-hit) - var(--control-h)) / -2);
  inset-inline: 0;
}

.hd__sections {
  height: 44px;
  display: flex;
  align-items: center;
  gap: var(--s5);
}

.theme-toggle {
  --toggle-travel: 30px;
  position: relative;
  flex: none;
  width: 70px;
  padding: 0;
  color: var(--ink-soft);
  transition: transform var(--t-toggle) var(--ease-out);
}
.theme-toggle__track {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border: 1px solid var(--line-2);
  border-radius: var(--r-pill);
  background: var(--paper-2);
  transition: background var(--t-toggle) var(--ease-out),
              border-color var(--t-toggle) var(--ease-out),
              box-shadow var(--t-toggle) var(--ease-out);
}
.theme-toggle__icon {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 1;
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--paper);
  transition: opacity var(--t-toggle) var(--ease-io),
              transform var(--t-toggle) var(--ease-io);
}
.theme-toggle__icon--sun {
  opacity: 1;
  transform: rotate(0) scale(1);
}
.theme-toggle__icon--moon {
  opacity: 0;
  transform: rotate(80deg) scale(.45);
}
.theme-toggle__thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--ink);
  box-shadow: var(--lift-1);
  transition: transform var(--t-toggle) var(--ease-io),
              background var(--t-toggle) linear,
              box-shadow var(--t-toggle) var(--ease-out);
}
.theme-toggle[aria-checked="true"] .theme-toggle__thumb {
  transform: translateX(var(--toggle-travel));
}
.theme-toggle[aria-checked="true"] .theme-toggle__icon--sun {
  opacity: 0;
  transform: rotate(-80deg) scale(.45);
}
.theme-toggle[aria-checked="true"] .theme-toggle__icon--moon {
  opacity: 1;
  transform: rotate(0) scale(1);
}
@media (hover: hover) and (pointer: fine) {
  .theme-toggle:hover .theme-toggle__track {
    border-color: var(--accent);
    background: var(--accent-wash);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent);
  }
  .theme-toggle:hover .theme-toggle__thumb { box-shadow: var(--lift-2); }
}

/* Mobile navigation is a compact extension of the masthead, not a separate
   full-screen scene. It remains inert outside the mobile breakpoint and while
   closed, so its duplicate Work/Play links never enter the tab order twice. */
.mobile-menu { display: none; }
.theme-toggle:active { transform: translateY(0) scale(.98); }

/* The contact control opens one compact route-picker instead of silently
   treating a public social profile as the only way to start a project. It is
   part of the shared header so every page exposes the same three real routes;
   page-level CTAs can open it with data-contact-trigger. */
.contact-menu {
  position: absolute;
  top: calc(100% + var(--s2));
  right: var(--gutter);
  width: min(380px, calc(100vw - var(--gutter) * 2));
  padding: var(--s3);
  display: grid;
  gap: var(--s2);
  border-radius: var(--r-2);
  background: var(--paper);
  box-shadow: var(--lift-3);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translate3d(0, -6px, 0);
  transition: opacity var(--t-hover) var(--ease-out),
              transform var(--t-hover) var(--ease-out),
              visibility 0s linear var(--t-hover);
}
.contact-menu.is-open {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate3d(0, 0, 0);
  transition-delay: 0s;
}
.contact-menu__option {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--s3);
  min-height: 68px;
  padding: var(--s3);
  border-radius: var(--r-1);
  background: var(--paper-2);
  transition: color var(--t-hover) var(--ease-out),
              background var(--t-hover) var(--ease-out);
}
.contact-menu__option strong,
.contact-menu__option small { display: block; }
.contact-menu__option strong {
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: var(--t-sm);
  font-weight: 500;
}
.contact-menu__option small {
  margin-bottom: var(--s1);
  color: var(--dim);
  font-size: var(--t-mono);
}
.contact-menu__arrow {
  color: var(--faint);
  transition: transform var(--t-hover) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .contact-menu__option:hover {
    color: var(--accent);
    background: var(--accent-wash);
  }
  .contact-menu__option:hover .contact-menu__arrow {
    transform: translate(3px, -3px);
  }
}

/* The scrolled bar is translucent and can pass over full-colour artwork, so
   its labels use the stronger secondary ink rather than the quieter metadata
   tone. Readability comes from the text token; the glass can remain visibly
   translucent instead of becoming an opaque bar with an idle blur behind it. */
.hd__link {
  position: relative;
  height: 44px;
  padding-inline: var(--s1);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink-soft);
  font-family: var(--display);
  font-size: 18px;
  font-weight: 600;
  transition: color var(--t-micro) linear;
}
.hd__link::after {
  content: '';
  position: absolute;
  inset-inline: 1px;
  bottom: 2px;
  height: 7px;
  background: var(--marker-2);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 8'%3E%3Cpath d='M2 5C19 2.5 43 5.8 63 4.2C77 3.1 88 2.8 98 3.5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 8'%3E%3Cpath d='M2 5C19 2.5 43 5.8 63 4.2C77 3.1 88 2.8 98 3.5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
  opacity: 0;
  transform: scaleX(.68) rotate(-.6deg);
  transform-origin: 0 50%;
  transition: opacity var(--t-micro) linear,
              transform var(--t-hover) var(--ease-out);
}
.hd__link[aria-current] { color: var(--ink); }
.hd__link[aria-current]::after { opacity: 1; transform: scaleX(1) rotate(-.6deg); }
@media (hover: hover) and (pointer: fine) {
  .hd__link:hover { color: var(--ink); }
  .hd__link:hover::after { opacity: 1; transform: scaleX(1) rotate(-.6deg); }
}

/* The centred portfolio links leave on small screens rather than collapsing
   into a menu for two destinations. Identity, theme and Contact remain. */
@media (max-width: 780px) {
  .hd__nav { display: none; }
  .hd__in {
    grid-template-columns: auto minmax(0, 1fr) auto;
    gap: var(--s2);
  }

  .hd__menu-trigger {
    position: relative;
    width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    justify-self: start;
    color: var(--ink);
    border-radius: var(--r-pill);
    transition: color var(--t-micro) linear,
                background var(--t-hover) var(--ease-out),
                transform var(--t-hover) var(--ease-out);
  }
  .hd__menu-trigger:active { transform: scale(.96); }
  .hd__menu-trigger[aria-expanded="true"] { background: var(--paper-2); }

  .hd__menu-icon,
  .hd__menu-icon::before,
  .hd__menu-icon::after {
    width: 22px;
    height: 2px;
    display: block;
    border-radius: var(--r-pill);
    background: currentColor;
    transition: transform var(--t-hover) var(--ease-out),
                opacity var(--t-micro) linear;
  }
  .hd__menu-icon { position: relative; }
  .hd__menu-icon::before,
  .hd__menu-icon::after {
    content: '';
    position: absolute;
    left: 0;
  }
  .hd__menu-icon::before { transform: translateY(-7px); }
  .hd__menu-icon::after  { transform: translateY(7px); }
  .hd__menu-trigger[aria-expanded="true"] .hd__menu-icon { background: transparent; }
  .hd__menu-trigger[aria-expanded="true"] .hd__menu-icon::before { transform: rotate(45deg); }
  .hd__menu-trigger[aria-expanded="true"] .hd__menu-icon::after  { transform: rotate(-45deg); }

  .mobile-menu {
    position: absolute;
    top: calc(100% + var(--s2));
    left: var(--gutter);
    right: var(--gutter);
    z-index: var(--z-float);
    padding: var(--s4);
    display: grid;
    gap: var(--s3);
    border: 1px solid color-mix(in srgb, var(--line-2) 72%, transparent);
    border-radius: var(--r-2);
    background: color-mix(in srgb, var(--paper) 92%, transparent);
    box-shadow: var(--lift-3);
    -webkit-backdrop-filter: blur(16px) saturate(1.08);
            backdrop-filter: blur(16px) saturate(1.08);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translate3d(0, -8px, 0);
    transition: opacity var(--t-hover) var(--ease-out),
                transform var(--t-hover) var(--ease-out),
                visibility 0s linear var(--t-hover);
  }
  .mobile-menu.is-open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translate3d(0, 0, 0);
    transition-delay: 0s;
  }
  .mobile-menu__link {
    position: relative;
    min-height: 56px;
    display: flex;
    align-items: center;
    width: fit-content;
    padding-inline: var(--s1);
    color: var(--ink-soft);
    font-family: var(--display);
    font-size: clamp(30px, 8vw, 42px);
    font-weight: 700;
    line-height: 1;
    letter-spacing: var(--tr-snug);
  }
  .mobile-menu__link::after {
    content: '';
    position: absolute;
    inset-inline: var(--s1);
    bottom: 2px;
    height: 8px;
    background: var(--marker-2);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 8'%3E%3Cpath d='M2 5C19 2.5 43 5.8 63 4.2C77 3.1 88 2.8 98 3.5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
            mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 8'%3E%3Cpath d='M2 5C19 2.5 43 5.8 63 4.2C77 3.1 88 2.8 98 3.5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
    opacity: 0;
    transform: scaleX(.68) rotate(-.6deg);
    transform-origin: 0 50%;
    transition: opacity var(--t-micro) linear,
                transform var(--t-hover) var(--ease-out);
  }
  .mobile-menu__link[aria-current] { color: var(--ink); }
  .mobile-menu__link[aria-current]::after {
    opacity: 1;
    transform: scaleX(1) rotate(-.6deg);
  }
}

@media (max-width: 480px) {
  .contact-menu {
    left: var(--gutter);
    right: var(--gutter);
    width: auto;
    transform-origin: 100% 0;
  }
}

@media (max-width: 520px) {
  .hd__wordmark { display: none; }
}

/* ------------------------------------------------------------------ footer
   A compact invitation and a ruled contact directory. The animated mark is
   the character beat; the layout around it stays calm and architectural. */
.ft {
  background: var(--night);
  color: var(--on-night);
}
.ft__in {
  padding-block: var(--s6) max(var(--s7), env(safe-area-inset-bottom));
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(340px, 1fr);
  align-items: start;
  gap: clamp(var(--s5), 5vw, var(--s7));
}
.ft__lead {
  display: flex;
  flex-direction: column;
  align-items: start;
  gap: var(--s3);
  min-width: 0;
}
/* The navigation and footer share one transparent SVG source. The footer
   resolves to this mark after its four hand-drawn transition frames. */
.ft__mark {
  position: relative;
  display: block;
  flex: none;
  width: 88px;
  height: 88px;
  padding: 0;
  color: var(--on-night);
  overflow: visible;
  touch-action: manipulation;
  transition: color var(--t-micro) linear;
}
.ft__mark::after {
  content: '';
  position: absolute;
  inset: 0;
  background: currentColor;
  -webkit-mask: url('../nav-logo.svg?v=20260830-3') center / contain no-repeat;
          mask: url('../nav-logo.svg?v=20260830-3') center / contain no-repeat;
}
.ft__mark-art {
  width: 100%;
  height: 100%;
  overflow: visible;
}
.ft__mark-frame { opacity: 0; }
.ft__mark.is-animated::after { opacity: 0; }
.ft__mark[data-frame='one'] .ft__mark-frame--1,
.ft__mark[data-frame='two'] .ft__mark-frame--2,
.ft__mark[data-frame='three'] .ft__mark-frame--3,
.ft__mark[data-frame='four'] .ft__mark-frame--4,
.ft__mark[data-frame='final']::after { opacity: 1; }
.ft__cta {
  display: grid;
  max-width: 640px;
}
.ft__contacts {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  min-width: 0;
  font-style: normal;
}
.ft__contact {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: auto auto;
  align-items: center;
  gap: var(--s1) var(--s3);
  min-width: 0;
  padding-block: var(--s3);
  border-top: 1px solid var(--line-night);
  color: var(--on-night);
}
.ft__contact--email { grid-column: auto; }
.ft__contact-label {
  color: var(--dim-night);
  font-size: var(--t-mono);
  letter-spacing: .04em;
}
.ft__contact-value {
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: clamp(17px, 1.45vw, 22px);
  line-height: var(--lh-snug);
  transition: color var(--t-micro) linear;
}
.ft__contact-arw {
  grid-column: 2;
  grid-row: 1 / -1;
  align-self: center;
  font-size: var(--t-md);
  transition: color var(--t-micro) linear,
              transform var(--t-hover) var(--ease-out);
}
.ft__mark:focus-visible,
.ft__contact:focus-visible { color: var(--accent-lift); }
.ft__contact:focus-visible .ft__contact-arw { transform: translate(3px, -3px); }

@media (hover: hover) and (pointer: fine) {
  .ft__mark:hover {
    color: var(--accent-lift);
  }
  .ft__contact:hover .ft__contact-value,
  .ft__contact:hover .ft__contact-arw { color: var(--accent-lift); }
  .ft__contact:hover .ft__contact-arw { transform: translate(3px, -3px); }
}

@media (max-width: 900px) {
  .ft__in {
    padding-block: var(--s5) max(var(--s6), env(safe-area-inset-bottom));
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s5);
  }
}

@media (max-width: 700px) {
  .ft__lead { gap: var(--s3); }
  .ft__mark { width: 64px; height: 64px; }
  .ft__contacts { grid-template-columns: minmax(0, 1fr); }
  .ft__contact--email { grid-column: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .ft__mark.is-animated .ft__mark-art { display: none; }
  .ft__mark.is-animated::after { opacity: 1; }
}

/* ----------------------------------------------------------------- utility */
.vh {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
/* Skip link — visible only when focused, which is the one time it matters. */
.skip {
  position: fixed; top: var(--s3); left: var(--s3); z-index: 300;
  background: var(--ink); color: var(--paper);
  padding: 10px 18px; border-radius: var(--r-pill); font-size: var(--t-xs);
  transform: translateY(-200%);
  transition: transform var(--t-hover) var(--ease-out);
}
.skip:focus-visible { transform: none; }

.stack > * + * { margin-top: var(--s3); }
.nowrap { white-space: nowrap; }
