/*
 * Nightfall — the shared dark skin for every FlutterGo page except `/`,
 * which carries its own homepage-only sheet (`home-nightfall.css`).
 *
 * HOW THIS WORKS
 * The site runs two independent token vocabularies:
 *   1. "Atelier Zero" (--paper / --ink / --line / --coral ...) used by the
 *      Astro pages through globals.css + sub-pages.css
 *   2. the static-page set (--bg / --surface / --text / --border ...) used by
 *      the hand-written pages under public/ through _fluttergo-*.css
 * Both are ~95% variable-driven, so this sheet re-points BOTH vocabularies at
 * the Nightfall palette instead of rewriting a few thousand rules. Anything
 * that still looks light afterwards is a hardcoded value in the source sheet
 * and is patched explicitly at the bottom.
 *
 * OPT-IN + SPECIFICITY
 * Everything hangs off `html.nf-page`. That is deliberate: (0,1,1) always
 * beats the `:root` (0,1,0) blocks it overrides, so this sheet wins no matter
 * where the loader places it in the cascade — and a page only goes dark when
 * it explicitly adds the class.
 *
 * Load it AFTER the page's own stylesheets.
 */

html.nf-page {
  /* ---- Nightfall palette — the single source ---------------------- */
  --nf-ink: #04060c;
  --nf-panel: #0a101b;
  --nf-panel-2: #0d1524;
  --nf-line: rgba(148, 170, 220, 0.13);
  --nf-line-bright: rgba(148, 170, 220, 0.26);
  --nf-star: #e9eef9;
  --nf-muted: #94a0b6;
  --nf-dim: #77839b;
  --nf-sky: #5bc8ff;
  --nf-sky-deep: #1e9be0;
  --nf-ember: #ff6a3d;
  --nf-ember-ink: #1c0b04;
  --nf-ok: #34d399;
  --nf-display: 'Fraunces Variable', Fraunces, Georgia, 'Times New Roman', serif;
  --nf-body: 'Inter Variable', Inter, 'Albert Sans', -apple-system, sans-serif;
  --nf-mono: 'JetBrains Mono Variable', 'JetBrains Mono', 'SF Mono', Menlo, monospace;
  --nf-ease: cubic-bezier(0.23, 1, 0.32, 1);

  /* ---- 1. Atelier Zero vocabulary (globals.css, sub-pages.css) ---- */
  --paper: #0a101b;
  --paper-warm: #04060c;
  --paper-dark: #0d1524;
  --bone: #0a101b;
  --ink: #e9eef9;
  --ink-soft: #c2cde1;
  --ink-mute: #94a0b6;
  --ink-faint: #77839b;
  /* The accent family goes sky; --mustard is the one the light theme used as
     a second highlight, so it becomes ember and the pages inherit the same
     sky→ember pairing the homepage runs on. */
  --coral: #5bc8ff;
  --coral-soft: #7ad4ff;
  --olive: #1e9be0;
  --mustard: #ff6a3d;
  /* Matched to --nf-line. At 0.16 the cards on pricing, download and the blog
     drew a visibly brighter hairline than the same card on docs, explore or
     the homepage, which all read --nf-line at 0.13. */
  --line: rgba(148, 170, 220, 0.13);
  --line-hover: rgba(148, 170, 220, 0.32);
  --line-soft: rgba(148, 170, 220, 0.12);
  --line-faint: rgba(148, 170, 220, 0.07);
  --shadow: 0 30px 60px -30px rgba(0, 0, 0, 0.75);
  /* Body copy joins the rest of the site on Inter. `--sans` is the Atelier
     Zero body token and still pointed at Albert Sans, so the Astro sub-pages
     — pricing, download, onboarding — set every paragraph in a face no other
     page uses: the homepage, the static pages and the daemon-rendered blog
     are all Inter. `sub-page-layout.astro` already self-hosts Inter as part
     of what it calls the Nightfall type trio; only the token was left behind.
     The CJK fallbacks the original stack carried are kept. */
  --sans: 'Inter Variable', Inter, 'Albert Sans', 'PingFang SC', 'Microsoft YaHei', sans-serif;
  --body: 'Inter Variable', Inter, 'Albert Sans', 'PingFang SC', 'Microsoft YaHei', sans-serif;

  /* ---- 2. Static-page vocabulary (_fluttergo-*.css) --------------- */
  --bg: #04060c;
  --bg-2: #0a101b;
  --surface: #0a101b;
  --surface-2: #0d1524;
  --surface-glass: rgba(12, 19, 33, 0.72);
  --surface-glass-strong: rgba(12, 19, 33, 0.88);
  --border: rgba(148, 170, 220, 0.13);
  --border-soft: rgba(148, 170, 220, 0.1);
  --border-strong: rgba(148, 170, 220, 0.3);
  --text: #e9eef9;
  --text-strong: #e9eef9;
  --text-muted: #94a0b6;
  --text-soft: #77839b;
  --brand-blue: #5bc8ff;
  --brand-blue-strong: #1e9be0;
  --brand-sky: #7ad4ff;
  --brand-violet: #9d8cff;
  --brand-gradient: linear-gradient(105deg, #5bc8ff 0%, #7ad4ff 100%);
  --brand-gradient-tri: linear-gradient(115deg, #9d8cff 0%, #5bc8ff 48%, #7ad4ff 100%);
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 8px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 8px 30px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 30px 70px -30px rgba(0, 0, 0, 0.8), 0 8px 24px -12px rgba(0, 0, 0, 0.6);
  --glow-blue: 0 24px 60px -20px rgba(91, 200, 255, 0.35);
  --ring: rgba(91, 200, 255, 0.22);
  --grid-line: rgba(148, 170, 220, 0.05);
  --dot: rgba(148, 170, 220, 0.1);

  /* The root paints white by default, and a document that outruns its body
     leaves a white sliver at the bottom — body backgrounds only propagate to
     the canvas when the root itself is transparent. */
  background: #04060c;
  color-scheme: dark;
}

html.nf-page body {
  background: var(--nf-ink);
  color: var(--nf-star);
}
/* The light theme's paper-grain multiply texture reads as smudge on ink. */
html.nf-page body::before { content: none; }
html.nf-page ::selection { background: rgba(91, 200, 255, 0.28); color: var(--nf-star); }
html.nf-page :focus-visible { outline: 2px solid var(--nf-sky); outline-offset: 3px; }

/* Native anchor jumps must clear the fixed header capsule. */
html.nf-page { scroll-padding-top: calc(var(--chrome-h, 84px) + 16px); }

/* ---- Atmosphere ------------------------------------------------------
   All three elements are optional; a page that omits them just renders flat
   ink. Injected by _fluttergo-nightfall.js so no page has to hand-write it. */
html.nf-page .nf-stars {
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
}
html.nf-page .nf-grain {
  position: fixed; inset: -50%; z-index: 3; pointer-events: none; opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  animation: nf-grain 9s steps(6) infinite;
}
@keyframes nf-grain {
  0% { transform: translate(0, 0); } 20% { transform: translate(-3%, 2%); }
  40% { transform: translate(2%, -3%); } 60% { transform: translate(-2%, -2%); }
  80% { transform: translate(3%, 3%); } 100% { transform: translate(0, 0); }
}
html.nf-page .nf-progress {
  position: fixed; top: 0; left: 0; right: 0; height: 2px; z-index: 60; pointer-events: none;
}
html.nf-page .nf-progress i {
  display: block; height: 100%; transform: scaleX(0); transform-origin: left;
  background: linear-gradient(90deg, var(--nf-sky), var(--nf-ember));
}
/* Page content has to sit above the fixed field.
   `data-nf-overlay` is the opt-out for a viewport-anchored layer mounted as a
   body child (a search palette, a modal): forcing `position: relative` on one
   of those pins it to the document instead of the viewport, so it would open
   wherever the reader happens to have scrolled to. */
html.nf-page body > *:not(.nf-stars):not(.nf-grain):not(.nf-progress):not(.site-chrome):not([data-nf-overlay]) {
  position: relative;
  z-index: 1;
}

/* ---- Site header parity ----------------------------------------------
   `.site-chrome` is `position: fixed` so the bar stays pinned and condenses
   into the floating capsule on scroll. The Astro pages (homepage, pricing,
   download, blog) get that for free because their layout nests the header
   inside `.shell`, so the rule above lands on the wrapper. The hand-written
   pages under `public/` mount the header as a DIRECT body child, where the
   rule hit `.site-chrome` itself and quietly demoted it to `relative` — the
   header scrolled away and never condensed, so docs, explore, about, product
   and the legal pages had a visibly different header from the homepage.

   `.site-chrome` is therefore excluded from the rule above — it carries its
   own `z-index: 50`, so it was never the layer that needed lifting — and the
   body gets back the height the header no longer occupies in flow. `body.fgs`
   is exactly the static-page set: the homepage is `body.fg-landing` and the
   Astro sub-pages are `body.sub-page`, both already correct and untouched. */
html.nf-page { --chrome-h: 84px; }
html.nf-page body.fgs { padding-top: var(--chrome-h); }

/* ---- Typography ------------------------------------------------------
   Display face for headings so the sub-pages share the homepage's voice;
   body copy stays on the sans stack for readability at small sizes. */
html.nf-page h1,
html.nf-page h2,
html.nf-page .nf-display {
  font-family: var(--nf-display);
  font-weight: 300;
  font-optical-sizing: auto;
  letter-spacing: -0.01em;
  color: var(--nf-star);
  text-wrap: balance;
}
html.nf-page h1 em, html.nf-page h2 em {
  font-style: italic; font-weight: 400; color: var(--nf-ember);
}
html.nf-page h3, html.nf-page h4, html.nf-page h5, html.nf-page h6 {
  color: var(--nf-star);
}
html.nf-page code, html.nf-page kbd, html.nf-page pre, html.nf-page samp {
  font-family: var(--nf-mono);
}

/* The static pages' own sheet opens its body stack with plain `Inter`, which
   is the Google-hosted static family they used to link. They self-host the
   variable file now (`_fluttergo-fonts.css`), so point the body at the same
   stack the Astro pages use: one build of the typeface across the site
   instead of a variable face on half the pages and static instances on the
   other half. */
html.nf-page body { font-family: var(--nf-body); }

/* ---- Surfaces the token pass can't reach ----------------------------
   Every selector below exists because the source sheet hardcodes a light
   value rather than reading a variable. */
html.nf-page pre,
html.nf-page .astro-code,
html.nf-page pre.astro-code {
  background: #070b14 !important;
  border: 1px solid var(--nf-line);
  color: #c7d3ea;
}
html.nf-page :is(pre, .astro-code) span { background: transparent !important; }
html.nf-page code { color: var(--nf-sky); }
html.nf-page hr { border-color: var(--nf-line); }
html.nf-page img[src$=".svg"].nf-invert,
html.nf-page .nf-invert { filter: invert(1) hue-rotate(180deg); }
html.nf-page table { border-color: var(--nf-line); }
html.nf-page :is(th, td) { border-color: var(--nf-line); }
html.nf-page thead { background: var(--nf-panel-2); }

/* Inputs keep their own light backgrounds in several sheets. */
html.nf-page :is(input, textarea, select):not([type="checkbox"]):not([type="radio"]) {
  background: rgba(13, 21, 36, 0.7);
  border-color: var(--nf-line-bright);
  color: var(--nf-star);
}
html.nf-page :is(input, textarea)::placeholder { color: var(--nf-dim); }

/* ---- Buttons --------------------------------------------------------
   The light theme's primary was solid brand blue on white; Nightfall's
   primary action is ember, matching the homepage composer + final CTA. */
html.nf-page :is(.btn-primary, .primary, .nav-primary-cta, .fgs-btn--primary):not([disabled]):not(.is-disabled) {
  background: var(--nf-ember);
  color: var(--nf-ember-ink);
  border-color: transparent;
}
html.nf-page :is(.btn-primary, .primary, .fgs-btn--primary):hover {
  background: var(--nf-ember);
  box-shadow: 0 6px 32px rgba(255, 106, 61, 0.4);
}
html.nf-page :is(.btn-primary, .primary, .fgs-btn--primary):active { filter: brightness(0.88); }
html.nf-page :is(.ghost, .primary-ghost, .btn-ghost) {
  border-color: var(--nf-line-bright);
  color: var(--nf-star);
  background: transparent;
}
html.nf-page :is(.ghost, .primary-ghost, .btn-ghost):hover {
  border-color: var(--nf-sky);
  background: rgba(91, 200, 255, 0.06);
}

/* ---- Header ---------------------------------------------------------
   Same shared markup as the homepage, so it gets the same treatment: light
   text at rest, frosted dark glass once condensed. */
html.nf-page .site-chrome .brand,
html.nf-page .site-chrome .nav-signin,
html.nf-page .site-chrome .fg-user-chip,
html.nf-page .site-chrome .fg-settings-gear { color: var(--nf-star); }
/* Desktop only. Below this the links live in the drawer, which the
   mobile-drawer block further down paints dark. */
@media (min-width: 881px) {
  html.nf-page .site-chrome .nav-links a { color: var(--nf-star); }
  html.nf-page .site-chrome .nav-links a:hover { color: var(--nf-sky); }
}
html.nf-page .site-chrome .nav-signin:hover { color: var(--nf-sky); }
html.nf-page .site-chrome .nav-primary-cta { box-shadow: 0 0 0 1px rgba(148, 170, 220, 0.35); }
html.nf-page .site-chrome .nav-toggle-icon,
html.nf-page .site-chrome .nav-toggle-icon::before,
html.nf-page .site-chrome .nav-toggle-icon::after { background: var(--nf-star); }
html.nf-page .site-chrome.is-condensed .nav {
  background: rgba(10, 16, 27, 0.52);
  -webkit-backdrop-filter: blur(18px) saturate(1.5);
  backdrop-filter: blur(18px) saturate(1.5);
  border-color: rgba(148, 170, 220, 0.18);
  box-shadow:
    inset 0 1px 0 rgba(233, 238, 249, 0.07),
    0 6px 24px rgba(0, 0, 0, 0.35),
    0 12px 40px rgba(0, 0, 0, 0.3);
}

/* ---- Mobile drawer --------------------------------------------------
   The shared header's drawer is a solid white card with `--ink` links,
   which works in the light theme. Here `--ink` is remapped to the star
   colour, so the sheet rendered as near-white text on white and the menu
   was effectively blank. It gets the condensed capsule's dark glass
   instead, which is what the rest of the chrome already looks like.
   Range matches the drawer's own breakpoint in globals.css. */
@media (max-width: 1080px) {
  html.nf-page .site-chrome .nav > .nav-inner > nav[data-nav-primary] {
    background: rgba(10, 16, 27, 0.94);
    -webkit-backdrop-filter: blur(20px) saturate(1.5);
    backdrop-filter: blur(20px) saturate(1.5);
    border-color: rgba(148, 170, 220, 0.18);
    box-shadow:
      inset 0 1px 0 rgba(233, 238, 249, 0.07),
      0 18px 46px rgba(0, 0, 0, 0.55);
  }
  html.nf-page .site-chrome .nav-links li + li { border-top-color: rgba(148, 170, 220, 0.12); }
  html.nf-page .site-chrome .nav-links a { color: var(--nf-star); }
  html.nf-page .site-chrome .nav-links a:hover,
  html.nf-page .site-chrome .nav-links a[aria-current="page"] { color: var(--nf-sky); }
  /* One action per surface: the drawer's "Create a project" is the ember
     one, the same way the bar's CTA is. */
  html.nf-page .site-chrome .nav-links .nav-drawer-cta { color: var(--nf-ember); }
  html.nf-page .site-chrome .nav-links .nav-drawer-cta:hover { color: var(--nf-ember); }
}

/* ---- Footer ---------------------------------------------------------
   Styling lives in app/footer-skin.css -> _fluttergo-footer.css, which
   the footer injects itself. Duplicating it here is what let the
   homepage and the sub-pages drift apart. */


/* ---- Reduced motion -------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html.nf-page .nf-grain { animation: none; }
}

/* ======================================================================
 * Residual patches
 * ----------------------------------------------------------------------
 * Everything above is a token remap. The rules below exist because the
 * source sheet hardcodes a light value that no token can reach. Each one
 * names the file and the reason so it can be retired if that sheet is ever
 * refactored onto variables.
 * ==================================================================== */

/* --- download.css -----------------------------------------------------
   `.dl-card` is liquid glass built on `hsl(0 0% 100% / 0.5)`. On ink that
   half-white slab swallowed its own body copy — the OS cards rendered as
   unreadable grey blocks. */
html.nf-page .dl-card {
  border-color: var(--nf-line);
  background: rgba(12, 19, 33, 0.62);
  box-shadow:
    inset 0 1px 0 rgba(233, 238, 249, 0.05),
    0 12px 40px rgba(0, 0, 0, 0.45);
}
html.nf-page .dl-card.is-detected {
  border-color: rgba(91, 200, 255, 0.32);
  box-shadow:
    inset 0 1px 0 rgba(233, 238, 249, 0.07),
    0 10px 30px rgba(91, 200, 255, 0.1),
    0 20px 56px rgba(0, 0, 0, 0.5);
}
/* Primary download action joins the ember CTA family. */
html.nf-page .dl-btn:not(.is-disabled):not(.dl-btn--ghost) {
  background: var(--nf-ember);
  border-color: transparent;
  color: var(--nf-ember-ink);
}
html.nf-page .dl-btn:not(.is-disabled):not(.dl-btn--ghost):hover {
  background: var(--nf-ember);
  border-color: transparent;
  box-shadow: 0 6px 32px rgba(255, 106, 61, 0.4);
}

/* --- auth.css ---------------------------------------------------------
   The submit read sky because it inherits --coral. Every other primary
   action on the site is ember, and sign-in is the most important button
   on its page, so it should not be the one exception. */
html.nf-page .auth-submit:not([disabled]) {
  background: var(--nf-ember);
  border-color: transparent;
  color: var(--nf-ember-ink);
}
html.nf-page .auth-submit:not([disabled]):hover {
  background: var(--nf-ember);
  border-color: transparent;
  box-shadow: 0 6px 32px rgba(255, 106, 61, 0.4);
}
html.nf-page .auth-oauth-btn {
  background: rgba(13, 21, 36, 0.7);
  border-color: var(--nf-line-bright);
  color: var(--nf-star);
}
html.nf-page .auth-oauth-btn:hover { border-color: var(--nf-sky); }

/* --- pricing.css ------------------------------------------------------
   Its private blue family drives badges, ticks and the featured ring. */
html.nf-page {
  --pr-blue: #5bc8ff;
  --pr-blue-deep: #1e9be0;
  --pr-blue-soft: #7ad4ff;
}
/* Floating hero glyph tiles were `rgba(255,255,255,0.72)` — four white
   chips scattered over the constellation. */
html.nf-page .pr-float-core {
  background: rgba(12, 19, 33, 0.66);
  border-color: rgba(91, 200, 255, 0.22);
  box-shadow:
    inset 0 1px 0 rgba(233, 238, 249, 0.06),
    0 12px 32px rgba(0, 0, 0, 0.4);
}
/* The plan CTA was a blue gradient; ember is the site's buy action. */
html.nf-page .pr-cta--primary {
  background: var(--nf-ember);
  border-color: transparent;
  color: var(--nf-ember-ink);
}
html.nf-page .pr-cta--primary:hover { box-shadow: 0 6px 32px rgba(255, 106, 61, 0.4); }
/* `.pr-cta--inverse` is deliberately left alone: it lives inside the
   already-dark `--pr-night` bands, where a light button is correct. */

/* Quiet states, restated after the ember rules so a disabled or secondary
   control can never out-shout the live action next to it. */
html.nf-page .dl-btn.is-disabled {
  background: rgba(148, 170, 220, 0.07);
  border-color: transparent;
  color: var(--nf-dim);
}
html.nf-page .dl-btn--ghost {
  background: transparent;
  border-color: var(--nf-line-bright);
  color: var(--nf-star);
}
html.nf-page .dl-btn--ghost:hover {
  background: rgba(91, 200, 255, 0.06);
  border-color: var(--nf-sky);
}
html.nf-page .auth-submit[disabled] {
  background: rgba(148, 170, 220, 0.1);
  border-color: transparent;
  color: var(--nf-dim);
}

/* --- globals.css / _fluttergo-blog.css --------------------------------
   `.fg-hl` is the sitewide headline highlight, a hardcoded blue gradient
   clipped to text. The homepage sets its accent word in ember italic, so
   every other page's highlight follows it rather than staying brand-blue. */
html.nf-page .fg-hl {
  background: linear-gradient(115deg, #ff8a5c 0%, #ff6a3d 55%, #ff5a2a 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  font-style: italic;
  font-weight: 400;
}

/* ======================================================================
 * Body-scoped token reset
 * ----------------------------------------------------------------------
 * _fluttergo-home-match.css re-lights 18 tokens on `body.fgs`,
 * `body.fgs--clean-site` and `body.fgs--clean-landing` — a "clean white
 * stage" pass that lands BELOW `html.nf-page` in the cascade because a
 * body-scoped declaration is closer to the element it styles.
 *
 * `html.nf-page body` is (0,1,2) against their (0,1,1), so it wins on
 * specificity for every one of those classes without having to name them.
 * ==================================================================== */
html.nf-page body {
  --bg: #04060c;
  --bg-2: #0a101b;
  --paper: #0a101b;
  --surface: #0a101b;
  --surface-2: #0d1524;
  --surface-glass: rgba(12, 19, 33, 0.72);
  --surface-glass-strong: rgba(12, 19, 33, 0.88);
  --border: rgba(148, 170, 220, 0.16);
  --border-strong: rgba(148, 170, 220, 0.3);
  --ink: #e9eef9;
  --text: #e9eef9;
  --text-strong: #e9eef9;
  --text-muted: #94a0b6;
  --text-soft: #77839b;
  /* The light pass zeroed the hero wash; Nightfall wants its own faint
     sky bloom back so the static heroes aren't dead flat ink. */
  --hero-glow-1: rgba(30, 155, 224, 0.16);
  --hero-glow-2: rgba(255, 106, 61, 0.06);
  --hero-glow-3: transparent;
  --hero-aurora-opacity: 1;

  background: var(--nf-ink);
  color: var(--nf-star);
}

/* ======================================================================
 * !important counters
 * ----------------------------------------------------------------------
 * _fluttergo-home-match.css paints the "clean white stage" with
 * `background: #ffffff !important` on the body and on the full-bleed
 * `.fgs-bg` layer, and _fluttergo-features.css does the same for its
 * section stage. No amount of specificity beats !important, so these are
 * the only rules here that have to escalate. They are deliberately narrow:
 * one property, on the three selectors that actually carry the flag.
 * ==================================================================== */
html.nf-page body.fgs,
html.nf-page body.fgs--clean-site,
html.nf-page body.fgs--clean-landing {
  background: var(--nf-ink) !important;
  color: var(--nf-star);
}
/* The full-bleed stage sits above the constellation; letting it go
   transparent is what lets the field show through the page. */
html.nf-page body.fgs .fgs-bg,
html.nf-page body.fgs--clean-site .fgs-bg,
html.nf-page body.fgs--clean-landing .fgs-bg {
  background: transparent !important;
}

/* --- Opaque rgba-white surfaces ---------------------------------------
   These carry `rgba(255,255,255,0.92)` rather than a hex, so the surface
   sweep over the source sheets did not see them. Their sibling low-alpha
   whites (card shines, header sheens, gem highlights) are deliberately
   left alone — on ink those read as specular gloss, which is correct. */
html.nf-page :is(.fgs-prompt__box, .fgs-inbox__shell) {
  background: rgba(12, 19, 33, 0.88);
  border-color: var(--nf-line-bright);
}
/* Code-window filename sat at 4.03:1 — just under the 4.5 floor. */
html.nf-page .fgs-codewin__bar .fn { color: var(--nf-muted); }

/* --- One display face --------------------------------------------------
   `.fgs-h1` / `.fgs-h2` / `.fgs-h3` carry their own Plus Jakarta Sans at
   weight 750, which outranks the bare `h1, h2` rule above. The result was
   two display faces on the same page — a Jakarta hero over Fraunces card
   titles. These pull the classed headings onto the same face and weight as
   the homepage so the site has one voice. */
html.nf-page :is(.fgs-h1, .fgs-h2, .fgs-h3, .fgpm-hero-title) {
  font-family: var(--nf-display);
  font-weight: 300;
  font-optical-sizing: auto;
  letter-spacing: -0.01em;
  color: var(--nf-star);
  text-wrap: balance;
}
html.nf-page :is(.fgs-h1, .fgs-h2, .fgs-h3) em,
html.nf-page :is(.fgs-h1, .fgs-h2, .fgs-h3) .fg-hl {
  font-style: italic;
  font-weight: 400;
}

/* --- download.css -----------------------------------------------------
   The closing band's headline carries the family's one ember-italic phrase,
   and its label reads in the utility face like every other label of its kind
   on the site. `--mono` in the Atelier vocabulary is Albert Sans, so the
   label has to be told which face to use. */
html.nf-page .dl-cta-label { font-family: var(--nf-mono); color: var(--nf-dim); }
html.nf-page .dl-cta-title { font-family: var(--nf-display); font-weight: 300; color: var(--nf-star); }
html.nf-page .dl-cta-title em { font-style: italic; font-weight: 400; color: var(--nf-ember); }
html.nf-page .dl-cta-sub { color: var(--nf-muted); }
/* Primary in the band joins the ember CTA family; the ghost stays quiet. */
html.nf-page .dl-cta .dl-btn:not(.dl-btn--ghost) {
  background: var(--nf-ember);
  border-color: transparent;
  color: var(--nf-ember-ink);
}
html.nf-page .dl-cta .dl-btn:not(.dl-btn--ghost):hover { box-shadow: 0 6px 32px rgba(255, 106, 61, 0.4); }

/* --- blog.css (generated into _fluttergo-blog.css) --------------------
   The post CTA was `background: var(--coral); color: #fff` — with --coral
   remapped to sky that lands white-on-sky at 1.88:1. It joins the ember
   primary family like every other buy/start action on the site. */
html.nf-page .blog-cta-btn {
  background: var(--nf-ember);
  color: var(--nf-ember-ink);
  border-color: transparent;
}
html.nf-page .blog-cta-btn:hover { box-shadow: 0 6px 32px rgba(255, 106, 61, 0.4); }
/* The active category chip was `background: var(--ink)` with `color: var(--paper)`
   — a light-theme inversion. After the token flip that is a white pill with dark
   text, the one bright slab on an ink page, and it reads as a live CTA rather
   than a filter state. It joins the sky-tinted selected states the rest of the
   site uses (explore's "All", the pricing toggle, the homepage's active tile). */
html.nf-page .blog-filter-chip[aria-pressed='true'] {
  background: rgba(91, 200, 255, 0.14);
  border-color: rgba(91, 200, 255, 0.45);
  color: var(--nf-star);
}

/* ======================================================================
 * Cross-page consistency: one continuous field
 * ----------------------------------------------------------------------
 * The homepage hero is transparent — the fixed constellation runs behind
 * the whole document and radial glows sit on top of it. Every other page
 * painted its hero/stage bands with an opaque `var(--paper)`, which after
 * the token flip became a solid #0a101b slab: it hid the field and left a
 * hard horizontal seam where the band ended.
 *
 * These bands go transparent so the field is continuous top-to-bottom, and
 * pick up the homepage's aurora so a hero still reads as a distinct opening
 * rather than flat ink. Cards, plans and docs panels stay opaque on purpose
 * — they are surfaces that sit ON the field, not the field itself.
 * ==================================================================== */
html.nf-page :is(
  .pr-hero, .dl-hero, .blog-hero, .fgs-hero, .fgs-hero--clean,
  .pr-packs-stage, .pr-faq-stage, .pr-cta-stage
) {
  background: transparent;
  position: relative;
}
/* Aurora opening, lifted from .nf-hero::before on the homepage. */
html.nf-page :is(.pr-hero, .dl-hero, .blog-hero, .fgs-hero, .fgs-hero--clean)::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(52% 46% at 50% 4%, rgba(30, 155, 224, 0.17), transparent 70%),
    radial-gradient(38% 32% at 78% 82%, rgba(255, 106, 61, 0.06), transparent 70%);
}
/* Keep hero content above the glow. */
html.nf-page :is(.pr-hero, .dl-hero, .blog-hero, .fgs-hero, .fgs-hero--clean) > * {
  position: relative;
  z-index: 1;
}
/* The bands above went transparent, but each one still carried the light
   theme's 1px divider. With no slab left to end, those rules just draw a
   bare hairline across the constellation — which is the seam this section
   set out to remove, so the residual borders go with the backgrounds. */
html.nf-page :is(.pr-hero, .dl-hero, .blog-hero) { border-bottom: 0; }
html.nf-page :is(.pr-packs-stage, .pr-faq-stage, .pr-cta-stage) { border-top: 0; }

/* --- Pricing's two dark bands ------------------------------------------
 * `.pr-enterprise` and `.pr-cta-banner` were the light theme's one dark
 * surface: a `--pr-night` gradient slab that earned its place by contrast
 * against white. On the Nightfall field they are ink on ink — a slightly
 * bluer rectangle punched out of the constellation.
 *
 * They become glass instead: the field runs straight through, a hairline
 * holds the shape, and the two glow blobs drop to the hero aurora's sky +
 * ember strength so the box keeps interior light without going back to
 * being a slab.
 * ==================================================================== */
html.nf-page :is(.pr-enterprise, .pr-cta-banner) {
  background: transparent;
  border-color: var(--nf-line-bright);
}
/* The closing banner drops its frame with the rest of the family's closing
   bands: a centred column of copy inside a full-width rectangle left most of
   the box as empty edge. `.pr-enterprise` keeps its border — that one is a
   plan, a real surface with content across its whole width. */
html.nf-page .pr-cta-banner { border-color: transparent; }
html.nf-page :is(.pr-enterprise-glow--a, .pr-cta-glow--a) {
  background: radial-gradient(closest-side, rgba(30, 155, 224, 0.18), transparent 70%);
}
html.nf-page :is(.pr-enterprise-glow--b, .pr-cta-glow--b) {
  background: radial-gradient(closest-side, rgba(255, 106, 61, 0.07), transparent 70%);
}
/* `.pr-cta--inverse` kept its white fill because it sat on that dark band.
   With the band gone it is a white slab floating on the field, and a second
   primary competing with the ember one, so it takes the ghost treatment the
   download page already uses for secondary actions. */
html.nf-page .pr-enterprise .pr-cta--inverse {
  background: transparent;
  border: 1px solid var(--nf-line-bright);
  color: var(--nf-star);
}
html.nf-page .pr-enterprise .pr-cta--inverse:hover {
  background: rgba(91, 200, 255, 0.06);
  border-color: var(--nf-sky);
}

/* --- Selected states --------------------------------------------------
   The billing toggle's thumb was `var(--ink)` — after the flip a bright
   white slab with dark text, the one light-mode artefact left on pricing.
   Sky-tinted glass matches the "All" chip on explore and the active tile
   on the homepage. */
html.nf-page .pr-toggle-thumb {
  background: rgba(91, 200, 255, 0.16);
  border: 1px solid rgba(91, 200, 255, 0.45);
  box-shadow: 0 4px 18px rgba(91, 200, 255, 0.12);
}
html.nf-page .pr-toggle-btn.is-on { color: var(--nf-star); }
html.nf-page .pr-toggle-btn.is-on .pr-toggle-save {
  color: var(--nf-ink);
  background: var(--nf-sky);
}

/* "Most popular" plan flag — the last bright-blue gradient on pricing. */
html.nf-page .pr-plan-badge,
html.nf-page .pr-badge {
  background: var(--nf-sky);
  color: var(--nf-ink);
  border-color: transparent;
}


