/* ══════════════════════════════════════════════════════════════════════════
   Public pages shell — /, /about and /privacy.

   One header, one footer, one column for the three pages a rider can reach
   before signing in. Loaded AFTER routesmith.css; tokens only, no new
   colours. Requires `class="public-page"` on <body> plus a `data-page`
   value of "login" | "about" | "privacy" (the page's own nav link is
   suppressed from that attribute).

   Direction: "card-first" — the sign-in card's language on every public
   page. Warm surface ground, one white card, full-bleed on phones.

   WHAT these pages may SAY is docs/public-copy-policy.md. This file styles
   chrome and never changes a claim.
   ════════════════════════════════════════════════════════════════════════ */

/* ── shared: header, footer, column ────────────────────────────────────── */

.public-wrap { width: 100%; margin: 0 auto; }

.public-head {
  display: flex; align-items: center; gap: 16px;
  flex-wrap: wrap;
}
.public-brand {
  display: flex; align-items: center; gap: 12px;
  margin-right: auto; text-decoration: none; color: var(--color-text);
}
.public-brand .glyph { flex: none; display: block; }
.public-brand .glyph svg { display: block; width: 100%; height: 100%; }
.public-brand-text {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  line-height: 1.05;
}
.public-tagline {
  display: block; font-family: var(--font-body); font-weight: 400;
  font-size: 0.625rem; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--color-accent); margin-top: 4px;
}
.public-nav { display: flex; align-items: center; gap: 18px; flex: none; }
.public-nav a { font-size: 0.8125rem; color: var(--color-neutral-700); }
/* Hover rules are gated on `@media (hover: hover)` here for the same reason
   they are in routesmith.css: a touch screen has no pointer to move away, so
   it applies a hover state on tap and never takes it off. The convention and
   its full reasoning live in that file. */
@media (hover: hover) { .public-nav a:hover { color: var(--color-accent); text-decoration: underline; } }
.public-nav a.btn { font-size: 0.875rem; color: var(--color-text); }
.public-nav a.btn-primary { color: var(--color-bg); }
/* One action per page: the page you are on never links to itself. Done in
   CSS off `data-page` so the partial carries no per-page branching. */
[data-page="login"] .public-nav a[data-nav="signin"],
[data-page="about"] .public-nav a[data-nav="about"],
[data-page="privacy"] .public-nav a[data-nav="privacy"] { display: none; }

.public-foot {
  display: flex; flex-direction: column; gap: 14px;
  margin-top: 34px; padding-top: 18px;
  border-top: 1px solid var(--color-hairline);
}
.public-foot-links { display: flex; flex-wrap: wrap; gap: 16px; }
.public-foot-links a { font-size: 0.8125rem; color: var(--color-neutral-700); }
@media (hover: hover) { .public-foot-links a:hover { color: var(--color-accent); text-decoration: underline; } }
[data-page="login"] .public-foot-links a[data-nav="signin"],
[data-page="about"] .public-foot-links a[data-nav="about"],
[data-page="privacy"] .public-foot-links a[data-nav="privacy"],
[data-page="terms"] .public-foot-links a[data-nav="terms"],
[data-page="contact"] .public-foot-links a[data-nav="contact"] { display: none; }

/* A fact shipped REVERSED so no harvester's regex matches it, painted the
   right way round for a person with no script (templates/_macros.html's
   obf_text); the script un-reverses the DOM text and marks it done. */
/* isolate-override, not bidi-override: each word is its own bidi run, so
   the LTR paragraph keeps the words in order and only the letters inside
   each are painted right-to-left (a plain override lets adjacent rtl spans
   merge into one run and the WORDS come out reversed). */
.obf-w { unicode-bidi: isolate-override; direction: rtl; }
/* Required API attribution (static/vendor/README.md) — the official mark,
   unmodified. Height only: never recoloured, never distorted.
   `align-self` because the footer is a stretch column: without it the img
   BOX spans the full width and the SVG centres inside it, which read as a
   centred mark in an otherwise left-aligned footer. */
