/* ==========================================================================
   nafen — public site
   ==========================================================================

   Separate from main.css on purpose. main.css dresses the internal platform:
   a neutral, rebrandable admin surface. This file is the brand — two colours,
   a script wordmark, and photography — and the two should never constrain each
   other.

   Structure
     1  Fonts
     2  Tokens
     3  Reset & base
     4  Texture
     5  Layout
     6  Typography
     7  Buttons & links
     8  Navigation
     9  Hero
    10  Cards, grids & sections
    11  Gallery
    12  Testimonials
    13  Forms
    14  Map
    15  Footer & sticky CTA
    16  Motion & preferences

   Colour discipline: there is no accent colour. On #0F2A24 a cream button is
   already a 13.5:1 event, and the narrowness of the palette is the premium
   signal — it is the one asset the brand already owns. Warmth comes from the
   photography, not from the UI.
   ========================================================================== */

/* == 1. Fonts ============================================================= */

/* Variable fonts, one file each — 118 KB for the whole type system. Italic is
   a separate file because Gambetta's italics are true drawings, not slants,
   and they carry the editorial voice for pull-quotes and captions. */

@font-face {
  font-family: 'Gambetta';
  src: url("../fonts/Gambetta-Variable.0be41c70945f.woff2") format('woff2-variations');
  font-weight: 300 700;
  font-style: normal;
  font-display: swap;
  /* Nudges the fallback's metrics towards Gambetta's so the swap does not
     reflow the page. Font-swap CLS is the likeliest Core Web Vitals failure on
     a type-led site. */
  size-adjust: 100%;
  ascent-override: 92%;
  descent-override: 24%;
}

@font-face {
  font-family: 'Gambetta';
  src: url("../fonts/Gambetta-VariableItalic.9dfda21dafa0.woff2") format('woff2-variations');
  font-weight: 300 700;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: 'Switzer';
  src: url("../fonts/Switzer-Variable.4523d2c62663.woff2") format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* == 2. Tokens ============================================================ */

:root {
  color-scheme: light dark;

  /* Brand — immutable */
  --nafen-forest: #0f2a24;
  --nafen-cream: #f4f1ea;
  --nafen-white: #ffffff;

  /* Forest ramp (hue 167) */
  --forest-950: #081714;
  --forest-900: #0f2a24;
  --forest-800: #143931;
  --forest-700: #1b4b41;
  --forest-600: #236154;
  --forest-500: #2e7a6a;
  --forest-400: #479483;
  --forest-300: #76b2a5;
  --forest-200: #afcfc8;
  --forest-100: #dae7e4;
  --forest-50: #f0f5f4;

  /* Sage — the on-dark neutral */
  --sage-900: #213a34;
  --sage-800: #344a44;
  --sage-700: #4b5e57;
  --sage-600: #61726b;
  --sage-500: #78867f; /* 4.01:1 on forest — UI borders */
  --sage-400: #949d97; /* 5.47:1 on forest — muted text */
  --sage-300: #afb5af; /* 7.30:1 */
  --sage-200: #cbcdc6;

  /* Stone — the on-light neutral */
  --stone-950: #22201b;
  --stone-900: #38352e;
  --stone-800: #534f46; /* 7.23:1 on cream — body */
  /* Muted text has to clear 4.5:1 on the darkest surface it is ever set on,
     which is --bg-band (stone-200), not the canvas. At #6f6a5d it measured
     4.48:1 there — a fail nobody can see and every audit reports. */
  --stone-700: #6d685b; /* 4.87:1 on cream, 4.62:1 on --bg-band — muted */
  --stone-650: #7e7768; /* 3.94:1 on cream — field borders */
  --stone-400: #c9c5ba;
  --stone-300: #dedbd3;
  --stone-200: #eceae4;
  --stone-100: #f3f1ed;
  --stone-50: #faf8f5;

  --error-ink: #8c3a2b;
  --error-on-dark: #e8b4a8;
  --success-ink: #236154;

  /* Semantic roles — light */
  --bg-canvas: var(--nafen-cream);
  --bg-surface: var(--stone-50);
  --bg-raised: var(--nafen-white);
  --bg-band: var(--stone-200);
  --bg-inverse: var(--forest-900);

  --text-primary: var(--forest-900);
  --text-secondary: var(--stone-800);
  --text-muted: var(--stone-700);
  --text-inverse: var(--nafen-cream);
  --text-link: var(--forest-600);

  --border-hairline: var(--stone-400);
  --border-subtle: var(--stone-300);
  --border-field: var(--stone-650);
  --border-strong: var(--forest-900);

  --focus-ring: var(--forest-900);
  --focus-offset: var(--bg-canvas);

  /* Scrims — never below 0.55 alpha over a photograph */
  --scrim-hero: linear-gradient(
    180deg,
    rgb(15 42 36 / 0.72) 0%,
    rgb(15 42 36 / 0.4) 45%,
    rgb(15 42 36 / 0.82) 100%
  );
  --scrim-nav: linear-gradient(180deg, rgb(15 42 36 / 0.55) 0%, rgb(15 42 36 / 0) 100%);

  /* Spacing */
  --space-3xs: 0.25rem;
  --space-2xs: 0.5rem;
  --space-xs: 0.75rem;
  --space-sm: 1rem;
  --space-md: 1.5rem;
  --space-lg: 2rem;
  --space-xl: 3rem;
  --space-2xl: 4rem;
  --space-3xl: 6rem;

  --section-y: clamp(4rem, 10vw, 10rem);
  --section-y-tight: clamp(2.5rem, 6vw, 5rem);
  --gutter: clamp(1.25rem, 4vw, 3rem);

  /* Containers */
  --container-prose: 38rem;
  --container-narrow: 48rem;
  --container-default: 75rem;
  --container-wide: 90rem;

  --grid-gap: clamp(1rem, 2vw, 1.5rem);
  --grid-gap-y: clamp(1.5rem, 4vw, 3rem);

  /* Radii — square-first. This brand is signpainted, not rounded. */
  --radius-xs: 2px;
  --radius-sm: 3px;
  --radius-md: 6px;
  --radius-pill: 999px;

  /* Elevation — tonal, not shadowy */
  --elev-1: inset 0 0 0 1px var(--border-hairline);
  --elev-3: 0 18px 40px -28px rgb(15 42 36 / 0.3);
  --elev-photo: 0 24px 48px -32px rgb(15 42 36 / 0.35);

  /* Motion */
  --dur-fast: 160ms;
  --dur-base: 240ms;
  --dur-slow: 420ms;
  --ease-standard: cubic-bezier(0.2, 0, 0, 1);
  --ease-entrance: cubic-bezier(0.16, 1, 0.3, 1);

  /* Type */
  --font-display: 'Gambetta', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --font-sans: 'Switzer', ui-sans-serif, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;

  --fs-display: clamp(2.75rem, 1rem + 7.778vw, 8rem);
  --fs-h1: clamp(2.25rem, 1.417rem + 3.704vw, 4.75rem);
  --fs-h2: clamp(1.75rem, 1.333rem + 1.852vw, 3rem);
  --fs-h3: clamp(1.313rem, 1.125rem + 0.833vw, 1.875rem);
  --fs-h4: clamp(1.125rem, 1.042rem + 0.37vw, 1.375rem);
  --fs-lead: clamp(1.063rem, 1rem + 0.278vw, 1.25rem);
  --fs-body: clamp(1rem, 0.958rem + 0.185vw, 1.125rem);
  --fs-small: clamp(0.875rem, 0.854rem + 0.093vw, 0.938rem);
  --fs-micro: clamp(0.688rem, 0.667rem + 0.093vw, 0.75rem);

  --lh-display: 0.94;
  --lh-heading: 1.14;
  --lh-snug: 1.35;
  --lh-body: 1.6;

  --ls-display: -0.025em;
  --ls-h2: -0.015em;
  --ls-caps: 0.14em;
  --ls-caps-sm: 0.1em;

  --nav-h: 4.5rem;
  --tap-min: 44px;

  --grain-opacity: 0.045;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --bg-canvas: var(--forest-950);
    --bg-surface: var(--forest-900);
    --bg-raised: var(--forest-800);
    --bg-band: var(--forest-900);
    --bg-inverse: var(--nafen-cream);

    --text-primary: var(--nafen-cream);
    --text-secondary: var(--sage-300);
    --text-muted: var(--sage-400);
    --text-inverse: var(--forest-900);
    --text-link: var(--forest-300);

    --border-hairline: var(--sage-900);
    --border-subtle: var(--sage-800);
    --border-field: var(--sage-500);
    --border-strong: var(--nafen-cream);

    --focus-ring: var(--nafen-cream);
    --focus-offset: var(--bg-canvas);

    --error-ink: var(--error-on-dark);
    --grain-opacity: 0.03;
  }
}

