html {
  /* Reserve the scrollbar track from the first paint. The loader sets
     overflow:hidden on the body, so without this the bar appears when it
     releases and the whole page jumps left by its width. Also keeps the
     gutter consistent across page transitions between long and short
     pages. */
  scrollbar-gutter: stable;
}
@supports not (scrollbar-gutter: stable) {
  html { overflow-y: scroll; }
}
html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
html, body { background: #ffffff; font-family: 'Figtree', sans-serif; color: #141414; }

/* ─── FOOTER (shared) ──────────────────────────── */

/* ─── FOOTER: STARFIELD ─────────────────────────────
   A long exposure of the night sky. Nothing is drawn as a "trail": the
   lower canvas is never cleared, only washed with a nearly transparent
   background each frame, so the dots a star leaves behind merge into a
   hairline arc while the oldest paint slowly disappears. That is what an
   exposure is, and it is why this reads as photographed rather than drawn.

   The sky is the site's text black, #141414. */
.footer { position: relative; height: 420px; overflow: hidden; background: #141414;
  font-family: 'Figtree', sans-serif; }

/* Two layers. Trails accumulate on the lower canvas; the upper one is
   cleared every frame so a glyph can ride the head of each arc without
   smearing into it. One canvas cannot do both. */
.footer canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

.footer-grain { position: absolute; inset: 0; pointer-events: none; opacity: 0.05; mix-blend-mode: screen;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23g)'/%3E%3C/svg%3E"); }

.footer-inner { position: relative; z-index: 2; height: 100%; padding: 44px 48px 22px;
  display: flex; flex-direction: column; justify-content: space-between; }
.footer-row { display: flex; justify-content: flex-end; }

.footer-links { display: flex; flex-direction: column; gap: 16px; text-align: right; }
.footer-link { color: var(--ink-faint); font-size: 14px; text-decoration: none;
  transition: color 0.2s ease; }
.footer-link:hover { color: #ffffff; }
/* Résumé's arrow: the navbar's .nav-link-arrow hover, same values. Inline
   rather than flex so the link stays plain text in the right-aligned column;
   the 4px gap arrives with the arrow (margin, not a flex gap), so at rest the
   text sits flush with the other links' edge. */
.footer-link-arrow { display: inline-block; vertical-align: middle; width: 0; margin-left: 0; opacity: 0; overflow: hidden; transform: translate(-2px, 2px); transition: opacity 0.2s ease, transform 0.2s cubic-bezier(0.23,1,0.32,1), width 0.2s ease, margin-left 0.2s ease; }
.footer-link-arrow svg { display: block; width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.footer-link:hover .footer-link-arrow { opacity: 1; width: 11px; margin-left: 4px; transform: translate(0,0); }
@media (prefers-reduced-motion: reduce) {
  .footer-link-arrow { transition: opacity 0.2s ease; transform: none; }
}

.footer-base { display: flex; justify-content: space-between; align-items: center; gap: 24px;
  padding-top: 16px; }
    /* Same ink as the links opposite, so the two ends of the row read as a pair. */
.footer-name { color: var(--ink-faint); font-size: 14px; }

.footer-socials { display: flex; gap: 8px; }
/* Filled, not transparent: the sky runs behind the whole footer, and stars
   tracking through a button read as dirt on the glass. The fill is the sky's
   own colour, so it still sits on the same surface. */
.social-btn { width: 34px; height: 34px; border-radius: var(--r-control, 8px);
  background: #141414;
  display: inline-flex; align-items: center;
  justify-content: center; color: var(--ink-faint); text-decoration: none;
  transition: color 0.2s ease; }
.social-btn:hover { color: #ffffff; }
.social-btn > svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round; }
.social-btn > svg.solid { fill: currentColor; stroke: none; }

@media (max-width: 760px) {
  .footer { height: 340px; }
  .footer-inner { padding: 32px 26px 20px; }
}

/* The tagline and its ASCII camera, carried over from the previous footer:
   top left, opposite the links. footer.js strikes the light when the footer
   scrolls into view and toggles it on click. */
/* The tagline takes the picture with the camera: an overexposed flash, the
   shutter's blink (the viewfinder blacks out for an instant), then it opens
   and settles into the steady lit state. 0.6s, so it lands with the shutter
   sound rather than trailing after it. Runs once with `forwards` and holds
   the lit state — no perpetual motion in the periphery. */
@keyframes tagline-strike {
  0%   { opacity: 0.28; }
  7%   { opacity: 1; }
  18%  { opacity: 0.12; }
  30%, 100% { opacity: 1; }
}
/* ASCII camera in place of the bulb. Mono because it is drawn out of
   characters — the one place on the site where the glyphs themselves are
   the picture. Only the flash character lights; the body stays put. */
.footer-cam { font-family: 'DM Mono', monospace; font-size: 18px; letter-spacing: 0.5px; color: #ffffff; margin-right: 9px; vertical-align: -1px; white-space: nowrap; }
.footer.is-struck .footer-cam .cam-flash { animation: flash-strike 0.6s cubic-bezier(0.23, 1, 0.32, 1) forwards; }
/* The flash fires once: a white pop, dim while the shutter is closed,
   then back to steady white. */
@keyframes flash-strike {
  0%   { color: var(--ink-soft); }
  7%   { color: #ffffff; }
  18%  { color: var(--ink-soft); }
  30%, 100% { color: #ffffff; }
}
.footer-statement { appearance: none; -webkit-appearance: none; background: none; border: 0; padding: 0; margin: 0; text-align: left; cursor: pointer;
  font-family: 'Figtree', sans-serif;
  color: #ffffff; opacity: 0.28;
  font-size: 18px;
  font-weight: 500; letter-spacing: -0.008em; line-height: 1.4; }
.footer.is-struck .footer-statement { animation: tagline-strike 0.6s cubic-bezier(0.23, 1, 0.32, 1) forwards; }
@media (prefers-reduced-motion: reduce) {
  .footer-statement { animation: none; opacity: 1; }
  .footer-cam .cam-flash { animation: none; }
}
/* margin-right:auto (on .footer-lead, below) pushes it left against
   .footer-row's flex-end, so the design's row rule stays as written;
   align-self keeps it its own height rather than stretching to the links
   column (a stretched button centres its text). The -2px lifts the tagline's capitals level with the
   links' (measured: its 18px/1.4 line puts caps 2px lower than the links'
   ones — whose cap tops land at the same spot at 13px and 16px, so 14px too, though both boxes start at the same top). Re-measure if either
   size changes. */
.footer-statement { margin-top: -2px; }
/* The tagline and the exposure counter under it form the left column. The
   push-left and no-stretch moved from the button to this wrapper. */
.footer-lead { margin-right: auto; align-self: flex-start; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
/* How long the current exposure has been open, like a camera's bulb timer.
   Mono: a machine readout. footer.js ticks it only while the sky is running
   and zeroes it when the tagline starts a new exposure. */
.footer-exposure { font-family: 'DM Mono', monospace; font-size: 13px; font-weight: 400; letter-spacing: 0; color: var(--ink-faint); }
