/* ============================================================================
   BLINDHACK
   One stylesheet. No framework, no bundler, no utility classes.

   THE IDEA: "the eye adjusts to the dark."
   The company is called Blindhack, the mark is an eye, and the site's own
   description says they are "probably hacking deep into the night". So the page
   is genuinely dark, and everything on it RESOLVES rather than appears. Three
   expressions, and nothing outside them:

     THE PUPIL     the mark watches the pointer, and blinks. Rarely.
     THE RESOLVE   nothing appears; things arrive. Text rises out of a mask,
                   images and hairlines open from their centre like an iris.
                   Same easing, same duration, so it reads as one surface.
     THE PAN       the work moves past you as you SCROLL. Nothing runs on a
                   clock, so a page standing still costs nothing to composite.

   THE PALETTE IS MAX'S OWN. These are the six custom properties he declared in
   the Webflow stylesheet, kept exactly. Contrast for every pairing is computed
   in tools/contrast.py, and the numbers decide the semantics rather than taste:

     bone on ink    8.50:1  AAA    -> all text
     green on ink   5.06:1  AA     -> every interactive thing (and it is the eye)
     red on ink     3.78:1  large  -> DISPLAY ONLY. Never small text, and never
                                      on bone, where it measures 2.25:1.
   ============================================================================ */

/* --------------------------------------------------------------- 1. tokens */
:root {
  --ink:        #2b2b2e;   /* --black      the ground                        */
  --indigo:     #363457;   /* --darkblue   the bar, and one band             */
  --bone:       #c8c8d2;   /* --white      text, faintly cool                */
  --red:        #f24333;   /* --lightred   the accent                        */
  --red-dark:   #942911;
  --green:      #7ba48c;   /* --lightgreen the mark, and every link          */
  --green-dark: #3f6c51;

  --ink-lift:   #313135;   /* one step up from the ground, for cards         */
  --ink-deep:   #232326;   /* one step down, for the wells                   */

  /* MUTED TEXT IS GROUND-DEPENDENT, and the percentages below are measured by
     tools/contrast.py rather than chosen. The minimum that clears 4.5:1 for
     body-size text is 65% on ink, 66% on ink-deep and 74% on indigo - so one
     global value is either illegal on the indigo band or pointlessly bright on
     the other two. The bands re-declare the tokens; every consumer just uses
     var(--bone-faint) and gets the right number for where it is sitting.

     The first version of this shipped one 54% token everywhere, which axe found
     at 3.4:1 on thirteen-pixel text. A muted colour is not a taste decision.
     NEVER stack `opacity` on top of these either - it compounds, and that is
     how a label ends up at 1.87:1 while every individual number looks fine. */
  --mute-pct:  80%;
  --faint-pct: 70%;
  --bone-mute:  color-mix(in srgb, var(--bone) var(--mute-pct), transparent);
  --bone-faint: color-mix(in srgb, var(--bone) var(--faint-pct), transparent);
  --rule:       color-mix(in srgb, var(--bone) 16%, transparent);
  --rule-soft:  color-mix(in srgb, var(--bone) 9%, transparent);

  --display: antarctican-headline, "Arial Narrow", system-ui, sans-serif;
  --text:    elza-text, system-ui, -apple-system, "Segoe UI", sans-serif;

  /* one fluid type scale; no media query anywhere sets a font size */
  --t-hero:  clamp(3.4rem, 12vw, 10.5rem);
  --t-h1:    clamp(2.4rem, 6.4vw, 5.2rem);
  --t-h2:    clamp(1.9rem, 4.2vw, 3.4rem);
  --t-h3:    clamp(1.15rem, 1.9vw, 1.5rem);
  --t-body:  clamp(1rem, 1.05vw, 1.125rem);
  --t-small: 0.8125rem;
  --t-micro: 0.6875rem;

  --gut:  clamp(1.25rem, 4vw, 3.5rem);
  --band: clamp(4.5rem, 11vh, 9rem);
  --wide: 78rem;
  --read: 46rem;

  /* one easing family; everything on the page uses these */
  --ease:     cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in:  cubic-bezier(0.55, 0, 0.68, 0.5);
  --dur:      0.58s;
  --dur-slow: 0.9s;
}

/* ------------------------------------------------------------- 2. baseline */
*, *::before, *::after { box-sizing: border-box; }

