/* Basalt Industries — public site.
   ------------------------------------------------------------------------
   Subject: a basalt aggregate resource in exploration and development, and the
   rock itself. The photographs show standard industry practice rather than this
   company's ground — the page says so on its face. Photography carries the site;
   the design's job is to frame it with the precision of a survey record rather
   than decorate around it.

   Direction: the photograph as a PLATE in a field record. Every image sits in a
   hairline frame with a plate number and a marginal data line beneath it. That
   is what makes the imagery read as documentation of a real operation instead of
   stock photography, and it is the one device the whole site is built on.

   The palette is deliberately achromatic — basalt greys on cool ash paper — so
   the photographs supply every colour on the page. A single deep survey green
   carries interaction. The five map-unit colours survive only in the geology
   legend, where each one still means a specific rock unit.

   Type: Libre Franklin (Franklin Gothic is the historical voice of American
   survey-sheet collars) set large, mixed case and tight for headlines, with
   tracked caps reserved for marginalia; DM Mono for plate data and measured
   values; Newsreader italic for plate numbers and map-unit codes, nothing else.

   Dependency-free static CSS, no build step (see CLAUDE.md). Scroll reveals use
   native scroll-driven animations behind @supports, so there is no JavaScript.
   ------------------------------------------------------------------------ */

:root {
  /* >>> shared tokens — generated by scripts/sync-tokens, do not edit by hand */
  /* Sheet — cool ash paper, not warm parchment and not card-white. */
  --ground: #edeee9;
  --ground-deep: #14171a;
  --surface: #f8f9f6;
  --sunken: #e2e4dd;
  --rule: #cbcec5;
  --rule-strong: #9ea49a;

  /* Ink */
  --ink: #141716;
  --ink-mute: #5c635c;
  --ink-faint: #8b918a;

  /* Ink on a dark ground — the hero scrim, and any solid-filled chip. */
  --on-dark: #f2f1ec;
  --on-dark-mute: #b4b9b2;
  --on-dark-faint: #7d847e;

  /* Interaction. Deep survey green, and the only chroma either surface spends
     on itself — every other colour comes from a photograph. */
  --accent: #2f5238;
  --accent-hover: #22402a;

  /* Map units. These mean rock units, not moods. Desaturated into the site's
     own ash-and-green family so the geology figure belongs to the page rather
     than looking like a survey sheet pasted into it; the two basalt units carry
     the survey green at two values, which is semantic rather than decorative.
     The hub does NOT use these for status — see the status scale in hub.css for
     why lithology colours turned out to be the wrong tool for that job. */
  --u-qb: #8ea896;
  --u-tcr: #b4c2b6;
  --u-qal: #dcd7c6;
  --u-ts: #c7ccd1;
  --u-ti: #6e7570;

  /* Type. Libre Franklin for display and body (Franklin Gothic is the
     historical voice of American survey-sheet collars), DM Mono for measured
     values and marginal labels, Newsreader italic for unit codes and plate
     numbers only. */
  --f-text: "Libre Franklin", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --f-mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --f-unit: "Newsreader", Georgia, "Times New Roman", serif;
  /* <<< shared tokens */

  /* Public-site only: the measure of the page and its display scale. */
  --measure: 78rem;
  --gutter: clamp(1.25rem, 4vw, 3.5rem);

  --t-hero: clamp(2.4rem, 5.8vw, 4.75rem);
  --t-h2: clamp(1.7rem, 3.3vw, 2.7rem);
  --t-lead: clamp(1.1rem, 1.8vw, 1.45rem);
}


* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font: 400 1.0625rem/1.6 var(--f-text);
  -webkit-font-smoothing: antialiased;
}

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

a { color: var(--accent); text-underline-offset: 0.2em; }
a:hover { color: var(--accent-hover); }

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

img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* The marginal voice: every label, caption line, and note on the page.
   Small, tracked, uppercase, mono — the way a sheet annotates its own margin. */
