/* Shared topnav + footer styles. One copy used by every page on graycloud.app.
 *
 * Consumed via:
 *   - styles.css     (app/dev pages: @import url("./nav.css"); at the top)
 *   - index.html     (marketing: <link rel="stylesheet" href="/nav.css">)
 *
 * CSS variables this file expects: --bar-bg, --bar-ink, --bar-ink-strong,
 * --ink-3. Each consumer defines them at :root.
 *
 * Navbar/footer geometry is owned here (--maxw-nav width, --nav-pad-x
 * gutter) so brand position is identical across every page, independent
 * of each page's body --maxw and --pad-x.
 *
 * The @import below is why every header surface gets the Inter @font-face +
 * --font-sans: nav.css is the one stylesheet every page loads (directly or
 * via styles.css's import). fonts.css is also linked directly by the pages
 * that don't load nav.css (blog, /home, /rain-ledger) — same URL, one fetch.
 */
@import url("./fonts.css");

:root {
  /* TOKENS:START nav generated by scripts/sync-tokens.py from design/tokens.json — do not edit between markers, edit the JSON and re-run */
  /* 1280px. Navbar/footer width is owned here so brand position is identical across every page, independent of each page's body --maxw. */
  --maxw-nav: 80rem;
  /* Header/footer gutter. Owned here, not by each page's --pad-x, so the brand sits at the same x on every route. */
  --nav-pad-x: clamp(16px, 4vw, 48px);
  --nav-active-underline: #3568A8;
  --nav-cta-bg: #ffffff;
  --nav-cta-ink: #111111;
  --nav-cta-bg-hover: #e8e8e8;
  --nav-pill-bg: #2c2c2e;
  --nav-pill-bg-hover: #3a3a3c;
  --nav-pill-ink: rgba(235, 235, 245, 0.6);
  --nav-pill-ink-faint: rgba(235, 235, 245, 0.35);
  --nav-pill-ink-strong: rgba(235, 235, 245, 0.9);
  --nav-pill-kbd-bg: rgba(255, 255, 255, 0.09);
  --nav-brand-tile-bg: #16181d;
  --nav-panel-bg: #ffffff;
  --nav-panel-border: rgba(23, 23, 23, 0.08);
  --nav-panel-ink-strong: #16181d;
  --nav-panel-ink-muted: #8a8f98;
  /* TOKENS:END nav */
}

/* ---------- Top nav ---------- */
/* Sticky on every page (the /weather board pioneered it; now site-wide).
   The bar bg is opaque, so content scrolls cleanly underneath. */
.topnav {
  /* Theme-following chrome the tokens block doesn't carry: the hairline used
     by every divider inside the bar (menu groups, mobile sheet rows) and the
     menu popover's layered shadow. Both re-map in .topnav--light below, so
     no rule inside the bar may hardcode a white/black divider. */
  --nav-hair: rgba(255, 255, 255, 0.08);
  --nav-menu-shadow: 0 2px 8px rgba(0, 0, 0, 0.22), 0 16px 44px rgba(0, 0, 0, 0.42);
  background: var(--bar-bg);
  color: var(--bar-ink);
  position: sticky; top: 0; z-index: 100;
  /* Pin typography so brand + links render identically across pages
     whose body sets different line-height / letter-spacing / features.
     -0.01em is the UserJot-match body tracking — Inter reads tighter and
     more finished with it; the wordmark and menu titles go further
     (-0.025em) via their own rules. */
  line-height: 1.45;
  letter-spacing: -0.01em;
  font-feature-settings: "tnum" 1;
  /* UserJot-match (2026-08-20): the bar has NO resting border. A dashed
     hairline fades in only once the page scrolls (nav.js toggles
     .topnav--scrolled at scrollY > 0). Color rides --nav-hair so it themes
     per bar variant. */
  border-bottom: 1px dashed transparent;
  transition: border-color 200ms ease-out;
}
.topnav--scrolled { border-bottom-color: var(--nav-hair); }
/* ---------- Light header variant ----------
   Single source of truth for the light-bar header on the weather surfaces
   (the /weather board + /records opt in via searchTopnav({ light: true })).
   It ONLY redefines the bar/pill tokens, so every child — brand, ⌘K pill,
   GPS icon, °F toggle, Menu — re-themes together and can never drift per page.
   Marketing/content pages keep the dark default; the landing runs its own
   data-theme system. (Palette mirrors the weather boards' --ink2/3/line.) */
/* The bar palette itself is shared with .site-footer--light below, declared
   once on both selectors so the header and footer can never drift apart. */
