/* ==========================================================================
   Zelofreight — Forest Green & Gold design system
   Single committed dark theme. No framework, no build step.

   Every colour on the site comes from the tokens below. To re-theme,
   change these values and nothing else — no other file hardcodes a
   brand colour except the two SVGs in images/ and the <meta name=
   "theme-color"> tag in each page's head.
   ========================================================================== */

/* ---------- Tokens ---------- */
:root {
  /* Surfaces, darkest to lightest. Deep forest green, slightly
     desaturated so the gold reads as the accent rather than competing. */
  --base:        #0A1512;
  --surface-1:   #10201B;
  --surface-2:   #162A23;
  --surface-3:   #1F3830;

  /* Gold ramp */
  --gold:        #C9A227;
  --gold-bright: #E3C765;
  --gold-deep:   #8C6F16;
  --gold-veil:   rgba(201, 162, 39, 0.12);
  --gold-line:   rgba(201, 162, 39, 0.32);

  /* Type. Muted and faint carry a faint green cast so they sit in the
     palette; both clear WCAG AA against base and surface-1. */
  --ivory:       #F4F1E9;
  --text:        #DCD8CE;
  --text-muted:  #98A49C;
  --text-faint:  #808D86;

  /* Structure */
  --line:        rgba(244, 241, 233, 0.10);
  --line-strong: rgba(244, 241, 233, 0.18);

  /* Status. Kept clear of the gold hue so a "customs" warning is never
     mistaken for brand accent, and bright enough to read on green. */
  --ok:          #7ED4A2;
  --warn:        #E0913A;
  --info:        #7FA9C9;
  --danger:      #E07A66;

  /* Rhythm */
  --gutter: clamp(1.25rem, 4vw, 3rem);
  --section-y: clamp(4.5rem, 10vw, 9rem);
  --radius: 4px;
  --radius-lg: 8px;

  /* Shape language. Generous radii and full pills are most of what separates a
     site that reads as "lively" from one that reads as "corporate" — the
     palette does less of that work than people expect. */
  --radius-card: 24px;
  --radius-pill: 100px;
  --tile-radius: 16px;

  /* Icon tiles behind card glyphs. */
  --tile-bg:  rgba(201, 162, 39, .16);
  --tile-ink: var(--gold-bright);
  --badge-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23E3C765' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 2 4 5.5V11c0 5 3.4 9.3 8 11 4.6-1.7 8-6 8-11V5.5Z'/%3E%3Cpath d='m9 12 2 2 4-4'/%3E%3C/svg%3E");

  /* Full-bleed colour block: a saturated band that breaks up the page.
     On the dark theme this is a *lighter* green than the page; on light it is
     a deep one. Either way it is the section that stops the scroll. */
  --brand-block:     #17342A;
  --brand-block-ink: #F4F1E9;

  /* The arrow badge inside a primary button. Dark ink, because it always sits
     on the gold fill in both themes. */
  --btn-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%230F2A20' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h13M12 5l7 7-7 7'/%3E%3C/svg%3E");

  /* Values that used to be hardcoded further down the file. They are tokens
     now because each one has to change between the dark and light themes, and
     a literal buried in a gradient cannot. */

  /* The base colour as raw channels, so scrims can be written
     rgba(var(--scrim-rgb), .78) instead of repeating the literal. */
  --scrim-rgb: 10, 21, 18;

  /* The gold BUTTON fill, kept separate from --gold. --gold is ink: it has to
     stay legible as link text, and on a light ground that means darkening it
     to bronze. A button's gold is a background, not ink, so it keeps the full
     brand value in both themes. Sharing one token made the light-mode button
     render dark-on-dark. */
  --gold-fill-from: var(--gold-bright);
  --gold-fill-to:   var(--gold);

  /* Ink printed ON the gold button fill. In dark mode that is the dark base;
     in light mode the base is pale, and light-on-gold is unreadable. */
  --btn-ink: var(--base);

  /* Photography treatment. Duotoning into the brand green is what stops a
     bright stock photo sitting on top of the palette; the brightness step is
     what has to change between themes, so the whole filter is a token. */
  --photo-filter:        grayscale(1) sepia(.5) hue-rotate(102deg) saturate(.9) brightness(.72) contrast(1.1);
  --photo-filter-hover:  grayscale(.15) sepia(.1) hue-rotate(102deg) saturate(1) brightness(.95) contrast(1.02);
  --hero-photo-filter:   grayscale(1) sepia(.55) hue-rotate(102deg) saturate(1.35) brightness(.42) contrast(1.12);
  --head-photo-filter:   grayscale(1) sepia(.55) hue-rotate(102deg) saturate(1.3) brightness(.32) contrast(1.08);
  /* Map tiles are inverted for the dark theme; in light mode they must not be. */
  --map-filter:          grayscale(1) invert(1) brightness(.92) contrast(.88) sepia(.42) hue-rotate(104deg) saturate(1.5);

  /* Film grain. Barely-there texture on dark; near-invisible on paper. */
  --grain-opacity: 0.035;

  /* The select chevron is drawn inside a data URI, and a data URI cannot
     read a custom property — so the entire URL is the token. Brand gold is
     invisible against a white field (about 2.4:1), which is exactly how the
     arrow vanished when the light theme was first switched on. */
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23C9A227' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");

  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  --font-display: 'Cormorant Garamond', Georgia, serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
}

/* ==========================================================================
   Light theme
   --------------------------------------------------------------------------
   A dark page is hard to read in daylight. The screen cannot out-emit the sun,
   and the glass reflects the sky straight back at you; light text on a dark
   ground is exactly the case that suffers most, because the reflection fills
   in the dark pixels and collapses the contrast that looked fine indoors.
   Measured, the dark palette is not short of contrast at all — body text sits
   at 13:1 and headings at 16:1. Adding contrast would not have helped. What is
   needed is a bright ground, so there is a light theme.

   Every value below was derived by script and checked against WCAG AA rather
   than picked by eye — see tools/contrast-check.mjs, which fails the build if
   any pair drops under 4.5:1. The gold in particular cannot simply carry over:
   brand gold #C9A227 on paper measures about 2.4:1, so link gold here is a
   darkened #7F6619, snapped against the DARKEST surface it can land on rather
   than against white. The brand gold survives untouched as a button FILL,
   where it is a background rather than ink.

   Selection: light is the DEFAULT. The inline script in each page's <head>
   writes data-theme on <html> before first paint, reading a stored choice and
   falling back to light — so there is one light block here rather than a
   [data-theme] rule and a near-identical prefers-color-scheme copy that would
   silently drift apart.

   The operating-system preference is deliberately NOT consulted. "OS dark
   mode" does not mean "I am indoors"; someone reading this on a phone in
   daylight has the same problem whichever way that setting points, and the
   complaint this theme exists to answer was about sunlight. A visitor who
   wants dark can say so with the toggle, and that choice is remembered.

   With JavaScript disabled no attribute is written and the dark theme applies.
   That is an accepted trade: the dark palette is fully AA-compliant, so the
   page is legible, just darker than ideal.
   ========================================================================== */

