/* ============================================================
   Couple v3 — site chrome (base layout only)
   ============================================================
   NOT part of the design-system port — colors_and_type.css, styles.css,
   and legal.css are lifted verbatim from couple-website-markup per the
   handoff mandate (README.md §3). This file supplies the small amount of
   base (non-responsive) nav/footer CHROME that the design reference only
   expressed as inline React styles (sections.jsx `Nav`) rather than real
   CSS classes — styles.css only carries the *responsive overrides* for
   `.nav-bar` / `.nav-list` / `.nav-hamburger` (e.g. the `@media (max-width:
   900px)` swap to the hamburger), not their resting state. Rebuilt here
   from those same inline values so the two layers agree at every
   breakpoint. Everything below reads exclusively from the ported design
   tokens (colors_and_type.css) — no new colors/sizes are invented.

   Batch 0 scope: enough to prove the pipeline renders correctly (nav +
   footer chrome on the placeholder home route). Page-level layout work
   happens in Batch 1 (01_ARCHITECTURE.md §1, 06_BATCH_PLAN.md Batch 1). */

/* NB: no global `body { padding-top }` here. Nav clearance for the fixed
   bar is owned per-page by whichever hero shell each page's first section
   uses — `.hero-shell-lavender` (styles.css) and `.hero-shell-plain`
   (styles.css) both document their own padding-top's 96px floor as the
   nav-clearance value (colors_and_type.css's `--rhythm-hero-top` comment).
   A global body rule here would double that clearance on every page that
   already has one of those heroes (all of them), pushing every hero
   section further down than the design intends — this was found and
   fixed as part of the nav/hero polish pass. If a future page ever lands
   under `layout/base.php` without either hero shell, it needs its own
   nav-clearance padding-top, not a revived global rule here. */

.nav-bar {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  width: calc(100% - 30px);
  max-width: 1440px;
  height: 64px;
  padding: 0 48px;
  border-radius: var(--r-card);
  background: var(--white);
  box-shadow: var(--shadow-card);
  display: flex;
  align-items: center;
  font: var(--type-body-sm);
  color: var(--ink-700);
  z-index: 20;
}

.nav-logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

.nav-list {
  list-style: none;
  margin: 0 0 0 auto;
  padding: 0;
  display: flex;
  align-items: center;
  gap: 28px;
}

.nav-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  padding: 0 24px;
  border-radius: var(--r-pill);
  background: var(--couple-purple);
  color: var(--white) !important;
  font: var(--type-button);
}
.nav-cta:hover {
  background: var(--couple-purple-hover);
  color: var(--white) !important;
  text-decoration: none !important;
}

.nav-hamburger {
  display: none;
}

@media (max-width: 900px) {
  .nav-cta {
    display: none;
  }
}

/* ---------- Nav dropdowns (Events, Account) ----------
   Pure CSS: `.nav-item` reveals its `.nav-dropdown-panel` on hover/focus
   (no JS needed — matches the FAQ accordion / Plans term toggle's
   no-JS-progressive-enhancement principle). Structural values (top:100%,
   +-16 alignment, paddingTop 16, minWidth 200) are ported from
   `sections.jsx`'s `NavItem`; `.nav-sublink`'s hover treatment already
   ships in styles.css's verbatim design-system port. */
