/* ==========================================================================
   Design system.

   Tokens are lifted from a live reading of 21st.dev's computed styles
   (zinc surfaces, one vivid blue primary, 0.5rem radius base, headings at
   weight 500 with tight negative tracking, an italic-serif accent word).
   Two things are deliberately different:

   - Light is the default. The audience is people renewing a passport, often
     on a mid-range Android in daylight; dark-first would be a style choice
     paid for by the reader.
   - Every font is served from this origin. Linking fonts.googleapis.com
     would ship the visitor's IP to a US server on page load, which is the
     exact thing LG Muenchen I 3 O 17493/20 found unlawful without consent.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Fonts — self-hosted, variable, Cyrillic first.

   Cyrillic is listed before Latin so the browser fetches the 19 KB subset the
   Ukrainian and Russian pages actually need before the larger Latin one.
   `font-display: swap` keeps text readable during the fetch; `size-adjust` is
   left alone because the fallback stack is metric-close enough that the swap
   does not visibly reflow.
   -------------------------------------------------------------------------- */

@font-face {
  font-family: "Inter var";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("fonts/inter-cyrillic-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: "Inter var";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("fonts/inter-latin-ext-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Inter var";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("fonts/inter-latin-wght-normal.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Lora var";
  font-style: italic;
  font-display: swap;
  font-weight: 400 700;
  src: url("fonts/lora-cyrillic-wght-italic.woff2") format("woff2-variations");
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: "Lora var";
  font-style: italic;
  font-display: swap;
  font-weight: 400 700;
  src: url("fonts/lora-latin-ext-wght-italic.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Lora var";
  font-style: italic;
  font-display: swap;
  font-weight: 400 700;
  src: url("fonts/lora-latin-wght-italic.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --------------------------------------------------------------------------
   2. Tokens
   -------------------------------------------------------------------------- */

:root {
  color-scheme: light;

  --font-sans: "Inter var", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --font-serif: "Lora var", Georgia, "Times New Roman", serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* Surfaces climb away from the page: canvas < surface < raised. */
  --canvas: oklch(0.985 0.001 286);
  --surface: oklch(1 0 0);
  --raised: oklch(1 0 0);
  --inset: oklch(0.968 0.002 286);

  --text: oklch(0.16 0.004 286);
  --text-muted: oklch(0.49 0.012 286);
  /* Measured, not eyeballed. At oklch(0.62) this was 3.66:1 on white — below
     the 4.5:1 that WCAG 2.1 AA requires for body text, and it is used for
     13 px captions where the shortfall matters most. */
  --text-faint: oklch(0.5 0.012 286);

  --border: oklch(0.916 0.004 286);
  --border-strong: oklch(0.85 0.006 286);

  /* Two blues on purpose: `primary` is a background that white sits on,
     `primary-text` is a foreground that sits on white. A single value cannot
     clear 4.5:1 in both directions. */
  /* Deepened from the bright oklch(0.485 0.245) this started as, taking the
     judgement — not the values — from 21st.dev's "Harbor Blue" theme (#194391).
     A startup blue is the wrong register for someone renewing a passport; a
     navy-leaning blue reads as institutional, and it buys contrast as well:
     white on this is about 6.5:1 rather than 4.6:1.

     Still two tokens, not one. Every published theme in that catalogue reuses a
     single --primary for buttons and for link text, and no single blue clears
     4.5:1 both as a background under white and as text on white. */
  --primary: oklch(0.44 0.19 262);
  --primary-hover: oklch(0.375 0.175 262);
  --primary-text: oklch(0.42 0.19 262);
  --primary-soft: oklch(0.955 0.022 262);
  --primary-soft-border: oklch(0.88 0.055 262);
  --on-primary: oklch(1 0 0);

  /* Measured on --inset, where the live badge sits: oklch(0.52) came out at
     2.7:1. Darkened until it clears 4.5:1 as 13 px text. */
  --success: oklch(0.44 0.13 155);
  --success-soft: oklch(0.96 0.03 155);
  --warning-text: oklch(0.48 0.12 75);
  --warning-soft: oklch(0.965 0.04 85);
  --warning-border: oklch(0.87 0.08 85);
  --danger: oklch(0.52 0.21 27);

  --ring: oklch(0.44 0.19 262);

  --radius-sm: 0.375rem;
  --radius: 0.625rem;
  --radius-lg: 0.875rem;
  --radius-xl: 1.25rem;
  --radius-pill: 999px;

  --shadow-xs: 0 1px 2px oklch(0.16 0.004 286 / 0.05);
  --shadow-sm: 0 1px 3px oklch(0.16 0.004 286 / 0.07), 0 1px 2px oklch(0.16 0.004 286 / 0.04);
  --shadow-md: 0 4px 16px oklch(0.16 0.004 286 / 0.07), 0 2px 4px oklch(0.16 0.004 286 / 0.04);
  --shadow-lg: 0 16px 48px oklch(0.16 0.004 286 / 0.12), 0 4px 12px oklch(0.16 0.004 286 / 0.06);

  /* 4 px rhythm. */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.25rem;
  --sp-6: 1.5rem;
  --sp-8: 2rem;
  --sp-10: 2.5rem;
  --sp-12: 3rem;
  --sp-16: 4rem;
  --sp-20: 5rem;
  --sp-24: 6rem;

  --measure: 68ch;
  --container: 72rem;
  --container-narrow: 44rem;

  --z-header: 40;
  --z-overlay: 90;
  --z-dialog: 100;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur-fast: 130ms;
  --dur: 200ms;

  --grid-line: oklch(0.16 0.004 286 / 0.045);
}

/* Dark mode is defined twice on purpose: once for the system preference and
   once for the explicit toggle, so the toggle can override the system in both
   directions instead of only one. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --canvas: oklch(0.141 0.004 286);
    --surface: oklch(0.171 0.005 286);
    --raised: oklch(0.205 0.006 286);
    --inset: oklch(0.19 0.005 286);

    --text: oklch(0.968 0.001 286);
    --text-muted: oklch(0.7 0.014 286);
    --text-faint: oklch(0.6 0.014 286);

    --border: oklch(0.29 0.006 286);
    --border-strong: oklch(0.37 0.008 286);

    --primary: oklch(0.55 0.21 262);
    --primary-hover: oklch(0.62 0.2 262);
    --primary-text: oklch(0.76 0.14 262);
    --primary-soft: oklch(0.26 0.055 262);
    --primary-soft-border: oklch(0.36 0.095 262);
    --on-primary: oklch(1 0 0);

    --success: oklch(0.75 0.15 155);
    --success-soft: oklch(0.26 0.05 155);
    --warning-text: oklch(0.82 0.13 85);
    --warning-soft: oklch(0.27 0.05 85);
    --warning-border: oklch(0.38 0.07 85);
    --danger: oklch(0.7 0.19 27);

    --ring: oklch(0.72 0.16 262);

    --shadow-xs: 0 1px 2px oklch(0 0 0 / 0.3);
    --shadow-sm: 0 1px 3px oklch(0 0 0 / 0.4);
    --shadow-md: 0 4px 16px oklch(0 0 0 / 0.45);
    --shadow-lg: 0 16px 48px oklch(0 0 0 / 0.6);

    --grid-line: oklch(1 0 0 / 0.045);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --canvas: oklch(0.141 0.004 286);
  --surface: oklch(0.171 0.005 286);
  --raised: oklch(0.205 0.006 286);
  --inset: oklch(0.19 0.005 286);

  --text: oklch(0.968 0.001 286);
  --text-muted: oklch(0.7 0.014 286);
  --text-faint: oklch(0.6 0.014 286);

  --border: oklch(0.29 0.006 286);
  --border-strong: oklch(0.37 0.008 286);

  --primary: oklch(0.55 0.21 262);
  --primary-hover: oklch(0.62 0.2 262);
  --primary-text: oklch(0.76 0.14 262);
  --primary-soft: oklch(0.26 0.055 262);
  --primary-soft-border: oklch(0.36 0.095 262);
  --on-primary: oklch(1 0 0);

  --success: oklch(0.75 0.15 155);
  --success-soft: oklch(0.26 0.05 155);
  --warning-text: oklch(0.82 0.13 85);
  --warning-soft: oklch(0.27 0.05 85);
  --warning-border: oklch(0.38 0.07 85);
  --danger: oklch(0.7 0.19 27);

  --ring: oklch(0.72 0.16 262);

  --shadow-xs: 0 1px 2px oklch(0 0 0 / 0.3);
  --shadow-sm: 0 1px 3px oklch(0 0 0 / 0.4);
  --shadow-md: 0 4px 16px oklch(0 0 0 / 0.45);
  --shadow-lg: 0 16px 48px oklch(0 0 0 / 0.6);

  --grid-line: oklch(1 0 0 / 0.045);
}

/* --------------------------------------------------------------------------
   3. Reset
   -------------------------------------------------------------------------- */

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

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 5.5rem;
  /* `clip` rather than `hidden`: it stops a stray wide child from creating a
     sideways scrollbar without turning the root into a scroll container, which
     would break the sticky header. A net, not a substitute for fixing the
     child — the build's viewport check still fails on real overflow. */
  overflow-x: clip;
}

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

body {
  min-height: 100dvh;
  background: var(--canvas);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 1rem;
  line-height: 1.65;
  font-feature-settings: "cv11", "ss01";
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

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

/* Every inline icon carries `.ic`, and this is its default size.
   Without it an SVG with only a viewBox stretches to the width of whatever
   contains it — which is invisible in the components that happen to size their
   own icons and catastrophic in the ones that do not. Declared here, before any
   component rule, so a more specific selector later always wins. */
.ic {
  width: 1.125rem;
  height: 1.125rem;
  flex: none;
}

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

button {
  cursor: pointer;
  background: none;
  border: none;
  padding: 0;
}

ul,
ol {
  padding: 0;
  list-style: none;
}

a {
  color: var(--primary-text);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
  border-radius: var(--radius-sm);
}

a:hover {
  text-decoration-thickness: 2px;
}

/* A visible focus ring is not optional. `:focus-visible` keeps it off mouse
   clicks, so there is no reason left to remove it. */
:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 3px;
}

::selection {
  background: var(--primary-soft);
  color: var(--text);
}

/* --------------------------------------------------------------------------
   4. Typography
   -------------------------------------------------------------------------- */

h1,
h2,
h3,
h4 {
  font-weight: 560;
  line-height: 1.12;
  letter-spacing: -0.024em;
  text-wrap: balance;
  color: var(--text);
}

/* Capped at 3.75rem rather than 4.25. Ukrainian words are long, and at 68 px
   the hero headline ran to four lines in a two-column layout, pushing the
   primary button below the fold on a laptop. */
h1 {
  font-size: clamp(2.125rem, 1.4rem + 3vw, 3.75rem);
  line-height: 1.06;
  letter-spacing: -0.03em;
}

h2 {
  font-size: clamp(1.6rem, 1.25rem + 1.5vw, 2.4rem);
  letter-spacing: -0.028em;
}

h3 {
  font-size: clamp(1.15rem, 1.05rem + 0.45vw, 1.4rem);
}

h4 {
  font-size: 1.0625rem;
  letter-spacing: -0.015em;
}

p,
li {
  text-wrap: pretty;
}

.lede {
  font-size: clamp(1.0625rem, 1rem + 0.4vw, 1.3125rem);
  line-height: 1.6;
  color: var(--text-muted);
  max-width: var(--measure);
}

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

.faint {
  color: var(--text-faint);
  font-size: 0.875rem;
}

/* The one flourish borrowed wholesale from the reference: a single italic
   serif word inside an otherwise plain grotesk headline. */
.accent {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 500;
  color: var(--primary-text);
  letter-spacing: -0.01em;
  /* Just enough to clear the italic's overhang. At 0.06em it stacked on top of
     the space that already follows the word and read as a double space. */
  padding-inline-end: 0.02em;
}

.underline-accent {
  text-decoration: underline;
  text-decoration-color: var(--primary);
  text-decoration-thickness: 2px;
  text-underline-offset: 0.22em;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 0.8125rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--text-muted);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 0.3125rem 0.75rem 0.3125rem 0.5rem;
  box-shadow: var(--shadow-xs);
}

.eyebrow .dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--success);
  flex: none;
  position: relative;
}

