/* =========================================================================
   Belver — landing page
   Palette: near-black field green, bone paper, orchard green, lime, wheat.
   ========================================================================= */

:root {
  --ink:        #002800;
  --soil:       #14201B;
  --bone:       #F5F2EA;
  --bone-dim:   #EAE6DA;
  --green:      #008200;
  /* Sits between the two, so the ramp ink -> deep -> green is one hue. */
  --green-deep: #005500;
  --lime:       #4CDC4C;
  --wheat:      #E0B15C;

  --text:       var(--ink);
  --text-soft:  #4A5A52;
  --text-light: rgba(245, 242, 234, 0.72);

  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;

  --wrap: 1180px;
  --gut: clamp(1.25rem, 5vw, 3rem);
  --pad-section: clamp(5rem, 11vw, 9.5rem);

  --r-sm: 8px;
  --r-md: 16px;
  --r-lg: 28px;
  --r-pill: 999px;

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

/* ------------------------------ reset ---------------------------------- */

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: clamp(1rem, 0.95rem + 0.25vw, 1.0625rem);
  line-height: 1.65;
  color: var(--text);
  background: var(--bone);
  -webkit-font-smoothing: antialiased;
  /* clip rather than hidden — hidden on one axis makes the body a scroll
     container, and the pinned stage in "how it works" would then stick to
     that instead of to the viewport, i.e. not stick at all */
  overflow-x: clip;
}

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

h1, h2 { margin: 0; line-height: 1.08; letter-spacing: -0.025em; font-weight: 600; }
p { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: inherit; }

:focus-visible {
  outline: 3px solid var(--lime);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 999;
  padding: 0.75rem 1.25rem;
  background: var(--ink);
  color: var(--bone);
  text-decoration: none;
  border-radius: 0 0 var(--r-sm) 0;
}
.skip-link:focus { left: 0; }

/* ----------------------------- layout ---------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gut);
}

.section { padding-block: var(--pad-section); position: relative; }

.section--team { background: var(--green-deep); color: var(--bone); }

/* --------------------------- typography -------------------------------- */

.h2 {
  font-size: clamp(2rem, 1.3rem + 3vw, 3.4rem);
  max-width: 20ch;
}
.h2--light { color: var(--bone); }

/* Centred section system — each content band's title sits centred, so the page
   reads as one column rather than jumping left/centre section to section.
   Driven purely by shared CSS on shared class names, so EN and FR render
   identically with no per-language drift. Two deliberate exceptions stay
   left-aligned: the "How it works" band (its own .section--how, not matched
   here) and the FAQ (overridden below). The hero keeps its full-bleed
   treatment. */
.section .h2 {
  text-align: center;
  margin-inline: auto;
}
/* The deliberate left-aligned exception, stated explicitly so it holds up even
   if the centering rule above is later broadened or the section gains the base
   .section class. (The FAQ title is centred like the rest of the sections.) */
.section--how .h2 {
  text-align: left;
  margin-inline: 0;
}

/* ----------------------------- buttons --------------------------------- */

.btn {
  --btn-bg: var(--ink);
  --btn-fg: var(--bone);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.95rem 1.7rem;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font: inherit;
  font-weight: 600;
  font-size: 0.975rem;
  letter-spacing: -0.005em;
  text-decoration: none;
  cursor: pointer;
  transition: transform 0.25s var(--ease), background-color 0.25s var(--ease),
              color 0.25s var(--ease), box-shadow 0.25s var(--ease);
}
.btn:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 28px -12px rgba(10, 21, 18, 0.55);
}
.btn:active { transform: translateY(0); }

/* The mid green at rest and the near-black on hover — a button that darkens
   under the cursor rather than lightening, which is the way round that reads
   as being pressed. */
.btn--primary { --btn-bg: var(--green-deep); --btn-fg: var(--bone); }
.btn--primary:hover { --btn-bg: var(--ink); }

.btn--accent { --btn-bg: var(--lime); --btn-fg: var(--ink); }
.btn--accent:hover { --btn-bg: var(--wheat); }

.btn--lg { padding: 1.3rem 2.5rem; font-size: 1.18rem; }

/* A small trailing arrow on every CTA, decorative only (::after content is
   skipped by assistive tech). It nudges right on hover, echoing the lift. */
.btn::after {
  content: "→";
  margin-left: 0.5em;
  font-size: 0.9em;
  font-weight: 400;
  line-height: 1;
  transition: transform 0.2s var(--ease);
}
.btn:hover::after { transform: translateX(3px); }

/* Persistent nav CTA: the primary money action, always in reach. */
.nav__cta { padding: 0.62rem 1.3rem; font-size: 0.92rem; white-space: nowrap;
  text-shadow: none; flex: none; }
@media (max-width: 34rem) {
  .nav__cta { padding: 0.55rem 1.05rem; font-size: 0.82rem; }
  .nav__inner .wordmark { font-size: 1.08rem; letter-spacing: 0.16em; }
}

/* ------------------------------- nav ----------------------------------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  transition: background-color 0.35s var(--ease), box-shadow 0.35s var(--ease),
              backdrop-filter 0.35s var(--ease);
}

.nav__inner {
  max-width: var(--wrap);
  margin-inline: auto;
  padding: 1.15rem var(--gut);
  color: var(--bone);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  /* At the top of the page the wordmark has nothing behind it but the footage
     now that the scrim clears the upper frame, and the frame up there is sky —
     bone letters on pale grey. Two shadows: a tight one to cut an edge on the
     letterforms, a wide one to sit them on something. The alternative is
     putting the wash back over the whole video to protect one word. */
  text-shadow: 0 1px 3px rgba(0, 25, 0, 0.7), 0 0 24px rgba(0, 25, 0, 0.6);
  transition: color 0.35s var(--ease), padding 0.35s var(--ease);
}

/* --solid is the same treatment held from the first pixel, for the pages that
   have no hero behind the bar. Without it the bone wordmark lands on bone
   paper and disappears. It is a separate class rather than a preset is-stuck,
   because main.js toggles that one off again the moment you are near the top. */
.nav.is-stuck,
.nav--solid {
  background: rgba(245, 242, 234, 0.88);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  box-shadow: 0 1px 0 rgba(10, 21, 18, 0.08);
}
.nav.is-stuck .nav__inner,
.nav--solid .nav__inner { color: var(--ink); padding-block: 0.8rem; text-shadow: none; }

.wordmark {
  display: inline-block;
  font-size: 1.32rem;
  font-weight: 700;
  letter-spacing: 0.26em;
  text-decoration: none;
}

/* ------------------------------- hero ---------------------------------- */

/* How far up from the bottom of the hero the eagle's head comes out.

   hero3d.js writes the measured number here the moment it has framed the
   shot; this is the same number worked out in advance, so the button does not
   move when it does. It is 0.1072 of the aircraft's span because the framing
   is a pure zoom — every part of the machine keeps its place relative to the
   bottom edge at every size — and the span is the video's rendered width,
   which is what max(100vw, 177.78svh) is. The ratio is measured off the mesh
   rather than derived, so it moves if EYE_BACK, EYE_HIGH or FRAME_RATIO in
   hero3d.js move, and nothing else does.

   With no WebGL, or under prefers-reduced-motion, there is no aircraft and
   nothing overwrites this. That is fine: the button lands where it would have
   landed, which is a sensible place to be whether or not anything is under
   it. */