/* == 3. Reset & base ====================================================== */

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

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* Anchored headings must clear the fixed navigation. */
  scroll-padding-top: calc(var(--nav-h) + var(--space-md));
}

body {
  background: var(--bg-canvas);
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img,
picture,
video,
svg {
  display: block;
  max-width: 100%;
  height: auto;
}

/* The `hidden` attribute has to win.
 *
 * The UA stylesheet's `[hidden] { display: none }` is a single attribute
 * selector, so any class that sets `display` beats it — `.btn` is
 * inline-flex, which silently un-hides every button JavaScript marked hidden.
 * This is the one place !important is the correct tool: `hidden` is a
 * statement about existence, not a style suggestion. */
[hidden] {
  display: none !important;
}

/* Images always declare width/height in the markup; this keeps the ratio
   without letting the declared attributes distort the rendering. */
img {
  background-color: var(--bg-band);
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

::selection {
  background: var(--forest-200);
  color: var(--forest-900);
}

:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
  border-radius: var(--radius-xs);
}

/* Never remove the outline without replacing it — this is the only way a
   keyboard user knows where they are. */
:focus:not(:focus-visible) {
  outline: none;
}

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

.skip-link {
  position: absolute;
  top: var(--space-sm);
  left: var(--space-sm);
  z-index: 200;
  padding: var(--space-2xs) var(--space-sm);
  background: var(--nafen-cream);
  color: var(--forest-900);
  border-radius: var(--radius-sm);
  transform: translateY(-200%);
  transition: transform var(--dur-fast) var(--ease-standard);
}

.skip-link:focus {
  transform: translateY(0);
}

/* == 4. Texture =========================================================== */

/* A two-colour site with no accent risks reading as flat and cheap. Grain is
   what stops that — a single SVG turbulence, no image request, sitting above
   the background and below the content. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: var(--grain-opacity);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-repeat: repeat;
}

/* == 5. Layout ============================================================ */

.shell {
  width: 100%;
  max-width: var(--container-default);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.shell--narrow {
  max-width: var(--container-narrow);
}

.shell--prose {
  max-width: var(--container-prose);
}

.shell--wide {
  max-width: var(--container-wide);
}

.section {
  position: relative;
  z-index: 2;
  padding-block: var(--section-y);
}

.section--tight {
  padding-block: var(--section-y-tight);
}

.section--band {
  background: var(--bg-band);
}

/* Always forest, in both colour schemes.
 *
 * This is a brand device — a dark band the page steps into — not a semantic
 * inversion of the current theme. Wiring it to --bg-inverse meant that in dark
 * mode the background flipped to cream while the block below still forced
 * cream text, and the whole section rendered invisible. */
.section--inverse {
  background: var(--forest-900);
  color: var(--nafen-cream);
}

/* Every always-forest surface flips the neutral ramp, so nested components
 * need no variant classes of their own.
 *
 * The list matters as much as the values. These surfaces are forest in *both*
 * colour schemes, but the neutral tokens are not: in light mode --text-muted
 * is stone-700 and --focus-ring is forest-900. A surface that hardcodes
 * `color: cream` and then leaves those tokens alone gets muted text at 1.3:1
 * and a focus ring at 1.1:1 — invisible, and invisible only in light mode,
 * which is why it survives a dark-mode machine. .section--inverse already did
 * this remap; the hero, the footer, the mobile panel, the transparent nav over
 * the hero and the lightbox did not, and every one of them was affected. Add
 * any new always-dark surface to this list, not a one-off colour to the
 * component. */
.section--inverse,
.hero,
.footer,
.nav-panel,
.nav[data-state='top'],
.lightbox {
  --text-primary: var(--nafen-cream);
  --text-secondary: var(--sage-300);
  --text-muted: var(--sage-400);
  --text-link: var(--forest-300);
  --border-hairline: var(--sage-900);
  --border-subtle: var(--sage-800);
  --border-field: var(--sage-500);
  --bg-raised: var(--forest-800);
  --focus-ring: var(--nafen-cream);
  --focus-offset: var(--forest-900);
}

.stack > * + * {
  margin-block-start: var(--space-md);
}

.stack--tight > * + * {
  margin-block-start: var(--space-2xs);
}

.stack--loose > * + * {
  margin-block-start: var(--space-lg);
}

/* == 6. Typography ======================================================== */

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  /* Never above 600. The wordmark owns "bold"; hierarchy here comes from
     scale and space, not weight. */
  font-weight: 500;
  line-height: var(--lh-heading);
  text-wrap: balance;
}

h1 {
  font-size: var(--fs-h1);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-display);
}

