/* Not For Free — poster pass (NFF-3b). Mobile-first.
   Reference: Barbara Kruger + Swiss grid — protest content, surgical execution. */

:root {
  --color-bg: #111111;
  --color-fg: #f4f4f2;
  --color-accent: #E63329;
  --color-border: #333333;
  --font-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-display: "Unbounded", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.04 0'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23g)'/%3E%3C/svg%3E");

  /* Modular scale, base 8px */
  --space-1: 0.5rem;   /* 8px */
  --space-2: 1rem;     /* 16px */
  --space-3: 1.5rem;   /* 24px */
  --space-4: 2rem;     /* 32px */
  --space-6: 3rem;     /* 48px */
  --space-8: 4rem;     /* 64px */
  --space-12: 6rem;    /* 96px  - section gap, mobile */
  --space-20: 10rem;   /* 160px - section gap, desktop */

  --gutter: var(--space-3);
}

@media (min-width: 768px) {
  :root {
    --gutter: var(--space-6);
  }
}

* {
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
}

html {
  background: var(--color-bg);
}

body {
  position: relative; /* the ambient layer spans the full page via inset: 0 */
  color: var(--color-fg);
  font-family: var(--font-base);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* Seamless wall: one fixed, viewport-covering texture layer behind the
   entire page (footer included). A fixed-position pseudo-element rather
   than `background-attachment: fixed` on body — the property is buggy on
   iOS Safari, and `cover` sized directly against a body as tall as the
   whole page would zoom the photo into an unrecognizable blur on scroll.
   Layer order (top to bottom): dark wash for AAA contrast, grain for
   liveliness, photo. Perfectly static regardless of scroll position —
   the wall does not move, only what's painted on it does. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(0, 0, 0, 0.68), rgba(0, 0, 0, 0.68)),
    var(--grain),
    url("public/wall.webp");
  background-repeat: no-repeat, repeat, no-repeat;
  background-position: center, center, center;
  background-size: cover, 240px 240px, cover;
}

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

a {
  color: inherit;
}

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

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

/* Vertical rhythm: one gap value between sections, applied as margin-top
   only, so consecutive sections never double up. Hero excluded (page start). */
.section-gap {
  margin-top: var(--space-12);
}

@media (min-width: 768px) {
  .section-gap {
    margin-top: var(--space-20);
  }
}

#hero, #manifesto, #slogans, #how-it-works, #join, #spray, .slogan-tail, .site-footer {
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* Hero: one statement, nothing else on the first viewport. svh = the
   small viewport (iOS Safari toolbar visible), so the scroll cue at the
   hero's bottom can never hide behind the toolbar; vh line is the
   fallback for browsers without viewport-unit variants. */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
}

/* Old paint traces running down from the page's top edge (NFF-4b):
   three uneven 1-3px streaks, static wall texture. Right-anchored
   750px strip: the two near the edge (x>~560) show on phones; the
   inward third only appears once the viewport is wide enough. */
.hero::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: auto;
  width: 750px;
  height: 130px;
  background: #ffffff;
  opacity: 0.15;
  clip-path: path("M656 0 H659 L658 44 L657.6 118 H656.9 L656.4 44 Z M588 0 H589.5 L589.2 20 L589 46 H588.4 L588.2 20 Z M210 0 H212.5 L211.8 32 L211.4 88 H210.6 L210.2 32 Z");
  pointer-events: none;
}

.hero-heading {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: clamp(4rem, 13vw, 13.75rem); /* 64px -> 220px */
  line-height: 0.95;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  margin: 0;
  overflow-wrap: normal;
  word-break: keep-all;
  hyphens: none;
}

/* Cyrillic runs wider than Latin at the same size class — "БЕЗКОШТОВНО"
   needs both a smaller scale and a forced single line to stay clear of
   the viewport edge; EN keeps its default (space-)wrapping behavior. */
html[lang="uk"] .hero-heading {
  font-size: clamp(1.5rem, 7.2vw, 9rem); /* 24px -> 144px */
  white-space: nowrap;
}

.hero-teaser {
  font-size: 1.5rem; /* 24px */
  line-height: 1.3;
  margin: var(--space-6) 0 0;
}