.hero {
  --eagle-head: calc(max(100vw, 177.78vh) * 0.1072);
  --eagle-head: calc(max(100vw, 177.78svh) * 0.1072);

  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  /* The content is bottom-aligned and the button is the last thing in it, so
     the bottom padding IS the button's clearance. Set off the head rather
     than off the viewport: the eagle is what the button has to stay clear of,
     and the eagle grows with the footage. */
  padding: 8rem var(--gut) calc(var(--eagle-head) + clamp(1.25rem, 2.4vh, 2rem));
  overflow: hidden;
  color: var(--bone);
  isolation: isolate;
}

/* Sits behind the video so the hero never renders bare while it loads/fails. */
.hero__backdrop {
  position: absolute;
  inset: 0;
  z-index: -3;
  background:
    radial-gradient(120% 90% at 70% 10%, rgba(224, 177, 92, 0.35), transparent 55%),
    radial-gradient(100% 80% at 15% 90%, rgba(76, 220, 76, 0.28), transparent 60%),
    linear-gradient(160deg, #008200 0%, #004E00 55%, #002800 100%);
}

.hero__video {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Only where the words are. This used to run 0.45 at its thinnest and 0.55 at
   the very top, which is to say it never stopped — a green wash over the whole
   frame, footage included. It now reaches nothing by two thirds up, so the top
   of the shot is the shot. The strong end is still doing real work: the title,
   the line under it and the button all sit in the bottom third. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(to top, rgba(0, 40, 0, 0.94) 0%, rgba(0, 40, 0, 0.82) 18%,
                    rgba(0, 40, 0, 0.42) 42%, rgba(0, 40, 0, 0) 66%);
}

/* The aircraft the footage is shot from, along the bottom of the shot. Flown
   by hero3d.js; empty and invisible until a frame has actually been drawn.

   z-index -1 and after the scrim in the source, which puts it in front of the
   fade and behind the words: the fade is distance, and this is the nearest
   object in the frame, but the headline still has to be readable through it.

   Before the scrim in the source, so the fade lies over it: at this range the
   aircraft would otherwise be the one thing in the hero the atmosphere does
   not touch, which is exactly how a composite gives itself away.

   The height here is only what stands until hero3d.js measures the video and
   takes the box over — the canvas is transparent until then, so it is a
   placeholder rather than a layout. It matters that it is the same shape the
   script will choose (half the video's rendered width), because the button
   below is positioned off the aircraft and nothing should move when the
   script arrives. */
.hero__pov {
  position: absolute;
  inset: auto 0 0;
  z-index: -1;
  display: block;
  width: 100%;
  height: calc(max(100vw, 177.78vh) * 0.26);
  height: calc(max(100vw, 177.78svh) * 0.26);
  pointer-events: none;
  /* Foreground, a hand's width from the lens: nothing that close to a camera
     focused on a field a kilometre away is sharp. It also keeps the blades
     from strobing against the pixel grid as they turn. */
  filter: blur(0.7px);
  opacity: 0;
  transition: opacity 1.4s var(--ease);
}
.hero__pov.is-live { opacity: 1; }

.hero__content { max-width: var(--wrap); width: 100%; margin-inline: auto; }

.hero__title {
  font-size: clamp(2.6rem, 1.2rem + 6.4vw, 6.2rem);
  letter-spacing: -0.035em;
  max-width: 16ch;
  text-wrap: balance;
}

/* Second line — continues the sentence, so it stays inside the h1. */
.hero__title-2 {
  display: block;
  margin-top: 0.9rem;
  max-width: 44ch;
  font-size: clamp(1.0625rem, 0.95rem + 0.75vw, 1.6rem);
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: rgba(245, 242, 234, 0.82);
  text-wrap: pretty;
}

/* Centred under the headline, and set a long way back from it. The headline
   and its line are left-aligned against the gutter; the button is not part of
   that column, it is the thing you do about it, so it sits on the middle of
   the screen with air around it. margin-inline on a fit-content box, because
   the button is inline-flex and text-align would not move it. */
.hero .btn {
  display: flex;
  width: fit-content;
  margin: clamp(4rem, 11vh, 8rem) auto 0;
}

.hero__scroll {
  position: absolute;
  left: 50%;
  bottom: 1.75rem;
  transform: translateX(-50%);
  width: 24px;
  height: 44px;
  display: grid;
  place-items: center;
}
.hero__scroll-line {
  width: 1.5px;
  height: 40px;
  background: linear-gradient(to bottom, transparent, rgba(245, 242, 234, 0.85));
  animation: scroll-hint 2.2s var(--ease) infinite;
}
@keyframes scroll-hint {
  0%   { transform: scaleY(0.3); transform-origin: top;    opacity: 0; }
  40%  { transform: scaleY(1);   transform-origin: top;    opacity: 1; }
  70%  { transform: scaleY(1);   transform-origin: bottom; opacity: 1; }
  100% { transform: scaleY(0);   transform-origin: bottom; opacity: 0; }
}

/* ----------------------------- solution -------------------------------- */

/* Clipped so the drone can never ride up over the hero video on its way in —
   it only exists once it has crossed into this section. Clipping here rather
   than on .scare keeps the horizontal clip at the section's own full width,
   so the animals still travel off-screen exactly as before. */
.section--solution { background: var(--bone); overflow: hidden; }

/* the animals we keep out */

/* Four rows of two. It began as a 4 x 2 block, which read as one solid group
   but sat too close under the hero; the tall stack is what puts air between
   the video and the claim, and eight into four rows is the only split that
   comes out even.

   Two across at every width, and the tiles are half the block rather than a
   size of their own — so a third can never fit, because there is never room
   for one. It used to be the other way round: a fixed tile in a 470px block,
   sized so that two fit and three did not. That only held at the two ends it
   was checked at. The tile grew on viewport width and the gap grew slower, so
   between 620 and 800px three of them came out narrow enough to fit and the
   block quietly went 3 / 3 / 2 — on a tablet, which is exactly the width
   nobody had open.

   Deriving the tile from the block instead of hoping the arithmetic lands
   also retires the phone breakpoint that used to cover the bottom end: every
   size below is one continuous curve now, with no width where the layout
   changes its mind. */
.animals {
  --col-gap: clamp(0.6rem, 2.2vw, 2rem);

  /* The disc, and the one measurement everything else is taken from — the
     block is sized off it rather than the other way round.

     It has to answer to the screen's height, not just its width. Eight tiles
     in four rows is a tall block: at 180px discs it stands 1011px, and on a
     laptop 898px high there is no scroll position where all eight are on
     screen at once. You lose the top row under the nav before the bottom row
     arrives, and by then the row is already fleeing — so the one thing this
     section exists to show you, it never shows you.

     12.5svh keeps four rows inside the viewport with the nav taken off and
     air at both ends: 112px discs on that laptop, a 740px block, and the row
     stands whole and still for about 60px of scrolling before it breaks.
     Full 180px discs from around 1440px of height up.

     Shrinking it further does not buy more of that window, which is worth
     knowing before anyone tries: .scare is only as tall as this block, and
     main.js scrubs the section against its own height, so a shorter block
     also shortens the band it is timed against and the two cancel. The size
     decides whether the window exists at all; main.js's 0.60 decides how wide
     it is. 12.5 rather than 13.5 buys margin on shorter screens instead —
     at 768px of height it still clears by 33px where 13.5 clears by 3.

     svh rather than vh because a phone's toolbars are the difference between
     fitting and not.

     34vw keeps it inside the gutter on a narrow screen, and the 72px floor
     stops a short landscape window shrinking it to nothing — at that size
     nothing fits anyway, and small is better than absurd. */
  --icon: max(72px, min(180px, 34vw, 12.5vh));
  --icon: max(72px, min(180px, 34vw, 12.5svh));

  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: clamp(1.5rem, 3.5vw, 2.75rem) var(--col-gap);
  /* Two tiles and the gap between them, where a tile is the disc plus the air
     it sits in. Under this the block takes what the gutter leaves it. */
  max-width: calc(var(--icon) / 0.88 * 2 + var(--col-gap));
  margin: 0 auto clamp(3.5rem, 8vw, 6rem);
}

/* Bigger than they were at four across. Two to a row leaves the block a
   narrow column down the middle of a wide page, and at the old size it read
   as small rather than as deliberate. */
.animal {
  flex: 0 0 calc((100% - var(--col-gap)) / 2);
  display: grid;
  justify-items: center;
  gap: 0.85rem;
  color: var(--green-deep);
}

/* The disc is the size; the tile around it is 1/0.88 of that, which is where
   the air between the columns comes from. Capped at the tile as well, for the
   window too narrow for even --icon's own 34vw to have saved it. */
.animal__icon {
  display: grid;
  place-items: center;
  width: min(var(--icon), 100%);
  aspect-ratio: 1;
  border-radius: 50%;
  background: rgba(0, 130, 0, 0.09);
}
.animal__icon svg { width: 64%; height: 64%; }

.animal__name {
  font-size: clamp(0.8125rem, 1.1vw, 1rem);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text-soft);
  text-align: center;
}