h2 {
  font-size: var(--fs-h2);
  letter-spacing: var(--ls-h2);
}

h3 {
  font-size: var(--fs-h3);
}

h4 {
  font-size: var(--fs-h4);
}

p {
  text-wrap: pretty;
}

/* Default vertical rhythm.
 *
 * The reset zeroes every margin, which is right for layout components but
 * leaves running text with none at all — a heading set at line-height 0.94
 * then collides with the paragraph under it. `.stack` was meant to supply the
 * spacing, but a template that forgets it should degrade to "slightly tight",
 * never to "overlapping". These are the floor; `.stack` and friends still win
 * where they apply because they set the same property to a larger value.
 */
:is(h1, h2, h3, h4) + * {
  margin-block-start: var(--space-md);
}

p + p,
p + ul,
p + ol,
ul + p,
ol + p {
  margin-block-start: var(--space-sm);
}

/* .eyebrow already carries its own bottom margin; adding a top margin to what
   follows it would double the gap. */
.eyebrow + * {
  margin-block-start: 0;
}

.lead {
  font-size: var(--fs-lead);
  line-height: var(--lh-snug);
  color: var(--text-secondary);
  max-width: 46ch;
}

.prose p + p {
  margin-block-start: var(--space-sm);
}

.prose {
  max-width: 68ch;
  color: var(--text-secondary);
}

.eyebrow {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--fs-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  color: var(--text-muted);
  margin-block-end: var(--space-sm);
}

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

.small {
  font-size: var(--fs-small);
}

.serif-italic {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 400;
}

/* The wordmark set as display type. Gambetta is not the logo — the logo is
   artwork — but this is its voice at scale for section openers. */
.display {
  font-family: var(--font-display);
  font-size: var(--fs-display);
  font-weight: 400;
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
}

a {
  color: var(--text-link);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  transition: color var(--dur-fast) var(--ease-standard);
}

a:hover {
  color: var(--text-primary);
}

/* == 7. Buttons =========================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  min-height: var(--tap-min);
  padding: var(--space-xs) var(--space-md);
  border: 1.5px solid transparent;
  border-radius: var(--radius-sm);
  font-family: var(--font-sans);
  font-size: var(--fs-small);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--ls-caps-sm);
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease-standard),
    color var(--dur-fast) var(--ease-standard),
    border-color var(--dur-fast) var(--ease-standard),
    transform var(--dur-fast) var(--ease-standard);
}

.btn:active {
  transform: translateY(1px);
}

.btn--primary {
  background: var(--forest-900);
  border-color: var(--forest-900);
  color: var(--nafen-cream);
}

.btn--primary:hover {
  background: var(--forest-700);
  border-color: var(--forest-700);
  color: var(--nafen-cream);
}

.btn--secondary {
  background: transparent;
  border-color: var(--border-strong);
  color: var(--text-primary);
}

.btn--secondary:hover {
  background: var(--forest-900);
  color: var(--nafen-cream);
}

/* On forest, cream is the primary. It measures 13.5:1 — the palette's own
   accent, which is why no third colour was introduced. */
.section--inverse .btn--primary,
.hero .btn--primary {
  background: var(--nafen-cream);
  border-color: var(--nafen-cream);
  color: var(--forest-900);
}

.section--inverse .btn--primary:hover,
.hero .btn--primary:hover {
  background: var(--nafen-white);
  border-color: var(--nafen-white);
  color: var(--forest-900);
}

.section--inverse .btn--secondary,
.hero .btn--secondary {
  border-color: var(--sage-400);
  color: var(--nafen-cream);
}

.section--inverse .btn--secondary:hover,
.hero .btn--secondary:hover {
  background: rgb(244 241 234 / 0.12);
  border-color: var(--nafen-cream);
  color: var(--nafen-cream);
}

