/*
    NomadStamp — nomadstamp.app

    One stylesheet for three pages. The palette is the app's own asset catalogue, pasted here
    as hex, because the site is a companion to the app and not a second brand. The light
    values are the "any" appearance and the dark ones are the "dark" appearance of the same
    colorsets, so a screenshot dropped onto this page sits on the background it was taken on.

    No framework, no build step, no web font, no external request of any kind — the same
    reason the app itself carries no dependencies: a privacy policy that says nothing leaves
    the device should not be served from a page that calls out to three CDNs to say so.

    ## The shape of it

    That constraint is why this file looks the way it does. The usual method for a page like
    this assumes a display webfont, a JS motion library and a utility framework, and all three
    are unavailable here on purpose — so what is taken from it is the part that is not about
    tools: one focal element, viewport-scale type, extreme whitespace, a palette of three
    hues, and an entry animation, because a static mount reads as broken.

    Two things follow from having no display face. The headline carries its weight through
    **size and tracking** rather than through a distinctive letterform — `-0.035em` at 5rem,
    which is the thing that most separates set display type from big body type. And the one
    piece of visual personality on the page is the **device frame**, drawn here in CSS with
    the same corner radius, bezel and shadow as the App Store frame treatment, so the site and
    the listing are one composition rather than two.

    **Motion is CSS-only and gated twice** — behind `prefers-reduced-motion` and behind
    `@supports (animation-timeline: view())`. The second gate is the important one: a
    scroll-driven reveal that hides its content first and then finds the browser cannot run
    the animation is a blank page, so nothing is ever hidden outside the `@supports` block.

    ## What Session 62 changed, and the rule it was applied under

    The page was **seven sections of near-identical rhythm with a great deal of empty ground
    between very little content**, which is the specific failure a reader recognises as
    machine-made even when every sentence on it is true. Four patterns were removed by name,
    because each of them is a documented tell rather than a matter of taste:

    - **The tracked uppercase micro-label above an oversized headline**, and its three repeats
      as ONE / TWO / THREE down the page. This is *the* default generated-SaaS hero.
    - **The three-equal-column grid** of screenshots, and the second one hiding inside the
      "Built for UK vans" bullets. Two weak sections became one dense one.
    - **Uniform radius and one soft shadow on everything.** Cards now differ by their job:
      the boundary has a rule down its edge, the pack pages sit at a document's 6px, the
      devices carry the app's own 15.5%.
    - **Air standing in for content.** Whitespace is worth having around something. Around
      three lines of text in a 700px-tall row it is just a page that has not been finished,
      and that was the honest read of the middle of this one.

    What replaced them is density that was already in the repository and was not being shown:
    the sample pack's real figures, the twelve headings it actually prints, and the questions
    the support page already answers. **No sentence on the page is new** — the sourcing rule
    in `website/README.md` is unchanged and this pass added nothing to argue with.
*/

:root {
    --page:        #F7F5EE;  /* PageBackground, light */
    --tint:        #EFEBDD;  /* the alternating ground — PageBackground, one step down */
    --surface:     #FFFFFF;  /* CardSurface, light */
    --border:      #E4E1D4;  /* CardBorder, light */
    --ink:         #1B1A17;  /* PageBackground's dark value, used as ink on paper */
    --ink-soft:    #55514A;
    --accent:      #2E7D5B;  /* AccentPine, light */
    --module:      #1C2A26;  /* ModuleSurface, light — the one inverted surface */
    --module-ink:  #F4F1E6;  /* ModulePrimary */
    --module-soft: #A8C4B2;  /* ModuleSecondary */
    --accent-dark: #58B389;  /* AccentPine, dark — used where ink is light on a dark ground */
    --bezel:       #0B110F;  /* the device frame; matches the App Store frame treatment */
    --measure: 34rem;
    --doc: 44rem;            /* the policy pages' measure — see "Documents" below */
    --shell: min(72rem, 100% - 2.5rem);
}

@media (prefers-color-scheme: dark) {
    :root {
        --page:        #1B1A17;
        --tint:        #211F1B;
        --surface:     #26241F;
        --border:      #3A3630;
        --ink:         #F4F1E6;
        --ink-soft:    #B3ADA1;
        --accent:      #58B389;  /* AccentPine, dark */
        --module:      #365B4C;
        --module-ink:  #F4F1E6;
        --module-soft: #C7DDCE;
    }
}

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

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
    margin: 0;
    background: var(--page);
    color: var(--ink);
    /* SF Pro where it exists, the platform's own face everywhere else. §4: no custom
       typeface, for the same reason the app has none. */
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    font-size: 1.0625rem;
    line-height: 1.55;
    text-rendering: optimizeLegibility;
    overflow-x: hidden;
}