.marginal {
  margin: 0;
  font-family: var(--f-mono);
  font-size: 0.7rem;
  line-height: 1.5;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ---------- Masthead ---------- */

.masthead {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem 2rem;
  padding: 1rem var(--gutter);
  background: var(--ground);
  border-bottom: 1px solid var(--rule);
}

.brand { display: inline-flex; align-items: center; gap: 0.6rem; color: var(--ink); text-decoration: none; }

/* The masthead carried a "Qb" chip — the geologic map-unit code for Quaternary
   basalt — as the company mark. It came out because the owner had to ask what it
   meant, which is the whole argument against a mark that needs explaining. Qb
   still appears in the Geology legend, where it denotes a rock unit rather than
   standing in for a company. The wordmark alone carries the masthead now, so it
   is set a little larger than it was beside the chip. */
.brand-name { font-weight: 800; font-size: 0.95rem; letter-spacing: 0.13em; text-transform: uppercase; }

.masthead-nav { display: flex; align-items: center; flex-wrap: wrap; gap: 1.5rem; }
.masthead-nav a {
  font-family: var(--f-mono);
  font-size: 0.72rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-mute);
  text-decoration: none;
  padding-bottom: 0.15rem;
  border-bottom: 1px solid transparent;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.masthead-nav a:hover { color: var(--ink); border-bottom-color: var(--accent); }

/* The nav sets small tracked type on a tight line, which leaves every item about
   21px tall — under the 24px minimum for a pointer target, and meaner still on a
   phone. This grows the hit area vertically without moving anything: the box
   stays where the type wants it, the target extends past it invisibly. Bounded
   left and right by each item, so adjacent links never steal each other's taps. */
.masthead-nav a { position: relative; }
.masthead-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}

/* The one nav item that leaves the site, and the only mark in the masthead
   besides the wordmark. Set as an icon rather than a worded button: the nav is
   four same-page anchors, and anything with a label and a box among them read as
   a fifth section and sat taller than the line they share.

   Specificity note: `.masthead-nav a` is (0,1,1), so a bare `.nav-signin` at
   (0,1,0) loses to it and the underline rules leak through. Scoped to
   `.masthead-nav .nav-signin` (0,2,0) so the resets actually apply. */
.masthead-nav .nav-signin {
  display: inline-flex;
  align-items: center;
  /* Same bottom padding the worded anchors carry for their hover underline, so
     the mark's optical centre lands on theirs rather than ~1px below it. */
  padding: 0 0 0.15rem 1.4rem;
  margin-left: -0.1rem;
  border-bottom: none;
  border-left: 1px solid var(--rule);
  color: var(--ink-mute);
  transition: color 0.15s ease;
}
.masthead-nav .nav-signin:hover { color: var(--ink); border-bottom: none; }

.masthead-nav .nav-signin svg {
  display: block;
  width: 1.15rem;
  height: 1.15rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: butt;
}

/* ---------- Hero ----------
   One image, full bleed, given a whole viewport. Nothing competes with it. */

.hero { position: relative; }

.hero-image {
  position: relative;
  height: min(84vh, 50rem);
  overflow: hidden;
  background: var(--ground-deep);
}

.hero-media { position: absolute; inset: 0; }

/* A single downward gradient so the headline holds over any photograph,
   rather than a flat scrim that flattens the image. */
.hero-image::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(to top, rgba(12, 14, 16, 0.72) 0%, rgba(12, 14, 16, 0.42) 45%, rgba(12, 14, 16, 0.30) 100%);
}

/* Centred on the photograph and nothing else — the hero's whole job is one
   image and one line over it. Anchored inside the image, so the marginal data
   bar below can never push the type off the picture. */
.hero-text {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.1rem;
  padding: 3rem var(--gutter) 5rem;
  text-align: center;
}

.hero-text h1 {
  margin: 0;
  max-width: 18ch;
  font-size: var(--t-hero);
  font-weight: 800;
  line-height: 1.03;
  letter-spacing: -0.022em;
  text-wrap: balance;
  color: var(--on-dark);
}