/* --- the scare: the animals are driven off, the drone takes the field --- */

/* Scrubbed by scroll position rather than run on a timer, so scrolling back
   up rewinds it exactly. main.js writes --p (0 = untouched, 1 = complete) on
   each participant every frame; there are deliberately no transitions here,
   because the scroll position IS the timeline.

   The stage keeps the animals in flow while they leave, so its height never
   collapses and the drone lands exactly where the problem used to be. */
/* The margin is the room the aircraft holds station into. --hold slides it
   down the page as the section rides up (see HOLD in drone3d.js), and without
   clearance here the end of the sequence lands on top of the claim — a rotor
   through the middle of the heading.

   What it has to cover is the hold, less however far the frame's bottom edge
   already sits above the bottom of the stage. On a wide screen the frame is
   nearly as tall as the row of animals, so that second term is small and the
   margin carries almost the whole hold. Checked at 800, 1000 and 1200 tall.
   Raise HOLD_RATE or widen HOLD and this has to grow with them. */
.scare {
  position: relative;
  margin-bottom: max(6rem, calc(24vh + 120px));
}

/* On a phone that second term is not small, and this is where the wide rule
   goes badly wrong. The frame keeps its aspect, so on a narrow screen it is
   short — 208px inside a 662px row of animals — and its bottom edge already
   sits 227px above the stage. Of a 332px hold, only about 105px still needs
   finding, where the rule above was reserving 315. The other 210 was simply a
   hole in the page, and on a screen 812px tall that is a quarter of it.

   After .scare rather than in the block of phone rules further up, because it
   has to beat it on source order — same specificity, later wins. */
@media (max-width: 620px) {
  .scare { margin-bottom: max(3rem, calc(10vh + 90px)); }
}

/* --dir (-1 left, 1 right) is set per tile in main.js from its laid-out
   position, so it survives reflow and any amount of scrubbing. They fade as
   they travel, so a tile is thinning out by the time it reaches the edge of
   the screen and is never cut off hard — clipping the flight to the stage
   instead was tried and reads far worse: the discs get sliced mid-canvas. */
/* Everything a tile does on its way out is a number main.js gives it, so no
   two leave alike: --dash how far it goes, --lift whether it rises or keeps
   low, --spin how hard it tilts. Birds go up and turn as they climb; the boar,
   the deer and the rabbit stay on the ground and bolt.

   The travel accelerates rather than running at one rate: a startled animal
   does not set off at full speed, it breaks and then goes. Eight tiles sliding
   out evenly is what made this read as furniture moving rather than as a
   scare. Part linear, part squared, though — on pure p squared they dawdle in
   the middle long enough for the drone to come down on top of them, which is
   the fault this section has had twice already. */
.animal {
  --p: 0;
  --dash: 62;   /* vw travelled at full flight */
  --lift: 0;    /* vh risen (negative) or dropped on the way */
  --spin: 8;    /* degrees of tilt */
  opacity: calc(1 - var(--p));
  transform:
    translateX(calc(var(--p) * (0.42 + 0.58 * var(--p)) * var(--dir, 1) * var(--dash) * 1vw))
    translateY(calc(var(--p) * (0.42 + 0.58 * var(--p)) * var(--lift) * 1vh))
    rotate(calc(var(--p) * var(--dir, 1) * var(--spin) * 1deg))
    scale(calc(1 - var(--p) * 0.22));
  will-change: transform, opacity;
}

/* The drone is the whole answer now, so it sits dead centre of the stage the
   animals just vacated — and it is given the full width of the screen to do
   it in, not the text column's. The stage lives inside .wrap, so left to
   itself the aircraft would be boxed in by the page gutters; on a phone that
   was the difference between 300px and the whole 375. Broken out here rather
   than by moving the markup, so the stage still overlays the animals exactly.
   .section--solution clips, so the 100vw cannot put a scrollbar on the page. */
.scare__answer {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 100vw;
  transform: translateX(-50%);
  display: grid;
  place-items: center;
  pointer-events: none;
}

/* The drone drops in from above the page. It leads the scrub rather than
   following it — the animals only break once it is already on its way down,
   so the arrival reads as the cause and the scattering as the effect. */
.scare__drone {
  --p: 0;
  /* Station-keeping, in px, written by drone3d.js — see HOLD there. The
     aircraft gives back a part of the page's scroll so it drifts up the
     screen slower than everything around it, which is both what buys the
     sequence its time and what a hovering drone would actually do. Zero
     without the script, so the still is unaffected. */
  --hold: 0px;
  display: block;
  /* the whole width it is given, up to a ceiling so it does not become the
     page on a very wide monitor */
  width: min(100%, 1000px);
  opacity: var(--p);
  /* smaller on the way down, full size once it owns the space — it reads as
     distance, and keeps its footprint off the tiles still clearing out */
  transform: translateY(calc((var(--p) - 1) * 34vh + var(--hold)))
             scale(calc(0.72 + 0.28 * var(--p)));
  will-change: transform, opacity;
}
.scare__drone img {
  display: block;
  width: 100%;
  height: auto;
  /* the idle hold lives on the image, so it never fights the descent that
     the scrub applies to the wrapper */
  animation: drone-hold 2.8s var(--ease) infinite alternate;
}
@keyframes drone-hold {
  from { transform: translateY(-6px); }
  to   { transform: translateY(6px); }
}

/* The aircraft in three dimensions. The canvas is hidden until drone3d.js has
   drawn a frame and put .is-3d on the wrapper, so every way this can fail —
   no WebGL, a context loss on load, the module not reaching the page — leaves
   the still showing rather than a gap. In 3D the hold and the descent are both
   the render loop's, so the CSS hold goes with the image.

   Taller than the still's 900x441. The aircraft banks, and at the end of the
   section the camera is underneath it and close, where the hull stands well
   clear of the rotor plane — 441 cropped the top of it. The box is centred
   and absolutely positioned, so the extra height costs no layout. */