/* Legible before beautiful (§1.2): the primary reader is older and may be on a phone in
   poor light, so nothing here is set smaller than the platform's body size except the
   footnotes, and everything scales with the browser's own text size. */

.wrap { width: min(60rem, 100% - 2.5rem); margin-inline: auto; }
.wide { width: var(--shell); margin-inline: auto; }
.narrow { max-width: var(--measure); }

a { color: var(--accent); text-underline-offset: 0.15em; }
a:hover { text-decoration-thickness: 2px; }

/* Heading scale. **Deliberately not one size for every `h2` on the page** — a page where
   every section opens at the same weight reads as a list of equal things, and these are not
   equal. `.h-lead` is the two arguments a buyer decides on; `.h-quiet` is the housekeeping. */
h1, h2, h3 { line-height: 1.2; letter-spacing: -0.01em; }
h1 {
    font-size: clamp(2.3rem, 5.6vw, 4.1rem);
    letter-spacing: -0.035em;
    line-height: 1.02;
    margin: 0 0 0.5em;
    text-wrap: balance;
}
h2 { font-size: clamp(1.5rem, 3.6vw, 2.1rem); letter-spacing: -0.02em; margin: 0 0 0.6em; text-wrap: balance; }
h2.h-lead { font-size: clamp(1.9rem, 4.4vw, 2.9rem); letter-spacing: -0.03em; }
h2.h-quiet { font-size: clamp(1.25rem, 2.4vw, 1.5rem); letter-spacing: -0.015em; }
h3 { font-size: 1.15rem; margin: 2em 0 0.4em; }
p { margin: 0 0 1em; }

/* Sentence case, no tracking, no uppercase. It says what the product is in the words a
   reader would use, one line above a headline that is deliberately evocative rather than
   literal. The uppercase letter-spaced version of this element is the single most
   recognisable generated-page tell there is, which is the only reason it changed. */
.standfirst {
    font-size: 1rem;
    color: var(--ink-soft);
    margin: 0 0 1.1rem;
}

/* ---- Motion ------------------------------------------------------------------ */

@keyframes rise {
    from { opacity: 0; transform: translateY(24px); }
    to   { opacity: 1; transform: none; }
}

/* Entry. Only `transform` and `opacity` move, so it is composited rather than laid out.

   **The stagger is five classes rather than an inline `--d` custom property**, and that is a
   correction rather than a preference: `_headers` sets `style-src 'self'` with no
   `'unsafe-inline'`, which blocks the `style` attribute as well as `<style>` elements. An
   inline delay would therefore be silently dropped in production and every element in the
   hero would arrive at once — the one place a stagger is doing the work. Nothing in this
   site may use an inline style; there is a class for it or it does not happen. */
@media (prefers-reduced-motion: no-preference) {
    .enter { animation: rise 0.75s cubic-bezier(0.16, 1, 0.3, 1) both; }
    .enter.d1 { animation-delay: 0.08s; }
    .enter.d2 { animation-delay: 0.18s; }
    .enter.d3 { animation-delay: 0.22s; }
    .enter.d4 { animation-delay: 0.28s; }
    .enter.d5 { animation-delay: 0.36s; }
}

/* Scroll reveal. Nested inside `@supports` so that a browser without scroll-driven
   animations never sees the hidden state at all — the failure mode of the naive version is a
   page of invisible text, which is worse than no animation by a very long way. */
@supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
        .reveal {
            animation: rise linear both;
            animation-timeline: view();
            animation-range: entry 4% cover 24%;
        }
    }
}

/* ---- Chrome ------------------------------------------------------------------ */

header.site {
    border-bottom: 1px solid var(--border);
    background: color-mix(in srgb, var(--page) 88%, transparent);
    backdrop-filter: saturate(180%) blur(12px);
    position: sticky;
    top: 0;
    z-index: 20;
}
header.site .wide {
    display: flex; align-items: center; gap: 1rem;
    padding: 0.75rem 0;
}
.brand { display: flex; align-items: center; gap: 0.6rem; font-weight: 600; color: var(--ink); text-decoration: none; letter-spacing: -0.01em; }
.brand img { width: 30px; height: 30px; display: block; }
header.site nav { margin-left: auto; display: flex; align-items: center; gap: 1rem; font-size: 0.95rem; }

/* **Support and Privacy are not in the header any more** (Session 62). They are required
   App Store Connect fields and they are linked from inside the app, so the audience that
   needs them arrives at the URL directly and never reads a menu; putting them in the one
   slot a conversion page has for a call to action cost the page its only permanent CTA to
   route two links that are also in the footer of every page. They are still in the footer,
   which is where a policy link belongs. */