.hero-sub {
  margin: 0;
  max-width: 46ch;
  font-size: var(--t-lead);
  line-height: 1.45;
  color: var(--on-dark-mute);
}

.hero-data {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.9rem;
  margin: 0;
  padding: 0.9rem var(--gutter);
  border-bottom: 1px solid var(--rule);
  font-family: var(--f-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* ---------- Sections ---------- */

.section,
.lead-block {
  max-width: var(--measure);
  margin: 0 auto;
  padding: clamp(4rem, 9vw, 8rem) var(--gutter);
}

.lead-block {
  display: grid;
  grid-template-columns: 9rem 1fr;
  gap: 2.5rem;
  align-items: start;
}

.lead {
  margin: 0;
  max-width: 58ch;
  font-size: var(--t-lead);
  line-height: 1.5;
  color: var(--ink);
  text-wrap: pretty;
}

/* A full-bleed tinted band whose contents stay on the same measure and the same
   left edge as every other section — centring the track, not each child, so a
   narrow child (the section head) still aligns left rather than drifting centre. */
.section-inset {
  background: var(--surface);
  max-width: none;
  display: grid;
  grid-template-columns: minmax(0, var(--measure));
  justify-content: center;
}

.section-close { padding-bottom: clamp(3rem, 6vw, 5rem); }

.section-head { margin-bottom: clamp(2.5rem, 5vw, 4rem); max-width: 60ch; }
.section-head h2 {
  margin: 0.9rem 0 0;
  font-size: var(--t-h2);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.018em;
  text-wrap: balance;
}
.section-sub { margin: 0.9rem 0 0; color: var(--ink-mute); max-width: 56ch; }

/* ---------- Plates ----------
   The signature. A photograph is presented the way a plate is presented in a
   field record: hairline frame, plate number, and a marginal line beneath. */

.plate-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(2rem, 4vw, 3.25rem) clamp(1.25rem, 2.5vw, 2rem);
}

.plate { margin: 0; }
.plate-wide { grid-column: 1 / -1; }

.plate-frame {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--sunken);
  border: 1px solid var(--rule-strong);
}
.plate-wide .plate-frame { aspect-ratio: 3 / 2; }

.plate figcaption {
  display: grid;
  grid-template-columns: 2.25rem 1fr;
  gap: 0.15rem 0.9rem;
  margin-top: 0.9rem;
}