html {
  background: var(--ink);
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5.5rem;   /* the sticky bar must not cover a jump target */
}
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--ink);
  color: var(--bone);
  font: 400 var(--t-body)/1.65 var(--text);
  font-synthesis-weight: none;
  overflow-x: hidden;
}

h1, h2, h3, h4 { margin: 0; font-weight: 400; line-height: 0.95; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }
img, svg, video { display: block; max-width: 100%; }
img { height: auto; }
a { color: var(--green); text-decoration: none; }

:where(a, button, input, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--green);
  outline-offset: 3px;
  border-radius: 2px;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.skip {
  position: absolute; top: 0.5rem; left: 0.5rem; z-index: 100;
  transform: translateY(-250%);
  background: var(--green); color: var(--ink);
  padding: 0.6rem 1rem;
  font: 400 var(--t-small)/1 var(--text);
}
.skip:focus { transform: none; }

/* ---------------------------------------------------------- 3. the surface */
/* Grain, so the dark reads as a material rather than as #2b2b2e. It CARRIES
   ITS OWN ALPHA: feColorMatrix pins RGB to white and takes alpha from the red
   channel. The obvious way to do this is mix-blend-mode: overlay, and that one
   declaration measured 25ms a frame on the last build, because a blend layer
   re-composites whenever anything underneath it moves. Plain alpha, no blend. */
body::after {
  content: "";
  position: fixed; inset: 0; z-index: 60;
  pointer-events: none;
  opacity: 0.5;
  background-image: url("grain.svg");
  background-repeat: repeat;
}

.wrap { width: min(100% - var(--gut) * 2, var(--wide)); margin-inline: auto; }
.read { max-width: var(--read); }

section { position: relative; }
.band { padding-block: var(--band); }
/* each band restates the muted-text percentages measured for ITS ground */
.band--deep   { background: var(--ink-deep); --mute-pct: 80%; --faint-pct: 72%; }
.band--indigo { background: var(--indigo);   --mute-pct: 88%; --faint-pct: 78%; }

.rule {
  height: 1px; border: 0; margin: 0;
  background: var(--rule);
  transform-origin: 0 50%;
}

/* ----------------------------------------------------------- 4. the header */
.hdr {
  position: sticky; top: 0; z-index: 50;
  /* FULLY OPAQUE ON PURPOSE. The tempting version is 94% plus backdrop-filter;
     the blur measured -14.5ms on the last build and was invisible behind the
     fill anyway, and 94% without it ghosts page text through the bar. */
  background: var(--ink);
  border-bottom: 1px solid var(--rule-soft);
  transition: border-color var(--dur) var(--ease);
}
.hdr--stuck { border-bottom-color: var(--rule); }

.hdr__in { display: flex; align-items: center; gap: 1rem; min-height: 4.25rem; }
.hdr__home { display: flex; align-items: center; gap: 0.7rem; color: var(--bone); }
.hdr__mark { width: 1.85rem; height: 1.85rem; color: var(--green); flex: none; }
.hdr__name {
  font: 400 1.32rem/1 var(--display);
  letter-spacing: 0.16em; text-indent: 0.16em;
}

.nav { margin-left: auto; display: flex; align-items: center; gap: clamp(0.75rem, 2.4vw, 2.1rem); }
.nav a {
  position: relative;
  color: var(--bone-mute);
  font: 400 var(--t-small)/1 var(--display);
  letter-spacing: 0.17em; text-indent: 0.17em; text-transform: uppercase;
  padding-block: 0.55rem;
  white-space: nowrap;      /* "WHAT WE DO" wrapped to three lines at 390px */
  transition: color 0.3s var(--ease);
}

/* On a phone the wordmark and four nav items do not fit on one line, and the
   nav is the part that has to survive. The mark alone is unmistakable at this
   size, and the link keeps its accessible name either way. */
@media (max-width: 34rem) {
  .hdr__name { display: none; }
  .hdr__mark { width: 1.65rem; height: 1.65rem; }
  .nav a { font-size: 0.72rem; letter-spacing: 0.12em; text-indent: 0.12em; }
  .hero { min-height: min(94svh, 54rem); padding-block: 1.5rem var(--band); }
}
.nav a::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0.1rem; height: 1px;
  background: var(--green);
  transform: scaleX(0); transform-origin: 100% 50%;
  transition: transform 0.42s var(--ease);
}
.nav a:hover, .nav a:focus-visible, .nav a[aria-current] { color: var(--bone); }
.nav a:hover::after, .nav a:focus-visible::after, .nav a[aria-current]::after {
  transform: scaleX(1); transform-origin: 0 50%;
}
.nav a[aria-current]::after { background: var(--red); }