footer.site {
    margin-top: 0;
    border-top: 1px solid var(--border);
    padding: 2.5rem 0 3rem;
    color: var(--ink-soft);
    font-size: 0.9rem;
}
footer.site p { max-width: 46rem; }
.foot-links { display: flex; flex-wrap: wrap; gap: 1.25rem; margin-bottom: 1.5rem; font-size: 0.95rem; }

/* ---- Sections ----------------------------------------------------------------

   Pacing is carried by the ground a section stands on and by the air it is given.
   `section + section { border-top }` is deliberately not here: a rule between two sections
   is what you reach for when the sections themselves are not distinguishable.

   The order of grounds down the page is cream → tint → cream → dark → tint → cream → tint →
   dark, so no two adjacent sections share a ground and the two dark bands bookend the
   argument rather than sitting next to each other. */

section { padding: 4rem 0; }
section.tight { padding: 2.75rem 0; }
.tinted { background: var(--tint); }

/* ---- Hero -------------------------------------------------------------------- */

.hero {
    padding: clamp(1.75rem, 3.5vw, 3rem) 0 clamp(3rem, 7vw, 5rem);
}
.hero .split {
    display: grid;
    gap: clamp(2.5rem, 5vw, 4rem);
    align-items: center;
}
@media (min-width: 60rem) {
    /* The asymmetric split: the headline column is the wider one and the device is the
       counterweight, not an equal partner. */
    .hero .split { grid-template-columns: 1.15fr 0.85fr; }
}
.hero .lede {
    font-size: clamp(1.1rem, 1.5vw, 1.3rem);
    color: var(--ink-soft);
    max-width: 34rem;
}

/* A wide but short window — a 1366×768 laptop, which is still what a lot of this audience
   is on. Width alone says "give it the big treatment" and the viewport disagrees, so the
   vertical rhythm comes in and the headline stands down. **Measured rather than assumed**:
   at 1280×720 the full-size version put the price and the call to action below the fold,
   which on the page whose entire job is those two things is not a small miss. */
@media (min-width: 60rem) and (max-height: 50rem) {
    .hero { padding-top: 1.25rem; }
    .hero h1 { font-size: clamp(2.3rem, 4.6vw, 3.3rem); margin-bottom: 0.4em; }
    .hero .lede { font-size: 1.05rem; }
    .hero .standfirst { margin-bottom: 0.8rem; }
    .hero .module { padding: 1.15rem 1.3rem 1.1rem; }
}

/* ---- The device --------------------------------------------------------------

   Drawn rather than baked in, so the site can show the **untreated** captures. The treated
   App Store frame carries its own dark ground and its own burned-in caption, and six of
   those dropped onto a cream page is six dark rectangles each shouting a headline that the
   heading beside it already made. The numbers are the frame treatment's: bezel 1.4% of the
   device width, screen radius 14.1%. */

.device {
    /* **The `vh` term is what makes the hero fit one viewport**, which is the whole point of
       a hero. The device is 2.17× as tall as it is wide, so a width chosen against the page
       width alone produces an 830px phone on a 720px laptop and pushes the price and the one
       call to action below the fold — measured, not guessed. */
    --w: min(21rem, 70vw, 33vh);
    width: var(--w);
    max-width: 100%;
    padding: calc(var(--w) * 0.014);
    background: var(--bezel);
    border-radius: calc(var(--w) * 0.155);
    box-shadow: 0 30px 70px rgba(0, 0, 0, 0.28), 0 4px 12px rgba(0, 0, 0, 0.14);
    margin-inline: auto;
}
.device img {
    display: block;
    width: 100%;
    height: auto;
    border-radius: calc(var(--w) * 0.141);
}

/* The same object at the two smaller sizes the page uses it at. One component, three sizes,
   so a screenshot never appears on this site outside a device. */
.device.md { --w: min(17rem, 62vw, 46vh); box-shadow: 0 22px 50px rgba(0, 0, 0, 0.24), 0 3px 9px rgba(0, 0, 0, 0.12); }
/* 40vw rather than 54: there are two of these side by side, so the viewport term has to
   budget for both of them and the gap between them, not for one. */
.device.sm { --w: min(13.5rem, 40vw); box-shadow: 0 16px 38px rgba(0, 0, 0, 0.2), 0 2px 7px rgba(0, 0, 0, 0.1); }

/* ---- The price module --------------------------------------------------------

   The app's one inverted surface, borrowed for the one thing on the page that is a
   commitment: what it costs and how you get it. */

