/* ---------------------------------------------------------------------------
   Plantera — coming-soon page

   PART ONE is copied verbatim from frontend/app/globals.css so this page and
   the real product are visually the same object. Do not "improve" anything in
   it here; change it there and re-copy, or the two will drift apart.

   PART TWO exists only on this page.
--------------------------------------------------------------------------- */

/* ===========================================================================
   PART ONE — shared with the app
   =========================================================================== */

:root {
  --ink: #1c2b23;
  --bg: #f7f4ee;
  --surface: #fffdf9;
  --green-900: #16241c;
  --green-700: #2c4a38;
  --sage: #8fa393;
  --gold: #b39159;
  --line: #e4ded2;
  --muted: #6b7267;
  --cream: #f3efe6;
  /* One source of truth for horizontal breathing room. */
  --gutter: 1.5rem;

  /* Motion. One easing curve across the whole product — surfaces should feel
     like they belong to the same object, so nothing here gets a bespoke
     bezier. Durations follow what an element is doing, not how big it is. */
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --dur-fast: 0.18s;
  --dur: 0.22s;
  --dur-panel: 0.28s;
  --dur-drawer: 0.32s;
  --dur-slow: 0.6s;

  --radius-panel: 14px;
  --radius-card: 16px;
  --radius-frame: 20px;
  --radius-pill: 999px;

  --shadow-panel: 0 24px 60px rgba(22, 36, 28, 0.16);
  --shadow-modal: 0 30px 80px rgba(22, 36, 28, 0.25);
  --shadow-lift: 0 18px 40px rgba(22, 36, 28, 0.1);

  /* next/font injects these in the app; here they come from the Google Fonts
     link in index.html. Same variable names, so every rule below is unchanged. */
  --font-display: 'Tenor Sans';
  --font-sans: 'Inter';
}

@media (min-width: 1024px) {
  :root {
    --gutter: 1.75rem;
  }
}

@media (max-width: 360px) {
  :root {
    --gutter: 1.15rem;
  }
}

* {
  box-sizing: border-box;
}

html {
  /* WebKit only reliably honours this on the root element. Without it iOS
     inflates text inside wide blocks. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font-family: var(--font-sans), 'Helvetica Neue', system-ui, sans-serif;
  background: var(--bg);
  color: var(--ink);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* Backstop only — real overflow is fixed at the source with min-width: 0. */
  overflow-x: clip;
}

/* Scroll lock for the form panel. `overflow: hidden` alone does not hold on
   iOS Safari — the page rubber-bands and jumps to the top on release — so the
   body is pinned and its offset restored on close. See openPanel() in main.js. */
body.is-locked {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
}

h1,
h2,
h3,
.display {
  font-family: var(--font-display), 'Tenor Sans', 'Trebuchet MS', sans-serif;
  font-weight: 400;
  /* Positive tracking at weight 400 is the display signature. Headings are
     never bold and never tightened. */
  letter-spacing: 0.015em;
  line-height: 1.16;
  margin: 0;
}

p {
  margin: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

button {
  cursor: pointer;
}

/* Form controls do not inherit font-family by default, which is how a phone
   ends up showing system fonts inside an otherwise branded page. */
input,
select,
textarea,
button {
  font-family: inherit;
}

a,
button,
[role='button'] {
  -webkit-tap-highlight-color: transparent;
}

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

.container {
  max-width: 1140px;
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
  /* Grid and flex children default to min-width: auto, so one wide descendant
     can push the whole page sideways. This one line prevents that class of bug. */
  min-width: 0;
}

.container > * {
  min-width: 0;
}

@supports (padding: max(0px)) {
  .container {
    padding-left: max(var(--gutter), env(safe-area-inset-left));
    padding-right: max(var(--gutter), env(safe-area-inset-right));
  }
}

/* padding-block, never the `padding` shorthand: elements carry
   `class="container section"`, and a shorthand would reset the gutter to zero. */
.section {
  padding-block: 5rem;
}

@media (max-width: 900px) {
  .section {
    padding-block: 3.25rem;
  }
}

@media (max-width: 560px) {
  .section {
    padding-block: 2.25rem;
  }
}

.dark-section {
  background: var(--green-900);
  color: var(--cream);
}

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

/* The rem base in the middle term is load-bearing. A plain clamp(2rem, 4.5vw,
   2.8rem) puts every phone on the 2rem floor, so headings never get smaller
   than desktop's lower bound. */
.page-title {
  font-size: clamp(1.7rem, 1.1rem + 2.4vw, 2.8rem);
}

.section-title {
  font-size: clamp(1.35rem, 0.95rem + 1.6vw, 2.1rem);
}

.eyebrow {
  font-family: var(--font-sans), sans-serif;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold);
}