/* Scroll progress, handed to the compositor where the browser supports it.
   No JS fallback on purpose - it is pure enhancement, and a browser without
   scroll timelines simply does not get it. */
.hdr__progress {
  position: absolute; left: 0; bottom: -1px; height: 2px; width: 100%;
  background: var(--green);
  transform: scaleX(0); transform-origin: 0 50%;
}
@supports (animation-timeline: scroll()) {
  .hdr__progress {
    animation: bh-progress linear both;
    animation-timeline: scroll(root);
  }
}
@keyframes bh-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* ------------------------------------------------------------- 5. the mark */
.mark { width: 100%; height: 100%; }
.mark__eye { transform-origin: 319px 311px; }
.mark--live .mark__eye { will-change: transform; }

/* ---------------------------------------------------------------- 6. hero */
.hero {
  position: relative;
  min-height: min(100svh, 54rem);
  display: grid; align-content: center;
  padding-block: clamp(3rem, 9vh, 7rem) var(--band);
  overflow: clip;   /* clip, NOT hidden: hidden still makes a scroll container */
}

/* the brushstroke, low and wide behind the type - the only pictorial thing on
   the page, so it gets the only parallax */
.hero__splash {
  position: absolute; z-index: 0;
  left: 50%; top: 58%;
  width: min(150vw, 92rem); max-width: none;
  translate: -50% -50%;
  opacity: 0.5;
  pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    .hero__splash {
      animation: bh-splash linear both;
      animation-timeline: scroll(root);
      animation-range: 0 90vh;
    }
  }
}
@keyframes bh-splash { to { transform: translate3d(0, 14%, 0) scale(1.06); } }

.hero__in {
  position: relative; z-index: 1;
  display: grid; justify-items: center;
  gap: clamp(1.1rem, 2.6vh, 2rem);
}

.hero__mark { width: clamp(6.5rem, 15vh, 11rem); aspect-ratio: 1; color: var(--green); }

.hero__name {
  font: 400 var(--t-hero)/0.86 var(--display);
  letter-spacing: 0.045em; text-indent: 0.045em;
  color: var(--bone);
  text-align: center;
}

/* The verb rotator, kept from the original because it is the best idea on the
   old site - eight verbs instead of twelve, and it only ticks while the hero is
   actually on screen. */
.hero__line {
  display: flex; flex-wrap: wrap; justify-content: center;
  align-items: baseline; gap: 0.28em;
  font: 400 var(--t-h1)/1 var(--display);
  color: var(--bone);
}
/* The slot is as wide as the LONGEST verb and the word is centred in it, with
   the full stop travelling inside the span. Reserving the width stops the line
   reflowing on every swap; carrying the punctuation stops a lonely dot sitting
   two centimetres off the end of "make". */
.hero__verb {
  display: inline-grid;
  justify-items: center;
  color: var(--red);        /* 3.78:1 - display size only, which this is */
  min-width: 6.6ch;
}
.hero__verb > span { grid-area: 1 / 1; white-space: nowrap; }
.hero__verb > span[data-out] { animation: bh-verb-out 0.4s var(--ease-in) both; }
.hero__verb > span[data-in]  { animation: bh-verb-in 0.52s var(--ease) both; }
@keyframes bh-verb-out {
  to { clip-path: inset(0 0 100% 0); transform: translateY(-0.16em); opacity: 0; }
}
@keyframes bh-verb-in {
  from { clip-path: inset(100% 0 0 0); transform: translateY(0.2em); opacity: 0; }
  to   { clip-path: inset(-20% 0 -20% 0); transform: none; opacity: 1; }
}

.hero__sub { max-width: 36ch; text-align: center; color: var(--bone-mute); }

.hero__scroll {
  position: absolute; left: 50%; bottom: 1.6rem; translate: -50% 0; z-index: 1;
  display: grid; justify-items: center; gap: 0.5rem;
  color: var(--bone-faint);
  font: 400 var(--t-micro)/1 var(--display);
  letter-spacing: 0.3em; text-indent: 0.3em; text-transform: uppercase;
}
.hero__scroll i { display: block; width: 1px; height: 2.2rem; background: currentColor; transform-origin: 50% 0; }
@media (prefers-reduced-motion: no-preference) {
  .hero__scroll i { animation: bh-tick 2.6s var(--ease) infinite; }
}
@keyframes bh-tick {
  0%, 100% { transform: scaleY(0.35); opacity: 0.5; }
  45%      { transform: scaleY(1);    opacity: 1; }
}