.btn--block {
  width: 100%;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

/* A button row directly after running text needs air, or the caps sit on the
   descenders of the paragraph above. `.hero .btn-row` overrides this with a
   larger value where the hero wants more. */
* + .btn-row {
  margin-block-start: var(--space-lg);
}

/* == 8. Navigation ======================================================== */

.nav {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 100;
  height: var(--nav-h);
  display: flex;
  align-items: center;
  transition:
    background-color var(--dur-base) var(--ease-standard),
    box-shadow var(--dur-base) var(--ease-standard);
}

/* Over the hero the bar is transparent with a gradient scrim, so the
   photograph starts at the very top of the viewport. */
.nav[data-state='top'] {
  background: transparent;
  color: var(--nafen-cream);
}

.nav[data-state='top']::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--scrim-nav);
  pointer-events: none;
}

.nav[data-state='scrolled'] {
  background: var(--bg-canvas);
  color: var(--text-primary);
  box-shadow: 0 1px 0 0 var(--border-subtle);
}

.nav-inner {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  width: 100%;
}

.nav-logo {
  display: flex;
  align-items: center;
  color: inherit;
  text-decoration: none;
}

.nav-logo svg {
  height: 1.75rem;
  width: auto;
  fill: currentColor;
}

.nav-links {
  display: none;
  align-items: center;
  gap: var(--space-md);
}

.nav-links a {
  color: inherit;
  text-decoration: none;
  font-size: var(--fs-small);
  font-weight: 450;
  padding-block: var(--space-2xs);
  border-block-end: 1.5px solid transparent;
}

.nav-links a:hover,
.nav-links a[aria-current='page'] {
  border-block-end-color: currentColor;
}

.nav-actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

@media (min-width: 62rem) {
  .nav-links {
    display: flex;
  }

  .nav-toggle {
    display: none;
  }
}

.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--tap-min);
  min-height: var(--tap-min);
  background: none;
  border: 0;
  color: inherit;
  cursor: pointer;
}

.nav-toggle svg {
  width: 1.5rem;
  height: 1.5rem;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.5;
}

/* Mobile panel. Uses [hidden] rather than a class so it is genuinely removed
   from the accessibility tree when closed. */
.nav-panel[hidden] {
  display: none;
}

.nav-panel {
  position: fixed;
  inset: var(--nav-h) 0 0 0;
  z-index: 99;
  background: var(--forest-900);
  color: var(--nafen-cream);
  padding: var(--space-xl) var(--gutter);
  overflow-y: auto;
}

.nav-panel a {
  display: block;
  color: inherit;
  text-decoration: none;
  font-family: var(--font-display);
  font-size: var(--fs-h3);
  padding-block: var(--space-xs);
  border-block-end: 1px solid var(--sage-900);
}

.lang-switch {
  display: flex;
  align-items: center;
  gap: var(--space-3xs);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-caps-sm);
}

.lang-switch a {
  color: inherit;
  text-decoration: none;
  opacity: 0.65;
  padding: var(--space-3xs) var(--space-2xs);
}

.lang-switch a:hover,
.lang-switch a[aria-current='true'] {
  opacity: 1;
}

/* == 9. Hero ============================================================== */

.hero {
  position: relative;
  z-index: 2;
  display: grid;
  align-items: end;
  min-height: min(92svh, 54rem);
  padding-block: calc(var(--nav-h) + var(--space-xl)) var(--space-2xl);
  background: var(--forest-900);
  color: var(--nafen-cream);
  isolation: isolate;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
}

/* <picture> is a plain block with height:auto, so an <img> inside it resolving
   `height: 100%` has no definite height to resolve against and silently falls
   back to its intrinsic ratio — the photo then stops partway down a tall hero
   and the rest is flat green. The wrapper needs the height too. */
.hero__media picture {
  display: block;
  width: 100%;
  height: 100%;
}

.hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Keep the vehicle in frame when the crop gets tall and narrow. */
  object-position: center 55%;
}

.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--scrim-hero);
}

/* NN/g eye-tracking puts 57% of viewing time above the fold and 42% in the top
   fifth of the page. So this block carries what it is, where it goes, why it
   is worth paying for, and the CTA — not a wordmark on a photograph, which is
   this category's default and its most expensive habit. */
.hero__content {
  max-width: 40rem;
}

.hero h1 {
  font-size: var(--fs-h1);
  margin-block-end: var(--space-md);
}

.hero .lead {
  color: var(--sage-200);
  max-width: 34ch;
}

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
  /* The reset zeroes margins but not padding, and a bare <ul> keeps the UA's
     40px indent — which reads as a mysterious offset once the bullets are
     gone. */
  padding: 0;
  margin-block-start: var(--space-lg);
  font-size: var(--fs-small);
  color: var(--sage-300);
}

.hero__meta li {
  list-style: none;
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

.hero .btn-row {
  margin-block-start: var(--space-xl);
}

/* == 10. Cards, grids & sections ========================================== */

.grid {
  display: grid;
  gap: var(--grid-gap-y) var(--grid-gap);
}

.grid--2 {
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
}

.grid--3 {
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
}

.grid--4 {
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
}

.section-head {
  max-width: var(--container-narrow);
  margin-block-end: var(--space-2xl);
}

.card {
  display: flex;
  flex-direction: column;
  background: var(--bg-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  overflow: hidden;
  transition:
    border-color var(--dur-base) var(--ease-standard),
    transform var(--dur-base) var(--ease-standard);
}

.card:hover {
  border-color: var(--border-strong);
}

.card__media {
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--bg-band);
}

.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-standard);
}

.card:hover .card__media img {
  transform: scale(1.03);
}

.card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-md);
  flex: 1;
}

.card__body h3 a {
  color: inherit;
  text-decoration: none;
}