.eyebrow--sage {
  color: var(--sage);
}

.lead {
  color: var(--muted);
  font-size: 1.05rem;
  line-height: 1.7;
}

.dark-section .lead {
  color: #b9c2b6;
}

.pill {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.35rem 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--gold);
  color: var(--gold);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  background: var(--green-700);
  color: var(--cream);
  padding: 0.9rem 1.7rem;
  border-radius: 999px;
  border: 1px solid var(--green-700);
  font-weight: 600;
  font-size: 0.92rem;
  letter-spacing: 0.02em;
  transition:
    background var(--dur) ease,
    border-color var(--dur) ease,
    color var(--dur) ease,
    transform var(--dur) ease;
}

.btn:hover {
  background: var(--ink);
  border-color: var(--ink);
  transform: translateY(-1px);
}

.btn--cream {
  background: var(--cream);
  border-color: var(--cream);
  color: var(--green-900);
}

.btn--cream:hover {
  background: #fff;
  border-color: #fff;
  color: var(--green-900);
  transform: translateY(-1px);
}

/* The quiet half of a pair. On the dark hero it is an outlined cream button;
   on cream it is an outlined ink one. */
.btn--outline {
  background: transparent;
  border-color: rgba(243, 239, 230, 0.45);
  color: var(--cream);
}

.btn--outline:hover {
  background: rgba(243, 239, 230, 0.12);
  border-color: var(--cream);
  color: var(--cream);
  transform: translateY(-1px);
}

.btn--ghost {
  background: transparent;
  border-color: var(--ink);
  color: var(--ink);
}

.btn--ghost:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--cream);
}

.btn:disabled {
  opacity: 0.4;
  pointer-events: none;
}

@media (max-width: 560px) {
  .btn {
    /* Padding alone left these at 42px on a phone; min-height holds the 44px
       floor without making them taller on a desktop. */
    min-height: 44px;
    padding: 0.72rem 1.3rem;
    font-size: 0.88rem;
  }
}

/* ------------------------------------------------------------------- cards */

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 16px;
  padding: 1.75rem;
  min-width: 0;
}

.hairline {
  border: 0;
  border-top: 1px solid var(--line);
  margin: 0;
}

/* ------------------------------------------------------------ form controls */

.input {
  padding: 0.78rem 0.95rem;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--surface);
  font-family: inherit;
  /* Must stay >= 16px: iOS Safari zooms the whole page when focusing a smaller
     field, which reads as the layout jumping on every tap. */
  font-size: 16px;
  font-weight: 400;
  color: var(--ink);
  width: 100%;
  /* .field sets uppercase and tracking on the label; without these resets the
     text someone types would inherit them. */
  text-transform: none;
  letter-spacing: normal;
  transition:
    border-color 0.15s ease,
    box-shadow 0.15s ease;
  min-width: 0;
}

.input:focus {
  outline: none;
  border-color: var(--green-700);
  /* 0.12 alpha was invisible on cream — about 1.1:1 against the surface beside
     it — which left a 1px border change carrying the whole indicator. */
  box-shadow: 0 0 0 3px rgba(44, 74, 56, 0.3);
}

.input[aria-invalid='true'] {
  border-color: #9c4a3c;
}

.field {
  display: grid;
  gap: 0.35rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  min-width: 0;
}

.field-hint {
  font-size: 0.78rem;
  color: var(--muted);
  line-height: 1.5;
  font-weight: 400;
  text-transform: none;
  letter-spacing: normal;
}

/* A radio option with its own explanation, sized as a tap target rather than a
   bare dot — on a phone the label is the only thing big enough to hit. */
.choice {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.7rem;
  align-items: start;
  padding: 0.8rem 0.9rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  cursor: pointer;
  text-transform: none;
  letter-spacing: normal;
  transition:
    border-color var(--dur-fast) var(--ease-out),
    background var(--dur-fast) var(--ease-out);
}

.choice:hover {
  border-color: var(--sage);
}

.choice--on {
  border-color: var(--green-700);
  background: var(--surface);
}

.choice input {
  margin-top: 0.15rem;
  accent-color: var(--green-700);
}

/* Keyboard users get no hover, so the ring is the only affordance. */
.choice:focus-within {
  border-color: var(--green-700);
  box-shadow: 0 0 0 3px rgba(44, 74, 56, 0.12);
}

.choice span {
  display: grid;
  gap: 0.15rem;
  min-width: 0;
}

.choice strong {
  font-size: 0.92rem;
  font-weight: 600;
  color: var(--ink);
}

.choice small {
  font-size: 0.78rem;
  color: var(--muted);
  line-height: 1.5;
  font-weight: 400;
}