.public-foot-strava { height: 22px; width: auto; align-self: flex-start; }
.public-foot-meta {
  font-size: 0.75rem; line-height: 1.6; color: var(--color-neutral-600);
}

/* ── document body — /about and /privacy ───────────────────────────────── */

.public-wrap h1 { font-size: 2.125rem; line-height: 1.06; margin: 26px 0 10px; }
.public-doc h2 {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 1.1875rem; line-height: 1.2; margin: 34px 0 10px;
  padding-top: 22px; border-top: 1px solid var(--color-hairline);
}
.public-doc h2:first-of-type { border-top: none; padding-top: 0; }
.public-doc h3 {
  font-family: var(--font-heading); font-weight: var(--font-heading-weight);
  font-size: 0.875rem; margin: 20px 0 8px;
}
.public-doc p, .public-doc li {
  font-size: 0.875rem; line-height: 1.65; color: var(--color-neutral-800);
  /* Body copy stops short of the 800px column: at this size it ran to
     ~110 characters a line on a desktop. Headings, tables and the hero
     keep the full width. */
  max-width: 70ch;
}
.public-doc p { margin-bottom: 12px; }
.public-doc ul { margin: 0 0 14px; padding-left: 20px; }
.public-doc li { margin-bottom: 7px; }
.public-doc strong { font-weight: 600; color: var(--color-text); }
.public-doc a { color: var(--color-accent); }
/* `.public-doc a` (0,1,1) outguns `.btn-primary` (0,1,0), which would paint
   the label accent-on-accent — invisible. Win it back explicitly. */
.public-doc a.btn-primary { color: var(--color-bg); }
.public-doc code {
  font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
  font-size: 0.7812rem; color: var(--color-accent);
  background: var(--color-accent-100); border-radius: 4px; padding: 1px 5px;
}
.doc-updated { font-size: 0.75rem; color: var(--color-neutral-600); margin-bottom: 26px; }
.hero { padding: 4px 0 8px; }
.hero .lede {
  font-size: 1rem; line-height: 1.55; color: var(--color-neutral-700);
  margin: 0 0 22px; max-width: 34em;
}
.hero .cta { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.hero .cta .gate { font-size: 0.75rem; line-height: 1.5; color: var(--color-neutral-600); }

/* ── the rotating punchline — / and /about (_public.html's `punchline`) ────
   Every line occupies the SAME grid cell, so the box is as tall as its
   tallest line and a rotation can never reflow what sits below it — on the
   login card that is the Strava button, which must not move under a
   hovering finger. Hidden lines are `visibility: hidden` (out of the
   accessibility tree, not just invisible); `.on` is the one showing.
   The visibility flip is DELAYED by the fade time on the way out, so the
   outgoing line stays paintable while its opacity runs — and undelayed on
   the way in. */
.public-punch {
  display: grid; margin: -4px 0 16px;
  font-family: var(--font-heading); font-weight: 600;
  font-size: 0.9375rem; line-height: 1.4; color: var(--color-accent);
}
.public-punch .pp-line {
  grid-area: 1 / 1; opacity: 0; visibility: hidden;
  transition: opacity 0.7s ease, visibility 0s linear 0.7s;
}
/* The lines are the app quoting itself (pages.py's PUBLIC_TAGLINES are
   verbatim weather-story sentences), and the quote marks say so. They are
   pseudo-elements ON PURPOSE: the served HTML keeps the bare verbatim
   string — which is what the policy test greps for and what a crawler
   quotes — and screen readers don't announce the decoration. */
.public-punch .pp-line::before { content: "\201C"; }
.public-punch .pp-line::after { content: "\201D"; }
.public-punch .pp-line.on {
  opacity: 1; visibility: visible;
  transition: opacity 0.7s ease;
}
@media (prefers-reduced-motion: reduce) {
  /* The script never rotates here (it checks the same query), so this is
     the belt to its braces: even a stray class flip would be a hard swap,
     never motion. */
  .public-punch .pp-line { transition: none; }
}
.callout {
  background: var(--color-tint); color: var(--color-on-tint);
  border-radius: var(--radius-md); padding: 14px 16px;
  font-size: 0.8438rem; line-height: 1.6; margin-bottom: 26px;
}
.callout strong { color: var(--color-on-tint); }
/* Wide tables scroll inside their own box — the page body never does. */
.doc-table { overflow-x: auto; margin: 0 0 16px; }
.doc-table table { border-collapse: collapse; width: 100%; min-width: 480px; }
.doc-table th, .doc-table td {
  text-align: left; vertical-align: top; padding: 9px 10px 9px 0;
  font-size: 0.8438rem; line-height: 1.55; color: var(--color-neutral-800);
  border-bottom: 1px solid var(--color-hairline);
}
.doc-table th {
  font-size: 0.6875rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--color-neutral-600); font-weight: var(--font-ui-weight);
}
.doc-table td:first-child { color: var(--color-text); font-weight: 600; padding-right: 18px; }
/* The contact address. Spelled out until someone reaches for it — the
   mailto is built on interaction, so it isn't in the DOM to be scraped
   (templates/_macros.html) — and readable as-is with no script at all. */
