/* ТЗ §1.3 palette + §9 mobile-first breakpoints. No framework, no build step. */
:root {
  --ink: #111111;
  /* ТЗ §1.3: "#3F9C35 or the exact one from the logo" — the placeholder hex was always
     provisional pending real artwork. This is the logo's actual fill colour, sampled as
     the single most common pixel across the mark and wordmark (not eyeballed, and not
     an edge/anti-aliasing blend) once the client supplied it. Every hardcoded
     `rgba(113, 179, 28, …)` a plain `var()` can't reach — glows, tints, hover shadows —
     was updated to match by hand; grep for `rgba(113, 179, 28` before adding a new one. */
  --green: #71b31c;
  --paper: #ffffff;
  --muted: #6b7280;
  --line: #e5e7eb;
  --radius: 12px;
  --container: 1320px;
  --tabbar-h: 64px;
  /* The header's own height, for pages that need to fill the rest of the screen. It is
     `.header`'s padding plus its content, so it changes with the padding at ≥1024px and
     the override lives next to that rule — keep the two together or this silently drifts
     and the page it feeds overflows by the difference. */
  --header-h: 68px;
  /* ТЗ §1.3 allows the system stack or Inter/Manrope; §11.2 rules out the Google Fonts
     CDN. The system stack costs no bytes and no GDPR question — swap in a locally
     hosted family here when the client's branding lands. */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }
body {
  margin: 0;
  color: var(--ink);
  background: var(--paper);
  line-height: 1.55;
  overflow-x: hidden; /* ТЗ §9: no horizontal scroll at any width */
  padding-bottom: var(--tabbar-h);
}
img { max-width: 100%; height: auto; }
a { color: inherit; }
h1, h2 { line-height: 1.2; margin: 0 0 .5em; }

/* --- header ------------------------------------------------------------ */
.header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: var(--ink);
  color: var(--paper);
}
.logo {
  display: inline-flex;
  align-items: center;
  margin-right: auto;
}
/* Sized off the header's own height rather than a fixed guess, so the two stay in step
   if `--header-h` ever changes: 32px leaves roughly the same 18px of clear space above
   and below it that the old text logo's line-height gave it at 68px. */
.logo img { height: 32px; width: auto; display: block; }
.logo--footer { margin: 0 0 8px; }
/* The footer has room the header doesn't fight the phone icon and language switcher
   for, so the mark reads a size larger there — same ratio judgement a paper letterhead
   makes between its header and its foot. */
.logo--footer img { height: 40px; }

.nav-desktop { display: none; }

/* Four languages as a <details> disclosure — one 44px control instead of four. */
.lang { position: relative; }
/* The summary carries no layout of its own. Giving a <summary> `display: grid` or
   `flex` stops WebKit toggling the disclosure at all — the element stops being treated
   as the details' summary box, so tapping it does nothing while scripted `.click()`
   still works, which is exactly how it hides during testing. ТЗ §12 makes iOS Safari
   mandatory, so the 44px box lives on a span inside instead. */
.lang summary {
  list-style: none;
  cursor: pointer;
}
.lang summary::-webkit-details-marker { display: none; }
.lang__toggle {
  display: grid;
  place-items: center;
  min-width: 44px;
  min-height: 44px;
  border: 1px solid #333;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 600;
}
.lang details[open] .lang__toggle { background: var(--green); border-color: var(--green); }
.lang__menu {
  position: absolute;
  right: 0;
  z-index: 20;
  display: grid;
  min-width: 160px;
  padding: 6px;
  border-radius: var(--radius);
  background: var(--ink);
  border: 1px solid #333;
  box-shadow: 0 8px 24px rgba(0, 0, 0, .4);
}
.lang__menu button {
  min-height: 44px;
  padding: 0 12px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--paper);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.lang__menu button:hover { background: #2a2a2a; }
.lang__menu button.is-active { color: var(--green); font-weight: 600; }

.header__phone {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  text-decoration: none;
}
.header__phone svg { width: 22px; height: 22px; fill: currentColor; }

/* --- content ----------------------------------------------------------- */
main { max-width: var(--container); margin: 0 auto; }
.section { padding: 32px 16px; }
.muted { color: var(--muted); }

/* Keyboard users reach the page without walking the whole navigation (ТЗ §12). */
.skip-link {
  position: absolute;
  left: -9999px;
  z-index: 40;
  padding: 12px 16px;
  background: var(--green);
  color: var(--paper);
  border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus { left: 0; }

/* Heading of a block, with the optional "all → " link on the right (ТЗ §4.1). */
.section__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 16px;
}
.section__head h2 { margin: 0; }
.section__more { color: var(--green); font-weight: 600; text-decoration: none; white-space: nowrap; }

.page-head { padding: 24px 16px 8px; }
.page-head h1 { font-size: clamp(1.5rem, 5vw, 2.25rem); margin-bottom: 8px; }
.page-head__lead { color: var(--muted); max-width: 60ch; margin: 0; }
.crumbs { display: flex; flex-wrap: wrap; gap: 8px; font-size: 14px; color: var(--muted); margin-bottom: 8px; }
.crumbs a { color: var(--muted); }

.contact-list { display: grid; gap: 4px 16px; margin: 0 0 24px; }
.contact-list dt { color: var(--muted); font-size: 14px; }
.contact-list dd { margin: 0 0 12px; font-weight: 600; }

/* The photo sits behind the copy at z-index -2 and the scrim at -1. `isolation`
   keeps both inside the hero's own stacking context instead of the page's. */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  align-content: end;
  min-height: 60vh;
  /* `svh`, not `dvh`. `dvh` tracks the mobile URL bar, so it changes on every scroll
     that collapses or restores it — the hero would resize mid-scroll and reflow the
     whole page under it. `svh` is the same viewport measurement without the movement. */
  min-height: 60svh;
  /* Full-bleed out of `main`'s 1320px column — the photo is the top edge of the page,
     so it runs to the screen edges. The copy stays on the container's grid via
     `.hero__body`, which carries the horizontal padding the hero used to have. */
  margin-inline: calc(50% - 50vw);
  padding: 64px 0 40px;
  background: var(--ink);
  color: var(--paper);
}
.hero__body {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: 16px;
}
/* Taller than the box it sits in, so the parallax below has somewhere to travel and
   never pulls an edge into frame: the overscan here (15%) has to stay larger than the
   furthest the keyframes move it (10% of a box that is 1.3x the hero = 13%).
   `.hero` clips the rest. */
.hero__media { position: absolute; inset: -15% 0; z-index: -2; }
.hero__media img { width: 100%; height: 100%; object-fit: cover; }
/* Two layers, not one: the vertical fade seats the copy at the bottom, the horizontal
   one keeps the right of the frame open so the photo still reads as a workshop. Many
   close stops rather than three far apart — a long ramp over near-black bands badly. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  --scrim-vertical: linear-gradient(180deg,
      rgba(17, 17, 17, .55) 0%,
      rgba(17, 17, 17, .34) 18%,
      rgba(17, 17, 17, .28) 38%,
      rgba(17, 17, 17, .50) 62%,
      rgba(17, 17, 17, .76) 82%,
      rgba(17, 17, 17, .90) 100%);
  background: var(--scrim-vertical);
}

/* The horizontal fade is a composition device for a wide frame, where the copy sits in
   the left third. In a 375px column the text runs the full width, so it would only
   lighten the ends of its own lines. */