.module {
    background: var(--module);
    color: var(--module-ink);
    border-radius: 18px;
    padding: 1.4rem 1.5rem 1.3rem;
    max-width: 27rem;
}
.module .price { font-size: 1.75rem; font-weight: 650; display: block; letter-spacing: -0.02em; }
.module p { color: var(--module-soft); margin: 0.4rem 0 0; font-size: 0.95rem; }
.module a { color: var(--module-ink); }
.module .store { margin-top: 1.15rem; }

/* ---- The App Store call to action --------------------------------------------

   **One component, four placements: the header, the hero, the pack band and the closer.**

   It is a real link and it goes somewhere real — `#price`, the block that says what the app
   costs and what state it is in — because the alternative on a page for an unreleased app is
   a button that does nothing, and a control that does nothing when tapped is worse than an
   honest sentence. On launch day the four `href`s become the App Store URL and nothing else
   about this component changes. `website/README.md` lists the four lines.

   The `.soon` variant is the same object with the state spelled out, used where there is
   room for it. Neither of them ever says "download" while there is nothing to download.

   **No Apple mark and no imitation of the App Store badge.** Apple supplies that artwork and
   requires its own, unaltered; a hand-drawn approximation of a badge is a trademark problem
   rather than a design shortcut, and this site has no external requests to fetch the real one
   with. The button is type on the app's own accent, which is also the only accent on the
   page, so it is the loudest thing in the header without borrowing anybody's logo. */