.nav-item {
  position: relative;
  list-style: none;
  height: 64px;
  display: flex;
  align-items: center;
}
.nav-spacer {
  list-style: none;
  width: 12px;
}
.nav-dropdown-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: var(--type-nav);
  color: var(--ink-700);
  text-decoration: none;
  padding: 8px 0;
  background: none;
  border: 0;
  cursor: pointer;
}
.nav-account-trigger {
  padding: 0;
  width: 44px;
  height: 44px;
  justify-content: center;
}
.nav-dropdown-panel {
  position: absolute;
  top: 100%;
  left: -16px;
  padding-top: 16px;
  z-index: 30;
  opacity: 0;
  visibility: hidden;
  transform: translateY(4px);
  transition: opacity 140ms ease-out, transform 140ms ease-out, visibility 140ms;
}
.nav-dropdown-panel--right {
  left: auto;
  right: -16px;
}
.nav-item:hover .nav-dropdown-panel,
.nav-item:focus-within .nav-dropdown-panel {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
.nav-dropdown-menu {
  margin: 0;
  padding: 8px 0;
  background: var(--white);
  border-radius: var(--r-card-sm);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  list-style: none;
  min-width: 200px;
}
.nav-sublink {
  display: block;
  padding: 12px 24px;
  font: var(--type-nav);
  color: var(--ink-700);
  text-decoration: none;
  white-space: nowrap;
}

/* ---------- Member toolbar icons (alert bell, messages envelope) ----------
   Sub-batch 5.1's member shell. `sections.jsx` expresses these as inline
   React styles on the `AlertBell` / `MessagesEnvelope` anchors (44px grid-
   centered hit area, pill radius); rebuilt here as real classes for the same
   reason every other nav rule lives in this file. The red indicator dot the
   reference draws on both was deliberately NOT ported at 5.1 — no unread or
   alert source existed yet, and an always-on dot would have been a lie (see
   templates/partials/nav.php's header comment). It lands below, with the
   sources that justify it. */
.nav-icon-link {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: var(--r-pill);
  color: var(--ink-700);
  text-decoration: none;
  position: relative;
}
.nav-icon-link:hover {
  background: var(--lavender-hover);
  text-decoration: none;
}
/* The bell is a <button> since FF-1 follow-up #2 (owner, 2026-08-20) — it
   opens a panel, it does not navigate. These are the UA button styles it has
   to shed to sit in a row of anchors; `.nav-account-trigger` above does the
   same for the account menu's trigger. */
.nav-alerts-trigger {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  cursor: pointer;
  color: var(--ink-700);
}
/* Click-to-open, the second opener beside :hover/:focus-within. Needed because
   `.nav-list` is hidden only at <=640 while the hamburger's Alerts section
   appears only there, so 641-1024 on touch can neither hover the bell nor
   reach the mobile list. nav.js clears `.is-open` on mouseleave, so on a
   pointer device the panel still behaves exactly like the hover-only menus. */
.nav-item.is-open .nav-dropdown-panel {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
/* The bell turns red only when there is an alert (owner decision 2026-08-11 —
   see nav.php's comment at the bell for why this deviates from the handoff's
   always-red glyph). Applied by nav.js to the anchor, so `currentColor` in the
   SVG carries it; the dot below renders at the same moment. */
.nav-icon-link--alert {
  color: var(--couple-red);
}
/* The indicator dot — the design's documented recipe (10px, --couple-red,
   2px white ring), and since FF-1 (2026-08-20) the ONLY signal on both
   toolbar icons. It replaces 5.2's count badge and 5.3's number outright
   (D2/D11): the bell's badge summed six counts into one figure that matched
   no single row in the panel that now sits below it, so the numbers moved
   into the alert row copy, where a count says something. One recipe, four
   placements — bell, envelope, hamburger, mobile Messages row — so every
   affordance signals identically.

   `[hidden]` is spelled out rather than left to the UA default (Lesson 2):
   .nav-mobile-dot below carries an author `display`, and a dot that cannot be
   hidden is worse than no dot at all. */
.nav-alert-dot {
  position: absolute;
  top: 5px;
  right: 3px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--couple-red);
  box-shadow: 0 0 0 2px var(--white);
}
.nav-alert-dot[hidden] {
  display: none;
}
/* The mobile Messages row's dot sits INLINE after the label, not pinned to a
   corner — the only placement that is not an overlay. */
.nav-mobile-dot {
  position: static;
  display: inline-block;
  margin-left: 8px;
  box-shadow: none;
  width: 8px;
  height: 8px;
}
/* The hamburger's collapsed dot. `.nav-hamburger` is `display: grid` from
   styles.css's responsive layer (:469), which gives it no positioning context
   of its own — this supplies it, in site.css rather than in the verbatim
   design-system port. */
.nav-hamburger {
  position: relative;
}
.nav-hamburger-dot {
  top: -2px;
  right: -2px;
}

/* ---------- The alert panel (FF-1, 2026-08-20) ----------
   The design's `AlertBell` hover card (`sections.jsx:653`), rebuilt as real
   classes from its inline React styles — width 460, `--r-card-sm`,
   `--shadow-card`, 8px-0 list padding, 12px-24px rows at 500 18px/1.4, and
   the 8px red row dot with its 8px top offset. Reveal is the SAME
   `.nav-item:hover .nav-dropdown-panel` rule the Events/Account menus use, so
   the panel needs no JS to open; nav.js only fills it and drops the `hidden`
   attribute once there is something to show (D10 — with nothing waiting,
   hovering the bell reveals nothing at all). */
.nav-alerts-panel[hidden] {
  display: none;
}
.nav-alerts-menu {
  margin: 0;
  padding: 8px 0;
  width: 460px;
  max-width: calc(100vw - 32px);
  background: var(--white);
  border-radius: var(--r-card-sm);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  list-style: none;
}
.nav-alert-link {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 12px 24px;
  font: 500 18px/1.4 var(--font-body);
  color: var(--ink-700);
  text-decoration: none;
  transition: color 140ms ease, background 140ms ease;
}
.nav-alert-link:hover {
  background: var(--lavender-hover);
  color: var(--couple-purple);
  text-decoration: none;
}
.nav-alert-row-dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--couple-red);
  margin-top: 8px;
}

