/* ==========================================================================
   SwiftSums — shared design layer ("Faceted Focus")

   PHASE 2. This is the sitewide half of the system: tokens, reset, and the
   components every page carries — header, brand, locale switch, saved menu,
   save control, proof row, footer, category colours.

   It is deliberately NOT loaded by anything yet. The rollout order is:
     1. this file is authored (here)
     2. tip-calculator.html is rebuilt on it, and reviewed
     3. the remaining archetypes follow
     4. index.html migrates off landing.css onto this file plus a
        landing-only stylesheet, at which point the duplication disappears

   Until step 4 these rules are duplicated in landing.css. That duplication
   is intentional and temporary: splitting landing.css up front would mean
   cutting a stylesheet already serving the live homepage, where a dropped
   rule stays invisible until someone loads the page. Proving the shared
   layer on a new page first costs a copy and removes that risk.

   Pages load this INSTEAD OF tokens.css + app.css, never alongside them —
   the two systems define different palettes under the same names, and the
   element reset below is unscoped.
   ========================================================================== */

:root {
  --ink:    #173b5d;   /* primary text, dark surfaces, footer */
  --blue:   #287bc4;   /* interactive accent, result figures */
  --sea:    #dcefe9;   /* pale mint fill — hover states, answer panels */
  --mist:   #f6f4ee;   /* page ground (warm off-white) */
  --cream:  #fffdf7;   /* card surface, text on dark */
  --mint:   #9fe0c6;   /* the accent — second line of display headings */

  /* Darkened from the #657d8d used on the landing page, which fails WCAG AA
     as body text on every surface in this palette: 3.61:1 on --sea, 4.24:1
     on --cream, 3.92:1 on --mist, against the 4.5:1 needed below 18.66px.
     This value measures 5.1:1 on --sea and 6.0:1 on --cream. */
  --muted:  #4f6475;

  --line:   rgba(23, 59, 93, 0.16);

  --font-display: "Plus Jakarta Sans", "Plus Jakarta Sans Fallback", Manrope, system-ui, sans-serif;
  --font-ui:      Manrope, "Manrope Fallback", system-ui, sans-serif;
  --font-mono:    "DM Mono", "DM Mono Fallback", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Metric-matched fallbacks for the three Google-hosted faces above. Task 12
   diagnosis: a throttled trace (Fast 3G + 4x CPU, localhost + real network
   to fonts.googleapis.com) reproduced CLS ~1.0 on every load, with/without
   localStorage populated (rules out the saved-calculators panel). Chrome's
   own CLS-culprit attribution named "a font loaded over the network" for
   the secondary shift, and the primary shift's impacted elements were
   BODY / .ss-shell / #ss-nav / .ss-locale-wrap on desktop — i.e. the header
   changes height when the fallback (system-ui) swaps to the webfont, and
   that pushes the whole page down. This matches Cloudflare RUM's own
   element attribution (#ss-nav, html>body) on the live 30-day sample.

   These @font-face rules give each stack a same-named "Fallback" face
   local()-sourced from a near-universal system font, with size-adjust /
   ascent-override / descent-override / line-gap-override tuned so it
   occupies the same box as the real webfont — so the swap changes glyphs,
   not layout. Values computed from published metrics (Capsize /
   seek-oss/capsize entireMetricsCollection.json) against Arial (the sans
   stacks) and Courier New (the mono stack), the standard next/font-style
   technique — not hand-tuned or guessed.

   Review round 2 fixed two gaps in the first pass:

   1. --font-display's stack order was wrong: Manrope sat BETWEEN the
      primary webfont and its Fallback. Manrope is a second REAL webfont
      from the same Google Fonts request, not a metric-matched stand-in —
      nothing guarantees Plus Jakarta Sans's file arrives before Manrope's,
      so a Manrope-first race would paint headings in Manrope's own
      unmatched box and then shift AGAIN when Plus Jakarta Sans lands,
      reintroducing the double-shift this fix exists to prevent, on the
      largest text on the page (hero headings run 74-86px). Fixed: the
      Fallback now sits directly after its one real webfont, before any
      other font family, in every stack.

   2. src: local("Arial") / local("Courier New") alone silently no-ops on
      any device without that exact font installed — Android ships Roboto
      and has neither Arial nor Courier New, and plenty of Linux desktops
      don't either. On those platforms the @font-face fails to resolve, the
      browser falls through to the next stack entry, and NO override is
      applied at all: full reversion to the original bug, with no error and
      no console signal. Fixed by listing multiple local() candidates per
      face (below), keeping one fixed set of percentages across all of a
      face's candidates rather than recomputing per platform font:
      ascent-override, descent-override and line-gap-override are absolute
      against font-size, and fully replace the resolved font's native
      metrics — so they pin the LINE BOX identically no matter which
      local() candidate resolves. That part is unconditionally safe.

      size-adjust is the only Arial-derived value in the set. On Arial,
      Helvetica Neue and Liberation Sans — all close metric matches for
      Plus Jakarta Sans / Manrope — the residual glyph-width error it
      leaves is small. On Roboto (the Android path, and the population
      this fix is specifically for) it is larger, and a width error is NOT
      harmless just because the line box is pinned: for text that wraps,
      different glyph widths can change the NUMBER of lines, and line
      count changes block height — which is exactly the reflow this whole
      technique exists to prevent. The 74-86px hero heading is routinely
      multi-line, so a wrap-driven height shift can survive this fix on
      Android specifically. It is nonetheless strictly better than the
      prior single local("Arial"), which resolved to nothing on Android
      and applied no overrides at all — this fix reduces the problem, it
      does not eliminate it on every platform. Do not "fix" this by trying
      to recompute per-platform-font percentages for size-adjust; capsize
      metrics for Roboto's specific glyph widths would narrow the residual
      error but the wrap-sensitivity itself is inherent to size-adjust
      being a single width-scaling number, not a bug in the current values.

   Known limitation, not fixed here: these metrics are Capsize's 400-weight
   (regular) entries for each family, but --font-display is only ever
   rendered at weight 700 on this site (ss-calc.css:46, 93, 368), and the
   Google Fonts URL never even requests Plus Jakarta Sans 400. Recomputing
   from the 700-weight metrics gives size-adjust ~108.6% and
   ascent-override ~95.6%, against the 104.98% / 98.88% shipped below. This
   is the same simplification next/font makes (it also uses the 400-weight
   entry as a stand-in), so it's a known, deliberate approximation rather
   than an error — recorded here and in docs/SEO_BASELINE.md so a future
   pass can refine it on purpose instead of rediscovering the gap. */
@font-face {
  font-family: "Plus Jakarta Sans Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Roboto"), local("Liberation Sans");
  ascent-override: 98.88%;
  descent-override: 21.15%;
  line-gap-override: 0%;
  size-adjust: 104.98%;
}
@font-face {
  font-family: "Manrope Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Roboto"), local("Liberation Sans");
  ascent-override: 103.31%;
  descent-override: 29.07%;
  line-gap-override: 0%;
  size-adjust: 103.19%;
}
@font-face {
  font-family: "DM Mono Fallback";
  src: local("Courier New"), local("DejaVu Sans Mono"), local("Roboto Mono");
  ascent-override: 99.22%;
  descent-override: 31.01%;
  line-gap-override: 0%;
  size-adjust: 99.98%;
}