/* ------------------------------------------------ 7. THE REVEAL: it resolves */
/* One family, two members, because one shape is wrong for both jobs:

     THE RISE    text comes up out of a mask. Reading is left-to-right and
                 top-to-bottom, and a headline that opens from its own middle
                 reads as a rendering fault rather than as an entrance. Every
                 word-bearing element gets this.
     THE IRIS    frames, images and hairlines open from their centre line -
                 the aperture, which is the mark's own gesture. These have no
                 reading order, so the symmetry is free.

   Same easing, same duration family, same clip-path mechanism: the page still
   reads as one surface, and neither element type is fighting its own content.

   The SAME keyframes are driven two ways:
     - where the browser has scroll-driven animations it is pure CSS on a view
       timeline, on the compositor, with zero JS involvement;
     - everywhere else motion.js adds .rv--on off one rAF loop.
   The timeline lives on a PARENT, never on the animating element: clip-path
   zeroes an element's own intersection rect, and driving a clip from its own
   visibility is the trap that leaves things invisible forever. */
.rv { --rv-delay: 0ms; }

@media (prefers-reduced-motion: no-preference) {
  .rv > .rv-i { clip-path: inset(-6% 0 100% 0); opacity: 0; transform: translateY(0.55rem); }
  .rv > .rv-a { clip-path: inset(0 50% 0 50%); opacity: 0; }
  .rv > .rule { transform: scaleX(0); }

  .rv--on > .rv-i { animation: bh-rise var(--dur-slow) var(--ease) var(--rv-delay) both; }
  .rv--on > .rv-a { animation: bh-iris 1.05s var(--ease) var(--rv-delay) both; }
  .rv--on > .rule { animation: bh-draw 1s var(--ease) var(--rv-delay) both; }

  @supports (animation-timeline: view()) {
    .rv:not(.rv--js) { view-timeline-name: --rv; view-timeline-axis: block; }
    .rv:not(.rv--js) > .rv-i {
      animation: bh-rise var(--dur-slow) var(--ease) both;
      animation-timeline: --rv;
      animation-range: entry 12% cover 30%;
    }
    .rv:not(.rv--js) > .rv-a {
      animation: bh-iris 1.05s var(--ease) both;
      animation-timeline: --rv;
      animation-range: entry 6% cover 30%;
    }
    .rv:not(.rv--js) > .rule {
      animation: bh-draw 1s var(--ease) both;
      animation-timeline: --rv;
      animation-range: entry 14% cover 34%;
    }
  }
}
/* the mask is grown top and bottom by 6%: with a display line-height under 1,
   a flat inset(0 ...) eats ascenders and descenders as the text passes through */
@keyframes bh-rise {
  from { clip-path: inset(-6% 0 100% 0); opacity: 0; transform: translateY(0.55rem); }
  to   { clip-path: inset(-6% 0 -6% 0); opacity: 1; transform: none; }
}
@keyframes bh-iris {
  from { clip-path: inset(0 50% 0 50%); opacity: 0; }
  to   { clip-path: inset(0 0 0 0); opacity: 1; }
}
@keyframes bh-draw { from { transform: scaleX(0); } to { transform: scaleX(1); } }

@media (prefers-reduced-motion: reduce) {
  .rv > .rv-i, .rv > .rv-a, .rv > .rule {
    clip-path: none; opacity: 1; transform: none; animation: none;
  }
}

/* ---------------------------------------------------- 8. section furniture */
.eyebrow {
  display: flex; align-items: center; gap: 0.8rem;
  font: 400 var(--t-micro)/1 var(--display);
  letter-spacing: 0.34em; text-indent: 0.34em; text-transform: uppercase;
  color: var(--green);
  margin-bottom: 1.5rem;
}
.eyebrow::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

.h-sec { font: 400 var(--t-h2)/0.98 var(--display); letter-spacing: 0.012em; max-width: 20ch; }
.lede  { margin-top: 1.1rem; max-width: 52ch; color: var(--bone-mute); }