/* --------------------------------------------------------------- the logo */

.logo {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  min-width: 0;
}

.logo__word {
  font-size: var(--logo-size, 1.05rem);
  letter-spacing: 0.32em;
  transform: translateY(1px);
  white-space: nowrap;
}

@media (max-width: 360px) {
  .logo__word {
    font-size: 0.85rem;
    letter-spacing: 0.14em;
  }

  .logo {
    gap: 0.35rem;
  }
}

/* --------------------------------------------------------------- utilities */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

[hidden] {
  display: none !important;
}

/* ===========================================================================
   PART TWO — this page only
   =========================================================================== */

/* ------------------------------------------------- accessibility overrides */

/* Two shared values do not clear WCAG AA on this page, so they are overridden
   here rather than edited in PART ONE — that block has to stay a verbatim copy
   of globals.css or the two files start lying about being the same system.
   Both are worth fixing in the app too.

   --gold #b39159 as small uppercase text on --bg is 2.69:1, well under the 4.5
   needed. It stays --gold everywhere it is used as a line, a border or a large
   numeral; only text drops to a darker step of the same hue.

   --muted #6b7267 on --bg is 4.51:1 — a pass by 0.01 — and the .grain layer
   below darkens the background just enough to push it to 4.48. One step darker
   removes the argument entirely. */
:root {
  --gold-text: #856428; /* 4.97:1 on --bg */
  --muted: #666d62; /* 4.87:1 on --bg */
}

.eyebrow {
  color: var(--gold-text);
}

/* On the dark sections the original gold is fine and the sage variant is
   already 6:1, so neither is touched there. */
.dark-section .eyebrow,
.hero .eyebrow {
  color: var(--gold);
}

/* ------------------------------------------------------------------- grain */

/* One fixed film-grain layer over the whole page. Flat vector colour with no
   texture is what reads as "template"; this costs one data URI and no request. */
.grain {
  position: fixed;
  inset: 0;
  /* Above the form panel (60), so the texture covers the whole page rather than
     stopping at the edge of an open sheet and making it read flatter than
     everything behind it. Safe because it is pointer-events: none. */
  z-index: 70;
  pointer-events: none;
  opacity: 0.035;
  mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ------------------------------------------------------------------ topbar */

.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  padding-block: 1.1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  color: var(--cream);
  transition:
    background var(--dur) ease,
    padding var(--dur) ease,
    box-shadow var(--dur) ease;
}

@supports (padding: max(0px)) {
  .topbar {
    padding-top: max(1.1rem, env(safe-area-inset-top));
  }
}

/* No background, ever. main.js sets data-over from whichever band is behind the
   bar, and the wordmark changes colour instead of hiding behind a slab. */
.topbar[data-over='light'] {
  color: var(--ink);
}

.topbar .logo__word,
.topbar .logo svg path {
  transition:
    color var(--dur-panel) var(--ease-out),
    fill var(--dur-panel) var(--ease-out),
    stroke var(--dur-panel) var(--ease-out);
}

/* The leaf is inline SVG with hardcoded sage fills, which read fine on both
   grounds — only the wordmark and the language switch need to move. */
.topbar .logo__word {
  color: var(--cream);
}

.topbar[data-over='light'] .logo__word {
  color: var(--ink);
}

.lang-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  padding: 0.2rem;
  border-radius: 999px;
  border: 1px solid rgba(243, 239, 230, 0.22);
  background: rgba(22, 36, 28, 0.3);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
}

.lang-toggle button {
  background: none;
  border: 0;
  /* The language switch on a bilingual page for a bilingual island was the
     smallest control here at 39x24. */
  min-height: 40px;
  min-width: 44px;
  padding: 0.3rem 0.7rem;
  border-radius: 999px;
  color: rgba(243, 239, 230, 0.72);
  font-size: inherit;
  letter-spacing: inherit;
  font-weight: 600;
  transition:
    background var(--dur) ease,
    color var(--dur) ease;
}

.lang-toggle button[aria-pressed='true'] {
  background: var(--cream);
  color: var(--green-900);
}

.topbar[data-over='light'] .lang-toggle {
  border-color: rgba(28, 43, 35, 0.25);
  background: rgba(255, 253, 249, 0.7);
}

.topbar[data-over='light'] .lang-toggle button {
  color: rgba(28, 43, 35, 0.6);
}

.topbar[data-over='light'] .lang-toggle button[aria-pressed='true'] {
  background: var(--green-700);
  color: var(--cream);
}

.lang-toggle,
.lang-toggle button {
  transition:
    background var(--dur-panel) var(--ease-out),
    border-color var(--dur-panel) var(--ease-out),
    color var(--dur-panel) var(--ease-out);
}

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