/* Document -------------------------------------------------------------- */
html { scroll-behavior: smooth; }
body {
  margin: 0;
  background: var(--mist);
  color: var(--ink);
  font-family: var(--font-ui);
}

/* Reset. Wrapped in :where() so every rule carries ZERO specificity. Written
   as plain descendant selectors, rules like `.ss-landing a { color: inherit }`
   (0,1,1) outrank single-class component rules (0,1,0) — which is exactly
   what rendered the brand and category links dark-on-dark during phase 1.
   Keep the :where(). */
:where(*), :where(*)::before, :where(*)::after { box-sizing: border-box; }
:where(a) { color: inherit; text-decoration: none; }
:where(button) { border: 0; background: none; font: inherit; color: inherit; cursor: pointer; }
:where(input, textarea, select) { font: inherit; }
:where(ul, ol) { margin: 0; padding: 0; list-style: none; }
:where(img, svg) { display: block; }
:where(h1, h2, h3, h4, p, figure) { margin: 0; }

/* Yellow on purpose: the one colour outside the palette, so it is
   unmistakable against both the navy and the cream surfaces. */
:focus-visible { outline: 3px solid rgba(242, 216, 108, .9); outline-offset: 3px; }

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

/* Layout ---------------------------------------------------------------- */
.ss-shell { width: min(1160px, calc(100% - 48px)); margin: 0 auto; }

