/* SafetyStop marketing site — one stylesheet, no framework, no fonts to fetch.
 *
 * Colours are lifted from Theme.swift and api/src/Page.php so the site, the app and the
 * transactional emails are visibly one product. A hex that appears in two of the three and not the
 * third is the thing to watch: the app is the source of truth.
 *
 * No fonts, no CDN, no analytics, and NO THIRD-PARTY REQUEST ON ANY PAGE LOAD. That is partly taste
 * and partly consistency: a privacy policy that says "we do not track you" alongside a third-party
 * font request is a policy that contradicts its own page.
 *
 * ⚠️ CORRECTED 2026-08-13, and the correction is the honest part. This used to say "NO external
 * requests of any kind", and that stopped being true when the web app gained a MAP of the dive site:
 * map tiles are fetched from OpenStreetMap, which therefore learns roughly where a dive was and the
 * diver's IP address. The claim was rewritten in the same commit that added the map, because a
 * stylesheet asserting a property the code no longer has is worse than one asserting nothing.
 *
 * What is still true, and is enforced rather than intended:
 *   * these marketing pages (about / privacy / support / computers / 404) fetch nothing external at
 *     all — the logo is a base64 data URI and the font stack is the system's;
 *   * the app at `/` makes no third-party request on any page load either — the login screen, the
 *     dive list and a dive's detail page all render with same-origin requests only;
 *   * map tiles are requested from OpenStreetMap ONLY when the diver taps "Show map" on a dive that
 *     has a coordinate, and the button says so before it is tapped (and the dive list's Map view is
 *     a globe drawn from a same-origin land file, so it requests nothing from anyone);
 *   * Leaflet itself is VENDORED (`site/leaflet-1.9.4.js`, sha256-pinned by
 *     `tools/leaflet-vendor-test.mjs`), never loaded from a CDN.
 * See `site/README.md`'s "Third-party requests" section and `site/privacy.html`. */

/* ⭐⭐ THE PALETTE IS `site/app.css`'s, DOWN TO THE HEX — and after T-502 the card colour is too.
 *
 * The owner's report was *"the dive computer page here looks nothing like the rest of the website"*,
 * and it was right. `/` had just become a landing page styled by `app.css`; these four prose pages
 * were still wearing the design that predated it. Same brand, two visibly different websites, and the
 * seam fell exactly where a diver crosses from the pitch to the support matrix.
 *
 * ⚠️ `--card` USED TO BE `rgba(255, 255, 255, 0.03)` — a barely-lighter wash. `app.css`'s
 * `--card-bg` is `rgba(17, 24, 39, 0.75)`, a dark translucent navy, which is what every card on `/`
 * is filled with. They were never going to look like the same site while the cards disagreed.
 *
 * ⚠️⚠️ THE TWO ALIASES BELOW EXIST SO THE CHROME RULES CAN BE COPIED VERBATIM. `app.css` names these
 * `--card-bg` and `--text-muted`; this file has always called them `--card` and `--muted`. A rule
 * pasted across with the wrong variable name does not fail loudly — it resolves to nothing and the
 * property silently falls back to its initial value, which reads as a layout bug in a place nobody
 * changed. Declaring both spellings makes a copied rule work as written in either file. */
:root {
  --bg: #0B0F19;
  --card: rgba(17, 24, 39, 0.75);
  --border: rgba(255, 255, 255, 0.10);
  --text: #F3F4F6;
  --muted: #9CA3AF;
  --primary: #00F2FE;
  --accent: #4FACFE;
  --danger: #EF4444;
  /* Aliases, not new colours. See the note above. */
  --card-bg: var(--card);
  --text-muted: var(--muted);
}

* { box-sizing: border-box; }

/* ⚠️ THE SIDE GUTTER MOVED OFF `body` AND ONTO `main` (T-502), AND THAT IS AN ALIGNMENT FIX, NOT TIDYING.
 * `.landing-bar` carries its own `max(16px, env(safe-area-inset-*))` padding — it must, because it is the
 * same rule `app.css` uses, where `.landing` beside it carries the identical padding so the bar's first
 * item and the page's first heading share an x. Here `body` supplied 1.25rem to the bar AND to `main`, so
 * the bar ended up inset 20 + 16 = 36px while the content sat at 20px: measured at a 1200px viewport, the
 * wordmark started at x=65 and "Dive computer support" at x=51. Fourteen pixels is small enough to read as
 * sloppiness rather than as a bug, which is exactly why it needed measuring instead of eyeballing.
 *
 * ⚠️ It also gives a narrow phone 8px MORE room than before (16px a side, not 20), so it cannot have
 * created an overflow — the direction matters, because the wordmark's `clamp()` below was sized against
 * the old budget at 320px. Re-measured at six widths after this change all the same. */