.store-cta {
    display: inline-flex;
    align-items: center;
    gap: 0.7rem;
    background: var(--accent);
    color: #FFFFFF;
    padding: 0.75rem 1.3rem;
    border-radius: 999px;
    text-decoration: none;
    font-weight: 600;
    line-height: 1.2;
    white-space: nowrap;
    transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1), filter 0.2s;
}
.store-cta:hover { filter: brightness(1.06); transform: translateY(-2px); box-shadow: 0 10px 26px rgba(0, 0, 0, 0.18); }
.store-cta:active { transform: translateY(0) scale(0.985); }
@media (prefers-color-scheme: dark) { .store-cta { color: #14231C; } }
@media (prefers-reduced-motion: reduce) {
    .store-cta { transition: filter 0.2s; }
    .store-cta:hover { transform: none; }
}

/* In the header it is smaller, because a header CTA is a reminder and not the ask. */
header.site .store-cta { padding: 0.5rem 1rem; font-size: 0.92rem; }

/* **Two labels, one of which is always hidden.** The button is `white-space: nowrap` on
   purpose — "Coming to the App / Store" broken over two lines in a sticky header looks like
   a mistake — and at 375px the full label plus the wordmark is 13px wider than the screen,
   which pushed the document to 437px and took the section below out with it. A shorter label
   is the fix a wrapping one is not. */
.cta-short { display: none; }
@media (max-width: 30rem) {
    .cta-long { display: none; }
    .cta-short { display: inline; }
}

/* On a dark ground the fill inverts, so the button is still the lightest thing in the band. */
.band .store-cta, .closer .store-cta { background: var(--module-ink); color: var(--module); }
.module .store-cta { background: var(--module-ink); color: var(--module); }

.soon { display: flex; flex-wrap: wrap; align-items: center; gap: 0.9rem; }
.soon .note { margin: 0; }

/* ---- Buttons ----------------------------------------------------------------- */

.button {
    display: inline-block;
    background: var(--accent);
    color: #FFFFFF;
    padding: 0.8rem 1.35rem;
    border-radius: 12px;
    text-decoration: none;
    font-weight: 600;
    transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1), filter 0.2s;
}
@media (prefers-color-scheme: dark) { .button { color: #14231C; } }
.button:hover { filter: brightness(1.06); transform: translateY(-2px); box-shadow: 0 10px 26px rgba(0, 0, 0, 0.16); }
.button:active { transform: translateY(0) scale(0.985); }
@media (prefers-reduced-motion: reduce) { .button { transition: filter 0.2s; } .button:hover { transform: none; } }

.button.ghost {
    background: none;
    color: inherit;
    border: 1px solid currentColor;
}
.button.ghost:hover { filter: none; background: color-mix(in srgb, currentColor 10%, transparent); }

.textlink {
    display: inline-block;
    font-weight: 600;
    text-decoration: none;
    border-bottom: 2px solid currentColor;
    padding-bottom: 2px;
}

/* ---- The evidence strip ------------------------------------------------------

   Four numbers, immediately under the hero. **This is the page's proof section and it is
   deliberately not social proof**: the app is unreleased, so there are no downloads, no
   reviews and no logos, and the honest options are to invent some or to prove something
   else. What it proves instead is the artefact — the sample pack sitting three sections
   below is 36 real pages off a van with 113 records on it, and every one of those numbers
   is checkable by opening the PDF and counting.

   Tabular figures so the row does not shuffle between the four columns. */

.evidence {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 1.75rem 1rem;
}
@media (min-width: 46rem) { .evidence { grid-template-columns: repeat(4, 1fr); } }
.evidence .fig {
    display: block;
    font-variant-numeric: tabular-nums;
    font-size: clamp(2.1rem, 4.5vw, 3.1rem);
    font-weight: 650;
    letter-spacing: -0.035em;
    line-height: 1;
}
.evidence dt { font-size: 0.95rem; color: var(--ink-soft); margin-top: 0.5rem; }
.evidence dd { margin: 0; }
.evidence-note { margin: 1.75rem 0 0; max-width: 44rem; }

/* ---- The moats ---------------------------------------------------------------

   **Two rows, not three.** The third — "it ends as a document" — is the whole of the next
   band, and running it here as a third identical row was the page making its strongest
   argument twice, once at the size of the two weaker ones.

   Each row is a device on one side and, on the other, a heading, a claim, and a **receipt**:
   the actual data off that screen, set as a small register the way the app sets it. That
   block is what the row was missing. A claim beside 400px of empty page reads as a claim
   nobody could support; the same claim beside six dated rows reads as a description of a
   screenshot the reader is looking at. */

.moat {
    display: grid;
    gap: clamp(2rem, 4.5vw, 3.5rem);
    align-items: center;
    padding: clamp(1.5rem, 3.5vw, 2.5rem) 0;
}
@media (min-width: 60rem) {
    .moat { grid-template-columns: 0.85fr 1.15fr; }
    .moat.flip .moat-figure { order: 2; }
}
.moat-figure { margin: 0; }
.moat h3 { font-size: clamp(1.45rem, 3vw, 2rem); letter-spacing: -0.025em; margin: 0 0 0.5em; }
.moat p { color: var(--ink-soft); max-width: 32rem; }

/* The receipt. Hairline rules and a tabular right-hand column — the app's own register row,
   which is the point: the site should look like it was made by whoever made the app. */
.receipt {
    margin: 1.5rem 0 0;
    max-width: 30rem;
    border-top: 1px solid var(--border);
    font-size: 0.95rem;
}
.receipt div {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1.5rem;
    padding: 0.55rem 0;
    border-bottom: 1px solid var(--border);
}
.receipt dt { color: var(--ink); }
.receipt dd { margin: 0; color: var(--ink-soft); font-variant-numeric: tabular-nums; white-space: nowrap; }
.receipt-note { font-size: 0.9rem; color: var(--ink-soft); margin: 0.9rem 0 0; }

/* ---- The inverted band -------------------------------------------------------

   The resale pack gets the page's one hard change of ground. This is the section a
   sceptical buyer decides the product is real on, so it is the section that looks different
   from every other one. Tokens are reassigned rather than colours overridden piecemeal, so
   everything inside inherits correctly. */

.band {
    --page: var(--module);
    --ink: var(--module-ink);
    --ink-soft: var(--module-soft);
    --accent: var(--accent-dark);
    --border: rgba(244, 241, 230, 0.18);
    --surface: rgba(244, 241, 230, 0.06);
    background: var(--module);
    color: var(--module-ink);
    padding: clamp(3.25rem, 7vw, 5.5rem) 0;
}
.band .button { background: var(--module-ink); color: var(--module); }
.band h2, .band h3 { color: var(--module-ink); }

.band-head { display: grid; gap: 1.5rem 3.5rem; align-items: end; margin-bottom: 2.5rem; }
@media (min-width: 58rem) { .band-head { grid-template-columns: 1.1fr 0.9fr; } }
.band-head p { margin: 0; font-size: 1.1rem; color: var(--module-soft); }

/* The pages, fanned. Three real renders of the sample pack, so the document reads as a
   document before anybody is asked to open a file. */
.fan {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: clamp(0.6rem, 2vw, 1.4rem);
    align-items: start;
    margin: 0;
    perspective: 1400px;
}
.fan img {
    display: block;
    width: 100%;
    height: auto;
    background: #FFFFFF;
    border-radius: 6px;
    box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34);
    transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}
@media (min-width: 48rem) {
    /* A shallow fan, not a card explosion: the middle page is the focal one, the outer two
       recede symmetrically. */
    .fan img:first-child { transform: rotate(-2.2deg) translateY(14px) scale(0.955); }
    .fan img:last-child  { transform: rotate(2.2deg)  translateY(14px) scale(0.955); }
    .fan img:nth-child(2) { position: relative; z-index: 2; }
    .fan:hover img:first-child { transform: rotate(-3.4deg) translateY(6px) scale(0.97); }
    .fan:hover img:last-child  { transform: rotate(3.4deg)  translateY(6px) scale(0.97); }
}
@media (prefers-reduced-motion: reduce) { .fan img { transition: none; } }

/* On a phone, three pages side by side are 105px wide each — three grey slivers that say
   "documents" without being one. A swipeable strip keeps all three and makes each of them
   big enough to see that it is a *page*, which is the entire job of this element. The
   scrolling is inside the strip, so the page itself still never scrolls sideways. */
@media (max-width: 34rem) {
    .fan {
        grid-auto-flow: column;
        grid-template-columns: none;
        grid-auto-columns: 62%;
        overflow-x: auto;
        scroll-snap-type: x mandatory;
        padding-bottom: 0.5rem;
        overscroll-behavior-x: contain;
    }
    .fan img { scroll-snap-align: center; }
}

.fan-figure { margin: 0; }
.fan-note { margin-top: 1rem; }

/* What is in it, and what it is worth — side by side under the fanned pages.

   **The twelve headings are the document's own**, read out of `PackSection.label` rather
   than written for the page, which is why they are worth printing: a list of twelve real
   headings is a specification, and "everything you need" is a sentence anybody could type
   about anything. They double as the answer to "you choose what goes in", because these are
   the exact twelve switches on the export screen. */
.pack-detail {
    display: grid;
    gap: clamp(2rem, 4vw, 3.5rem);
    margin-top: clamp(2.5rem, 5vw, 3.5rem);
    align-items: start;
}
@media (min-width: 56rem) { .pack-detail { grid-template-columns: 1fr 1fr; } }

.contents {
    columns: 2;
    column-gap: 1.75rem;
    margin: 1.25rem 0 1.5rem;
    padding: 0;
    list-style: none;
    font-size: 0.98rem;
}
.contents li {
    break-inside: avoid;
    padding: 0.3rem 0 0.3rem 1.35rem;
    position: relative;
    color: var(--ink-soft);
}
/* A tick drawn in CSS rather than an icon font or an inline SVG twelve times over. */
.contents li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.72em;
    width: 0.45rem;
    height: 0.22rem;
    border-left: 2px solid var(--accent);
    border-bottom: 2px solid var(--accent);
    transform: rotate(-45deg);
}
@media (max-width: 30rem) { .contents { columns: 1; } }

.resale {
    border-left: 3px solid var(--accent);
    padding: 0.15rem 0 0.15rem 1.25rem;
    margin: 0 0 1.5rem;
    max-width: 32rem;
}
.resale p:last-child { margin-bottom: 0; }

/* ---- The specification split -------------------------------------------------

   **Two weak sections became this one.** There was a three-across grid of screenshots with
   nothing said about them, and below it a two-column list of bullets with nothing shown.
   Neither could carry a section: the pictures had no argument and the argument had no
   pictures. Together they are the densest block on the page, and the layout direction is
   different from both the moats above and the band below it.

   The list is set as label / value rather than bullets, because that is what it is — a
   specification — and because a bulleted list of six things is the shape the eye skips. */

.spec-split { display: grid; gap: clamp(2.25rem, 5vw, 4rem); align-items: start; }
@media (min-width: 62rem) { .spec-split { grid-template-columns: 1fr 0.95fr; } }
/* A grid item's default `min-width: auto` lets an over-wide child widen its own column, so
   one 416px pair of phones was making the specification list beside it 416px too, on a 375px
   screen. This is the line that stops an overflow spreading to its neighbours. */
.spec-split > * { min-width: 0; }

.spec { margin: 0; border-top: 1px solid var(--border); }
.spec > div {
    display: grid;
    gap: 0.15rem 1.5rem;
    padding: 0.85rem 0;
    border-bottom: 1px solid var(--border);
}
@media (min-width: 34rem) { .spec > div { grid-template-columns: 9.5rem 1fr; gap: 0.15rem 1.5rem; } }
.spec dt { font-weight: 600; }
.spec dd { margin: 0; color: var(--ink-soft); }

/* The two supporting screens, staggered rather than sat in a row — a row of three
   equal-width phones is the pattern this page had and is the one to stay away from. */
.screen-pair { display: flex; justify-content: center; gap: clamp(0.75rem, 2vw, 1.5rem); max-width: 100%; }
.screen-pair figure { margin: 0; min-width: 0; }
.screen-pair figcaption { font-size: 0.85rem; color: var(--ink-soft); margin-top: 0.85rem; text-align: center; max-width: 13.5rem; }
@media (min-width: 34rem) { .screen-pair > *:first-child { margin-top: 2.25rem; } }

/* ---- The boundary ------------------------------------------------------------

   The never-advisory boundary is a positioning decision rather than a legal footnote, and
   this paragraph is where the page states it. A buyer who discovers this after paying leaves
   a one-star review; a buyer who reads it first either wants the app or doesn't. */

.boundary {
    /* Centred rather than set left, which is what it was. A 34rem card ranged left in a
       60rem container leaves a 26rem stripe of empty page beside a statement that is not
       waiting for anything to appear next to it. */
    margin-inline: auto;
    max-width: 40rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 5px solid var(--accent);
    border-radius: 16px;
    padding: clamp(1.5rem, 4vw, 2.4rem);
}
.boundary h2 { margin-top: 0; }
.boundary p:last-child { margin-bottom: 0; }
.boundary .lead-line { font-size: 1.15rem; color: var(--ink); }

/* ---- Questions ---------------------------------------------------------------

   `<details>` and `<summary>`, so an accordion costs no JavaScript — which matters here
   because the content-security policy in `_headers` has no `script-src` at all and adding
   one to open a panel would be a real cost for a small convenience.

   Two columns on a wide screen. The answers are the support page's, word for word: these
   are the questions somebody actually sends an email about, so they are the right ones to
   answer before they have to. */

.qa { display: grid; gap: 0 3rem; }
@media (min-width: 56rem) { .qa { grid-template-columns: 1fr 1fr; } }

.qa details {
    border-bottom: 1px solid var(--border);
}
.qa summary {
    cursor: pointer;
    padding: 1rem 2rem 1rem 0;
    font-weight: 600;
    list-style: none;
    position: relative;
}
.qa summary::-webkit-details-marker { display: none; }
/* The chevron, drawn from two borders and rotated — same reasoning as the tick above. */
.qa summary::after {
    content: "";
    position: absolute;
    right: 0.4rem;
    top: 1.35rem;
    width: 0.5rem;
    height: 0.5rem;
    border-right: 2px solid var(--accent);
    border-bottom: 2px solid var(--accent);
    transform: rotate(45deg);
    transition: transform 0.25s ease;
}
.qa details[open] summary::after { transform: rotate(-135deg); top: 1.5rem; }
@media (prefers-reduced-motion: reduce) { .qa summary::after { transition: none; } }
.qa details p { color: var(--ink-soft); margin: 0 0 1rem; padding-right: 1rem; }
.qa summary:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; border-radius: 4px; }