/* Makes the whole card clickable without nesting interactive elements — the
   link stays the only tab stop and screen readers announce one target. */
.card__link::after {
  content: '';
  position: absolute;
  inset: 0;
}

.card--linked {
  position: relative;
}

.card__foot {
  margin-block-start: auto;
  padding-block-start: var(--space-sm);
  font-size: var(--fs-small);
  color: var(--text-muted);
}

/* Numbered feature list — used for "how it works" */
.steps {
  counter-reset: step;
  list-style: none;
  padding: 0;
}

.steps li {
  counter-increment: step;
  position: relative;
  padding-inline-start: var(--space-xl);
  padding-block: var(--space-sm);
  border-block-start: 1px solid var(--border-subtle);
}

.steps li::before {
  content: counter(step, decimal-leading-zero);
  position: absolute;
  inset-inline-start: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-micro);
  letter-spacing: var(--ls-caps);
  color: var(--text-muted);
  padding-block-start: 0.35em;
}

/* Price tiers */
.tier {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  padding: var(--space-lg);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-raised);
}

.tier--featured {
  border-color: var(--border-strong);
  border-width: 1.5px;
}

.tier__price {
  font-family: var(--font-display);
  font-size: var(--fs-h2);
  line-height: 1;
}

.tier__price small {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  color: var(--text-muted);
  margin-block-start: var(--space-2xs);
}

.tier ul {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-2xs);
  font-size: var(--fs-small);
  color: var(--text-secondary);
}

.tier li {
  padding-inline-start: var(--space-md);
  position: relative;
}

.tier li::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.6em;
  width: 0.5rem;
  height: 1px;
  background: var(--text-muted);
}

/* Accordion — FAQ. <details> so it works with no JavaScript at all. */
.faq-item {
  border-block-end: 1px solid var(--border-subtle);
}

.faq-item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block: var(--space-md);
  min-height: var(--tap-min);
  cursor: pointer;
  font-family: var(--font-display);
  font-size: var(--fs-h4);
  list-style: none;
}

.faq-item summary::-webkit-details-marker {
  display: none;
}

.faq-item summary::after {
  content: '';
  flex: none;
  width: 0.75rem;
  height: 0.75rem;
  border-inline-end: 1.5px solid currentColor;
  border-block-end: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--dur-base) var(--ease-standard);
}

.faq-item[open] summary::after {
  transform: rotate(-135deg);
}

.faq-item__answer {
  padding-block-end: var(--space-md);
  max-width: 68ch;
  color: var(--text-secondary);
}

/* The vehicle line drawing.
 *
 * The source artwork is cream strokes on a solid field, so as an <img> it is a
 * black rectangle with lines in it. Painted through a mask instead, it takes
 * currentColor, sits on any surface, and needs no second colour variant. */
.linedrawing {
  display: block;
  width: min(100%, 30rem);
  aspect-ratio: 792 / 721;
  background-color: currentColor;
  -webkit-mask: url("../img/photo/vehicle-linedrawing-mask.6e19a275eb6b.png") center / contain no-repeat;
  mask: url("../img/photo/vehicle-linedrawing-mask.6e19a275eb6b.png") center / contain no-repeat;
}

/* == 11. Gallery ========================================================== */

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr));
  gap: var(--space-2xs);
}

.gallery__item {
  position: relative;
  margin: 0;
  overflow: hidden;
  background: var(--bg-band);
  border-radius: var(--radius-xs);
}

.gallery__item img {
  width: 100%;
  height: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-standard);
}

.gallery__item:hover img {
  transform: scale(1.04);
}

.gallery__item figcaption {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  padding: var(--space-md) var(--space-sm) var(--space-sm);
  background: linear-gradient(180deg, rgb(15 42 36 / 0), rgb(15 42 36 / 0.8));
  color: var(--nafen-cream);
  font-size: var(--fs-small);
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-standard);
}

.gallery__item:hover figcaption,
.gallery__item:focus-within figcaption {
  opacity: 1;
}

.filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
  margin-block-end: var(--space-lg);
}

.chip {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding: var(--space-2xs) var(--space-md);
  border: 1px solid var(--border-field);
  border-radius: var(--radius-pill);
  font-size: var(--fs-small);
  text-decoration: none;
  color: var(--text-secondary);
}

/* The selected filter, stated in tokens rather than in brand colours.
 *
 * Hardcoding forest-on-cream is right in light mode and 1.2:1 in dark, where
 * the canvas is forest-950: the fill vanished into the page and the one chip
 * that was selected became the only chip with no visible edge — the state read
 * backwards. Keyed to --text-primary/--bg-canvas it inverts with the scheme,
 * so it stays the same forest pill on cream in light and becomes the cream
 * pill on forest in dark, which is the rule the buttons already follow. */
.chip[aria-current='true'] {
  background: var(--text-primary);
  border-color: var(--text-primary);
  color: var(--bg-canvas);
}

/* Lightbox — a native <dialog>, so Escape, focus trapping and the top layer
   are the browser's job rather than ours. */
.lightbox {
  border: 0;
  padding: 0;
  max-width: 100vw;
  max-height: 100vh;
  width: 100%;
  height: 100%;
  background: rgb(8 23 20 / 0.96);
  color: var(--nafen-cream);
}

.lightbox::backdrop {
  background: rgb(8 23 20 / 0.9);
}

.lightbox__figure {
  display: grid;
  place-items: center;
  gap: var(--space-sm);
  height: 100%;
  padding: var(--space-lg);
  margin: 0;
}

.lightbox__figure img {
  max-height: 80vh;
  width: auto;
  object-fit: contain;
}