@media (min-width: 768px) {
  .hero::after {
    background:
      var(--scrim-vertical),
      linear-gradient(94deg,
        rgba(17, 17, 17, .82) 0%,
        rgba(17, 17, 17, .62) 26%,
        rgba(17, 17, 17, .34) 55%,
        rgba(17, 17, 17, .12) 78%,
        rgba(17, 17, 17, 0) 100%);
  }
}
.hero h1, .hero__title { font-size: clamp(1.75rem, 6vw, 3rem); max-width: 18ch; }
.hero p, .hero__lead { color: #d1d5db; max-width: 46ch; }
.hero__actions { display: flex; flex-wrap: wrap; gap: 12px; }
.hero__actions .btn { margin-top: 16px; }

.btn {
  display: inline-block;
  margin-top: 16px;
  padding: 14px 24px;
  min-height: 44px; /* ТЗ §9: touch targets ≥ 44px */
  /* Transparent rather than none: `.btn--ghost` and `.btn--outline` add a 1px border,
     and without a border here they would stand 2px taller than a plain button beside
     them. */
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--green);
  color: var(--paper);
  /* A `<button class="btn">` does not inherit the page font — the UA gives it 13px
     Arial, so the same class rendered a visibly smaller, lighter button than the `<a>`
     next to it, and in a flex row the shorter one was then stretched to match. Same
     reason `.lang__menu button` sets it. `font` resets weight, so it is restated. */
  font: inherit;
  font-weight: 600;
  text-align: center;
  text-decoration: none;
}

.btn--ghost {
  background: var(--paper);
  color: var(--ink);
  border-color: var(--line);
}
.btn--block { display: block; text-align: center; }
/* A CTA and the social buttons beside it, wherever there is no grid already spacing them.
   No `.btn { margin-top: 0 }` here on purpose — every item carries the same 16px, so they
   stay aligned and the row keeps its distance from whatever sits above it. */
.btn-row { display: flex; flex-wrap: wrap; gap: 12px; }

/* --- cards, tiles and chips (ТЗ §4.1–4.4) ------------------------------- */
.grid { display: grid; gap: 16px; }

.card {
  display: block;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
  overflow: hidden;
  text-decoration: none;
}
.card__media { position: relative; aspect-ratio: 4 / 3; background: var(--line); }
.card__media img { width: 100%; height: 100%; object-fit: cover; }
.card__price {
  position: absolute;
  left: 12px;
  bottom: 12px;
  padding: 6px 12px;
  border-radius: 999px;
  background: var(--green);
  color: var(--paper);
  font-weight: 700;
}
.card__body { padding: 12px 16px 16px; }
.card__title { margin: 0 0 4px; font-size: 1.05rem; font-weight: 700; }
.card__meta { margin: 0; color: var(--muted); font-size: 14px; }

.tile {
  display: grid;
  gap: 8px;
  place-items: center;
  padding: 20px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-align: center;
  text-decoration: none;
  font-weight: 600;
}
.tile img, .tile svg { width: 32px; height: 32px; color: var(--green); }

.chips { display: flex; gap: 8px; overflow-x: auto; padding: 4px 0 12px; }
.chip {
  flex: 0 0 auto;
  min-height: 40px;
  display: inline-flex;
  align-items: center;
  padding: 0 16px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}
.chip.is-active { background: var(--green); border-color: var(--green); color: var(--paper); }

.badge {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 999px;
  background: var(--line);
  font-size: 13px;
  font-weight: 600;
}
.badge--green { background: var(--green); color: var(--paper); }

/* --- forms (ТЗ §5.3, §9) ------------------------------------------------ */
/* `align-content: start` is load-bearing, not tidying. A `.field` is a grid, and two of
   them side by side in `.form__row` are stretched to the taller one — which, left at the
   default, stretches the *rows inside* the shorter one too. So "Best way to reach you"
   grew a 74px select and dropped 28px below the "Email" box next to it, purely because
   Email carries a hint line and it does not. Every misaligned pair on the form was this. */
.field { display: grid; gap: 6px; margin-bottom: 16px; align-content: start; }
.field label { font-weight: 600; font-size: 14px; }
/* Text-shaped controls only. A checkbox, a radio or a file input given the box below
   comes out as a 44px-tall bordered rectangle with a tiny native control adrift inside
   it — and `min-height` beats any `height` an override tries to set afterwards, so the
   exclusion has to happen here rather than being undone further down. */