/* ---------- The mobile Alerts section (FF-1) ----------
   The Design System Guide's mobile-alerts-badge ruling: the desktop bell is a
   hover panel and touch cannot hover, so the same alerts expand inline in the
   menu as deep-linking rows, mirroring the menu's own Events/Account group
   pattern. Values from `sections.jsx`'s mobile branch (:985-1027) — the 32px
   tinted bell circle, 44px row targets, 500 16px/1.4 copy, 7px dot offset. */
.nav-mobile-alerts[hidden] {
  display: none;
}
.nav-mobile-alerts-label {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  font: var(--type-button);
  color: var(--couple-red);
}
.nav-mobile-alerts-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(230, 62, 41, 0.1);
  display: grid;
  place-items: center;
}
.nav-mobile-alerts-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
}
.nav-mobile-alert-link {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  min-height: 44px;
  padding: 8px 0;
  font: 500 16px/1.4 var(--font-body);
  color: var(--ink-700);
  text-decoration: none;
}
.nav-mobile-alert-link .nav-alert-row-dot {
  margin-top: 7px;
}

/* ---------- Top banner ribbon (SC-2, 2026-08-24) ----------
   THE BASE RULE THIS CLASS NEVER HAD. `templates/partials/banner.php` has
   emitted `<a class="top-banner">` since Batch 1, but v3 loaded no
   `.top-banner` base rule at all — only styles.css:70-74's full/short
   visibility swap and styles.css:381-387's <=640 override. The plate's own
   styling lives INLINE in the reference (`sections.jsx:784-802`), which is
   Lesson 2's exact shape, so until now the campaign ribbon rendered as an
   unstyled inline link on every host. Rebuilt here from those inline values
   so the base layer and styles.css's responsive override agree at every
   breakpoint — the same note this file's own header makes about `.nav-bar`.

   AUTO-HEIGHT, deliberately (`Design System Guide.html:1366`, the Mobile
   Review S1 ruling): min-height 44 and it GROWS if the copy wraps. Nothing
   may assume 44px — see the nav offset below, which measures.

   `margin-inline: calc(-1 * var(--shell-overhang))` is the reference's own
   Plate Width ruling: the ribbon sits flush atop the lavender hero plate, so
   it carries the same overhang. Content is centered, so nothing moves —
   only the plate extends. */
.top-banner {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  padding: 10px 64px;
  background: var(--couple-purple);
  color: var(--couple-yellow);
  font: 700 18px/1.2 var(--font-body);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  margin-inline: calc(-1 * var(--shell-overhang));
  position: relative;
  z-index: 10;
}
.top-banner:hover {
  color: var(--couple-yellow);
  text-decoration: none;
}

/* Blocked/suspended account (FF-6, 2026-08-24). Legacy's plate is
   `.homePageBanner.acctSpsdBkg` — full width, centered, uppercase, white on
   `--blocked: #414141` (`Couple/css/couple_site.css:3,1327`). v3 keeps the
   dark-grey read the owner asked for but takes it from the DESIGN TOKEN
   `--ink-600` (#444444), one shade off legacy's value, rather than inventing
   a colour this system does not have (FF-6-D4, owner 2026-08-24). Type goes
   white — the plate's own `--couple-yellow` is the brand's celebratory
   accent and is wrong on a suspension notice. */