.obf-mail { color: var(--color-accent); cursor: pointer; }

/* ── the sign-in card — / ──────────────────────────────────────────────────
   AFTER the document body on purpose: `.login-card h1` and `.public-wrap h1`
   are both (0,1,1), so source order is the only thing that decides which
   one sizes the card's heading. The card's is smaller than the documents'
   — the same distinction the phone block below makes explicitly. */

.login-card { width: 100%; max-width: 460px; }
.login-card h1 { font-size: 1.875rem; line-height: 1.08; margin-bottom: 14px; }
/* The secret long-press target (static/pwa-install.js). Without this iOS
   raises the selection magnifier over the press and the gesture never
   completes; a page heading is not something anyone selects on purpose. */
#login-title { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.login-card p.sub {
  font-size: 0.9375rem; line-height: 1.55; color: var(--color-neutral-700);
  margin-bottom: 20px;
}
.login-card .points { list-style: none; margin: 0 0 24px; padding: 0; }
.login-card .points li {
  font-size: 0.8125rem; line-height: 1.55; color: var(--color-neutral-700);
  padding: 0 0 0 16px; margin-bottom: 12px; position: relative;
}
.login-card .points li::before {
  content: ""; position: absolute; left: 0; top: 8px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--color-accent);
}
.login-card .points strong { color: var(--color-text); font-weight: 600; }
/* Official "Connect with Strava" button — an image asset shown at its
   native aspect ratio (Strava prohibits recolouring or distorting it). */
.strava-btn { display: inline-flex; line-height: 0; text-decoration: none; flex: none; }
.strava-btn img { display: block; height: 48px; width: auto; }
@media (hover: hover) { .strava-btn:hover { opacity: 0.92; } }
.club-note {
  font-size: 0.75rem; line-height: 1.55; color: var(--color-neutral-600);
  margin-top: 16px;
}
/* Everything under the pitch, in one column so the ORDER is a class rather
   than a DOM move (static/pwa-install.js). Server-rendered order — the one
   a rider with no JavaScript gets — is sign-in first; on a phone the script
   pulls the Install row (or Open in Safari, where installing isn't possible
   yet) to the front with `.lead`. */
.login-actions { display: flex; flex-direction: column; }
.login-actions .error { order: -2; }
.login-actions .login-signin { order: 1; }
.login-actions .login-wait { order: 2; }
.login-actions .club-note { order: 3; }
.login-actions .login-install { order: 4; }
/* Deliberately class-based and declared LAST: at equal specificity source
   order decides, so the promoted row jumps the queue. Written against the
   rows' ids it would silently lose to them (an id outranks two classes) and
   `.lead` would set a colour scheme while changing nothing about position —
   which is exactly how it failed the first time. */
.login-actions .lead { order: 0; }
/* The pre-paint half of install-first (templates/login.html's inline head
   script): the sign-in is off screen from the first frame on a phone, until
   static/pwa-install.js drops the class and takes over with `hidden`. */