.field input:not([type="checkbox"], [type="radio"], [type="file"]),
.field select,
.field textarea {
  min-height: 44px; /* ТЗ §9: touch targets */
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper);
  color: inherit;
  font: inherit;
  width: 100%;
}
.field textarea { min-height: 120px; resize: vertical; }
.field--error input, .field--error select, .field--error textarea { border-color: #b91c1c; }
.field__error { color: #b91c1c; font-size: 14px; }
.field__hint { color: var(--muted); font-size: 14px; }

.checklist { list-style: none; padding: 0; display: grid; gap: 10px; }
.checklist li::before { content: "✓"; color: var(--green); font-weight: 700; margin-right: 10px; }

/* --- Why choose us (ТЗ §4.1 block 5) ------------------------------------ */
/* Flex, not `grid-template-columns: repeat(auto-fit, …)`. auto-fit builds equal
   columns, so five cards came out 4+1 with the last one stranded beside three
   column-widths of nothing — the exact emptiness this block was redesigned to fix.
   Flex items grow to fill whatever row they land on, orphan included.

   `max-width` is what stops that generosity going too far: without it a lone card on
   the last row stretches edge to edge and reads as a different component. With it,
   short rows centre instead. No breakpoints of its own to keep in step. */
.advantages {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
}
.advantage {
  flex: 1 1 170px;
  /* Wide enough that a phone gets an edge-to-edge card, low enough that a lone one on
     the last row cannot stretch across a desktop. Five still fit one row well past
     1900px before the cap starts centring them. */
  max-width: 400px;
  display: grid;
  gap: 12px;
  place-items: center;
  align-content: start;
  padding: 28px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  text-align: center;
  font-weight: 600;
  transition: transform .2s ease-out, box-shadow .2s ease-out;
}
.advantage__icon {
  display: grid;
  place-items: center;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  /* Same green wash the thank-you page's tick already uses. */
  background: rgba(113, 179, 28, .12);
  color: var(--green);
}
/* The vendored SVGs carry no presentation attributes — the same declaration `.tile svg`
   needs, for the same reason: without it the paths render as black blobs. */
.advantage__icon svg {
  width: 26px;
  height: 26px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform .2s ease-out;
}
.advantage:hover { transform: translateY(-3px); box-shadow: 0 8px 20px rgba(0, 0, 0, .08); }
.advantage:hover .advantage__icon svg { transform: scale(1.12); }

.counters { display: grid; gap: 16px; text-align: center; list-style: none; margin: 0; padding: 0; }
.counters li {
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
  display: grid;
  gap: 4px;
  color: var(--muted);
}
/* Tabular figures: the count-up must not shuffle the layout as digits change. */
.counters strong { font-size: 2rem; color: var(--green); font-variant-numeric: tabular-nums; }

/* --- footer ------------------------------------------------------------ */
.footer { background: var(--ink); color: #d1d5db; padding: 32px 16px; }
.footer__grid { display: grid; gap: 20px; max-width: var(--container); margin: 0 auto; }
.footer p { margin: 0 0 4px; }
.footer__nav { display: grid; gap: 8px; align-content: start; }
.footer__nav a { padding: 4px 0; }
.footer__social { display: flex; gap: 16px; margin-top: 12px; }
.footer__legal { margin-top: 24px; font-size: 14px; color: var(--muted); }

/* --- mobile tab bar (ТЗ §3.1) — the only navigation below 1024px -------- */
.tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 30;
  height: var(--tabbar-h);
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  /* Translucent so content passing underneath reads as depth rather than as a seam.
     The solid fallback below matters: without backdrop-filter this would be a
     see-through bar with text running through it. */
  background: rgba(255, 255, 255, .82);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom); /* iPhone home indicator */
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .tabbar { background: var(--paper); }
}

.tabbar a {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 2px;
  text-align: center;
  text-decoration: none;
  color: var(--muted);
  -webkit-tap-highlight-color: transparent; /* the pill below is the feedback */
  transition: color .2s ease-out;
}

/* Five Finnish labels have to share 375px, and "Yhteystiedot" is twelve characters.
   Scaling with the viewport keeps them on one line at 360px without shrinking the
   label to nothing on a big phone. */
.tabbar__label {
  font-size: clamp(9.5px, 2.7vw, 11px);
  line-height: 1.1;
  letter-spacing: -.01em;
  transition: font-weight .2s ease-out;
}

/* The active indicator, and the thing that scales on press. */
.tabbar__icon {
  display: grid;
  place-items: center;
  width: 56px;
  max-width: 100%;
  height: 30px;
  border-radius: 999px;
  transition: background-color .25s ease-out, transform .25s cubic-bezier(.34, 1.4, .64, 1);
}

.tabbar svg {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  /* Lucide is drawn for stroke-width 2; the active tab gets the full weight and the
     rest sit slightly lighter, so selection reads even in a screenshot without colour. */
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke-width .2s ease-out;
}

.tabbar a.is-active { color: var(--green); }
.tabbar a.is-active .tabbar__icon { background: rgba(113, 179, 28, .14); }
.tabbar a.is-active .tabbar__label { font-weight: 600; }
.tabbar a.is-active svg { stroke-width: 2; }

/* Transform, not size or margin: the label must not move when the icon is pressed. */
.tabbar a:active .tabbar__icon { transform: scale(.88); }

/* --- ≥768px: tablets --------------------------------------------------- */
@media (min-width: 768px) {
  .counters { grid-template-columns: repeat(3, 1fr); }
  .grid { grid-template-columns: repeat(2, 1fr); }
  .contact-list { grid-template-columns: max-content 1fr; }
  .footer__grid { grid-template-columns: repeat(3, 1fr); }
  .checklist { grid-template-columns: repeat(2, 1fr); }
}

/* --- ≥1024px: desktop nav replaces burger + tab bar --------------------- */
@media (min-width: 1024px) {
  body { padding-bottom: 0; }
  .tabbar { display: none; }
  .nav-desktop { display: flex; gap: 20px; margin: 0 auto; }
  .nav-desktop a { text-decoration: none; padding: 8px 0; }
  .nav-desktop a:hover, .nav-desktop a.is-active { color: var(--green); }
  /* 8px more padding top and bottom, so `--header-h` moves with it. */
  .header { padding: 16px 32px; }
  :root { --header-h: 76px; }
  .logo img { height: 36px; }
  .section, .page-head, .hero__body { padding-inline: 32px; }
  .grid { grid-template-columns: repeat(3, 1fr); }
}

/* --- ≥1440px: ТЗ §9 asks for four catalogue columns on large screens ---- */
@media (min-width: 1440px) {
  .grid { grid-template-columns: repeat(4, 1fr); }
}

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

/* ======================================================================== */
/* Landing page (ТЗ §4.1) and the motion layer.                             */
/* Transform and opacity only — nothing here can trigger a reflow.          */
/* ======================================================================== */

/* --- car rail: scroll-snap below 768px, `.grid` columns above ----------- */
.rail { list-style: none; margin: 0; padding: 0; }

@media (max-width: 767px) {
  .rail {
    display: flex;
    gap: 12px;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    /* Bleed to the screen edge so a half-visible next card hints at the scroll. */
    margin-inline: -16px;
    padding-inline: 16px;
    scroll-padding-inline: 16px;
    padding-bottom: 4px;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .rail::-webkit-scrollbar { display: none; }
  .rail__item { flex: 0 0 78%; scroll-snap-align: start; }
}

.rail__dots { display: flex; justify-content: center; gap: 4px; margin-top: 12px; }
.rail__dots[hidden] { display: none; } /* `display:flex` would otherwise beat [hidden] */
.rail__dot {
  width: 44px; /* ТЗ §9 touch target, around an 8px dot */
  height: 44px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
.rail__dot::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 999px;
  background: var(--line);
  transition: background-color .2s ease-out, transform .2s ease-out;
}
.rail__dot.is-active::before { background: var(--green); transform: scale(1.5); }
@media (min-width: 768px) { .rail__dots { display: none; } }

/* --- service tiles ------------------------------------------------------ */
.services {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(2, 1fr);
}
.services .tile { height: 100%; align-content: start; }
/* The inline SVGs carry no fill/stroke of their own — the tab bar sets its own,
   so a tile has to say the same thing or the paths render as black blobs. */
.tile svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform .2s ease-out;
}
.tile__title { font-size: 1rem; }
/* ТЗ §4.1: icon tiles on mobile, cards with a description from 768px up.
   The full text lives on /services/ either way. */
.tile__text { display: none; font-weight: 400; font-size: 14px; color: var(--muted); }

@media (min-width: 768px) {
  .services { grid-template-columns: repeat(3, 1fr); gap: 16px; }
  .tile { place-items: start; text-align: left; padding: 24px; }
  .tile__text { display: block; }
}

/* --- buy-out banner ----------------------------------------------------- */
/* Same photo-behind-copy technique `.hero` uses: media at z-index -2, a scrim at -1,
   copy in normal flow on top. Was a CSS-drawn car-icon watermark and an animated glow
   before the client supplied a real photo — both retired as redundant once there was
   an actual car to show, and the photo's own light-trail effects already move the eye
   the glow was trying to. */
.buyout {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  min-height: 260px;
  display: grid;
  align-content: center;
  padding: 40px 16px;
  color: var(--paper);
  background: var(--ink); /* fallback while the photo loads, or if it never does */
}
.buyout__media { position: absolute; inset: 0; z-index: -2; }
.buyout__media img { width: 100%; height: 100%; object-fit: cover; object-position: 68% 45%; }
/* Darker on the left, where the copy sits, fading out toward the right so the car and
   its light trails stay visible — the same reasoning as `.hero::after`'s horizontal
   fade, tuned for a shorter box and copy that never moves off the left edge at any
   width (`.hero__body` recentres on mobile; `.buyout__body` never does). */
.buyout::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(100deg,
      rgba(17, 17, 17, .88) 0%,
      rgba(17, 17, 17, .74) 32%,
      rgba(17, 17, 17, .4) 58%,
      rgba(17, 17, 17, .1) 80%,
      rgba(17, 17, 17, 0) 100%);
}
.buyout__body { position: relative; }
.buyout h2 { font-size: clamp(1.35rem, 4.5vw, 2rem); max-width: 20ch; }
.buyout p { color: #d1d5db; max-width: 48ch; margin: 0; }

/* --- about block -------------------------------------------------------- */
.about { display: grid; gap: 20px; }
.about__media img { width: 100%; border-radius: var(--radius); }
.about p { max-width: 62ch; }

/* --- about page (ТЗ §4.6) ------------------------------------------------ */
/* Same .about grid the landing teaser uses, just given more room to be the page's
   main content rather than one aside among several. A deliberate crop rather than the
   teaser's natural-ratio image: a full-width photo at whatever aspect ratio the file
   happens to be looks like an afterthought on a page that is otherwise all about it. */
.about--page .about__media img { aspect-ratio: 4 / 3; object-fit: cover; }
.about--page .about__body p { font-size: 1.05rem; }

/* A light band around the counters so they read as the page's "by the numbers"
   moment rather than the same small tiles repeated from the landing page. */
.about-stats { background: #fafafa; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }

@media (min-width: 768px) {
  .about { grid-template-columns: 1fr 1fr; align-items: center; gap: 32px; }
}
@media (min-width: 1024px) {
  .buyout { padding: 56px 32px; min-height: 380px; }
  .hero { min-height: 68vh; padding: 96px 0 72px; }
}

/* --- interaction states ------------------------------------------------- */
.btn {
  cursor: pointer;
  transition: transform .18s ease-out, box-shadow .18s ease-out, background-color .18s ease-out;
}
.btn:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(113, 179, 28, .3); }
.btn:active { transform: translateY(0); }

.btn--outline {
  background: transparent;
  border-color: rgba(255, 255, 255, .55);
  color: var(--paper);
}
.btn--outline:hover, .btn--ghost:hover { box-shadow: 0 6px 16px rgba(0, 0, 0, .12); }
.btn--outline:hover { background: rgba(255, 255, 255, .12); border-color: var(--paper); }

.card { transition: transform .2s ease-out, box-shadow .2s ease-out, border-color .2s ease-out; }
/* focus-within so the lift is reachable by keyboard, not only by mouse. */
.card:hover, .card:focus-within {
  transform: translateY(-4px);
  box-shadow: 0 12px 28px rgba(0, 0, 0, .13);
  border-color: #d6dbe2;
}
.card__media { overflow: hidden; }
.card__media img { transition: transform .35s ease-out; }
.card:hover .card__media img, .card:focus-within .card__media img { transform: scale(1.04); }

.tile { transition: transform .2s ease-out, box-shadow .2s ease-out, border-color .2s ease-out; }
.tile:hover, .tile:focus-within {
  transform: translateY(-3px);
  border-color: var(--green);
  box-shadow: 0 10px 24px rgba(0, 0, 0, .09);
}
.tile:hover svg { transform: scale(1.1); }

/* --- hero entrance ------------------------------------------------------ */
/* `backwards` holds the from-state during the delay, so nothing flashes first. */
.hero__title, .hero__lead, .hero__actions {
  animation: rise .6s cubic-bezier(.16, 1, .3, 1) backwards;
}
.hero__title { animation-delay: .05s; }
.hero__lead { animation-delay: .15s; }
.hero__actions { animation-delay: .25s; }
/* An ambient drift, so it wants a near-constant rate — `ease-out` front-loads the
   movement and then visibly stalls. No `will-change` here: this runs once and the
   browser promotes a transform animation on its own, then releases the layer. */
.hero__media img { animation: hero-settle 24s cubic-bezier(.33, .18, .5, .86) forwards; }

@keyframes rise { from { opacity: 0; transform: translateY(18px); } }
@keyframes hero-settle { from { transform: scale(1.045); } to { transform: scale(1); } }

/* --- hero parallax ------------------------------------------------------ */
/* The photo leaves at roughly 90% of the page's speed, so the hero slides out from
   under the content rather than away with it. Deliberately small: this is depth, not
   an effect, and a big differential on a full-bleed image is what makes parallax
   read as seasickness.
   Driven by the scroll position itself rather than a scroll listener: no JS, no
   main-thread work per frame, and nothing to throttle. Browsers without
   `animation-timeline` simply get the static hero — which is why this is additive
   rather than a rewrite. The scale lives on the img and the travel on its wrapper,
   because two animations on the same `transform` would overwrite each other. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    /* The timeline is named on `.hero`, not taken with `view()` on the image.
       `.hero` is `overflow: hidden`, which makes it a scroll container, so a
       timeline resolved from inside it measures the child against a box that never
       scrolls and sits at 50% forever. Naming it on the hero measures the hero
       against the page, which is the movement we actually want to follow. */
    .hero { view-timeline-name: --hero; }

    .hero__media {
      will-change: transform;
      animation: hero-parallax linear both;
      animation-timeline: --hero;
      animation-range: cover 0% cover 100%;
    }
  }
}