:root[data-theme="light"] {
  /* White ground. Raised surfaces carry the faintest green cast rather than a
     grey, so panels separate without introducing a fourth neutral. */
  --base:        #FFFFFF;
  --surface-1:   #F7FAF8;
  --surface-2:   #EEF4F0;
  --surface-3:   #E0EAE4;

  /* Gold. The fill keeps full brand gold; the ink is darkened so it survives
     as link text. Snapped against --surface-3, the darkest thing it lands on. */
  --gold:        #7D6418;
  --gold-bright: #604E13;
  --gold-deep:   #A98A1F;
  --gold-veil:   rgba(125, 100, 24, 0.09);
  --gold-line:   rgba(125, 100, 24, 0.28);
  --gold-fill-from: #E9CE77;
  --gold-fill-to:   #C9A227;

  /* Green carries the brand in type: headings are deep green, not black. */
  --ivory:       #0F2A20;
  --text:        #23342C;
  --text-muted:  #5D6B64;
  --text-faint:  #606A65;
  --green-ink:   #1E7657;

  --line:        rgba(15, 42, 32, 0.10);
  --line-strong: rgba(15, 42, 32, 0.20);

  --ok:          #22754D;
  --warn:        #8D5E14;
  --info:        #326D97;
  --danger:      #B5412D;

  --scrim-rgb:   255, 255, 255;
  --btn-ink:     #0F2A20;

  /* Bright gold tile with deep-green ink — the high-contrast anchor that makes
     a card grid read as lively rather than as a list. */
  --tile-bg:  #F6E4A6;
  --tile-ink: #5A4A12;
  --badge-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%235A4A12' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 2 4 5.5V11c0 5 3.4 9.3 8 11 4.6-1.7 8-6 8-11V5.5Z'/%3E%3Cpath d='m9 12 2 2 4-4'/%3E%3C/svg%3E");

  /* The colour block is a deep green band inside an otherwise white page. */
  --brand-block:     #143026;
  --brand-block-ink: #FFFFFF;

  /* Photography runs unfiltered. The duotone existed to stop a bright photo
     fighting a dark page; on white there is nothing to fight, and tinting a
     photograph green only made it look muddy. Where text used to sit on top of
     an image, the text has been moved beside it instead — see the hero and
     page-header rules below — so nothing needs an overlay to stay readable. */
  --photo-filter:       none;
  --photo-filter-hover: none;
  --hero-photo-filter:  none;
  --head-photo-filter:  none;
  --map-filter:         saturate(1.02);

  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%235A4A12' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");

  /* Grain is texture on a dark field and dirt on a white one. */
  --grain-opacity: 0;

  color-scheme: light;
}

/* ==========================================================================
   Light theme — structure
   --------------------------------------------------------------------------
   The dark theme puts text on top of photographs and relies on a scrim to keep
   it readable. Running the photography unfiltered means that scrim has to go,
   and text over a bare photograph is not readable at any contrast the palette
   can supply — a headline crossing a bright sky and a dark hull cannot pass
   both. So the text moves OFF the image: the hero becomes type on white beside
   a full-bleed photograph, and page headers put their image below the title
   rather than behind it. Nothing is overlaid, and nothing needs to be.

   All of this is CSS only. The markup is shared with the dark theme, which
   still uses the original stacked treatment.
   ========================================================================== */

/* ---- Hero: type left on white, photograph right, full bleed ---- */
:root[data-theme="light"] .hero {
  /* Full height is what gives the headline and the buttons room above the stat
     strip, which is absolutely positioned along the bottom. Collapsing this to
     min-height:0 clips the call to action clean off the page. */
  min-height: 100svh;
  padding-block: 9rem 0;
}
:root[data-theme="light"] .hero__bg {
  left: auto;
  right: 0;
  /* Inset below the header. The header is transparent until you scroll, so a
     photograph running to the top edge puts the navigation on top of a bright
     image where it cannot be read. */
  top: 6.25rem;
  bottom: 8.5rem;               /* clear of the stat strip */
  width: min(46%, 820px);
  height: auto;
  z-index: 0;
  /* An arch rather than a rectangle. The curve is what stops a large
     photograph reading as a slab, and it echoes the pill buttons. */
  border-radius: 280px 0 0 24px;
  overflow: hidden;
  box-shadow: 0 30px 70px -40px rgba(15, 42, 32, .5);
}
:root[data-theme="light"] .hero__bg img { height: 100%; object-fit: cover; }
/* The scrim is what made text-on-photo work. There is no text on the photo now. */
:root[data-theme="light"] .hero__bg::after { display: none; }
:root[data-theme="light"] .hero__inner { max-width: 40rem; padding-bottom: 6rem; }
:root[data-theme="light"] .hero .lede { max-width: 32rem; margin-bottom: 2rem; }

/* Smaller than the dark theme's headline, and heavier. Presence at display
   size comes from weight far more than from size, and the 100px version
   overflowed a 800px-tall viewport badly enough to hide the buttons behind the
   stat strip — which is every ordinary laptop. Voltex, for reference, sets its
   hero headline at 56px. */
:root[data-theme="light"] .hero h1 {
  font-size: clamp(2.4rem, 4.2vw, 3.9rem);
  line-height: 1.08;
  margin-bottom: 1.25rem;
}

/* The lane canvas would cross the photograph and read as scratches on it.
   Clipped to the type side, where it belongs. */
:root[data-theme="light"] .hero__lanes { width: 56%; }

:root[data-theme="light"] .hero__stats {
  background: var(--surface-1);
  border-top-color: var(--line);
}
:root[data-theme="light"] .scroll-cue { display: none; }

@media (max-width: 900px) {
  /* One column: headline, then the photograph as a card, then the figures.
     order is what puts the image after the text without touching the markup. */
  :root[data-theme="light"] .hero {
    display: flex;
    flex-direction: column;
    padding-block: 7rem 0;
  }
  :root[data-theme="light"] .hero .wrap  { order: 1; }
  /* The padding exists to clear the absolutely-positioned stat strip. In this
     single-column layout the strip is in normal flow, so it only leaves a gap. */
  :root[data-theme="light"] .hero__inner { padding-bottom: 0; }
  :root[data-theme="light"] .hero__bg    { order: 2; }
  :root[data-theme="light"] .hero__stats { order: 3; }
  :root[data-theme="light"] .hero__bg {
    position: relative;
    inset: auto;
    width: auto;
    height: clamp(240px, 40vh, 350px);
    margin: 2.5rem var(--gutter) 0;
    border-radius: 160px 160px 20px 20px;
  }
  :root[data-theme="light"] .hero__lanes { display: none; }
}