.eyebrow .dot::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 1px solid var(--success);
  opacity: 0.35;
  animation: pulse 2.4s var(--ease) infinite;
}

@keyframes pulse {
  0%,
  100% {
    transform: scale(1);
    opacity: 0.35;
  }
  50% {
    transform: scale(1.35);
    opacity: 0;
  }
}

/* --------------------------------------------------------------------------
   5. Layout
   -------------------------------------------------------------------------- */

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--sp-5);
}

@media (min-width: 48rem) {
  .container {
    padding-inline: var(--sp-8);
  }
}

.narrow {
  max-width: var(--container-narrow);
  margin-inline: auto;
}

.section {
  padding-block: clamp(var(--sp-12), 8vw, var(--sp-24));
}

.section--tight {
  padding-block: clamp(var(--sp-10), 6vw, var(--sp-16));
}

.section--alt {
  background: var(--surface);
  border-block: 1px solid var(--border);
}

.section-head {
  max-width: var(--measure);
  margin-bottom: var(--sp-10);
}

.section-head p {
  margin-top: var(--sp-3);
  color: var(--text-muted);
  font-size: 1.0625rem;
}

.stack > * + * {
  margin-top: var(--sp-4);
}

.stack-lg > * + * {
  margin-top: var(--sp-8);
}

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

@media (min-width: 40rem) {
  .grid--2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 62rem) {
  .grid--3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .grid--4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

@media (min-width: 40rem) and (max-width: 61.999rem) {
  .grid--3,
  .grid--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.skip-link {
  position: absolute;
  left: var(--sp-4);
  top: var(--sp-4);
  z-index: 200;
  transform: translateY(-200%);
  background: var(--primary);
  color: var(--on-primary);
  padding: var(--sp-3) var(--sp-5);
  border-radius: var(--radius);
  font-weight: 500;
  text-decoration: none;
  transition: transform var(--dur) var(--ease);
}

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

/* --------------------------------------------------------------------------
   6. Header
   -------------------------------------------------------------------------- */

.header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: color-mix(in oklab, var(--canvas) 82%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease);
}

.header[data-scrolled="true"] {
  border-bottom-color: var(--border);
}

.header__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  min-height: 4rem;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  font-size: 1.0625rem;
  letter-spacing: -0.02em;
  color: var(--text);
  text-decoration: none;
  flex: none;
}

.brand__mark {
  width: 1.75rem;
  height: 1.75rem;
  flex: none;
}

.nav {
  display: none;
  margin-inline-start: var(--sp-6);
  gap: var(--sp-1);
}

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

.nav a {
  display: inline-flex;
  align-items: center;
  height: 2.25rem;
  padding-inline: var(--sp-3);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-size: 0.9375rem;
  font-weight: 500;
  text-decoration: none;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}

.nav a:hover {
  color: var(--text);
  background: var(--inset);
}

.nav a[aria-current="page"] {
  color: var(--text);
  background: var(--inset);
}

.header__spacer {
  flex: 1 1 auto;
}

.header__actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* Scoped to `.header` on purpose. As a bare `.header__cta` this loses to the
   later `.btn { display: inline-flex }` at equal specificity, which put the
   full-width CTA back into a 375 px header and gave the whole page a
   horizontal scrollbar. */
.header .header__cta {
  display: none;
}

@media (min-width: 48rem) {
  .header .header__cta {
    display: inline-flex;
  }
}