.lightbox__close {
  position: absolute;
  inset-block-start: var(--space-sm);
  inset-inline-end: var(--space-sm);
  min-width: var(--tap-min);
  min-height: var(--tap-min);
  background: none;
  border: 1px solid var(--sage-500);
  border-radius: var(--radius-sm);
  color: inherit;
  cursor: pointer;
}

/* == 12. Testimonials ===================================================== */

.quote {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  padding: var(--space-lg);
  border-inline-start: 1.5px solid var(--border-strong);
}

.quote blockquote {
  font-family: var(--font-display);
  font-style: italic;
  font-size: var(--fs-h4);
  line-height: var(--lh-snug);
}

.quote figcaption {
  font-size: var(--fs-small);
  color: var(--text-muted);
}

.quote figcaption strong {
  display: block;
  color: var(--text-primary);
  font-weight: 500;
}

/* == 13. Forms ============================================================ */

.form {
  display: grid;
  gap: var(--space-md);
}

.field {
  display: grid;
  gap: var(--space-2xs);
}

.field label {
  font-size: var(--fs-small);
  font-weight: 500;
}

.field-input {
  width: 100%;
  min-height: var(--tap-min);
  padding: var(--space-xs) var(--space-sm);
  background: var(--bg-raised);
  border: 1px solid var(--border-field);
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  transition:
    border-color var(--dur-fast) var(--ease-standard),
    box-shadow var(--dur-fast) var(--ease-standard);
}

.field-input:focus {
  outline: none;
  border-color: var(--border-strong);
  box-shadow: 0 0 0 3px rgb(15 42 36 / 0.12);
}

.field-input:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}

textarea.field-input {
  min-height: 7rem;
  resize: vertical;
}

.field__help {
  font-size: var(--fs-small);
  color: var(--text-muted);
}

.field__error {
  font-size: var(--fs-small);
  color: var(--error-ink);
  font-weight: 500;
}

.field--invalid .field-input {
  border-color: var(--error-ink);
  border-width: 1.5px;
}

.check {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2xs) var(--space-xs);
  align-items: start;
  font-size: var(--fs-small);
}

.check input {
  width: 1.15rem;
  height: 1.15rem;
  margin-block-start: 0.2em;
  accent-color: var(--forest-900);
}

/* The honeypot. Hidden from sight, from the tab order and from assistive
   technology — a real person is never offered it in any modality. */
.hp-field {
  position: absolute !important;
  left: -9999px !important;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.form-error-summary {
  padding: var(--space-md);
  border: 1.5px solid var(--error-ink);
  border-radius: var(--radius-sm);
  color: var(--error-ink);
}

.form-error-summary ul {
  margin-block-start: var(--space-2xs);
  padding-inline-start: var(--space-md);
}

/* Multi-step. Steps exist only when JavaScript is available; without it the
   form is one honest scrolling column that submits perfectly well. */
.form-steps[data-enhanced='true'] .form-step[hidden] {
  display: none;
}

.form-progress {
  display: flex;
  gap: var(--space-2xs);
  margin-block-end: var(--space-md);
  font-size: var(--fs-micro);
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  color: var(--text-muted);
}

.form-progress__bar {
  flex: 1;
  height: 2px;
  background: var(--border-subtle);
  align-self: center;
}

.form-progress__bar span {
  display: block;
  height: 100%;
  background: var(--forest-900);
  transition: width var(--dur-base) var(--ease-standard);
}

.form-nav {
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
}

/* == 14. Map ============================================================== */

/* Matches the silhouette's 1000x700 viewBox, so the region fills the frame
   instead of floating in letterboxing. Kept at every width — a portrait
   version would just add empty sea. */
.map {
  position: relative;
  aspect-ratio: 10 / 7;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--bg-band);
  border: 1px solid var(--border-subtle);
}

.map__canvas {
  width: 100%;
  height: 100%;
  /* Labels are drawn in viewBox units, which scale with the map. Rendering
     them as text keeps them selectable and translatable. */
  font-family: var(--font-sans);
}

/* The map's own colours live in section 21, next to the tokens they use. The
   province-outline rules that used to sit here were left behind when the
   generator stopped emitting .region__home / .region__neighbour and started
   emitting a single coastline path; while they lingered, this
   `fill: currentColor` quietly beat the new markup's presentation attribute
   and inverted the whole map. Dead CSS is not free. */

.pin__dot {
  fill: currentColor;
}

.pin__halo {
  fill: none;
  stroke: currentColor;
  stroke-opacity: 0.4;
  stroke-width: 2;
}

.pin__leader {
  stroke: currentColor;
  stroke-opacity: 0.45;
  stroke-width: 1.25;
}

.pin__label {
  fill: currentColor;
  font-size: 22px;
  font-weight: 500;
  letter-spacing: 0.01em;
  /* A cream label crossing the coastline needs to stay readable without a
     solid plate behind it obscuring the shape. */
  paint-order: stroke;
  stroke: var(--bg-canvas);
  stroke-width: 5;
  stroke-linejoin: round;
}

.pin--base .pin__label {
  font-weight: 600;
}

/* Shown until the tiles load, and left in place permanently if the visitor
   declined the third-party request. The list underneath is the real content —
   the map is the illustration. */
.map__fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: var(--space-lg);
  text-align: center;
  color: var(--text-muted);
}

.area-list {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}

.area-list li {
  font-size: var(--fs-small);
  padding: var(--space-3xs) var(--space-xs);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-pill);
  color: var(--text-secondary);
}

/* == 15. Footer & sticky CTA ============================================== */

.footer {
  position: relative;
  z-index: 2;
  background: var(--forest-950);
  color: var(--sage-200);
  padding-block: var(--space-3xl) var(--space-xl);
}

.footer a {
  color: inherit;
  text-decoration: none;
}

.footer a:hover {
  color: var(--nafen-cream);
  text-decoration: underline;
}