@keyframes hero-parallax {
  from { transform: translateY(-10%); }
  to { transform: translateY(10%); }
}

/* --- scroll reveal ------------------------------------------------------ */
/* Scoped to `.js`: without JavaScript nothing ever adds `.is-visible`, and an
   invisible page is a worse failure than an unanimated one. The class is set by
   an inline one-liner in the head, before first paint. */
.js .reveal {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity .5s ease-out, transform .5s ease-out;
}
.js .reveal.is-visible { opacity: 1; transform: none; }

/* One movement for the block, then a staggered fade for its items — the items
   do not travel as well, or the two motions read as a wobble. */
.js .reveal :is(.rail__item, .services li, .checklist li, .counters li, .advantage) {
  opacity: 0;
  transition: opacity .5s ease-out calc(var(--i, 0) * 70ms);
}
.js .reveal.is-visible :is(.rail__item, .services li, .checklist li, .counters li, .advantage) { opacity: 1; }

/* ======================================================================== */
/* Services (ТЗ §4.4)                                                       */
/* ======================================================================== */

.service-cards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 16px;
}

.service-card {
  display: grid;
  gap: 8px;
  align-content: start;
  height: 100%;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
  text-decoration: none;
  transition: transform .2s ease-out, box-shadow .2s ease-out, border-color .2s ease-out;
}
.service-card:hover, .service-card:focus-within {
  transform: translateY(-3px);
  border-color: var(--green);
  box-shadow: 0 10px 24px rgba(0, 0, 0, .09);
}
.service-card__icon { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 12px; background: rgba(113, 179, 28, .1); }
.service-card__icon svg {
  width: 24px;
  height: 24px;
  color: var(--green);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.service-card__title { margin: 4px 0 0; font-size: 1.05rem; }
.service-card__text { margin: 0; color: var(--muted); font-size: 14px; }
.service-card__price { color: var(--green); font-weight: 700; }

/* --- one service ------------------------------------------------------- */
.service { display: grid; gap: 24px; }
/* The h1 belongs to the article under it, not to the gap above it: the section's own
   32px on top of the page-head's spacing read as a break between two blocks. */
.page-head + .service { padding-top: 8px; }
.service__banner img { width: 100%; border-radius: var(--radius); }
.service__lead { font-size: 1.05rem; }
.service__text { color: var(--ink); }
.service__body p { max-width: 65ch; }
.service__body h2 { margin-top: 28px; font-size: 1.25rem; }

.service__aside {
  display: grid;
  gap: 12px;
  align-content: start;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
}
.service__price { display: grid; gap: 2px; }
.service__price-label { color: var(--muted); font-size: 14px; }
.service__price strong { font-size: 1.5rem; color: var(--green); font-variant-numeric: tabular-nums; }
.service__aside .btn { margin-top: 0; }

/* A quiet block that is not the dark buy-out banner — this one is an aside, not a
   second competing call to action. */
.callout {
  display: grid;
  gap: 8px;
  justify-items: start;
  padding: 24px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--green);
  border-radius: var(--radius);
  background: #f8faf8;
}
.callout h2 { margin: 0; font-size: 1.15rem; }
.callout p { margin: 0; color: var(--muted); max-width: 55ch; }