/* Icon buttons must still clear the 44 px touch minimum, so the visual box
   stays 36 px and the hit area is grown with a pseudo-element. */
.icon-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

.icon-btn::after {
  content: "";
  position: absolute;
  inset: -0.25rem;
}

.icon-btn:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

.icon-btn svg {
  width: 1.125rem;
  height: 1.125rem;
}

.theme-toggle .icon-moon {
  display: none;
}

:root[data-theme="dark"] .theme-toggle .icon-sun,
.theme-toggle .icon-sun {
  display: block;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-sun {
    display: none;
  }
  :root:not([data-theme="light"]) .theme-toggle .icon-moon {
    display: block;
  }
}

:root[data-theme="dark"] .theme-toggle .icon-sun {
  display: none;
}

:root[data-theme="dark"] .theme-toggle .icon-moon {
  display: block;
}

:root[data-theme="light"] .theme-toggle .icon-sun {
  display: block;
}

:root[data-theme="light"] .theme-toggle .icon-moon {
  display: none;
}

/* Language switcher: a real <details>, so it works with JavaScript disabled. */
.lang {
  position: relative;
}

.lang > summary {
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  height: 2.25rem;
  padding-inline: 0.625rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}

.lang > summary::-webkit-details-marker {
  display: none;
}

.lang > summary:hover {
  color: var(--text);
  border-color: var(--border-strong);
}

.lang > summary svg {
  width: 1rem;
  height: 1rem;
}

.lang__menu {
  position: absolute;
  inset-inline-end: 0;
  top: calc(100% + 0.375rem);
  min-width: 11rem;
  background: var(--raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-1);
  z-index: var(--z-overlay);
}

.lang__menu a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-height: 2.5rem;
  padding: 0.4375rem 0.625rem;
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 0.9375rem;
  text-decoration: none;
}

.lang__menu a:hover {
  background: var(--inset);
}

.lang__menu a[aria-current="true"] {
  color: var(--primary-text);
  font-weight: 500;
}

.lang__menu .check {
  width: 1rem;
  height: 1rem;
  opacity: 0;
}

.lang__menu a[aria-current="true"] .check {
  opacity: 1;
}

/* Mobile navigation, also a <details>: no JavaScript, no focus trap to get
   wrong, and it degrades to a plain open list if CSS fails. */
.menu {
  position: relative;
}

@media (min-width: 62rem) {
  .menu {
    display: none;
  }
}

.menu > summary {
  list-style: none;
}

.menu > summary::-webkit-details-marker {
  display: none;
}

.menu[open] .icon-menu {
  display: none;
}

.menu:not([open]) .icon-close {
  display: none;
}

.menu__panel {
  position: fixed;
  inset: 4rem 0 auto 0;
  max-height: calc(100dvh - 4rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--canvas);
  border-bottom: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-4) var(--sp-5) var(--sp-8);
  z-index: var(--z-overlay);
}

.menu__panel a {
  display: flex;
  align-items: center;
  min-height: 3rem;
  padding-inline: var(--sp-2);
  border-radius: var(--radius);
  color: var(--text);
  font-size: 1.0625rem;
  font-weight: 500;
  text-decoration: none;
}

.menu__panel a:hover {
  background: var(--inset);
}

.menu__panel hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin-block: var(--sp-3);
}

.menu__panel .btn {
  margin-top: var(--sp-4);
  width: 100%;
}

/* --------------------------------------------------------------------------
   7. Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: 2.75rem;
  padding: 0.6875rem 1.25rem;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  font-size: 0.9375rem;
  font-weight: 550;
  letter-spacing: -0.005em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease);
}

.btn svg {
  width: 1.125rem;
  height: 1.125rem;
  flex: none;
}

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

.btn--primary {
  background: var(--primary);
  color: var(--on-primary);
  box-shadow: var(--shadow-sm);
}

.btn--primary:hover {
  background: var(--primary-hover);
  box-shadow: var(--shadow-md);
}

.btn--secondary {
  background: var(--surface);
  color: var(--text);
  border-color: var(--border);
  box-shadow: var(--shadow-xs);
}

.btn--secondary:hover {
  border-color: var(--border-strong);
  background: var(--inset);
}

.btn--ghost {
  background: transparent;
  color: var(--text-muted);
}

.btn--ghost:hover {
  color: var(--text);
  background: var(--inset);
}

.btn--lg {
  min-height: 3.25rem;
  padding: 0.875rem 1.75rem;
  font-size: 1.0625rem;
}

.btn--sm {
  min-height: 2.25rem;
  padding: 0.375rem 0.875rem;
  font-size: 0.875rem;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  align-items: center;
}

/* --------------------------------------------------------------------------
   8. Hero
   -------------------------------------------------------------------------- */

.hero {
  position: relative;
  overflow: hidden;
  padding-block: clamp(var(--sp-12), 9vw, var(--sp-24)) clamp(var(--sp-10), 7vw, var(--sp-20));
  isolation: isolate;
}

/* A faint ruled grid, faded out by a radial mask. Pure CSS, no image request. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  background-image: linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
  background-size: 3.5rem 3.5rem;
  -webkit-mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 40%, transparent 100%);
  mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, #000 40%, transparent 100%);
}

.hero::after {
  content: "";
  position: absolute;
  z-index: -1;
  inset-inline: 0;
  top: -14rem;
  height: 32rem;
  background: radial-gradient(
    ellipse 60% 50% at 50% 50%,
    color-mix(in oklab, var(--primary) 16%, transparent),
    transparent 70%
  );
  pointer-events: none;
}

.hero__grid {
  display: grid;
  gap: clamp(var(--sp-8), 5vw, var(--sp-16));
  align-items: center;
}

@media (min-width: 62rem) {
  .hero__grid {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  }
}

.hero h1 {
  margin-top: var(--sp-6);
}

.hero .lede {
  margin-top: var(--sp-5);
}

.hero .btn-row {
  margin-top: var(--sp-8);
}

.hero__note {
  margin-top: var(--sp-4);
  font-size: 0.875rem;
  color: var(--text-faint);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.hero__note svg {
  width: 1rem;
  height: 1rem;
  flex: none;
  color: var(--success);
}

/* --------------------------------------------------------------------------
   9. Phone mock — the alert as the reader will actually receive it
   -------------------------------------------------------------------------- */

.phone {
  position: relative;
  margin-inline: auto;
  width: min(21rem, 100%);
  border-radius: 2.25rem;
  border: 1px solid var(--border);
  background: var(--surface);
  box-shadow: var(--shadow-lg);
  padding: 0.625rem;
}

.phone__screen {
  border-radius: 1.75rem;
  background: var(--inset);
  border: 1px solid var(--border);
  overflow: hidden;
}