.footer__grid {
  display: grid;
  gap: var(--space-xl);
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
}

.footer__wordmark svg {
  width: min(100%, 14rem);
  height: auto;
  fill: var(--nafen-cream);
}

.footer h2 {
  font-family: var(--font-sans);
  font-size: var(--fs-micro);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  color: var(--sage-400);
  margin-block-end: var(--space-sm);
}

.footer ul {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-2xs);
  font-size: var(--fs-small);
}

.footer__legal {
  margin-block-start: var(--space-2xl);
  padding-block-start: var(--space-md);
  border-block-start: 1px solid var(--sage-900);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm) var(--space-md);
  justify-content: space-between;
  font-size: var(--fs-micro);
  color: var(--sage-400);
}

/* Spain has Europe's highest WhatsApp penetration, so it is a co-primary
   channel here rather than a nice-to-have. Deep link, not a widget script —
   no third-party JavaScript, no tracker, works offline of our control. */
.sticky-cta {
  position: fixed;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: 90;
  display: flex;
  gap: var(--space-2xs);
  padding: var(--space-2xs);
  padding-block-end: max(var(--space-2xs), env(safe-area-inset-bottom));
  background: var(--bg-canvas);
  border-block-start: 1px solid var(--border-subtle);
  transform: translateY(110%);
  transition: transform var(--dur-base) var(--ease-standard);
}

.sticky-cta[data-visible='true'] {
  transform: translateY(0);
}

.sticky-cta .btn {
  flex: 1;
}

@media (min-width: 48rem) {
  .sticky-cta {
    display: none;
  }
}

/* WCAG 2.2 SC 2.4.11 — a sticky bar must never cover the element that has
   focus. The bar hides itself whenever focus enters the page's form; this
   rule is the belt to that braces, keeping the last field clear of it. */
body:has(.sticky-cta[data-visible='true']) {
  scroll-padding-bottom: 5rem;
}

/* == 16. Motion & preferences ============================================= */

/* Scroll-driven animations are NOT Baseline in 2026 — they are an Interop
   focus area precisely because support is still incomplete. So the reveal is
   opt-in: content is visible by default, and only becomes animatable once
   JavaScript adds [data-reveal], which it only does when IntersectionObserver
   exists. Nothing is ever hidden behind an effect that might not run. */
/* Gated on .js-reveal, which the script adds to <html> only once it has
 * decided it will actually animate. Hiding on [data-reveal] alone — which the
 * templates put in the markup — meant any failure to observe left the content
 * invisible forever. Content is visible until JavaScript takes responsibility
 * for revealing it. */
.js-reveal [data-reveal] {
  opacity: 0;
  transform: translateY(1.25rem);
  transition:
    opacity var(--dur-slow) var(--ease-entrance),
    transform var(--dur-slow) var(--ease-entrance);
}

.js-reveal [data-reveal='shown'] {
  opacity: 1;
  transform: none;
}

/* View Transitions are Baseline and used for the gallery lightbox. */
@view-transition {
  navigation: auto;
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  /* .js-reveal has to be in the selector, not just [data-reveal].
   *
   * The script reads prefers-reduced-motion once, at load, and skips adding
   * .js-reveal when it is set. But a visitor who turns reduced motion on
   * mid-session keeps the class, and `.js-reveal [data-reveal]` (0,2,0) beats
   * a bare `[data-reveal]` (0,1,0) — so everything not yet scrolled past
   * stayed at opacity 0 forever. Matching the specificity is the whole fix. */
  .js-reveal [data-reveal],
  [data-reveal] {
    opacity: 1;
    transform: none;
  }

  .card:hover .card__media img,
  .gallery__item:hover img {
    transform: none;
  }
}

@media (prefers-contrast: more) {
  :root {
    --border-subtle: var(--border-strong);
    --text-muted: var(--text-secondary);
  }
}

@media print {
  .nav,
  .sticky-cta,
  .hero__media,
  .map {
    display: none !important;
  }

  body {
    background: #fff;
    color: #000;
  }
}

/* == 21. Coverage map ======================================================
 *
 * Appended as its own section, and self-contained: everything it needs is a
 * local custom property, so nothing above this line has to know the map exists.
 *
 * The whole job of these rules is one question a visitor answers in half a
 * second — where does the land stop and the Mediterranean start? That is
 * carried by three things and no colour outside the brand's two:
 *
 *   1. a tonal step between --map-sea and --map-land, in both schemes;
 *   2. a coastline stroke heavier than anything else drawn;
 *   3. a soft band of tone in the water hugging the shore (the two wide,
 *      faint strokes in _region.svg that the land is painted over).
 *
 * Every one of these is also written as a presentation attribute in
 * _region.svg, so a map that loses this stylesheet degrades to the light
 * palette on a white page rather than to a black rectangle.
 *
 * The rules below restate `fill`, `stroke-width` and `font-size` even where
 * the value looks like a default. Section 14 above styles the same class names
 * from the province-outline version of this map, and a stylesheet rule beats a
 * presentation attribute however specific the attribute looks — leaving one
 * out filled the province boundary with a solid tone and inverted the whole
 * land/sea reading.
 */

.map {
  /* Light scheme. Land is the warmer, lighter tone — paper — and the water is
     the cooler, deeper one, which is the way round every printed chart does it
     and therefore the way round that needs no explaining. */
  --map-sea: #ccdbd4;
  --map-land: #f1ede2;
  --map-ink: #12332c;
  --map-coast-opacity: 1;

  /* Follows the silhouette's own viewBox, set inline by the template from the
     projection, so a re-crop cannot letterbox or stretch the map. */
  aspect-ratio: var(--map-ratio, 10 / 7);
  background: var(--map-sea);
}