html.install-first #strava-login { display: none; }
html.install-first #login-email { display: none; }
html.install-first #login-passkey { display: none; }

/* The three doors as one set (templates/login.html): a column of boxes
   the size of the Strava mark, the app's two doors first and the official
   mark last. The passkey and email doors take the MARK's box, 237 × 48 with
   its 6px corner, rather than the app's 10px control radius, so the three
   read as equals; glyph and label sit from the left edge like a row. The
   error line under them is the passkey door's. */
.login-signin { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.login-signin .error { align-self: stretch; margin: 2px 0 0; }
.login-door {
  display: inline-flex; align-items: center; gap: 10px;
  width: 237px; height: 48px; box-sizing: border-box; padding: 0 14px;
  cursor: pointer; text-decoration: none;
  font-family: var(--font-heading); font-weight: var(--font-ui-weight);
  font-size: 0.875rem; line-height: 1.2; color: var(--color-text);
  background: transparent; border: 1px solid var(--color-divider); border-radius: 6px;
}
.login-door .ld-glyph {
  display: block; flex: none; width: 20px; height: 20px;
  fill: none; stroke: var(--color-accent); stroke-width: 1.75;
  stroke-linecap: round; stroke-linejoin: round;
}
@media (hover: hover) { .login-door:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); } }
.login-door:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
.login-door[hidden] { display: none; }

/* The demoted sign-in. On a phone it is HIDDEN outright while something
   else leads — the deployment wants riders in the installed app. Two ways
   through survive, both invisible: `?signin=1` (exact match, which is what
   carries the accessibility duty — no gesture is asked of a keyboard or a
   screen reader) and a long press on the heading for anyone who knows. The
   install coach offers no way round the install (templates/_install.html);
   its "Sign in without installing" link was removed at the user's
   direction. */
.strava-btn[hidden] { display: none; }

/* Install offer — quiet and BELOW the sign-in button off a phone, where a
   browser tab is a fine way to use the app and there is no second cookie
   jar to strand a session in. Revealed by static/pwa-install.js only when
   this browser can actually install. */
.login-install {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px 12px;
  margin-top: 18px; padding: 12px 14px;
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  background: var(--color-surface);
}
.login-install[hidden] { display: none; }
.login-install .li-glyph {
  width: 30px; height: 30px; flex: none; border: 2px solid var(--color-accent);
  border-radius: var(--radius-md); color: var(--color-accent);
  display: flex; align-items: center; justify-content: center;
}
/* The button drops to its own line rather than squeezing the sentence into
   a four-word column — on a phone the card is the full width of the screen
   and this row is what has to cope with it. */
.login-install .li-text { min-width: 0; flex: 1 1 200px; }
.login-install .li-name { font-family: var(--font-heading); font-weight: 600; font-size: 0.8125rem; line-height: 1.25; }
.login-install .li-desc { font-size: 0.75rem; color: var(--color-neutral-600); line-height: 1.4; margin-top: 2px; }
/* `.btn` as well as `button`: the Safari row's action is an anchor, because
   an in-app browser is likelier to hand a real link navigation to the system
   than a scripted one. */
.login-install button,
.login-install .btn { flex: none; margin-left: auto; }
/* The "the tap went nowhere" line — full width under the row it explains,
   revealed only after a tap that visibly did nothing. */
.login-install .li-fallback {
  flex: 1 1 100%; font-size: 0.75rem; line-height: 1.45;
  color: var(--color-neutral-600);
  padding-top: 10px; border-top: 1px solid var(--color-divider);
}
.login-install .li-fallback[hidden] { display: none; }
.login-install .li-fallback .lf-or { display: block; margin-top: 6px; }
/* "Already installed?" — the branch for a rider the button is not for, so it
   sits AFTER the action rather than inside the pitch, on its own line under a
   rule. Same full-width flex line as the fallback above. */