.phone__bar {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.875rem 1rem;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.phone__avatar {
  width: 2.125rem;
  height: 2.125rem;
  border-radius: 50%;
  background: var(--primary);
  color: var(--on-primary);
  display: grid;
  place-items: center;
  flex: none;
}

.phone__avatar svg {
  width: 1.125rem;
  height: 1.125rem;
}

.phone__title {
  font-size: 0.9375rem;
  font-weight: 550;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

.phone__sub {
  font-size: 0.75rem;
  color: var(--text-faint);
}

.phone__body {
  padding: var(--sp-4);
  display: grid;
  gap: var(--sp-3);
}

.bubble {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 1rem 1rem 1rem 0.25rem;
  padding: 0.8125rem 0.9375rem;
  font-size: 0.9375rem;
  line-height: 1.5;
  box-shadow: var(--shadow-xs);
  max-width: 95%;
}

.bubble strong {
  font-weight: 600;
}

.bubble__meta {
  display: block;
  margin-top: 0.375rem;
  font-size: 0.6875rem;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

.bubble--alert {
  border-color: var(--primary-soft-border);
  background: var(--primary-soft);
}

.bubble__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  margin-top: 0.5rem;
  font-size: 0.875rem;
  font-weight: 550;
  color: var(--primary-text);
  text-decoration: none;
}

.bubble__cta svg {
  width: 0.875rem;
  height: 0.875rem;
}

/* --------------------------------------------------------------------------
   10. Cards
   -------------------------------------------------------------------------- */

.card {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: var(--sp-6);
  box-shadow: var(--shadow-xs);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
    transform var(--dur) var(--ease);
}

.card h3,
.card .card-title {
  margin-bottom: var(--sp-2);
}

/* A card heading takes its level from where it sits in the document outline,
   not from how big it should look. On a city page the facts card follows the
   h1 directly, so it must be an h2 — and then it needs h3's size. */
.card h2.card-title,
.callout h2.callout-title {
  font-size: clamp(1.15rem, 1.05rem + 0.45vw, 1.4rem);
  line-height: 1.25;
  letter-spacing: -0.022em;
}

.card p {
  color: var(--text-muted);
  font-size: 0.9375rem;
}

.card__icon {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius);
  background: var(--primary-soft);
  border: 1px solid var(--primary-soft-border);
  color: var(--primary-text);
  margin-bottom: var(--sp-4);
}

.card__icon svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* Whole-card link: the anchor covers the card, so the target is the card and
   not just the words, but the text still reads as one heading to a screen
   reader instead of a wall of repeated link text. */
.card--link:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
  transform: translateY(-2px);
}

.card--link:focus-within {
  border-color: var(--ring);
}

.card--link .stretch::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

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

.card__arrow {
  position: absolute;
  top: var(--sp-6);
  inset-inline-end: var(--sp-6);
  color: var(--text-faint);
  transition: transform var(--dur) var(--ease), color var(--dur) var(--ease);
}

.card__arrow svg {
  width: 1.125rem;
  height: 1.125rem;
}

.card--link:hover .card__arrow {
  color: var(--primary-text);
  transform: translateX(3px);
}

.city-card__meta {
  margin-top: var(--sp-4);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--border);
  display: grid;
  gap: var(--sp-2);
  font-size: 0.875rem;
  color: var(--text-muted);
}

.city-card__meta div {
  display: flex;
  gap: 0.5rem;
  align-items: flex-start;
}

.city-card__meta svg {
  width: 1rem;
  height: 1rem;
  flex: none;
  margin-top: 0.2em;
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   11. Steps
   -------------------------------------------------------------------------- */

.steps {
  counter-reset: step;
  display: grid;
  gap: var(--sp-5);
}

@media (min-width: 62rem) {
  .steps--3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.step {
  position: relative;
  padding-top: var(--sp-8);
}

.step::before {
  counter-increment: step;
  content: counter(step);
  position: absolute;
  top: 0;
  inset-inline-start: 0;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--text);
  color: var(--canvas);
  font-size: 0.8125rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.step h3 {
  margin-bottom: var(--sp-2);
}

.step p {
  color: var(--text-muted);
  font-size: 0.9375rem;
  max-width: 38ch;
}

/* --------------------------------------------------------------------------
   12. Stats
   -------------------------------------------------------------------------- */

/* The minimum track is 9.5 rem rather than 13 so that four figures land as a
   2×2 block on a 390 px phone instead of a single column a full screen tall.
   Four numbers the reader can take in at a glance is the entire point of the
   section; four numbers they have to scroll through is just more page. */
.stats {
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9.5rem), 1fr));
}

@media (min-width: 48rem) {
  .stats {
    gap: var(--sp-5);
  }
}

.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: var(--sp-5);
}

@media (min-width: 48rem) {
  .stat {
    padding: var(--sp-6);
  }
}

.stat__value {
  font-size: clamp(1.75rem, 1.4rem + 1.4vw, 2.5rem);
  font-weight: 560;
  letter-spacing: -0.03em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.stat__label {
  margin-top: var(--sp-2);
  font-size: 0.9375rem;
  color: var(--text-muted);
}

.stat__note {
  margin-top: var(--sp-3);
  font-size: 0.8125rem;
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   13. Callouts
   -------------------------------------------------------------------------- */

.callout {
  display: flex;
  gap: var(--sp-4);
  padding: var(--sp-5);
  border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 0.9375rem;
}

.callout svg {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
  margin-top: 0.15em;
}

.callout p + p {
  margin-top: var(--sp-2);
}

.callout strong {
  font-weight: 600;
}

.callout--warn {
  background: var(--warning-soft);
  border-color: var(--warning-border);
}

.callout--warn > svg {
  color: var(--warning-text);
}

.callout--info {
  background: var(--primary-soft);
  border-color: var(--primary-soft-border);
}

.callout--info > svg {
  color: var(--primary-text);
}

/* --------------------------------------------------------------------------
   14. FAQ
   -------------------------------------------------------------------------- */

.faq {
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--surface);
  overflow: hidden;
}

.faq__item + .faq__item {
  border-top: 1px solid var(--border);
}

.faq__item > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-6);
  font-weight: 550;
  font-size: 1.0625rem;
  letter-spacing: -0.015em;
  line-height: 1.4;
  min-height: 3.5rem;
  transition: background var(--dur-fast) var(--ease);
}

.faq__item > summary::-webkit-details-marker {
  display: none;
}

.faq__item > summary:hover {
  background: var(--inset);
}

.faq__chevron {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
  margin-top: 0.2em;
  color: var(--text-faint);
  transition: transform var(--dur) var(--ease);
}

.faq__item[open] .faq__chevron {
  transform: rotate(180deg);
}

.faq__body {
  padding: 0 var(--sp-6) var(--sp-5);
  color: var(--text-muted);
  max-width: var(--measure);
}

.faq__body p + p {
  margin-top: var(--sp-3);
}

/* --------------------------------------------------------------------------
   15. Prose — the legal and long-form pages
   -------------------------------------------------------------------------- */

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

.prose > * + * {
  margin-top: var(--sp-4);
}

.prose h2 {
  margin-top: var(--sp-12);
  font-size: clamp(1.35rem, 1.15rem + 0.9vw, 1.75rem);
  scroll-margin-top: 6rem;
}

.prose h3 {
  margin-top: var(--sp-8);
}

.prose p,
.prose li {
  color: var(--text-muted);
}

.prose strong {
  color: var(--text);
  font-weight: 600;
}

.prose ul,
.prose ol {
  padding-inline-start: var(--sp-6);
  display: grid;
  gap: var(--sp-2);
}

.prose ul {
  list-style: disc;
}

.prose ol {
  list-style: decimal;
}

.prose li::marker {
  color: var(--text-faint);
}