@media (min-width: 768px) {
  .service-cards { grid-template-columns: repeat(2, 1fr); }
  /* The price and the CTAs stay beside the description rather than below the fold. */
  /* A capped text column rather than `1fr`: at 1320px a fluid column runs ~65 characters
     past a comfortable measure. The aside stays pinned to the right edge. */
  .service {
    grid-template-columns: minmax(0, 680px) 300px;
    justify-content: space-between;
    align-items: start;
    column-gap: 32px;
  }
  .service__banner { grid-column: 1 / -1; }
  .service__aside { position: sticky; top: 16px; }
}

@media (min-width: 1024px) {
  .service-cards { grid-template-columns: repeat(3, 1fr); }
}

/* ======================================================================== */
/* Catalogue (ТЗ §4.2) and car detail (§4.3)                                */
/* ======================================================================== */

/* --- filter panel ------------------------------------------------------- */
.filters {
  margin-top: 8px;
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fafbfa;
}
.filters__grid {
  display: grid;
  gap: 12px 16px;
  grid-template-columns: 1fr;
}
.filters .field { margin-bottom: 0; }
/* Two boxes reading "from / to" as one control. `minmax(0, 1fr)` and not `1fr`: a
   number input's default intrinsic width is wider than half a phone screen, and a bare
   `1fr` floors at that width instead of shrinking. */
.pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.filters__actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 16px; }
.filters__actions .btn { margin-top: 0; }

@media (min-width: 768px) {
  .filters__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 1024px) {
  /* The two range pairs are twice as wide as a select, so five controls sit on one
     row as 2 + 2 + 1 + 1 + 1 without a second grid definition per breakpoint. */
  .filters__grid { grid-template-columns: repeat(7, 1fr); align-items: end; }
  .field--pair { grid-column: span 2; }
}

.results-count { margin: 0 0 16px; font-size: 14px; }
.car-grid { list-style: none; margin: 0; padding: 0; }

.empty {
  display: grid;
  justify-items: center;
  gap: 8px;
  padding: 48px 16px;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  text-align: center;
  color: var(--muted);
}
.empty p { margin: 0; }

.pager { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 12px; margin-top: 24px; }
.pager .btn { margin-top: 0; }
.pager__status { font-size: 14px; }

/* A card with no photo says so rather than showing an unexplained grey box. */
.card__nophoto {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--muted);
  font-size: 14px;
}
.card__status {
  position: absolute;
  top: 12px;
  left: 12px;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(17, 17, 17, .82);
  color: var(--paper);
  font-size: 13px;
  font-weight: 600;
}

/* --- gallery (ТЗ §4.3, scroll-snap, no slider library) ------------------ */
.car { display: grid; gap: 24px; }
.car__gallery { display: grid; gap: 8px; }

.gallery { position: relative; }
.gallery__track {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  border-radius: var(--radius);
  background: var(--line);
}
.gallery__track::-webkit-scrollbar { display: none; }
.gallery__slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  aspect-ratio: 4 / 3;
  /* The anchor lands the slide in its container without dragging the page: without
     this the browser scrolls the whole document to put the slide at the very top. */
  scroll-margin: 0;
}
.gallery__slide img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery--empty { display: grid; place-items: center; aspect-ratio: 4 / 3; border-radius: var(--radius); background: var(--line); }

/* Top right, not bottom right: the bottom of the frame belongs to the video slide's
   controls — the native control bar spans it, and YouTube puts its logo in that
   corner — so a counter down there sits on top of them. */
.gallery__counter {
  position: absolute;
  right: 12px;
  top: 12px;
  margin: 0;
  padding: 4px 12px;
  border-radius: 999px;
  background: rgba(17, 17, 17, .72);
  color: var(--paper);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}

/* The same frosted surface as the tab bar rather than a second flat white circle —
   one decision about what a floating control looks like on this site. */
.gallery__arrow, .lightbox__close {
  width: 44px; /* ТЗ §9 touch target */
  height: 44px;
  display: none; /* shown only with JS — see below */
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .82);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  color: var(--ink);
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .2);
  transition: background-color .2s ease-out, color .2s ease-out, opacity .2s ease-out,
    transform .25s cubic-bezier(.34, 1.4, .64, 1);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .gallery__arrow, .lightbox__close { background: var(--paper); }
}
.gallery__arrow svg, .lightbox__close svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.gallery__arrow:hover, .lightbox__close:hover { background: var(--green); color: var(--paper); }

.gallery__arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
}
.gallery__arrow--prev { left: 12px; }
.gallery__arrow--next { right: 12px; }
.gallery__arrow:active { transform: translateY(-50%) scale(.9); }
/* At the first or last slide the arrow used to clamp in silence, which is
   indistinguishable from a broken button. */
.gallery__arrow.is-disabled { opacity: .3; pointer-events: none; }
/* Scripted controls, so they exist only where scripting does — and ТЗ §4.3 asks for
   arrows on the desktop and swipe on mobile, so they start at the tablet breakpoint.
   The thumbnails and the swipe carry the gallery everywhere else. */
@media (min-width: 768px) {
  .js .gallery__arrow { display: grid; }
}

/* The zoom target is a link to the file: with JavaScript off it opens the full-size
   photo in the browser's own viewer, which is a real full-screen view. */
.gallery__zoom { display: block; width: 100%; height: 100%; }
.js .gallery__zoom { cursor: zoom-in; }

/* --- video slide (last in the strip) ------------------------------------ */
.gallery__slide--video { background: var(--ink); }
.gallery__slide video, .gallery__slide iframe {
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
  /* `contain`, not the `cover` the photos use: cropping a video cuts the subject out. */
  object-fit: contain;
  background: #000;
}

