:root {
  /* The site has no light mode, but nothing ever told the browser that, so it
     assumed light and painted every native control to match: scrollbars came
     out white against the dark page. This also fixes the form controls, text
     selection and autofill styling that follow the same signal. landing.css is
     the one stylesheet every page loads, so it belongs here. */
  color-scheme: dark;

  --accent: #0daa70;
  --accent-soft: color-mix(in srgb, var(--accent) 14%, white);
  --accent-soft2: color-mix(in srgb, var(--accent) 32%, white);
  --accent-dark: color-mix(in srgb, var(--accent) 78%, black);

  --page-bg: #101215;
  --ink: #f4f2eb;
  --muted: #a7acb6;
  --muted-light: #767d89;
  --border: #292c33;
  --card-bg: #1a1c21;
  --header-bg: rgba(16, 18, 21, 0.85);
  --band-bg: #16181d;
  --cta-band-bg: #000000;
}

/* Every link in the header, most of the calls to action and half the footer
   point at a section further down the same page. A hard jump lands the reader
   somewhere with no sense of which way they travelled or how far, and on this
   page in particular it skips the whole story on the way past.
 *
 * Set on html rather than per link, so scripted scrolls inherit it too:
 * survey.js already asks for smooth by name, and now it matches the rest of the
 * site instead of being the one thing that glides.
 *
 * Written as no-preference rather than switched back off in the reduce block at
 * the bottom of this file. Same result today, but this way a reader who asked
 * for less motion is never relying on a rule 1500 lines away to undo one they
 * should not have been given, and the two cannot drift apart.
 *
 * scroll-padding-top is the sticky header's measured height, 74px, plus ten of
 * air: without it every anchor target arrives with its first line tucked under
 * the bar. It is not motion, so it sits outside the query and applies either
 * way. */
html { scroll-padding-top: 84px; }
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}
/* The header stops being sticky here (see the 700px block further down), so
   the padding would just be a gap above every target. */
@media (max-width: 700px) {
  html { scroll-padding-top: 0; }
}

/* Skip link. First focusable thing on every page, invisible until it is
   focused, then parked over the header.
 *
 * Moved with `top` rather than hidden with display:none or visibility:hidden,
 * because both of those take an element out of the tab order entirely, which
 * would make this a link no keyboard user could ever reach. It has to stay
 * focusable while it is off-screen; that is the whole trick.
 *
 * Not clip-path/`sr-only` either: this one is meant to become visible, and a
 * sighted keyboard user needs to see where their focus went. */
.skip-link {
  position: fixed;
  top: -100px;
  left: 16px;
  z-index: 2000;
  padding: 12px 18px;
  border-radius: 10px;
  background: var(--accent);
  color: var(--on-accent, #0b111b);
  font-size: 14px;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.45);
  transition: top 140ms ease;
}

.skip-link:focus,
.skip-link:focus-visible {
  top: 12px;
  outline: 3px solid var(--ink);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .skip-link { transition: none; }
}

/* The skip target itself. Without this, focus moves but some screen readers
   keep reading from where they were, because a plain <main> is not focusable
   and the jump is purely visual. -1 puts it out of the tab order (nobody
   should tab *into* main) while still allowing focus(). */
#main:focus { outline: none; }

/* Landing page: its own deeper bespoke palette, sitting on a fixed gradient
   wallpaper, a distinct look from the rest of the site's base dark theme
   above, not a light/dark toggle (the site has no light mode). */
/* The palette, picked in the browser against the live page with the design
   editor (removed 2026-08-08, see public/overrides.css) and folded back in
   here.
 *
 * It replaces the old five-colour brand set (#766153 brown, #bcbd8b sage,
 * #717744 olive, #373d20 forest, #eff1ed bone), which was built around the fact
 * that three of its five members could not carry text: brown was 1.95:1 against
 * forest and olive 2.39:1, so the whole set had to be layered rather than flat.
 * That constraint is gone. This set is one near-black green, one mid green, one
 * pale green and bone, and every text pairing in it clears 4.5:1 with room to
 * spare, the tightest being small print on the band at 5.57:1.
 *
 * Measured, all against the page unless stated:
 *   bone   #eff1ed  15.28:1     body text
 *   grey   #e0e1dd  13.22:1     secondary text and small print
 *   sage   #8fb996   7.90:1     accent, and 7.90:1 under a dark label
 *   band   #415d43   6.43:1     under bone, 5.57:1 under grey */
body.landing-page {
  --page-bg: #0c1e10;
  --ink: #eef2eb;
  --muted: #e0e1db;
  /* The 12-14px token (hero note, price unit, example captions, footer
     tagline, waitlist note, survey hints). Never large text, so it owes the
     full 4.5:1, and at 13.22:1 it is nowhere near the line. It is the same
     value as --muted now, so the two sizes are separated by size and weight
     alone; the colour no longer carries any of that hierarchy. */
  --muted-light: #e0e1db;
  /* Bone rather than white, so even the hairlines stay in the palette. */
  --border: rgba(238, 242, 235, 0.11);
  /* A step below the page rather than level with it, so a card reads as a hole
     cut in the floor instead of a panel sitting on it. The step is small in
     contrast terms, 1.02:1 against the page, which is the point: it is the
     border and the shadow that draw the card, and this only stops the wallpaper
     showing through as if the card were glass. Text on it is unaffected, 15.56:1
     for body copy and 13.46:1 for small print. */
  --card-bg: #041d14;
  /* Black rather than a tinted dark: the header sits over the moving wallpaper,
     and a green-tinted bar picked up whichever part of the pour happened to be
     behind it. Neutral black stays the same bar all the way down the page. */
  --header-bg: rgba(0, 0, 0, 0.8);
  --band-bg: #36603a;
  /* #373d20, folded out of public/overrides.css on 2026-08-08, replacing the
     #191b0e this line held before. That is what the page has actually been
     showing since 2026-08-05, so this is the live value being written down
     rather than a new choice.
     Worth knowing where it came from: #373d20 is forest, out of the five-colour
     set this palette replaced (see the note at the top of this block). The
     editor put it back by hand, against a palette that had deliberately moved
     off it. It holds up on the numbers, so it stays: white title 11.35:1, the
     75%-opacity subtitle 7.19:1, and the accent word 5.16:1, against 17.14:1,
     11.71:1 and 7.79:1 on #191b0e. Less headroom, all still clear.
     The old rule carried !important, which let it outrank the two palette
     blocks below and flatten every palette to this one olive. Set as the base
     value only, so .palette-grove and .palette-epoxy keep the cta-band colours
     their own contrast notes were written for. */
  --cta-band-bg: #363e15;
}
/* The accent moves with the page, but only here: :root keeps the old green for
   the shelf and the auth pages, which this palette has not been fitted to yet. */
body.landing-page {
  --accent: #7dbf8a;
  --accent-soft: color-mix(in srgb, var(--accent) 14%, white);
  --accent-soft2: color-mix(in srgb, var(--accent) 32%, white);
  --accent-dark: color-mix(in srgb, var(--accent) 78%, black);
}
/* The accent is light enough that .btn-primary's default white label falls to
   1.95:1 on it, on every call to action on the page. The label takes the page
   colour instead, 8.92:1, which is the same inversion grove and epoxy already
   make for the same reason.
 *
 * Written as .landing-page rather than body.landing-page on purpose. That is
 * (0,2,0), the same weight as .partner-band .btn-primary further down, so the
 * shops band still wins on source order and keeps its own brown label. One more
 * element in this selector would silently take that over. */