.prose dl {
  display: grid;
  gap: var(--sp-3);
  padding: var(--sp-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.prose dt {
  font-weight: 600;
  font-size: 0.9375rem;
}

.prose dd {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.9375rem;
}

.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
  min-width: 34rem;
}

th,
td {
  text-align: start;
  padding: var(--sp-3) var(--sp-4);
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

th {
  font-weight: 600;
  color: var(--text);
  background: var(--inset);
  white-space: nowrap;
}

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

tbody tr:last-child td {
  border-bottom: none;
}

/* --------------------------------------------------------------------------
   16. Breadcrumbs & page header
   -------------------------------------------------------------------------- */

.page-head {
  padding-block: clamp(var(--sp-8), 5vw, var(--sp-16)) var(--sp-8);
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.crumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.8125rem;
  color: var(--text-faint);
  margin-bottom: var(--sp-4);
}

.crumbs a {
  color: var(--text-muted);
  text-decoration: none;
}

.crumbs a:hover {
  color: var(--text);
  text-decoration: underline;
}

.crumbs svg {
  width: 0.875rem;
  height: 0.875rem;
  opacity: 0.6;
}

.updated {
  margin-top: var(--sp-5);
  font-size: 0.8125rem;
  color: var(--text-faint);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.updated svg {
  width: 0.9375rem;
  height: 0.9375rem;
}

/* --------------------------------------------------------------------------
   17. CTA band
   -------------------------------------------------------------------------- */

.cta-band {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-xl);
  border: 1px solid var(--border);
  background: var(--surface);
  padding: clamp(var(--sp-8), 5vw, var(--sp-16));
  text-align: center;
  isolation: isolate;
}

.cta-band::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset-inline: 0;
  bottom: -12rem;
  height: 24rem;
  background: radial-gradient(
    ellipse 50% 50% at 50% 50%,
    color-mix(in oklab, var(--primary) 18%, transparent),
    transparent 70%
  );
}

.cta-band h2 {
  max-width: 22ch;
  margin-inline: auto;
}

.cta-band p {
  margin-top: var(--sp-4);
  margin-inline: auto;
  max-width: 52ch;
  color: var(--text-muted);
  font-size: 1.0625rem;
}

.cta-band .btn-row {
  margin-top: var(--sp-8);
  justify-content: center;
}

/* --------------------------------------------------------------------------
   18. Footer
   -------------------------------------------------------------------------- */

.footer {
  border-top: 1px solid var(--border);
  background: var(--surface);
  padding-block: var(--sp-12) var(--sp-8);
  margin-top: var(--sp-8);
}

.footer__grid {
  display: grid;
  gap: var(--sp-8);
}

@media (min-width: 48rem) {
  .footer__grid {
    grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr));
    gap: var(--sp-10);
  }
}

.footer__about p {
  margin-top: var(--sp-4);
  font-size: 0.875rem;
  color: var(--text-muted);
  max-width: 34ch;
}

.footer h2 {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: var(--sp-4);
}

.footer ul {
  display: grid;
  gap: 0.125rem;
}

.footer li a,
.footer li button {
  display: inline-flex;
  align-items: center;
  min-height: 2.25rem;
  font-size: 0.875rem;
  color: var(--text-muted);
  text-decoration: none;
  text-align: start;
}

.footer li a:hover,
.footer li button:hover {
  color: var(--text);
  text-decoration: underline;
}

.footer__bottom {
  margin-top: var(--sp-10);
  padding-top: var(--sp-6);
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  align-items: center;
  justify-content: space-between;
  font-size: 0.8125rem;
  color: var(--text-faint);
}

.footer__disclaimer {
  margin-top: var(--sp-6);
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--inset);
  font-size: 0.8125rem;
  color: var(--text-muted);
  max-width: var(--measure);
}

/* --------------------------------------------------------------------------
   19. Consent — banner and preferences dialog
   -------------------------------------------------------------------------- */

/* Hidden until the script decides it is needed, so a reader who already chose
   never sees a flash of the banner. */
.consent[hidden] {
  display: none !important;
}

/* Anchored bottom-end. Centred, it lands squarely on the hero's primary
   button; bottom-start covers it too, because the headline column is on the
   left. The right corner is the only one where the banner overlaps nothing the
   reader needs to press. */
.consent {
  position: fixed;
  z-index: var(--z-dialog);
  inset-inline: 0;
  bottom: 0;
  padding: var(--sp-4);
  padding-bottom: max(var(--sp-4), env(safe-area-inset-bottom));
  display: flex;
  justify-content: flex-end;
  pointer-events: none;
}