/* A dark band. Calculator pages put the header and hero inside one of
   these, which is why no light header variant is needed. */
.ss-dark {
  position: relative;
  background: linear-gradient(112deg, #173b5d 0%, #123d5c 100%);
  color: var(--cream);
}

/* Brand ----------------------------------------------------------------- */
.ss-brand {
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--cream); font-size: 17px; font-weight: 800; letter-spacing: -.04em;
}
.ss-brand em { color: var(--mint); font-style: normal; }
/* The tile is tilted -7deg and the glyph counter-rotated +7deg so it sits
   level inside a tilted tile. That tilt is the mark. */
.ss-mark {
  width: 34px; height: 34px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 11px; transform: rotate(-7deg);
  background: linear-gradient(145deg, #3d9de0 0%, #287bc4 52%, #278b88 125%);
  box-shadow: inset 1px 1px 0 rgba(255,255,255,.28),
              inset -1px -2px 0 rgba(9,54,83,.22),
              7px 8px 14px rgba(5,31,54,.2);
}
.ss-mark svg { width: 23px; height: 23px; transform: rotate(7deg); }
.ss-icon {
  width: 1em; height: 1em; fill: none; stroke: currentColor;
  stroke-linecap: round; stroke-linejoin: round; stroke-width: 1.8;
}

/* Header ---------------------------------------------------------------- */
.ss-topbar {
  position: relative; z-index: 5; height: 82px;
  display: flex; align-items: center; justify-content: space-between;
  border-bottom: 1px solid rgba(255,253,247,.13);
}
.ss-nav {
  display: flex; align-items: center; gap: 30px;
  color: rgba(246,244,238,.7); font-size: 12px; font-weight: 700;
}
.ss-nav a { transition: color .2s ease; }
.ss-nav a:hover { color: #fff; }
/* Active-page marker. A class, not :first-child — a positional selector
   marks "Calculators" active on every page regardless of where you are. */
.ss-nav a.is-current { color: var(--cream); border-bottom: 1px solid var(--mint); padding-bottom: 4px; }
.ss-actions { display: flex; align-items: center; gap: 6px; }
.ss-menu-btn {
  display: none; padding: 10px; border-radius: 8px;
  background: transparent; color: #fff;
}

/* Locale switch. core.js owns .locale__* by name — do not rename. */
.ss-locale-wrap { position: relative; }
.locale__btn {
  display: flex; align-items: center; gap: 7px; padding: 10px 9px; line-height: 1;
  border: 1px solid transparent; border-radius: 9px; background: transparent;
  color: rgba(246,244,238,.78); font-size: 11px; font-weight: 700;
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.locale__btn:hover,
.locale__btn[aria-expanded="true"] {
  border-color: rgba(255,253,247,.2); background: rgba(255,253,247,.12); color: #fff;
}
/* core.js writes either an emoji into .locale__flag or, when it finds an
   <img> inside, swaps that image's src. New-system pages use the image. */
.locale__flag {
  width: 23px; height: 23px; flex: none; display: inline-block; position: relative;
  overflow: hidden; border: 1px solid rgba(255,255,255,.3); border-radius: 50%;
}
.locale__flag img {
  position: absolute; left: 50%; top: 50%; width: 100%; height: 100%;
  max-width: none; transform: translate(-50%, -50%); object-fit: cover;
}
.locale__menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 8; width: 186px; padding: 6px;
  border: 1px solid rgba(23,59,93,.14); border-radius: 12px; background: var(--cream);
  box-shadow: 0 15px 30px rgba(4,21,39,.28);
}
.locale__opt {
  width: 100%; display: flex; align-items: center; gap: 9px; padding: 10px;
  border-radius: 8px; background: transparent; color: var(--ink);
  text-align: left; font-size: 11px; font-weight: 700;
}
.locale__opt:hover,
.locale__opt[aria-checked="true"] { background: var(--sea); }

/* Saved calculators ------------------------------------------------------ */
.ss-saved-wrap { position: relative; }
.ss-saved-btn {
  display: inline-flex; align-items: center; gap: 7px; padding: 7px 10px;
  border: 1px solid transparent; border-radius: 9px; background: transparent;
  color: inherit; font-size: 12px; font-weight: 700; line-height: 1;
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.ss-saved-btn:hover, .ss-saved-btn[aria-expanded="true"] {
  border-color: rgba(255,253,247,.2); background: rgba(255,253,247,.12); color: #fff;
}
.ss-saved-btn .ss-icon { width: 14px; height: 14px; flex: none; }
.ss-saved-chev { width: 12px; height: 12px; }
.ss-saved-count {
  min-width: 17px; padding: 2px 5px; border-radius: 999px;
  background: var(--mint); color: #10405c;
  font: 10px/1.4 var(--font-mono); text-align: center;
}
.ss-saved-count[hidden] { display: none; }
.ss-saved-menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 9;
  width: 274px; max-height: 320px; overflow-y: auto; padding: 6px;
  border: 1px solid rgba(23,59,93,.14); border-radius: 12px; background: var(--cream);
  box-shadow: 0 15px 30px rgba(4,21,39,.28);
}
.ss-saved-menu[hidden] { display: none; }
.ss-saved-item {
  display: flex; align-items: center; gap: 10px; padding: 10px;
  border-radius: 8px; color: var(--ink); font-size: 12px; font-weight: 700;
}
.ss-saved-item:hover, .ss-saved-item:focus-visible { background: var(--sea); }
.ss-saved-item > span:first-child { flex: 1; min-width: 0; }
.ss-saved-cat {
  flex: none; color: #3f7580; font-family: var(--font-mono);
  font-size: 9px; letter-spacing: .08em; text-transform: uppercase;
}
.ss-saved-empty { padding: 12px 10px; color: var(--muted); font-size: 12px; line-height: 1.5; }

/* Save control ----------------------------------------------------------- */
/* core.js owns .save-btn by name — do not rename. It also owns the LABEL:
   wireSaveButton() writes "Save"/"Saved" into the button's first <span> and
   flips the svg fill. An earlier draft here swapped two labelled spans off
   aria-pressed instead; that loses to core.js, which overwrites the first of
   them. One span, core.js drives it. */
.ss-save {
  display: inline-flex; align-items: center; gap: 9px; padding: 13px 20px;
  border: 1px solid rgba(220,239,233,.68); border-radius: 999px;
  background: rgba(255,253,247,.15);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 8px 18px rgba(3,22,39,.14);
  color: var(--cream); font-size: 13px; font-weight: 700;
  transition: transform .2s ease, background .2s ease, border-color .2s ease;
}
.ss-save:hover { transform: translateY(-2px); border-color: rgba(159,224,198,.9); background: rgba(159,224,198,.18); }
/* Sized by element, NOT via .ss-icon. wireSaveButton() marks the saved state
   by writing fill="currentColor" onto the svg, and .ss-icon's `fill: none`
   is a CSS declaration — it beats the presentation attribute, so the
   bookmark would stay hollow no matter what core.js set. */
.ss-save svg { width: 16px; height: 16px; flex: none; stroke: currentColor; stroke-width: 1.3; }
.ss-save[aria-pressed="true"] { background: rgba(159,224,198,.18); border-color: rgba(159,224,198,.9); }

/* Proof row -------------------------------------------------------------- */
.ss-proof {
  display: flex; flex-wrap: wrap; gap: 24px;
  color: var(--cream); font: 14px var(--font-mono);
}
.ss-proof span { display: inline-flex; align-items: center; gap: 8px; }
.ss-proof .ss-icon { width: 14px; height: 14px; color: var(--mint); }

/* Category colours ------------------------------------------------------- */
.ss-c-maths        { background: linear-gradient(145deg,#5eaff0,#287bc4); }
.ss-c-money        { background: linear-gradient(145deg,#54c6aa,#218b78); }
.ss-c-construction { background: linear-gradient(145deg,#f3c96a,#cd8c27); }
.ss-c-health       { background: linear-gradient(145deg,#f27f91,#bf4560); }
.ss-c-science      { background: linear-gradient(145deg,#9586e2,#6151b6); }
.ss-c-time         { background: linear-gradient(145deg,#81c6cb,#277f9b); }

/* Footer ----------------------------------------------------------------- */
.ss-footer { padding: 72px 0 28px; background: #173b5d; color: var(--cream); }
/* Sits above the sitemap as its own centred band. The proof row's job is
   done on the landing page - by the time someone is deep in a calculator,
   trust is established and they have moved on to using the tool - so it
   rests here rather than competing with the work.

   min-height supplies the vertical space and centres the text inside it.
   :has() drops the footer's top padding, which would otherwise stack on top
   and push the row off centre: measured 72px above against 32px below.
   Browsers without :has() keep the padding and the row simply sits high. */
.ss-footer:has(.ss-proof) { padding-top: 0; }
.ss-footer .ss-proof {
  justify-content: center; align-content: center; text-align: center;
  min-height: 104px; margin-bottom: 40px;
  border-bottom: 1px solid rgba(255,253,247,.14);
}
.ss-footer-inner { display: grid; grid-template-columns: 1.35fr 1.6fr 1fr 1fr; gap: 42px; }
.ss-footer-brand { display: grid; align-content: start; gap: 18px; }
.ss-footer-tagline { max-width: 210px; margin: 0; color: #d8f0e4; font-size: 13px; line-height: 1.6; }
.ss-footer h3 {
  margin: 3px 0 18px; color: #a7e0ca;
  font: 10px var(--font-mono); letter-spacing: .14em; text-transform: uppercase;
}
.ss-footer ul { display: grid; gap: 11px; }
/* Scoped to the link lists, NOT `.ss-footer a`. The broader selector (0,1,1)
   also matches the footer's brand anchor and outranks .ss-brand (0,1,0),
   which rendered the footer wordmark at 12px against the header's 17px. */
.ss-footer ul a { color: var(--cream); font-size: 12px; opacity: .9; }
.ss-footer ul a:hover { color: #bce9d0; opacity: 1; }
.ss-footer-brand .ss-brand { justify-self: start; }
.ss-footer-bottom {
  display: flex; justify-content: space-between; gap: 20px; margin-top: 70px;
  color: #c7e7da; font: 10px/1.5 var(--font-mono);
}

/* Responsive ------------------------------------------------------------- */
@media (max-width: 820px) {
  .ss-shell { width: min(100% - 34px, 680px); }
  .ss-nav {
    display: none; position: absolute; top: 70px; left: 0; right: 0; z-index: 9;
    flex-direction: column; align-items: stretch; gap: 0; padding: 10px 15px;
    border: 1px solid var(--line); border-radius: 12px; background: var(--cream);
    box-shadow: 0 14px 30px rgba(23,59,93,.12);
  }
  .ss-nav.open { display: flex; }
  .ss-nav a { padding: 14px 4px; border-bottom: 1px solid var(--line); color: var(--ink); }
  .ss-nav a:last-child { border-bottom: 0; }
  .ss-nav a.is-current { color: var(--ink); border-bottom: 1px solid var(--line); padding-bottom: 14px; }
  .ss-menu-btn { display: block; }
  /* Inside the stacked panel the saved list drops in place rather than
     floating — a dropdown hanging off an already-open dropdown is a mess on
     a phone. */
  .ss-saved-wrap { width: 100%; }
  .ss-saved-btn { width: 100%; justify-content: flex-start; padding: 14px 4px; border: 0; border-radius: 0; color: var(--ink); }
  .ss-saved-btn:hover, .ss-saved-btn[aria-expanded="true"] { background: transparent; color: var(--ink); }
  .ss-saved-btn > span:first-of-type { flex: 1; text-align: left; }
  .ss-saved-menu {
    position: static; width: auto; margin: 0 0 8px;
    border-color: rgba(23,59,93,.1); box-shadow: none; background: rgba(23,59,93,.04);
  }
}
@media (max-width: 760px) {
  .ss-footer { padding: 58px 0 24px; }
  .ss-footer-inner { grid-template-columns: 1fr 1fr; gap: 35px 22px; }
  .ss-footer-brand { grid-column: 1 / -1; }
  .ss-footer-bottom { display: block; margin-top: 46px; }
}
@media (max-width: 560px) {
  .ss-shell { width: calc(100% - 32px); }
  .ss-topbar { height: 70px; }
  .locale__label { display: none; }
  /* At 14px the row exceeds a narrow phone, so it wraps rather than forcing
     a horizontal scrollbar on the page. */
  .ss-proof { gap: 8px 14px; font-size: 11px; max-width: calc(100% - 26px); }
  .ss-footer-inner { grid-template-columns: 1fr; gap: 30px; }
  .ss-footer-brand { grid-column: auto; }
  .ss-footer-bottom { font-size: 9px; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important; transition: none !important; scroll-behavior: auto !important;
  }
}