/* ---- Page headers: title on white, photograph beneath ---- */
:root[data-theme="light"] .page-head {
  display: flex;
  flex-direction: column;
  padding-block: clamp(8rem, 14vh, 10rem) 0;
  border-bottom: 0;
}
:root[data-theme="light"] .page-head .wrap { order: 1; }
:root[data-theme="light"] .page-head__bg {
  order: 2;
  position: relative;
  inset: auto;
  /* Held to the content column rather than full bleed. Two reasons: it reads
     as a composed panel beside the type instead of a banner, and it roughly
     halves the width these images have to cover — several of the source files
     are under 1000px wide and were being upscaled past the point where the
     softness shows, which the old duotone used to disguise. */
  width: min(100% - (var(--gutter) * 2), 1240px);
  margin: clamp(2rem, 4vw, 3rem) auto 0;
  /* Shape, not a fixed height. This band was a 1240x300 strip — 4.1:1 at every
     screen width — while the photographs are 1.2:1 to 1.8:1, so `cover` could
     only ever show 29-44% of each one's height: always the middle slice, which
     is how the vault page ended up showing a hinge and the about page a
     maker's plate. 2.4:1 roughly doubles what survives. The max-height stops
     a very wide screen growing it into a wall. */
  height: auto;
  aspect-ratio: 12 / 5;
  max-height: 520px;
  border-radius: var(--radius-card);
  overflow: hidden;
  box-shadow: 0 24px 60px -36px rgba(15, 42, 32, .45);
}
:root[data-theme="light"] .page-head__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Where the subject sits. Set per page with style="--focus: 50% 70%" on the
     band, so each photo is cropped around what it is actually of rather than
     around its geometric centre. */
  object-position: var(--focus, 50% 50%);
}
@media (max-width: 760px) {
  /* On a phone a 2.4:1 band is a sliver; a taller shape keeps the subject. */
  :root[data-theme="light"] .page-head__bg { aspect-ratio: 16 / 10; }
}
:root[data-theme="light"] .page-head__bg::after { display: none; }

/* ---- Body images ---- */
:root[data-theme="light"] .img-frame,
:root[data-theme="light"] .media {
  border: 0;
  box-shadow: 0 18px 44px -28px rgba(15, 42, 32, .4);
}
/* The hover treatment was a filter change; with no filter it is a slow zoom. */
:root[data-theme="light"] .media:hover img { transform: scale(1.04); }

/* ---- Form fields ---- */
:root[data-theme="light"] .input,
:root[data-theme="light"] .select,
:root[data-theme="light"] .textarea {
  /* background-color, never the `background` shorthand: the shorthand resets
     background-image to none, and .select draws its chevron with one. */
  background-color: var(--base);
  border-color: var(--line-strong);
}
:root[data-theme="light"] .input:focus,
:root[data-theme="light"] .select:focus,
:root[data-theme="light"] .textarea:focus {
  background-color: var(--base);
  border-color: var(--gold);
}

/* ---- Green earns its place in the palette ---- */
:root[data-theme="light"] .eyebrow { color: var(--green-ink); }
:root[data-theme="light"] .eyebrow::before { background: var(--green-ink); opacity: .85; }
:root[data-theme="light"] .section--surface { background: var(--surface-2); }
:root[data-theme="light"] .timeline__item.is-done::before { background: var(--green-ink); border-color: var(--green-ink); }

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  scroll-padding-top: 88px;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background-color: var(--base);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.7;
  font-weight: 350;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }
a { color: var(--gold); text-decoration: none; transition: color .3s var(--ease); }
a:hover { color: var(--gold-bright); }
button { font: inherit; color: inherit; }
[hidden] { display: none !important; }

::selection { background: var(--gold); color: var(--btn-ink); }

/* Film grain over the whole page — stops large dark fields from banding
   and gives the deep green some texture instead of reading as flat fill. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9999;
  pointer-events: none;
  opacity: var(--grain-opacity);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- Typography ---------- */
h1, h2, h3, h4 {
  font-family: var(--font-display);
  /* 600 rather than 400. The lighter weight reads as restraint at small sizes
     and as timidity at display sizes; the extra weight is most of what gives a
     headline presence. */
  font-weight: 600;
  color: var(--ivory);
  line-height: 1.12;
  letter-spacing: -0.01em;
  margin: 0 0 .5em;
}

h1 { font-size: clamp(2.75rem, 7vw, 5.25rem); }
h2 { font-size: clamp(2rem, 4.5vw, 3.5rem); }
h3 { font-size: clamp(1.4rem, 2.4vw, 2rem); }
h4 { font-size: 1.2rem; }

p { margin: 0 0 1.25rem; }
p:last-child { margin-bottom: 0; }

.lede {
  font-size: clamp(1.05rem, 1.6vw, 1.3rem);
  line-height: 1.65;
  color: var(--text);
  font-weight: 300;
}

/* Small uppercase label that sits above a heading */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .75rem;
  font-size: .7rem;
  font-weight: 500;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 1.25rem;
}
/* A small asterisk rather than a rule. It reads as a mark of emphasis instead
   of a divider, and it survives being placed centred as well as left. */
.eyebrow::before {
  content: "*";
  width: auto;
  height: auto;
  background: none;
  font-size: 1.15em;
  line-height: 0;
  opacity: 1;
  transform: translateY(.06em);
}
.eyebrow--center { justify-content: center; }

.muted { color: var(--text-muted); }
.gold  { color: var(--gold); }

.rule {
  height: 1px;
  border: 0;
  background: linear-gradient(90deg, var(--gold-line), transparent);
  margin: 2rem 0;
}