.consent__card {
  pointer-events: auto;
  width: min(29rem, 100%);
  background: var(--raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
  padding: var(--sp-5);
  animation: consent-in 260ms var(--ease) both;
}

@keyframes consent-in {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .consent__card {
    animation: none;
  }
}

.consent__title {
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin-bottom: var(--sp-2);
}

.consent__text {
  font-size: 0.875rem;
  color: var(--text-muted);
  max-width: 62ch;
}

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

.consent__actions {
  margin-top: var(--sp-5);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* Accept and reject are the same size, same weight, same visual rank. The
   EDPB cookie-banner taskforce treats a quieter reject button as no valid
   consent at all, so this symmetry is a requirement, not a preference. */
.consent__actions .btn--primary,
.consent__actions .btn--secondary {
  flex: 1 1 9.5rem;
}

.consent__actions .btn--settings {
  flex: 1 1 100%;
}

dialog.prefs {
  /* Restored explicitly. The `* { margin: 0 }` reset overrides the user-agent
     `dialog { margin: auto }`, which is what centres a modal dialog — without
     this the sheet pins itself to the top-left corner. */
  margin: auto;
  width: min(34rem, calc(100vw - 2rem));
  max-height: min(80dvh, 44rem);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--raised);
  color: var(--text);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

dialog.prefs::backdrop {
  background: oklch(0.16 0.004 286 / 0.5);
  backdrop-filter: blur(3px);
}

.prefs__head {
  padding: var(--sp-5) var(--sp-6);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
}

.prefs__head h2 {
  font-size: 1.125rem;
}

.prefs__body {
  padding: var(--sp-5) var(--sp-6);
  overflow-y: auto;
  max-height: 46dvh;
  display: grid;
  gap: var(--sp-4);
}

.prefs__foot {
  padding: var(--sp-4) var(--sp-6) var(--sp-5);
  border-top: 1px solid var(--border);
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.prefs__foot .btn {
  flex: 1 1 10rem;
}

.switch-row {
  display: flex;
  gap: var(--sp-4);
  align-items: flex-start;
  justify-content: space-between;
  padding: var(--sp-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.switch-row__text {
  flex: 1 1 auto;
}

.switch-row__text strong {
  display: block;
  font-size: 0.9375rem;
  font-weight: 600;
}

.switch-row__text span {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.8125rem;
  color: var(--text-muted);
}

/* Native checkbox, restyled. Keeping the real input means keyboard, screen
   readers and form semantics all work without a line of JavaScript. */
.switch {
  position: relative;
  flex: none;
  width: 2.75rem;
  height: 1.625rem;
  margin-top: 0.125rem;
}

.switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.switch__track {
  display: block;
  width: 100%;
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--border-strong);
  transition: background var(--dur) var(--ease);
  pointer-events: none;
}

.switch__track::after {
  content: "";
  position: absolute;
  top: 0.1875rem;
  inset-inline-start: 0.1875rem;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur) var(--ease);
}

.switch input:checked ~ .switch__track {
  background: var(--primary);
}

.switch input:checked ~ .switch__track::after {
  transform: translateX(1.125rem);
}

.switch input:disabled {
  cursor: not-allowed;
}

.switch input:disabled ~ .switch__track {
  opacity: 0.55;
}

.switch input:focus-visible ~ .switch__track {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

[dir="rtl"] .switch input:checked ~ .switch__track::after {
  transform: translateX(-1.125rem);
}

/* --------------------------------------------------------------------------
   20. Utilities
   -------------------------------------------------------------------------- */

.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;
}

.center {
  text-align: center;
}

/* Spacing utilities exist because the Content-Security-Policy on every page is
   `style-src 'self'` — no `unsafe-inline`. A `style="margin-top:…"` attribute
   is silently dropped under that policy, so the layout would quietly degrade
   in production while looking correct in a local file:// preview. */
.mt-0 {
  margin-top: 0;
}

.mt-3 {
  margin-top: var(--sp-3);
}

.mt-4 {
  margin-top: var(--sp-4);
}

.mt-5 {
  margin-top: var(--sp-5);
}

.mt-6 {
  margin-top: var(--sp-6);
}

.mt-8 {
  margin-top: var(--sp-8);
}

.mt-10 {
  margin-top: var(--sp-10);
}

.mb-8 {
  margin-bottom: var(--sp-8);
}

.mb-10 {
  margin-bottom: var(--sp-10);
}

.measure {
  max-width: var(--measure);
}

.center-x {
  margin-inline: auto;
}

.justify-center {
  justify-content: center;
}

.callout-title {
  font-size: 1.25rem;
  margin-bottom: var(--sp-3);
}

/* The address block. Label and value are distinguished by weight and colour
   rather than by indentation, so the two never collide when a long Polish
   street name wraps on a narrow screen. */
.facts-dl {
  margin-top: var(--sp-4);
  border: 0;
  padding: 0;
  background: none;
  display: block;
}

.facts-dl dt {
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: 0.25rem;
}

.facts-dl dd {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--text);
}

.facts-dl dd + dt {
  margin-top: var(--sp-4);
}

.pill-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.3125rem 0.75rem;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  font-size: 0.8125rem;
  color: var(--text-muted);
  text-decoration: none;
}

a.pill:hover {
  border-color: var(--border-strong);
  color: var(--text);
}

.pill svg {
  width: 0.875rem;
  height: 0.875rem;
}

.source-note {
  margin-top: var(--sp-6);
}

.source-note__label {
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: var(--sp-3);
}

.source-note .pill {
  max-width: min(100%, 30rem);
  align-items: flex-start;
  text-align: start;
  line-height: 1.45;
  padding-block: 0.4375rem;
}

.source-note .pill svg {
  margin-top: 0.15em;
  width: 0.8125rem;
  height: 0.8125rem;
}

/* --------------------------------------------------------------------------
   20b. Motion

   Rules this section follows, because the difference between motion that reads
   as expensive and motion that reads as a template is entirely in the numbers:

   - Entrances are 400-600 ms with a soft ease-out. Anything faster reads as a
     glitch, anything slower makes the reader wait for the page.
   - Stagger is 60-90 ms. Below that the group moves as one blob; above it the
     reader notices they are being animated at.
   - Only `transform` and `opacity` are animated. Both are composited, so none
     of this touches layout and none of it can shift text under a thumb.
   - Nothing moves more than about 16 px. Large travel is what makes a page
     feel like a slideshow.
   - Every element is fully visible with animation switched off. The reveal
     adds polish to a page that already works, rather than gating content
     behind a script.
   -------------------------------------------------------------------------- */

/* Transform only. No fade.
 *
 * The fade was the obvious way to do this and it was wrong in four places at
 * once. Text at partial opacity fails a contrast check — Lighthouse sampled
 * `.stat__label` at #efeff0 on white, 1.14:1, because it happened to look
 * while the element was a tenth of the way through its transition. The same
 * partial state is what a crawler, a print job and a full-page screenshot see.
 *
 * Sliding without fading keeps every colour at its declared value for the
 * whole animation, so the contrast is correct at every frame and there is no
 * moment at which the page is invisible to anything. The movement still reads
 * as deliberate; the fade was never what made it feel considered. */
[data-reveal] {
  transform: translateY(16px);
  transition: transform 520ms var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: transform;
}

[data-reveal].is-revealed {
  transform: none;
}

/* Without the script the offset never resolves, so it is not applied at all.
   `.js` is set on <html> by the theme bootstrap before first paint. */
html:not(.js) [data-reveal] {
  transform: none;
}

/* Slow aurora behind the hero. Two counter-rotating radial blooms at low
   opacity; the movement is barely perceptible frame to frame and obvious over
   ten seconds, which is the intended effect. */
.hero__aurora {
  position: absolute;
  inset: -30% -10% auto -10%;
  height: 46rem;
  z-index: -1;
  pointer-events: none;
  filter: blur(60px);
  opacity: 0.55;
}

.hero__aurora span {
  position: absolute;
  display: block;
  border-radius: 50%;
  mix-blend-mode: normal;
}

.hero__aurora span:nth-child(1) {
  width: 38rem;
  height: 30rem;
  left: 8%;
  top: 18%;
  background: radial-gradient(circle, color-mix(in oklab, var(--primary) 42%, transparent), transparent 68%);
  animation: drift-a 26s ease-in-out infinite alternate;
}

.hero__aurora span:nth-child(2) {
  width: 32rem;
  height: 26rem;
  right: 4%;
  top: 6%;
  background: radial-gradient(circle, color-mix(in oklab, oklch(0.62 0.19 200) 34%, transparent), transparent 68%);
  animation: drift-b 32s ease-in-out infinite alternate;
}

.hero__aurora span:nth-child(3) {
  width: 26rem;
  height: 22rem;
  left: 42%;
  top: 40%;
  background: radial-gradient(circle, color-mix(in oklab, oklch(0.6 0.2 300) 26%, transparent), transparent 68%);
  animation: drift-a 38s ease-in-out infinite alternate-reverse;
}

@keyframes drift-a {
  from {
    transform: translate3d(-4%, -3%, 0) scale(1);
  }
  to {
    transform: translate3d(6%, 5%, 0) scale(1.14);
  }
}

@keyframes drift-b {
  from {
    transform: translate3d(5%, 4%, 0) scale(1.08);
  }
  to {
    transform: translate3d(-6%, -4%, 0) scale(0.94);
  }
}

/* Cursor spotlight. The script writes --mx/--my as percentages on the card and
   nothing else; all the rendering is CSS, so the pointer handler stays cheap
   enough to run on move without throttling. */
.card--spot::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
  background: radial-gradient(
    22rem circle at var(--mx, 50%) var(--my, 50%),
    color-mix(in oklab, var(--primary) 12%, transparent),
    transparent 70%
  );
  pointer-events: none;
}

@media (hover: hover) {
  .card--spot:hover::before {
    opacity: 1;
  }
}

/* Count-up. The value is swapped by script; `tabular-nums` on body keeps the
   digits from jittering as they change width. */
.stat__value {
  font-variant-numeric: tabular-nums;
}

.stat {
  position: relative;
  overflow: hidden;
}

.stat::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 0;
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--primary), transparent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 900ms var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}

.stat.is-revealed::after {
  transform: scaleX(1);
}

/* --------------------------------------------------------------------------
   20c. Live alert feed — the day's real detections
   -------------------------------------------------------------------------- */

.feed {
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--surface);
  overflow: hidden;
}

.feed__head {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--border);
  background: var(--inset);
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--text-muted);
}

.feed__live {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  color: var(--success);
  font-weight: 600;
}

.feed__live i {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: currentColor;
  animation: blink 2s var(--ease) infinite;
}

@keyframes blink {
  0%,
  100% {
    opacity: 1;
    transform: scale(1);
  }
  50% {
    opacity: 0.35;
    transform: scale(0.8);
  }
}

.feed__list {
  display: grid;
}

.feed__row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border);
  transform: translateY(10px);
  transition: transform 460ms var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}

.feed__row:first-child {
  border-top: 0;
}

.is-revealed > .feed__row,
.feed__row.is-revealed {
  opacity: 1;
  transform: none;
}

.feed__time {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  font-size: 1.0625rem;
  letter-spacing: -0.02em;
}

.feed__city {
  font-size: 0.9375rem;
  color: var(--text-muted);
}

.feed__tag {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.25rem 0.625rem;
  border-radius: var(--radius-pill);
  background: var(--success-soft);
  color: var(--success);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
}

.feed__tag svg {
  width: 0.8125rem;
  height: 0.8125rem;
}

/* The 03:37 row is the argument. Marking it costs one line and does the work
   of a paragraph. */
.feed__row--night .feed__time {
  color: var(--primary-text);
}

.feed__row--night .feed__city::after {
  content: " · " attr(data-note);
  color: var(--primary-text);
  font-weight: 500;
}

.feed__foot {
  padding: var(--sp-4) var(--sp-5);
  border-top: 1px solid var(--border);
  background: var(--inset);
  font-size: 0.8125rem;
  color: var(--text-faint);
}

/* --------------------------------------------------------------------------
   20d. Phone demo — typing indicator, then the alert lands
   -------------------------------------------------------------------------- */

.phone {
  transition: transform 700ms var(--ease);
}