.hero {
  position: relative;
  background: var(--green-900);
  color: var(--cream);
  overflow: hidden;
  isolation: isolate;
}

/* svh, not vh: on iOS the address bar makes 100vh taller than the screen, so a
   vh-sized hero always hides its own bottom edge. */
@media (min-width: 760px) {
  .hero {
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
}

.hero__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Desaturated and darkened so the photo reads as ground, not subject. A
     full-colour stock photo behind text is the most template-looking thing a
     landing page can do. */
  filter: saturate(0.62) contrast(1.05) brightness(0.95);
  transform: scale(1.04);
}

/* A light multiply colour barely darkens and a dark one darkens hard, so this
   gradient doubles as a "how much photo do you get" gradient: almost none
   under the headline, most of it out at the far corner. If a browser ignores
   the blend mode it degrades to an ordinary scrim. */
.hero__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg, #16241c 0%, #24402f 45%, #55705d 100%);
  mix-blend-mode: multiply;
}

.hero__scrim--fade {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(120% 90% at 15% 25%, rgba(143, 163, 147, 0.16) 0%, transparent 60%),
    linear-gradient(
      to top,
      rgba(16, 29, 22, 0.9) 0%,
      rgba(16, 29, 22, 0.62) 48%,
      rgba(16, 29, 22, 0.42) 100%
    );
}

/* On a phone the copy runs the full width, so the veil stays even. Once there
   is room, the photo is let through on the right where no text sits. */
@media (min-width: 1000px) {
  .hero__bg {
    filter: saturate(0.68) contrast(1.05) brightness(1.06);
  }

  .hero__scrim--fade {
    background:
      radial-gradient(
        88% 105% at 10% 50%,
        rgba(16, 29, 22, 0.92) 0%,
        rgba(16, 29, 22, 0.62) 52%,
        rgba(16, 29, 22, 0.16) 82%
      ),
      linear-gradient(to top, rgba(16, 29, 22, 0.6) 0%, rgba(16, 29, 22, 0.1) 55%);
  }
}

/* No max-width here. This element carries `class="container hero__inner"`, and
   a narrower max-width would beat .container's 1140px — which, with its
   `margin: 0 auto`, centred the whole hero block instead of aligning it to the
   page gutter like every other section. Individual children are constrained
   instead. */
.hero__inner {
  position: relative;
  z-index: 1;
  /* width: 100% is load-bearing. .hero is a column flex container, and .container
     gives this element `margin: 0 auto` — auto margins on a flex item's cross
     axis cancel `align-items: stretch`, so without an explicit width the hero
     shrank to its content (824px) and centred itself, sitting 158px to the right
     of every other section on the page. */
  width: 100%;
  padding-block: 6rem 3.25rem;
  display: grid;
  gap: 1.5rem;
  justify-items: start;
}

@media (min-width: 760px) {
  .hero__inner {
    padding-block: 8rem 5rem;
    gap: 1.75rem;
  }
}

.hero__title {
  font-size: clamp(2.3rem, 1.2rem + 4.6vw, 4rem);
  line-height: 1.06;
  max-width: 12em;
  text-wrap: balance;
}

.hero__lead {
  color: rgba(243, 239, 230, 0.8);
  font-size: 1.03rem;
  line-height: 1.7;
  max-width: 34rem;
  text-wrap: pretty;
}

.hero .pill {
  border-color: rgba(203, 176, 131, 0.5);
  background: rgba(203, 176, 131, 0.1);
  color: #d9bd8a;
}

/* One quietly living thing on the page: the difference between "this is a
   site" and "this is a project underway". */
.pill__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  animation: pulse 2.4s var(--ease-out) infinite;
}

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

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  margin-top: 0.25rem;
}

/* ------------------------------------------------------------- flip clock */

.countdown {
  display: grid;
  gap: 0.85rem;
  justify-items: start;
  width: 100%;
}

.countdown__label {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: rgba(243, 239, 230, 0.55);
}

.countdown__groups {
  display: flex;
  align-items: flex-start;
  gap: clamp(0.55rem, 0.2rem + 1.4vw, 1.35rem);
  flex-wrap: nowrap;
  min-width: 0;
}

.unit {
  display: grid;
  gap: 0.5rem;
  justify-items: center;
}

.unit__digits {
  display: flex;
  gap: 3px;
}

.unit__label {
  font-family: var(--font-sans), sans-serif;
  font-size: 0.56rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  /* 0.5 measured 4.61:1 over the hero photo — a pass only because of where the
     image happens to be dark. 0.6 is 5.92:1 and does not depend on the photo. */
  color: rgba(243, 239, 230, 0.6);
}

