/* Upland Maps — landing page.
 *
 * A conventional marketing page, done properly: sticky nav, hero with one clear action,
 * proof strip, alternating feature sections, a three-up of the smaller things, the story,
 * two pricing cards, FAQ, closing call, footer. That order works because people already
 * know how to read it; the earlier attempts at reinventing it read as a blog.
 *
 * The character comes from the palette, the screenshots and the words — not from an
 * unusual structure. Warm oat paper, forest green as the primary, ember as the accent.
 *
 * House rules kept: no gradients and no blur behind anything decorative (flat fills
 * and opacity only), no status pill
 * with a pulsing dot, no third-party requests of any kind.
 */

:root {
  --paper:      #faf6ee;
  --paper-2:    #f3ede0;
  --card:       #fffdf8;
  --ink:        #1e1b16;
  --body:       #554e43;
  --muted:      #8a8073;
  --line:       #e6ddcb;
  --line-2:     #d6cab2;

  --forest:     #2f4a37;
  --forest-2:   #3d5f46;
  --ember:      #b4552a;
  --gold:       #c89a3c;

  --radius:     14px;
  --radius-lg:  20px;
  --shadow:     0 1px 2px rgba(50,38,20,.05), 0 12px 32px rgba(50,38,20,.08);
  --shadow-lg:  0 2px 6px rgba(50,38,20,.06), 0 26px 64px rgba(50,38,20,.13);

  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial,
          sans-serif;
  --serif: ui-serif, "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
}

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

/* The `hidden` attribute loses to any rule that sets display — #notice is display:flex and
   .contact is display:grid, so hiding either from script did nothing at all. This puts the
   attribute back in charge, which is what everything here assumes. */
[hidden] { display: none !important; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--paper);
  color: var(--body);
  font: 1rem/1.65 var(--sans);
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }
a { color: var(--forest); }

h1, h2, h3, h4 { margin: 0; color: var(--ink); line-height: 1.15; font-weight: 600; }
p { margin: 0; }

.wrap { width: min(100% - 2.5rem, 1140px); margin-inline: auto; }

/* Serif only where it carries feeling: the large headings. Everything functional is sans,
   which is what makes the page read as an app rather than an essay. */
.display { font-family: var(--serif); letter-spacing: -.02em; font-weight: 600; }

.kicker {
  font-size: .74rem; letter-spacing: .16em; text-transform: uppercase;
  color: var(--muted); font-weight: 600;
}

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

.btn {
  display: inline-flex; align-items: center; gap: .5rem;
  background: var(--forest); color: #fff; text-decoration: none;
  font-weight: 600; font-size: .97rem; padding: .8rem 1.4rem;
  border-radius: 999px; border: 1px solid var(--forest);
  transition: background .15s ease, border-color .15s ease;
}
.btn:hover { background: var(--forest-2); border-color: var(--forest-2); }
.btn.ghost { background: transparent; color: var(--forest); border-color: var(--line-2); }
.btn.ghost:hover { background: var(--paper-2); border-color: var(--forest); }
.btn.sm { padding: .55rem 1.05rem; font-size: .9rem; }

/* Not open yet. A real disabled button rather than a link styled to look dead, so it
   announces itself as unavailable to a screen reader and cannot be tabbed into. */
.btn.soon,
.btn.soon:hover {
  background: var(--paper-2);
  color: var(--muted);
  border-color: var(--line-2);
  cursor: not-allowed;
  box-shadow: none;
}
/* on the red bar it needs to read as dead without disappearing into it */
.nav .btn.soon,
.nav .btn.soon:hover {
  background: rgba(251,245,233,.1);
  color: rgba(251,245,233,.6);
  border-color: rgba(251,245,233,.3);
}
.closing .btn.soon,
.closing .btn.soon:hover {
  background: rgba(255,255,255,.12);
  color: rgba(238,242,234,.75);
  border-color: rgba(255,255,255,.25);
}

/* ---------------------------------------------------------------- gutters */