.scare__canvas {
  display: none;
  width: 100%;
  aspect-ratio: 900 / 500;
}
.scare__drone.is-3d img { display: none; }
.scare__drone.is-3d .scare__canvas { display: block; }

/* The name arrives as the answer to the row of pests: the two halves come in
   hard from opposite edges and meet in the middle. --p is set on the parent
   and inherits down to both halves. */
/* the claim is the payoff, so it rides the same scrub as the drone rather
   than revealing on its own scroll position */
.scare__claim {
  --p: 0;
  opacity: var(--p);
  transform: translateY(calc((1 - var(--p)) * 22px));
}

/* claim + gains, centred like the wireframe */

.claim { text-align: center; margin-bottom: clamp(3.5rem, 8vw, 6rem); }
/* The payoff of the whole sequence, and the only thing left standing once the
   row has scattered — so it carries the section on its own and is set to. */
.claim .h2 {
  max-width: 18ch;
  margin-inline: auto;
  font-size: clamp(2.6rem, 1.4rem + 5.2vw, 6rem);
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.gains { text-align: center; }
/* No label above these — the four of them read as the gains on their own, and
   the claim right above is the introduction. */
.gains__list {
  margin-block: 0 3rem;
  display: grid;
  gap: 0.5rem;
}
.gains__list li {
  font-size: clamp(1.4rem, 1.05rem + 1.6vw, 2.35rem);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.25;
  color: var(--green-deep);
}

/* Who stands behind it, kept deliberately quiet: it is a footnote to the
   button above, not a second trust section — that one has its own place
   further down the page. */
.backed__label {
  /* set well apart from the button above it: this is a separate thought, not
     a caption to the call to action */
  margin-top: clamp(5.5rem, 11vw, 9rem);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-soft);
}

.backed {
  margin-top: 1.75rem;
  /* and clear of whatever the section runs into next */
  margin-bottom: clamp(3.5rem, 8vw, 6.5rem);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(1.75rem, 5vw, 3.5rem);
}
/* One height for all three, so a wide wordmark cannot swamp a tall crest, and
   one colour: three house palettes side by side pull against each other and
   against the page. brightness(0) takes every visible pixel to black and
   leaves the transparency alone, so what is left is the silhouette. */
.backed img {
  height: clamp(34px, 4.4vw, 52px);
  width: auto;
  filter: brightness(0);
}

/* --------------------------- how it works ------------------------------ */

/* No band above the film and none below: the section is the film, edge to
   edge, with the heading, the step and the bars laid over it. The page cuts
   straight from paper to footage rather than going through a slab of green
   that was framing it like a photograph. */
.section--how { position: relative; background: var(--ink); }

/* The pinned, scrubbed treatment is an enhancement, and this section is now
   arranged so that it is the only thing that is.

   By default — no JavaScript, JavaScript that threw, a browser that will not
   pin, a phone — the section is an ordinary column: heading, film at its own
   shape, then the four steps one under another, all of them readable. The
   scrubbed version takes over only once main.js has confirmed it can drive
   it, by putting .js-scrub on <html>.

   It used to be the other way round. Pinning was unconditional and all four
   captions shared one grid cell at opacity 0, lit one at a time by a scrubbed
   value; when that value never arrived they sat on top of each other and the
   section could not be read at all. A fallback that only exists when the
   clever path works is not a fallback. */

/* Everything the section has to say sits over the film, in its own layer.
   Without this the heading, the captions and the rail are all at z-index
   auto and paint in DOM order — which puts them *under* the video, because
   it is absolutely positioned and comes after them in the markup. Inert in
   the column, where none of them is positioned. */
.how__heading,
.how__captions,
.how__rail { z-index: 2; }

.how { position: relative; }
.how__pin { position: relative; }
.how__stage { position: relative; overflow: hidden; }

/* Both washes exist to hold white words over moving footage. In the column
   there is nothing to hold them over — the section's own green is behind
   them — and a gradient across the middle of the film only obscures it. */
.how__stage::before { content: none; }
.how__scrim { display: none; }

.how__heading {
  position: static;
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding: 0 var(--gut) clamp(1.5rem, 4vw, 2.25rem);
  color: var(--bone);
}

.how__video {
  position: relative;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
}

/* Portrait: a 16:9 frame cropped to a tall viewport throws most of the
   picture away, and this film is a diagram — the parts cropped off are the
   parts that explain it. Show all of it and let it letterbox on the
   section's own green. */
@media (orientation: portrait) {
  /* Both versions of the section, pinned or not. The pinned one needs saying
     explicitly, because html.js-scrub .how__video outweighs a bare class and
     would otherwise put the crop back on exactly the device that cannot
     afford it. Letterboxed on the section's own green reads as a framed
     film; cropped to a portrait phone reads as a mistake, because the parts
     it cuts off are the parts of the diagram that explain it. */
  .how__video,
  html.js-scrub .how__video { object-fit: contain; background: var(--ink); }
}

.how__captions {
  position: static;
  display: grid;
  gap: clamp(1.75rem, 5vw, 2.75rem);
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding: clamp(2rem, 6vw, 3.25rem) var(--gut) clamp(3rem, 9vw, 5.5rem);
}
.how__caption {
  width: 100%;
  padding: 0;
  color: var(--bone);
  opacity: 1;
}

.how__rail { display: none; }

/* ---- the scrubbed version, only where something can drive it ------------ */

html.js-scrub .how {
  height: 380vh;
  height: 380svh;
}

html.js-scrub .how__pin {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
}

/* fills the pin outright, as the whole of the section */
html.js-scrub .how__stage { height: 100%; }

html.js-scrub .how__stage::before {
  /* Its own wash, the same shape as the one carrying the caption at the foot:
     hard at the very edge and gone almost at once, so it holds the words
     without reaching the part of the picture worth looking at. */
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 1;
  height: 38%;
  background: linear-gradient(to bottom, rgba(0, 40, 0, 0.82) 0%,
                              rgba(0, 40, 0, 0.4) 35%, transparent 100%);
}
/* Just enough to carry the caption and the bars. It reaches the ink exactly
   at the very bottom, so the film meets the paper below it on a solid edge
   rather than a half-transparent one, and eases out of it slowly enough that
   there is no band of its own: half gone by a fifth of the way up, gone by
   just over a third. The picture above that is untouched. */
html.js-scrub .how__scrim {
  display: block;
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to top,
                              var(--ink) 0%,
                              rgba(0, 40, 0, 0.94) 6%,
                              rgba(0, 40, 0, 0.72) 12%,
                              rgba(0, 40, 0, 0.42) 20%,
                              rgba(0, 40, 0, 0.16) 28%,
                              transparent 38%);
}

/* Centred on the same --wrap column as the captions below, so the title's left
   edge lands on the step block's. Pinned absolute, its margin-inline:auto is
   dropped by the browser (an over-constrained left:0/right:0 box keeps its auto
   margins at 0 and stays hard left); the captions escape that only because each
   is an in-flow child. left:50% + translateX(-50%) centres the box outright, so
   both sit on the one column at every width above --wrap. */
html.js-scrub .how__heading {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  padding: clamp(5rem, 13vh, 8rem) var(--gut) 0;
}