/* Scroll cue: sprayed arrow, bottom center of the hero. Instant state
   switch on hover/focus — a nudge, not a bounce. Default ink matches
   wall slogans; focus matches the hero heading (--color-fg). */
.scroll-arrow {
  position: absolute;
  bottom: var(--space-6);
  left: 50%;
  transform: translateX(-50%);
  padding: var(--space-1);
  background: transparent;
  border: none;
  color: rgba(255, 255, 255, 0.55);
  cursor: pointer;
}

.scroll-arrow svg {
  height: 48px;
  width: auto;
}

.scroll-arrow:focus-visible {
  outline: none;
  color: var(--color-fg);
  transform: translateX(-50%) translateY(2px);
}

/* Hover only on real pointers — iOS keeps :hover after tap, which would
   leave the arrow in the focused colour after a swipe. */
@media (hover: hover) {
  .scroll-arrow:hover {
    outline: none;
    color: var(--color-fg);
    transform: translateX(-50%) translateY(2px);
  }
}

/* Manifesto — starts on scroll, below the hero's first viewport. Padding
   (not the shared section-gap margin) so the text column's own top offset
   is independent of whatever sits above it. */
.manifesto {
  position: relative; /* containing block for the side slogan zone */
  padding-top: var(--space-12); /* 96px mobile */
}

@media (min-width: 768px) {
  .manifesto {
    padding-top: var(--space-20); /* 160px desktop */
  }
}

.manifesto-inner {
  position: relative;
  max-width: 72ch;
  margin-inline: auto;
}

/* Depth (NFF-4b): edgeless dark pool behind the manifesto column, lifting
   the text off the wall. Negative-z pseudo: behind the text, in front of
   the wall texture and the ambient murmur. closest-side radial reaches
   exactly 0 at the box edge — no visible seam. */
.manifesto-inner::before {
  content: "";
  position: absolute;
  inset: -3rem -1.5rem;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(closest-side, rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0));
}

@media (min-width: 768px) {
  .manifesto-inner::before {
    inset: -4rem -3rem;
  }
}

.manifesto-line {
  font-size: 1.1875rem; /* 19px mobile */
  font-weight: 500;
  line-height: 1.6;
  color: #ffffff;
  margin: 0 0 var(--space-4);
}

@media (min-width: 768px) {
  .manifesto-line {
    font-size: 1.375rem; /* 22px desktop */
  }
}

/* inline-block so clip-path applies to one box even when the line wraps */
.manifesto-line-part {
  display: inline-block;
}

/* Kruger plaque: the closing manifesto lines, wherever they appear
   (hero teaser and the end of the full manifesto). Same red as buttons
   and the future user-sprayed slogan — it appears nowhere else. */
.plaque {
  background: var(--color-accent);
  color: #ffffff;
  font-family: var(--font-display);
  font-weight: 700;
  padding: 0.1em 0.4em;
}

/* Stacked plaque lines: their boxes abut on a fractional pixel and can
   show a hairline of wall between them (DPR rounding). Pull the lower
   line 2px up — same-color overlap, the patch reads as one paint. */
.plaque ~ .plaque {
  margin-top: -2px;
}

/* Spray-plaque moment (manifesto closing lines only): the red swatch is
   sprayed in behind the line first, then the white text lands on it.
   One-way — the observer never removes .revealed. The span itself stays
   opaque; the reveal lives entirely in its pseudo-element and text color. */
.plaque--spray {
  position: relative;
  isolation: isolate;
  background: none;
  color: transparent;
}

.plaque--spray::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--color-accent);
  opacity: 0;
}

.to-reveal.plaque--spray {
  opacity: 1;
  transition: none;
}

/* Mobile: pre-drawn swatch pops in whole (one opacity switch, no
   clip-path animation — cheap), text follows 100ms later. */
.to-reveal.plaque--spray.revealed {
  animation: plaque-text-in 80ms linear 100ms both;
}

.to-reveal.plaque--spray.revealed::before {
  opacity: 1;
}

/* Desktop: rough ~45° diagonal sweep draws the swatch in, quantized
   steps for the sprayed-not-tweened feel; text lands right after. */
@media (min-width: 768px) {
  .to-reveal.plaque--spray.revealed {
    animation: plaque-text-in 90ms linear 320ms both;
  }

  .to-reveal.plaque--spray.revealed::before {
    animation: swatch-in 320ms steps(6, jump-end) both;
  }
}