/* ---------- Layout ---------- */
.wrap {
  width: 100%;
  max-width: 1240px;
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--narrow { max-width: 820px; }

.section { padding-block: var(--section-y); }
.section--tight { padding-block: clamp(3rem, 6vw, 5rem); }
.section--surface { background: var(--surface-1); }

/* Full-bleed colour block. The single most effective way to stop a page
   reading as one long white scroll. */
.section--brand {
  background: var(--brand-block);
  color: var(--brand-block-ink);
  position: relative;
  overflow: hidden;
}
.section--brand h2,
.section--brand h3,
.section--brand h4 { color: var(--brand-block-ink); }
.section--brand .muted,
.section--brand p { color: rgba(244, 241, 233, .74); }
/* Literal, not a token. The colour block is dark in BOTH themes, so its ink
   cannot come from tokens that flip with the theme — the light theme's gold is
   darkened for legibility on white and disappears against this ground. */
:root .section--brand .eyebrow,
:root[data-theme="light"] .section--brand .eyebrow { color: #E3C765; }
/* Qualified with :root so this outranks the theme's own `.card` rule.
   Without it the card kept its white background while the text turned white —
   invisible. A colour-block section inverts the ground, so anything that
   paints a surface inside it has to be re-stated at matching specificity. */
:root .section--brand .card,
:root[data-theme="light"] .section--brand .card {
  background: rgba(244, 241, 233, .07);
  border-color: rgba(244, 241, 233, .14);
  box-shadow: none;
}
:root .section--brand .card:hover,
:root[data-theme="light"] .section--brand .card:hover {
  background: rgba(244, 241, 233, .11);
  border-color: var(--gold-line);
  box-shadow: 0 24px 50px -30px rgba(0, 0, 0, .6);
}
:root .section--brand .card p,
:root[data-theme="light"] .section--brand .card p { color: rgba(244, 241, 233, .72); }
:root .section--brand .card h3,
:root[data-theme="light"] .section--brand .card h3 { color: var(--brand-block-ink); }
:root .section--brand .stat__num,
:root[data-theme="light"] .section--brand .stat__num { color: #E3C765; }
:root .section--brand .btn--ghost,
:root[data-theme=light] .section--brand .btn--ghost {
  border-color: rgba(244, 241, 233, .38);
  color: #FFFFFF;
}
:root .section--brand .btn--ghost:hover,
:root[data-theme=light] .section--brand .btn--ghost:hover {
  border-color: #E3C765;
  color: #E3C765;
  background: rgba(227, 199, 101, .1);
}

.section-head { max-width: 44rem; margin-bottom: clamp(2.5rem, 5vw, 4rem); }
.section-head--center { margin-inline: auto; text-align: center; }

.grid { display: grid; gap: clamp(1.5rem, 3vw, 2.5rem); }

/* A grid track sized `1fr` carries an implicit `min-width: auto`, so it will
   refuse to shrink below the min-content width of whatever is inside it — one
   long word, a wide input, or a nested two-column field row is enough to push
   the track wider than the page and take the whole layout with it. Pinning
   grid children to `min-width: 0` lets the track shrink as intended; the
   children then wrap or scroll inside their own box instead. */
.grid > *,
.split > * { min-width: 0; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }

.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
}

@media (max-width: 900px) {
  .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .split { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
  .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  padding: 1rem 2rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  font-size: .78rem;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  cursor: pointer;
  transition: all .35s var(--ease);
  background: none;
}

.btn--gold {
  background: linear-gradient(135deg, var(--gold-fill-from), var(--gold-fill-to));
  color: var(--btn-ink);
  font-weight: 600;
}
.btn--gold:hover {
  color: var(--btn-ink);
  box-shadow: 0 8px 30px -8px rgba(201, 162, 39, .55);
  transform: translateY(-2px);
}

.btn--ghost {
  border-color: var(--line-strong);
  color: var(--ivory);
}
.btn--ghost:hover {
  border-color: var(--gold);
  color: var(--gold);
  background: var(--gold-veil);
}

/* Primary buttons carry a circular arrow badge. The extra right padding is
   what makes room for it — the same trick the badge pattern always uses.
   ::before, because ::after is already the hover shimmer in motion.css. */
.btn--gold { padding-right: 3.9rem; }
.btn--gold::before {
  content: "";
  position: absolute;
  right: .55rem;
  top: 50%;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background-color: rgba(15, 42, 32, .16);
  background-image: var(--btn-arrow);
  background-repeat: no-repeat;
  background-position: center;
  transform: translateY(-50%);
  transition: transform .4s var(--ease), background-color .3s var(--ease);
}
.btn--gold:hover::before {
  transform: translateY(-50%) translateX(4px);
  background-color: rgba(15, 42, 32, .26);
}

.btn--sm { padding: .6rem 1.25rem; font-size: .7rem; }
.btn--sm.btn--gold { padding-right: 2.9rem; }
.btn--sm.btn--gold::before { width: 24px; height: 24px; right: .4rem; background-size: 11px; }
.btn[disabled] { opacity: .45; pointer-events: none; }

/* Text link with an arrow that slides on hover */
.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: .78rem;
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.link-arrow svg { transition: transform .35s var(--ease); }
.link-arrow:hover svg { transform: translateX(5px); }

/* ---------- Cards ---------- */
.card {
  position: relative;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius-card);
  padding: clamp(1.75rem, 3vw, 2.5rem);
  transition: border-color .4s var(--ease), transform .4s var(--ease), background .4s var(--ease);
  overflow: hidden;
}
.card:hover {
  border-color: var(--gold-line);
  background: var(--surface-2);
  transform: translateY(-4px);
}

/* Gold line that wipes across the top edge on hover */
.card--accent::before {
  content: "";
  position: absolute;
  inset: 0 auto auto 0;
  height: 2px;
  width: 0;
  background: linear-gradient(90deg, var(--gold), transparent);
  transition: width .5s var(--ease);
}
.card--accent:hover::before { width: 100%; }

/* The glyph sits in a tinted tile rather than floating on the card. It gives
   each card a fixed, high-contrast anchor point for the eye to land on. */
.card__icon {
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  border-radius: var(--tile-radius);
  background: var(--tile-bg);
  color: var(--tile-ink);
  margin-bottom: 1.5rem;
  transition: transform .45s var(--ease), background .35s var(--ease);
}
.card__icon svg { width: 27px; height: 27px; }
.card:hover .card__icon { transform: translateY(-3px) rotate(-6deg); }

.card h3 { font-size: 1.45rem; margin-bottom: .6rem; }
.card p { font-size: .95rem; color: var(--text-muted); margin: 0; }

/* Framed editorial image. Duotoned into the brand green so photography
   sits inside the palette instead of floating on top of it. Used by every
   inline image in the page body — change the treatment here, not in the
   markup. */
/* Body images take one shape. Left to their natural ratios the source files
   ranged from 1.06:1 to 1.8:1, so paired sections sat at different heights and
   a tall photo stretched its card well past the text beside it. A fixed 4:3
   with cover crops each around its --focus point instead, set per image with
   style="--focus: 60% 40%" where the subject is not central. */
.img-frame,
.media img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  object-position: var(--focus, 50% 50%);
}

.img-frame {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* brightness() is what stops a bright photo (an aircraft against sky)
     turning pale mint — the hue-rotate alone leaves highlights too hot. */
  filter: var(--photo-filter);
}

/* ---------- Forms ---------- */
.field { margin-bottom: 1.1rem; }
.field label {
  display: block;
  font-size: .68rem;
  font-weight: 500;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: .55rem;
}

.input, .select, .textarea {
  width: 100%;
  padding: .9rem 1.1rem;
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--ivory);
  font-family: inherit;
  font-size: .95rem;
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.input::placeholder, .textarea::placeholder { color: var(--text-faint); }
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--gold);
  background: var(--surface-2);
}
.textarea { resize: vertical; min-height: 120px; }