/* One card per digit, turning about its horizontal axis. Deliberately NOT the
   split-flap this replaces: that design shows the incoming digit in its top half
   while the outgoing one is still in its bottom half, and at 25-40px tall with a
   hairline border it does not read as a card turning — it reads as two numerals
   colliding. Turning the whole card means one complete number is on screen at
   every frame, so the collision is impossible rather than unlikely. */
.digit {
  /* The floor is what matters here: nine tiles plus their gaps have to fit the
     narrowest phone. */
  --digit-w: clamp(1.5rem, 0.9rem + 2.8vw, 2.85rem);
  --digit-h: clamp(2.2rem, 1.3rem + 3.9vw, 3.9rem);
  position: relative;
  width: var(--digit-w);
  height: var(--digit-h);
  perspective: 320px;
  font-family: var(--font-display), 'Tenor Sans', serif;
  /* Proportional figures make the row twitch on every tick. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
  font-size: calc(var(--digit-h) * 0.56);
  color: var(--cream);
}

/* A flat translucent fill, not backdrop-filter. What sits behind is a scaled
   photo under a mix-blend-mode gradient under a second gradient under a fixed
   grain layer; a backdrop-filter would force all of it to be re-read and
   re-blurred every frame of every flip, once a second, forever. */
.digit__card {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  line-height: 1;
  background: rgba(243, 239, 230, 0.1);
  border: 1px solid rgba(243, 239, 230, 0.16);
  border-radius: 10px;
  backface-visibility: hidden;
  transform-origin: center center;
}

/* The hairline across the middle is the only thing left of the split-flap, and
   it is what still makes the tile read as something that flips. */
.digit__card::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 1px;
  background: rgba(16, 29, 22, 0.4);
  pointer-events: none;
}

.digit.is-flipping .digit__card {
  animation: flip-out 0.16s ease-in forwards;
}

/* Four classes beats three, so adding is-back swaps which animation applies and
   restarts it — that is the whole state machine. */
.digit.is-flipping.is-back .digit__card {
  animation: flip-in 0.18s var(--ease-out) forwards;
}

@keyframes flip-out {
  from {
    transform: rotateX(0deg);
  }
  to {
    transform: rotateX(-90deg);
  }
}

@keyframes flip-in {
  from {
    transform: rotateX(90deg);
  }
  to {
    transform: rotateX(0deg);
  }
}

.countdown__date {
  font-size: 0.85rem;
  color: rgba(243, 239, 230, 0.62);
}

/* ------------------------------------------------------------------ ticker */

/* The 78 municipios, scrolling. It says "the whole island" faster than a
   sentence can, and it is the one element that moves on its own. */