/* Deep water, lifted land. Keeping land the lighter of the two in both schemes
   means the shape a visitor learns in one is the shape they read in the other.
   Three selectors carry the same four values because the theme toggle has three
   cases — follow the system, forced dark, and the inverse band, which is a
   brand device rather than a theme and is forest in both schemes. Plain CSS has
   no way to share a declaration block; change one, change all three. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) .map {
    --map-sea: #081b17;
    --map-land: #1d4339;
    --map-ink: #cfe2db;
    --map-coast-opacity: 0.8;
  }
}

:root[data-theme='dark'] .map {
  --map-sea: #081b17;
  --map-land: #1d4339;
  --map-ink: #cfe2db;
  --map-coast-opacity: 0.8;
}

.section--inverse .map {
  --map-sea: #081b17;
  --map-land: #1d4339;
  --map-ink: #cfe2db;
  --map-coast-opacity: 0.8;
}

.region__sea {
  fill: var(--map-sea);
}

.region__land {
  fill: var(--map-land);
  fill-opacity: 1;
  stroke: none;
}

/* Wide and faint, drawn before the land and covered by it, so only the half of
   each stroke that falls in the water survives. Three widths rather than one
   because a single band ends in a hard edge out at sea and reads as a drop
   shadow; stacked, they fall off. */
.region__deep,
.region__shelf,
.region__shore {
  fill: none;
  stroke: var(--map-ink);
}

.region__coast {
  fill: none;
  stroke: var(--map-ink);
  stroke-opacity: var(--map-coast-opacity);
  stroke-width: 3;
}

.pin__dot {
  fill: var(--map-ink);
  /* The land tone, not a colour: a dot on the coastline needs to be legible
     against sea and land at once. */
  stroke: var(--map-land);
  stroke-width: 2;
}

.pin__halo {
  fill: none;
  stroke: var(--map-ink);
  stroke-opacity: 0.45;
  stroke-width: 1.6;
}

.pin__leader {
  stroke: var(--map-ink);
  stroke-opacity: 0.5;
  stroke-width: 1.2;
}

.pin__label {
  fill: var(--map-ink);
  font-family: var(--font-sans);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.005em;
  /* Halo rather than a solid plate: the label stays readable where it crosses
     the coastline without punching a hole in the shape it is labelling. */
  paint-order: stroke;
  stroke: var(--map-land);
  stroke-width: 3.5;
  stroke-linejoin: round;
}

.map__sea-label {
  fill: var(--map-ink);
  /* 0.4 measured 2.14:1 on the light sea — this label is the map's legend,
     the one thing that names which side is water, so it has to clear AA
     rather than be decoration. 0.8 gives 5.70:1 light / 8.75:1 dark and still
     reads as quiet against the coastline. */
  fill-opacity: 0.8;
  font-family: var(--font-sans);
  font-weight: 500;
}

/* The map is drawn in viewBox units, so text shrinks with the frame. Below
   this width the frame is around 350 px against a 720-unit viewBox and an
   18-unit label is under 9 px, which is not a label. */
@media (max-width: 700px) {
  .pin__label {
    font-size: 25px;
    stroke-width: 4.5;
  }

  .pin__dot {
    r: 7;
    stroke-width: 2.5;
  }

  .pin--base .pin__dot {
    r: 9;
  }

  .pin__halo {
    r: 21;
  }

  .map__sea-label {
    font-size: 24px;
  }
}

/* == 17. Theme toggle ====================================================== */

/* The dark token set, applied explicitly.
 *
 * Section 2 applies these under `@media (prefers-color-scheme: dark)` for
 * `:root:not([data-theme='light'])`, which handles "follow the system" and
 * "force light". This block is the third case: a visitor on a light system who
 * has chosen dark. The values are duplicated rather than shared because plain
 * CSS has no way to reuse a declaration block, and a preprocessor is not worth
 * adding for twenty lines. If you change one, change both. */
:root[data-theme='dark'] {
  --bg-canvas: var(--forest-950);
  --bg-surface: var(--forest-900);
  --bg-raised: var(--forest-800);
  --bg-band: var(--forest-900);
  --bg-inverse: var(--nafen-cream);

  --text-primary: var(--nafen-cream);
  --text-secondary: var(--sage-300);
  --text-muted: var(--sage-400);
  --text-inverse: var(--forest-900);
  --text-link: var(--forest-300);

  --border-hairline: var(--sage-900);
  --border-subtle: var(--sage-800);
  --border-field: var(--sage-500);
  --border-strong: var(--nafen-cream);

  --focus-ring: var(--nafen-cream);
  --focus-offset: var(--bg-canvas);

  --error-ink: var(--error-on-dark);
  --grain-opacity: 0.03;

  color-scheme: dark;
}

:root[data-theme='light'] {
  color-scheme: light;
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* Meets the 44px target without drawing a 44px box — the icon stays small
     while the hit area does not. */
  min-width: var(--tap-min);
  min-height: var(--tap-min);
  padding: 0;
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  color: inherit;
  cursor: pointer;
  opacity: 0.7;
  transition: opacity var(--dur-fast) var(--ease-standard);
}

.theme-toggle:hover {
  opacity: 1;
}

.theme-toggle svg {
  width: 1.15rem;
  height: 1.15rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
}

/* One icon at a time, keyed off the button's own state rather than a media
   query — the state is already resolved in aria-pressed, so the icon and the
   announced state can never disagree. */
.theme-toggle[aria-pressed='true'] .theme-toggle__moon,
.theme-toggle[aria-pressed='false'] .theme-toggle__sun {
  display: none;
}

/* Until JavaScript resolves the theme there is no correct icon to show, and a
   flicker to the wrong one is worse than a beat of nothing. */
.theme-toggle:not([data-ready]) svg {
  visibility: hidden;
}