.select {
  appearance: none;
  -webkit-appearance: none;
  background-image: var(--select-chevron);
  background-repeat: no-repeat;
  background-position: right 1.1rem center;
  padding-right: 2.75rem;
  cursor: pointer;
}
.select option { background: var(--surface-2); color: var(--ivory); }

/* Qualified with the element name on purpose. These sit inside .field, whose
   `.field label` rule is more specific than a bare `.checkbox` class and was
   winning — turning the option into an uppercase block label with no gap, so
   the text ran straight into its own tickbox. `label.checkbox` matches that
   specificity and, coming later in the file, takes it. */
label.checkbox {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  font-size: .85rem;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-muted);
  cursor: pointer;
  margin-bottom: 0;
}
.checkbox input {
  margin-top: .28rem;
  accent-color: var(--gold);
  width: 15px; height: 15px;
  flex-shrink: 0;
  cursor: pointer;
}

.form-note {
  margin-top: 1rem;
  padding: .85rem 1.1rem;
  border-radius: 2px;
  font-size: .88rem;
  border: 1px solid var(--gold-line);
  background: var(--gold-veil);
  color: var(--gold-bright);
}
.form-note--error {
  border-color: rgba(217, 112, 95, .4);
  background: rgba(217, 112, 95, .1);
  color: var(--danger);
}

/* ---------- Header / nav ---------- */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 1000;
  padding-block: 1.25rem;
  transition: background .45s var(--ease), padding .45s var(--ease), border-color .45s var(--ease);
  border-bottom: 1px solid transparent;
}
/* The frosted bar is painted by a pseudo-element, NOT by the header itself.
   An element with a backdrop-filter becomes the containing block for its
   position:fixed descendants, and the mobile menu is one of those — put the
   blur back on .site-header and the drawer collapses to the height of the
   header the moment the page is scrolled. */
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: 0;
  background: rgba(var(--scrim-rgb), .88);
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
  transition: opacity .45s var(--ease);
  pointer-events: none;
}
.site-header.is-stuck::before { opacity: 1; }
.site-header.is-stuck {
  border-bottom-color: var(--line);
  padding-block: .85rem;
}

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
}

.brand { display: flex; align-items: center; gap: .7rem; }
.brand svg { height: 34px; width: auto; flex-shrink: 0; }
.brand__name {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--ivory);
  line-height: 1;
  white-space: nowrap;
}
.brand__name em { font-style: normal; color: var(--gold); }

.nav__links {
  display: flex;
  align-items: center;
  gap: 2.25rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.nav__links a {
  position: relative;
  font-size: .76rem;
  font-weight: 400;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text);
  padding-block: .35rem;
}
.nav__links a::after {
  content: "";
  position: absolute;
  left: 0; bottom: 0;
  width: 0; height: 1px;
  background: var(--gold);
  transition: width .35s var(--ease);
}
.nav__links a:hover { color: var(--gold); }
.nav__links a:hover::after,
.nav__links a[aria-current="page"]::after { width: 100%; }
.nav__links a[aria-current="page"] { color: var(--gold); }

/* Theme toggle. Sits left of the burger and stays visible at every width —
   the reader who needs it most is the one outdoors on a phone. */
.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  margin-left: auto;
  padding: 0;
  background: none;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  color: var(--text);
  cursor: pointer;
  flex-shrink: 0;
  transition: color .3s var(--ease), border-color .3s var(--ease), background .3s var(--ease);
}
.theme-toggle:hover,
.theme-toggle:focus-visible {
  color: var(--gold);
  border-color: var(--gold);
  background: var(--gold-veil);
}
.theme-toggle svg { display: block; width: 17px; height: 17px; }

/* Show the icon for the theme you would switch TO, not the one you are in:
   the button advertises its action rather than describing the status quo. */
.theme-toggle__sun  { display: none; }
.theme-toggle__moon { display: block; }
:root[data-theme="dark"] .theme-toggle__sun  { display: block; }
:root[data-theme="dark"] .theme-toggle__moon { display: none; }

/* The nav sits right of the toggle; margin-left:auto on the toggle pushes
   both away from the wordmark. */
.nav__links { margin-left: 1.5rem; }
@media (min-width: 961px) { .theme-toggle { order: 2; } .nav__links { order: 3; } }

.nav__toggle {
  display: none;
  background: none;
  border: 1px solid var(--line-strong);
  border-radius: 2px;
  padding: .55rem .7rem;
  cursor: pointer;
  color: var(--ivory);
  z-index: 1001;
}
.nav__toggle svg { display: block; width: 20px; height: 20px; }

@media (max-width: 960px) {
  .nav__toggle { display: block; }
  .nav__links {
    position: fixed;
    inset: 0 0 0 auto;
    width: min(320px, 82vw);
    flex-direction: column;
    align-items: flex-start;
    justify-content: center;
    /* The Sign in link is appended after the session is known, so the drawer
       has to cope with one more item than the markup shows — and a phone held
       sideways is short. `safe` keeps the first item reachable when the list
       is taller than the drawer; plain centring would push it out of reach. */
    overflow-y: auto;
    gap: 1.75rem;
    padding: 2rem var(--gutter);
    background: var(--surface-1);
    border-left: 1px solid var(--line);
    transform: translateX(100%);
    transition: transform .45s var(--ease);
  }
  @supports (justify-content: safe center) {
    .nav__links { justify-content: safe center; }
  }
  .nav__links.is-open { transform: translateX(0); }
  /* Under the header, so the logo and the close button stay on top of it. */
  .nav__links { z-index: 999; }

  /* Dims the page the drawer is covering, so the menu reads as a layer over
     the site rather than as a panel tacked onto the side of it. */
  .nav-scrim {
    position: fixed;
    inset: 0;
    z-index: 998;
    background: rgba(var(--scrim-rgb), .5);
    opacity: 0;
    pointer-events: none;
    transition: opacity .45s var(--ease);
  }
  body.nav-open .nav-scrim { opacity: 1; pointer-events: auto; }
  .nav__links a { font-size: .95rem; }
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-block: 9rem 7rem;
  overflow: hidden;
}
.hero__bg { position: absolute; inset: 0; z-index: -2; }
.hero__bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* Duotone the photo into the brand green: strip colour, reintroduce it
     via sepia, then rotate that warm tone round to green. A plain
     grayscale image cannot be hue-rotated — there is no saturation to
     shift — which is why the sepia step is needed first. */
  filter: var(--hero-photo-filter);
}
/* Two-stop scrim keeps the headline readable over any photo */
.hero__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, var(--base) 8%, rgba(var(--scrim-rgb), .78) 45%, rgba(var(--scrim-rgb), .35) 100%),
    linear-gradient(to top, var(--base), transparent 45%);
}

.hero__inner { max-width: 46rem; }
.hero h1 { margin-bottom: 1.5rem; }
.hero h1 em { font-style: italic; color: var(--gold); }
.hero .lede { max-width: 34rem; margin-bottom: 2.5rem; }
.hero__actions { display: flex; flex-wrap: wrap; gap: 1rem; }