html.js-scrub .how__video {
  position: absolute;
  inset: 0;
  height: 100%;
  aspect-ratio: auto;
  object-fit: cover;
  background: none;
}

/* The labels sit over the clips rather than inside them, so a caption's
   legibility owes nothing to how far through a cross-fade its clip happens to
   be. All four occupy the one cell; main.js lights whichever step is on. */
html.js-scrub .how__captions {
  position: absolute;
  inset: auto 0 0 0;
  gap: 0;
  padding: 0;
}
html.js-scrub .how__caption {
  grid-area: 1 / 1;
  max-width: var(--wrap);
  margin-inline: auto;
  /* clears the bars, which now sit on the film below it */
  padding: 0 var(--gut) clamp(5rem, 11vh, 7.5rem);
  opacity: 0;
  transition: opacity 0.2s var(--ease);
}
html.js-scrub .how__caption.is-on { opacity: 1; }

html.js-scrub .how__rail { display: flex; }

/* where you are in the four, and that there is more to come — on the film's
   own foot now, rather than on a band underneath it */
.how__rail {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  gap: 0.5rem;
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding: 0 var(--gut) clamp(1.25rem, 3.5vh, 2.25rem);
}
.how__tick {
  flex: 1;
  /* a 3px bar is nobody's idea of a click target — the padding gives it one
     without changing anything you can see */
  padding: 0.8rem 0;
  border: 0;
  background: none;
  font: inherit;
  /* the step's name is left in for screen readers; the bar is the visual */
  font-size: 0;
  color: transparent;
  cursor: pointer;
  -webkit-appearance: none;
          appearance: none;
}
.how__tick::before,
.how__tick::after { content: ""; display: block; height: 3px; }

.how__tick::before {
  background: rgba(245, 242, 234, 0.22);
  transition: background-color 0.2s var(--ease);
}
.how__tick:hover::before { background: rgba(245, 242, 234, 0.45); }

/* --f is 0 to 1 across each tick: the bar under the step that is on fills with
   its clip's playback and empties again on every loop, so it reads as the
   position in the video rather than in the scroll */
.how__tick::after {
  margin-top: -3px;
  background: var(--lime);
  transform: scaleX(var(--f, 0));
  transform-origin: left;
}

.how__num {
  display: block;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--lime);
  margin-bottom: 0.7rem;
}

.how__title {
  text-shadow: 0 2px 14px rgba(0, 25, 0, 0.6);
  margin: 0;
  font-size: clamp(1.6rem, 1.15rem + 2vw, 3rem);
  font-weight: 600;
  letter-spacing: -0.025em;
  line-height: 1.1;
}

/* The step's own line is the smallest type on the page and, in the pinned
   version, it is over moving footage — a bright frame can leave it hard to
   read however well judged the scrim is. A slight panel fixes that where it
   matters and is barely visible in the column, where it sits on the
   section's own near-black green anyway. */
.how__text {
  padding: 0.6rem 0.9rem;
  border-radius: var(--r-sm);
  background: rgba(0, 0, 0, 0.38);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  margin-top: 0.75rem;
  max-width: 46ch;
  font-size: clamp(1rem, 0.95rem + 0.35vw, 1.2rem);
  line-height: 1.45;
  color: rgba(245, 242, 234, 0.82);
  text-wrap: pretty;
}

/* ------------------------------ trust ---------------------------------- */

.section--trust { background: var(--bone-dim); }
/* it opens the section now, so it needs the room a heading gets */
.section--trust > .wrap > .h2 { margin-bottom: clamp(3rem, 6vw, 4.5rem); }

/* --- horizontal rails: scroll, swipe or drag ---------------------------- */

.rail {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--gut);
  padding-block: 0.5rem;
  margin-bottom: clamp(3rem, 6vw, 4.5rem);
  /* The fade tells you there is more to either side without needing a
     scrollbar, which is hidden below. It lands exactly on the gutter, so at
     rest the first and last item are fully opaque rather than half-faded. */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--gut),
                        #000 calc(100% - var(--gut)), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 var(--gut),
                        #000 calc(100% - var(--gut)), transparent 100%);
  scrollbar-width: none;
}
.rail::-webkit-scrollbar { display: none; }

.rail__track {
  display: flex;
  align-items: center;
  gap: clamp(2.5rem, 6vw, 5rem);
  width: max-content;
  padding-inline: var(--gut);
  /* Centred when there is room to spare — on a wide screen there are not
     enough cards to fill the width and a left-anchored row reads as though
     the rest fell off. The margins collapse to nothing the moment the track
     is wider than the rail, so the scrolling case is untouched and nothing
     ends up stranded off the left edge. */
  margin-inline: auto;
}
.rail__item { flex: 0 0 auto; scroll-snap-align: center; }

/* Every logo is exported onto a canvas of the same height, with the mark
   scaled inside it by aspect ratio, so one CSS height gives them all a
   comparable optical weight — a wide wordmark does not swamp a square badge. */
.logo__img {
  display: block;
  /* tall enough that the busy marks — the chamber badge, Prince de Bretagne —
     stay legible, which is what sets the floor here */
  height: clamp(78px, 10.5vw, 136px);
  width: auto;
}

/* customer quotes, as cards on the same rail */

.rail--quotes .rail__track { align-items: stretch; gap: clamp(1rem, 2vw, 1.75rem); }
.rail--quotes .rail__item { width: clamp(270px, 76vw, 420px); }

.quote {
  height: 100%;
  margin: 0;
  display: grid;
  gap: 1.5rem;
  align-content: space-between;
  padding: clamp(1.6rem, 3vw, 2.25rem);
  background: var(--bone);
  border-radius: var(--r-lg);
  box-shadow: 0 18px 40px -30px rgba(10, 21, 18, 0.5);
}

.quote blockquote {
  margin: 0;
  font-size: clamp(1.0625rem, 0.98rem + 0.45vw, 1.3rem);
  line-height: 1.45;
  letter-spacing: -0.015em;
  text-wrap: pretty;
}
/* the quotation marks are decoration, so they hang outside the measure
   rather than indenting the first line */
.quote blockquote::before { content: '\201C'; }
.quote blockquote::after  { content: '\201D'; }

.quote__by {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: 0.9375rem;
  font-weight: 600;
  /* the deeper green, not --green: at this size the lighter one lands at
     4.5:1 against the card and under it against the section behind */
  color: var(--green-deep);
}

/* The faces behind the words. Small and round and beside the name rather than
   above the quote — they are there to say a real person said this, not to be
   looked at. Shipped at 160px for a 40px slot, which is what a 2x screen
   wants and still only 15 KB each.

   alt is empty on purpose: the name is right there in the caption, so a
   screen reader announcing the photo as well would only say it twice. */
.quote__face {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--bone-dim);
}

/* Stands in until a photo arrives, so a quote without one still lines up with
   the others instead of having its name sit 40px to the left of theirs. */

/* Closing CTA: sits last, below the use-case cards, set off by the same
   vertical rhythm the cards get above them so the block reads as intentional. */
.trust__cta { margin-top: clamp(3rem, 7vw, 5rem); display: grid; gap: 2rem; justify-items: center; text-align: center; }
.trust__cta .h2 { max-width: none; }

/* --------------------- the problem / SEO prose ------------------------- */

.section--problem { background: var(--bone); }
.section--problem > .wrap > .h2 { margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }

/* Real, crawlable body copy — the keyword-bearing text the page was thin on.
   Held to a comfortable measure and set well clear of the case below it. */
.prose {
  display: grid;
  gap: 1.25rem;
  max-width: 66ch;
  font-size: clamp(1.02rem, 0.97rem + 0.3vw, 1.18rem);
  line-height: 1.7;
  color: var(--text);
  margin-bottom: clamp(2.5rem, 6vw, 4rem);
}

/* ------------------- expandable use-case cards ------------------------- */

/* Native <details>: the teaser is the <summary>, always in the DOM and
   indexable, and the whole thing opens with no JavaScript. */
.case {
  background: var(--bone);
  border: 1px solid rgba(0, 40, 0, 0.12);
  border-radius: var(--r-lg);
  box-shadow: 0 18px 40px -30px rgba(10, 21, 18, 0.5);
}
.case + .case { margin-top: clamp(1rem, 2vw, 1.5rem); }

.case__summary {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: clamp(0.75rem, 2vw, 1.5rem);
  padding: clamp(1.4rem, 3vw, 2.25rem);
  cursor: pointer;
  font-size: clamp(1.05rem, 0.98rem + 0.45vw, 1.3rem);
  line-height: 1.5;
  letter-spacing: -0.015em;
  text-wrap: pretty;
  /* strip the native disclosure triangle — the CSS chevron below stands in */
  list-style: none;
}
.case__summary::-webkit-details-marker { display: none; }
.case__summary strong { color: var(--green-deep); }

/* A chevron drawn from two borders, pointing down at rest and flipping up when
   the card is open. Decorative, so no text alternative is needed. */
.case__summary::after {
  content: "";
  flex: 0 0 auto;
  width: 0.66em;
  height: 0.66em;
  margin-top: 0.4em;
  border-right: 2px solid var(--green-deep);
  border-bottom: 2px solid var(--green-deep);
  transform: rotate(45deg);
  transition: transform 0.25s var(--ease);
}
.case[open] .case__summary::after { transform: rotate(-135deg); }

.case__body {
  display: grid;
  gap: 1.15rem;
  padding: 0 clamp(1.4rem, 3vw, 2.25rem) clamp(1.4rem, 3vw, 2.25rem);
}
.case__body p { max-width: 68ch; }
.case__body strong { color: var(--green-deep); }
.case__cite { font-size: 0.85rem; color: var(--text-soft); }

/* Both named use-cases grouped in one block, sitting between the media strip
   and the closing CTA. Same <details> cards as above; the label sets them off
   as a small section. */
.cases { margin-top: clamp(3rem, 7vw, 5rem); }
.cases__label {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-soft);
  text-align: center;
  margin-bottom: clamp(1.25rem, 3vw, 2rem);
}

/* --------------------------- in the media ------------------------------ */

/* Named-outlet link pills, not a logo strip — the repo has no logo assets for
   these outlets, so tidy buttons carrying the outlet name it is. Each opens
   the real article in a new tab and shows an external-link cue on the right. */
.media { margin-top: clamp(3rem, 6vw, 4.5rem); text-align: center; }
.media__label {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.media__list {
  margin-top: 1.5rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(0.7rem, 2vw, 1.1rem);
}
.media__link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.62rem 1.15rem;
  border: 1px solid rgba(0, 40, 0, 0.16);
  border-radius: var(--r-pill);
  background: var(--bone);
  font-weight: 600;
  font-size: clamp(0.92rem, 0.88rem + 0.2vw, 1.05rem);
  letter-spacing: -0.01em;
  color: var(--text-soft);
  text-decoration: none;
  box-shadow: 0 10px 24px -20px rgba(10, 21, 18, 0.55);
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease),
              background-color 0.2s var(--ease), transform 0.2s var(--ease),
              box-shadow 0.2s var(--ease);
}
.media__link:hover {
  color: var(--green-deep);
  border-color: rgba(0, 85, 0, 0.4);
  background: #fff;
  transform: translateY(-1px);
  box-shadow: 0 16px 30px -22px rgba(10, 21, 18, 0.6);
}
.media__link:focus-visible {
  outline: 2px solid var(--green-deep);
  outline-offset: 2px;
}
/* Decorative up-right arrow — the link already says "opens in a new tab" via
   target/rel; hidden from assistive tech so it isn't read as content. */
.media__cue {
  font-size: 0.9em;
  line-height: 1;
  color: var(--green-deep);
  transition: transform 0.2s var(--ease);
}
.media__link:hover .media__cue { transform: translate(2px, -2px); }

/* The strip now lives under the "Join the team" CTA, inside the dark team
   section. The bone pills read fine on green-deep, but the label needs light
   ink, and the block drops its standalone top margin — the team__claim grid
   gap already sets the rhythm under the button, so the margin would double it. */
.section--team .media { margin-top: 0; }
.section--team .media__label { color: rgba(245, 242, 234, 0.7); }

/* ------------------------------- faq ----------------------------------- */

.section--faq { background: var(--bone-dim); }
/* The FAQ title and the accordion box are centred on the page like the rest of
   the centred section system; only the question/answer text inside each card
   reads left, with the disclosure marker pinned to the right of each question. */
.section--faq > .wrap > .h2 { margin-bottom: clamp(1.5rem, 4vw, 2.5rem); }

.faq { max-width: 760px; margin-inline: auto; text-align: left; }
.faq__item { border-top: 1px solid rgba(0, 40, 0, 0.14); }
.faq__item:last-child { border-bottom: 1px solid rgba(0, 40, 0, 0.14); }