.plate-no {
  grid-row: span 2;
  font-family: var(--f-unit);
  font-style: italic;
  font-size: 1.2rem;
  line-height: 1.2;
  color: var(--ink-faint);
}
.plate-title { font-weight: 600; font-size: 1rem; letter-spacing: 0.005em; }
.plate-note {
  font-family: var(--f-mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

/* A plate whose photograph has not landed yet. Hatched, the way an unmapped
   area is hatched on a sheet, and carrying its own shot brief — so the gap
   reads as reserved space rather than a broken image. */
.plate-pending {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 1.5rem;
  text-align: center;
  background-color: var(--sunken);
  background-image: repeating-linear-gradient(-45deg, transparent 0 11px, rgba(20, 23, 22, 0.05) 11px 12px);
}

.pending-mark {
  margin: 0;
  font-family: var(--f-unit);
  font-style: italic;
  font-size: 1.15rem;
  color: var(--ink-mute);
}
.pending-brief {
  margin: 0;
  max-width: 32ch;
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--ink-mute);
}
.pending-spec {
  margin: 0;
  font-family: var(--f-mono);
  font-size: 0.64rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.hero-pending { justify-content: flex-end; padding-bottom: 1.25rem; }
.hero-pending .pending-spec { max-width: none; }

.plate-pending-dark {
  background-color: var(--ground-deep);
  background-image: repeating-linear-gradient(-45deg, transparent 0 13px, rgba(242, 241, 236, 0.055) 13px 14px);
}
.plate-pending-dark .pending-mark { color: var(--on-dark-mute); }
.plate-pending-dark .pending-brief { color: var(--on-dark-mute); }
.plate-pending-dark .pending-spec { color: var(--on-dark-faint); }

/* ---------- Plate carousel ----------
   Plates 07-12, one at a time, stepped through by number. No JavaScript: the
   public site ships none and that holds (CLAUDE.md).

   Mechanism, and why it is this one. The obvious build is a scroll-snap track
   with the numbers as anchor links, which also buys native touch swiping. It was
   built that way first and it does not work: a fragment link does not scroll a
   NESTED scroll container. Measured, not assumed — clicking a number set
   :target and moved the active marker correctly while the track sat at
   scrollLeft 0, and the same element scrolled to 4158 the moment scrollIntoView
   was called on it. Turning snap off or down to proximity changed nothing, so it
   is fragment-to-nested-scroller that fails, not snap.

   So the track is moved by transform instead, and nothing scrolls at all.
   :target sets an index on the carousel, the track reads it. The nav links keep
   working, Back steps to the previous plate, and the whole thing stays
   declarative. The cost is touch swiping, which a scroll container would have
   given free — a real loss on a phone, and the reason the numbers are large,
   spaced, and carry a 44px tap target.

   overflow: clip rather than hidden, deliberately: `hidden` still creates a
   scroll container, so the browser could scroll a clipped slide into view and
   shove the track out of alignment behind the transform. `clip` cannot scroll. */

.carousel {
  --i: 0;
  overflow: clip;
}

.carousel:has(#plate-08:target) { --i: 1; }
.carousel:has(#plate-09:target) { --i: 2; }
.carousel:has(#plate-10:target) { --i: 3; }
.carousel:has(#plate-11:target) { --i: 4; }
.carousel:has(#plate-12:target) { --i: 5; }

.carousel-track {
  display: flex;
  margin: 0;
  padding: 0;
  list-style: none;
  transform: translateX(calc(var(--i) * -100%));
  transition: transform 0.55s cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* No gap: the track steps by exact multiples of its own width, and a gap would
   put the slides progressively out of register with that. Air between plates is
   the stage's business, not the track's. */
.slide { flex: 0 0 100%; min-width: 0; }

.slide-figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.1rem;
}

/* A fixed stage, so stepping between a portrait and a landscape plate does not
   make the page jump. Tall enough that a portrait fills it and lands with some
   weight; a landscape is limited by the slide width long before this, so it just
   centres in a little more air. The frame is on the photograph rather than on
   the stage — the border should hug the picture the way a plate is mounted, not
   box the empty ground beside it. */
.slide-shot {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: min(72vh, 40rem);
}

.slide-shot img {
  width: auto;
  height: 100%;
  max-width: 100%;
  object-fit: contain;
  border: 1px solid var(--rule-strong);
}

.slide-figure figcaption {
  display: flex;
  align-items: baseline;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.3rem 0.9rem;
  text-align: center;
}

.carousel-nav {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1.5rem;
  margin-top: clamp(1.75rem, 3.5vw, 2.5rem);
  padding-top: 1.15rem;
  border-top: 1px solid var(--rule);
}

.carousel-nav a {
  position: relative;
  font-family: var(--f-mono);
  font-size: 0.74rem;
  letter-spacing: 0.12em;
  color: var(--ink-faint);
  text-decoration: none;
  padding-bottom: 0.25rem;
  border-bottom: 1px solid transparent;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.carousel-nav a:hover { color: var(--ink); border-bottom-color: var(--rule-strong); }

/* Same device the masthead uses: grow the tap target past the type without
   moving anything. It matters more here, because these numbers are the only way
   through the carousel on a touch screen. */
.carousel-nav a::after {
  content: "";
  position: absolute;
  left: -0.6rem;
  right: -0.6rem;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}

/* Which plate is showing. Before any click nothing is targeted, so the first
   plate is marked instead. */
.carousel:not(:has(.slide:target)) .carousel-nav a:first-child,
.carousel:has(#plate-07:target) .carousel-nav a[href="#plate-07"],
.carousel:has(#plate-08:target) .carousel-nav a[href="#plate-08"],
.carousel:has(#plate-09:target) .carousel-nav a[href="#plate-09"],
.carousel:has(#plate-10:target) .carousel-nav a[href="#plate-10"],
.carousel:has(#plate-11:target) .carousel-nav a[href="#plate-11"],
.carousel:has(#plate-12:target) .carousel-nav a[href="#plate-12"] {
  color: var(--ink);
  border-bottom-color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .carousel-track { transition: none; }
}

@media (max-width: 44rem) {
  .slide-shot { height: min(64vh, 30rem); }
  .carousel-nav { gap: 1.15rem; }
}

/* ---------- Specification table ----------
   Set like an assay table: mono tracked headers, tabular figures, hairline
   rules. The sieve series is standard; the values are the owner's to publish. */

.spec-wrap { overflow-x: auto; }

.spec-table {
  width: 100%;
  min-width: 34rem;
  border-collapse: collapse;
  background: var(--ground);
  border: 1px solid var(--rule-strong);
}

.spec-table caption {
  text-align: left;
  padding-bottom: 0.85rem;
  color: var(--ink-faint);
}

.spec-table thead th {
  text-align: left;
  padding: 0.7rem 1.15rem;
  background: var(--sunken);
  border-bottom: 1px solid var(--rule-strong);
  font-family: var(--f-mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-mute);
  white-space: nowrap;
}

.spec-table tbody th {
  text-align: left;
  padding: 0.7rem 1.15rem;
  font-family: var(--f-mono);
  font-size: 0.78rem;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  white-space: nowrap;
}

.spec-table td {
  padding: 0.7rem 1.15rem;
  font-family: var(--f-mono);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}

.spec-table tbody tr { border-bottom: 1px solid var(--rule); }
.spec-table tbody tr:last-child { border-bottom: none; }

.spec-note { margin-top: 0.9rem; }

/* A specification table whose values are prose rather than measured figures.
   Same frame, same rules, same mono headers — only the body switches to the
   text face, because sentences set in tabular mono read as code rather than as
   writing. Also drops the numeric table's 34rem floor, which existed to keep
   sieve columns from crushing together and on a phone would force a table of
   plain sentences to scroll sideways instead of simply wrapping. */
.spec-table-prose { min-width: 0; }

.spec-table-prose tbody th {
  font-family: var(--f-text);
  font-size: 0.92rem;
  font-weight: 600;
  white-space: normal;
}

.spec-table-prose td {
  font-family: var(--f-text);
  font-size: 0.92rem;
  line-height: 1.5;
  font-variant-numeric: normal;
  color: var(--ink-mute);
}

/* ---------- Contact ----------
   The only place on the page that asks the reader to act. Set as a measured
   value in the marginal voice rather than as a button: nothing else on this
   site is a button, and one here would instantly be the loudest object on the
   page. The accent rule under it is the same interaction cue the 404 link uses. */

.contact-channel { margin: 0; }

.contact-channel a {
  font-family: var(--f-mono);
  font-size: 1rem;
  letter-spacing: 0.02em;
  text-decoration: none;
  border-bottom: 1px solid var(--accent);
  padding-bottom: 0.2rem;
}
.contact-channel a:hover { border-bottom-width: 2px; }

/* ---------- Geology ----------
   A figure, not a facsimile. The information is a real geologic map's — mapped
   units, contacts, a fault with its downthrown ticks, strike and dip — but it is
   drawn with the restraint of the rest of the page rather than reproduced in
   survey-sheet dress: desaturated units, hairline contacts, and a legend set as
   a table rather than as a printed collar. */

.geology-body {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: start;
}

/* Hairline, not the heavy rule. The map already reads as an object because of
   its own fills; a strong border on top made it look pasted on. */
.map-frame { border: 1px solid var(--rule); }

/* The drawing sets the box, not the other way round. This was a 3:1 strip shown
   in a much squarer container with preserveAspectRatio="slice", which cropped the
   sides away — including, at two-column widths, almost the whole Qb unit, the one
   the legend names as Quaternary basalt. Redrawn at 900x620 and matched here with
   aspect-ratio, so the figure is complete at every width and nothing is cropped. */
.map {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 900 / 620;
  background: var(--u-tcr);
}

.map-fills .u-qb { fill: var(--u-qb); }
.map-fills .u-tcr { fill: var(--u-tcr); }
.map-fills .u-qal { fill: var(--u-qal); }
.map-fills .u-ts { fill: var(--u-ts); }
.map-fills .u-ti { fill: var(--u-ti); }

/* Every line lighter than it was. With the fills desaturated, contacts at the
   old weight and opacity turned into the loudest thing in the figure. */
.map-contacts path { fill: none; stroke: var(--ink); stroke-width: 1; stroke-opacity: 0.35; }
.map-fault path { fill: none; stroke: var(--ink); stroke-width: 1.5; stroke-opacity: 0.55; }
.map-symbols path { fill: none; stroke: var(--ink); stroke-width: 1.4; stroke-opacity: 0.45; }
/* Sized for the rendered figure, not for the viewBox. The map draws at roughly
   0.35x on a phone and 0.75x on a wide desktop, so type set at "reasonable"
   viewBox values arrives on screen far smaller than it looks in the source. */
.map-symbols text { font-family: var(--f-mono); font-size: 16px; fill: var(--ink); fill-opacity: 0.5; }
.map-labels text {
  font-family: var(--f-unit);
  font-style: italic;
  font-size: 26px;
  fill: var(--ink);
  fill-opacity: 0.66;
}

.explanation { display: flex; flex-direction: column; gap: 1.1rem; }

.unit-list { margin: 0; padding: 0; list-style: none; }

/* Set like the specification table rather than like a map collar: hairline
   rules, a square swatch, the code as a label and the description as prose. */
.unit {
  display: grid;
  grid-template-columns: 1.9rem 2.6rem 1fr;
  align-items: start;
  gap: 0 1.1rem;
  padding: 0.85rem 0;
  border-bottom: 1px solid var(--rule);
}
.unit:first-child { border-top: 1px solid var(--rule); }

.chip {
  display: block;
  width: 1.9rem;
  height: 1.9rem;
  align-self: start;
  /* A hairline at low alpha rather than --rule-strong: the swatch should read as
     the colour it carries, not as a bordered box. */
  border: 1px solid rgba(20, 23, 22, 0.14);
}
.chip.u-qb { background: var(--u-qb); }
.chip.u-tcr { background: var(--u-tcr); }
.chip.u-qal { background: var(--u-qal); }
.chip.u-ts { background: var(--u-ts); }
.chip.u-ti { background: var(--u-ti); }

.unit-code {
  font-family: var(--f-unit);
  font-style: italic;
  font-size: 1.05rem;
  line-height: 1.6;
  color: var(--ink-mute);
}
.unit-desc { font-size: 0.875rem; line-height: 1.55; color: var(--ink-mute); }
.unit-desc strong { color: var(--ink); font-weight: 600; }

/* ---------- Not found ----------
   Pages serves this for any unmatched path. Without it every missing URL
   returned the homepage with a 200, which invites search engines to index
   unlimited duplicate pages. Same voice as the rest of the site: state what
   happened and what to do, no apology. */

.notfound {
  max-width: var(--measure);
  margin: 0 auto;
  padding: clamp(5rem, 14vh, 10rem) var(--gutter) clamp(6rem, 18vh, 12rem);
}

.notfound h1 {
  margin: 0.9rem 0 0;
  font-size: var(--t-h2);
  font-weight: 800;
  line-height: 1.08;
  letter-spacing: -0.018em;
}

.notfound-body { margin: 1rem 0 1.75rem; max-width: 46ch; color: var(--ink-mute); font-size: var(--t-lead); }

.notfound-link {
  font-family: var(--f-mono);
  font-size: 0.72rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  text-decoration: none;
  border-bottom: 1px solid var(--accent);
  padding-bottom: 0.15rem;
}
.notfound-link:hover { border-bottom-width: 2px; }

/* ---------- Colophon ---------- */

.colophon {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.25rem 2rem;
  max-width: var(--measure);
  margin: 0 auto;
  padding: 2rem var(--gutter) 3rem;
  border-top: 1px solid var(--rule);
}

.scale { display: flex; align-items: center; gap: 0.7rem; }

/* An actual scale bar: alternating filled and open segments. */
.scale-bar {
  display: block;
  width: 7rem;
  height: 0.5rem;
  border: 1px solid var(--rule-strong);
  background-image: linear-gradient(to right,
    var(--ink-faint) 0 25%, transparent 25% 50%,
    var(--ink-faint) 50% 75%, transparent 75% 100%);
}

.imprint {
  margin: 0;
  font-weight: 800;
  font-size: 0.75rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

/* ---------- Motion ----------
   One orchestrated arrival, then plates rise as they enter the viewport. Scroll
   reveals use native scroll-driven animation, so this stays JavaScript-free and
   simply does not run where the feature is unsupported. */

@keyframes hero-settle { from { transform: scale(1.05); } to { transform: scale(1); } }
@keyframes rise { from { opacity: 0; transform: translateY(1.25rem); } to { opacity: 1; transform: none; } }

.hero-media { animation: hero-settle 1.6s cubic-bezier(0.22, 0.61, 0.36, 1) both; }
.hero-text > * { animation: rise 0.9s cubic-bezier(0.22, 0.61, 0.36, 1) both 0.25s; }
.hero-text > .hero-sub { animation-delay: 0.4s; }

/* There is deliberately no scroll reveal. An earlier pass faded plates in on a
   view() timeline, which meant every plate below the fold sat at opacity 0 until
   its range ran — so arriving via an anchor link, or any browser resolving that
   range differently, hid the page's content with no way back. Photographs that
   simply exist as you reach them are also more confident than photographs that
   perform, and one orchestrated moment beats several scattered ones. */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .hero-media,
  .hero-text > * { animation: none; opacity: 1; transform: none; }
}

/* ---------- Narrow ---------- */

@media (max-width: 56rem) {
  .geology-body { grid-template-columns: 1fr; }
  .map { height: clamp(14rem, 32vh, 20rem); }
}

@media (max-width: 44rem) {
  /* The nav wraps to a third row here once "The rock" joins it, which put a
     123px sticky masthead over every screen on a 812px phone — 15% of the
     viewport, permanently, on a site whose entire argument is the photographs.
     Tightening the gap and the type does not recover the row (measured: 116px at
     0.65rem gaps, still three rows), so the header gives up stickiness instead.
     It scrolls away, the pictures get the whole screen, and the nav is a scroll
     to the top away — which on a phone is one tap on the status bar. */
  .masthead { position: static; gap: 0.6rem 1.25rem; padding: 0.85rem var(--gutter); }
  .masthead-nav { gap: 1.1rem; }

  /* The nav wraps to two rows here, ~39px between their baselines, so a 44px
     target would reach into the row above and steal its taps. 36px keeps every
     item comfortably over the 24px minimum while staying inside its own row. */
  .masthead-nav a::after { height: 36px; }

  /* The divider earns its keep inline, separating the mark from Contact. Once
     the nav wraps it is just a stub hanging at the start of a row, so it goes
     and the mark lines up on the gutter with everything else. */
  .masthead-nav .nav-signin { border-left: none; padding-left: 0; }

  .hero-image { height: min(82vh, 42rem); }

  .lead-block { grid-template-columns: 1fr; gap: 1rem; }

  .plate-grid { grid-template-columns: 1fr; }
  .plate-frame, .plate-wide .plate-frame { aspect-ratio: 4 / 3; }

  .colophon { justify-content: flex-start; }
}