/* ТЗ §11.2 — the poster stands in for the YouTube player until the visitor asks for it,
   so nothing reaches a third party on page load. main.js swaps this for the iframe. */
.facade {
  position: relative;
  display: grid;
  place-items: center;
  align-content: center;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  background: var(--ink);
  color: var(--paper);
  font: inherit;
  cursor: pointer;
}
.facade img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: .5; }
.facade__play {
  position: relative;
  display: grid;
  place-items: center;
  width: 72px;
  height: 72px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .92);
  color: var(--ink);
  transition: background-color .2s ease-out, color .2s ease-out, transform .2s ease-out;
}
/* A triangle is optically off-centre in a circle until it is nudged right. */
.facade__play svg { width: 26px; height: 26px; fill: currentColor; stroke: none; margin-left: 3px; }
.facade:hover .facade__play { background: var(--green); color: var(--paper); transform: scale(1.06); }
.facade__note { position: relative; margin-top: 12px; font-size: 14px; }

/* --- full-screen viewer (ТЗ §4.3) --------------------------------------- */
.lightbox {
  width: 100vw;
  max-width: 100vw;
  /* `dvh` is right here and wrong in the hero: this is a fixed overlay over a page
     whose scrolling is locked while it is open, so the URL bar cannot move under it. */
  height: 100vh;
  height: 100dvh;
  max-height: 100dvh;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: hidden;
}
.lightbox::backdrop { background: rgba(17, 17, 17, .94); }
.lightbox__frame { width: 100%; height: 100%; }
.lightbox .gallery__track { height: 100%; background: transparent; border-radius: 0; }
.lightbox .gallery__slide { aspect-ratio: auto; height: 100%; }
.lightbox .gallery__slide img { object-fit: contain; }
/* The viewer holds photos only, so the bottom is free and the counter reads better
   centred under them. `top: auto` because the base rule pins it to the top. */
.lightbox .gallery__counter {
  top: auto;
  bottom: 24px;
  left: 50%;
  right: auto;
  transform: translateX(-50%);
}
.lightbox__close { position: fixed; top: 16px; right: 16px; }
.js .lightbox__close { display: grid; }

.card__video {
  position: absolute;
  top: 12px;
  right: 12px;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 999px;
  background: rgba(17, 17, 17, .72);
  color: var(--paper);
}
.card__video svg { width: 13px; height: 13px; fill: currentColor; stroke: none; }

.thumbs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: 8px;
  overflow-x: auto;
  /* Visible on purpose. Past about ten photos the strip runs off the edge of its
     column, and a hidden scrollbar leaves a mouse user with no sign the rest exist
     and nothing to drag to reach them — a touchscreen swipes, a trackpad does not. */
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
  padding-bottom: 6px;
}
/* `scrollbar-width` is recent in WebKit; this is the same bar for older Safari. */
.thumbs::-webkit-scrollbar { height: 8px; }
.thumbs::-webkit-scrollbar-thumb { background: var(--line); border-radius: 4px; }
.thumbs__item {
  display: block;
  width: 84px;
  aspect-ratio: 4 / 3;
  border: 2px solid transparent;
  border-radius: 8px;
  overflow: hidden;
  opacity: .6;
  transition: opacity .2s ease-out, border-color .2s ease-out;
}
.thumbs__item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.thumbs__item.is-active, .thumbs__item:hover { opacity: 1; border-color: var(--green); }
/* The video shares the main photo as its poster, so without a badge its thumbnail is
   just the same picture again. */
.thumbs__item--video { position: relative; background: var(--ink); }
.thumbs__play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: rgba(17, 17, 17, .4);
  color: var(--paper);
}
.thumbs__play svg { width: 18px; height: 18px; fill: currentColor; stroke: none; }

/* --- specification and price ------------------------------------------- */
.car__summary { margin: 0 0 24px; }
.car__main h2 { font-size: 1.15rem; margin: 24px 0 12px; }
.car__text p { max-width: 65ch; }

.spec {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0 24px;
  /* Capped: stretched over a 900px column the value ends up an inch from its label
     and the table stops reading as pairs. */
  max-width: 560px;
  margin: 0;
  font-size: 15px;
}
.spec dt { color: var(--muted); padding: 8px 0; }
.spec dd { margin: 0; padding: 8px 0; font-weight: 600; }
/* Every row but the last gets a rule. Two elements per row, so both halves need it. */
.spec dt:not(:nth-last-of-type(1)), .spec dd:not(:nth-last-of-type(1)) { border-bottom: 1px solid var(--line); }
.spec__vin { font-variant-numeric: tabular-nums; letter-spacing: .02em; word-break: break-all; }

.car__aside {
  display: grid;
  gap: 12px;
  align-content: start;
  justify-items: stretch;
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
}
.car__aside .badge { justify-self: start; }
.car__price {
  margin: 0;
  font-size: 2rem;
  font-weight: 800;
  color: var(--green);
  font-variant-numeric: tabular-nums;
}
.car__aside .btn { margin-top: 0; }
.car__aside-note { margin: 0; font-size: 14px; }

@media (min-width: 768px) {
  .car {
    grid-template-columns: minmax(0, 1fr) 300px;
    justify-content: space-between;
    align-items: start;
    column-gap: 32px;
  }
  .car__gallery { grid-column: 1 / -1; }
  .car__aside { position: sticky; top: 16px; }
}

@media (min-width: 1024px) {
  /* The gallery beside the price rather than above it: on a wide screen the photo and
     the number a buyer is deciding on belong on the same screenful. The aside is
     placed explicitly — left to auto-placement it drops into the second row, next to
     the specification, which is exactly what this rule exists to prevent. */
  .car__gallery { grid-column: 1; }
  .car__main { grid-column: 1; }
  .car__aside { grid-column: 2; grid-row: 1 / span 2; }
  /* 4:3 across a 900px column is 700px tall — the whole viewport for one photo. */
  .gallery__slide { aspect-ratio: 16 / 10; }
}

/* ======================================================================== */
/* Buy-out form (ТЗ §5)                                                     */
/* ======================================================================== */

.section--form { display: grid; gap: 24px; }

.form { display: grid; gap: 8px; }

/* A fieldset's default border and padding do not survive contact with a grid, and the
   grouping here is done by the legend and the gap rather than by a box inside a box. */
.form__group { border: 0; padding: 0; margin: 0 0 8px; }
.form__legend {
  padding: 0;
  margin-bottom: 12px;
  font-size: 1.15rem;
  font-weight: 700;
  /* The rule under the legend is what separates the four groups now that the fieldset
     draws nothing — cheaper than a card per group, and it survives a one-column phone
     layout where four stacked cards would read as four separate forms. */
  border-bottom: 1px solid var(--line);
  padding-bottom: 8px;
  width: 100%;
}

/* One column on a phone. Two from 560px, which is where a half-width "Year" box stops
   being too narrow to read its own placeholder — chosen off the content rather than off
   the device breakpoints above, because this is the only two-column form on the site. */
.form__row { display: grid; gap: 0 16px; }
@media (min-width: 560px) {
  .form__row { grid-template-columns: repeat(2, 1fr); }
  .form__row .field--wide { grid-column: 1 / -1; }
}