.topnav--light,
.site-footer--light {
  --bar-bg: #ffffff;
  --bar-ink: rgba(23, 23, 23, 0.78);
  --bar-ink-strong: #16181d;
}
.topnav--light {
  --nav-hair: rgba(23, 23, 23, 0.08);
  --nav-menu-shadow: 0 2px 8px rgba(0, 0, 0, 0.07), 0 16px 44px rgba(0, 0, 0, 0.16);
  --nav-pill-bg: #eef0f2;
  --nav-pill-bg-hover: #e6e8ea;
  --nav-pill-ink: #5a6068;
  --nav-pill-ink-faint: #8a8f98;
  --nav-pill-ink-strong: #16181d;
  --nav-pill-kbd-bg: rgba(23, 23, 23, 0.06);
  /* CTA pills (Get the app / Sign up) invert on the light bar: near-black
     fill, white ink — the dark bar's white-pill default would vanish here. */
  --nav-cta-bg: #16181d;
  --nav-cta-ink: #ffffff;
  --nav-cta-bg-hover: #2b2f36;
  /* No resting border-bottom since 2026-08-20 — the scrolled dashed hairline
     (.topnav--scrolled above) replaced the constant solid one. */
}
/* Keep in-page anchor targets clear of the sticky bar. */
html { scroll-padding-top: 60px; }
.topnav-inner {
  max-width: var(--maxw-nav);
  margin: 0 auto;
  /* Own the box model: host pages without a global border-box reset (the
     weather-events SPA) otherwise size this content-box, and the bar runs
     2×--pad-x wider than every other page (the footer-inner fix's twin). */
  box-sizing: border-box;
  /* 8px, not the original 14px: the bar is chrome, and on the weather detail
     page it stands in for a 54px floating header — 69px there was a
     regression. Padding is the only lever, since the 40px brand mark stays. */
  padding: 8px var(--nav-pad-x);
  display: flex; align-items: center; gap: 24px;
}
.brand {
  display: inline-flex; align-items: center; gap: 4px;
  color: var(--bar-ink-strong);
  font-weight: 300;
  font-size: 17px;
  letter-spacing: 0.2px;
  text-decoration: none;
}
.brand:hover { text-decoration: none; }
.brand-mark { width: 32px; height: 32px; border-radius: 22%; display: block; object-fit: cover; }
.brand-word { font-weight: 500; }
/* .brand--logo (a header that opts out of the wordmark) keeps the same 28px
   tile as everyone else since 2026-08-20 — the old 40px bare-mark sizing died
   with the logo-only era. The class itself remains a valid opt-out hook. */
.topnav-links {
  display: flex; align-items: center; gap: clamp(16px, 2.5vw, 32px);
  margin-left: auto;
  font-size: 15px;
  font-weight: 500;
}
.topnav-links a {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--bar-ink);
  text-decoration: none;
  transition: color 0.15s;
  position: relative;
}
.topnav-links a:hover { color: var(--bar-ink-strong); text-decoration: none; }
.topnav-links a:not(.topnav-cta).active { color: var(--bar-ink-strong); }
.topnav-links a:not(.topnav-cta).active::after,
.topnav-links a:not(.topnav-cta):hover::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  bottom: -6px;
  height: 2px;
  background: var(--nav-active-underline);
  border-radius: 2px;
}

/* Marketing CTA pill - only rendered by the `marketing` topnav variant,
 * but defined here so every page that loads nav.css can style it. */
.topnav-cta {
  display: inline-flex; align-items: center;
  background: var(--nav-cta-bg);
  color: var(--nav-cta-ink) !important;
  /* Explicit, not inherited: this pill only cleared its underline on :hover and
     relied on the host page's global `a { text-decoration: none }` at rest. The
     weather SPA never loads styles.css, so the pill shipped underlined there.
     Cross-scope components carry their own resets (see design/STYLE-GUIDE.md). */
  text-decoration: none;
  padding: 8px 14px; border-radius: 999px;
  font-weight: 600; font-size: 14px;
  line-height: 1.2;
  border: none;
  transition: background 0.15s, scale 0.12s ease;
}
.topnav-cta:hover { text-decoration: none; background: var(--nav-cta-bg-hover); }
.topnav-cta:active { scale: 0.96; }

/* Compact "Download" pill rendered on every page except the landing.
 * Sits to the right of the search field; opt in via download="true" on the
 * TOPNAV:START marker (see scripts/partials.mjs). */
.topnav-download {
  height: 36px;
  padding: 0 16px;
  font-size: 13.5px;
  white-space: nowrap;
}
@media (max-width: 380px) {
  .topnav-download { padding: 0 12px; font-size: 13px; }
}