/* Green either side, reaching a third of the way into the reading column before it goes.
 *
 * One flat green with its alpha masked away — not a ramp between two colours. Present at
 * every width, phone included, because it is the part that carries the season; only the
 * trees below need room they do not have on a phone.
 */
.greenwash {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  /* A pale sage rather than the deep forest green. At full strength on the outer few
     percent, a dark green read as a hard band at the edge of the page; this is the same
     idea in a colour that sits down. */
  background: #a9c49b;
  opacity: 1;
  /* Solid at the very edge, gone by a quarter of the way in, so the middle half of the
     page is plain paper. Alpha only — one flat colour made transparent. */
  --wash: linear-gradient(to right,
    #000 0%, #000 3%, rgba(0,0,0,.60) 10%, rgba(0,0,0,.24) 18%, transparent 25%,
    transparent 75%, rgba(0,0,0,.24) 82%, rgba(0,0,0,.60) 90%, #000 97%, #000 100%);
  -webkit-mask-image: var(--wash);
          mask-image: var(--wash);
}

/* A stand of trees in the margins. On a phone there is no margin to speak of, but the
   page still keeps about 20px either side of the text — enough for one narrow trunk at
   the very edge, which is what keeps the woodland present on a small screen. */
.gutters { display: block; position: fixed; inset: 0; z-index: 0;
  overflow: hidden; pointer-events: none; }
.gut { position: absolute; top: 0; bottom: 0; width: 13px; overflow: hidden; }
.gut-l { left: 0; }
.gut-r { right: 0; transform: scaleX(-1); }
.gut .tr { position: absolute; top: 0; bottom: 0; z-index: 1; }
/* only the first trunk fits at that width; the rest wait for a real gutter */
.gut .t1 { left: 0; width: 100%; background: #6b4a2e; opacity: .42; }
.gut .t2, .gut .t3, .gut .t4, .gut .t5 { display: none; }

/* No green at the edges on a phone.
   The wash is a wide, soft thing — solid at the outer few percent, gone by a quarter of
   the way in. That reads as a woodland edge across a 1400px page. Across 390px there is
   no "quarter of the way in" worth having: the two fades meet near the middle and the
   whole screen goes green, which is not the effect at all. Desktop keeps it.

   860px is where the feature rows drop to one column, so it is this page's own idea of
   where mobile starts rather than a number picked off a device list. */
@media (max-width: 860px) {
  .greenwash { display: none; }

  /* Trunks only alongside the opening screen.
     They are a fixed overlay by default, which is right on a wide page where they read as
     a stand you are walking past. On a phone that means a bare brown bar tracking you down
     every section — and with the wash gone there is no green for them to be trees in, so
     they stop being scenery and start being a stripe. Switching to absolute anchors them
     to the top of the document instead, so they flank the hero and scroll away.

     A hard stop above the hero screenshot, not a fade. Fixed pixels rather than a share
     of the viewport because what sits above the image is a block of text, whose height
     depends on how the headline wraps and not at all on how tall the phone is. Measured
     across 320-860px the image starts between 455px and 616px down — 420 clears the
     lowest of those, so the trunks always stop before the image and never run past it. */
  .gutters {
    position: absolute;
    top: 0;
    height: 420px;
  }
}

@media (min-width: 1440px) {
  .gutters {
    display: block; position: fixed; inset: 0; z-index: 0;
    overflow: hidden; pointer-events: none;
  }
  .gut {
    position: absolute; top: 0; bottom: 0;
    /* Exactly the margin beside the 1180px column and not a pixel more, so a trunk can
       never end up behind a word. Below 1380px there is not enough margin to be worth it. */
    width: calc((100vw - 1180px) / 2);
    overflow: hidden;
  }
  .gut-l { left: 0; }
  .gut-r { right: 0; transform: scaleX(-1); }   /* mirrored, so the sides differ */

  /* The other four trunks come back once there is a real gutter. They are switched off
     in the base rules for the phone, where only one fits in the page margin, and that
     rule outranks these on specificity — so it has to be undone here explicitly. */
  .gut .t2, .gut .t3, .gut .t4, .gut .t5 { display: block; }

  /* All five finish by 78% of the gutter, so no trunk runs up against the text column.
     Variation is width and colour now that they are all full height. */
  .t1 { left:  2%; width: 14%; background: #6b4a2e; opacity: .52; }
  .gut .t1 { left: 2%; width: 14%; }
  .t2 { left: 20%; width:  8%; background: #7d5936; opacity: .40; }
  .t3 { left: 33%; width: 18%; background: #5c3f27; opacity: .48; }
  .t4 { left: 55%; width: 10%; background: #77522f; opacity: .36; }
  .t5 { left: 68%; width: 10%; background: #674628; opacity: .44; }

}

/* The page sits above all of it. Bands with their own background cover the trees where
   they run full width, which keeps this from looking like wallpaper. */
.nav, main, footer { position: relative; z-index: 1; }

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

.nav {
  position: sticky; top: 0; z-index: 50; overflow: hidden;
  /* Deep autumn red — the darkest of the bright leaf colours rather than a dark colour,
     so the bar reads as turned foliage above the brown trunks in the gutters. One accent
     against it: a turned gold. Measured 6.9:1 for the cream and 4.7:1 for the gold, which
     is what set how deep the red could go. */
  background: #9c3f24;
  border-bottom: 1px solid #7f3119;
}
.nav-in { position: relative; z-index: 1;
  display: flex; align-items: center; gap: 1.5rem; padding: .7rem 0; }
.logo { display: flex; align-items: center; gap: .6rem; text-decoration: none; color: #f4d68c; }
.logo img { width: 34px; height: 34px; border-radius: 50%; }
/* The wordmark is drawn outlines, not a webfont — see tools/wordmark.py. Painted as a
   mask over currentColor rather than an <img>, so one cached file serves both the gold on
   the red bar and the red on paper, and it can never load in the wrong colour.

   The ?v= is not decoration: images here are cached for thirty days, so regenerating the
   outlines under the same name leaves the old ones being served for a month. Bump it
   whenever tools/wordmark.py is re-run. */
.wordmark {
  display: block;
  height: 26px;                        /* a script needs more height to read */
  width: 113px;                       /* option AL measures 4.34:1 on its box */
  background: currentColor;
  -webkit-mask: url(/img/wordmark-al.svg?v=1786046791) no-repeat center / contain;
          mask: url(/img/wordmark-al.svg?v=1786046791) no-repeat center / contain;
}
@media (max-width: 420px) { .wordmark { height: 22px; width: 95px; } }
/* Lost when the wordmark styles were swapped in: without it the links sit hard against
   the logo with no spacing, instead of grouping to the right of the bar. */
.nav-links { display: flex; gap: 1.5rem; margin-left: auto; }
.nav-links a { color: rgba(251,245,233,.88); text-decoration: none; font-size: .93rem;
  font-weight: 500; }
.nav-links a:hover { color: #f4d68c; }
.nav .btn { margin-left: .25rem; }
@media (max-width: 800px) { .nav-links { display: none; } .nav .btn { margin-left: auto; } }

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

.hero { padding: clamp(3rem, 7vw, 5.5rem) 0 clamp(2rem, 4vw, 3rem); text-align: center; }
.hero h1 { font-size: clamp(2.3rem, 6vw, 4rem); margin: 1rem auto .9rem; max-width: 16ch; }
.hero .lede {
  font-size: clamp(1.05rem, 2.1vw, 1.2rem); max-width: 52ch; margin: 0 auto;
  color: var(--body);
}
.hero .actions {
  display: flex; gap: .8rem; justify-content: center; flex-wrap: wrap; margin-top: 1.8rem;
}
.hero .fineprint { margin-top: 1rem; font-size: .87rem; color: var(--muted); }

.shot-frame {
  margin-top: clamp(2rem, 5vw, 3.4rem);
  border-radius: var(--radius-lg); overflow: hidden;
  border: 1px solid var(--line-2); box-shadow: var(--shadow-lg);
  background: var(--paper-2);
}
.shot-frame img { width: 100%; }

/* ---------------------------------------------------------------- proof strip */

.proof {
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  background: var(--paper-2);
}
.proof-in {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;
  padding: 1.5rem 0; text-align: center;
}
.proof b { display: block; font-size: 1.35rem; color: var(--ink); font-family: var(--serif); }
.proof span { font-size: .84rem; color: var(--muted); }
@media (max-width: 700px) { .proof-in { grid-template-columns: repeat(3, 1fr); gap: 1rem; } }

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

section { padding: clamp(3rem, 7vw, 5.5rem) 0; }
.section-head { text-align: center; max-width: 46ch; margin: 0 auto clamp(2rem,4vw,3rem); }
.section-head h2 { font-size: clamp(1.7rem, 3.6vw, 2.4rem); margin: .7rem 0; }
.section-head p { font-size: 1.03rem; }

.feature {
  display: grid; grid-template-columns: 1fr 1.15fr; gap: clamp(2rem,5vw,4rem);
  align-items: center;
}
.feature + .feature { margin-top: clamp(3rem, 6vw, 5rem); }
.feature.flip .feature-text { order: 2; }
.feature h3 { font-size: clamp(1.35rem, 2.6vw, 1.75rem); margin-bottom: .7rem; }
.feature p { font-size: 1.02rem; }
.feature ul { margin: 1.1rem 0 0; padding: 0; list-style: none; display: grid; gap: .55rem; }
.feature li {
  display: grid; grid-template-columns: 1.3rem 1fr; gap: .6rem;
  font-size: .95rem; align-items: start;
}
.tick { color: var(--forest); font-weight: 700; line-height: 1.5; }
/* The wrapper is inert above the carousel breakpoint: `display: contents` drops it out of
   the box tree entirely, so the desktop stack of features is laid out exactly as it was
   before the element existed. */
.feature-track { display: contents; }
.dots { display: none; }

@media (max-width: 860px) {
  .feature { grid-template-columns: 1fr; }
  .feature.flip .feature-text { order: 0; }

  /* Five full-height feature sections stacked on a phone is a very long scroll for what
     is really one idea shown five ways. Side by side, it is a swipe. */
  .feature-track {
    display: flex;
    gap: 1.1rem;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    -webkit-overflow-scrolling: touch;
    /* Bleed to the screen edges so a card can sit centred with its neighbours showing,
       then pad the inside back so the first and last still line up with the text above. */
    margin-inline: calc((100vw - 100%) / -2);
    padding-inline: calc((100vw - 100%) / 2);
    scrollbar-width: none;
  }
  .feature-track::-webkit-scrollbar { display: none; }

  .feature-track .feature {
    /* Not 100%. The sliver of the next card is the only thing on screen that says this
       swipes — and this audience will not go hunting for a gesture nobody hinted at. */
    flex: 0 0 87%;
    scroll-snap-align: center;
    margin-top: 0;
  }
  /* The stacking margin is what separates them when they are stacked. In a row the gap
     does that job, and the margin would push every card but the first out of alignment. */
  .feature-track .feature + .feature { margin-top: 0; }

  /* Where you are in the five. Dots rather than a scrollbar: a hairline scrollbar is
     invisible on a phone and says nothing about how many there are. */
  .dots {
    display: flex; justify-content: center; gap: .5rem;
    margin-top: 1.6rem;
  }
  .dots i {
    width: 7px; height: 7px; border-radius: 50%;
    background: var(--line-2); transition: background .2s, transform .2s;
  }
  .dots i.on { background: var(--forest); transform: scale(1.35); }
}

/* ---------------------------------------------------------------- three up */

.trio { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.2rem; }
.tile {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 1.5rem; box-shadow: var(--shadow);
}
.tile h3 { font-size: 1.1rem; margin-bottom: .45rem; }
.tile p { font-size: .93rem; }
.tile .mark {
  width: 34px; height: 34px; border-radius: 9px; margin-bottom: .9rem;
  display: grid; place-items: center;
}
.tile .mark svg { width: 18px; height: 18px; }
@media (max-width: 800px) { .trio { grid-template-columns: 1fr; } }

/* ---------------------------------------------------------------- story */

.story {
  background: var(--paper-2);
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.story-in {
  display: grid; grid-template-columns: auto 1fr; gap: clamp(1.4rem,3vw,2.6rem);
  align-items: start;
}
.story img { width: 76px; height: 76px; border-radius: 50%; }
.story h2 { font-size: clamp(1.5rem, 3vw, 2rem); margin-bottom: .9rem; }
.story p { font-size: 1.05rem; margin-bottom: .9rem; max-width: 62ch; }
.story .sign { font-family: var(--serif); font-style: italic; color: var(--ink); }
@media (max-width: 640px) { .story-in { grid-template-columns: 1fr; } }

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

.plans { display: grid; grid-template-columns: 1fr 1fr; gap: 1.4rem; align-items: stretch; }
.plan {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius-lg);
  padding: clamp(1.5rem, 3vw, 2.2rem); display: flex; flex-direction: column;
  box-shadow: var(--shadow);
}
.plan.featured { border-color: var(--forest); border-width: 2px; }
.plan .tag {
  font-size: .74rem; letter-spacing: .14em; text-transform: uppercase;
  font-weight: 700; color: var(--forest); margin-bottom: .8rem;
}
.plan.featured .tag { color: var(--ember); }
/* Figure and term on one line, as "$1 / year".
   On its own row underneath, the term read as a caption — something you could skip — and
   a caption next to a very low number gets filled in as "per month" by anyone scanning.
   Set on the same baseline behind a slash, the price cannot be read without its unit,
   which is the whole point. Baseline alignment rather than centre: the slash and the word
   have to sit on the figure's baseline or the pair looks like a typo. */
.plan .amount {
  font-family: var(--serif); font-size: 2.6rem; color: var(--ink);
  line-height: 1; margin-bottom: 1.2rem;
  display: flex; align-items: baseline; gap: .35rem; flex-wrap: wrap;
}
.plan .amount .yr {
  font-family: var(--sans); font-size: 1.15rem; font-weight: 800;
  color: var(--forest); letter-spacing: .01em;
}
.plan.featured .amount .yr { color: var(--ember); }
.plan ul { margin: 0 0 1.6rem; padding: 0; list-style: none; display: grid; gap: .6rem; }

/* The buy button sits at the foot of its card, so the two line up even though one tier
   lists more than the other. `margin-top: auto` does it — the card is a flex column, and
   without it the shorter card's button floats mid-air beside the taller one's. */
/* Smaller, and a squircle rather than a pill.
   A full-width pill reading "Get everything" was doing too much for a two dollar
   decision: the shape shouted and the wording sounded like an indulgence. Both cards say
   the same neutral thing now, so neither reads as the splurge and the choice is about
   what you want on the map rather than about how much you are spending. */
.plan .buy {
  margin-top: auto;
  align-self: start;
  padding: .6rem 1.15rem;
  font-size: .92rem;
  border-radius: 14px;
  background: var(--forest);
  color: #fff;
  border-color: var(--forest);
}
.plan .buy:hover { background: var(--forest-2); border-color: var(--forest-2); }
.plan.featured .buy { background: var(--ember); border-color: var(--ember); }
.plan.featured .buy:hover { filter: brightness(1.08); }

.plan li {
  display: grid; grid-template-columns: 1.3rem 1fr; gap: .55rem;
  font-size: .95rem; align-items: start;
}
.plan .why {
  font-size: .9rem; color: var(--muted); margin-top: auto;
  padding-top: 1rem; border-top: 1px solid var(--line);
}
@media (max-width: 760px) { .plans { grid-template-columns: 1fr; } }

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

.faq { max-width: 760px; margin-inline: auto; }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  cursor: pointer; list-style: none; padding: 1.1rem 2rem 1.1rem 0; position: relative;
  font-weight: 600; color: var(--ink); font-size: 1.02rem;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+"; position: absolute; right: .35rem; top: 50%; transform: translateY(-50%);
  font-size: 1.3rem; color: var(--muted); font-weight: 400;
}
.faq details[open] summary::after { content: "\2212"; }
.faq details p { padding: 0 0 1.2rem; font-size: .98rem; }

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

.closing { background: var(--forest); color: #eef2ea; text-align: center; }
/* 0.8rem was the gap between the heading and a PARAGRAPH, which no longer exists — the
   button sat almost against the heading as a result. A button is a target, not a line of
   text, and wants air on both sides. */
.closing h2 {
  color: #fff; font-size: clamp(1.7rem, 3.6vw, 2.4rem);
  margin-bottom: 2rem;
}
.closing p { max-width: 46ch; margin: 0 auto 1.8rem; color: rgba(238,242,234,.85); }
.closing .btn { background: #fff; color: var(--forest); border-color: #fff; }
.closing .btn:hover { background: var(--paper-2); border-color: var(--paper-2); }
/* `p.closing-note`, not `.closing-note`: `.closing p` is class-plus-element and outranks a
   bare class, and its margin shorthand zeroes margin-top. That silently discarded this
   rule through two attempts at changing the number — the gap measured 0px each time. */
/* `p.closing-note`, not `.closing-note`: `.closing p` is class-plus-element and outranks a
   bare class, and its margin shorthand zeroes margin-top. That silently discarded two
   earlier attempts at this — the gap measured 0px both times.

   Wider than the 46ch the other paragraphs use, because at 46ch this sentence stacked into
   a narrow three-line column under a short centred button and looked like a mistake. Two
   comfortable lines sit under the button as a caption should. */
.closing p.closing-note {
  margin: 1.6rem auto 0;
  max-width: 62ch;
  font-size: .95rem;
  line-height: 1.6;
  color: rgba(238,242,234,.8);
}
/* Was .fineprint at 70% opacity on the green band, which sank into it. This is an
   invitation, not small print — it should read as clearly as anything else here. */
/* `p.closing-note`, not `.closing-note`. `.closing p` above is class-plus-element and so
   outranks a bare class — its `margin: 0 auto 1.8rem` shorthand was zeroing margin-top and
   throwing this rule away entirely. The measured gap between the button and this note was
   0px the whole time, through two attempts at changing the number, because the number was
   never being read. Matching the element as well makes the two rules equal on specificity,
   and this one wins by coming later.

   These are two different offers — "wait for it" and "talk to us now" — so the note must
   not read as a caption on the button. */

.closing-note a { color: #fff; text-decoration: underline; text-underline-offset: 4px;
  text-decoration-thickness: 2px; text-decoration-color: var(--gold); }
.closing-note a:hover { text-decoration-color: #fff; }

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

footer { padding: 2.4rem 0 3rem; font-size: .9rem; color: var(--muted); }

/* Copyright and links on one line, in the middle of the footer rather than the top.
   The disclaimer above it is the part that matters and now leads; this is the housekeeping
   row, and the trademark line below is smaller still. Three sizes, three jobs — before,
   all three were the same weight and ran together. */
.foot-in {
  display: flex; justify-content: space-between; gap: 1.2rem; flex-wrap: wrap;
  align-items: center;
  margin-top: 1.6rem;
  padding-top: 1.4rem;
  border-top: 1px solid var(--line);
}
footer nav a { color: var(--body); text-decoration: none; margin-left: 1.4rem; }
footer nav a:hover { color: var(--forest); }
.payments {
  margin-top: 1.6rem; text-align: center; font-style: italic;
  font-size: .92rem; color: var(--muted);
}

/* Trademark attribution. Small, last, and factual: naming somebody else's mark on a
   commercial page reads as more official when the ownership is acknowledged, and the
   no-affiliation line is what keeps "official-looking" from shading into implying an
   endorsement nobody gave us. */
footer .marks {
  margin-top: 1.4rem; font-size: .76rem; opacity: .8;
  max-width: 76ch; margin-inline: auto; text-align: center;
}
footer .disclaimer {
  margin: 0 auto;
  font-size: .84rem; line-height: 1.6;
  max-width: 76ch; text-align: center;
}
@media (max-width: 640px) { footer nav a { margin: 0 1.2rem 0 0; } }

/* ---------------------------------------------------------------- cookie notice */

#notice {
  position: fixed; inset: auto .8rem .8rem; z-index: 60;
  background: var(--ink); color: var(--paper);
  border-radius: var(--radius); padding: .9rem 1.1rem;
  display: flex; align-items: center; gap: 1rem; flex-wrap: wrap;
  box-shadow: var(--shadow-lg); font-size: .88rem;
  max-width: 640px; margin-inline: auto;
}
#notice p { margin: 0; flex: 1 1 20rem; }
#notice a { color: var(--gold); }
#notice button {
  font: inherit; font-weight: 600; background: none; color: var(--paper);
  border: 1px solid rgba(250,246,238,.4); border-radius: 999px;
  padding: .35rem 1rem; cursor: pointer;
}
#notice button:hover { background: rgba(250,246,238,.12); }

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

.doc { padding: 3rem 0 4rem; max-width: 760px; margin-inline: auto; }
.doc h1 { font-size: 2rem; margin-bottom: .3rem; }
.doc .updated { font-size: .85rem; color: var(--muted); margin-bottom: 2.2rem; }
.doc h2 { font-size: 1.15rem; margin: 2rem 0 .5rem; }
.doc p, .doc li { margin-bottom: .8rem; }
.doc ul { padding-left: 1.1rem; }
.doc .notice-box {
  border-left: 3px solid var(--ember); padding: .2rem 0 .2rem 1.2rem; margin: 1.2rem 0;
}


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

.form-page { padding: clamp(2.4rem, 6vw, 4rem) 0 3rem; max-width: 660px; }
.form-page h1 { font-size: clamp(1.9rem, 5vw, 2.7rem); margin: .6rem 0 .8rem; }
.form-page .lede { font-size: 1.05rem; margin-bottom: 2rem; }

.contact { display: grid; gap: 1.1rem; }
.contact label { display: grid; gap: .4rem; }
.contact label > span { font-size: .9rem; font-weight: 600; color: var(--ink); }
.contact label em { font-style: normal; font-weight: 400; color: var(--muted); }
.contact input,
.contact textarea {
  font: inherit; font-size: 1rem; color: var(--ink);
  background: var(--card); border: 1px solid var(--line-2); border-radius: 10px;
  padding: .75rem .9rem; width: 100%;
}
.contact textarea { resize: vertical; line-height: 1.6; }
.contact input:focus,
.contact textarea:focus {
  outline: 2px solid var(--forest); outline-offset: 1px; border-color: var(--forest);
}
.contact ::placeholder { color: var(--muted); opacity: .75; }

/* Off-screen rather than display:none — a bot reading the markup fills anything it finds,
   and some skip fields that are explicitly hidden. */
.trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-foot { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; margin-top: .4rem; }
.form-foot .small { flex: 1 1 16rem; }

.result {
  border-radius: 10px; padding: .9rem 1.1rem; margin-bottom: 1.6rem; font-size: .98rem;
}
.result.ok  { background: rgba(47,74,55,.1);  color: var(--forest); border: 1px solid rgba(47,74,55,.3); }
.result.bad { background: rgba(180,85,42,.1); color: var(--ember);  border: 1px solid rgba(180,85,42,.3); }


/* ------------------------------------------- classes for what were inline styles

   serve.py sends `style-src 'self'`, which discards style attributes exactly as it
   discards inline <style> blocks. Everything set that way was being thrown away: the
   three tile icon backgrounds never painted, and two sections kept padding they were
   meant to lose. */
.tile .mark.m-forest { background: rgba(47,74,55,.10); }
.tile .mark.m-ember  { background: rgba(180,85,42,.12); }
.tile .mark.m-gold   { background: rgba(200,154,60,.15); }
.shot-frame.flush { margin-top: 0; }
section.tight-top { padding-top: 0; }
.kicker.push { margin-left: auto; }