body {
  margin: 0;
  padding: 0 0 4rem;
  background:
    radial-gradient(900px 500px at 15% -5%, rgba(0, 242, 254, 0.10), transparent 60%),
    radial-gradient(700px 500px at 85% 10%, rgba(79, 172, 254, 0.08), transparent 60%),
    var(--bg);
  /* System stack, so there is no font to download and text renders instantly. */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--text);
  line-height: 1.6;
  /* Prevents iOS inflating text in landscape, which would break the measured layout. */
  -webkit-text-size-adjust: 100%;
}

/* ⭐⭐ 1100px, UP FROM 46rem (T-502) — AND THE READING MEASURE IS NOW ON THE TEXT, NOT THE CONTAINER.
 *
 * This is the change that makes these pages line up with `/`. `app.css`'s `.landing` container is
 * `max-width: 1100px`, so on `/` the top bar, the headings and the cards all begin at the same x. At
 * 46rem (736px) these pages began 232px further in at a 1200px viewport while the bar above them tried
 * to span the page — two different grids stacked on one screen, which is most of what "looks nothing
 * like the rest of the website" actually was.
 *
 * ⚠️ A 1100px-wide paragraph is unreadable, and widening the container is only half the change. The
 * landing page solves this with `.landing-section > p { max-width: 46rem }` — a wide container holding
 * a narrow column of text — and the rule below is that same trick. So the measure a diver reads is
 * unchanged; only the frame around it moved.
 *
 * ⚠️ The cap is deliberately NOT on `.card p` or on a table cell. Cards on `/` sit two-up in a grid and
 * are ~530px wide, so their text needs no cap; the dive-computer matrix WANTS the full 1100 (it is the
 * one table on the site and it was the cramped page in the report). Capping every descendant would
 * have re-created the narrow layout inside a wide box. */
/* The padding is `.landing`'s, character for character (`app.css`), so the bar above and the content below
   agree on where the page starts. See the note on `body` for what it was before and how far out it was. */
main {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0 max(16px, env(safe-area-inset-right)) 0 max(16px, env(safe-area-inset-left));
}

/* The reading measure. `46rem` is the same number the landing page uses, so a paragraph here and a
   paragraph on `/` are the same width even though their containers are not. */
main > p, main > ul, main > ol { max-width: 46rem; }

/* The lockup: mark left, wordmark and tagline stacked beside it — the same arrangement the email
 * pages use, and for the reason recorded there. Stacked vertically the logo read as a separate
 * object above unrelated text; side by side the three parts read as one signature. */
/* ⭐ `28px 0 8px` is `app.css`'s `.landing-hero` padding, copied (T-502). It was `2.5rem 0 2rem`, and the
   2rem bottom does NOT collapse with the following `h2`'s 2.5rem top margin — padding never collapses with
   a margin — so every one of these pages opened with 72px of empty space under the lockup where `/` opens
   with 28. On a page whose first screen is mostly logo that reads as a different template. */
header.brand {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 28px 0 8px;
}
/* **128px, up from 88.** The owner asked for it larger, and at 88 the mark read as a small badge
 * beside a 2rem wordmark rather than as half of a lockup; at 128 it balances the two-line text block.
 * Compared at 88 / 104 / 112 / 128 side by side against the real wordmark before choosing — the
 * decision is about the ratio to the text, which is invisible when the mark is judged alone.
 *
 * The source is a 256px PNG for a 128px box, so it stays sharp on a Retina screen. `border-radius`
 * scales with it: 19/88 ≈ 0.216, the same proportion iOS uses for an app icon, so the web mark and the
 * home-screen icon read as the same shape. 128 × 0.216 ≈ 28.
 *
 * `flex: 0 0 auto` so the mark never shrinks — the wordmark gives way instead (see `min-width: 0`
 * below), because a squashed logo looks broken while a wrapped wordmark does not. */
header.brand img { width: 128px; height: 128px; border-radius: 28px; flex: 0 0 auto; }
/* `min-width: 0` defeats flex's `auto` floor, so the wordmark shrinks instead of forcing the row
 * wider than the viewport. Same trap as the dive form's overlapping fields. */