.ticker {
  position: relative;
  background: var(--green-900);
  /* 0.4 alpha measured 3.40:1 against the green. 0.55 is 5.21:1. */
  color: rgba(243, 239, 230, 0.55);
  border-top: 1px solid rgba(243, 239, 230, 0.08);
  padding-block: 0.85rem;
  overflow: hidden;
  /* The right stop is earlier than the left because the pause button sits over
     that corner — town names have to be faded out before they reach it rather
     than running underneath it. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 84%, transparent 97%);
  mask-image: linear-gradient(90deg, transparent, #000 6%, #000 84%, transparent 97%);
}

.ticker__track {
  display: flex;
  width: max-content;
  animation: ticker 90s linear infinite;
}

.ticker__group {
  display: flex;
  gap: 2.25rem;
  padding-right: 2.25rem;
  font-size: 0.74rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* The track holds the list twice; -50% lands exactly on the start of the
   second copy, so the loop has no visible seam. */
@keyframes ticker {
  to {
    transform: translateX(-50%);
  }
}

/* Nothing behind an open form panel is visible, so nothing behind it should be
   animating a phone's battery away. */
body.is-locked .ticker__track {
  animation-play-state: paused;
}

.ticker__pause {
  position: absolute;
  right: 0.5rem;
  top: 50%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: none;
  color: rgba(243, 239, 230, 0.45);
  transition: color var(--dur-fast) var(--ease-out);
}

.ticker__pause:hover,
.ticker__pause:focus-visible {
  color: var(--cream);
}

/* Two bars while running, one triangle once paused — drawn in CSS so the
   button does not need two icons or a script to swap them. */
.ticker__pause[aria-pressed='true'] svg rect:first-child {
  transform: scaleX(1.9);
  transform-origin: 3.5px center;
}

.ticker__pause[aria-pressed='true'] svg rect:last-child {
  opacity: 0;
}

/* -------------------------------------------------------- numbered sections */

.section-head {
  display: grid;
  gap: 0.6rem;
  max-width: 44rem;
  margin-bottom: 2.75rem;
}

/* Deliberately not bordered cards. Hairline-divided editorial columns with an
   oversized outlined numeral read as a magazine spread rather than a
   bootstrap card row. */
.numbered {
  display: grid;
  gap: 0;
}

@media (min-width: 860px) {
  .numbered {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

.numbered__item {
  padding-block: 1.75rem;
  border-top: 1px solid var(--line);
}

.dark-section .numbered__item {
  border-top-color: rgba(243, 239, 230, 0.15);
}

@media (min-width: 860px) {
  .numbered__item {
    padding: 2rem 1.75rem 0 0;
  }

  .numbered__item + .numbered__item {
    border-left: 1px solid var(--line);
    padding-left: 1.75rem;
  }

  .dark-section .numbered__item + .numbered__item {
    border-left-color: rgba(243, 239, 230, 0.15);
  }
}

.numbered__num {
  display: block;
  font-size: 2.9rem;
  line-height: 1;
  color: rgba(179, 145, 89, 0.32);
  margin-bottom: 0.75rem;
}

/* Outlined where supported; a solid tint everywhere else, so the numeral is
   never invisible if -webkit-text-stroke is unavailable. */
@supports (-webkit-text-stroke: 1px red) {
  .numbered__num {
    color: transparent;
    -webkit-text-stroke: 1px rgba(179, 145, 89, 0.85);
  }
}

/* Numerals mean sequence. "Busca / Pide / Recibe" is a sequence; "fotos reales
   / entrega / viveros verificados" is a list, and numbering it as well made the
   same 01-02-03 run twice down one page for no reason. The hairline columns
   carry the structure on their own. */
.numbered--plain .numbered__item {
  padding-top: 1.75rem;
}

.numbered__title {
  font-size: 1.15rem;
  margin-block: 0 0.5rem;
}

.numbered__copy {
  font-size: 0.92rem;
  color: var(--muted);
  line-height: 1.65;
  max-width: 30rem;
}

.dark-section .numbered__copy {
  color: #b9c2b6;
}

/* --------------------------------------------------------------- vivero cta */

/* The second audience, and the more valuable one: a shopper here is a signup,
   a nursery owner here is the pilot. */
.cta-band {
  background: var(--green-700);
  color: var(--cream);
  position: relative;
  overflow: hidden;
}

.cta-band::after {
  content: '';
  position: absolute;
  right: -6rem;
  top: -8rem;
  width: 26rem;
  height: 26rem;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(143, 163, 147, 0.28), transparent 65%);
  pointer-events: none;
}

.cta-band__inner {
  position: relative;
  z-index: 1;
  padding-block: 3.5rem;
  display: grid;
  gap: 1.5rem;
  align-items: center;
}

@media (min-width: 820px) {
  .cta-band__inner {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 2.5rem;
    padding-block: 4rem;
  }
}

.cta-band__copy {
  color: rgba(243, 239, 230, 0.8);
  font-size: 0.98rem;
  line-height: 1.7;
  max-width: 34rem;
  margin-top: 0.6rem;
}

.cta-band__points {
  position: relative;
  z-index: 1;
  padding-bottom: 3.5rem;
}

@media (min-width: 820px) {
  .cta-band__points {
    padding-bottom: 4rem;
  }
}

/* The band is --green-700, not the --green-900 of .dark-section, so the hairline
   and body colours need their own step rather than inheriting either. */
.cta-band .numbered__item {
  border-top-color: rgba(243, 239, 230, 0.2);
}

@media (min-width: 860px) {
  .cta-band .numbered__item + .numbered__item {
    border-left-color: rgba(243, 239, 230, 0.2);
  }
}

.cta-band .numbered__copy {
  color: rgba(243, 239, 230, 0.78);
}

.cta-band .numbered__num {
  color: rgba(243, 239, 230, 0.32);
}

@supports (-webkit-text-stroke: 1px red) {
  .cta-band .numbered__num {
    color: transparent;
    -webkit-text-stroke: 1px rgba(243, 239, 230, 0.55);
  }
}

/* ------------------------------------------------------------- closing cta */

.closing {
  text-align: center;
  display: grid;
  justify-items: center;
  gap: 1rem;
}

.closing__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  justify-content: center;
  margin-top: 0.35rem;
}

.closing__note {
  font-size: 0.8rem;
  color: var(--muted);
}

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

.footer {
  background: var(--green-900);
  color: #8a9488;
  padding-block: 3rem 2.25rem;
  font-size: 0.8rem;
}

.footer a {
  color: var(--cream);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.footer__top {
  display: grid;
  gap: 1.25rem;
  padding-bottom: 2rem;
  border-bottom: 1px solid rgba(243, 239, 230, 0.15);
}

@media (min-width: 720px) {
  .footer__top {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
  }
}

.footer__tag {
  color: rgba(243, 239, 230, 0.55);
  margin-top: 0.6rem;
  max-width: 30rem;
}

.footer__row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  align-items: center;
  justify-content: space-between;
  padding-top: 1.5rem;
}

/* =========================================================== the form panel */

/* Delayed visibility keeps a closed panel out of the tab order while still
   letting it finish animating out. Same idiom as .search-overlay in the app. */
.panel {
  position: fixed;
  inset: 0;
  z-index: 60;
  visibility: hidden;
  transition: visibility 0s linear var(--dur-drawer);
}

.panel.is-open {
  visibility: visible;
  transition: visibility 0s;
}

.panel__scrim {
  position: absolute;
  inset: 0;
  background: rgba(22, 36, 28, 0.5);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity var(--dur-panel) var(--ease-out);
}

.panel.is-open .panel__scrim {
  opacity: 1;
}

.panel__sheet {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg);
  box-shadow: var(--shadow-modal);
  border-radius: 22px 22px 0 0;
  padding: 1.1rem var(--gutter) calc(2rem + env(safe-area-inset-bottom));
  transform: translateY(100%);
  transition: transform var(--dur-drawer) var(--ease-out);
}