/* Floating credential card over the hero photograph. Hidden below the point
   where the hero stacks — at that width it would sit on top of the headline. */
.hero__badge {
  position: absolute;
  z-index: 2;
  left: calc(50% + 1rem);
  bottom: 11rem;
  display: flex;
  align-items: center;
  gap: .85rem;
  padding: .9rem 1.25rem;
  border-radius: var(--radius-card);
  background: rgba(var(--scrim-rgb), .92);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid var(--line);
  box-shadow: 0 24px 50px -24px rgba(0, 0, 0, .45);
  max-width: 21rem;
}
.hero__badge__tile {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: 12px;
  background: var(--tile-bg);
  /* The glyph is a background image so the card needs no extra markup. */
  background-image: var(--badge-glyph);
  background-repeat: no-repeat;
  background-position: center;
}
.hero__badge__text { display: grid; gap: .1rem; line-height: 1.3; }
.hero__badge__text b {
  font-size: .85rem;
  font-weight: 600;
  color: var(--ivory);
}
.hero__badge__text small {
  font-size: .74rem;
  color: var(--text-muted);
}
/* Small screens. The badge used to be hidden below 900px, because its desktop
   position would have put it on top of the headline. It now moves instead.

   Width is capped to leave room for its ±14px bounce: at full width the swing
   would carry it past the screen edge, where .hero's overflow:hidden would
   clip it mid-card. */
@media (max-width: 900px) {
  /* Dark theme and no-JS: from 900px down the hero drops into normal flow —
     headline, buttons, badge, then the stat strip — as it already did below
     760px. The first version kept the badge absolutely positioned above the
     stat strip between 761 and 900px; measured on a short 880x700 screen it
     sat on the buttons, because an absolutely positioned element cannot know
     how many lines the headline has wrapped to. In flow, each part takes the
     room it actually needs.

     The stat strip goes static here for the light theme too. Pinned to the
     hero's bottom edge between 761 and 900px, it overlapped whatever came last
     whenever the screen was short — which, once the badge is shown, is the
     badge. */
  .hero { display: block; min-height: 0; padding-block: 8rem 0; }
  /* `.hero .hero__stats`, not `.hero__stats`. This block sits earlier in the
     file than the base rule that pins the strip with position:absolute; at
     equal specificity the later rule wins, so a bare class here silently lost
     between 761 and 900px while the margin beside it applied. The extra class
     makes it win regardless of where either rule lives. */
  .hero .hero__stats { position: static; margin-top: 3rem; }
  .hero__badge {
    position: relative;
    left: auto;
    bottom: auto;
    width: fit-content;
    margin: 2rem var(--gutter) 0;
  }
}
@media (max-width: 900px) {
  /* Light theme: one column, with the photograph as a card. The badge takes the
     photograph's own flex order (2). Ties fall back to source order and the
     photograph comes first in the markup, so the badge lands directly beneath
     it; the negative margin then pulls it up to straddle the photo's bottom
     edge, the way it straddles the photo's side on desktop. */
  :root[data-theme="light"] .hero__badge {
    position: relative;
    order: 2;
    left: auto;
    bottom: auto;
    align-self: center;
    width: fit-content;
    margin: -2.75rem var(--gutter) 0;
  }
}

/* The scroll cue and the badge occupy the same strip along the bottom of the
   hero and collide. The badge earns its place; the cue is decoration, so it
   stands down wherever a badge is present. */
.hero:has(.hero__badge) .scroll-cue { display: none; }

/* Stat strip pinned to the bottom of the hero */
.hero__stats {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  border-top: 1px solid var(--line);
  background: rgba(var(--scrim-rgb), .55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
.hero__stats .wrap {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1rem;
  padding-block: 1.5rem;
}
@media (max-width: 760px) {
  /* The hero is a flex row so the headline can be vertically centred against
     a full-height photograph. On a phone the stat strip stops being absolute
     and would then become a flex SIBLING of the headline — sitting beside the
     text and pushing the page into horizontal overflow. Switching the hero to
     block layout puts the strip back underneath, where it belongs. */
  .hero { display: block; min-height: 0; padding-block: 8rem 0; }
  .hero__stats {
    position: static;
    background: var(--surface-1);
    margin-top: 3rem;
  }
  .hero__stats .wrap { grid-template-columns: repeat(2, 1fr); gap: 1.5rem 1rem; }
}

.stat__num {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  color: var(--gold);
  line-height: 1;
  display: block;
}
/* The figure style is sized for two or three digits. A phrase in it wraps and
   the row goes ragged, so a text variant steps the size down and stops it. */
.stat__num--text {
  font-size: clamp(1.05rem, 1.5vw, 1.35rem);
  line-height: 1.25;
  letter-spacing: .01em;
  text-wrap: balance;
}

.stat__label {
  font-size: .66rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: .5rem;
  display: block;
}

/* ---------- Interior page header ---------- */
.page-head {
  position: relative;
  padding-block: clamp(9rem, 16vw, 13rem) clamp(3rem, 6vw, 5rem);
  border-bottom: 1px solid var(--line);
  overflow: hidden;
}
.page-head__bg { position: absolute; inset: 0; z-index: -1; }
.page-head__bg img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: var(--focus, 50% 50%);
  /* Same duotone treatment as the hero, held darker. */
  filter: var(--head-photo-filter);
}
.page-head__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, var(--base), rgba(var(--scrim-rgb), .6));
}
.page-head h1 { font-size: clamp(2.5rem, 6vw, 4.5rem); margin-bottom: .5rem; }
.page-head p { color: var(--text-muted); max-width: 40rem; }

.crumbs {
  font-size: .7rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 1.5rem;
}
.crumbs a { color: var(--text-muted); }
.crumbs a:hover { color: var(--gold); }
.crumbs span { margin-inline: .5rem; opacity: .5; }

/* ---------- Tracking timeline ---------- */
.timeline { position: relative; padding-left: 2.5rem; margin: 0; list-style: none; }
.timeline::before {
  content: "";
  position: absolute;
  left: 7px; top: 8px; bottom: 8px;
  width: 1px;
  background: var(--line-strong);
}
.timeline__item { position: relative; padding-bottom: 2.25rem; }
.timeline__item:last-child { padding-bottom: 0; }
.timeline__item::before {
  content: "";
  position: absolute;
  left: -2.5rem; top: 6px;
  width: 15px; height: 15px;
  border-radius: 50%;
  background: var(--base);
  border: 1px solid var(--line-strong);
  transition: all .3s var(--ease);
}
.timeline__item.is-done::before {
  border-color: var(--gold);
  background: var(--gold);
  box-shadow: 0 0 0 4px var(--gold-veil);
}
/* Current milestone pulses so the eye lands on it first */
.timeline__item.is-current::before {
  border-color: var(--gold-bright);
  background: var(--gold-bright);
  animation: pulse 2.2s ease-in-out infinite;
}
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 5px rgba(227, 199, 101, .22); }
  50%      { box-shadow: 0 0 0 11px rgba(227, 199, 101, 0); }
}
/* What the client reads about a movement. Set in the body face rather than the
   meta line's small caps: it is a sentence, not a label. */