.top-banner--blocked {
  background: var(--ink-600);
  color: var(--white);
}
.top-banner--blocked:hover {
  color: var(--white);
}

/* Discount offer (FF-7, 2026-08-25). NO colour variant: the discount banner
   is the brand's celebratory plate — purple with yellow type — so it inherits
   `.top-banner` whole and adds nothing but the affordance. Legacy's is the
   same undecorated `.homePageBanner` the campaign ribbon uses
   (`Couple/index.php:252`), with no modifier class of its own, so matching it
   means adding no colour rather than inventing one (the standing rule this
   file's own header states, and FF-6-D4's precedent).

   This is the slot's ONLY interactive plate — the others are a mailto, a
   note, and a dead `href="#"` — so it is the only one that earns a pointer
   and a pressed state. `.is-busy` is set by nav.js's shared
   `[data-cart-add]` handler while the POST is in flight; it must not look
   clickable twice. Height stays auto: admin-authored copy wraps, and
   `nav.js` measures the plate (SC-2). */
.top-banner--discount {
  cursor: pointer;
}
.top-banner--discount.is-busy {
  cursor: default;
  opacity: 0.7;
}

/* ---------- Nav clearance for the ribbon (SC-2, 2026-08-24) ----------
   `.nav-bar` is fixed at `top: 16px`, so before this a rendered banner sat
   UNDERNEATH it — the defect the owner reported against the referred-user
   nudge, and one the campaign ribbon shared. `banner.php:6-9` recorded the
   offset as deliberately deferred ("the static nav does not yet shift down
   to make room for the ribbon"); this is that deferral closed.

   PROGRESSIVE ENHANCEMENT, not JS-only (SC-2-D2, owner 2026-08-24). The
   reference measures the ribbon and does nothing without JS
   (`sections.jsx:843-866`); v3's nav principle is that chrome works with JS
   off (`nav.php`'s docblock — CSS-only dropdowns, and the hamburger is the
   one deliberate exception). So the offset is CSS-first:
   `body[data-banner]` is stamped by the layout whenever the banner partial
   rendered anything at all, and `--banner-h` carries a first-paint estimate
   that matches the plate's own min-height. `nav.js` then measures the real
   rendered height and rewrites `--banner-h`, which is what makes WRAPPED
   copy safe — the estimate is only ever right for single-line copy.

   The variable is written on `<body>` (not `:root`) so nav.js touches one
   element and the CSS below reads it through the cascade. */
body[data-banner] {
  --banner-h: 44px;
}
@media (max-width: 640px) {
  /* styles.css:381-387 drops the ribbon to 14/1.4 with 12px padding at this
     width; two lines of that is the realistic worst case for a first paint,
     and nav.js corrects it either way. */
  body[data-banner] {
    --banner-h: 60px;
  }
}
body[data-banner] .nav-bar {
  top: calc(16px + var(--banner-h, 0px));
}
/* ---------- Impersonation + banner stacking (SC-2 walkthrough fix, 2026-08-24) ----------
   Impersonation and a banner can be true at once — a blocked member viewed by an
   admin is exactly the case FF-6-D5 rules IN. `.admin-view-bar` is fixed at
   `top: 0` above everything (z-index 25), so the banner must START below it and
   the nav must clear BOTH.

   **THIS WAS FIRST WRITTEN AS A STATIC TWO-STEP LADDER (44px, 72px at <=640) AND
   THAT WAS WRONG.** It was derived from `.nav-bar--admin-view`'s own 60px/88px
   values, on the assumption they encoded the bar's height plus the nav's 16px
   resting gap. The walkthrough found the hole (2026-08-24, owner: *"when
   impersonating on a device/browser width less than 1100px, the banner is covered
   by the impersonation module/tab"*): `.admin-view-bar-inner` is `display: flex;
   flex-wrap: wrap`, so the bar is AUTO-HEIGHT and grows a row at a time as its
   four items (label, member name, note, Stop button) wrap — which begins far
   above 640. Any fixed number is wrong at some width, and the two-step ladder was
   wrong across a ~460px band of them.

   So the bar is MEASURED, exactly as the banner already is (`nav.js`) — same
   mechanism, same reason: both plates are auto-height by construction.

   The fallbacks below are deliberately the OLD ladder's exact values, so a JS-off
   admin sees precisely what shipped before this fix and nothing moves for anyone
   else: `--admin-bar-h: 44px` + the 16px gap reproduces `top: 60px`, and 72px +
   16px reproduces `top: 88px`. The measurement only ever corrects them toward the
   truth. */