@media (hover: hover) and (min-width: 62rem) {
  .phone {
    transform: perspective(1400px) rotateY(-7deg) rotateX(2deg) translateZ(0);
  }

  .phone:hover {
    transform: perspective(1400px) rotateY(0deg) rotateX(0deg);
  }
}

.typing {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.75rem 0.9375rem;
}

.typing i {
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: 50%;
  background: var(--text-faint);
  animation: typing 1.25s ease-in-out infinite;
}

.typing i:nth-child(2) {
  animation-delay: 0.16s;
}

.typing i:nth-child(3) {
  animation-delay: 0.32s;
}

@keyframes typing {
  0%,
  60%,
  100% {
    transform: translateY(0);
    opacity: 0.45;
  }
  30% {
    transform: translateY(-4px);
    opacity: 1;
  }
}

/* The alert bubble arrives rather than appearing: a short rise plus a one-off
   ring that fades. The ring is what makes it read as "this just happened". */
.bubble--arriving {
  animation: arrive 620ms var(--ease) both;
}

@keyframes arrive {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.bubble--alert {
  position: relative;
}

.bubble--alert.is-fresh::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: inherit;
  border: 2px solid var(--primary);
  opacity: 0;
  animation: ring 1.4s var(--ease) 1;
}

@keyframes ring {
  0% {
    opacity: 0.7;
    transform: scale(0.99);
  }
  100% {
    opacity: 0;
    transform: scale(1.03);
  }
}

.phone__hidden {
  display: none;
}

/* --------------------------------------------------------------------------
   20e. Alert bar — one line of news, not a paragraph
   -------------------------------------------------------------------------- */

.newsbar {
  border-block: 1px solid var(--warning-border);
  background: var(--warning-soft);
}

.newsbar__inner {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding-block: var(--sp-3);
  font-size: 0.9375rem;
  color: var(--text);
}

.newsbar svg {
  color: var(--warning-text);
  flex: none;
}

.newsbar strong {
  font-weight: 600;
}

.newsbar a {
  color: var(--text);
  white-space: nowrap;
  margin-inline-start: auto;
  font-size: 0.875rem;
  font-weight: 500;
}

@media (max-width: 40rem) {
  .newsbar a {
    display: none;
  }
}

/* --------------------------------------------------------------------------
   20f. Support
   -------------------------------------------------------------------------- */

.support {
  display: grid;
  gap: var(--sp-8);
  align-items: center;
}

@media (min-width: 62rem) {
  .support {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

.support__points {
  display: grid;
  gap: var(--sp-4);
  margin-top: var(--sp-6);
}

.support__point {
  display: flex;
  gap: var(--sp-3);
  align-items: flex-start;
  font-size: 0.9375rem;
}

.support__point svg {
  color: var(--success);
  margin-top: 0.2em;
}

.chat {
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--surface);
  box-shadow: var(--shadow-md);
  padding: var(--sp-5);
  display: grid;
  gap: var(--sp-3);
}

.chat__msg {
  max-width: 85%;
  padding: 0.75rem 0.9375rem;
  border-radius: 1rem 1rem 1rem 0.25rem;
  background: var(--inset);
  border: 1px solid var(--border);
  font-size: 0.9375rem;
  line-height: 1.5;
}

.chat__msg--me {
  margin-inline-start: auto;
  border-radius: 1rem 1rem 0.25rem 1rem;
  background: var(--primary);
  border-color: var(--primary);
  color: var(--on-primary);
}

.chat__meta {
  display: block;
  margin-top: 0.375rem;
  font-size: 0.6875rem;
  opacity: 0.75;
  font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------------------------
   20g. Testimonials
   -------------------------------------------------------------------------- */

.quote {
  display: grid;
  gap: var(--sp-4);
  padding: var(--sp-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--surface);
}

.quote blockquote {
  font-size: 1.0625rem;
  line-height: 1.6;
  letter-spacing: -0.01em;
}

.quote figcaption {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: 0.875rem;
  color: var(--text-muted);
}

.quote__avatar {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--primary-soft);
  color: var(--primary-text);
  font-weight: 600;
  flex: none;
}

/* --------------------------------------------------------------------------
   21. Motion preferences
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .card--link:hover,
  .phone,
  .phone:hover {
    transform: none;
  }

  /* Reveals must land in their final state rather than merely arrive quickly:
     with `transition-duration` clamped, an element whose class is never added
     would otherwise stay at opacity 0 forever. */
  [data-reveal],
  .feed__row {
    transform: none !important;
  }

  /* Ambient motion has no informational content, so it goes entirely rather
     than becoming a fast version of itself. */
  .hero__aurora span,
  .feed__live i,
  .eyebrow .dot::after,
  .typing i {
    animation: none !important;
  }

  .stat::after {
    transform: scaleX(1);
  }
}

/* --------------------------------------------------------------------------
   22. Print
   -------------------------------------------------------------------------- */

@media print {
  .header,
  .footer,
  .consent,
  .cta-band,
  .skip-link,
  .hero__aurora {
    display: none !important;
  }

  /* Printing happens without scrolling, so an element still waiting for its
     reveal would come out shifted. Nothing is ever invisible — the reveal
     moves, it does not fade — but a page of nudged blocks still prints badly. */
  [data-reveal],
  .feed__row {
    transform: none !important;
  }

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

  .card,
  .callout,
  .faq {
    break-inside: avoid;
    border: 1px solid #ccc;
  }

  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.75em;
    color: #555;
  }
}

/* --------------------------------------------------------------------------
   23. Telegram mark
   -------------------------------------------------------------------------- */

.tg-mark {
  width: 1.25rem;
  height: 1.25rem;
  flex: none;
}

.tg-note {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  max-width: var(--measure);
}

.tg-note .tg-mark {
  margin-top: 0.1em;
}

/* --------------------------------------------------------------------------
   24. Tariffs
   -------------------------------------------------------------------------- */

.plans {
  display: grid;
  gap: var(--sp-5);
  /* STRETCH, NOT START. The cards carry different numbers of lines — only one
     of them has a saving badge — and left to their natural heights they ended
     up 379, 405 and 403 pixels tall, with three "Обрати" buttons on three
     different baselines. `.plan` is already a flex column with the button on
     `margin-top: auto`, so stretching is all that was missing. */
  align-items: stretch;
}

/* ONE COLUMN, THEN THREE. This was `repeat(4, ...)` back when the ladder had
   four period tariffs; with three packages it left an empty fourth column and
   the whole row sat visibly off-centre against the container. Two columns in
   between is worse than one: it strands the third card alone on its own row,
   and whichever package lands there reads as an afterthought. */
@media (min-width: 64rem) {
  .plans {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.plan:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-md);
}

/* The recommended plan stands out by weight, not by position.
   Lifting it broke the row: with four cards, one of them floating above the
   others made the whole block look misaligned rather than emphasised. A
   heavier border, a tinted ground and the only filled button do the same job
   and leave the grid straight. */
.plan--featured {
  border-color: var(--primary);
  border-width: 2px;
  background: linear-gradient(
    180deg,
    color-mix(in oklab, var(--primary) 5%, var(--surface)),
    var(--surface) 60%
  );
  box-shadow: var(--shadow-md);
}

.plan--featured .plan__price {
  color: var(--primary-text);
}

.plan--featured .plan__saving {
  background: var(--primary);
  color: var(--on-primary);
}

.plan__badge {
  position: absolute;
  top: -0.75rem;
  inset-inline-start: var(--sp-6);
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.75rem;
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--on-primary);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.plan__name {
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--text-muted);
  margin: 0;
}

.plan__price {
  font-size: clamp(2rem, 1.6rem + 1.6vw, 2.75rem);
  font-weight: 560;
  letter-spacing: -0.035em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.plan__per {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-2);
  /* Reserved on every card, badge or no badge, so the rule under it lands on
     one line across all three. Without it the package with no saving badge
     pulled its whole feature list a row higher than its neighbours. */
  min-height: 3.35rem;
  font-size: 0.875rem;
  color: var(--text-faint);
  margin-top: calc(var(--sp-4) * -1 + 0.25rem);
}