.field__label { font-weight: 600; font-size: 14px; }
/* The required marker. Colour alone is not a distinction (WCAG 1.4.1), so it is also a
   character that is there or is not. */
.field__label b { color: var(--green); }
.field__count { font-size: 13px; margin: 0; text-align: right; font-variant-numeric: tabular-nums; }
.field__count.is-near { color: #b45309; }

/* Django renders `<select>` with the UA's own arrow and 13px font otherwise. */
.field select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 18px;
  padding-right: 40px;
}

.form__alert {
  padding: 12px 16px;
  border: 1px solid #fca5a5;
  border-radius: var(--radius);
  background: #fef2f2;
  color: #991b1b;
}
.form__alert p { margin: 0; }

/* ТЗ §5.5 — the honeypot. Off-screen rather than `display: none`: a bot that skips
   hidden inputs walks past the trap, and this is still unreachable by tab or by eye. */
.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- intent (a two-option radio group as one control) ------------------- */
.segmented { display: grid; gap: 8px; }
@media (min-width: 480px) { .segmented { grid-template-columns: repeat(2, 1fr); } }

.segmented__option {
  display: flex;
  align-items: center;
  justify-content: center; /* the tile is the control, so its label belongs in the middle */
  gap: 10px;
  min-height: 56px;
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  cursor: pointer;
  font-weight: 600;
  text-align: center;
  transition: border-color .15s ease-out, background .15s ease-out, box-shadow .15s ease-out;
}
.segmented__option:hover { border-color: #c9cfd6; }
/* `:has` rather than a class toggled by script: the state belongs to the radio, and
   duplicating it in JavaScript is how the two get out of step. Browsers without it
   still show a perfectly usable radio, which is the whole control underneath. */
.segmented__option:has(input:checked) {
  border-color: var(--green);
  background: rgba(113, 179, 28, .08);
  box-shadow: inset 0 0 0 1px var(--green); /* a 2px border would shift the text by 1px */
}
.segmented__option:has(input:focus-visible) { outline: 3px solid var(--green); outline-offset: 2px; }

/* Drawn rather than native. `accent-color` only recolours the dot and leaves the ring
   the platform's own grey — which is what made these look unfinished next to the green
   tile they sit in. The input keeps being a real radio: focus, arrow keys and the
   form all still work, only the paint is ours. */
.segmented__option input {
  appearance: none;
  flex: none;
  width: 22px;
  height: 22px;
  margin: 0;
  border: 2px solid #c9cfd6;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--paper);
  transition: border-color .15s ease-out;
}
.segmented__option input::before {
  content: "";
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--green);
  /* Scaled rather than toggled: `display: none` cannot animate, and the dot growing
     into place is the whole feedback that a tap registered. */
  transform: scale(0);
  transition: transform .15s ease-out;
}
.segmented__option input:checked { border-color: var(--green); }
.segmented__option input:checked::before { transform: scale(1); }

/* --- file inputs -------------------------------------------------------- */
/* The label *is* the control: a real file input lives inside it, sized to the whole
   box. Not `display: none` — a hidden input cannot be reached by keyboard, and the
   no-JavaScript path (ТЗ §5.2) depends on this still opening the picker. */
.dropzone {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 132px;
  padding: 20px;
  border: 2px dashed var(--line);
  border-radius: var(--radius);
  background: #fafafa;
  text-align: center;
  cursor: pointer;
  transition: border-color .15s ease-out, background .15s ease-out;
}
.dropzone--slim { min-height: 92px; }
.dropzone:hover, .dropzone.is-over { border-color: var(--green); background: rgba(113, 179, 28, .06); }
.dropzone:has(input:focus-visible) { outline: 3px solid var(--green); outline-offset: 2px; }

/* Transparent and full-size rather than hidden: the click target is the whole box, the
   input keeps its own keyboard behaviour, and no JavaScript is forwarding anything. */
.dropzone input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
.dropzone__text { display: grid; gap: 4px; pointer-events: none; }
.dropzone__text b { color: var(--green); }
/* `.field label` sets 600 for the real labels; the hint under the box is not one. */
.dropzone__text .muted { font-size: 14px; font-weight: 400; }

/* --- previews (ТЗ §5.3.1) ----------------------------------------------- */
.previews {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: grid;
  /* auto-fill, so three photos on a phone and ten on a desktop both look deliberate
     without a breakpoint for each count. */
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: 8px;
}
.previews[hidden] { display: none; } /* `display: grid` would otherwise beat [hidden] */