body[data-admin-view] {
  --admin-bar-h: 44px;
}
@media (max-width: 640px) {
  body[data-admin-view] {
    --admin-bar-h: 72px;
  }
}
body[data-admin-view] .top-banner {
  margin-top: var(--admin-bar-h, 44px);
}
/* Supersedes the hardcoded `top: 60px` / `88px` in the admin-bar block below,
   which carried the same auto-height bug and had simply never been noticed
   without a banner beneath it to make the overlap obvious. 16px is the nav's own
   resting gap. */
.nav-bar--admin-view {
  top: calc(var(--admin-bar-h, 44px) + 16px);
}
body[data-banner] .nav-bar--admin-view {
  top: calc(var(--admin-bar-h, 44px) + 16px + var(--banner-h, 0px));
}


/* ---------- Admin impersonation bar (sub-batch 5.1) ----------
   v3's replacement for legacy `dashboard/_inc_adminView.php::minHeader()`,
   which renders a fixed cyan "COUPLE ADMIN" plate with its own menu and then
   dead-links the entire page with `a { pointer-events: none }`. v3 keeps the
   *signal* — an unmistakable, always-visible band saying whose account is on
   screen — and drops the link-disabling, because the guarantee that matters
   is the server-side refusal (ProfileController::refuseIfImpersonated()), not
   a CSS rule that any client can ignore.

   Layering: 25 — above the fixed nav's 20 so it can never be covered, and
   below the overlay tier the nav-layering ruling reserves (scrim 40,
   savebar 60, toast 70, dialogs 1000). The nav clears it by riding to
   `calc(var(--admin-bar-h) + 16px)` — MEASURED, not the fixed 60px this said
   until the 2026-08-24 walkthrough, because the bar wraps and grows.

   Uses --couple-red, the brand's single attention colour, as type and
   border on a normal surface rather than a saturated plate of its own (the
   moderation-notice ruling's "one red, and only for errors" treatment). */
.admin-view-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 25;
  background: var(--white);
  border-bottom: 2px solid var(--couple-red);
  box-shadow: var(--shadow-subtle);
}
.admin-view-bar-inner {
  max-width: 1440px;
  margin: 0 auto;
  padding: 8px 24px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  font: 500 14px/1.3 var(--font-body);
  color: var(--ink-700);
}
.admin-view-bar-label {
  font: 700 14px/1.3 var(--font-body);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--couple-red);
}
.admin-view-bar-target {
  font-weight: 700;
}
.admin-view-bar-note {
  color: var(--ink-500);
}
.admin-view-bar-form {
  margin: 0 0 0 auto;
}
.admin-view-bar-stop {
  min-height: 32px;
  padding: 0 16px;
  border: 1px solid var(--couple-red);
  border-radius: var(--r-pill);
  background: var(--white);
  color: var(--couple-red);
  font: var(--type-button);
  cursor: pointer;
}
.admin-view-bar-stop:hover {
  background: var(--couple-red);
  color: var(--white);
}
/* `.nav-bar--admin-view`'s own `top` now lives with the banner-clearance
   block above, as `calc(var(--admin-bar-h) + 16px)` — one source of truth,
   because the hardcoded 60px/88px pair here assumed a fixed-height admin bar
   that `flex-wrap: wrap` never guaranteed (walkthrough, 2026-08-24). The
   fallbacks there reproduce these two values exactly. */
@media (max-width: 640px) {
  .admin-view-bar-form {
    margin-left: 0;
  }
}

/* ---------- Mobile menu sheet ----------
   Full-screen takeover (Modal Takeover ruling) opened by `.nav-hamburger`;
   real open/close + body-scroll-lock state lives in public/assets/js/nav.js
   (no CSS-only equivalent, unlike the dropdowns above). `.mobile-menu-panel`
   itself already carries a radius:0 override at <=640 in styles.css; the
   base rule below (also radius:0) matches the reference's unconditional
   value at every width the sheet can appear. */