.timeline__remark {
  margin: .5rem 0 0;
  font-size: .9rem;
  color: var(--text);
}

.timeline__status {
  font-family: var(--font-display);
  font-size: 1.3rem;
  color: var(--ivory);
  line-height: 1.2;
}
.timeline__item.is-current .timeline__status { color: var(--gold-bright); }
.timeline__meta {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem 1.25rem;
  font-size: .78rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: .45rem;
}

/* Horizontal progress rail above the timeline */
/* min-width:0 throughout: grid and flex children default to min-width:auto,
   which is min-content — the six stage labels then held the page open 97px
   wider than a phone viewport on every tracked consignment. */
.progress-rail { display: grid; gap: .75rem; margin-bottom: 2.5rem; min-width: 0; }
.progress-rail__track {
  height: 3px;
  background: var(--surface-3);
  border-radius: 2px;
  overflow: hidden;
}
.progress-rail__fill {
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--gold-deep), var(--gold-bright));
  border-radius: 2px;
  transition: width 1.4s var(--ease);
}
.progress-rail__labels {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  min-width: 0;
  font-size: .66rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.progress-rail__labels > span { min-width: 0; }

/* Shown only on phones, in place of the labels. */
.progress-rail__caption { display: none; }

@media (max-width: 600px) {
  .progress-rail__labels { display: none; }
  .progress-rail__caption {
    display: block;
    margin: 0;
    font-size: .7rem;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--gold-bright);
  }
}

/* ---------- Map ---------- */
.map-shell {
  position: relative;
  height: clamp(300px, 45vh, 460px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-1);
}

/* Controls float over the map. 500 clears Leaflet's own panes (400) but stays
   under its popups (700), so a popup is never covered by a button. */
.map-toolbar {
  position: absolute;
  z-index: 500;
  top: .7rem;
  right: .7rem;
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
}
.map-btn {
  padding: .45rem .75rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(var(--scrim-rgb), .92);
  color: var(--ivory);
  font-family: inherit;
  font-size: .64rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  cursor: pointer;
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
}
.map-btn:hover,
.map-btn:focus-visible {
  border-color: var(--gold-line);
  background: var(--gold-veil);
  color: var(--gold-bright);
}
.map-hint {
  position: absolute;
  z-index: 500;
  left: .7rem;
  bottom: 1.5rem;
  margin: 0;
  padding: .35rem .65rem;
  border-radius: 999px;
  background: rgba(var(--scrim-rgb), .92);
  color: var(--text-faint);
  font-size: .62rem;
  letter-spacing: .1em;
  text-transform: uppercase;
}

/* The route itself. Leaflet sets stroke as a presentation attribute, which
   CSS outranks, so the colour lives here with the rest of the palette. */
.route-remaining {
  fill: none;
  stroke: var(--gold);
  stroke-dasharray: 3 8;
  stroke-linecap: round;
}
.route-done {
  fill: none;
  stroke: var(--gold-bright);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.map-vehicle {
  position: relative;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 2px solid var(--gold);
  background: var(--base);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
  color: var(--gold-bright);
}
.map-vehicle svg {
  width: 16px;
  height: 16px;
  fill: currentColor;
}
.map-vehicle__halo {
  position: absolute;
  inset: -5px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(201, 162, 39, .45), transparent 70%);
}
#track-map { width: 100%; height: 100%; }
/* Desaturate + invert OSM tiles so the map sits inside the dark theme */
.leaflet-tile {
  filter: var(--map-filter);
}
.leaflet-container { background: var(--surface-1) !important; }
.leaflet-control-attribution {
  background: rgba(var(--scrim-rgb), .75) !important;
  color: var(--text-faint) !important;
  font-size: 10px !important;
}
.leaflet-control-attribution a { color: var(--text-muted) !important; }
.leaflet-bar a {
  background: var(--surface-2) !important;
  color: var(--ivory) !important;
  border-color: var(--line) !important;
}
.leaflet-bar a:hover { background: var(--surface-3) !important; }
.map-pin {
  position: relative;
  width: 14px; height: 14px;
  border-radius: 50%;
  border: 2px solid var(--base);
}
.map-pin__halo {
  position: absolute;
  inset: -2px;
  border-radius: 50%;
  background: rgba(227, 199, 101, .45);
}
.map-pin--origin { background: var(--text-muted); }
.map-pin--dest   { background: var(--gold); }
.map-pin--event  { background: var(--gold); }
.map-pin--now    { background: var(--gold-bright); box-shadow: 0 0 0 5px rgba(227,199,101,.25); }
/* Lifted while the matching entry in the movement history is hovered. */
.map-pin.is-linked {
  box-shadow: 0 0 0 6px rgba(227, 199, 101, .38);
  z-index: 620;
}

/* A movement the map can fly to. The button carries the whole entry, so the
   look is the list's and only the behaviour is the button's. */