/* ------------------------------------------------------------ 9. services */
.svc { margin-top: clamp(2.5rem, 6vh, 4.5rem); border-top: 1px solid var(--rule-soft); }
.svc__row {
  display: grid; gap: 0.3rem 2rem;
  grid-template-columns: 3.2rem minmax(0, 15rem) minmax(0, 1fr);
  align-items: start;
  padding-block: clamp(1.4rem, 3.4vh, 2.3rem);
  border-bottom: 1px solid var(--rule-soft);
}
.svc__no   { font: 400 var(--t-small)/1.5 var(--display); letter-spacing: 0.1em; color: var(--green); }
.svc__name { font: 400 var(--t-h3)/1.2 var(--display); letter-spacing: 0.04em; }
.svc__what { color: var(--bone-mute); }
@media (max-width: 46rem) {
  .svc__row  { grid-template-columns: 2.4rem minmax(0, 1fr); }
  .svc__what { grid-column: 2; }
}

/* --------------------------------------------------------------- 10. work */
.work { display: grid; gap: clamp(2.75rem, 7vh, 5.5rem); margin-top: clamp(2.5rem, 6vh, 4rem); }
.proj { display: grid; gap: 1.4rem; }

.proj__frame {
  position: relative;
  border: 1px solid var(--rule-soft);
  background: var(--ink-deep);
  overflow: clip;
  aspect-ratio: 21 / 8;
}
@media (max-width: 46rem) { .proj__frame { aspect-ratio: 16 / 9; } }

/* THE PAN. Both project images are wide filmstrips - screens stitched left to
   right, which is exactly what they were made for. They pan as you scroll past,
   so the work moves at the speed you read it, and NOTHING MOVES WHEN YOU ARE
   STILL. A timer-driven marquee would repaint forever for no information. */
/* Slightly transparent OVER the dark card rather than filtered. Two reasons:
   opacity is compositor-only where `filter: brightness()` re-runs the pass every
   frame, and the screenshots were shot on their own backgrounds - OCR Buddy's is
   a bright product blue - which shouts against an ink page. Letting the ground
   come through settles them into the palette without touching the pixels. */
.proj__strip {
  position: absolute; inset: 0;
  width: auto; height: 100%; max-width: none;
  object-fit: cover; object-position: left center;
  opacity: 0.86;
}
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .proj__frame { view-timeline-name: --pan; view-timeline-axis: block; }
    .proj__strip {
      animation: bh-pan linear both;
      animation-timeline: --pan;
      animation-range: cover 0% cover 100%;
    }
  }
}
@keyframes bh-pan { from { translate: 0 0; } to { translate: var(--pan, -20%) 0; } }

/* a scrim so the strip never fights the type sitting under it */
.proj__frame::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, color-mix(in srgb, var(--ink) 78%, transparent), transparent 58%);
}

.proj__head   { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem 1.1rem; }
.proj__name   { font: 400 var(--t-h2)/0.98 var(--display); letter-spacing: 0.015em; }
.proj__client {
  font: 400 var(--t-micro)/1 var(--display);
  letter-spacing: 0.26em; text-indent: 0.26em; text-transform: uppercase;
  color: var(--green);
}
.proj__what { color: var(--bone-mute); max-width: 56ch; }
.proj__note { color: var(--bone-faint); font-size: var(--t-small); max-width: 56ch; margin-top: 0.5rem; }

.facts { display: flex; flex-wrap: wrap; gap: 0.5rem; margin-top: 0.35rem; }
.facts li {
  font: 400 var(--t-small)/1.35 var(--text);
  color: var(--bone-faint);
  border: 1px solid var(--rule-soft);
  padding: 0.3rem 0.7rem;
}

/* --------------------------------------------------- 11. links and buttons */
.links { display: flex; flex-wrap: wrap; gap: 0.35rem 1.4rem; margin-top: 0.3rem; }

.lk {
  position: relative;
  display: inline-flex; align-items: center; gap: 0.45rem;
  font: 400 var(--t-small)/1.6 var(--display);
  letter-spacing: 0.16em; text-indent: 0.16em; text-transform: uppercase;
  color: var(--green);
  padding-block: 0.15rem;
}
.lk::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: currentColor;
  transform: scaleX(1); transform-origin: 100% 50%;
  transition: transform 0.4s var(--ease);
}
.lk:hover::after, .lk:focus-visible::after { transform: scaleX(0); }
.lk__arw { transition: translate 0.4s var(--ease); }
.lk:hover .lk__arw, .lk:focus-visible .lk__arw { translate: 0.28em 0; }