.faq__q {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  padding: clamp(1.05rem, 2.4vw, 1.4rem) 0;
  cursor: pointer;
  font-weight: 600;
  font-size: clamp(1.02rem, 0.97rem + 0.4vw, 1.25rem);
  letter-spacing: -0.015em;
  line-height: 1.4;
  list-style: none;
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q::after {
  content: "";
  flex: 0 0 auto;
  width: 0.6em;
  height: 0.6em;
  margin-top: 0.3em;
  border-right: 2px solid var(--green-deep);
  border-bottom: 2px solid var(--green-deep);
  transform: rotate(45deg);
  transition: transform 0.25s var(--ease);
}
.faq__item[open] .faq__q::after { transform: rotate(-135deg); }

.faq__a {
  padding: 0 0 clamp(1.1rem, 2.4vw, 1.5rem);
  max-width: 68ch;
  color: var(--text-soft);
  line-height: 1.65;
}

/* ------------------------- pricing simulator --------------------------- */

/* Sits directly under the trust band. Trust is --bone-dim, so this returns to
   the plain --bone to keep the beige alternation the rest of the page uses. */
.section--pricing { background: var(--bone); }
.section--pricing > .wrap { max-width: 920px; }

.section--pricing .h2 {
  max-width: none;
  text-align: center;
  margin: 0 auto;
}
.pricing__lead {
  max-width: 56ch;
  margin: 1rem auto 0;
  text-align: center;
  color: var(--text-soft);
  font-size: clamp(1rem, 0.96rem + 0.3vw, 1.15rem);
  line-height: 1.6;
}

/* The simulator itself — one rounded card in the house style. */
.sim {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  background: var(--bone-dim);
  border-radius: var(--r-lg);
  padding: clamp(1.5rem, 4vw, 3rem);
  box-shadow: 0 1px 0 rgba(0, 40, 0, 0.05), 0 30px 60px -40px rgba(0, 40, 0, 0.35);
}

/* The live figure, held in a soft pill above the controls. */
.sim__readout {
  text-align: center;
  padding-bottom: clamp(1.5rem, 3.5vw, 2.25rem);
  margin-bottom: clamp(1.5rem, 3.5vw, 2.25rem);
  border-bottom: 1px solid rgba(0, 40, 0, 0.12);
}
/* Three typographic tiers so the number leads: a small muted "Begins at"
   prefix above, the dominant figure, and a smaller unit beside it. Stacking
   the prefix keeps the hierarchy intact on phone as well as desktop. */
.sim__price {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  font-weight: 700;
  line-height: 1.05;
  color: var(--green-deep);
}
.sim__price-pre {
  font-size: clamp(0.85rem, 0.8rem + 0.4vw, 1.05rem);
  font-weight: 500;
  letter-spacing: 0.01em;
  opacity: 0.65;
}
.sim__price-fig {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 0.35rem;
  font-variant-numeric: tabular-nums;
}
.sim__price-num {
  font-size: clamp(2.8rem, 1.8rem + 4.2vw, 4.2rem);
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
}
.sim__price-unit {
  font-size: clamp(1rem, 0.85rem + 0.6vw, 1.4rem);
  font-weight: 600;
  opacity: 0.78;
}
/* Small plain-language note under a control (the neighbour explainer). */
.sim__hint {
  margin: -0.05rem 0 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-soft);
}
/* One row per slider: title + live value on top, the styled rail below. */
.sim__controls { display: grid; gap: clamp(1.5rem, 3.5vw, 2.25rem); }
.sim__row { display: grid; gap: 0.7rem; }
.sim__row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.sim__label { font-weight: 600; font-size: 1.02rem; }
.sim__value {
  font-weight: 700;
  color: var(--green-deep);
  font-variant-numeric: tabular-nums;
}

/* Range inputs, restyled off the browser default into a green-filled pill
   rail with a solid round thumb. --fill (0–100%, set from JS) is how far the
   green runs. Comfortable 44px touch target via an oversized invisible track. */
.sim__range {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 44px;
  margin: 0;
  background: transparent;
  cursor: pointer;
}
.sim__range:focus { outline: none; }
.sim__range:focus-visible { outline: 3px solid var(--lime); outline-offset: 4px; border-radius: var(--r-pill); }

/* WebKit / Blink */
.sim__range::-webkit-slider-runnable-track {
  height: 8px;
  border-radius: var(--r-pill);
  background:
    linear-gradient(var(--green), var(--green)) 0 / var(--fill, 0%) 100% no-repeat,
    rgba(0, 40, 0, 0.14);
}
.sim__range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 26px;
  height: 26px;
  margin-top: -9px;             /* centre the 26px thumb on the 8px track */
  border-radius: 50%;
  background: var(--green-deep);
  border: 3px solid var(--bone);
  box-shadow: 0 2px 6px rgba(0, 40, 0, 0.3);
  transition: transform 0.15s var(--ease);
}
.sim__range:active::-webkit-slider-thumb { transform: scale(1.12); }

/* Firefox */
.sim__range::-moz-range-track {
  height: 8px;
  border-radius: var(--r-pill);
  background: rgba(0, 40, 0, 0.14);
}
.sim__range::-moz-range-progress {
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--green);
}
.sim__range::-moz-range-thumb {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--green-deep);
  border: 3px solid var(--bone);
  box-shadow: 0 2px 6px rgba(0, 40, 0, 0.3);
}

/* Small tick labels + icons sitting under the crop rail, marking the four
   stops. A native range thumb (26px wide) is centred half its width in from
   each track end at the extremes, so its CENTRE travels from 13px to
   (100% − 13px), NOT from 0 to 100%. The stops are absolutely positioned so
   their centres land exactly on that thumb travel: the first sits under the
   thumb at min, the last under the thumb at max, and the middle two at the
   thirds. A plain 4-column / space-between row would miss the ends by 13px. */
.sim__scale {
  position: relative;
  height: 3.25rem;
  margin-top: 0.55rem;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  color: var(--text-soft);
}
.sim__stop {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  text-align: center;
  line-height: 1.2;
  width: max-content;
  max-width: 5.5rem;
  overflow-wrap: anywhere;
}
/* Thumb radius is 13px; centres at 13px, +1/3, +2/3 and 100%−13px of the rail. */
.sim__stop:nth-child(1) { left: 13px; }
.sim__stop:nth-child(2) { left: calc(13px + (100% - 26px) / 3); }
.sim__stop:nth-child(3) { left: calc(13px + (100% - 26px) * 2 / 3); }
.sim__stop:nth-child(4) { left: calc(100% - 13px); }
.sim__stop-ic {
  width: 1.2rem;
  height: 1.2rem;
  flex: none;
  color: var(--green-deep);
  opacity: 0.9;
}

@media (max-width: 30rem) {
  .sim__scale { font-size: 0.62rem; height: 3rem; }
  .sim__stop { gap: 0.22rem; max-width: 4.75rem; }
  .sim__stop-ic { width: 1rem; height: 1rem; }
}

/* The on-demand branch: the "very-high-value" stop has no computed figure, so
   the period + size rows fold away and this note takes their place. */
.sim__ondemand {
  display: none;
  margin-top: clamp(1.25rem, 3vw, 1.75rem);
  text-align: center;
  color: var(--text-soft);
  line-height: 1.6;
}
.sim__foot {
  margin-top: clamp(1.75rem, 4vw, 2.5rem);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.9rem;
  text-align: center;
}
.sim__note { font-size: 0.85rem; color: var(--text-soft); max-width: 52ch; }

/* On-demand state: hide the period + size controls and the €/ha figure line,
   reveal the on-demand note. */
.section--pricing.is-ondemand [data-row-period],
.section--pricing.is-ondemand [data-row-size] { display: none; }
.section--pricing.is-ondemand .sim__ondemand { display: block; }
/* The "starting price" footnote is about a computed figure; on request there
   is none, so it would contradict the panel — the on-demand note above stands
   in its place. */
.section--pricing.is-ondemand .sim__note { display: none; }
.section--pricing.is-ondemand .sim__price { font-size: clamp(2rem, 1.4rem + 2.6vw, 3rem); }

/* ------------------------------- team ---------------------------------- */

.section--team { padding-top: 0; overflow: hidden; }