header.brand > div { min-width: 0; }
header.brand h1 {
  margin: 0 0 0.15rem;
  /* **Fluid, not a flat 2rem, and this fixes a measured overflow.**
   *
   * "SafetyStop" is one unbreakable word. At 2rem it renders **331px wide**, so on a 320px viewport
   * (iPhone SE, and any phone in a split view) it ran off the right edge — measured inside a real
   * 320px iframe, because headless Chrome clamps its own viewport to 500px and reports 500 no matter
   * what `--window-size` says. That clamp is why this went unnoticed: every earlier check was
   * effectively at 500px.
   *
   * `min-width: 0` on the text column does **not** help here, and the comment at the foot of this file
   * used to claim it did. It lets the flex *box* shrink; it cannot break a single word, so the word
   * overflows the box it was allowed to shrink.
   *
   * `clamp()` rather than an `@media` breakpoint: the failure is continuous in viewport width, so a
   * breakpoint would just move the edge it happens at. 1.5rem is the floor (still clearly a heading),
   * 8vw tracks the viewport, 2rem is the cap so nothing changes on a desktop. At 320px this resolves
   * to 25.6px, which measures 265px — inside the 320 − 40 (body padding) − 128 (mark) − 16 (gap)
   * budget with room for the gradient's descenders. */
  font-size: clamp(1.5rem, 8vw, 2rem);
  line-height: 1.1;
  background: linear-gradient(90deg, var(--primary), var(--accent));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
header.brand p { margin: 0; color: var(--muted); font-style: italic; font-size: 0.95rem; }

/* ⭐ The heading scale is `app.css`'s `.landing-section > h2` (T-502): 1.35rem / 800 / -0.01em here, and
   1.6rem at the 860px breakpoint at the foot of this file. It read as a different site partly because
   these headings were a lighter weight and never grew on a desktop while the landing page's did. */
h2 { font-size: 1.35rem; font-weight: 800; letter-spacing: -0.01em; margin: 2.5rem 0 0.75rem; }
h3 { font-size: 1.05rem; margin: 1.75rem 0 0.5rem; }
p, li { color: #D1D5DB; }
a { color: var(--accent); }
strong { color: var(--text); }

/* ⭐ Matched to `app.css`'s `.landing-card` (T-502): the same 20px radius and the same 20px padding, and
   `--card` now resolves to the same navy the landing cards use. It was 14px / 1.25rem 1.5rem, which is
   a visibly tighter and rounder-cornered object than the one on `/`. */
.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 20px;
  padding: 20px;
  margin: 1.25rem 0;
}
/* The warn card keeps its red tint — it is the one card on this site whose colour carries meaning, and
   the landing page has no equivalent to copy from. Radius and padding still follow `.card` above. */
.card.warn { border-color: rgba(239, 68, 68, 0.35); background: rgba(239, 68, 68, 0.07); }
.card h3 { color: var(--primary); font-weight: 700; }
.card > h3:first-child { margin-top: 0; }

.lead { font-size: 1.1rem; color: var(--text); }
.meta { color: var(--muted); font-size: 0.85rem; }

nav.links { margin: 2rem 0; display: flex; flex-wrap: wrap; gap: 1.25rem; }

/* ⭐⭐ THE TOP BAR — THE SAME OBJECT AS THE ONE ON `/`, NOT A SECOND NAV THAT RESEMBLES IT.
 *
 * T-501 gave these pages a nav for the first time (they had been live, byte-identical to the repo, and
 * reachable only from three words in the app's login footer). T-502 is the correction: that nav was a
 * row of plain text links, while `/` had a gradient wordmark, pill-shaped items, a filled call to
 * action and a rule under the whole thing. The owner crossed from one to the other and said the pages
 * *"look nothing like the rest of the website"*.
 *
 * ⚠️⚠️ THESE RULES ARE A VERBATIM COPY OF THE `.landing-bar` BLOCK IN `site/app.css`, AND THE MARKUP
 * IN ALL FOUR PAGES IS A VERBATIM COPY OF THE `<header class="landing-bar">` IN `site/index.html`.
 * Same class names on purpose. `index.html` loads ONLY `/app.css` and these pages load ONLY
 * `/_shared.css` — neither sheet can reach the other's pages, so one definition cannot serve both and
 * the duplication is forced by the two-stylesheet split, not chosen.
 *
 * ⚠️ Which means it CAN drift, and a drift here is invisible until somebody loads two pages in a row.
 * If you change a value below, change it in `app.css` too, and vice versa. The selectors are named
 * identically so that a grep for `.landing-menu` finds both homes at once.
 *
 * ⭐⭐ AND THAT SENTENCE IS NOT WHAT ENFORCES IT — `tools/check-landing-chrome.mjs` is. It compares the
 * seven rule bodies in the two sheets byte-for-byte and checks the markup on all five pages. Run it after
 * touching either sheet. A comment asking the next person to remember is the failure mode this repo has
 * already paid for more than once (T-407: the thing policing a rule was a comment).
 *
 * ⚠️ `<a>` elements only, never a `<button>`: `tools/desktop-layout-check.mjs` visits all four of these
 * pages anonymously and fails a button wider than 320px.
 *
 * The current page stays in the list, marked `aria-current="page"`, rather than being removed. Dropping
 * the item would shift every other one between pages, which is how a nav starts reading as five
 * different navs. */
.landing-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  max-width: 1100px;
  margin: 0 auto;
  padding: max(14px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right)) 14px
           max(16px, env(safe-area-inset-left));
  border-bottom: 1px solid var(--border);
}