.previews__item {
  position: relative;
  aspect-ratio: 1;
  border-radius: 8px;
  overflow: hidden;
  background: var(--line);
}
.previews__item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.previews__item.is-bad { outline: 2px solid #b91c1c; outline-offset: -2px; }
/* HEIC: no browser but Safari can draw it, so the tile shows the file name instead. */
.previews__item--noPreview { display: grid; place-items: center; padding: 8px; }
.previews__name { font-size: 11px; word-break: break-all; text-align: center; color: var(--muted); }

.previews__size {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 2px 4px;
  background: rgba(0, 0, 0, .55);
  color: var(--paper);
  font-size: 11px;
  text-align: center;
}

/* 32px rather than 44px: it sits on top of an 88px thumbnail, and a 44px button would
   cover half the photo it is meant to identify. The thumbnail itself is not a target. */
.previews__drop {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, .6);
  color: var(--paper);
  font: inherit;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.previews__drop:hover { background: #b91c1c; }

.previews--file { grid-template-columns: 1fr; }
.previews__file {
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper);
  font-size: 14px;
  word-break: break-all;
}
.previews__file.is-bad { border-color: #b91c1c; color: #b91c1c; }

/* --- consent (ТЗ §11.3) ------------------------------------------------- */
.consent {
  padding: 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fafafa;
}
.consent__label { display: flex; gap: 12px; align-items: flex-start; cursor: pointer; font-size: 14px; }

/* Drawn to match the radios above it. Aligned to the *first line* of the text, not to
   the block: the sentence wraps to three lines on a phone, so centring on the box would
   leave the tick floating beside the middle of a paragraph. A 24px box against a 21.7px
   line needs to start 1px above the text for the two centres to meet — it was sitting
   2px below, which is the "not in the level" in the screenshot. */
.consent__label input {
  appearance: none;
  flex: none;
  width: 24px;
  height: 24px;
  margin: -1px 0 0;
  border: 2px solid #c9cfd6;
  border-radius: 7px;
  display: grid;
  place-items: center;
  background: var(--paper);
  cursor: pointer;
  transition: background .15s ease-out, border-color .15s ease-out;
}
/* The same tick the `.checklist` bullets use, so the page has one checkmark and not two. */
.consent__label input::before {
  content: "✓";
  color: var(--paper);
  font-size: 15px;
  font-weight: 700;
  line-height: 1;
  transform: scale(0);
  transition: transform .15s ease-out;
}
.consent__label input:checked { background: var(--green); border-color: var(--green); }
.consent__label input:checked::before { transform: scale(1); }
.consent a { color: var(--green); }

/* --- submit + progress (ТЗ §5.3.4) -------------------------------------- */
.form__submit { display: grid; gap: 8px; margin-top: 8px; }
.form__submit .btn { margin-top: 0; }

/* Waiting on the consent tick (ТЗ §11.3). `--muted` rather than a pale grey so white
   text still clears 4.5:1 — the button is `aria-disabled`, not `disabled`, so it stays
   focusable and its label has to stay readable. `pointer-events` is deliberately left
   alone: the press must reach the form so the browser can say what is missing. */
.btn.is-disabled,
.btn.is-disabled:hover {
  background: var(--muted);
  box-shadow: none;
  transform: none;
  cursor: not-allowed;
}

.progress { display: grid; gap: 6px; }
.progress[hidden] { display: none; }
.progress progress {
  appearance: none;
  width: 100%;
  height: 8px;
  border: 0;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}
/* Both vendors' pseudo-elements, and they cannot be combined into one selector — an
   unrecognised one invalidates the whole rule for the browser that does know the other. */
.progress progress::-webkit-progress-bar { background: var(--line); border-radius: 999px; }
.progress progress::-webkit-progress-value { background: var(--green); border-radius: 999px; }
.progress progress::-moz-progress-bar { background: var(--green); border-radius: 999px; }
.progress__text { margin: 0; font-size: 14px; color: var(--muted); font-variant-numeric: tabular-nums; }

.form__aside {
  padding: 20px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  align-content: start;
}
.form__aside h2 { margin: 0 0 8px; font-size: 1.1rem; }

.checklist--inline { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }

/* --- thank-you page ----------------------------------------------------- */
/* This page drops the footer, which otherwise filled the bottom of the screen — without
   it the confirmation sat at the top of 400px of blank white and read as a page that had
   failed to finish loading. Centring it in what is left is the point of removing the
   footer rather than a side effect of it.

   Subtract the header and the strip the bottom bar occupies, which `body` already
   reserves as `--tabbar-h`. Both are variables rather than numbers: the header is 68px
   on a phone and 76px from 1024px up, and hardcoding the smaller one made this page
   overflow by exactly 8px on a desktop — a scrollbar on a screen with 290px of spare
   whitespace on it.

   The section's own 32px padding is *not* subtracted: `box-sizing: border-box` means
   min-height already contains it, and taking it off again left the block sitting 32px
   above centre. `svh`, never `dvh` — `dvh` tracks the mobile URL bar and reflows the
   page on every scroll that collapses it. */
.sent {
  max-width: 46ch;
  margin-inline: auto;
  text-align: center;
  min-height: calc(100svh - var(--header-h) - var(--tabbar-h));
  display: grid;
  align-content: center;
}
@media (min-width: 1024px) {
  /* The bar is hidden up here but `--tabbar-h` still has to hold its height for
     `.tabbar` itself, so the allowance is dropped here rather than zeroing the var. */
  .sent { min-height: calc(100svh - var(--header-h)); }
}
.sent__mark {
  display: grid;
  place-items: center;
  width: 72px;
  height: 72px;
  margin: 0 auto 16px;
  border-radius: 50%;
  background: rgba(113, 179, 28, .12);
  color: var(--green);
  font-size: 2rem;
}
.sent__lead { font-size: 1.05rem; }
.sent__number { font-size: 1.15rem; }
.sent__actions { display: flex; flex-wrap: wrap; gap: 12px; justify-content: center; }

.notfound__code { font-size: 4rem; font-weight: 700; line-height: 1; color: var(--green); margin-bottom: 8px; }

/* --- Cookie consent banner (ТЗ §11.1) ----------------------------------- */
.cookie-banner {
  position: fixed;
  inset-inline: 0;
  bottom: var(--tabbar-h);
  z-index: 50;
  background: var(--ink);
  color: #fff;
  padding: 16px;
}
@media (min-width: 1024px) {
  .cookie-banner { bottom: 0; }
}
.cookie-banner__body {
  max-width: 960px;
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 16px 24px;
}
.cookie-banner__body p { flex: 1 1 280px; margin: 0; font-size: .9rem; }
.cookie-banner__body a { color: #fff; }
.cookie-banner__actions { display: flex; flex-wrap: wrap; gap: 8px; }
.cookie-banner__actions .btn { margin-top: 0; }
.cookie-banner__panel { max-width: 960px; margin: 16px auto 0; display: grid; gap: 10px; }
/* `display: grid` above beats the UA stylesheet's `[hidden] { display: none }` —
   author rules always win over the browser default, whatever the selector weight —
   so the panel needs its own hidden override or it shows before any button is clicked. */
.cookie-banner__panel[hidden] { display: none; }
.cookie-banner__panel .btn { margin-top: 4px; justify-self: start; }
.cookie-banner__toggle { display: flex; align-items: center; gap: 8px; font-size: .9rem; }

/* --- consent-gated embeds (ТЗ §11.2) ------------------------------------- */
.embed-gate__placeholder {
  display: grid;
  place-items: center;
  gap: 12px;
  aspect-ratio: 16 / 9;
  padding: 24px;
  text-align: center;
  border-radius: var(--radius);
  background: var(--line);
}
.embed-gate__placeholder p { max-width: 40ch; }
.embed-gate__placeholder .btn { margin-top: 0; }
.embed-gate iframe { width: 100%; aspect-ratio: 16 / 9; border: 0; border-radius: var(--radius); }

.privacy-text p { max-width: 65ch; }
.privacy-text h2 { margin-top: 32px; font-size: 1.25rem; }
.privacy-text h3 { margin-top: 20px; font-size: 1.05rem; }
.privacy-text ul { max-width: 65ch; padding-left: 20px; }
.privacy-text li + li { margin-top: 6px; }
/* Not optional: `a { color: inherit }` at the top of this file would otherwise render
   every link in the policy — tietosuoja.fi, Google, YouTube, OSM — as plain body text
   indistinguishable from the prose around it. Same fix `.consent a` already needed. */
.privacy-text a { color: var(--green); }

.footer__cookie-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  text-decoration: underline;
  cursor: pointer;
}

@media (min-width: 1024px) {
  /* The form takes the width, the "call us instead" card rides beside it. Below this
     the card sits under the submit button, which is the right order on a phone: it is
     the alternative you reach for after seeing how long the form is. */
  .section--form { grid-template-columns: minmax(0, 1fr) 300px; align-items: start; gap: 32px; }
}

/* --- ТЗ §12 / WCAG 2.3.3: motion is optional --------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .js .reveal,
  .js .reveal :is(.rail__item, .services li, .checklist li, .counters li, .advantage) {
    opacity: 1;
    transform: none;
  }
  .btn:hover, .card:hover, .card:focus-within, .tile:hover, .tile:focus-within { transform: none; }
  .advantage:hover, .advantage:hover .advantage__icon svg { transform: none; }
  .tabbar a:active .tabbar__icon { transform: none; }
  .service-card:hover, .service-card:focus-within { transform: none; }
  .facade:hover .facade__play { transform: none; }
  /* `none` would drop the arrow to the bottom of the photo — the translate is what
     centres it, not an effect. */
  .gallery__arrow:active { transform: translateY(-50%); }
}