/* ---------------------------------------------------------------- 12. who */
.who__grid {
  display: grid; gap: clamp(2rem, 5vw, 4.5rem);
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
}
.who__q     { font: 400 var(--t-h1)/1.06 var(--display); letter-spacing: 0.01em; max-width: 18ch; }
.who__body  { margin-top: 1.6rem; max-width: 46ch; color: var(--bone-mute); display: grid; gap: 1rem; }
.who__body strong { color: var(--bone); font-weight: 400; }
.who__markwrap { display: grid; place-items: center; }
.who__mark  { width: min(100%, 20rem); aspect-ratio: 1; color: var(--green); opacity: 0.9; }
@media (max-width: 52rem) {
  .who__grid { grid-template-columns: 1fr; }
  .who__markwrap { order: -1; }
  .who__mark { width: min(60%, 12rem); }
}

/* -------------------------------------------------------------- 14. embed */
/* A YouTube iframe is roughly 1.5MB of third-party JS before anyone presses
   play, and it loads whether they ever do or not. This is the facade: a still,
   a real button, and the iframe only after a click. */
/* A facade with no still is a large empty rectangle, which reads as a broken
   image rather than as a control. Only the embeds that ship a poster reserve
   16:9 up front; the rest are a compact bar until they are actually loaded, and
   .embed--live (set by embed.js on mount) gives them the player's shape. */
.embed {
  position: relative;
  min-height: 8.5rem;
  background: var(--ink-deep);
  border: 1px solid var(--rule-soft);
  overflow: clip;
}
.embed:has(.embed__poster), .embed--live { aspect-ratio: 16 / 9; min-height: 0; }
.embed iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.embed__btn {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: grid; place-items: center;
  background: transparent; border: 0; cursor: pointer;
  color: var(--bone);
  font: inherit;
}
.embed__btn::before {
  content: ""; position: absolute; inset: 0;
  background: color-mix(in srgb, var(--ink) 38%, transparent);
  transition: background 0.4s var(--ease);
}
.embed__btn:hover::before, .embed__btn:focus-visible::before {
  background: color-mix(in srgb, var(--ink) 22%, transparent);
}
.embed__poster {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: 62% center;   /* a truck, not the map UI */
  /* the button already lays a 46% scrim over this; two dimmers stacked read as
     a washed-out image rather than as a dimmed one */
  opacity: 0.92;
}
.embed__label {
  position: relative;
  display: inline-flex; align-items: center; gap: 0.6rem;
  font: 400 var(--t-small)/1 var(--display);
  letter-spacing: 0.2em; text-indent: 0.2em; text-transform: uppercase;
  border: 1px solid var(--bone); padding: 0.8rem 1.4rem;
  background: color-mix(in srgb, var(--ink) 72%, transparent);
  transition: background 0.35s var(--ease), color 0.35s var(--ease);
}
.embed__btn:hover .embed__label, .embed__btn:focus-visible .embed__label {
  background: var(--bone); color: var(--ink);
}

/* ------------------------------------------------------------- 15. footer */
.ftr { border-top: 1px solid var(--rule-soft); padding-block: clamp(2.5rem, 6vh, 4rem); }
.ftr__in   { display: flex; flex-wrap: wrap; align-items: center; gap: 1.2rem 2rem; }
.ftr__name { font: 400 var(--t-small)/1.4 var(--text); color: var(--bone-faint); }
.ftr__soc  { margin-left: auto; display: flex; align-items: center; gap: 1.3rem; }
.ftr__soc a {
  display: inline-flex; align-items: center; gap: 0.5rem;
  color: var(--bone-mute); font-size: var(--t-small);
  transition: color 0.3s var(--ease);
}
.ftr__soc a:hover, .ftr__soc a:focus-visible { color: var(--green); }
.ftr__soc img { width: 1.05rem; height: 1.05rem; opacity: 0.85; }
@media (max-width: 40rem) { .ftr__soc { margin-left: 0; } }

/* -------------------------------------------------------- 16. small pages */
.plain { min-height: 68svh; display: grid; align-content: center; padding-block: var(--band); }
.plain h1 { font: 400 var(--t-h1)/1 var(--display); letter-spacing: 0.02em; }
.plain p  { margin-top: 1.3rem; color: var(--bone-mute); max-width: 52ch; }
.plain .links { margin-top: 2.2rem; }
.plain__mark { width: 5rem; aspect-ratio: 1; color: var(--green); margin-bottom: 2rem; opacity: 0.8; }

/* print styles live in print.css, loaded with media="print" so they never
   block a render. */