.timeline__jump {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.timeline__item.is-linked .timeline__status,
.timeline__jump:hover .timeline__status,
.timeline__jump:focus-visible .timeline__status { color: var(--gold-bright); }

/* A destructive control that is not a button: it should be findable without
   competing with the actions someone actually came here to use. */
.link-danger {
  display: inline-block;
  padding: 0;
  border: 0;
  background: none;
  color: var(--danger);
  font-family: inherit;
  font-size: .78rem;
  letter-spacing: .04em;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.link-danger:hover { text-decoration-thickness: 2px; }
.link-danger:disabled { opacity: .5; cursor: default; }

/* ---------- Data table ---------- */
.data-table { width: 100%; border-collapse: collapse; font-size: .93rem; }
.data-table th, .data-table td {
  padding: .95rem 1.1rem;
  text-align: left;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.data-table th {
  width: 38%;
  font-weight: 500;
  font-size: .68rem;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.data-table td { color: var(--ivory); }
.data-table tr:last-child th, .data-table tr:last-child td { border-bottom: 0; }
.table-scroll { overflow-x: auto; }

/* ---------- Status pill ---------- */
.pill {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  padding: .35rem .85rem;
  border-radius: 100px;
  font-size: .68rem;
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  border: 1px solid currentColor;
}
.pill::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: currentColor;
}
.pill--transit   { color: var(--info); }
.pill--delivered { color: var(--ok); }
.pill--pending   { color: var(--warn); }
.pill--hold      { color: var(--danger); }

/* ---------- Panel ---------- */
.panel {
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.5rem, 3vw, 2.5rem);
}
/* A tile reporting something that wants attention.

   After .panel, not before it: that rule sets the `border` shorthand, which
   resets border-color at equal specificity, so a variant declared earlier is
   silently discarded.

   The signal is the border and the label, not the number — --warn in the light
   theme is #8D5E14, near enough to the gold ink that a tinted number reads as
   ordinary. The label sits on the plain panel surface, which is the pair
   "status warn on surface-1" already checks. */
.panel--alert { border-color: var(--warn); }
.panel--alert .stat__label { color: var(--warn); }

.panel--gold {
  border-color: var(--gold-line);
  background: linear-gradient(160deg, var(--gold-veil), transparent 55%), var(--surface-1);
}
.panel__title {
  font-size: .7rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 1.5rem;
  display: block;
}

/* ---------- Quote calculator ---------- */
.quote-figure {
  font-family: var(--font-display);
  font-size: clamp(3rem, 8vw, 4.75rem);
  line-height: 1;
  color: var(--gold);
}
.quote-figure small {
  font-family: var(--font-body);
  font-size: .72rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--text-muted);
  display: block;
  margin-bottom: .6rem;
}
.quote-breakdown { list-style: none; margin: 0; padding: 0; font-size: .9rem; }
.quote-breakdown li {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: .7rem 0;
  border-bottom: 1px solid var(--line);
  color: var(--text-muted);
}
.quote-breakdown li:last-child { border-bottom: 0; }
.quote-breakdown li span:last-child { color: var(--ivory); white-space: nowrap; }
.quote-breakdown li.is-total {
  border-top: 1px solid var(--gold-line);
  border-bottom: 0;
  margin-top: .5rem;
  padding-top: 1rem;
  color: var(--ivory);
  font-weight: 500;
}
.quote-breakdown li.is-total span:last-child { color: var(--gold); }

/* ---------- Vault ---------- */
.vault-tier {
  display: grid;
  gap: 1.25rem;
  align-content: start;
  padding: clamp(1.75rem, 3vw, 2.5rem);
  background: var(--surface-1);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  transition: border-color .4s var(--ease), transform .4s var(--ease);
}
.vault-tier:hover { border-color: var(--gold-line); transform: translateY(-4px); }
.vault-tier--feature {
  border-color: var(--gold-line);
  background: linear-gradient(165deg, var(--gold-veil), transparent 50%), var(--surface-1);
}
.vault-tier__price {
  font-family: var(--font-display);
  font-size: 2.75rem;
  color: var(--gold);
  line-height: 1;
}
.vault-tier__price span {
  font-family: var(--font-body);
  font-size: .8rem;
  color: var(--text-muted);
  letter-spacing: .1em;
}
.vault-tier ul { list-style: none; margin: 0; padding: 0; font-size: .9rem; }
.vault-tier li {
  padding: .55rem 0 .55rem 1.6rem;
  position: relative;
  color: var(--text-muted);
}
.vault-tier li::before {
  content: "";
  position: absolute;
  left: 0; top: 1rem;
  width: 8px; height: 8px;
  border-left: 1px solid var(--gold);
  border-bottom: 1px solid var(--gold);
  transform: rotate(-45deg);
}

/* Grid rather than flex-wrap: the value column stays right-aligned on its
   own row instead of dropping below the name when the name runs long. */
.holding-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: .5rem 1.5rem;
  padding: 1.1rem 0;
  border-bottom: 1px solid var(--line);
}
@media (max-width: 520px) {
  .holding-row { grid-template-columns: 1fr; }
}
.holding-row:last-child { border-bottom: 0; }
.holding-row__name { color: var(--ivory); font-size: 1rem; }
.holding-row__meta {
  font-size: .72rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.holding-row__value {
  font-family: var(--font-display);
  font-size: 1.4rem;
  color: var(--gold);
  white-space: nowrap;
  text-align: right;
}

/* ---------- Footer ---------- */
.site-footer {
  background: var(--surface-1);
  border-top: 1px solid var(--line);
  padding-block: clamp(3.5rem, 7vw, 5.5rem) 0;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr 1.4fr;
  gap: clamp(2rem, 4vw, 3.5rem);
}
@media (max-width: 900px) { .footer-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .footer-grid { grid-template-columns: 1fr; } }

.footer-grid h4 {
  font-family: var(--font-body);
  font-size: .68rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 1.25rem;
}
.footer-grid ul { list-style: none; margin: 0; padding: 0; }
.footer-grid li { margin-bottom: .7rem; }
.footer-grid a { color: var(--text-muted); font-size: .9rem; }
.footer-grid a:hover { color: var(--gold); }
.footer-grid p { font-size: .9rem; color: var(--text-muted); }

.footer-bottom {
  margin-top: clamp(3rem, 5vw, 4rem);
  padding-block: 1.75rem;
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  justify-content: space-between;
  font-size: .78rem;
  color: var(--text-faint);
}

/* ---------- Back to top ---------- */
.to-top {
  position: fixed;
  right: clamp(1rem, 3vw, 2rem);
  bottom: clamp(1rem, 3vw, 2rem);
  z-index: 900;
  width: 46px; height: 46px;
  display: grid;
  place-items: center;
  border: 1px solid var(--gold-line);
  border-radius: 50%;
  background: rgba(var(--scrim-rgb), .8);
  backdrop-filter: blur(8px);
  color: var(--gold);
  opacity: 0;
  pointer-events: none;
  transform: translateY(12px);
  transition: all .4s var(--ease);
}
.to-top.is-visible { opacity: 1; pointer-events: auto; transform: translateY(0); }
.to-top:hover { background: var(--gold); color: var(--base); }

/* ---------- Scroll reveal ---------- */
.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .9s var(--ease), transform .9s var(--ease);
}
.reveal.is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* ---------- Utilities ---------- */
.text-center { text-align: center; }
.mt-1 { margin-top: .5rem; }
.mt-2 { margin-top: 1rem; }
.mt-3 { margin-top: 1.5rem; }
.mt-4 { margin-top: 2rem; }
.mt-6 { margin-top: 3rem; }
.mb-0 { margin-bottom: 0; }
.mb-2 { margin-bottom: 1rem; }
.mb-4 { margin-bottom: 2rem; }
.flex { display: flex; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-1 { gap: .5rem; }
.gap-2 { gap: 1rem; }
.gap-3 { gap: 1.5rem; }

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

/* ---------- Print ---------- */
@media print {
  body { background: #fff; color: #000; }
  body::after { display: none; }
  .site-header, .site-footer, .to-top, .map-shell, .no-print { display: none !important; }
  .section, .page-head { padding-block: 1rem !important; }
  .panel, .card { border-color: #ccc; background: #fff; }
  h1, h2, h3, h4, .data-table td, .timeline__status { color: #000; }
  .data-table th, .muted, .timeline__meta { color: #444; }
}