/* ---- The closer --------------------------------------------------------------

   One heading, the price, one button. The page's last section before the footer, and the
   only one whose entire job is the click. */

.closer {
    --ink: var(--module-ink);
    --ink-soft: var(--module-soft);
    --border: rgba(244, 241, 230, 0.18);
    background: var(--module);
    color: var(--module-ink);
    padding: clamp(3rem, 6vw, 4.5rem) 0;
    text-align: center;
}
.closer h2 { color: var(--module-ink); margin-bottom: 0.4em; }
.closer .price {
    display: block;
    font-size: clamp(2.4rem, 6vw, 3.6rem);
    font-weight: 650;
    letter-spacing: -0.035em;
    line-height: 1;
    margin-bottom: 0.6rem;
}
.closer p { color: var(--module-soft); max-width: 32rem; margin-inline: auto; }
.closer .soon { justify-content: center; margin-top: 1.75rem; }
.closer .note { color: var(--module-soft); }

/* ---- Plain content ----------------------------------------------------------- */

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 1.25rem 1.4rem;
}

ul.plain { padding-left: 1.15rem; margin: 0 0 1em; }
ul.plain li { margin-bottom: 0.55em; }

.cols { display: grid; gap: 1.5rem 3rem; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); }

/* Two prose blocks side by side, where each is short enough that a full-width measure would
   leave a stripe of empty page beside it. */