@keyframes swatch-in {
  from {
    opacity: 1;
    clip-path: polygon(0 0, 5% 0, -13% 100%, 0 100%);
  }
  to {
    opacity: 1;
    clip-path: polygon(0 0, 118% 0, 100% 100%, 0 100%);
  }
}

@keyframes plaque-text-in {
  from { color: transparent; }
  to { color: #ffffff; }
}

/* Manifesto reveal: sharp and one-way, like a spray stencil.
   Mobile default — plain opacity switch, no filters/masks on the reveal. */
.to-reveal {
  opacity: 0;
  transition: opacity 120ms linear;
}

.to-reveal.revealed {
  opacity: 1;
}

/* Desktop: stencil sweep — clip-path quantized into 4 jumps, 140ms total */
@media (min-width: 768px) {
  .to-reveal {
    transition: none;
  }

  .to-reveal.revealed {
    animation: spray-in 140ms steps(4, jump-end) both;
  }
}

@keyframes spray-in {
  from {
    opacity: 1;
    clip-path: inset(0 100% 0 0);
  }
  to {
    opacity: 1;
    clip-path: inset(0 0 0 0);
  }
}

@media (prefers-reduced-motion: reduce) {
  .to-reveal,
  .to-reveal.revealed {
    opacity: 1;
    transition: none;
    animation: none;
  }

  /* Static plaque: `animation: none` above kills plaque-text-in, so the
     text color and swatch must be set directly. */
  .plaque--spray {
    color: #ffffff;
  }

  .plaque--spray::before {
    opacity: 1;
    animation: none;
  }
}

/* Section heading: shared by join + spray */
.section-heading {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.5rem, 7vw, 2.25rem); /* 24px -> 36px, fits one line */
  line-height: 1.05;
  text-transform: uppercase;
  letter-spacing: -0.01em;
  white-space: nowrap;
  margin: 0 0 var(--space-4);
}

/* How it works (NFF-4c): same design language as the rest of the page —
   section heading, manifesto-style body copy, no new components. Sits
   between the manifesto's ambient gap and the join form. */
.how-inner {
  max-width: 60rem; /* between the form column (40rem) and manifesto (72ch) */
  margin-inline: auto;
}

/* Same family/weight/case/tracking as .section-heading, just larger —
   this section reads as the page's second headline after the hero. At
   this size "ЯК ЦЕ ПРАЦЮЄ" no longer fits one line on narrow screens, so
   (unlike the single-line join/spray headings) this one is allowed to wrap. */
.how-heading {
  font-size: clamp(2.5rem, 8vw, 3rem); /* 40px -> 48px */
  white-space: normal;
  margin-bottom: var(--space-3);
}

.how-chain {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: 0.6em;
  row-gap: 0.5em;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.25rem, 2.6vw, 1.5rem); /* 20px -> 24px */
  color: #ffffff;
  margin: 0 0 var(--space-4);
}

.how-step {
  display: inline-flex;
  align-items: baseline;
  gap: 0.35em;
  white-space: nowrap;
}

/* Mobile: vertical stack, one step per line, arrow leading each next step
   (the first step carries no arrow — nothing precedes it). */
@media (max-width: 767px) {
  .how-chain {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* Honest-status note: a note sprayed on the wall, not a plaque — red
   accent edge + a dark backdrop, no filled red block. */
.how-note {
  border-left: 3px solid var(--color-accent);
  background: rgba(0, 0, 0, 0.35);
  padding: var(--space-3) var(--space-4);
}

/* Wall slogans (NFF-4): decorative graffiti layer, rendered by JS from
   content/slogans.json into three zones — beside the manifesto column
   (slots a-d), the gap before the join form (e/f), the tail after
   "Spray your own" (g/h). Pure decoration: aria-hidden zones, no
   pointer events, no selection. Slot rotations stay within -6..+4deg. */

.wall-slogan {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.25rem, 2.2vw, 2.125rem); /* 20px -> 34px */
  line-height: 1.2;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.55); /* NFF-4b: recedes into the wall */
  margin: 0;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
  overflow-wrap: break-word; /* last-resort guard: never escape the slot */
}

/* The author's own sprayed slogan — the only red text on the wall
   (shares the accent with plaques and buttons, nothing else). */