.plan__features {
  display: grid;
  gap: var(--sp-3);
  font-size: 0.9375rem;
  color: var(--text-muted);
  padding-top: var(--sp-2);
  border-top: 1px solid var(--border);
}

.plan__features li {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
}

.plan__features svg {
  color: var(--success);
  margin-top: 0.2em;
}

.plan .btn {
  margin-top: auto;
  width: 100%;
}

/* --------------------------------------------------------------------------
   25. The three confirmations before payment
   -------------------------------------------------------------------------- */

.consent-box {
  border: 1px solid var(--warning-border);
  border-radius: var(--radius-xl);
  background: var(--warning-soft);
  padding: var(--sp-6);
}

.consent-box h2 {
  font-size: 1.125rem;
  margin-bottom: var(--sp-4);
}

.consent-box ul {
  display: grid;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
}

.consent-item {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  font-size: 0.9375rem;
  line-height: 1.5;
}

.consent-item svg {
  flex: none;
  margin-top: 0.2em;
  color: var(--warning-text);
}

/* The saving sits with the daily rate rather than beside the headline price:
   it is a fact about the rate, and putting it next to the total would read as
   a discount off a price that never existed. */
.plan__saving {
  display: inline-block;
  margin-inline-start: 0.5rem;
  padding: 0.125rem 0.5rem;
  border-radius: var(--radius-pill);
  background: var(--success-soft);
  color: var(--success);
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
}

/* ==========================================================================
   26. Depth, texture and photography

   Everything below is ambient: it carries no information, so all of it is
   removed under `prefers-reduced-motion` at the end of the section rather
   than being made faster. Only `transform`, `opacity` and `clip-path` are
   animated — a layout property here would jank on the mid-range Android this
   site is actually read on.

   Scroll-linked motion uses the native `animation-timeline: view()`. A JS
   scroll listener was the alternative and is worse in the way that matters:
   it runs on the main thread, so it stutters exactly when the page is busy.
   Where the property is unsupported the animations simply never start, and
   the page is the static one it was before — which is why every rule here
   sets its resting state outside the `@supports` block.
   ========================================================================== */

/* --------------------------------------------------------------------------
   26.1 Grain

   A fixed 140px noise tile at 2.8% opacity. Flat colour fields on an OLED
   phone show banding across a gradient this wide; noise is the standard
   answer and costs about 700 bytes as a data URI. `img-src 'self' data:` in
   the CSP already permits it.

   z-index 250 puts it over the header (200) but not over a <dialog>, which
   the browser promotes to the top layer regardless of z-index. The consent
   dialog therefore stays clean.
   -------------------------------------------------------------------------- */

body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 250;
  pointer-events: none;
  opacity: 0.028;
  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.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --------------------------------------------------------------------------
   26.4 Pointer depth — tilt and spotlight

   Both are driven by custom properties the script writes on the element.
   Writing through the CSSOM rather than a `style` attribute in the markup is
   not a style choice: `style-src 'self'` blocks the attribute, and
   `tests/test_website.py` fails the build if one appears in the output.

   Gated on `(hover: hover) and (pointer: fine)`. On a touch screen there is
   no cursor to follow, and applying the transform anyway leaves the card
   frozen at whatever angle the last tap implied.
   -------------------------------------------------------------------------- */

@media (hover: hover) and (pointer: fine) {
  .tilt {
    transform: perspective(1100px)
      rotateX(calc(var(--tilt-y, 0) * 1deg))
      rotateY(calc(var(--tilt-x, 0) * 1deg))
      translate3d(0, 0, 0);
    transform-style: preserve-3d;
    transition: transform 220ms var(--ease);
  }

  /* The spotlight is a border, not a wash: the highlight rides the 1px edge
     under the cursor. A filled radial gradient over the card body was the
     first attempt and it fought every piece of text on top of it. */
  .spot {
    position: relative;
  }

  .spot::before {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: radial-gradient(
      16rem circle at var(--mx, 50%) var(--my, 0%),
      color-mix(in oklab, var(--primary) 55%, transparent),
      transparent 70%
    );
    -webkit-mask:
      linear-gradient(oklch(0 0 0)) content-box,
      linear-gradient(oklch(0 0 0));
    mask:
      linear-gradient(oklch(0 0 0)) content-box,
      linear-gradient(oklch(0 0 0));
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    opacity: 0;
    transition: opacity 260ms var(--ease);
    pointer-events: none;
  }

  .spot:hover::before,
  .spot:focus-within::before {
    opacity: 1;
  }
}

/* --------------------------------------------------------------------------
   26.5 Ambient motion off

   Not "slower" — off. None of it carries information, so the reduced-motion
   reader loses nothing but the movement.
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .band__img,
  .band__figure,
  .band__caption {
    animation: none !important;
    transform: none !important;
    clip-path: none !important;
    opacity: 1 !important;
  }

  .tilt {
    transform: none !important;
    transition: none !important;
  }

  .spot::before {
    display: none !important;
  }
}

/* ==========================================================================
   The arithmetic, and the account beside it
   --------------------------------------------------------------------------
   These replaced a full-bleed photograph of a window with a roller shutter.
   The picture was on-metaphor and still wrong: a stock image on a page whose
   whole argument is "we measured this ourselves" costs more trust than any
   image returns. Type carries it instead.
   ========================================================================== */

.odds__punch {
  /* Left, not centred: the section head above it is left-aligned, and a
     centred line under a left-aligned heading reads as a different component
     that wandered in. */
  max-width: 68ch;
  margin: 1.7rem 0 0;
  color: var(--muted);
  font-size: 1.02rem;
  line-height: 1.65;
}
.odds__punch strong { color: var(--text); }

/* One person's account, and the label is load-bearing: it is the operator
   describing his own use, not a customer review. `TESTIMONIALS` renders those
   and is gated on `verified` for exactly the reason this block is labelled.

   Built from the same tokens as `.card`, so it reads as part of the page
   rather than as a quote somebody pasted in. The accent tint is what separates
   it from the four myth cards immediately above. */
.story {
  margin: 0;
  padding: var(--sp-8) var(--sp-8) var(--sp-6);
  border: 1px solid var(--primary-soft-border);
  border-radius: var(--radius-xl);
  background: var(--primary-soft);
  box-shadow: var(--shadow-xs);
}
.story__who {
  margin: 0 0 var(--sp-5);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--primary-text);
}
.story__body {
  margin: 0 0 var(--sp-5);
  padding: 0;
  border: 0;
  font-size: 1.14rem;
  line-height: 1.62;
  color: var(--text);
}
.story__body strong { color: var(--primary-text); font-weight: 650; }
.story__after {
  margin: 0;
  padding-top: var(--sp-5);
  border-top: 1px solid var(--primary-soft-border);
  font-size: 0.93rem;
  line-height: 1.6;
  color: var(--text-muted);
}
@media (max-width: 40rem) {
  .story { padding: var(--sp-6) var(--sp-5) var(--sp-5); }
  .story__body { font-size: 1.02rem; }
}

/* ==========================================================================
   Price tiles
   --------------------------------------------------------------------------
   The euro figure is the headline because it is the one people think in; the
   star count sits directly under it because it is the one they are charged.
   Neither is decoration, so both are set as type rather than as a badge.
   ========================================================================== */

.plan__stars {
  margin-top: calc(var(--sp-1) * -1);
  font-family: var(--font-mono);
  font-size: 0.86rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.01em;
  color: var(--text-muted);
}
.plan--featured .plan__stars { color: var(--primary-text); }

.price-note {
  max-width: 60ch;
  margin-inline: auto;
  line-height: 1.6;
}
.price-note strong { color: var(--text); font-weight: 600; }