.duo { display: grid; gap: clamp(2rem, 5vw, 3.5rem); }
@media (min-width: 56rem) { .duo { grid-template-columns: 1fr 1fr; } }
.duo > * > :last-child { margin-bottom: 0; }

table {
    width: 100%;
    border-collapse: collapse;
    font-size: 0.95rem;
    margin-bottom: 1.5rem;
}
th, td { text-align: left; padding: 0.55rem 0.7rem; border-bottom: 1px solid var(--border); vertical-align: top; }
th { font-weight: 600; }
.scroll { overflow-x: auto; }

.flush { margin: 0; }
.spaced { margin-top: 2.5rem; }

.note { color: var(--ink-soft); font-size: 0.95rem; }
.updated { color: var(--ink-soft); font-size: 0.9rem; margin-bottom: 2rem; }

/* ---- Documents ---------------------------------------------------------------

   The support and privacy pages. **They were `.wrap.narrow`, which is a 34rem measure
   centred inside a 60rem container** — on a laptop that is a ribbon of text down the middle
   of the window with a third of the screen empty on either side, and it is why both pages
   scrolled about twice as far as they needed to. A policy page is a *document*: it is read
   by somebody looking for one clause, so the two things it owes them are a wider column and
   a way to jump.

   Above 62rem it becomes two columns — a sticky contents list and a 44rem measure. 44rem is
   roughly 85 characters at this size, which is wide for running prose and right for a page
   whose densest element is a fifteen-row table that was previously scrolling sideways inside
   a 34rem box on a 1440px screen. */