.landing-mark {
  font-size: 1.15rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-decoration: none;
  background: linear-gradient(135deg, var(--primary), var(--accent));
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* On a narrow phone the menu is a scrolling row rather than a wrapped block: the items stacked two deep
   would push the page's own heading off the screen. Measured on `/` at six widths from 320px up — see
   the T-501 note in `site/app.css`. */
.landing-menu {
  display: flex;
  align-items: center;
  gap: 6px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* Scrollbar gutter would clip the focus ring on the last link. */
  padding-bottom: 2px;
}

.landing-menu a {
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.82rem;
  padding: 7px 10px;
  border-radius: 999px;
  white-space: nowrap;
}

.landing-menu a:hover { color: var(--text); background: rgba(255, 255, 255, 0.06); }

.landing-menu a.landing-menu-cta {
  color: var(--bg);
  font-weight: 700;
  background: linear-gradient(135deg, var(--primary), var(--accent));
}

.landing-menu a.landing-menu-cta:hover { color: var(--bg); }

/* ⚠️ ONLY ON THESE PAGES, and it has no counterpart in `app.css`. On `/` the bar's items are fragment
   links down one long page, so none of them is ever "the current page". Here four of them are real
   URLs and one of them is the page you are on, so it is marked and shown as the one solid item. */
.landing-menu a[aria-current="page"] {
  color: var(--text);
  font-weight: 700;
  background: rgba(255, 255, 255, 0.08);
}

/* ⭐ THE APP STORE BADGE. Apple's own artwork, served from our origin like everything else here.
 * `width`/`height` keep the SVG's intrinsic 119.66×40 ratio so the row does not reflow, and the link is
 * the badge alone — Apple's guidelines treat the badge as one object, so it never sits inside a
 * sentence. */
.appstore { display: inline-block; line-height: 0; }
.appstore img { width: 140px; height: 47px; }

/* ⭐ Centred, to match `app.css`'s `.landing-footer` (T-502). It was left-aligned, which is a small
   difference on its own and a compounding one at the bottom of a page whose top already disagreed. */
footer {
  margin-top: 3.5rem;
  padding-top: 1.5rem;
  padding-bottom: calc(16px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: 0.85rem;
  text-align: center;
}

/* The bottom link list, which predates the top bar and is kept: it is the only place `/404`, the
   App Store link and the rest sit together. Centred with the footer above rather than flush left. */
nav.links { justify-content: center; }

/* --- Wide screens -----------------------------------------------------------
   ⚠️ 860px is the SAME breakpoint as `app.css`'s, on purpose. The two sheets change gear at the same
   viewport width, so a diver resizing a window never sees one page reflow while the other has not. */
@media (min-width: 860px) {
  h2 { font-size: 1.6rem; }
}

/* Deliberately no @media query. The layout is a single centred column that reflows on its own, and
 * the one thing that could overflow — the brand row — is handled by `min-width: 0` plus the `clamp()`
 * on the wordmark above.
 *
 * **Corrected 2026-08-08:** this comment used to claim `min-width: 0` alone handled the brand row. It
 * did not. "SafetyStop" is one unbreakable word and measured 331px against a 320px viewport, so the
 * page really did overflow on a small phone — a pre-existing bug, found while making the logo larger
 * and not caused by it. `min-width: 0` shrinks the box; only a smaller font shrinks the word.
 *
 * A media query was previously added to the email pages for a *phantom* overflow and then removed when
 * that one turned out to be a cropped screenshot. Both stories are worth keeping side by side: one
 * overflow was imaginary and one was real, and the way to tell them apart is to ask the DOM for
 * `scrollWidth` inside a genuinely narrow viewport rather than to look at a picture. */