.login-install .li-note {
  flex: 1 1 100%; margin: 0; font-size: 0.75rem; line-height: 1.45;
  color: var(--color-neutral-600);
  padding-top: 10px; border-top: 1px solid var(--color-divider);
}
.login-install .li-note[hidden] { display: none; }
/* The lead row is the card's primary action, so it stops looking like a
   footnote: accent edge, tinted ground, and the button reads as the thing
   to press. Same markup, same row — only its job on the page changed. */
.login-install.lead {
  margin-top: 4px;
  border-color: var(--color-accent);
  background: var(--color-tint); color: var(--color-on-tint);
}
.login-install.lead .li-name { font-size: 0.9375rem; }
.login-install.lead .li-desc { color: inherit; opacity: 0.85; }
.login-install.lead .li-glyph { border-color: currentColor; color: inherit; }
.login-install.lead .btn {
  background: var(--color-accent); border-color: var(--color-accent);
  color: var(--color-bg);
}
.login-install.lead .li-note {
  color: inherit; opacity: 0.72;
  border-top-color: color-mix(in srgb, currentColor 22%, transparent);
}
/* The promoted row STACKS, and only the promoted one. Two failures came out
   of sharing the quiet row's geometry: `align-items: center` centred the
   30px glyph against a description that runs to several lines here, so it
   sat opposite the middle of the paragraph instead of the title it marks;
   and the button, too wide to share the line at 390px, wrapped — where
   `margin-left: auto` threw it into the right-hand corner over an empty
   half-line, which reads as a footnote rather than as the card's one action.
   `flex: 1 0 100%` makes the wrap deliberate instead: the button always
   takes its own line, full width, at a real touch target, and no width can
   put the orphan back. The quiet row keeps the inline button — off a phone
   it IS a footnote in an 800px card, and a full-width button would say
   otherwise. */
.login-install.lead { align-items: flex-start; }
.login-install.lead button,
.login-install.lead .btn { flex: 1 0 100%; margin-left: 0; min-height: 44px; }
/* The installed-app sign-in wait state (revealed by login.html's script once
   the rider has been handed to Strava). It has to carry a real instruction —
   iOS lands the OAuth callback in the browser and nothing can bring a
   home-screen app back to the front — so it sits directly under the button
   that caused it, tinted rather than quiet. */
.login-wait {
  margin-top: 16px; padding: 13px 15px;
  border-radius: var(--radius-md);
  background: var(--color-tint); color: var(--color-on-tint);
}
.login-wait[hidden] { display: none; }
.login-wait .lw-title {
  font-family: var(--font-heading); font-weight: 600; font-size: 0.875rem;
  line-height: 1.25;
}
.login-wait .lw-desc { font-size: 0.8125rem; line-height: 1.5; margin-top: 5px; }
.login-wait .lw-alt {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px;
  margin-top: 12px;
}
.login-wait .lw-alt[hidden] { display: none; }
/* Same reasoning as the install row: on a phone this button takes its own
   line rather than crushing the sentence beside it into a narrow column. */
.login-wait .lw-note { font-size: 0.75rem; line-height: 1.4; flex: 1 1 180px; min-width: 0; }
/* The signed-out error slot, and the missing-configuration branch the page
   falls back to when the deployment has no Strava credentials. */
.login-card .error {
  background: var(--color-tint); border-radius: var(--radius-md);
  color: var(--color-on-tint); font-size: 0.8125rem; line-height: 1.5;
  padding: 11px 14px; margin-bottom: 22px;
}
.login-card .setup {
  border: 1px solid var(--color-divider); border-radius: var(--radius-md);
  background: var(--color-surface); padding: 14px 16px;
  font-size: 0.8125rem; line-height: 1.8;
}
.login-card .setup code {
  font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
  font-size: 0.75rem; color: var(--color-accent);
}

/* ── the card ──────────────────────────────────────────────────────────── */