.wall-slogan--user {
  color: var(--color-accent);
}

/* Mobile (<768px): 2-3 slogans, full-width, in normal flow — slot a after
   the manifesto column, the gap slot before the join form, the tail slot
   after the spray form — plus the user's own if sprayed. Side slots b-d
   stay hidden: no absolute layout to reflow, no filter. */
.wall-slogan {
  display: none;
}

.wall-slogan--a,
.wall-slogan--e,
.wall-slogan--f,
.wall-slogan--g,
.wall-slogan--h,
.wall-slogan--user {
  display: block;
}

@media (max-width: 767px) {
  .wall-slogan--a {
    margin-top: var(--space-8);
  }

  /* tail zone can stack the session slogan and the user's own */
  .slogan-tail .wall-slogan + .wall-slogan {
    margin-top: var(--space-6);
  }
}

/* Tablet/desktop (>=768px): gap + tail slogans take absolute slots inside
   their zone wrappers. Rough sprayed edge via the same displacement filter
   as the hero arrow (defined inline in index.html) — desktop only, and a
   silent no-op where SVG filter references are unsupported. */
@media (min-width: 768px) {
  .wall-slogan {
    display: block;
    position: absolute;
    filter: url(#arrowSpray);
  }

  /* Back to an absolute overlay (in flow only on mobile). */
  .slogan-zone--sides {
    position: absolute;
    inset: 0;
  }

  .slogans {
    position: relative;
    min-height: 16rem;
  }

  .slogan-tail {
    position: relative;
    min-height: 28rem;
  }

  .wall-slogan--e {
    left: calc(var(--gutter) + 2%);
    top: 2rem;
    max-width: 34rem;
    transform: rotate(-2.5deg);
  }

  .wall-slogan--f {
    right: calc(var(--gutter) + 4%);
    top: 3rem;
    max-width: 34rem;
    transform: rotate(1.5deg);
  }

  .wall-slogan--g {
    left: calc(var(--gutter) + 3%);
    top: 1rem;
    max-width: 30rem;
    transform: rotate(-3.5deg);
  }

  .wall-slogan--h {
    right: calc(var(--gutter) + 2%);
    top: 16rem;
    max-width: 30rem;
    transform: rotate(2deg);
  }

  /* Side slots stay hidden until the wall is wide enough for them. */
  .wall-slogan--a,
  .wall-slogan--b,
  .wall-slogan--c,
  .wall-slogan--d {
    display: none;
    margin: 0;
  }
}

/* Wide wall (>=1200px): side slots beside the manifesto column light up.
   The column is ~730px wide (72ch), so each side zone is bounded by
   calc(50% + 25rem) — geometry guarantees no overlap with the text even
   after rotation. Smaller type than the gap slogans: these read as tags
   sprayed further away, and long Cyrillic words must fit the narrow zone. */
@media (min-width: 1200px) {
  .wall-slogan--a,
  .wall-slogan--b,
  .wall-slogan--c,
  .wall-slogan--d {
    display: block;
    font-size: clamp(1rem, 1.35vw, 1.5rem); /* 16px -> 24px */
  }

  .wall-slogan--a {
    left: var(--gutter);
    right: calc(50% + 25rem);
    top: 10%;
    transform: rotate(-6deg);
  }

  .wall-slogan--b {
    left: calc(50% + 25rem);
    right: var(--gutter);
    top: 32%;
    transform: rotate(2.5deg);
  }

  .wall-slogan--c {
    left: var(--gutter);
    right: calc(50% + 25rem);
    top: 60%;
    transform: rotate(3.5deg);
  }

  .wall-slogan--d {
    left: calc(50% + 25rem);
    right: var(--gutter);
    top: 80%;
    transform: rotate(-4deg);
  }
}

/* Spray-write (NFF-4b): hero slogans draw themselves on — letter by
   letter left-to-right (per word on mobile: fewer nodes, steady fps).
   The parent is a visible shell; the units carry the hidden state and
   their own delay (--d) and entry rotation (--r), set inline by JS.
   Manifesto reveal and ambient layer are untouched. */
.wall-slogan .ws-word {
  display: inline-block;
  white-space: nowrap;
}

.wall-slogan .ws-unit {
  display: inline-block;
  opacity: 0;
  transform-origin: 60% 80%;
}

.wall-slogan.to-reveal {
  opacity: 1;
  transition: none;
}

.wall-slogan.to-reveal.revealed {
  animation: none; /* letters animate, not the block (kills spray-in) */
}

.wall-slogan.revealed .ws-unit {
  animation: char-in 120ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
  animation-delay: var(--d, 0ms);
}

@keyframes char-in {
  from {
    opacity: 0;
    transform: scale(1.15) rotate(var(--r, 0deg));
  }
  to {
    /* settles at the letter's own ink level (--j: 0.85-1.0 jitter) */
    opacity: var(--j, 1);
    transform: none;
  }
}

/* Spray-write, reduced motion: letters just are there (jitter kept).
   Placed after the rules above so file order settles the tie. */
@media (prefers-reduced-motion: reduce) {
  .wall-slogan .ws-unit,
  .wall-slogan.revealed .ws-unit {
    opacity: var(--j, 1);
    animation: none;
  }
}

/* Paint drips (NFF-4b): finale plaque only. Tapering streaks as
   absolutely-positioned pseudo-elements clipped by path() — zero layout
   impact, no content, invisible to the a11y tree. Grows once (scaleY,
   ease-out, then still forever). Ambient / hero / user slogans never drip. */

/* two red drips: 4px x 50px and 3px x 28px, both tapering */
.plaque--drip::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 0;
  width: 100px;
  height: 52px;
  background: var(--color-accent);
  clip-path: path("M22 0 H26 L25 16 L24.6 50 H23.4 L23 16 Z M62 0 H65 L64.3 12 L64 28 H62.9 L62.6 12 Z");
  transform: scaleY(0);
  transform-origin: top;
  pointer-events: none;
}