.team__media { margin: 0 0 clamp(3rem, 7vw, 5.5rem); }
.team__media img {
  width: 100%;
  max-height: 62vh;
  max-height: 62svh;
  object-fit: cover;
  background: linear-gradient(140deg, #24503A, #16301F);
}

.team { display: grid; gap: clamp(2.5rem, 6vw, 4rem); justify-items: center; text-align: center; }

.team__group {
  max-width: 40ch;
  font-size: clamp(1.125rem, 1rem + 0.7vw, 1.5rem);
  color: rgba(245, 242, 234, 0.82);
  line-height: 1.5;
}

/* The same step as .team above it, so the heading has equal air either side.
   The two were on different rhythms — .team's gap fell above the heading and
   this one below it, which put 64px over it and 26px under. */
.team__claim {
  display: grid;
  gap: clamp(2.5rem, 6vw, 4rem);
  justify-items: center;
  /* One measure for the heading and the line under it, so the two are the
     same width and read as one block. They were set in ch of their own very
     different font sizes, which made the heading a head wider than the
     sentence for no reason anyone could see. */
  --measure: min(100%, 46rem);
}
.team__claim .h2 { max-width: var(--measure); }

/* Between the claim and the ask, and pitched under both — it is the reason
   for them, not a second headline, so it is set at reading size rather than
   at the heading weight it had when it was a section of its own. */
.team__dream {
  max-width: var(--measure);
  font-size: clamp(1.0625rem, 1rem + 0.55vw, 1.35rem);
  line-height: 1.5;
  color: rgba(245, 242, 234, 0.82);
  text-wrap: balance;
}

/* ------------------------------ footer --------------------------------- */

.footer {
  background: var(--ink);
  color: var(--text-light);
  padding-block: clamp(2.5rem, 5vw, 3.5rem);
  font-size: 0.925rem;
}
/* Three groups — who and where, where to write, and the small print. Aligned
   to the top rather than centred now that the first of them is four lines
   tall; centred, the wordmark drifted to the middle of the address. */
.footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: clamp(2rem, 5vw, 3rem);
}
.footer__brand { display: grid; gap: 1rem; }
.wordmark--footer { color: var(--bone); }

/* Not italic, which is what a browser does with <address> left alone. */
.footer__address {
  font-style: normal;
  line-height: 1.6;
  opacity: 0.72;
}

/* The registration numbers, quieter than the address — they are there because
   they have to be, not to be read. */
.footer__ids {
  font-size: 0.85rem;
  line-height: 1.6;
  opacity: 0.55;
}

.footer__links { display: grid; gap: 0.6rem; justify-items: start; }

/* Sits below the text links rather than beside them, so it does not have to
   compete with them for the eye. The label is on the link, not the glyph —
   there is no text for a screen reader to fall back to. */
.footer__social {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  margin-top: 0.4rem;
  border-radius: var(--r-sm);
  background: rgba(245, 242, 234, 0.08);
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease);
}
.footer__social svg { width: 1.15rem; height: 1.15rem; }
.footer__social:hover { background: rgba(245, 242, 234, 0.16); }
.footer__inner a { text-decoration: none; transition: color 0.2s var(--ease); }
.footer__inner a:hover { color: var(--lime); }
/* the page you are already on stays lit, so the row says where you are */
.footer__links a[aria-current="page"] { color: var(--bone); }

/* The language switcher. Its own column rather than another line in the link
   list: it does not navigate the site, it changes which site you are on, and
   the two do not belong under one heading.

   Both languages are always spelled out, each in its own language — a visitor
   who has landed on the wrong one has to be able to recognise the way out
   without reading the one they cannot read. */
.footer__lang { display: grid; gap: 0.6rem; align-content: start; }

.footer__lang-title {
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.55;
}

.footer__lang-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 0.75rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* The one you are reading is not a link out of anywhere, so it is lit rather
   than underlined — the same treatment the current page gets in the row above,
   for the same reason. */
.footer__lang-list a[aria-current="true"] {
  color: var(--bone);
  font-weight: 600;
}

.footer__legal { font-size: 0.85rem; opacity: 0.7; }

/* ------------------------- legal / text pages -------------------------- */

.doc {
  background: var(--bone);
  padding-block: clamp(7rem, 14vh, 10rem) clamp(4rem, 9vw, 7rem);
}
/* A measure, not the full page width — this is reading matter, and the wrap
   is sized for a landing page. */
.doc__inner { max-width: 70ch; }

.doc__title {
  font-size: clamp(2.1rem, 1.4rem + 2.4vw, 3.4rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.025em;
  margin: 0 0 0.75rem;
}

.doc__meta {
  font-size: 0.9375rem;
  color: var(--text-soft);
}

.doc__lead {
  margin-top: 1.5rem;
  font-size: clamp(1.0625rem, 1rem + 0.4vw, 1.3rem);
  color: var(--text-soft);
  max-width: 46ch;
}

.doc h2 {
  margin: clamp(2.75rem, 6vw, 4rem) 0 1rem;
  font-size: clamp(1.35rem, 1.1rem + 0.9vw, 1.8rem);
  font-weight: 600;
  letter-spacing: -0.02em;
}
.doc h3 {
  margin: clamp(2rem, 4vw, 2.75rem) 0 0.75rem;
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: var(--green-deep);
}

.doc p, .doc li { line-height: 1.7; }
.doc p + p { margin-top: 1rem; }
.doc ul { margin: 1rem 0 0; padding-left: 1.25rem; }
.doc li + li { margin-top: 0.5rem; }

/* the registered-office and host blocks, which are lists set as lines */
.doc__block { line-height: 1.75; }

.doc a { color: var(--green); text-underline-offset: 0.18em; }
.doc a:hover { color: var(--green-deep); }

/* --------------------------- contact form ------------------------------ */

.form {
  margin-top: clamp(2.5rem, 6vw, 3.5rem);
  display: grid;
  gap: 1.5rem;
  max-width: 40rem;
}
/* two up where there is room, stacked where there is not */
.form__row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.5rem;
}

.form__field { display: grid; gap: 0.5rem; }
.form__label { font-size: 0.9375rem; font-weight: 600; }
.form__opt { font-weight: 400; color: var(--text-soft); }

.form__input {
  font: inherit;
  color: var(--text);
  padding: 0.85rem 1rem;
  background: rgba(255, 255, 255, 0.7);
  border: 1px solid rgba(0, 40, 0, 0.18);
  border-radius: var(--r-sm);
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.form__input:focus-visible {
  outline: none;
  border-color: var(--green);
  box-shadow: 0 0 0 3px rgba(0, 130, 0, 0.18);
}
.form__input--area { resize: vertical; min-height: 9rem; line-height: 1.6; }

/* Left, on the same line as the note, in the column the fields are in. */
.form__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.5rem;
  margin-top: 0.5rem;
}

/* The honeypot. Off the page rather than display:none, because a field that is
   not rendered at all is one a bot can learn to skip. Nothing here is reachable
   by tab or by a screen reader. */
.form__hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* What the form says back. Green when it worked, wheat when it did not — the
   failure is never a dead end, the address is always written out below. */
.form__status {
  padding: 0.9rem 1.15rem;
  margin-bottom: 1.75rem;
  border-radius: var(--r-sm);
  border-left: 3px solid var(--green);
  background: rgba(0, 130, 0, 0.08);
  color: var(--text);
  line-height: 1.5;
}
.form__status--error {
  border-left-color: var(--wheat);
  background: rgba(224, 177, 92, 0.14);
}
.form__status[hidden] { display: none; }

/* --------------------------- scroll reveal ----------------------------- */

.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}
.reveal.is-visible { opacity: 1; transform: none; }

/* ------------------------- reduced motion ------------------------------ */

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

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .reveal { opacity: 1; transform: none; }
  .hero__scroll-line { animation: none; opacity: 0.8; }

  /* the scare still tracks the scroll, it just fades rather than travels */
  .animal, .scare__drone, .scare__claim { transform: none; }
  .scare__drone img { animation: none; }
  /* Neither 3D scene starts under this, so neither canvas has anything in
     it — but say so here too rather than leave it to the scripts. The hero
     video itself keeps playing: an ambient shot of the product is not the
     kind of motion this setting is asking about, and without it the hero is
     a still with nothing to say. */
  .scare__canvas { display: none !important; }
  .hero__pov { display: none !important; }
}