.landing-page .btn-primary { color: #0c1e10; }
/* The five lights are parked at fixed points and each one is offset by the same
   pair of custom properties, multiplied by its own depth factor. public/bg.js
   writes those two values from the pointer; with no script, no pointer, or
   reduced motion they stay 0px and this is exactly the wallpaper it always was.
   Two of the layers use a negative factor, so they slide against the others.
   That counter-motion is what makes it read as depth rather than as one flat
   sheet being dragged around. */
/* ---- Variation: "grove" -------------------------------------------------
   The second palette: #7B9669 grass, #E6E6E6 grey, #6C8480 teal, #BAC8B1 pale,
   #404E3B deep. Add class="palette-grove" to <body>, or load the page with
   ?palette=grove, to see it.

   It is laid out differently from the default set because its numbers are
   different. Nothing in it can be a filled button under white text (grass
   3.28:1, teal 4.00:1, pale 1.75:1), so the primary button inverts: pale fill,
   deep label, 5.05:1. And teal cannot carry text on anything here (best case
   4.00:1 against white), so it is spent on non-text UI, where 3:1 is the bar it
   has to clear, rather than on a band with words in it.
   Deep walked 55% toward black is the page: the first step dark enough for
   grass to work as text at 4.90:1, which is what frees grass for the small
   print. */
body.landing-page.palette-grove {
  --page-bg: #1b2417;
  --ink: #e6e6e6;
  --muted: #b4cba5;
  --muted-light: #739a56;
  --accent: #b4cba5;
  --accent-soft: color-mix(in srgb, var(--accent) 14%, white);
  --accent-soft2: color-mix(in srgb, var(--accent) 32%, white);
  --accent-dark: color-mix(in srgb, var(--accent) 82%, black);
  /* Teal's job: edges, not words. */
  --border: rgba(97, 135, 129, 0.42);
  --card-bg: #3b5033;
  --band-bg: #3b5033;
  --header-bg: rgba(27, 36, 23, 0.82);
  --cta-band-bg: #131b11;
}
/* Pale is too light to sit under white, so every filled button in this
   variation carries the deep green instead. */
body.landing-page.palette-grove .btn-primary { color: #3b5033; }
/* The shops band changes colour here too, but to deep rather than to teal:
   teal would leave its paragraph at 3.21:1. Deep under grey is 7.10:1. */
body.landing-page.palette-grove .partner-band {
  background: #3b5033;
  --muted: #e6e6e6;
}
body.landing-page.palette-grove .partner-band .partner-eyebrow { color: #b4cba5; }
body.landing-page.palette-grove .partner-band .btn-primary { background: #b4cba5; color: #3b5033; }
body.landing-page.palette-grove .partner-band .btn-primary:hover { background: #c8dbbd; }
/* The card's own ramp, in this variation's family. */
body.landing-page.palette-grove .story-face { background: linear-gradient(158deg, #334526 0%, #293721 52%, #1f2b1a 100%); }

/* ---- Variation: "epoxy" -------------------------------------------------
   The third palette: one green ramp, nine steps, #d8f3dc mint down to #081c15
   ink. Add class="palette-epoxy" to <body>, or load the page with
   ?palette=epoxy, to see it.

   A single-hue ramp is much easier to place than the other two sets, because
   lightness is the only variable and the steps are evenly spaced. Measured
   against the darkest step as the page: mint 14.97:1, pale 12.58, sage 10.48,
   jade 8.68, emerald 7.15, green 4.62. Six of the nine can carry body text, so
   the constraint that shaped the other palettes is simply absent here, and the
   assignment below is free to be about hierarchy instead of about survival.

   The two darkest steps carry the surfaces: ink is the page, forest is every
   raised thing. Band is forest rather than pine for the usual reason: on pine
   the small print falls to 3.79:1, on forest it holds 6.56:1. Pine is spent on
   the floor pattern instead, where nothing has to be read off it. */
body.landing-page.palette-epoxy {
  --page-bg: #041d14;
  --ink: #cff7d5;
  --muted: #a3eabe;
  /* The 12-14px token, so it owes the full 4.5:1 everywhere it lands: 10.48:1
     on the page and 6.56:1 on a card. Comfortably the safest this token has
     been in any of the three palettes. */
  --muted-light: #7bdbaa;
  --accent: #36bb83;
  --accent-soft: color-mix(in srgb, var(--accent) 14%, white);
  --accent-soft2: color-mix(in srgb, var(--accent) 32%, white);
  --accent-dark: color-mix(in srgb, var(--accent) 78%, black);
  --card-bg: #0f4530;
  --band-bg: #0f4530;
  --header-bg: rgba(4, 29, 20, 0.82);
  /* Below the page rather than above it, so the closing band still reads as a
     step down even though the page is already the darkest step in the set. */
  --cta-band-bg: #02110c;
  /* Sage at low alpha: the hairlines stay inside the ramp. */
  --border: rgba(123, 219, 170, 0.18);
}
/* White on emerald is 2.47:1, so the filled button inverts the way grove's
   does: the label takes the darkest step, 7.15:1. */
body.landing-page.palette-epoxy .btn-primary { color: #041d14; }
/* The shops band is the one contrasting surface in the other palettes. There
   is no contrasting hue to reach for here, so it steps up the ramp instead:
   pine under mint is 5.41:1. Its small print has to come up with it, because
   sage on pine is 3.79:1. */
body.landing-page.palette-epoxy .partner-band {
  background: #1c6c4c;
  --muted: #cff7d5;
}
body.landing-page.palette-epoxy .partner-band .partner-eyebrow { color: #a3eabe; }
body.landing-page.palette-epoxy .partner-band .btn-primary { background: #cff7d5; color: #0f4530; }
body.landing-page.palette-epoxy .partner-band .btn-primary:hover { background: #ffffff; }
body.landing-page.palette-epoxy .story-face { background: linear-gradient(158deg, #14573d 0%, #0f4530 52%, #093123 100%); }

/* The wallpaper: a poured metallic-epoxy floor, on the nine-step green ramp
   (#d8f3dc mint down to #081c15 ink).
 *
 * Gradients cannot make this shape. What a metallic pour actually looks like is
 * branching rivers of pigment with feathered edges and a lacy cell structure
 * eaten into them, and that comes out of an SVG filter chain:
 *
 *   feTurbulence      a noise field
 *   feColorMatrix     alpha forced opaque. Without this the whole layer renders
 *                     transparent: turbulence writes noise into alpha as well as
 *                     colour, and premultiplication then crushes the luminance
 *                     that luminanceToAlpha is about to read.
 *   feDisplacementMap a second turbulence shoves the first one around. This is
 *                     the step that turns a smooth field into swirls.
 *   luminanceToAlpha  luminance becomes alpha.
 *   feFuncA table     and this is the one that matters: a narrow spike centred
 *                     in the field's range keeps only the values near the
 *                     median, which draws contour ribbons through the noise
 *                     rather than a grey cloud. The width of that spike is the
 *                     coverage dial. fractalNoise clusters hard around its
 *                     midpoint, so the response is steep and not at all
 *                     linear: measured on this field, a half-width of 0.008
 *                     gives 9% coverage, 0.028 gives 16%, 0.045 gives 28% and
 *                     0.07 gives 44%. 0.045 is used below because roughly a
 *                     third pigment is what the reference floor has.
 *
 * The result is used as a MASK, not as an image, so the rivers take their colour
 * from the gradient underneath and stay inside the palette.
 *
 * That chain is not run in the browser. It is deterministic and identical on
 * every device, and leaving it inline meant every visitor recomputed it, and
 * recomputed it again on scroll because the wallpaper is position:fixed: 2fps
 * against 43 with these layers hidden. It is baked to pour-rivers.webp and
 * pour-veins.webp instead (238KB the pair, WebP because the mask is a smooth
 * alpha field). The filter source lives in the commit that added it; to change
 * the pattern, regenerate the images rather than editing values here.
 *
 * Everything is then held at low opacity. The reference is white on black and
 * this has body text over it, so the layer that would look best on its own is
 * not the one that belongs here. */
.landing-bg {
  --bg-x: 0px;
  --bg-y: 0px;
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    /* Aggregate speckle suspended in the resin, blended rather than laid on
       top so it disturbs what is already there instead of greying it down. */
    url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20width%3D'180'%20height%3D'180'%3E%3Cfilter%20id%3D'g'%3E%3CfeTurbulence%20type%3D'fractalNoise'%20baseFrequency%3D'0.85'%20numOctaves%3D'4'%20stitchTiles%3D'stitch'%2F%3E%3CfeColorMatrix%20type%3D'saturate'%20values%3D'0'%2F%3E%3C%2Ffilter%3E%3Crect%20width%3D'180'%20height%3D'180'%20filter%3D'url(%23g)'%2F%3E%3C%2Fsvg%3E"),
    /* The wet look: one broad highlight, the brightest thing on the floor and
       still only 7%. It is what makes this read as poured resin rather than as
       painted concrete, and the only layer worth having follow the pointer. */
    /* These four follow the pointer but deliberately NOT the scroll. A gradient
       position is a paint property: moving it re-paints this whole layer, and
       doing that on every scroll frame is a cost the two texture layers above
       already pay for the drift. The pointer moves rarely, eases, and stops. */
    radial-gradient(circle at calc(46% + var(--bg-x) * 0.25) calc(34% + var(--bg-y) * 0.25), rgba(207, 247, 213, 0.07) 0%, transparent 55%),
    radial-gradient(ellipse 64% 44% at calc(20% + var(--bg-x) * 1) calc(18% + var(--bg-y) * 1), rgba(54, 187, 131, 0.13) 0%, transparent 62%),
    radial-gradient(ellipse 56% 46% at calc(82% + var(--bg-x) * -0.7) calc(26% + var(--bg-y) * -0.7), rgba(28, 108, 76, 0.26) 0%, transparent 64%),
    radial-gradient(ellipse 54% 44% at calc(74% + var(--bg-x) * -0.4) calc(70% + var(--bg-y) * -0.4), rgba(5, 27, 18, 0.55) 0%, transparent 58%),
    linear-gradient(160deg, #081c14 0%, #0d2a1d 46%, #051410 100%);
  /* The grain is the surface of the resin rather than something suspended in
     it, so it is the one layer that stays put while everything under it moves.
     Sharp and still over a soft drifting floor is what sells the depth. */
  background-size: 180px 180px, cover, cover, cover, cover, cover;
  background-repeat: repeat, no-repeat, no-repeat, no-repeat, no-repeat, no-repeat;
  background-blend-mode: overlay, normal, normal, normal, normal, normal;
}

/* Scroll drift. The wallpaper is position:fixed, so without this it is the one
   thing on the page that does not move.
 *
 * The masks are oversized and slid inside that headroom with mask-position,
 * rather than the layers being translated: translating means making each layer
 * taller than the viewport so there is something to move into view, and two
 * extra full-bleed layers to rasterise cost more than the slide saves. Measured
 * both ways once the masks were baked: 33fps sliding, 31fps translating.
 *
 * No will-change here. It was load-bearing while the masks were inline SVG
 * filters, because without a promoted layer every scroll frame re-ran both
 * feTurbulence chains (2fps, against 43 with these layers hidden). Now that the
 * masks are images there is nothing to recompute, and promoting two full-bleed
 * layers costs more than it saves: 26fps with it, 33 without.
 *
 * Travel is in vh so a short window cannot slide a mask past its own headroom
 * and expose an edge. Rivers 22vh against 25vh, veins 15vh against 17.5vh.
 *
 * The two run in opposite directions. That counter-motion is what reads as
 * depth; both moving together would just look like one sheet being dragged.
 * public/bg.js writes --bg-scroll as 0..1 across the document, on this element
 * itself rather than on the root: see the note in that file. With no script,
 * or with reduced motion, it stays 0 and this is a still floor. */
.landing-bg::before,
.landing-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.landing-bg::before {
  -webkit-mask-size: 115% 150%;
  mask-size: 115% 150%;
  -webkit-mask-position: 50% calc(50% - var(--bg-scroll, 0) * 22vh);
  mask-position: 50% calc(50% - var(--bg-scroll, 0) * 22vh);
}
.landing-bg::after {
  -webkit-mask-size: 110% 135%;
  mask-size: 110% 135%;
  -webkit-mask-position: 50% calc(50% + var(--bg-scroll, 0) * 15vh);
  mask-position: 50% calc(50% + var(--bg-scroll, 0) * 15vh);
}

/* The pigment rivers, with the cell structure subtracted out of them: the lacy
   holes are cells eating into the pour, which is what gives a metallic floor its
   foam instead of a flat shape. 28% of the mask is opaque.
 *
 * The gradient tops out at #586758 rather than at white, and that ceiling is
 * measured, not chosen. A reference metallic floor is white on black, roughly
 * 15:1 within itself, and text cannot sit on that: with a white top stop the
 * brightest river pixel rendered as rgb(144,165,153), which puts 13px small
 * print at 1.99:1. Fading the whole layer instead does not fix it, it just
 * turns the pour into a flat grey wash and still only reached 4.05:1 at the
 * point where the effect had already gone.
 *
 * So the cap carries the constraint and the opacity stays at 1, which keeps the
 * river/base separation as strong as it can be. Walking the cap down the ramp:
 * #9aae9f gives 1.88:1, #7d9184 2.66:1, #68796d 3.67:1, #586758 4.77:1.
 * #586758 is the lightest step that clears 4.5:1, so that is the one. */
.landing-bg::before {
  background: linear-gradient(150deg, #516a52 0%, #475c4b 38%, #3c4e43 68%, #314238 100%);
  -webkit-mask-image: url('pour-rivers.webp');
  mask-image: url('pour-rivers.webp');
}

/* Fine veining at a higher frequency and a much narrower spike (9% coverage),
   for the detail that only shows when you look at it. Same ceiling. */
.landing-bg::after {
  background: #516a52;
  -webkit-mask-image: url('pour-veins.webp');
  mask-image: url('pour-veins.webp');
  opacity: 0.6;
}

/* Grove keeps the wallpaper it was designed against: its greys and teal have
   nothing to do with this ramp, and the floor would fight them. It replaces the
   background itself below, so only the two mask layers need turning off. */
body.landing-page.palette-grove .landing-bg::before,
body.landing-page.palette-grove .landing-bg::after {
  display: none;
}

/* ---- The travelling curves ----
 *
 * A fan of long curves sweeping across the wallpaper, built by
 * public/paths.js, which adds .has-paths once it has drawn them. That class is
 * what turns the pour off: the curves take the pour's slot rather than sitting
 * on top of it, because both are the single texture layer over the base
 * gradient and the page's small print was measured against one of them, not
 * against the sum. Written this way round so that a browser which never runs
 * the script keeps the pour instead of ending up with a bare gradient.
 *
 * The paths' own stroke ceiling is measured in paths.js. */
.landing-bg.has-paths::before,
.landing-bg.has-paths::after {
  display: none;
}

.landing-paths {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* Bone, the same light everything else on this page is drawn in. The stroke
     alpha per curve is set as an attribute, so this is only the hue. */
  color: #eef2eb;
  stroke: currentColor;
}

/* The dash, and the whole of the motion.
 *
 * Every path was told pathLength="1000", so 350/650 is a dash covering just
 * over a third of its curve whatever that curve actually measures, and the
 * offset below is in the same hundredths.
 *
 * -2000 is two full laps across the length of the document. --k is this
 * curve's own starting phase, so the dashes are spread around the family
 * instead of travelling as one band. There is no animation and no timer here:
 * public/bg.js writes --bg-scroll on .landing-bg, which this fan sits inside,
 * and nothing else touches it, so
 * these move when the page moves and stop when it stops. With no script, or
 * under reduced motion where bg.js writes nothing at all, --bg-scroll falls
 * back to 0 and the fan is a still drawing. */
.landing-paths path {
  stroke-dasharray: 350px 650px;
  stroke-dashoffset: calc(var(--bg-scroll, 0) * -2000px - var(--k, 0) * 1000px);
}

body.landing-page.palette-grove .landing-bg {
  background:
    radial-gradient(circle at calc(15% + var(--bg-x) * 1) calc(20% + var(--bg-y) * 1), color-mix(in srgb, var(--accent) 13%, transparent) 0%, transparent 32%),
    radial-gradient(circle at calc(85% + var(--bg-x) * -0.7) calc(15% + var(--bg-y) * -0.7), color-mix(in srgb, #618781 16%, transparent) 0%, transparent 38%),
    radial-gradient(circle at calc(30% + var(--bg-x) * 0.55) calc(80% + var(--bg-y) * 0.55), color-mix(in srgb, var(--accent) 11%, transparent) 0%, transparent 40%),
    radial-gradient(circle at calc(75% + var(--bg-x) * -0.4) calc(65% + var(--bg-y) * -0.4), color-mix(in srgb, #618781 12%, transparent) 0%, transparent 45%),
    radial-gradient(circle at calc(50% + var(--bg-x) * 0.25) calc(45% + var(--bg-y) * 0.25), color-mix(in srgb, var(--accent) 7%, transparent) 0%, transparent 60%),
    linear-gradient(160deg, #1b2417 0%, #22321d 50%, #171f14 100%);
}

.landing-content { position: relative; z-index: 1; }

/* spin360 / floatY / cardCycle went with the hero's rotating-orbit graphic,
   only the how-it-works divider still animates. */
@keyframes dashMove {
  to { background-position: -32px 0; }
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: 'Inter', -apple-system, 'Segoe UI', sans-serif;
  color: var(--ink);
  background: var(--page-bg);
  min-height: 100vh;
  overflow-x: hidden;
  transition: background 0.3s ease, color 0.3s ease;
}

a { color: inherit; text-decoration: none; }
a:hover { opacity: 0.75; }

.display {
  font-family: 'Sora', 'Segoe UI', sans-serif;
  font-weight: 800;
  letter-spacing: -0.02em;
}

/* Header */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--header-bg);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--border);
  transition: background 0.3s ease, border-color 0.3s ease;
}
.header-inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: 18px 32px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
}
.logo {
  font-family: 'Sora', sans-serif;
  font-weight: 800;
  font-size: 22px;
  letter-spacing: -0.02em;
  /* The wordmark and its dot are two spans, and at 390px the header ran out of
     room and wrapped between them: the first thing a phone visitor saw was
     "RAMU" with a stray green square underneath it. */
  white-space: nowrap;
}
/* The wordmark's period is an accent square, not a round dot. The literal '.'
   stays in the markup so the logo still reads as "RAMU." without CSS and when
   copied as text; the fixed box hides the glyph and the square takes its place,
   sitting on the baseline. Sized in em so it tracks whatever font-size the
   surrounding logo uses (22px in the header, 28px on the shelf loader). */
.logo .dot,
.page-loading-logo .dot,
.footer-logo .dot {
  display: inline-block;
  width: 0.24em;
  height: 0.24em;
  margin-left: 0.08em;
  background: var(--accent);
  border-radius: 1px;
  color: transparent;
  overflow: hidden;
}
.nav { display: flex; gap: 32px; flex-wrap: wrap; }
.nav a { font-size: 15px; font-weight: 500; color: var(--muted); }
.header-actions { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; justify-content: flex-end; }
.login-link {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-weight: 600;
  font-size: 14px;
  color: var(--ink);
  white-space: nowrap;
}

/* ---- Every button that is not the one big one ----
 *
 * Glass: a translucent pill with a lit rim and a shadow under it, rather than a
 * flat fill. The look is almost entirely in the shadows, and the order they are
 * written in is the order they are read: a bright inset along the top edge is
 * the light catching the near lip, a dark inset along the bottom is the far one
 * in shadow, a hairline inset all the way round is the glass's own thickness,
 * and the two outer shadows are what the thing casts on the page.
 *
 * Still one declaration block on .btn-primary, so every override that already
 * exists keeps working untouched: the label inversion on the landing page, the
 * partner band's brown pair, and both palette variants all outrank this on
 * specificity exactly as they did before. */
.btn-primary {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 11px 22px;
  /* Translucent, or none of the above is glass. On the accent this is a small
     change in colour and a large one in what the button looks made of. */
  background: color-mix(in srgb, var(--accent) 88%, transparent);
  -webkit-backdrop-filter: blur(10px) saturate(1.4);
  backdrop-filter: blur(10px) saturate(1.4);
  color: #ffffff;
  border-radius: 999px;
  font-weight: 600;
  font-size: 14px;
  white-space: nowrap;
  border: none;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.4),
    inset 0 -1px 0 rgba(0, 0, 0, 0.16),
    inset 0 0 0 1px rgba(255, 255, 255, 0.1),
    0 5px 14px rgba(0, 0, 0, 0.3),
    0 1px 2px rgba(0, 0, 0, 0.35);
  transition:
    transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.25s ease,
    background 0.25s ease;
}

/* The specular sweep across the top half. A separate layer rather than another
   inset shadow, because it has to be clipped to the pill's own radius and fade
   out before the middle, which a shadow cannot do. */
.btn-primary::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.28), rgba(255, 255, 255, 0) 52%);
  pointer-events: none;
}

.btn-primary:hover {
  background: color-mix(in srgb, var(--accent) 96%, transparent);
  opacity: 1;
  transform: translateY(-1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 -1px 0 rgba(0, 0, 0, 0.16),
    inset 0 0 0 1px rgba(255, 255, 255, 0.16),
    0 9px 22px rgba(0, 0, 0, 0.36),
    0 1px 2px rgba(0, 0, 0, 0.35);
}
/* Pressed: down onto the page, and the cast shadow collapses with it. */
.btn-primary:active {
  transform: translateY(1px);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.25),
    inset 0 0 0 1px rgba(255, 255, 255, 0.1),
    0 2px 5px rgba(0, 0, 0, 0.3);
}
@media (prefers-reduced-motion: reduce) {
  .btn-primary { transition: box-shadow 0.25s ease, background 0.25s ease; }
  .btn-primary:hover,
  .btn-primary:active { transform: none; }
}

/* ==========================================================================
   The major button: order, and sign up.
   ==========================================================================

   Only these two actions get it. It is loud, and a page where the loud thing is
   everywhere has no loud thing in it.

   public/button3d.js builds the parts; everything here is what they do. Without
   that script the element is a plain link with its own text, so the block below
   only ever styles a button that got built. */
/* The three selectors are one rule, not three looks. .price-cta and
   .auth-submit both size the buttons they are put on, both are a single class,
   and auth.css loads after this file: on specificity and on order a bare
   .btn-major loses to them and the button came out stretched to its container
   with the shell and the pill different widths. Naming them here is what makes
   this win, and it is why the list is exactly the two places a major button is
   also one of those. */
.btn-major,
.price-cta.btn-major,
.auth-submit.btn-major {
  position: relative;
  display: block;
  /* Sized by the pill inside it, then centred in whatever it is placed in.
     Without this the shell is as wide as its container and .btn3d-bg, which
     covers the shell, sits out past the button it is supposed to be under. */
  width: fit-content;
  margin-left: auto;
  margin-right: auto;
  padding: 0;
  border: none;
  background: none;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  /* The splash reaches well past the button's own box. */
  overflow: visible;
  cursor: pointer;
  isolation: isolate;
  --btn3d-drop: 5px;
  --btn3d-label: #052016;
}
/* The glass pill's specular sweep, which has nothing to sweep across here. */
.btn-major::before { display: none; }

/* The layer the button sits on, offset downward: the whole of the third
   dimension is this and the transform on .btn3d-wrap. Pressing moves the top
   down onto it, which is why the drop distance is a variable and used twice. */
.btn3d-bg {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 55%, black);
  transform: translateY(var(--btn3d-drop));
}

.btn3d-wrap {
  position: relative;
  display: block;
  min-width: 210px;
  border-radius: 999px;
  background: var(--accent);
  transition: transform 0.14s cubic-bezier(0.22, 1, 0.36, 1);
}
.btn-major:hover .btn3d-wrap { transform: translateY(2px); }
.btn-major:active .btn3d-wrap { transform: translateY(var(--btn3d-drop)); }
.btn-major:focus-visible .btn3d-wrap { outline: 2px solid var(--ink); outline-offset: 4px; }

/* The light that runs the outline. Stretched to the button rather than kept at
   its own proportions, so a longer label does not leave the stroke floating
   inside the edge it is meant to be drawing. */
.btn3d-path {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  stroke: rgba(255, 255, 255, 0.9);
  stroke-dasharray: 60 480;
  stroke-dashoffset: 0;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.btn-major:hover .btn3d-path {
  opacity: 1;
  animation: btn3dPath 1.6s ease-in-out infinite;
}
@keyframes btn3dPath {
  from { stroke-dashoffset: 0; }
  to { stroke-dashoffset: -480; }
}

.btn3d-outline {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.5),
    inset 0 0 0 1px rgba(255, 255, 255, 0.14);
  pointer-events: none;
}

/* The splash. Twelve strokes that are a dot at rest and a dash on the way
   out, so the whole thing reads as something bursting away from the button
   rather than as lines being drawn. Sits behind the button, centred on it, and
   never takes a pointer event. */
.btn3d-splash {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: -1;
  width: 342px;
  height: 208px;
  transform: translate(-50%, -50%);
  pointer-events: none;
  stroke: var(--accent);
  stroke-dasharray: 1 60;
  stroke-dashoffset: 0;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.btn-major:hover .btn3d-splash {
  opacity: 0.85;
  animation: btn3dSplash 0.7s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes btn3dSplash {
  to {
    stroke-dasharray: 2 60;
    stroke-dashoffset: -60;
  }
}

.btn3d-content {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 48px;
  padding: 0 28px;
  overflow: hidden;
  color: var(--btn3d-label);
  font-family: 'Sora', sans-serif;
  font-weight: 700;
  font-size: 15px;
  letter-spacing: 0.01em;
}

/* Both labels occupy the same place; the second is stacked on top of the first
   and invisible until the pointer arrives. */
.btn3d-char {
  display: flex;
  white-space: pre;
}
.btn3d-char.state-2 {
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
}
.btn3d-char span {
  display: inline-block;
  /* Each letter leaves and arrives a beat after the one before it. 26ms is
     small enough that the word still reads as a word rather than as a queue. */
  animation-delay: calc(var(--i) * 26ms);
  animation-fill-mode: both;
}
.btn3d-char.state-1 span { opacity: 1; }
.btn3d-char.state-2 span { opacity: 0; }

.btn-major:hover .btn3d-char.state-1 span { animation: btn3dCharOut 0.45s ease forwards; }
.btn-major:hover .btn3d-char.state-2 span { animation: btn3dCharIn 0.55s ease forwards; }

@keyframes btn3dCharOut {
  from { transform: translateY(0); opacity: 1; }
  to { transform: translateY(-70%); opacity: 0; filter: blur(3px); }
}
@keyframes btn3dCharIn {
  0% { transform: translateY(50%); opacity: 0; filter: blur(14px); }
  20% { transform: translateY(70%); opacity: 1; }
  50% { transform: translateY(-15%); opacity: 1; filter: blur(0); }
  100% { transform: translateY(0); opacity: 1; }
}

/* The arrow. It leaves to the right, is put back well off to the left while it
   cannot be seen, and comes home: one element, no second copy, and the return
   never crosses the label. */
.btn3d-icon {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 18px;
  height: 2px;
  margin-top: -1px;
  opacity: 0;
}
.btn3d-icon > span {
  position: absolute;
  inset: 0;
  display: block;
  background: currentColor;
  border-radius: 2px;
}
.btn3d-icon > span::before,
.btn3d-icon > span::after {
  content: '';
  position: absolute;
  right: 0;
  width: 8px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
  transform-origin: right center;
}
.btn3d-icon > span::before { transform: rotate(45deg); }
.btn3d-icon > span::after { transform: rotate(-45deg); }

.btn-major:hover .btn3d-icon { animation: btn3dArrow 0.9s ease forwards; }
.btn-major:hover .btn3d-icon > span::before { animation: btn3dHeadA 0.9s ease forwards; }
.btn-major:hover .btn3d-icon > span::after { animation: btn3dHeadB 0.9s ease forwards; }

@keyframes btn3dArrow {
  0% { transform: translateX(-9px); opacity: 1; }
  50% { transform: translateX(80px); opacity: 0; }
  51% { transform: translateX(-140px); opacity: 0; }
  100% { transform: translateX(-9px); opacity: 0; }
}
@keyframes btn3dHeadA {
  50% { transform: rotate(30deg); }
  80% { transform: rotate(55deg); }
}
@keyframes btn3dHeadB {
  50% { transform: rotate(-30deg); }
  80% { transform: rotate(-55deg); }
}

/* Reduced motion keeps the button and drops the performance. The press stays:
   it is feedback for an action, not decoration, and it is instant either way.
   The second label never arrives, so the first one must not leave. */
@media (prefers-reduced-motion: reduce) {
  .btn-major:hover .btn3d-splash,
  .btn-major:hover .btn3d-path,
  .btn-major:hover .btn3d-char.state-1 span,
  .btn-major:hover .btn3d-char.state-2 span,
  .btn-major:hover .btn3d-icon,
  .btn-major:hover .btn3d-icon > span::before,
  .btn-major:hover .btn3d-icon > span::after {
    animation: none;
  }
  .btn-major:hover .btn3d-splash,
  .btn-major:hover .btn3d-path { opacity: 0; }
  .btn-major:hover .btn3d-wrap { transform: none; }
}

.btn-outline {
  padding: 15px 28px;
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--border);
  border-radius: 999px;
  font-weight: 600;
  font-size: 15px;
}

/* Hero */
.hero {
  max-width: 1200px;
  margin: 0 auto;
  /* Small bottom padding on purpose: the following .section already brings
     100px of its own, and 80px here stacked into ~180px of empty band between
     the hero and the first heading. */
  padding: 92px 32px 28px;
  display: flex;
  gap: 64px;
  align-items: center;
  flex-wrap: wrap;
}
.hero-copy { flex: 1 1 420px; }
/* ---- The story's four labels ----
 *
 * A radial gradient whose centre, spread, five colours and five stops are all
 * custom properties, and which moves between two whole sets of them on hover.
 *
 * @property is the entire trick, and it is not decoration: a plain custom
 * property is an untyped string as far as the cascade is concerned, so a
 * transition on it can only flip from one value to the other at the halfway
 * mark. Registered with a syntax, the browser knows --pos-x is a percentage and
 * --color-1 is a colour, and can interpolate them. That is what makes the
 * gradient slide and bloom rather than cut.
 *
 * A browser that does not know @property (Firefox before 128) ignores these
 * blocks, reads the values in the rules below as plain strings, and gets both
 * states correctly with a hard cut between them. Nothing is lost but the
 * half second.
 *
 * inherits: false on everything except the border pair, which the ::before ring
 * has to read off the parent. */
@property --pos-x { syntax: '<percentage>'; initial-value: 11.14%; inherits: false; }
@property --pos-y { syntax: '<percentage>'; initial-value: 140%; inherits: false; }
@property --spread-x { syntax: '<percentage>'; initial-value: 150%; inherits: false; }
@property --spread-y { syntax: '<percentage>'; initial-value: 180.06%; inherits: false; }
@property --color-1 { syntax: '<color>'; initial-value: #041d14; inherits: false; }
@property --color-2 { syntax: '<color>'; initial-value: #062518; inherits: false; }
@property --color-3 { syntax: '<color>'; initial-value: #0b3125; inherits: false; }
@property --color-4 { syntax: '<color>'; initial-value: #0f4530; inherits: false; }
@property --color-5 { syntax: '<color>'; initial-value: #155b3c; inherits: false; }
@property --stop-1 { syntax: '<percentage>'; initial-value: 37.35%; inherits: false; }
@property --stop-2 { syntax: '<percentage>'; initial-value: 61.36%; inherits: false; }
@property --stop-3 { syntax: '<percentage>'; initial-value: 78.42%; inherits: false; }
@property --stop-4 { syntax: '<percentage>'; initial-value: 89.52%; inherits: false; }
@property --stop-5 { syntax: '<percentage>'; initial-value: 100%; inherits: false; }
@property --border-angle { syntax: '<angle>'; initial-value: 20deg; inherits: true; }
@property --border-color-1 { syntax: '<color>'; initial-value: rgba(125, 191, 138, 0.18); inherits: true; }
@property --border-color-2 { syntax: '<color>'; initial-value: rgba(125, 191, 138, 0.55); inherits: true; }

/* Was a near-white pill with dark green text, the only light element on an
   otherwise dark page, which made a label louder than the actual CTA. Then a
   quiet outlined chip. Now a filled one, still dark: the weight it gained is in
   the fill and the lit border, not in brightness, so it is still the button and
   the wordmark dot that carry the accent.
 *
 * Every colour in both ramps is measured against the label's own text
 * (--muted, #e0e1dd). The lightest stop that appears at rest is #24593f at
 * 6.21:1 and the lightest on hover is #2d6a4f at 4.86:1, so the small
 * uppercase type clears 4.5:1 wherever the gradient happens to be under it.
 * That is what caps the hover ramp: #40916c, one step further up the same
 * brand green, is 2.91:1 and cannot carry this text. The lift that the ramp is
 * not allowed to provide comes from the border instead, which has no text on
 * it and can go all the way to bone. */
.eyebrow {
  display: inline-block;
  position: relative;
  color: var(--muted);
  border: none;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 14px;
  border-radius: 999px;
  margin-bottom: 20px;
  background: radial-gradient(
    var(--spread-x) var(--spread-y) at var(--pos-x) var(--pos-y),
    var(--color-1) var(--stop-1),
    var(--color-2) var(--stop-2),
    var(--color-3) var(--stop-3),
    var(--color-4) var(--stop-4),
    var(--color-5) var(--stop-5)
  );
  transition:
    --pos-x 0.5s, --pos-y 0.5s,
    --spread-x 0.5s, --spread-y 0.5s,
    --color-1 0.5s, --color-2 0.5s, --color-3 0.5s, --color-4 0.5s, --color-5 0.5s,
    --stop-1 0.5s, --stop-2 0.5s, --stop-3 0.5s, --stop-4 0.5s, --stop-5 0.5s,
    --border-angle 0.5s, --border-color-1 0.5s, --border-color-2 0.5s;
}

/* The border, as a gradient rather than a flat hairline: a two-stop linear
   gradient filling the whole pill, then masked down to a one pixel band by the
   usual pair of masks composited with `exclude` (an outer mask over the whole
   box, an inner one over everything inside the padding, and what survives is
   the padding, which is the band). --border-angle turns it on hover, so the lit
   end of the hairline travels round the pill.
 *
 * Both the prefixed and unprefixed mask properties are needed; Safari still
   wants the old syntax, where the operator is `xor`. */
.eyebrow::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  pointer-events: none;
  background: linear-gradient(var(--border-angle), var(--border-color-1), var(--border-color-2));
  -webkit-mask:
    linear-gradient(#ffffff 0 0) content-box,
    linear-gradient(#ffffff 0 0);
  -webkit-mask-composite: xor;
  mask:
    linear-gradient(#ffffff 0 0) content-box,
    linear-gradient(#ffffff 0 0);
  mask-composite: exclude;
}

.eyebrow:hover {
  --pos-x: 0%;
  --pos-y: 91.51%;
  --spread-x: 120.24%;
  --spread-y: 103.18%;
  --color-1: #0b3125;
  --color-2: #0f4530;
  --color-3: #155b3c;
  --color-4: #1c6c4c;
  --color-5: #052016;
  --stop-1: 0%;
  --stop-2: 8.8%;
  --stop-3: 21.44%;
  --stop-4: 71.34%;
  --stop-5: 85.76%;
  --border-angle: 190deg;
  --border-color-1: rgba(238, 242, 235, 0.2);
  --border-color-2: rgba(238, 242, 235, 0.75);
}

/* The second look, for a label that wants to read as the end of something
   rather than the start: the ramp runs the other way, dark at the rim and open
   in the middle, and the border starts lit and fades. Same measured ceiling. */
.eyebrow-variant {
  --color-1: #052016;
  --color-2: #0f4530;
  --color-3: #1c6c4c;
  --color-4: #0b3125;
  --border-angle: 200deg;
  --border-color-1: rgba(238, 242, 235, 0.55);
  --border-color-2: rgba(125, 191, 138, 0.15);
}
.eyebrow-variant:hover {
  --pos-x: 0%;
  --pos-y: 95.51%;
  --spread-x: 110.24%;
  --spread-y: 110.2%;
  --color-1: #052016;
  --color-2: #1c6c4c;
  --color-3: #155b3c;
  --color-4: #0f4530;
  --color-5: #041d14;
  --stop-1: 0%;
  --stop-2: 10%;
  --stop-3: 35.44%;
  --stop-4: 71.34%;
  --stop-5: 90.76%;
  --border-angle: 210deg;
  --border-color-1: rgba(125, 191, 138, 0.2);
  --border-color-2: rgba(238, 242, 235, 0.75);
}

/* The gradient is the label's resting look, so it stays. What goes is the half
   second of travel between the two states: both are still correct, the hover
   one simply arrives at once. */
@media (prefers-reduced-motion: reduce) {
  .eyebrow { transition: none; }
}
.hero-title {
  font-family: 'Sora', sans-serif;
  font-weight: 800;
  font-size: clamp(36px, 5vw, 58px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  margin: 0 0 20px;
  text-wrap: pretty;
}
.hero-sub {
  font-size: 18px;
  color: var(--muted);
  line-height: 1.6;
  max-width: 480px;
  margin: 0 0 32px;
}
.hero-sub .accent-word { color: var(--accent); font-weight: 600; }
.hero-actions { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
.hero-actions .btn-primary { padding: 15px 28px; font-size: 15px; font-weight: 700; }
.hero-note { font-size: 13px; color: var(--muted-light); }

.hero-visual { flex: 1 1 320px; display: flex; justify-content: center; }
/* A replica of the shelf's own order card, used in the hero. Mirrors
   shelf.css's .card treatment (left accent rule, same radii, same muted
   metadata) so the landing page shows the actual product rather than an
   abstract graphic. */
.hero-card {
  width: 100%;
  max-width: 380px;
  background: color-mix(in srgb, var(--card-bg) 100%, black 18%);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 16px;
  padding: 20px 22px;
  box-shadow: 0 24px 48px rgba(0, 0, 0, 0.4);
}
.hero-card-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 4px; }
.hero-card-name { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 17px; }
.hero-card-stamp {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  padding: 3px 9px;
  border-radius: 999px;
}
.hero-card-meta {
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--muted-light);
  text-transform: uppercase;
  margin-bottom: 16px;
}
.hero-card-item {
  display: flex;
  align-items: center;
  gap: 12px;
  background: rgba(255, 255, 255, 0.03);
  border-radius: 12px;
  padding: 10px 12px;
}
.hero-card-thumb {
  width: 38px;
  height: 38px;
  flex-shrink: 0;
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.05);
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: center;
}
.hero-card-thumb svg { width: 19px; height: 19px; }
.hero-card-item-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.hero-card-item-name { font-size: 13.5px; font-weight: 600; }
.hero-card-item-price { font-size: 12.5px; color: var(--muted-light); }
.hero-card-foot { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
.hero-card-foot-row { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 9px; }
.hero-card-next { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.hero-card-count { font-size: 12.5px; color: var(--muted-light); white-space: nowrap; }
.hero-card-bar { height: 4px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); overflow: hidden; }
.hero-card-bar span { display: block; height: 100%; border-radius: 999px; background: var(--accent); }

/* Reveal on scroll */
.reveal { opacity: 0; transform: translateY(28px); transition: opacity 0.8s ease, transform 0.8s ease; }
.reveal.is-revealed { opacity: 1; transform: translateY(0); }

/* Generic section chrome.
   Vertical rhythm is per section, not one number for all of them. The same
   100px above and below reads as generous around the six-card "why" grid and
   as a canyon around "Kaip veikia", which is four short lines: two text blocks
   in a row each contributed 100px and left 200px of black between them. So
   each section sets its own --section-y (or one side of it) next to the layout
   it belongs to, and the value scales with the viewport rather than staying at
   its desktop size all the way down to a phone. */
.section {
  max-width: 1200px;
  margin: 0 auto;
  --section-y: clamp(56px, 6vw, 80px);
  padding: var(--section-top, var(--section-y)) 32px var(--section-bottom, var(--section-y));
}
.section-head { text-align: center; max-width: 560px; margin: 0 auto var(--head-gap, 48px); }
.section-eyebrow {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--accent);
  text-transform: uppercase;
  margin-bottom: 12px;
}
.section-title { font-family: 'Sora', sans-serif; font-weight: 700; font-size: clamp(28px, 4vw, 40px); margin: 0 0 16px; }
.section-sub { color: var(--muted); font-size: 16px; line-height: 1.6; margin: 0; }

/* Four steps as a rail, not a column. Each one is a single line of text, and
   stacked into a 640px column they left a tall empty band down both sides of
   the section; across, the block is wide and shallow and needs a fraction of
   the air. The dashed line used to sit above the list as decoration. Now it
   runs through the row with the numbers sitting on it, so it reads as the path
   an order takes rather than as a rule. */
.how-section { --section-y: clamp(40px, 4vw, 56px); --head-gap: 40px; }
.steps-list {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 30px;
}
.step-row { position: relative; display: flex; flex-direction: column; gap: 13px; }
/* One segment per step, from its own marker across the gutter to the next one,
   rather than a single line behind the whole row: a full-width rule ran on past
   the fourth marker and trailed off into nothing. The last step has nowhere to
   point, so it draws none. 17px is half of .step-num's height. */
.step-row::before {
  content: '';
  position: absolute;
  top: 17px;
  left: 46px;
  right: -22px;
  height: 2px;
  background-image: repeating-linear-gradient(to right, var(--accent) 0 16px, transparent 16px 32px);
  background-size: 32px 2px;
  animation: dashMove 1s linear infinite;
}
.step-row:last-child::before { content: none; }
.step-num {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  /* Opaque, so the rail stops at the marker instead of running through the
     digits, and tinted the same way as .feature-icon and the hero card's stamp
     rather than being the near-white circle it was: four light discs on a dark
     page were louder than the page's actual accents. */
  background: color-mix(in srgb, var(--accent) 15%, var(--page-bg));
  border: 1px solid color-mix(in srgb, var(--accent) 38%, transparent);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 700;
  flex-shrink: 0;
}
.step-title { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 16px; line-height: 1.35; margin-bottom: 7px; }
/* The gap under the title is spacing before a description. With the steps down
   to titles alone it is trailing space, so it only applies when something
   actually follows. */
.step-title:last-child { margin-bottom: 0; }
.step-desc { color: var(--muted); font-size: 14px; line-height: 1.6; }

/* The band paints its own background edge to edge, so the section inside it
   carries no padding and the band owns the rhythm instead. */
.features-band { background: var(--band-bg); padding: clamp(56px, 5.5vw, 76px) 32px; }
.features-band .section { padding: 0; }
.features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 24px; }
.feature-card { padding: 28px 24px; background: var(--card-bg); border: 1px solid var(--border); border-radius: 16px; }
/* Was an empty coloured square, which read as "the icons aren't done yet".
   The accent tint is kept but now sits behind an actual glyph. */
.feature-icon {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
  margin-bottom: 16px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.feature-icon svg { width: 21px; height: 21px; }
.feature-title { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 17px; margin-bottom: 8px; }
.feature-desc { color: var(--muted); font-size: 14px; line-height: 1.6; }

/* The fee card used to stand alone in the middle of a 1200px section: one
   small object with black on all sides of it, which is the shape of a pricing
   section that hasn't been finished. It travels with the breakdown card now,
   and the pair is capped well inside the section width so it reads as one
   block rather than as something that lost its neighbours. */
.pricing-section {
  --section-top: clamp(48px, 5vw, 64px);
  --section-bottom: clamp(44px, 4.5vw, 56px);
  --head-gap: 40px;
}
/* The section is the positioning context for the two lights behind it, and
   clips them so they cannot bleed into the sections above and below. */
.pricing-section { position: relative; isolation: isolate; overflow: hidden; }

/* Two soft lights, blurred past any edge of their own. The reference runs them
   on 20 and 25 second loops in opposite directions; these are driven from
   --bg-scroll instead, the 0..1 document position public/bg.js writes on this
   section while it is on screen, so they move while the page is being scrolled
   and stop with it. Same
   rule as the wallpaper and the why cards, and for the same reason: on a page
   whose name means calm, one thing moving on its own clock is the thing you
   end up watching.
 *
   The travel is deliberately small. These are 520px circles under 90px of
   blur, so a hundred pixels of drift is a change in the light, not a shape
   crossing the screen. */
.price-aurora {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  filter: blur(90px);
  /* Measured: the brighter of the two composites to about rgb(29,57,40) over
     the page, which leaves the section's body copy (--muted, #e0e1dd) at
     9.62:1. There is a lot of room here, and it is spent on keeping the lights
     soft rather than on making them bright. */
  opacity: 0.18;
}
.price-aurora span {
  position: absolute;
  border-radius: 50%;
  display: block;
}
.price-aurora-a {
  width: 520px;
  height: 520px;
  top: -12%;
  left: 6%;
  background: #36bb83;
  transform: translate(calc(var(--bg-scroll, 0) * 110px), calc(var(--bg-scroll, 0) * 60px));
}
.price-aurora-b {
  width: 440px;
  height: 440px;
  bottom: -14%;
  right: 6%;
  background: #1c6c4c;
  transform: translate(calc(var(--bg-scroll, 0) * -110px), calc(var(--bg-scroll, 0) * -60px));
}

/* The eyebrow gains the lightning bolt the reference puts on its badge. */
.price-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
.price-eyebrow svg { width: 13px; height: 13px; }

/* ---- The two views ----
 *
 * A segmented control: two labels with a pill sliding under whichever is
 * chosen. The pill is one element that moves, not a background on each tab, so
 * there is a single thing travelling between the two rather than one lighting
 * up as the other goes out. */
.price-tabs {
  position: relative;
  display: inline-flex;
  margin-top: 26px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: color-mix(in srgb, var(--page-bg) 70%, transparent);
}
.price-tabs-thumb {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 4px;
  width: calc(50% - 4px);
  border-radius: 999px;
  background: var(--accent);
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
/* Moved by a whole width of itself, so the pill lands exactly on the second tab
   whatever the two labels happen to measure. */
.price-tabs[data-on='1'] .price-tabs-thumb { transform: translateX(100%); }

.price-tab {
  position: relative;
  z-index: 1;
  min-width: 118px;
  padding: 9px 20px;
  border: none;
  background: none;
  border-radius: 999px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s ease;
}
/* The page colour on the accent, not white: the accent is light enough that a
   white label on it is 1.95:1. Same inversion every call to action here makes. */
.price-tab.is-on { color: #0c1e10; }
.price-tab:not(.is-on):hover { color: var(--ink); }
.price-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* Both panels are visible until the script says otherwise, so a visitor without
   JavaScript reads the fee and the worked example one after the other, which is
   what this section was before it had tabs. */
.price-panels {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
}
.price-panel { width: 100%; display: flex; justify-content: center; }
/* Set by public/pricing.js once it has taken over. */
.price-panels.is-tabbed .price-panel { display: none; }
.price-panels.is-tabbed .price-panel.is-on {
  display: flex;
  animation: pricePanelIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes pricePanelIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: none; }
}

.pricing-grid {
  display: flex;
  gap: 24px;
  flex-wrap: wrap;
  justify-content: center;
  align-items: stretch;
  max-width: 800px;
  margin: 0 auto;
}
/* Now that it is a view of its own rather than half of a row, it gets the room
   the pair used to share, and a light of its own under it. */
.price-card {
  position: relative;
  isolation: isolate;
  flex: 1 1 340px;
  width: 100%;
  max-width: 440px;
  background: var(--card-bg);
  border: 2px solid var(--accent);
  border-radius: 20px;
  padding: 36px 34px;
}

/* The card's own glow, brightening under the pointer. This is the one piece of
   motion in the section that answers a person rather than the scroll, which is
   what makes the card feel picked up: the reference does the same, at the same
   strength, and it is the only place a hover state belongs here. */
.price-card::before {
  content: '';
  position: absolute;
  inset: -30px;
  z-index: -1;
  border-radius: 40px;
  background: radial-gradient(ellipse at 50% 40%, var(--accent) 0%, transparent 65%);
  opacity: 0.1;
  filter: blur(40px);
  transition: opacity 0.5s ease;
}
.price-card:hover::before { opacity: 0.3; }

.price-card-title {
  font-family: 'Sora', sans-serif;
  font-weight: 700;
  font-size: 18px;
  margin-bottom: 16px;
}
.price-amount { display: flex; align-items: baseline; gap: 8px; margin-bottom: 16px; }
/* Bigger, and filled with a ramp from the brand's light into the accent rather
   than sitting flat in the accent. It is the one number the section exists to
   show, and it was the same weight as the card's own heading.
 *
   background-clip:text needs a colour to fall back to, because a browser that
   does not clip it would otherwise paint the gradient as a block behind
   transparent glyphs and show nothing at all. -webkit- included: Safari still
   requires the prefixed pair. */
.price-amount-value {
  font-family: 'Sora', sans-serif;
  font-weight: 800;
  font-size: clamp(46px, 6vw, 58px);
  line-height: 1.05;
  letter-spacing: -0.02em;
  color: var(--accent);
  background: linear-gradient(150deg, #eef2eb 0%, var(--accent) 62%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}
.price-amount-unit { color: var(--muted-light); font-size: 15px; }
.price-desc { color: var(--muted); font-size: 14px; line-height: 1.6; margin: 0 0 26px; }
.price-features { display: flex; flex-direction: column; gap: 13px; margin-bottom: 28px; }
.price-feature { display: flex; align-items: center; gap: 11px; font-size: 14px; line-height: 1.45; }
.price-check {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}
.price-check svg { width: 11px; height: 11px; }
.price-cta {
  display: flex;
  justify-content: center;
  text-align: center;
  padding: 14px;
  font-size: 15px;
}
/* The closed-state button spans the card like the open-state one does. It did
   not: `body.ordering-closed a[data-closed-cta] { display: inline-flex }` near
   the bottom of this file is (0,2,1) and quietly outranked .price-cta, so while
   we are shut the card's call to action was a shrink-to-fit pill sitting on the
   left. That rule is right everywhere else, which is why this overrides it here
   rather than being changed there. */
body.ordering-closed a.price-cta[data-closed-cta] { display: flex; }

/* The fee card's neighbour: what a single top-up is actually made of. Quieter
   than the card beside it on purpose, 1px border against that one's 2px accent,
   so the pair has a lead and a supporting half rather than two of the same. */
.example-card {
  flex: 1 1 300px;
  width: 100%;
  max-width: 440px;
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 32px 28px;
  /* So the closing line can sit on the bottom edge: the fee card beside it is
     the taller of the two, and stretching left this one's last row floating in
     the middle of its own box. */
  display: flex;
  flex-direction: column;
}
.example-title { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 16px; margin-bottom: 4px; }
.example-subtitle { font-size: 13px; color: var(--muted-light); margin-bottom: 16px; }
.example-row { display: flex; justify-content: space-between; gap: 14px; font-size: 14px; color: var(--muted); padding: 10px 0; border-bottom: 1px solid var(--border); }
.example-row-value { color: var(--ink); font-weight: 600; text-align: right; }
.example-total { display: flex; justify-content: space-between; gap: 14px; font-weight: 700; font-size: 17px; margin-top: 14px; padding-top: 14px; border-top: 2px solid var(--border); }
/* The one figure in the card that is the point of it. */
.example-total .example-row-value { font-size: 17px; color: var(--accent); }
.example-note { margin: auto 0 0; padding-top: 20px; font-size: 12.5px; line-height: 1.6; color: var(--muted-light); }

/* Was the stock arrangement: centred heading, then full-width bordered rows
   across an 800px column. Split in two instead, heading and the way out to a
   real person on the left, the questions on the right. That also breaks the
   centred-heading rhythm every section above it shares, which is the point:
   by this far down the page the visitor has seen it four times. */
.faq-section { max-width: 1080px; --section-top: clamp(52px, 5.5vw, 72px); --section-bottom: clamp(56px, 6vw, 80px); }
.faq-layout {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: 56px;
  align-items: start;
}
/* Sticky, so the heading and the contact offer stay with the questions while
   a long answer is open. 104px clears the sticky header. */
.faq-aside { position: sticky; top: 104px; }
.faq-aside .section-title { font-size: clamp(26px, 3vw, 34px); }
.faq-aside .section-sub { font-size: 15px; }
.faq-help {
  margin-top: 28px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}
.faq-help-text { margin: 0 0 14px; font-size: 14px; line-height: 1.6; color: var(--muted); }
/* --border is 8% white, which is right for dividing a card off its background
   and too faint to say "button" on its own. */
.faq-help-cta {
  display: inline-block;
  padding: 12px 22px;
  font-size: 14px;
  border-color: color-mix(in srgb, var(--accent) 45%, transparent);
  color: var(--accent);
}
.faq-help-cta:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); opacity: 1; }

.faq-list { display: flex; flex-direction: column; gap: 10px; }
/* Cards rather than rules: a row that can open needs to look like an object
   you can press, and the open one needs to be able to say so. */
.faq-item {
  background: var(--card-bg);
  border: 1px solid var(--border);
  border-left: 2px solid transparent;
  border-radius: 14px;
  padding: 4px 20px;
  transition: border-color 0.2s ease, background 0.2s ease;
}
.faq-item:hover { border-color: color-mix(in srgb, var(--accent) 30%, var(--border)); }
.faq-item.is-open {
  border-left-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 5%, var(--card-bg));
}
.faq-question {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 18px;
  width: 100%;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  padding: 16px 0;
  font-family: 'Sora', sans-serif;
  font-weight: 600;
  font-size: 15.5px;
  line-height: 1.4;
  color: var(--ink);
}
/* Drawn rather than typed: the indicator used to be a literal "+" glyph
   rotated 45 degrees, which lands as a lopsided x in Sora. Two bars, and the
   upright one goes away, so a plus becomes a minus. */
.faq-indicator {
  position: relative;
  flex-shrink: 0;
  width: 15px;
  height: 15px;
}
.faq-indicator::before,
.faq-indicator::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  height: 2px;
  margin-top: -1px;
  border-radius: 2px;
  background: var(--accent);
}
.faq-indicator::after { transition: transform 0.3s ease, opacity 0.3s ease; transform: rotate(90deg); }
.faq-item.is-open .faq-indicator::after { transform: rotate(0deg); opacity: 0; }
.faq-answer-row { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 0.35s ease; }
.faq-item.is-open .faq-answer-row { grid-template-rows: 1fr; }
.faq-answer-inner { overflow: hidden; }
.faq-answer { margin: 0 0 18px; padding-right: 32px; color: var(--muted); font-size: 14.5px; line-height: 1.65; }

@media (max-width: 900px) {
  .faq-layout { grid-template-columns: minmax(0, 1fr); gap: 32px; }
  /* Nothing to stick to once it is one column: it would follow the visitor
     down over the questions it belongs to. */
  .faq-aside { position: static; }
  .faq-answer { padding-right: 0; }
}

/* "Why us". Breaks the eyebrow/title/subtitle/card-grid rhythm the other
   sections share by giving the first card the full width: the arrival-date
   idea is the whole argument, and it should not sit as one tile among five. */
/* The densest block on the page, and the one the whole argument rests on, so
   this is where the page's air is actually spent. */
.why-section { --section-top: clamp(52px, 5.5vw, 72px); --section-bottom: clamp(56px, 6vw, 80px); }
/* Looser than the shared .section-title, because this is the one section title
   that runs to two sentences and wraps. Folded out of public/overrides.css on
   2026-08-08. */
.why-section .section-title { line-height: 1.4; }
/* Photography. One component for every picture on the page rather than a
   bespoke rule per slot, so a new photo is a figure and a class.

   width/height are on the <img> itself and the frame carries an aspect-ratio,
   so the space is reserved before the file arrives: a photo dropped in later
   cannot shove the section below it down the page as it loads. */
.photo {
  position: relative;
  margin: 0;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 18px;
  background: var(--card-bg);
}
.photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The page is near-black and most stock photography is not. Pulled down and
     slightly desaturated so a bright frame does not tear a hole in the middle
     of the section; the accent-lit UI around it stays the brightest thing. */
  filter: brightness(0.82) saturate(0.9);
  transition: filter 0.5s ease, transform 0.7s ease;
}
.photo:hover img { filter: brightness(0.95) saturate(1); transform: scale(1.03); }
.photo-4x3 { aspect-ratio: 4 / 3; }
.photo-wide { aspect-ratio: 21 / 9; }

/* Sits on the picture, so it needs its own scrim: over an unknown photo,
   text alone is a coin toss. */
.photo-caption {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 40px 20px 18px;
  font-size: 14px;
  line-height: 1.5;
  color: #eff2ef;
  background: linear-gradient(to top, rgba(3, 6, 5, 0.92) 0%, rgba(3, 6, 5, 0.7) 45%, transparent 100%);
}
.photo-caption-label {
  display: block;
  margin-bottom: 3px;
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.photo-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
  margin-bottom: 16px;
}
/* One of the pair can be dropped by landing.js when its file is missing, and a
   lone 4:3 stretched across the full width would be enormous. */
.photo-pair:has(.photo:only-child) { grid-template-columns: minmax(0, 1fr); max-width: 640px; margin-inline: auto; }
/* Both dropped: the wrapper is cleared by landing.js rather than by :empty
   here, which never matches once the source has a newline between the two
   figures. */
.photo-pair.hidden { display: none; }

/* The stand-in for a photograph that has not been found yet. Only ever drawn
   on localhost (see landing.js), so it can afford to be explicit rather than
   pretty: it names the file to create and the size to create it at. */
.photo.is-placeholder {
  border-style: dashed;
  border-color: color-mix(in srgb, var(--accent) 30%, transparent);
  background: color-mix(in srgb, var(--accent) 4%, transparent);
}
.photo-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 24px;
  text-align: center;
  color: var(--muted);
}
.photo-placeholder svg {
  width: 30px;
  height: 30px;
  margin-bottom: 6px;
  color: color-mix(in srgb, var(--accent) 65%, transparent);
}
.photo-placeholder-file {
  font-family: ui-monospace, 'Cascadia Code', Consolas, monospace;
  font-size: 13px;
  color: var(--accent);
}
.photo-placeholder-desc { font-size: 13px; line-height: 1.5; max-width: 30ch; }
.photo-placeholder-size {
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted-light);
}
/* The caption is real copy that ships, so it stays visible over the stand-in,
   but without the photo scrim it would be a gradient over nothing. */
.photo.is-placeholder .photo-caption { background: none; padding-top: 18px; }

@media (max-width: 720px) {
  .photo-pair { grid-template-columns: minmax(0, 1fr); }
}

.why-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px;
}
/* Each card is three layers: a coloured field, a dark panel over it, and the
   copy on the panel. It is the arrangement a WebGL shader card uses, done with
   gradients, because the reference component is a React wrapper around a
   @paper-design/shaders-react canvas and this project has no React, no build
   step and no appetite for a WebGL context per card in a five-card grid.
 *
   What the shader actually gives you is a warped field of two or three colours
   with a fine pattern laid over it. Three gradients at different angles and a
   dot screen get close enough at this size, behind a panel that covers most of
   it, for a fraction of the cost.
 *
   --i is which colour set the card takes, set per card in the markup. */
.why-card {
  position: relative;
  /* The field and the screen sit at negative z-index so the copy, which is not
     positioned, still paints over them. Without an isolated stacking context
     here they would go behind the section instead and vanish. */
  isolation: isolate;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 24px;
  background: var(--card-bg);
  /* Five sets, all in the brand's greens. The reference runs a different
     saturated hue per card (purple, cyan, orange, violet, pink), which is right
     for a component demo and would be six new colours on a page that has four.
     These vary by depth instead of by hue, so the grid reads as one material
     lit five ways. */
  --f1: #1c6c4c;
  --f2: #36bb83;
  --f3: #052016;
}
.why-card:nth-child(2) { --f1: #0f4530; --f2: #299468; --f3: #052016; }
.why-card:nth-child(3) { --f1: #0b3125; --f2: #1c6c4c; --f3: #041d14; }
.why-card:nth-child(4) { --f1: #299468; --f2: #7dbf8a; --f3: #062518; }
.why-card:nth-child(5) { --f1: #052016; --f2: #1c6c4c; --f3: #041d14; }

/* The field. Oversized and rotated so its edges are never in frame, which is
   what lets it turn without a corner swinging into view.
 *
   The rotation and the drift are both off --bg-scroll, the 0..1 document
   position public/bg.js writes on .why-section while it is on screen. Same
   rule as the wallpaper: this
   moves while the page is being scrolled and stands still the moment it stops.
   There is no timer. With no script, or under reduced motion where bg.js writes
   nothing, it falls back to 0 and every card is a still field. */
.why-card::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  background:
    radial-gradient(ellipse 70% 80% at calc(20% + var(--bg-scroll, 0) * 22%) 15%, var(--f2) 0%, transparent 68%),
    radial-gradient(ellipse 80% 70% at calc(85% - var(--bg-scroll, 0) * 18%) 80%, var(--f1) 0%, transparent 66%),
    /* Two of the five stops, not three: with --f3 on every other stop the dark
       took half the sweep and the colour never got a chance to show through the
       panel. */
    conic-gradient(from calc(var(--bg-scroll, 0) * 220deg) at 50% 50%, var(--f3), var(--f1), var(--f2), var(--f1), var(--f3));
  transform: scale(1.7) rotate(calc(var(--bg-scroll, 0) * -40deg));
  filter: blur(14px);
}

/* The panel, and the screen over the field, in one layer. The dots are the
   shader's `shape` parameter: at this scale they are the difference between a
   soft blurred wash and something that looks made.
 *
   0.68 alpha on the panel is measured. The lightest colour any card uses is
   #8fb996; under this panel that composites to about rgb(53,80,64), which
   leaves the 14.5px body copy (--muted, #e0e1dd) at 6.71:1 and the titles well
   past that. It started at 0.74, which was safe by a wider margin and left the
   field so far under the panel that all five cards read as the same flat dark.
 *
   The dots are faint and widely spaced on purpose. At half this spacing and
   twice this alpha the screen stopped being a texture and became graph paper,
   which is a stronger thing than the copy sitting on it. */
.why-card::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: rgba(5, 32, 22, 0.68);
  background-image: radial-gradient(circle, rgba(238, 242, 235, 0.22) 0.5px, transparent 0.6px);
  background-size: 14px 14px;
}

.why-card-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  margin-bottom: 16px;
  border-radius: 12px;
  border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  color: var(--accent);
}
.why-card-icon svg { width: 21px; height: 21px; }

.why-card-lead {
  grid-column: 1 / -1;
  border-left: 3px solid var(--accent);
}
.why-card-lead .why-card-icon { width: 48px; height: 48px; }
.why-card-lead .why-card-icon svg { width: 24px; height: 24px; }
.why-card-lead .why-card-title { font-size: 21px; }
.why-card-lead .why-card-text { font-size: 16px; max-width: 62ch; }
/* line-height folded out of public/overrides.css on 2026-08-08. The editor had
   written it as five separate rules, one per card, all with the same value:
   the lead card by class and the other four by nth-of-type. Five identical
   rules covering every card in the set is one rule, which is what it is now. */
.why-card-title { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 17px; line-height: 1.24; margin: 0 0 8px; }
.why-card-text { margin: 0; color: var(--muted); font-size: 14.5px; line-height: 1.65; }

/* Aimed at shop owners, not shoppers, so it is deliberately quieter than the
   customer CTA above it and sits below it in the page order. */
/* The one band addressed to shops rather than to customers, so it is the one
   band that changes colour: brown is the warmest of the five and marks the
   change of audience. Its own muted tone, because sage on brown is 2.99:1 and
   would fail; bone dimmed against brown clears it instead. The accent would
   also disappear here (olive on brown is 1.23:1), so the eyebrow borrows the
   band's own text colour. */
.partner-band {
  border-top: 1px solid var(--border);
  padding: 56px 32px;
  background: #7e5e48;
  /* Not a dimmed bone: brown is light enough that even undimmed bone only
     reaches 5.12:1, so any softening at all drops under 4.5:1 (82% measured
     3.13:1). Everything on this band is full-strength bone, and the hierarchy
     is carried by size and weight, which it already was. */
  --muted: var(--ink);
}
.partner-band .partner-eyebrow { color: var(--ink); }
/* On brown the filled olive button loses its edge (1.23:1), so the call to
   action inverts: bone fill, brown label. */
.partner-band .btn-primary { background: var(--ink); color: #4f3a2c; }
.partner-band .btn-primary:hover { background: #ffffff; }
.partner-inner {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 32px;
  flex-wrap: wrap;
}
.partner-text { max-width: 62ch; }
.partner-eyebrow {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 8px;
}
.partner-title { font-family: 'Sora', sans-serif; font-weight: 700; font-size: clamp(20px, 2.6vw, 26px); margin: 0 0 10px; }
.partner-sub { margin: 0; color: var(--muted); font-size: 15px; line-height: 1.65; }
.partner-cta { flex-shrink: 0; display: inline-block; padding: 14px 26px; }

@media (max-width: 720px) {
  .why-grid { grid-template-columns: minmax(0, 1fr); }
  .partner-inner { flex-direction: column; align-items: flex-start; }
  .partner-cta { width: 100%; text-align: center; }
}

.cta-band { background: var(--cta-band-bg); color: #ffffff; padding: 80px 32px; text-align: center; }
.cta-band-title { font-family: 'Sora', sans-serif; font-weight: 700; font-size: clamp(28px, 4vw, 40px); margin: 0 0 16px; }
.cta-band-sub { font-size: 16px; opacity: 0.75; margin: 0 0 32px; }
.cta-band .btn-primary { display: inline-block; padding: 16px 32px; font-size: 16px; }

.site-footer { padding: 48px 32px 32px; }
.footer-inner { max-width: 1200px; margin: 0 auto; }
.footer-top { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 32px; }
.footer-brand { max-width: 260px; }
.footer-logo { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 20px; margin-bottom: 8px; }
.footer-tagline { font-size: 13px; color: var(--muted-light); line-height: 1.6; }
.footer-contact { display: flex; flex-direction: column; gap: 4px; margin-top: 10px; }
.footer-phone { display: inline-block; font-size: 14px; font-weight: 600; color: var(--accent); }
.footer-phone:hover { opacity: 0.8; }
/* align-items, because .footer-top is a flex row and this nav is one of its
   items: left to stretch, the nav took the height of the brand block beside it
   and every link inside stretched with it, giving eight 21px labels a 102px
   click target and, for anyone tabbing through, a focus ring five times the
   height of its own text. */
.footer-nav { display: flex; gap: 24px; flex-wrap: wrap; align-items: flex-start; }
.footer-nav a { font-size: 14px; color: var(--muted); }
.footer-bottom {
  margin-top: 32px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  color: var(--muted-light);
  text-align: center;
}
.footer-bottom .accent-word { color: var(--accent); }

/* A fixed notice covers whatever the fold happens to land on, and on a phone
   that was the hero's order card: the mockup the page is selling with sat half
   hidden under a legal notice for the visitor's first few seconds. Two parts to
   the fix. Here, the page reserves room at the bottom for whatever is currently
   pinned there, so scrolling to the end no longer buries the CTA or the footer
   under it; landing.js measures the notice and the survey tab that stacks above
   it and sets --bottom-overlay-h. And in landing.js, on a phone the notice waits
   until the visitor has actually started reading. */
body.has-bottom-overlay { padding-bottom: var(--bottom-overlay-h, 0px); }

.cookie-banner {
  position: fixed;
  left: 20px;
  right: 20px;
  bottom: 20px;
  z-index: 300;
  max-width: 720px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  background: color-mix(in srgb, var(--page-bg) 92%, black);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid var(--accent);
  border-radius: 16px;
  padding: 18px 22px;
  box-shadow: 0 20px 45px rgba(0, 0, 0, 0.5), 0 0 0 4px var(--accent-soft), 0 0 32px color-mix(in srgb, var(--accent) 35%, transparent);
  animation: cookieBannerIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.cookie-banner.hidden { display: none; }
@keyframes cookieBannerIn {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: translateY(0); }
}
.cookie-banner-text { flex: 1 1 380px; margin: 0; font-size: 13.5px; color: var(--muted); line-height: 1.6; }
.cookie-banner-text a { color: var(--accent); font-weight: 600; }
.cookie-banner-btn { flex-shrink: 0; padding: 10px 22px; font-weight: 700; }

/* Two answers now, not one acknowledgement. Analytics needs opt-in consent,
   and consent means declining has to be as easy as agreeing. */
.cookie-banner-actions { display: flex; gap: 10px; align-items: center; flex-shrink: 0; }

/* Quieter than the accept button but deliberately not hidden or greyed out:
   same size, same padding, same hit area, readable contrast. A decline styled
   as a barely-visible link is the pattern regulators single out, and it would
   make the consent it collects worthless anyway. */
.cookie-banner-btn-ghost {
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font-size: 14px;
  cursor: pointer;
}

.cookie-banner-btn-ghost:hover { border-color: var(--muted-light); }

.cookie-banner-btn-ghost:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Until these existed, the page had no width breakpoints at all, the only
   media query in this file was the reduced-motion one below. Everything relied
   on flex-wrap, so on a phone the header's nav links wrapped into a tall
   vertical stack and pushed the logo's accent dot onto its own line. Most
   traffic here is a phone, so that was the first thing a visitor saw. */

/* Tablet and below: the in-header nav is the thing that breaks first. There's
   no hamburger menu, so the links are dropped rather than stacked, every one
   of them is a jump link to a section the visitor scrolls past anyway, and
   "Prisijungti"/"Užsakyti" are the only two that actually need to be reachable
   from the header. */
@media (max-width: 900px) {
  .nav { display: none; }
  .header-inner { padding: 14px 20px; }
  .hero { padding: 56px 20px 56px; gap: 40px; }
  .section { padding-inline: 20px; }

  /* Two abreast. Only the left-hand step of each pair has a neighbour to point
     at; the segment leaving the right-hand one would run off the edge. */
  .steps-list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 30px 24px; }
  .step-row::before { right: -16px; }
  .step-row:nth-child(even)::before { content: none; }
}

@media (max-width: 560px) {
  /* One column, and with nothing beside it the marker sits next to the text
     instead of above it, so four steps do not read as four separate blocks. */
  .steps-list { grid-template-columns: minmax(0, 1fr); gap: 22px; }
  .step-row { flex-direction: row; align-items: flex-start; gap: 16px; }
}

@media (max-width: 700px) {
  /* Single column, with the example order card under the copy rather than
     competing with it for a 390px-wide screen. */
  .hero { flex-direction: column; align-items: flex-start; padding: 40px 20px 48px; }
  .hero-copy, .hero-visual { flex: 1 1 auto; width: 100%; }
  .hero-visual { justify-content: flex-start; }
  .hero-card { max-width: none; }
  .hero-sub { font-size: 16px; max-width: none; }
  .hero-actions { gap: 10px; }
  .hero-actions .btn-primary, .hero-actions .btn-secondary { flex: 1 1 auto; text-align: center; }

  /* Not sticky here: on a phone a pinned bar is screen the story cannot use.
     `relative` rather than `static` though, because a static element ignores
     z-index entirely, and the header's z-index: 50 went with it. The story
     section is position: relative with a z-index of its own, so it won the
     stack and painted its full-viewport beat straight over the header, leaving
     `Pranešti man` visible, normal-looking and completely dead to the touch on
     every phone width. relative keeps the header in the flow exactly as static
     did and gives the z-index something to apply to. */
  .site-header { position: relative; z-index: 50; }
  .login-link span { display: none; }
  .header-actions { gap: 10px; }

  .footer-top { flex-direction: column; gap: 24px; }
  .footer-nav { gap: 14px 20px; }

  /* Stacked, the banner's own padding plus the button made it eat most of a
     phone screen. */
  .cookie-banner { left: 12px; right: 12px; bottom: 12px; padding: 14px 16px; gap: 12px; }
  .cookie-banner-text { flex: 1 1 100%; font-size: 12.5px; }
  /* Side by side on one row rather than stacked full-width: the notice already
     eats a chunk of a phone screen, and two stacked buttons made it taller
     than the hero card it is sitting over. */
  .cookie-banner-actions { width: 100%; }
  .cookie-banner-actions .cookie-banner-btn { flex: 1 1 0; width: auto; padding: 11px 12px; }
}

/* A card resting on the page's gradient wallpaper can be translucent, and
   --card-bg (rgba(255,255,255,0.03) here) is tuned for exactly that. A dialog
   floating over the page cannot: the landing copy read straight through the
   survey. Scoped to body.landing-page, since that is the only page with the
   see-through palette, and the standalone login/register cards are meant to
   sit on the wallpaper. The backdrop goes darker too, so what is behind the
   dialog stays out of the way rather than competing with it. */
body.landing-page .overlay { background: rgba(3, 5, 7, 0.82); }
body.landing-page .overlay .auth-card {
  background: color-mix(in srgb, var(--page-bg) 94%, white);
  border: 1px solid var(--border);
}

/* ---- Ordering closed ----
   Both states are in the markup and CSS picks one, rather than JavaScript
   rewriting buttons after paint. That way the page is never briefly showing
   the wrong one, and there is no flash of "Užsakyti" on a site that is shut.
   The server adds .ordering-closed to <body> before sending the page (see
   sendWithOrderingState in server.js).

   This is for buttons and the copy around them. Price copy is not done this
   way: display:none still ships the number to anyone who opens view-source,
   so the fee is cut out of the response instead, see lib/pageState.js. */
[data-closed-cta] { display: none; }
body.ordering-closed [data-order-cta] { display: none; }
body.ordering-closed [data-closed-cta] { display: revert; }
/* Buttons are inline-flex in this stylesheet, and `revert` on an <a> would
   give it back `inline`, which drops the padding. */
body.ordering-closed a[data-closed-cta] { display: inline-flex; }

/* Sticky phone CTA. Hidden outright above 700px, where the header is sticky
   and already carries the same button: two pinned copies of one action is
   just clutter, and the desktop never loses the header the way a phone does.
 *
 * display:none rather than opacity or a transform, so it is out of the tab
 * order on desktop instead of being an invisible thing a keyboard user can
 * still land on.
 *
 * This knowingly overrides the note in the 700px block above ("not sticky
 * here: on a phone a pinned bar is screen the story cannot use"). That call
 * was made for the header and still holds for it, the header is not pinned on
 * a phone. It was reconsidered for this bar and the trade was taken on
 * purpose: the bar is visible from page load, including over the opening
 * story, in exchange for a call to action that is always one tap away. If the
 * story ever needs its full height back, hiding this until the reader is past
 * the hero is the change to make, not deleting it. */
.mobile-cta { display: none; }

@media (max-width: 700px) {
  .mobile-cta {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* Above the survey tab (150) and the cookie notice, below the overlays
       (200) so an open dialog's backdrop covers this rather than leaving a
       lit button floating over it. */
    z-index: 160;
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px));
    background: color-mix(in srgb, var(--page-bg) 92%, transparent);
    /* The page scrolls under this, so without a backdrop the button sits on
       whatever text happens to be behind it. */
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-top: 1px solid var(--border);
    transform: translateY(0);
    transition: transform 220ms ease, opacity 220ms ease;
  }

  .mobile-cta-btn {
    flex: 1 1 auto;
    justify-content: center;
    text-align: center;
    padding: 14px 18px;
    font-size: 15px;
  }

  /* The notice and the survey tab own the same corner, so they move up by
     however tall this bar is (measured in landing.js, see --mobile-cta-h). */
  .cookie-banner { bottom: calc(12px + var(--mobile-cta-h, 0px)); }
}

@media (prefers-reduced-motion: reduce) {
  .mobile-cta { transition: none; }
}

.waitlist-form { max-width: 480px; margin: 0 auto; }
.waitlist-row { display: flex; gap: 10px; flex-wrap: wrap; }
.waitlist-row input {
  flex: 1 1 240px;
  min-width: 0;
  padding: 14px 16px;
  background: color-mix(in srgb, var(--page-bg) 88%, white);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--ink);
  font: inherit;
  font-size: 15px;
}
.waitlist-row input:focus { outline: none; border-color: var(--accent); }
.waitlist-row input::placeholder { color: var(--muted-light); }
.waitlist-row .btn-primary { flex: 0 0 auto; }
.waitlist-note { margin: 12px 0 0; font-size: 12.5px; color: var(--muted-light); }
.waitlist-error {
  margin: 12px 0 0;
  font-size: 13.5px;
  color: #ff8a8a;
}
.waitlist-error.hidden, .waitlist-done.hidden { display: none; }
.waitlist-done {
  max-width: 480px;
  margin: 0 auto;
  padding: 16px 20px;
  border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
  background: color-mix(in srgb, var(--accent) 10%, transparent);
  border-radius: 14px;
  color: var(--ink);
  font-size: 14.5px;
}

/* The hero's paste-a-link box. Left-aligned rather than centred like the
   waitlist form, because it sits in the story's left-aligned copy column and a
   centred field there reads as a different section's stray element. */
/* 34, not 22. The extra twelve was a `translate: 0 12px` the live design editor
   had saved onto #try-url, #try-submit and #try-error separately
   (public/overrides.css), which moved the three of them down without moving the
   form. That was invisible while the input was the only thing in its own box.
   It stopped being invisible once the input gained a wrapper for the floating
   label: translate paints, it does not lay out, so the wrapper stayed put and
   the label ended up sitting twelve pixels above the field it belongs to.
   Folded in here as margin, which is what it always meant, and the three
   editor rules are gone. */
.try-form { margin: 34px 0 0; max-width: 520px; }
/* stretch, so the button is exactly as tall as the field beside it. The two are
   one control split in half and a button sitting short of the box it belongs to
   reads as an afterthought. The input sets the height; the button follows it,
   which is why its own padding below is horizontal only. */
.try-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: stretch; }

/* The field the whole page is built around: paste a product link here.
 *
 * The label starts inside the empty box, where a placeholder would be, and
 * lifts onto the top edge as soon as the field is focused or has anything in
 * it. That is the point of doing it this way rather than with a placeholder:
 * a placeholder is gone the moment you start typing, and takes with it the
 * only thing on screen saying what the box wants. A label that moves is still
 * there while you check what you pasted.
 *
 * No script. The two states are :focus-within and :placeholder-shown, both of
 * which CSS can see on its own. */
.try-field {
  position: relative;
  /* flex, and this is not cosmetic. As a block container the wrapper held the
     newlines around the input as inline content, which generated a line box
     above the input and made the wrapper 61px tall against the input's 49: the
     input hung 12px below its own wrapper, and the label, which is centred on
     the wrapper, sat 12px above the text it was standing in for. A flex
     container discards whitespace-only children outright, so the wrapper is
     the input's height and the two agree on where the middle is. */
  display: flex;
  flex: 1 1 260px;
  min-width: 0;
}

.try-row input {
  /* block, not the default inline-level. An inline input sits on its parent's
     text baseline, and the leading under that baseline pushed it 12px down
     inside .try-field: the wrapper's box and the input's box no longer lined
     up, so the label, which is centred on the wrapper, sat 12px high. */
  display: block;
  width: 100%;
  min-width: 0;
  padding: 14px 18px;
  background: color-mix(in srgb, var(--page-bg) 88%, white);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--ink);
  font: inherit;
  font-size: 15px;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
/* A visible ring, not just a border tint: this is the page's primary control
   now, and a 1px colour change is not a focus indicator. */
.try-row input:focus-visible,
.try-row input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 28%, transparent);
}
/* The placeholder is a single space holding :placeholder-shown open, and the
   label is sitting on top of it. Nothing should ever render. */
.try-row input::placeholder { color: transparent; }

.try-label {
  position: absolute;
  left: 18px;
  top: 50%;
  transform: translateY(-50%);
  /* Clicks go to the field under it, which is what a label over an input has to
     do; `for` still moves focus for anyone using the keyboard. */
  pointer-events: none;
  font-size: 15px;
  /* Deliberately not line-height: 1. The input's text is laid out at the
     font's natural line height, and a label squeezed to a tighter box centres
     its own box correctly while sitting a couple of pixels above the text it is
     standing in for. Matching the input's metrics is what puts the two on the
     same line. */
  line-height: normal;
  color: var(--muted-light);
  transition: top 0.18s ease, left 0.18s ease, font-size 0.18s ease, color 0.18s ease;
}
/* The span carries the background, not the label, so the patch it punches in
   the border is exactly as wide as the words. It is the field's own fill
   rather than the page's: the field sits on the moving wallpaper, and a patch
   painted in the page colour would be a pale rectangle floating over whatever
   the pour happened to be doing behind it. */
.try-label span {
  display: inline-block;
  padding: 0 6px;
  background: color-mix(in srgb, var(--page-bg) 88%, white);
  border-radius: 4px;
}

.try-field:focus-within .try-label,
.try-field input:not(:placeholder-shown) ~ .try-label {
  top: 0;
  left: 14px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink);
}
.try-field:focus-within .try-label { color: var(--accent); }

@media (prefers-reduced-motion: reduce) {
  .try-label { transition: none; }
}
.try-row .btn-primary {
  flex: 0 0 auto;
  /* No vertical padding: the row stretches it to the field's height, and
     padding on top of that would only make it taller than the thing it is
     supposed to match. */
  padding: 0 32px;
  font-size: 15px;
  /* The stretch above only works while the button shares a flex line with the
     field. The row wraps on a phone, and a lone item on its own line stretches
     to a line whose height is its own content, so the zero padding left a 17px
     sliver on every phone width: unreadable as a button and less than half the
     44px a touch target owes. The floor is the field's own height, so nothing
     changes on the one line where stretch was already doing the job. */
  min-height: 49px;
}
.try-note { margin: 12px 0 0; font-size: 12.5px; color: var(--muted-light); }
.try-error { margin: 12px 0 0; font-size: 13.5px; color: #ff8a8a; }
.try-error.hidden { display: none; }
/* Both of the above are <p>, and story.css styles `.story-copy p` at up to
   20px, which outranks a lone class. Two classes beats one class plus an
   element, so these win on specificity rather than on load order (landing.css
   is linked before story.css, so order would lose). */
.try-form .try-note { font-size: 12.5px; line-height: 1.5; max-width: 46ch; color: var(--muted-light); }
.try-form .try-error { font-size: 13.5px; line-height: 1.5; color: #ff8a8a; }

/* The result dialog. */
.try-card { text-align: left; }
.try-product {
  display: flex;
  gap: 16px;
  align-items: center;
  margin: 0 0 20px;
  padding: 14px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--card-bg);
}
.try-image {
  width: 78px;
  height: 78px;
  flex: 0 0 auto;
  object-fit: cover;
  border-radius: 10px;
  background: color-mix(in srgb, var(--page-bg) 80%, white);
}
.try-image.hidden { display: none; }
.try-product-text { min-width: 0; }
.try-title {
  margin: 0;
  font-family: 'Sora', sans-serif;
  font-size: 16px;
  line-height: 1.35;
  color: var(--ink);
}
.try-price { margin: 6px 0 0; font-size: 13.5px; color: var(--muted); }
.try-secondary { margin-top: 10px; width: 100%; justify-content: center; }
#try-found.hidden, #try-unknown.hidden { display: none; }

/* The same signup, sitting inside the survey's thank-you screen. */
.survey-waitlist {
  margin: 0 0 22px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
  text-align: left;
}
.survey-waitlist-lead { margin: 0 0 14px; font-size: 14px; line-height: 1.6; color: var(--muted); }
.survey-waitlist .waitlist-form { max-width: none; }

/* Screen readers still get the label; sighted users get the placeholder. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 560px) {
  .waitlist-row { flex-direction: column; }
  .waitlist-row .btn-primary { width: 100%; }
}

/* The standalone /apklausa page: the same card as the dialog, but resting on
   the page instead of floating over it. */
.survey-page {
  max-width: 640px;
  margin: 0 auto;
  padding: 48px 20px 72px;
}
/* Solid, for the same reason the dialog is: --card-bg is 3% white here, which
   is a long form to read through a translucent panel. */
.survey-page .auth-card {
  width: 100%;
  background: color-mix(in srgb, var(--page-bg) 94%, white);
  border: 1px solid var(--border);
}
@media (max-width: 700px) {
  .survey-page { padding: 28px 14px 48px; }
  .survey-page .auth-card { padding: 28px 20px; }
}

/* ---- Survey: the floating invite tab, and the form it opens ---- */

/* Below .overlay (200) and the cookie banner (300) deliberately. At 250 it
   floated on top of a modal's dimmed backdrop, un-blurred, which read as a
   stray element rather than as something behind the dialog. The cookie notice
   outranking it matters too: that one is legally required to stay readable.
   `bottom` is overwritten inline by landing.js while the notice is up, so the
   two stack instead of sharing the corner. */
.survey-tab {
  position: fixed;
  left: 20px;
  bottom: 20px;
  z-index: 150;
  max-width: 320px;
  /* Built up from --page-bg, not --card-bg. On the landing page --card-bg is
     rgba(255,255,255,0.03), which is right for a card resting on the gradient
     wallpaper and wrong for something floating over whatever happens to be
     scrolled underneath: the copy behind it showed straight through. Lifted
     slightly off the page colour so it still reads as a raised surface. */
  background: color-mix(in srgb, var(--page-bg) 90%, white);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: 0 18px 40px rgba(0, 0, 0, 0.45);
  animation: surveyTabIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.survey-tab.hidden { display: none; }
@keyframes surveyTabIn {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

.survey-tab-open {
  display: flex;
  align-items: center;
  gap: 13px;
  width: 100%;
  padding: 15px 40px 15px 17px;
  background: none;
  border: none;
  border-radius: 16px;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}
.survey-tab-open:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }

.survey-tab-icon {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}
.survey-tab-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.survey-tab-title { font-weight: 700; font-size: 14.5px; color: var(--ink); }
.survey-tab-sub { font-size: 12.5px; color: var(--muted); }

/* Dismissing has to be as easy as opening, or the tab reads as a trap. */
.survey-tab-close {
  position: absolute;
  top: 7px;
  right: 7px;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  border-radius: 7px;
  color: var(--muted-light);
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}
.survey-tab-close:hover { color: var(--ink); background: var(--page-bg); }

/* Two classes deep so it beats .auth-card's 400px regardless of whether
   landing.css or auth.css is loaded second. The extra width is for the
   answer options, several of which are a full sentence. */
.auth-card.survey-card { max-width: 560px; }

/* ---- The survey is neutral dark, not green ----
 *
 * Everything else on this page is built out of the landing palette, whose
 * --page-bg, --card-bg and --border are all tinted green, so the survey card
 * came out as one more green panel on a green floor and read as part of the
 * page rather than as a thing to sit down and fill in.
 *
 * The fix is the four surface tokens, not the individual rules: every element
 * inside the card already draws itself out of them (the option rows, their
 * borders, the section intro's inset panel, the punched-out radio dot), so
 * restating them here moves the whole form at once and nothing inside needs to
 * know. They are the site's own base palette from :root, which the shelf and
 * the auth pages use and which was neutral all along.
 *
 * --accent stays sage. It is the brand, it is only ever a few small marks here
 * (section titles, the eyebrow, a checked box) and at 7.24:1 on this surface it
 * is doing no harm. Neutral is what the panel had to be, not the whole thing.
 *
 * The selectors are heavy on purpose. `body.landing-page .overlay .auth-card`
 * further up sets the green panel and is (0,3,1); anything lighter than this
 * loses to it silently. The second one is for /apklausa, where the same card
 * rests on the page instead of floating over it.
 *
 * Measured on this surface: body copy 7.88:1, the 12.5px hints 5.17:1, the sage
 * 7.24:1. --muted-light is the one value not taken straight from :root: the
 * base #797d84 is 4.34:1 here, just under the line for text that small, so it
 * goes up one step. */
body.landing-page .overlay .auth-card.survey-card,
.survey-page .auth-card.survey-card {
  --page-bg: #101215;
  --card-bg: #1a1c21;
  --border: #292c33;
  --ink: #f4f2eb;
  --muted: #a7acb6;
  --muted-light: #838a96;
  background: #15171c;
  border: 1px solid var(--border);
}

/* The tab that opens it, so the survey is the same colour before and after it
   is opened. Same lift off --page-bg the rule above it uses, just neutral now. */
body.landing-page .survey-tab {
  --page-bg: #101215;
  --border: #292c33;
  --ink: #f4f2eb;
  --muted: #a7acb6;
}

/* Numbered from the stylesheet rather than from survey.js: the questions come
   off the server in one list and are dropped into sections as they go, so
   nothing in the rendering knows what number it is up to. A counter does, and
   it stays right if a question is ever added, reordered, or held back (q11 is,
   while the fee is not public). */
.survey-questions { display: flex; flex-direction: column; gap: 26px; counter-reset: survey-q; }
.survey-q { counter-increment: survey-q; }
.survey-q-label::before {
  content: counter(survey-q) '.';
  margin-right: 8px;
  color: var(--accent);
  font-weight: 700;
}

.survey-section {
  margin-top: 8px;
  padding-top: 22px;
  border-top: 1px solid var(--border);
}
.survey-section:first-child { margin-top: 0; padding-top: 0; border-top: none; }
.survey-section-title {
  font-family: 'Sora', sans-serif;
  font-weight: 700;
  font-size: 15px;
  color: var(--accent);
  margin: 0 0 8px;
}
.survey-section-intro {
  margin: 0;
  font-size: 13.5px;
  line-height: 1.65;
  color: var(--muted);
  padding: 13px 15px;
  background: var(--page-bg);
  border-radius: 12px;
}

.survey-q-label {
  display: block;
  font-weight: 600;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink);
  margin-bottom: 4px;
}
.survey-q-hint { display: block; font-size: 12.5px; color: var(--muted-light); margin-bottom: 11px; }
.survey-q-label + .survey-options, .survey-q-label + input, .survey-q-label + textarea { margin-top: 11px; }

.survey-options { display: flex; flex-direction: column; gap: 7px; }
.survey-option {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: 11px;
  font-size: 14px;
  line-height: 1.45;
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.survey-option:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.survey-option.is-checked {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 9%, transparent);
  color: var(--ink);
}

/* The box and the tick are drawn here rather than left to the browser. A
   native control takes its shape and its animation from the OS, so accent-color
   was the only thing this page could say about it, and a row of Windows
   checkboxes is the one part of the form that could not be made to look like
   the rest of the site. The input itself stays: still focusable, still
   keyboard-operable, still announced as a checkbox or a radio. */
.survey-option input {
  appearance: none;
  -webkit-appearance: none;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin: 1px 0 0;
  border: 1.5px solid color-mix(in srgb, var(--muted) 60%, transparent);
  background: transparent;
  cursor: inherit;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.survey-option input[type='radio'] { border-radius: 50%; }
.survey-option input[type='checkbox'] { border-radius: 5px; }
.survey-option:hover input { border-color: color-mix(in srgb, var(--accent) 60%, transparent); }
.survey-option input:checked { border-color: var(--accent); background: var(--accent); }
/* The dot is the page colour punched out of a filled circle, so it needs no
   second element to hold it. */
.survey-option input[type='radio']:checked { box-shadow: inset 0 0 0 3.5px var(--page-bg); }
.survey-option input[type='checkbox']:checked {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='3.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 12px 12px;
}
.survey-option input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* A checkbox group that has hit its cap shouldn't look clickable when it
   isn't, so the unpicked options grey out rather than silently refusing. */
.survey-option.is-locked { opacity: 0.4; cursor: not-allowed; }
.survey-option.is-locked:hover { border-color: var(--border); }

.survey-scale { display: flex; gap: 8px; }
.survey-scale label {
  position: relative; /* contains the visually hidden radio below */
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 13px 0;
  border: 1px solid var(--border);
  border-radius: 11px;
  font-weight: 700;
  font-size: 15px;
  color: var(--muted);
  cursor: pointer;
}
.survey-scale label:hover { border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
/* Filled rather than tinted. A 12% wash behind ink-coloured text left the
   picked number looking much the same as the four it was picked over, which on
   the one question that is a single click is the whole answer. */
.survey-scale label.is-checked {
  border-color: var(--accent);
  background: var(--accent);
  color: #ffffff;
}
.survey-scale label { transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease; }
/* The radio is visually hidden, so the ring it would have drawn for keyboard
   users has to be drawn by the label around it. */
.survey-scale label:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }
.survey-scale input { position: absolute; opacity: 0; pointer-events: none; }
.survey-scale-ends { display: flex; justify-content: space-between; margin-top: 7px; font-size: 12px; color: var(--muted-light); }

.survey-form input[type="text"], .survey-form input[type="email"], .survey-form textarea {
  width: 100%;
  padding: 12px 14px;
  background: var(--page-bg);
  border: 1px solid var(--border);
  border-radius: 11px;
  color: var(--ink);
  font: inherit;
  font-size: 14px;
}
.survey-form textarea { resize: vertical; min-height: 88px; }
.survey-form input:focus, .survey-form textarea:focus { outline: none; border-color: var(--accent); }

.survey-loading { padding: 26px 0; text-align: center; color: var(--muted); font-size: 14px; }

.survey-done { text-align: center; padding: 12px 0 4px; }
.survey-done.hidden { display: none; }
.survey-done-mark {
  width: 56px;
  height: 56px;
  margin: 0 auto 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  color: var(--accent);
}
.survey-done h2 { font-family: 'Sora', sans-serif; font-size: 22px; margin: 0 0 10px; color: var(--ink); }
.survey-done p { margin: 0 0 22px; color: var(--muted); font-size: 14.5px; line-height: 1.6; }

@media (max-width: 700px) {
  /* Full width, because a 320px card floating next to a 390px screen edge
     looks like it fell off rather than like it belongs there. */
  .survey-tab { left: 12px; right: 12px; bottom: 12px; max-width: none; }
  .survey-scale label { padding: 11px 0; font-size: 14px; }
}

@media (prefers-reduced-motion: reduce) {
  .step-row::before { animation: none !important; }
  .reveal { transition: none; opacity: 1; transform: none; }
  .survey-tab { animation: none; }
}