.to-reveal.plaque--drip.revealed::after {
  animation: drip-grow 1.8s ease-out 250ms both;
}

/* Desktop plaque: swatch (320ms) + text land first, then the run */
@media (min-width: 768px) {
  .to-reveal.plaque--drip.revealed::after {
    animation-delay: 500ms;
  }
}

@keyframes drip-grow {
  from { transform: scaleY(0); }
  to { transform: scaleY(1); }
}

@media (prefers-reduced-motion: reduce) {
  .plaque--drip::after,
  .to-reveal.plaque--drip.revealed::after {
    transform: none;
    animation: none;
  }
}

/* Double-pass ghost (NFF-4b): 1-2 slogans per session carry a second,
   faint sweep of the can — the full text repeated from data-text,
   offset a hair, behind the letters. Fades in after the write. */
/* mobile slogans are static blocks — anchor the ghost layer */
@media (max-width: 767px) {
  .wall-slogan--ghost {
    position: relative;
  }
}

.wall-slogan--ghost::before {
  content: attr(data-text);
  position: absolute;
  inset: 0;
  transform: translate(1.5px, 1px);
  opacity: 0;
  transition: opacity 300ms linear 600ms;
  z-index: -1;
  pointer-events: none;
}

.wall-slogan--ghost.revealed::before {
  opacity: 0.18;
}

@media (prefers-reduced-motion: reduce) {
  .wall-slogan--ghost::before,
  .wall-slogan--ghost.revealed::before {
    opacity: 0.18;
    transition: none;
  }
}

/* A/B hook (NFF-4b): variant B (?v=b or #v-b) softens the hero slogans
   with a 1px blur — the user's own red slogan stays sharp. */
html.variant-b .wall-slogan:not(.wall-slogan--user) {
  filter: blur(1px);
}

@media (min-width: 768px) {
  html.variant-b .wall-slogan:not(.wall-slogan--user) {
    filter: url(#arrowSpray) blur(1px);
  }
}

/* Ambient murmur (NFF-4b): 20-30 tiny slogans across the full page
   height — knowledge under the plaster. One absolutely-positioned
   container filled once by JS: zero layout cost, no animation, no
   observers. overflow: hidden so a rotated item can never widen the page. */
.ambient-layer {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}

.ambient-slogan {
  position: absolute;
  font-family: var(--font-display);
  font-weight: 700;
  text-transform: uppercase;
  line-height: 1.3;
  color: #ffffff;
  margin: 0;
}

/* Join / spray forms: one shared column for both, so every button in it
   shares identical width. */
.form-column {
  max-width: 40rem; /* 640px */
  margin-inline: auto;
}

.join form,
.spray form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-4);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.field-label {
  font-size: 1rem; /* 16px */
}