.panel.is-open .panel__sheet {
  transform: translateY(0);
}

/* Past phone width the sheet stops being a sheet and becomes a centred dialog:
   a full-width slab of form on a monitor is unreadable. */
@media (min-width: 720px) {
  .panel {
    display: grid;
    place-items: center;
    padding: 2rem;
  }

  .panel__sheet {
    position: relative;
    inset: auto;
    width: min(560px, 100%);
    max-height: min(88vh, 88dvh);
    border-radius: var(--radius-card);
    padding: 1.75rem 2rem 2rem;
    transform: translateY(14px) scale(0.98);
    opacity: 0;
    transition:
      transform var(--dur-panel) var(--ease-out),
      opacity var(--dur-panel) var(--ease-out);
  }

  .panel.is-open .panel__sheet {
    transform: none;
    opacity: 1;
  }
}

/* A grab handle — purely a signal that the sheet is dismissible. */
/* The sheet is focused programmatically on open, purely so a screen reader
   announces the dialog and so the Tab sequence starts inside it. It is
   tabindex="-1" and never keyboard-reachable, so a focus ring here is noise —
   it read as a blue border around the whole form. */
.panel__sheet:focus {
  outline: none;
}

.panel__grip {
  width: 40px;
  height: 4px;
  border-radius: 999px;
  background: var(--line);
  margin: 0 auto 1rem;
}

@media (min-width: 720px) {
  .panel__grip {
    display: none;
  }
}

.panel__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.panel__title {
  font-size: 1.5rem;
}

.panel__sub {
  font-size: 0.88rem;
  color: var(--muted);
  line-height: 1.6;
  margin-top: 0.3rem;
}

.panel__close {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--muted);
  transition:
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out);
}

.panel__close:hover {
  border-color: var(--ink);
  color: var(--ink);
}

.panel__form {
  display: grid;
  gap: 1.05rem;
}

.roles {
  display: grid;
  gap: 0.6rem;
  border: 0;
  padding: 0;
  margin: 0;
}

.grid-2 {
  display: grid;
  gap: 0.9rem;
  /* Without this, a cell whose field carries a hint line makes the row taller
     and the other cell's control stretches to match. */
  align-items: start;
}

@media (min-width: 520px) {
  .grid-2 {
    grid-template-columns: 1fr 1fr;
  }
}

/* The vivero name field slides in rather than appearing, so the form does not
   jump under the thumb of someone who just tapped "Tengo un vivero". */
.slide-in {
  animation: slide-down 0.28s var(--ease-out);
}