/* **The title is its own grid item rather than the first thing inside the body**, and that is
   a mobile fix rather than a tidy-up. Stacked in source order, the rail came first, so a phone
   landed on ten anchor links and pushed "Privacy policy" off the bottom of the screen — the
   reader arrives at a table of contents for a page they have not been shown yet. Named areas
   put the heading above the rail on a phone and beside it on a laptop, from one declaration
   each, with no duplicated markup. */
.doc-layout {
    display: grid;
    gap: 1.75rem;
    grid-template-areas: "head" "toc" "body";
}
@media (min-width: 62rem) {
    .doc-layout {
        grid-template-columns: 13rem minmax(0, var(--doc));
        grid-template-areas: ". head" "toc body";
        gap: 0 4rem;
        justify-content: center;
    }
}
.doc-head { grid-area: head; }
.doc-head > :last-child { margin-bottom: 0; }
.toc { grid-area: toc; }
.doc-body { grid-area: body; max-width: var(--doc); min-width: 0; }
.doc-body h2 { font-size: clamp(1.3rem, 2.6vw, 1.6rem); margin-top: 2.5rem; }
.doc-body h2:first-of-type { margin-top: 2rem; }
.doc-body h3 { font-size: 1.08rem; margin-top: 1.75rem; }
.doc-body > h1 { font-size: clamp(2rem, 4.5vw, 2.8rem); }

/* The contents rail. Sticky on a tall enough window only — a sticky element on a short
   laptop screen is a scrolling box inside a scrolling page, which is worse than no rail. */
.toc { font-size: 0.92rem; margin-top: 0.5rem; }
.toc h2 {
    font-size: 0.85rem;
    letter-spacing: 0.04em;
    text-transform: none;
    color: var(--ink-soft);
    margin: 0 0 0.8rem;
    font-weight: 600;
}
.toc ul { list-style: none; margin: 0; padding: 0; }
.toc li { margin-bottom: 0.55rem; }
.toc a { color: var(--ink-soft); text-decoration: none; display: block; line-height: 1.35; }
.toc a:hover { color: var(--accent); text-decoration: underline; }
@media (min-width: 62rem) and (min-height: 40rem) {
    .toc { position: sticky; top: 5.5rem; align-self: start; max-height: calc(100vh - 8rem); overflow-y: auto; }
}
/* On a phone the rail is a plain block above the text rather than a floating widget, and it is
   set in two columns down to a genuinely narrow screen — a ten-item list in one column is most
   of a phone screen spent on links to a page the reader has not started reading. */
@media (max-width: 62rem) {
    .toc { border-bottom: 1px solid var(--border); padding-bottom: 1.25rem; margin-top: 0; }
    .toc ul { columns: 2; column-gap: 1.5rem; }
    .toc li { break-inside: avoid; margin-bottom: 0.45rem; }
}
@media (max-width: 22rem) { .toc ul { columns: 1; } }

/* Anchored headings must not land underneath the sticky header when the rail jumps to them. */
.doc-body h2, .doc-body h3 { scroll-margin-top: 5rem; }

/* The support page's questions sit two-up on a wide screen — the same reasoning as the
   landing page's, and the reason both pages got shorter rather than longer.

   **Multi-column rather than a two-column grid**, and the difference is visible: a grid sizes
   every row to its tallest cell, so a four-line answer beside a two-line one leaves a hole
   under the short one and the next question starts halfway down the page. Columns flow, so
   the two sides stay level. The accordion on the landing page is a grid for the opposite
   reason — an item there changes height when it opens, and rebalancing both columns under
   the reader's thumb is worse than a rag. */
.doc-qa { columns: 1; }
@media (min-width: 74rem) { .doc-qa { columns: 2; column-gap: 3rem; } }
.doc-qa > div { break-inside: avoid; margin-bottom: 1.75rem; }
.doc-qa h3 { margin-top: 0; }
.doc-qa p:last-child { margin-bottom: 0; }

:where(a, .button, .store-cta):focus-visible {
    outline: 3px solid var(--accent);
    outline-offset: 3px;
    border-radius: 4px;
}