.nav-mobile-backdrop {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--scrim-dialog);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  display: flex;
  flex-direction: column;
}
.nav-mobile-backdrop[hidden] {
  display: none;
}
.mobile-menu-panel {
  background: var(--white);
  border-radius: 0;
  padding: 20px 24px calc(24px + env(safe-area-inset-bottom));
  flex: 1 1 auto;
  min-height: 100dvh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.nav-mobile-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 20px;
}
.nav-mobile-close {
  width: 44px;
  height: 44px;
  border-radius: var(--r-pill);
  border: 0;
  background: var(--lavender-hover);
  color: var(--ink-700);
  cursor: pointer;
  display: grid;
  place-items: center;
}
.nav-mobile-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.nav-mobile-group {
  border-bottom: 1px solid var(--ink-100);
  padding: 4px 0;
}
.nav-mobile-group-link,
.nav-mobile-group-label {
  display: flex;
  align-items: center;
  min-height: 40px;
  font: var(--type-button);
  color: var(--fg-heading);
  text-decoration: none;
}
.nav-mobile-sublist {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
}
.nav-mobile-sublist a {
  display: flex;
  align-items: center;
  min-height: 44px;
  font: 500 16px var(--font-body);
  color: var(--ink-500);
  text-decoration: none;
}
@media (min-width: 901px) {
  .nav-mobile-backdrop {
    display: none !important;
  }
}

/* ---------- Footer chrome not covered by the base .footer-shell rules
   (piped-link separators use JS to hide at line wraps in the reference;
   v3's static placeholder just renders plain separators). ---------- */

/* The footer plate capped at the design's 1440 content box, centered
   (SC-5, found 2026-08-27 at FF-11's walkthrough — NOT an FF-11
   regression). The design renders <Footer/> INSIDE `.page` (styles.css'
   `.page { max-width: 1440px; margin: 0 auto }`), so its `.footer-shell`
   overhang (`margin-inline: calc(-1 * var(--shell-overhang))`) pokes the
   plate out to 1440 + 2×overhang, CENTERED, and the rounded top corners
   always sit inside the viewport. v3's `layout/base` renders the footer as
   a direct <body> child, so uncapped the same overhang made the plate
   viewport+80px wide and `body { overflow-x: clip }` cropped the corners
   off-screen on any window wider than ~1440 — present since the Plate
   Width ruling entered the synced design CSS. Inert at ≤1440 viewports
   (the plate is viewport-wide there either way, overhang 0). */
.footer-cap {
  width: 100%;
  max-width: 1440px;
  margin-inline: auto;
}

.footer-sep {
  opacity: 0.6;
}
.footer-links .footer-sep {
  opacity: 0.6;
}

/* ---------- "Live now" CTA badge (cta-scenarios.jsx) ----------
   The reference injects this rule into <head> via JS at runtime
   (`injectLiveStyle()`); ported to a real static rule here since v3 has no
   such runtime injection step. Used by `render_live_badge()`
   (src/Support/helpers.php) on the Home hero + ClosingCTA whenever
   `CtaService::forVisitor()`'s `live` flag is set.
   ✅ That flag went LIVE with FF-10 (2026-08-25) — the `join-live` scenario.
   This comment used to end "always false this batch, but structurally wired",
   which was true from 2.7 until then and is now not; corrected here, with the
   change that made it false, rather than left for a later item's copy list to
   pay off (the FF-4 lesson). These rules are unchanged — Lesson 2 was already
   paid in 2.7 — and are now smoke-pinned, since a rule nothing checks is a
   rule that can be deleted by someone who reads it as dead. */
.cta-live {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font: 700 14px/1 var(--font-body);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--couple-red);
}
.cta-live-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--couple-red);
  box-shadow: 0 0 0 4px rgba(230, 62, 41, 0.16);
  animation: ctaLivePulse 1.3s ease-in-out infinite;
}
@keyframes ctaLivePulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .45; transform: scale(.72); }
}
@media (prefers-reduced-motion: reduce) {
  .cta-live-dot { animation: none; }
}