@keyframes slide-down {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* The whole label is the tap target, so it carries the height rather than the
   16px checkbox inside it. */
.consent {
  display: flex;
  gap: 0.6rem;
  align-items: flex-start;
  padding-block: 0.3rem;
  min-height: 44px;
  font-size: 0.82rem;
  line-height: 1.5;
  color: var(--muted);
}

.consent input {
  margin-top: 0.2rem;
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  accent-color: var(--green-700);
}

.privacy {
  font-size: 0.73rem;
  color: var(--muted);
  line-height: 1.5;
}

.btn--submit {
  width: 100%;
  padding-block: 1rem;
  font-size: 0.95rem;
}

.btn--submit svg {
  transition: transform var(--dur) var(--ease-out);
}

.btn--submit:hover svg {
  transform: translateX(3px);
}

/* The honeypot. Off-screen rather than display:none — some bots skip hidden
   fields but happily fill positioned ones. Never shown, never announced. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form-error {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: #9c4a3c;
  outline: none;
}

/* Collapsed rather than toggled with the `hidden` attribute. Unhiding a live
   region and writing its text in the same task fails to announce in several
   screen-reader/browser pairs, so main.js also moves focus to it — which is
   the affordance that actually works, for sighted users too. */
.form-error:empty {
  display: none;
}

/* ------------------------------------------------------------ success state */

.done {
  display: grid;
  gap: 0.85rem;
  justify-items: center;
  text-align: center;
  padding-block: 1.5rem 1rem;
  animation: slide-down 0.35s var(--ease-out);
}

.done__mark {
  width: 54px;
  height: 54px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(44, 74, 56, 0.09);
  color: var(--green-700);
}

.done__title {
  font-size: 1.4rem;
}

.done__body {
  font-size: 0.92rem;
  color: var(--muted);
  line-height: 1.65;
  max-width: 26rem;
}

/* ---------------------------------------------------------------- combobox */

.combo {
  position: relative;
  min-width: 0;
}

.combo__input {
  padding-right: 2.4rem;
}

.combo__caret {
  position: absolute;
  right: 0.85rem;
  top: 50%;
  transform: translateY(-50%);
  color: var(--sage);
  pointer-events: none;
  transition: transform var(--dur-fast) var(--ease-out);
}

.combo[data-open='true'] .combo__caret {
  transform: translateY(-50%) rotate(180deg);
}

.combo__list {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 5;
  margin: 0;
  padding: 0.35rem;
  list-style: none;
  /* Bounded so it cannot outgrow the sheet it lives in — the panel scrolls,
     and an absolutely-positioned list taller than the sheet would be clipped
     rather than scroll with it. */
  max-height: min(40vh, 15rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-panel);
}

.combo__option {
  display: block;
  width: 100%;
  text-align: left;
  border: 0;
  background: none;
  /* 44px is Apple's floor for a tap target, and these get picked with a thumb. */
  min-height: 44px;
  padding: 0.6rem 0.7rem;
  border-radius: 10px;
  font-size: 0.95rem;
  color: var(--ink);
  transition:
    background var(--dur-fast) var(--ease-out),
    transform var(--dur-fast) var(--ease-out);
}

/* Mouse and keyboard share one highlight, so there is never a second, ghostly
   "hovered" row competing with the arrow-key position. */
.combo__option[aria-selected='true'] {
  background: var(--cream);
  transform: translateX(3px);
}

.combo__option mark {
  background: none;
  color: var(--green-700);
  font-weight: 600;
}

/* Flipped above the input when the list would otherwise run off the bottom of
   the sheet — which is the normal case on a phone once the keyboard is up. */
.combo--above .combo__list {
  top: auto;
  bottom: calc(100% + 6px);
}

.combo__empty {
  padding: 0.75rem 0.7rem;
  font-size: 0.88rem;
  color: var(--muted);
}

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

/* Hero children get `is-in` immediately; everything below the fold gets it
   from an IntersectionObserver. Anything without JS never gets the class, so
   the no-js fallback has to un-hide them unconditionally. */
.reveal {
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity 0.6s var(--ease-out),
    transform 0.6s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 70ms);
}

.reveal.is-in {
  opacity: 1;
  transform: none;
}

/* transition: none matters as much as the opacity. When the watchdog restores
   this class 2.5s after a failed script load, the content has to appear at
   once — fading it in over another 0.6s would make a page that already looked
   broken look like it was still loading. */
.no-js .reveal {
  opacity: 1;
  transform: none;
  transition: none;
}

/* Without the script the panel cannot open, so the buttons that open it are
   hidden rather than left as controls that do nothing, and an address to write
   to takes their place. */
.no-js-only {
  display: none;
}

.no-js .no-js-only {
  display: block;
}

.no-js [data-open-panel],
.no-js .ticker__pause {
  display: none;
}

/* Motion is decoration — never a barrier for people who ask for less of it. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  html {
    scroll-behavior: auto;
  }

  /* transition-duration alone is not enough here: .panel delays its visibility
     switch, and a delay is not a duration — the closed panel would stay
     visible for a third of a second after everything else had stopped. */
  *,
  *::before,
  *::after {
    transition-delay: 0s !important;
  }

  /* Killing the duration is not enough on its own: an element whose resting
     state is offset or rotated would still snap into place, which is the jump
     the setting exists to avoid. The offsets themselves have to go. */
  .reveal {
    opacity: 1;
    transform: none;
  }

  .ticker__track {
    animation: none;
  }

  .digit__card {
    animation: none !important;
  }
}