/* Lone Download pill in the `cta` topnav variant (brand + download, no nav
 * links or search). Pinned to the right edge in place of .topnav-links. */
.topnav-cta--solo { margin-left: auto; }

/* ---------- Topnav: THE site header ----------------------------------------
 * One header for every page on graycloud.app (2026-08-20). The only per-page
 * difference is the theme: the marketing landing runs the black bar, every
 * other surface runs .topnav--light.
 *
 * Layout: brand, then the primary links immediately beside it, then the
 * account/CTA cluster hard right. Columns are `auto 1fr auto` — the brand and
 * the end cluster take exactly their content width and the LINK column absorbs
 * the slack, so the links stay pinned left next to the logo at every width
 * instead of floating toward the middle or the far edge.
 * The .topnav-mobile-panel wrapper uses display:contents on desktop so its
 * children participate in the grid directly. Below 720px the panel becomes a
 * slide-down sheet and the inner row reverts to flex (see the ≤720px block). */
.topnav--search .topnav-inner {
  /* Follows the shared --maxw-nav cap (site max content width) so the brand
     and actions align with page content and the footer. */
  display: grid;
  /* 1fr auto 1fr: the outer columns are equal, so the link row centers on
     the BAR, not on whatever space the brand and CTA leave over. */
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  /* ONE fixed bar height on every route (~58px: the link row's ~38px floor
     + 10px block padding) — /app, /maps, /support and the SPA all match, so
     the header never jumps across navigations. The nav.js scroll toggle only
     drives the dashed hairline now, never the height. */
  padding-block: 10px;
}
/* The link row: column 2, hard left, set off from the wordmark by a gap wider
   than the gaps BETWEEN the links, so it reads as one group beside the brand
   rather than a fourth sibling of it. */
.topnav--search .topnav-inline {
  grid-column: 2; justify-self: center;
}
.topnav--search .brand { grid-column: 1; justify-self: start; gap: 10px; }
/* UserJot-match brand (2026-08-20): a 28px near-black tile holds the cloud
   glyph, wordmark 15px/600 tracked tight. Scoped to the search variant so
   the dev-dashboard partial variants keep their own brand sizing. */