body.public-page {
  background: var(--color-surface);
  min-height: 100dvh;
  /* The card goes full-bleed on phones, so the footer would otherwise sit
     under the home indicator. Matches what /about and /privacy already do. */
  padding-bottom: env(safe-area-inset-bottom);
}
body.public-page .public-wrap {
  max-width: 800px; margin: 24px auto; padding: 0 0 4px;
  background: var(--color-card); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md); overflow: hidden;
}
body.public-page .public-head {
  padding: calc(24px + env(safe-area-inset-top)) 28px 24px; border-bottom: 1px solid var(--color-hairline);
}
body.public-page .public-brand .glyph { width: 44px; height: 44px; }
body.public-page .public-brand-text { font-size: 1.3125rem; }
body.public-page .public-tagline { margin-top: 5px; }
body.public-page .public-body { padding: 4px 28px 8px; }
body.public-page .public-foot { margin: 22px 28px 24px; }
body.public-page .login-card { max-width: none; }
body.public-page .public-wrap h1 { margin-top: 22px; }

/* ── mobile ────────────────────────────────────────────────────────────── */

@media (max-width: 560px) {
  /* The nav drops to its own line under the brand, but keeps the desktop
     alignment: ONE group, at the right. With the current page's own link
     suppressed, /about and /privacy are down to two items, and an auto
     margin on the button threw those two to opposite ends of an empty row. */
  .public-nav { gap: 14px; width: 100%; justify-content: flex-end; }
  .public-wrap h1 { font-size: 1.6875rem; }
  .login-card h1 { font-size: 1.625rem; }
  .hero .lede { font-size: 0.9375rem; }
  body.public-page .public-wrap { margin: 0; border-radius: 0; box-shadow: none; }
  /* The top inset matters here and nowhere else on these pages: a signed-out
     INSTALLED app renders the login card in the standalone window, where
     `viewport-fit=cover` puts this header under the status bar. */
  body.public-page .public-head { padding: calc(18px + env(safe-area-inset-top)) 18px 18px; }
  body.public-page .public-body { padding: 0 18px; }
  body.public-page .public-foot { margin: 22px 18px 28px; }
  /* Wide tables stop being a horizontal scroller nobody notices on a phone.
     Lifting the desktop min-width floor is the half that does that; the
     negative margin alone only bought the table 4px and left all four of
     /privacy's tables 152px wider than the screen, scrolling behind an edge
     with nothing to announce it. The floor still applies above this width,
     where it stops a two-column table thinning out in a wide column, and
     .doc-table keeps its overflow-x for anything genuinely too wide to wrap. */
  .doc-table { margin-inline: -2px; }
  .doc-table table { min-width: 0; }
}

/* ── print ─────────────────────────────────────────────────────────────── */

/* The documents on paper: the notice and the terms are the pages someone
   prints. Chrome goes — the nav, the footer, the hero's sign-in row — and
   the card stops being a card, since a shadow and rounded corners are
   screen furniture and the column should take the page. The brand line
   stays so the printout says what it is. */
@media print {
  .public-nav, .public-foot, .hero .cta, .login-actions { display: none; }
  body.public-page { background: none; padding: 0; }
  body.public-page .public-wrap {
    max-width: none; margin: 0; border-radius: 0; box-shadow: none;
    background: none; overflow: visible;
  }
  body.public-page .public-head,
  body.public-page .public-body,
  body.public-page .public-foot { padding-left: 0; padding-right: 0; }
  .public-doc a { text-decoration: none; }
}

/* The loud path, offered only after the quiet one visibly failed. Reads as
   an action rather than a sentence, because pressing it is a decision: it
   is the navigation that leaves an error alert behind in this browser. */
.safari-direct {
  display: inline-flex; align-items: center; min-height: 34px;
  margin: 6px 0 0; padding: 7px 13px;
  border: 1px solid var(--color-accent); border-radius: var(--radius-md);
  background: none; cursor: pointer;
  font: inherit; font-size: 0.75rem; font-weight: 600; color: var(--color-accent);
}
@media (hover: hover) { .safari-direct:hover { background: var(--color-accent); color: var(--color-bg); } }

/* The sign-up form's honeypot (open-signup.md §5): off-screen, never
   display:none, since a script that skips hidden inputs is exactly the
   one this exists to catch. */
.hp-field { position: absolute; left: -10000px; top: auto; width: 1px; height: 1px; }