/* "Like writing on the wall": transparent, flush, only a hairline to
   write against. */
.field-input {
  display: block;
  width: 100%;
  background: transparent;
  border: none;
  border-bottom: 2px solid var(--color-border);
  border-radius: 0;
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  color: var(--color-fg);
  font-size: 1.125rem; /* 18px */
  padding: var(--space-2) 0;
}

/* Border width stays constant (color-only change) so focus/validation can
   never shift layout — a submit that moves focus to an invalid field must
   not move the page. */
.field-input:focus {
  outline: none;
  border-bottom-color: var(--color-fg);
}

.field-input.has-error {
  border-bottom-color: var(--color-accent);
}

/* Error line: height reserved from the start (empty or not) so validation
   never shifts anything below it — fixed, not min-height, and clipped so
   font-metric rounding can never add a stray pixel. */
.field-error {
  height: 1.1rem;
  overflow: hidden;
  font-size: 0.875rem; /* 14px */
  line-height: 1.1rem;
  color: var(--color-accent);
  margin: 0;
}

/* Kill webkit autofill styling: the yellow/blue fill would break the
   wall illusion. Box-shadow trick keeps the background transparent, the
   endless transition stops webkit repainting it, size stays intact. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px transparent inset;
  -webkit-text-fill-color: #ffffff;
  caret-color: #ffffff;
  transition: background-color 999999s;
}

/* Tile-select: selectable tiles instead of native radios/selects.
   Shared by role and consent — every option is a full-width tile. */
.tile-group {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.tile-group > .field-label {
  margin-bottom: 0;
}

.tile {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2);
  border: 1px solid rgba(255, 255, 255, 0.25);
  font-size: 1rem; /* 16px */
  cursor: pointer;
}

.tile-dot {
  flex: none;
  width: var(--space-2);
  height: var(--space-2);
  margin-top: 0.2em;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 50%;
}

/* Selected: border stays 1px (no layout shift) — the inset shadow adds
   the second red pixel. */
.tile:has(input:checked) {
  border-color: var(--color-accent);
  box-shadow: inset 0 0 0 1px var(--color-accent);
}

.tile:has(input:checked) .tile-dot {
  background: var(--color-accent);
  border-color: var(--color-accent);
}

.tile-group.has-error .tile {
  border-color: var(--color-accent);
}

/* Button: one component for every call to action on the page. */
.btn {
  display: block;
  width: 100%;
  height: var(--space-8); /* 64px */
  border: none;
  outline: none;
  background: var(--color-accent);
  color: #ffffff;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.125rem; /* 18px */
  text-transform: uppercase;
  cursor: pointer;
}

.btn:hover,
.btn:focus-visible {
  background: #ffffff;
  color: var(--color-accent);
}

.spray-note {
  font-size: 1rem; /* 16px */
  opacity: 0.7;
  margin-top: var(--space-2);
}

/* Footer: one baseline-aligned line, no borders — spacing does the work.
   Bottom padding clears the iOS Safari toolbar / home indicator. */
.site-footer {
  padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom, 0px)); /* 48px+ */
}

.footer-top {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  justify-content: space-between;
  align-items: baseline;
}

.footer-nav {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
}

.footer-nav a {
  text-decoration: none;
  text-transform: uppercase;
  font-size: 0.875rem; /* 14px */
  letter-spacing: 0.05em;
}

.footer-nav a:hover {
  color: var(--color-accent);
}

.lang-switch {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
}

.lang-switch button {
  background: transparent;
  border: none;
  color: var(--color-fg);
  font-size: 0.875rem; /* 14px */
  letter-spacing: 0.05em;
  padding: 0.2em 0.5em;
  cursor: pointer;
}

.lang-switch button:hover {
  color: var(--color-accent);
}

.lang-switch button.active {
  background: var(--color-accent);
  color: #ffffff;
}

.lang-switch button.active:hover {
  color: #ffffff;
}

.footer-signature {
  font-size: 0.8125rem; /* 13px */
  opacity: 0.6;
  margin: var(--space-2) 0 0;
}