.topnav--search .brand-word { font-weight: 600; font-size: 15px; letter-spacing: -0.025em; }
.topnav--search .brand-mark { width: 28px; height: 28px; }
.brand-tile {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px;
  border-radius: 8px;
  background: var(--nav-brand-tile-bg, #16181d);
  flex: none;
}
/* Inside the tile the mark is the bare glyph, not the 28px rounded image. */
.topnav .brand .brand-tile .brand-mark {
  width: 18px; height: 18px;
  border-radius: 0;
  object-fit: contain;
}
/* The header renders in the site font (Inter — see fonts.css, imported at the
   top of this file). Explicit rather than inherited so host pages that pin
   their own body face can never restyle the bar. */
.topnav--search {
  font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* ---------- Menu disclosure ------------------------------------------------
 * The Resources popover. Since 2026-08-20 (UserJot-match) it opens on HOVER
 * with short delays (nav.js: 80ms in / 140ms out; click, focus and Escape
 * still work) and wears a fixed light surface on BOTH bar themes: white fill,
 * 1px --nav-panel-border hairline (an approved exception to the no-borders
 * rule, this surface only), and a large soft shadow. Rows are icon + title +
 * muted description. At ≤720px the trigger hides and this same panel renders
 * as a static block inside the hamburger sheet (see the mobile block below),
 * where the --menu-* locals below re-map back to the bar theme. */
/* End cluster: Linear-style inline text links + the account avatar, grouped
   as one grid item pinned to column 3. */
.topnav--search .topnav-end {
  grid-column: 3;
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 12px;
}
/* 12px + the links' own 10px inline padding = ~32px between labels. */
.topnav-inline { display: flex; align-items: center; gap: 12px; }
.topnav-inline > a {
  font-size: 14.5px;
  font-weight: 500;
  color: var(--bar-ink);
  text-decoration: none;
  white-space: nowrap;
  /* Padded hit areas (UserJot-match): the 4px flex gap + this padding gives
     the same ~24px visual rhythm the old 26px bare gap did. */
  padding: 8px 10px;
  border-radius: 6px;
  transition: color 200ms ease-out;
}
.topnav-inline > a:hover { color: var(--bar-ink-strong); text-decoration: none; }
/* Current page (nav.js stamps aria-current="page" at load): strong ink plus
   a half-step of weight (550 renders exactly on the InterVariable axis;
   static fallbacks round to 500/600). Set at load, never on hover, so the
   extra width can't make the row shift under the pointer. */
.topnav-inline > a[aria-current="page"] { color: var(--bar-ink-strong); font-weight: 550; }
/* Inline links that carry a glyph — "App" and "Forecast" (see topnav.mjs
   NAV_APP_LINK / NAV_FORECAST_LINK). Scoped to the class so the icon-free
   rhythm of "API" is untouched. 16px/1.8 matches the popover row glyphs. The
   icon rides the label's currentColor, so it goes strong on hover and on
   aria-current with no extra rule. */
.topnav-inline > a.topnav-glyph { display: inline-flex; align-items: center; gap: 6px; }
.topnav-inline > a.topnav-glyph .topnav-glyph-icon { display: inline-flex; flex: none; }
.topnav-inline > a.topnav-glyph svg { width: 16px; height: 16px; flex: none; }
.topnav-inline > a:focus-visible { outline: 2px solid var(--sev-minor, #2f7bff); outline-offset: 3px; border-radius: 4px; }
/* Auth cluster (Linear-style): hairline divider · quiet "Log in" · filled
   "Sign up" pill (.topnav-cta). Ships display:none because /login 404s while
   ACCOUNTS_ENABLED is off; the account-widgets.js injected stylesheet (which
   only loads when accounts are on) flips it to flex. display (not [hidden])
   so no display rule can beat it. */
.topnav-auth { display: none; align-items: center; gap: 26px; }
.topnav-divider { width: 1px; height: 16px; background: var(--nav-hair); }
.topnav-login {
  font-size: 15px;
  font-weight: 500;
  color: var(--bar-ink);
  text-decoration: none;
  white-space: nowrap;
  transition: color 120ms ease-out;
}
.topnav-login:hover { color: var(--bar-ink-strong); text-decoration: none; }
.topnav-login:focus-visible { outline: 2px solid var(--sev-minor, #2f7bff); outline-offset: 3px; border-radius: 4px; }
.topnav-signup { white-space: nowrap; }
/* Avatar in a filled circle — the same fill family as the ⌘K pill, sized
   36px so it centers on the search pill's midline. Wordless: the circle +
   person glyph say "you / your menu", the panel says what. */
/* .topnav-menu-trigger is now a BEHAVIOUR HOOK ONLY — nav.js finds the panel's
 * trigger by this class. It used to carry the retired avatar's appearance (a
 * 36x36 pill with a fill), which silently squashed the inline "Resources"
 * trigger into a 36px box and made its label overflow onto the link beside it.
 * The one surviving avatar is the weather SPA's .map-nav-trigger, which styles
 * itself in src/weather-events.styles.css. Appearance now belongs to whichever
 * component uses the hook — see .topnav-disc-trigger below. */

/* Desktop panel (UserJot-match): 304px, white, 12px radius, hairline border,
   two-layer large soft shadow, 8px inner padding around 8px-radius rows.
   The --menu-* locals exist so the ≤720px sheet (where the panel flattens
   onto the bar) can re-map row inks back to the bar theme without touching
   these rules. */
.topnav-menu-panel {
  --menu-ink-strong: var(--nav-panel-ink-strong, #16181d);
  --menu-ink-muted: var(--nav-panel-ink-muted, #8a8f98);
  --menu-hover: rgba(23, 23, 23, 0.03);
  --menu-hair: var(--nav-panel-border, rgba(23, 23, 23, 0.08));
  position: absolute; top: calc(100% + 12px); right: 0;
  width: 304px;
  background: var(--nav-panel-bg, #ffffff);
  background-clip: padding-box;
  border: 1px solid var(--nav-panel-border, rgba(23, 23, 23, 0.08));
  border-radius: 12px;
  padding: 8px;
  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
  overflow: hidden;
  display: flex; flex-direction: column;
  opacity: 0; transform: translateY(-4px); transform-origin: top right;
  pointer-events: none; visibility: hidden;
  transition: opacity 160ms ease-out, transform 160ms ease-out, visibility 0s linear 160ms;
  z-index: 120;
}
.topnav-menu-trigger[aria-expanded="true"] + .topnav-menu-panel {
  opacity: 1; transform: none;
  pointer-events: auto; visibility: visible;
  transition: opacity 160ms ease-out, transform 160ms ease-out, visibility 0s linear 0s;
}
.topnav-menu-group { display: flex; flex-direction: column; padding: 4px 0; }
/* flex above would beat the bare [hidden] attribute — account-widgets.js hides
   the Explore group for signed-in menus via hidden. */
.topnav-menu-group[hidden] { display: none; }
.topnav-menu-group + .topnav-menu-group { border-top: 1px solid var(--menu-hair, rgba(23, 23, 23, 0.08)); }
.topnav-menu-heading {
  margin: 0; padding: 4px 12px;
  font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--menu-ink-muted, var(--nav-pill-ink-faint));
}
.topnav-menu-panel a {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 10px 12px; border-radius: var(--radius-8, 8px);
  color: var(--menu-ink-strong); text-decoration: none;
  font-size: 14px; font-weight: 600; letter-spacing: -0.025em; line-height: 1.45;
  transition: background-color 150ms ease-out;
}
.topnav-menu-panel a:hover { background: var(--menu-hover); text-decoration: none; }
/* Icons hang at the title's cap line (items-start + 3px nudge), so two-line
   rows keep the glyph beside the TITLE, not centered on the block. Single-line
   rows (the SPA map menu, the injected account group) sit a hair high — fine. */
.topnav-menu-panel a svg { width: 16px; height: 16px; flex: none; margin-top: 3px; }
.topnav-menu-text { display: flex; flex-direction: column; min-width: 0; }
.topnav-menu-desc {
  font-size: 13px; font-weight: 400; letter-spacing: -0.01em; line-height: 1.66;
  color: var(--menu-ink-muted);
  white-space: normal;
}
@media (prefers-reduced-motion: reduce) {
  .topnav-menu-panel { transition: none; }
}

/* ---------- Inline disclosure ("Resources") -------------------------------
 * Reuses the popover above wholesale — same .topnav-menu-panel / -group / -a
 * rules, same nav.js wireMenu (click, Escape, click-outside, close-on-pick).
 * The only difference from the retired avatar menu is the anchor: that trigger
 * was pinned to the bar's right edge, this one sits inline among the links, so
 * the panel opens from its LEFT edge (--start) instead of its right. */
.topnav-disc { position: relative; display: inline-flex; align-items: center; }
/* Reads as one of the inline links, because it is one — same size, weight and
 * ink as .topnav-inline a, plus a chevron that flips when the panel opens. */
.topnav-disc-trigger {
  appearance: none; border: 0; background: none; margin: 0;
  cursor: pointer;
  display: inline-flex; align-items: center;
  font: inherit; font-size: 14.5px; font-weight: 500; line-height: inherit;
  color: var(--bar-ink); white-space: nowrap;
  padding: 8px 10px;
  border-radius: 6px;
  transition: color 200ms ease-out;
  -webkit-tap-highlight-color: transparent;
}
/* Invisible hover bridge under the OPEN trigger, spanning the 12px gap to
   the panel, so the pointer can travel down without firing the disc's
   mouseleave. Only exists while open — a closed trigger must not open from a
   hover 14px below it. */
.topnav-disc-trigger[aria-expanded="true"]::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 100%;
  height: 14px;
}
.topnav-disc-trigger:hover,
.topnav-disc-trigger[aria-expanded="true"] { color: var(--bar-ink-strong); }
.topnav-disc-trigger:focus-visible { outline: 2px solid var(--sev-minor, #2f7bff); outline-offset: 3px; }
/* Chevron removed 2026-08-20 (UserJot-match): the trigger is the bare label,
   colored like its link siblings; the panel itself is the affordance. */
.topnav-menu-panel--start { left: 0; right: auto; transform-origin: top left; }
/* Desktop: the trigger IS the label. The heading below only exists for the
 * mobile sheet, where the trigger is hidden and the panel renders as a static
 * block that would otherwise have no name. */
.topnav-disc-label { display: none; }
@media (prefers-reduced-motion: reduce) {
  .topnav-disc-trigger { transition: none; }
  .topnav { transition: none; }
}

/* The header's GPS + ⌘K pill + °F cluster was removed on 2026-08-20, and its
 * ~90 lines of styling went with it — .topnav-search-group, .topnav--search
 * .search-pill (+ label/kbd/icon), .topnav-units and .topnav-search-gps. No
 * header emits any of them now. The weather SPA's floating pill is a separate
 * component that lives outside .topnav and styles itself in
 * src/weather-events.styles.css; it was never styled by these rules. */

/* Mobile-menu wrapper. On desktop this element is invisible to layout
   (`display: contents`) so its children — the search group and the GPS-error
   status pill — participate in the .topnav-inner flexbox exactly as they did
   before the mobile-menu wrap was introduced. On mobile (≤720px, in the rule
   block below) it becomes a slide-down panel under the header. */
.topnav-mobile-panel { display: contents; }

/* Tightest viewports: tighten the topnav side padding so the slide-down panel
   fits comfortably on small phones. Applies to every variant (the plain
   .topnav on the dev dashboard included), not just --search. */
@media (max-width: 480px) {
  .topnav-inner { padding-inline: clamp(12px, 4vw, 16px); }
}

/* iPhone SE / very narrow: trim the brand wordmark down so the input keeps
   a usable typing area. The icon still anchors the brand. */
@media (max-width: 380px) {
  .topnav--search .brand-word { font-size: 15px; }
  .topnav--search .brand-mark { width: 28px; height: 28px; }
}

/* ---------- Footer ---------- */
.site-footer {
  /* Theme-following hairline, same arrangement as the bar's --nav-hair: the
     copyright rule below used to hardcode an on-dark white, which vanished
     the moment the footer went light. Re-mapped in .site-footer--light. */
  --footer-hair: rgba(255, 255, 255, 0.08);
  /* Muted ink for the copyright row + CTA sublink. The level is per-theme, not
     one constant: 60% of --bar-ink is 5.35:1 on the black footer but only
     3.09:1 once the same mix lands on white. .site-footer--light raises it. */
  --footer-muted: color-mix(in oklab, var(--bar-ink) 60%, transparent);
  --footer-social: color-mix(in oklab, var(--bar-ink) 70%, transparent);
  background: var(--bar-bg);
  color: var(--bar-ink);
  /* Horizontal inset lives on .footer-inner / .footer-copy so they share the
     header's exact box model (max-width + padding-inline on the inner row). */
  padding: 88px 0 24px;
  font-size: 14px;
  font-weight: 300;
  /* Pin typography (same reason as .topnav). */
  line-height: 1.45;
  letter-spacing: normal;
  font-feature-settings: "tnum" 1, "ss01" 1;
  /* Lets .footer-copy::before bleed full-width without triggering page scroll. */
  overflow-x: clip;
}
.footer-inner {
  /* Always the shared nav width (--maxw-nav) so header and footer align on
     every page, independent of each page's body content measure (--maxw).
     border-box explicitly: pages that load nav.css without styles.css (the
     weather-events SPA) have no global box-sizing reset. */
  box-sizing: border-box;
  max-width: var(--maxw-nav);
  margin: 0 auto;
  padding-inline: var(--nav-pad-x);
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 40px;
}
.brand--footer {
  font-size: 28px;
  color: var(--bar-ink-strong);
}
.brand--footer .brand-mark { width: 48px; height: 48px; }
.footer-lead {
  display: flex;
  flex-direction: column;
  gap: 28px;
}
.footer-cta-headline {
  margin: 0;
  font-size: clamp(17px, 1.9vw, 20px);
  font-weight: 300;
  color: var(--bar-ink-strong);
  letter-spacing: -0.01em;
  line-height: 1.2;
}
.footer-cta-sublink {
  display: inline-block;
  margin-top: 4px;
  /* Muted derived from the bar ink (--bar-ink), not the page-level --ink-3:
     --bar-ink flips with the footer theme, so this stays legible on both the
     black landing footer and the light one everywhere else. */
  color: var(--footer-muted);
  font-size: 17px;
  font-weight: 400;
  text-decoration: none;
}
.footer-cta-sublink:hover { color: var(--bar-ink-strong); }
.footer-links {
  display: grid;
  grid-template-columns: auto auto;
  gap: 8px 56px;
  text-align: right;
  color: var(--bar-ink);
}
.footer-links:has(.footer-col) { grid-template-columns: repeat(4, auto); gap: 8px 88px; }
.footer-col { display: grid; gap: 8px; align-content: start; }
.footer-col-heading {
  margin: 0 0 4px;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--bar-ink-strong);
}
.footer-links a { color: var(--bar-ink); text-decoration: none; }
.footer-links a:hover { color: var(--bar-ink-strong); text-decoration: none; }
.footer-copy {
  box-sizing: border-box;
  position: relative;
  max-width: var(--maxw-nav);
  margin: 112px auto 0;
  padding: 24px var(--nav-pad-x) 0;
  text-align: left;
  font-size: 12px;
  /* Bar-ink muted — same reason as .footer-cta-sublink above. */
  color: var(--footer-muted);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}
.footer-copy-text { flex: 1 1 auto; min-width: 0; }
.footer-copy a {
  color: var(--bar-ink-strong, #fff);
  text-decoration: none;
}
.footer-copy a:hover { text-decoration: underline; }
.footer-social {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  color: var(--footer-social);
}
.footer-social:hover { color: var(--bar-ink-strong, #fff); text-decoration: none; }
.footer-social svg { width: 14px; height: 14px; display: block; }
.footer-copy::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 100vw;
  height: 1px;
  background: var(--footer-hair);
  pointer-events: none;
}

/* ---------- Light footer variant ----------
   Light is the default on every surface; only the marketing landing (/app)
   keeps the black footer, via light="false" on its FOOTER:START marker. The
   bar tokens come from the shared block up with .topnav--light. A top hairline
   does the separating here: --bg is #ffffff, so a white footer has no edge of
   its own the way the black one does. */
.site-footer--light {
  --footer-hair: rgba(23, 23, 23, 0.08);
  /* 60% -> 3.09:1 on white, under AA. 80% lands at 5.08:1 and still reads as
     secondary next to the 8.86:1 link ink. */
  --footer-muted: color-mix(in oklab, var(--bar-ink) 80%, transparent);
  --footer-social: color-mix(in oklab, var(--bar-ink) 85%, transparent);
  border-top: 1px solid var(--footer-hair);
}

/* ---------- Mobile nav toggle (hamburger) ----------
 * Hidden on desktop; revealed at <=720px. The bars use a single :before/:after
 * pseudo trick so the icon morphs to an X without an SVG swap. */
.nav-toggle {
  display: none;
  align-items: center; justify-content: center;
  width: 40px; height: 40px;
  margin: -4px -8px -4px 0;
  padding: 0;
  background: transparent;
  border: 0;
  color: var(--bar-ink-strong);
  border-radius: 8px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.nav-toggle:focus-visible {
  outline: 2px solid var(--bar-ink-strong);
  outline-offset: 2px;
}
.nav-toggle-icon {
  position: relative;
  display: block;
  width: 22px; height: 2px;
  background: currentColor;
  border-radius: 1px;
  transition: background-color 150ms ease-out;
}
.nav-toggle-icon::before,
.nav-toggle-icon::after {
  content: "";
  position: absolute; left: 0;
  width: 22px; height: 2px;
  background: currentColor;
  border-radius: 1px;
  transition: transform 150ms ease-out, top 150ms ease-out;
}
.nav-toggle-icon::before { top: -7px; }
.nav-toggle-icon::after  { top:  7px; }
.nav-open .nav-toggle-icon { background: transparent; }
.nav-open .nav-toggle-icon::before { top: 0; transform: rotate(45deg); }
.nav-open .nav-toggle-icon::after  { top: 0; transform: rotate(-45deg); }

@media (max-width: 720px) {
  .topnav-inner { gap: 12px; }
  /* Search variant: the desktop grid (1fr auto 1fr) doesn't fit the mobile
     pattern — the mobile-panel goes position:absolute and the nav-toggle
     needs margin-left:auto to land on the right edge. Revert to flex. */
  .topnav--search .topnav-inner { display: flex; }
  .footer-inner { flex-direction: column; gap: 24px; }
  .footer-links { grid-template-columns: 1fr; gap: 24px; text-align: left; }

  /* .topnav-links goes position:absolute below, losing its margin-left:auto.
     Push the toggle to the right edge in its place. */
  .nav-toggle { display: inline-flex; margin-left: auto; }

  /* .topnav-links transforms from inline row -> slide-down panel under header. */
  .topnav-links {
    position: absolute;
    top: 100%; left: 0; right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin: 0;
    padding: 4px var(--nav-pad-x) 12px;
    background: var(--bar-bg);
    border-top: 1px solid var(--nav-hair, rgba(255, 255, 255, 0.08));
    font-size: 17px;
    opacity: 0;
    transform: translateY(-6px);
    pointer-events: none;
    visibility: hidden;
    transition:
      opacity 150ms ease-out,
      transform 150ms ease-out,
      visibility 0s linear 150ms;
  }
  .topnav-links a:not(.topnav-cta) {
    display: block;
    padding: 14px 0;
  }
  .topnav-links a:not(.topnav-cta) + a:not(.topnav-cta) {
    border-top: 1px solid var(--nav-hair, rgba(255, 255, 255, 0.08));
  }
  .topnav-links a:not(.topnav-cta).active::after,
  .topnav-links a:not(.topnav-cta):hover::after { display: none; }
  /* CTA inside the dropdown becomes a full-width pill below the links. */
  .topnav-links .topnav-cta {
    margin: 16px 0 4px;
    justify-content: center;
    width: 100%;
    padding: 14px 22px;
    font-size: 15px;
  }
  .nav-open .topnav-links {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
    visibility: visible;
    transition:
      opacity 150ms ease-out,
      transform 150ms ease-out,
      visibility 0s linear 0s;
  }
  /* Lock background scroll while the panel is open. */
  body.nav-open { overflow: hidden; }

  /* Search variant: the .topnav-mobile-panel wrapper switches from
     display:contents (desktop, where its children flow inline) to a
     slide-down panel anchored under the header — same pattern as
     .topnav-links above. The brand + units (if present) + hamburger
     remain inline in the topnav row. */
  .topnav-mobile-panel {
    display: flex;
    flex-direction: column;
    gap: 10px;
    position: absolute;
    top: 100%; left: 0; right: 0;
    margin: 0;
    padding: 12px var(--nav-pad-x) 14px;
    background: var(--bar-bg);
    border-top: 1px solid var(--nav-hair, rgba(255, 255, 255, 0.08));
    opacity: 0;
    transform: translateY(-6px);
    pointer-events: none;
    visibility: hidden;
    transition:
      opacity 150ms ease-out,
      transform 150ms ease-out,
      visibility 0s linear 150ms;
  }
  .nav-open .topnav-mobile-panel {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
    visibility: visible;
    transition:
      opacity 150ms ease-out,
      transform 150ms ease-out,
      visibility 0s linear 0s;
  }
  /* The desktop end-cluster dissolves in the sheet: its children (inline
     links, account menu) stack as direct rows of the column-flex panel. */
  .topnav-mobile-panel .topnav-end { display: contents; }
  .topnav-mobile-panel .topnav-inline {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    width: 100%;
  }
  .topnav-mobile-panel .topnav-inline a { font-size: 16px; padding: 12px 4px; }
  /* Get-the-app pill in the sheet: a full-width centered row under the
     links, mirroring the auth pair's stacked treatment below. */
  .topnav-mobile-panel .topnav-getapp { align-self: stretch; justify-content: center; margin-top: 8px; }
  /* Auth pair in the sheet: full-width row under the links, no divider (a
     vertical hairline is meaningless in a stacked sheet). Still gated by the
     same accounts-on un-hide. */
  .topnav-mobile-panel .topnav-auth { width: 100%; gap: 16px; padding-top: 4px; }
  .topnav-mobile-panel .topnav-divider { display: none; }
  .topnav-mobile-panel .topnav-login { font-size: 16px; padding: 12px 4px; }
  .topnav-mobile-panel .topnav-signup { margin-left: auto; }
  /* Menu disclosure inside the slide-down sheet: the "Menu" trigger is
     redundant here (the hamburger already opened the sheet), so hide it and
     render the popover's contents inline as a static, full-width block —
     grouped links with their section headings, straight on the bar. */
  .topnav-mobile-panel .topnav-menu { width: 100%; }
  .topnav-menu-trigger { display: none; }
  .topnav--search .topnav-menu-panel {
    position: static; min-width: 0; width: auto;
    background: none; border: 0; box-shadow: none; border-radius: 0; padding: 0;
    opacity: 1; transform: none; visibility: visible; pointer-events: auto;
    transition: none; z-index: auto;
    /* Flattened onto the bar, the rows re-theme to the bar's inks (the fixed
       white-panel inks above would vanish on the dark landing sheet). */
    --menu-ink-strong: var(--bar-ink-strong);
    --menu-ink-muted: var(--bar-ink);
    --menu-hover: var(--nav-pill-kbd-bg);
    --menu-hair: var(--nav-hair);
  }
  .topnav-menu-panel a { font-size: 16px; padding: 12px 4px; }
  .topnav-menu-desc { font-size: 13px; }
  .topnav-menu-heading { padding-inline: 4px; padding-top: 12px; }
  .topnav-menu-group:first-child .topnav-menu-heading { padding-top: 4px; }
  /* Resources in the slide-down sheet: the disclosure is redundant once the
     hamburger has already opened everything, so the trigger hides (via the
     .topnav-menu-trigger rule above) and the panel flattens into a static,
     full-width block — same treatment the avatar menu used to get. Its heading
     un-hides here to carry the name the trigger was holding. */
  .topnav-disc { display: block; width: 100%; }
  .topnav-disc-label { display: block; }

}

@media (max-width: 720px) and (prefers-reduced-motion: reduce) {
  .topnav-links,
  .topnav-mobile-panel,
  .nav-toggle-icon,
  .nav-toggle-icon::before,
  .nav-toggle-icon::after { transition: none; }
  .topnav-links { transform: none; }
}

@media print {
  .topnav, .site-footer { display: none; }
}
