/**
 * base.css — reset, layout shell, accessibility, background effects.
 */

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* clip, not hidden: never a scroll container, so sticky still works. No
   scroll-behavior: smooth — Lenis (js/scroll.js) is the smoothing, and the
   two fight each other. */
html { overflow-x: clip; }
/* a stopped Lenis would clip the root (lenis.css); the overlays already lock
   <body> and pad for the scrollbar themselves, so keep the root as it was */
html.lenis.lenis-stopped:not(.lenis-autoToggle) { overflow: visible; overflow-x: clip; }

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
  transition: background .25s var(--ease-out-quart), color .25s var(--ease-out-quart);
  position: relative;
}

button, input, textarea, select { font-family: var(--font); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- accessibility ---------- */

/* Visible keyboard focus everywhere. Mouse users don't see it. */
:focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-radius: 4px;
}

.skip-link {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 10000;
  background: var(--blue);
  color: var(--on-blue);
  padding: 10px 18px;
  border-radius: var(--radius-pill);
  font-size: 13px;
  font-weight: 700;
  text-decoration: none;
  transition: top var(--dur-fast) var(--ease-out-quart);
}
.skip-link:focus { top: 12px; }

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

/* ---------- layout ---------- */

.wrap {
  max-width: var(--wrap-max);
  margin: 0 auto;
  padding: 0 var(--wrap-pad) 90px;
}

/* One slim row (.topbar-main): the brand, the section switch with the
   chapter rail (the visit's progress) beside it, the actions right. The
   rail's column keeps its space in PLANT, where the rail is hidden, so
   the switch never moves. At ≤900px the switch and rail drop to row 2. */
.topbar {
  display: flex; flex-direction: column;
  padding: 10px var(--wrap-pad) 0;
  max-width: var(--wrap-max);
  margin: 0 auto 34px;
  position: sticky; top: 0; z-index: 500;
  pointer-events: none;               /* the gutter around the bar lets clicks through */
}
.topbar > * { pointer-events: auto; }

.topbar-main {
  position: relative;                 /* the batch button's travel runs across it */
  display: grid; grid-template-columns: auto auto minmax(0, 1fr) auto;
  grid-template-areas: "brand switch rail actions";
  align-items: center; column-gap: 18px;
  min-height: 60px; padding: 8px 0;
}

.brand-group { grid-area: brand; justify-self: start; display: flex; align-items: center; gap: 10px; min-width: 0; }

/* The supplied original FrontWell wordmark, shared by the header and footer. */
.brand { display: flex; align-items: center; text-decoration: none; border-radius: 6px; }
.brand:focus-visible { outline: 2px solid var(--blue); outline-offset: 4px; }
.brand-logo { display: block; height: 36px; width: auto; }

.brand-sub { font-size: 12.5px; color: var(--text-muted); font-weight: 500; }

.topbar-right { grid-area: actions; justify-self: end; display: flex; align-items: center; gap: 10px; }

/* the section switch: a segmented control, the current section raised */
.section-switch {
  grid-area: switch; justify-self: start;
  display: inline-flex; align-items: center; gap: 2px; padding: 4px;
  border-radius: var(--radius-pill); background: var(--tile);
  box-shadow: inset 0 0 0 1px var(--border);
}
.switch-tab {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 7px 16px; border-radius: var(--radius-pill);
  font-size: 13px; font-weight: 600; color: var(--text-muted); text-decoration: none; white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out-quart), background var(--dur-fast) var(--ease-out-quart), box-shadow var(--dur-fast) var(--ease-out-quart);
}
.switch-tab svg { flex: none; opacity: .8; }
.switch-tab:hover { color: var(--navy); }
.switch-tab[aria-current="page"] {
  background: var(--surface); color: var(--blue); font-weight: 700;
  box-shadow: 0 1px 2px rgba(var(--fw-s80-rgb), .08), 0 2px 8px -2px rgba(var(--fw-s80-rgb), .12), inset 0 0 0 1px var(--border);
}
.switch-tab[aria-current="page"] svg { opacity: 1; }
.switch-tab:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* the theme: a quiet round icon button; Book a visit stays the one filled action */
.topbar-right .theme-toggle {
  width: 36px; height: 36px; padding: 0; justify-content: center; border-radius: 50%;
}
/* it shows the mode it switches to: the moon in light, the sun in dark */
.theme-toggle svg { display: block; }
#theme-icon-sun { display: none; }
[data-theme="dark"] #theme-icon-sun { display: block; }
[data-theme="dark"] #theme-icon-moon { display: none; }
.topbar-right .topbar-cta { display: inline-flex; align-items: center; gap: 8px; padding: 10px 18px; font-size: 13px; }
/* the header's call to action wears the website's "Contact Us": its blue, deep type */
.topbar-right .topbar-cta { background: var(--steel); color: var(--steel-ink); }
.topbar-cta .cta-arrow { transition: translate var(--dur-fast) var(--ease-out-quart); }
.topbar-cta:hover .cta-arrow { translate: 3px 0; }


@media (max-width: 900px) {
  /* brand and actions share row 1; the switch and the rail share row 2 */
  .topbar-main {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "brand brand actions" "switch rail rail";
    column-gap: 14px; row-gap: 8px; min-height: 0; padding: 8px 0;
  }
}
/* the bar floats: a glass capsule a little wider than the content, so
   its contents stay on the page's grid. It is clear at the top of the
   page and frosts over as content passes under it — paint only, its box
   never changes (one fixed padding: condensing would animate layout). */
.topbar > .topbar-main {
  margin-inline: -14px; padding: 8px 8px 8px 14px;
  border-radius: 22px; border: 1px solid transparent;
  transition: background-color .35s var(--ease-out-quart), border-color .35s var(--ease-out-quart),
    box-shadow .35s var(--ease-out-quart), backdrop-filter .35s var(--ease-out-quart);
}
.topbar.scrolled > .topbar-main {
  background-color: color-mix(in srgb, var(--surface) 74%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.5); backdrop-filter: blur(18px) saturate(1.5);
  border-color: color-mix(in srgb, var(--border) 85%, transparent);
  box-shadow: var(--surface-hi), 0 14px 36px -16px rgba(var(--fw-s70-rgb), .28);
}

@media (max-width: 560px) {
  .switch-tab { padding: 7px 11px; gap: 6px; }
  .switch-long { display: none; }
  /* four tabs and the actions no longer share columns: the switch takes
     its own full row (tabs share it evenly), the rail the row under it */
  .topbar-main {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: "brand actions" "switch switch" "rail rail";
  }
  .section-switch { justify-self: stretch; }
  .switch-tab { flex: 1 1 0; min-width: 0; }
}
@media (max-width: 400px) {
  .switch-tab svg { display: none; }
}

/* ---------- batch "back to landing" button + header travel animation ---------- */

.batch-home-btn {
  position: relative; width: 28px; height: 28px; flex-shrink: 0;
  border-radius: 50%; border: 1px solid var(--border); background: var(--surface);
  color: var(--blue); display: flex; align-items: center; justify-content: center;
  cursor: pointer; transition: border-color var(--dur-fast) var(--ease-out-quart), transform var(--dur-fast) var(--ease-out-quart), box-shadow var(--dur-fast) var(--ease-out-quart);
}
.batch-home-btn:hover, .batch-home-btn:focus-visible {
  border-color: var(--blue); transform: translateY(-1px);
  box-shadow: 0 4px 14px -4px var(--blue-dim);
}
.batch-home-btn:active { transform: translateY(0); }
.batch-home-btn.travelling { pointer-events: none; border-color: var(--blue); }

.batch-home-pulse {
  position: absolute; inset: -4px; border-radius: 50%;
  border: 1.5px solid var(--blue); opacity: 0;
  animation: batch-home-pulse 2.6s var(--ease-out-quart) infinite;
}
@keyframes batch-home-pulse {
  0%   { transform: scale(.78); opacity: .55; }
  70%  { transform: scale(1.35); opacity: 0; }
  100% { transform: scale(1.35); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .batch-home-pulse { display: none; } }

/* full-width overlay the button's click animation runs inside — sits above
   the topbar content, invisible/inert until a click fills it in via JS */
.header-batch-track {
  position: absolute; left: 0; right: 0; top: 50%; height: 0;
  pointer-events: none; z-index: 1;
}
.header-batch-checkpoint {
  position: absolute; top: 50%; width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px;
  border-radius: 50%; background: var(--border); transition: background var(--dur-fast) var(--ease-out-quart), box-shadow var(--dur-fast) var(--ease-out-quart);
}
.header-batch-checkpoint.lit { background: var(--blue); box-shadow: 0 0 10px 1px var(--blue-dim); }
.header-batch-marker {
  position: absolute; top: 50%; width: 8px; height: 8px; margin: -4px 0 0 -4px;
  border-radius: 50%; background: var(--blue); box-shadow: 0 0 14px 2px var(--blue-dim);
  transition: transform var(--batch-travel-ms, 900ms) var(--ease-in-out-smooth);
}

/* the footer: who we are, what we build for, every section — then the line */
.site-footer {
  max-width: none;
  margin: 0;
  padding: clamp(40px, 5vw, 64px) max(var(--wrap-pad), calc((100% - var(--wrap-max)) / 2 + var(--wrap-pad))) 40px;
  background: linear-gradient(180deg, var(--fw-s80) 0%, var(--fw-s90) 100%);
  font-size: 12.5px; color: var(--text-faint);
}
.site-footer a { color: var(--text-muted); text-decoration: none; transition: color var(--dur-fast) var(--ease-out-quart); }
.site-footer a:hover { color: var(--blue); }
.footer-top {
  display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1.9fr); gap: 40px;
  padding: 48px 0 40px; border-top: 1px solid var(--border);
}
.footer-top:empty { display: none; }
.footer-name { display: flex; margin: 0 0 16px; }
.footer-name .brand-logo { height: 44px; }
.footer-line { margin: 0 0 18px; max-width: 38ch; font-size: 13.5px; line-height: 1.6; color: var(--text-muted); }
.footer-badges { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
.footer-badges li {
  padding: 4px 9px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface);
  font-size: 10.5px; letter-spacing: .02em; color: var(--navy);
}
.footer-nav { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 24px; }
.footer-col h2 { margin: 0 0 12px; font-size: 11.5px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase; }
.footer-col h2 a { color: var(--navy-deep); }
.footer-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.footer-col li a { font-size: 13px; }
.footer-base {
  display: flex; justify-content: space-between; align-items: center; gap: 14px; flex-wrap: wrap;
  padding-top: 20px; border-top: 1px solid var(--border);
}
@media (max-width: 820px) {
  .footer-top { grid-template-columns: minmax(0, 1fr); gap: 28px; }
}
@media (max-width: 520px) {
  .footer-nav { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* ---------- typography ---------- */

/* the website's headings: Manrope, and its round full stop */
:where(h1, h2, h3, h4,
  .chapter-title, .lab-hero-title, .home-title, .door-title, .st-title, .st-panel-title, .gd-title,
  .twin-title, .handoff-title, .card-name, .realm-index-title, .batch-headline, .detail-about-name,
  .hood-title, .plan-title, .agenda-title, .orbit-story-title, .recap-title, .pass-title, .hosts-title,
  .event-cap-text, .event-view-text, .cw-title, .tw-card-title, .wb-title, .ebr-title, .stop-title, .footer-name) {
  font-family: var(--font-display);
}

h1 {
  font-size: clamp(30px, 4vw, 42px);
  font-weight: 800; line-height: 1.14;
  color: var(--navy-deep);
  max-width: 560px;
  margin-bottom: 14px;
}

.eyebrow {
  font-size: 12.5px; font-weight: 700; color: var(--blue);
  margin-bottom: 12px; letter-spacing: .02em;
}

.lead { color: var(--text-muted); font-size: 15.5px; line-height: 1.65; max-width: 420px; }

.story-h1 {
  font-size: clamp(26px, 3.4vw, 34px);
  font-weight: 800; line-height: 1.18;
  color: var(--navy-deep); max-width: 600px; margin-bottom: 14px;
}
.story-lead { color: var(--text-muted); font-size: 15px; line-height: 1.65; max-width: 600px; margin-bottom: 36px; }

.section-heading {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-bottom: 18px; flex-wrap: wrap; gap: 8px;
}
.section-heading h2 { font-size: var(--fs-h3); font-weight: 800; letter-spacing: -0.02em; line-height: 1.2; color: var(--navy-deep); }
.section-heading .hint { font-size: 12.5px; color: var(--text-faint); font-weight: 500; }

/* ---------- first paint: the arrival ----------
   One short, orchestrated entrance — a choreographed arrival, never a
   splash — on the motion tokens, settled in well under a second:
     1. the frame: the top bar, then the chapter rail, drop in  0 → --dur-base
     2. the hero: eyebrow, the title line by line, the subtitle, then the
        lab room rising in — one GSAP timeline in js/hero.js
     3. the space is lit: background mesh, dot grid and grain ease up
        from nothing over --dur-slow
   html.is-arriving is set by an inline script in <head> (never under
   reduced motion) and cleared by main.js once it has played. Keyframes
   with `both` hold each element hidden only until its own beat. */
@keyframes arrive-drop  { from { opacity: 0; transform: translateY(-10px); } }
@keyframes arrive-rise  { from { opacity: 0; transform: translateY(12px); } }
@keyframes arrive-light { from { opacity: 0; } }
.is-arriving .topbar   { animation: arrive-drop var(--dur-base) var(--ease-out-expo) both; }
.is-arriving .chapter-rail { animation: arrive-drop var(--dur-base) var(--ease-out-expo) calc(2 * var(--stagger-step)) both; }
.is-arriving :is(.bg-layer, .bg-grain), .is-arriving body::before { animation: arrive-light var(--dur-slow) var(--ease-out-quart) var(--dur-instant) both; }
/* the hero's words wait for hero.js, which takes them over inline before
   .is-arriving clears; if it never runs, clearing it shows them as they are */
.is-arriving .lab-hero :is(.lab-hero-eyebrow, .lab-hero-title, .lab-hero-sub) { opacity: 0; }
/* the title stays hidden until its lines are split (fonts in), then rises */
.lab-hero.is-opening .lab-hero-title { visibility: hidden; }
.lab-hero-title .split-line-mask { padding-bottom: .14em; margin-bottom: -.14em; }   /* room for descenders */

/* ---------- page routing containers ---------- */

.page {
  display: none; opacity: 0; transform: scale(.98) translateY(10px);
  transition: opacity var(--dur-base) var(--ease-out-expo), transform var(--dur-base) var(--ease-out-expo);
}
.page.active { display: block; }
.page.active.in { opacity: 1; transform: scale(1) translateY(0); }
.page.leaving { opacity: 0; transform: scale(.97) translateY(-4px); transition: opacity var(--dur-fast) var(--ease-out-quart), transform var(--dur-fast) var(--ease-out-quart); }
@media (prefers-reduced-motion: reduce) { .page, .page.leaving { transition: none; } }

/* THE VISIT IS ONE SCROLL: the chapters (direct children of .wrap) are
   always laid out, one after another — the transition between them is
   the scroll itself (visit.css stages each chapter's arrival on it).
   Nested grid / detail views keep the depth transition above. */
.wrap > .page.chapter { display: block; opacity: 1; transform: none; transition: none; }

/* shared-element morphs (transitions.js): the incoming view skips its own
   scale-in so the flying ghost lands on the element's final box */
.page[data-morph] { transform: none !important; }

/* ---------- interactive background ---------- */

.bg-layer { position: fixed; inset: 0; z-index: -1; pointer-events: none; overflow: hidden; }
.bg-mesh { position: absolute; inset: -10%; }
/* Soft light pools. Each is a radial gradient — the soft edge is drawn
   once, not re-blurred every frame (a blur(70px) filter on three
   viewport-sized layers was the costliest thing on the page). They drift
   on the compositor (a CSS transform loop) and lean toward the pointer via
   the `translate` property (effects.js), which composes with it. */
.bg-mesh span {
  position: absolute; border-radius: 50%;
  background: radial-gradient(closest-side, var(--c) 0%, color-mix(in srgb, var(--c) 55%, transparent) 45%, transparent 100%);
  will-change: transform;
  animation: blob-drift 26s var(--ease-in-out-smooth) infinite alternate;
}
.blob-1 { --c: var(--mesh-a); width: 56vw; height: 56vw; left: -12%; top: -16%; }
.blob-2 { --c: var(--mesh-b); width: 48vw; height: 48vw; right: -14%; top: 10%; animation-duration: 31s; animation-direction: alternate-reverse; }
.blob-3 { --c: var(--mesh-c); width: 52vw; height: 52vw; left: 18%; bottom: -22%; animation-duration: 23s; }
@keyframes blob-drift {
  from { transform: translate(-22px, -14px); }
  to   { transform: translate(22px, 16px); }
}

/* the dot spotlight follows the pointer: until there is one (touch, a
   pointer resting off the page) there is nothing to follow, so no dots */
.bg-grid {
  position: absolute; inset: 0;
  opacity: 0; transition: opacity var(--dur-slow) var(--ease-out-quart);
  background-image: radial-gradient(var(--grid-dot) 1.4px, transparent 1.4px);
  background-size: 28px 28px;
  -webkit-mask-image: radial-gradient(circle 220px at var(--mx, 50%) var(--my, 20%), #000 0%, transparent 75%);
  mask-image: radial-gradient(circle 220px at var(--mx, 50%) var(--my, 20%), #000 0%, transparent 75%);
}

.bg-grid.is-live { opacity: 1; }

/* the website's band: every page opens on frontwellsolutions.com's blue —
   its 135° wash from --steel toward white, mirrored here, as the website
   keeps its words on white and its picture on the blue: the blue rises
   behind the picture on the right and is gone before the words. Faded in
   under the top bar, so the wordmark, itself in this blue, never stands
   on it. It scrolls away with the page's opening screen. */
/* retired: the openers are navy bands now (.band-dark), as on the website */
body::before {
  display: none;
  content: ""; position: absolute; left: 0; right: 0; top: 0; z-index: -1; pointer-events: none;
  height: min(100vh, 1000px);
  background: linear-gradient(225deg, var(--steel) 0%, rgba(var(--steel-rgb), .45) 22%, rgba(var(--steel-rgb), 0) 46%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 150px, #000 58%, transparent);
          mask-image: linear-gradient(to bottom, transparent 0, #000 150px, #000 58%, transparent);
}
[data-theme="dark"] body::before {
  background: linear-gradient(225deg, rgba(var(--steel-rgb), .30) 0%, rgba(var(--steel-rgb), .10) 24%, rgba(var(--steel-rgb), 0) 46%);
}
/* narrower, the words run the width of the screen: no band behind them —
   the blue stays in the header's button and the dark panels' glow */
@media (max-width: 1080px) { body::before { display: none; } }

/* ── dark bands: the website's rhythm ───────────────────────────────
   The app is light, as the website is, and a few sections are navy
   bands from edge to edge of the screen, as its dark sections are: the
   openers (Home, the lab visit, FIDA PLANT), Home's last call, the
   footer. Each carries data-theme="dark", so the dark roles in
   tokens.css apply to it and to everything inside it (the same set the
   old dark mode used). The band leaves the page's column for the full
   width; its padding puts the content back exactly where it was. An
   opener's navy also runs up behind the top bar, to the top of the page. */
.band-dark {
  position: relative; isolation: isolate;
  margin-inline: calc(50% - 50vw);
  padding-inline: calc(50vw - 50%);
  padding-bottom: clamp(48px, 6vw, 88px);
  color: var(--text);
  background:
    radial-gradient(60% 80% at 100% 100%, rgba(var(--steel-rgb), .14), transparent 70%),
    linear-gradient(180deg, var(--fw-navy) 0%, var(--fw-s80) 100%);
}
/* the width, over any section's own margins and max-width (the hero rules load later) */
.band-dark[data-theme] { margin-inline: calc(50% - 50vw); padding-inline: calc(50vw - 50%); max-width: none; }
/* Home ends on its last call: the band runs straight into the footer, as on the website */
html[data-open-realm="home"] .wrap { padding-bottom: 0; }
html[data-open-realm="home"] .wrap > #page-home.page.chapter { padding-bottom: 0; margin-bottom: 0; }
html[data-open-realm="home"] .home-closing { margin-bottom: 0; }
/* an opener that fills the first screen (Home): its navy reaches the bottom
   of the view; --band-top is where it starts on the page (js/home.js) */
.band-dark.is-full { min-height: calc(100svh - var(--band-top, 0px)); align-content: center; }
.band-dark.is-opener::after {
  content: ""; position: absolute; z-index: -1; left: 0; right: 0; bottom: 100%;
  height: 280px; background: var(--fw-navy); pointer-events: none;
}

.bg-grain {
  position: fixed; inset: 0; z-index: -1; pointer-events: none;
  opacity: .035; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- custom cursor (pointer devices only) ---------- */

@media (hover: hover) and (pointer: fine) {
  body.custom-cursor, body.custom-cursor * { cursor: none !important; }
}
/* The dot and ring are drawn in the theme's ink with a thin halo of the
   opposite tone, so they stay legible over the page, photos and the navy
   chrome. (They used mix-blend-mode: difference, which made the browser
   re-composite the whole page on every frame the cursor or page moved —
   the single biggest scroll cost measured.) The ring's size comes from --cursor-size,
   which effects.js sets per context (resting · interactive · text ·
   labelled), and it springs between sizes.
   The follow loop writes the pointer position into the `translate`
   property; `transform` only centres the element on it. A click changes
   nothing about the cursor: no press shrink, no movement. */
.cursor-dot, .cursor-ring {
  --cursor-ink: var(--navy-deep);
  --cursor-halo: rgba(255, 255, 255, .7);
  position: fixed; top: 0; left: 0; pointer-events: none; z-index: 9999;
  border-radius: 50%; transform: translate(-50%, -50%); display: none;
}
[data-theme="dark"] :is(.cursor-dot, .cursor-ring) { --cursor-halo: rgba(0, 0, 0, .5); }
.cursor-dot {
  width: 6px; height: 6px; background: var(--cursor-ink); box-shadow: 0 0 0 1.5px var(--cursor-halo);
  transition: opacity var(--dur-fast) var(--ease-out-quart);
}
.cursor-ring {
  --cursor-size: 28px;
  width: var(--cursor-size); height: var(--cursor-size);
  border: 1.5px solid var(--cursor-ink); opacity: .6;
  box-shadow: 0 0 0 1px var(--cursor-halo), inset 0 0 0 1px var(--cursor-halo);
  display: flex; align-items: center; justify-content: center;
  transition:
    width var(--dur-base) var(--ease-spring), height var(--dur-base) var(--ease-spring),
    border-radius var(--dur-base) var(--ease-out-expo),
    opacity var(--dur-fast) var(--ease-out-quart), background-color var(--dur-fast) var(--ease-out-quart),
    border-color var(--dur-fast) var(--ease-out-quart);
}
/* over something clickable, a faint brand lens */
.cursor-ring.hover { background-color: color-mix(in srgb, var(--blue) 12%, transparent); border-color: var(--blue); opacity: 1; }
/* text fields: the ring collapses into a caret bar (the native I-beam is
   hidden with the rest of the system cursor) and the dot steps aside */
.cursor-ring[data-kind="text"] {
  width: 2px; border-radius: 2px; border-color: transparent; background-color: var(--cursor-ink); box-shadow: 0 0 0 1px var(--cursor-halo);
}
.cursor-dot.is-text { opacity: 0; }
/* labelled targets drop the blend: a solid brand disc carrying a word
   reads over any photo, where an inverted disc would not */
.cursor-ring.labeled {
  background-color: var(--blue); border-color: transparent; box-shadow: none; opacity: .94;
}

/* ---------- off-screen chapters pause their loops (effects.js) ---------- */
:is(.page.chapter, .realm-section).is-offscreen *,
:is(.page.chapter, .realm-section).is-offscreen *::before,
:is(.page.chapter, .realm-section).is-offscreen *::after { animation-play-state: paused !important; }
.cursor-dot.is-labeled { opacity: 0; }
.cursor-ring.is-away, .cursor-dot.is-away { opacity: 0; }
body.custom-cursor .cursor-dot { display: block; }
body.custom-cursor .cursor-ring { display: flex; }
/* no custom cursor (a touch screen, reduced motion): no ring — it would
   sit parked at the page's corner, half off screen */
body:not(.custom-cursor) :is(.cursor-dot, .cursor-ring) { display: none; }

.cursor-label {
  font-size: 8px; font-weight: 800; letter-spacing: .05em; color: #fff;
  opacity: 0; transform: scale(.7); transition: opacity var(--dur-fast) var(--ease-out-quart), transform var(--dur-base) var(--ease-spring);
  pointer-events: none; white-space: nowrap;
}
.cursor-ring.labeled .cursor-label { opacity: 1; transform: scale(1); }

/* ---------- scroll reveal ---------- */

/* the same rise as data-reveal="rise" (MOTION, js/scroll.js): one
   distance, duration, curve and stagger. --i is set by the builder in JS,
   or by initReveal from the element's position among its .reveal siblings */
.reveal {
  opacity: 0; transform: translateY(var(--reveal-y));
  transition: opacity var(--reveal-dur) var(--ease-out-expo), transform var(--reveal-dur) var(--ease-out-expo);
  transition-delay: calc(var(--i, 0) * var(--reveal-stagger));
}
.reveal.in { opacity: 1; transform: translateY(0); }
@media (prefers-reduced-motion: reduce) { .reveal { opacity: 1; transform: none; } }
/* data-reveal="rise" (js/vocab.js): while a group's items rise, their own
   hover transitions stand down so GSAP alone moves them */
.is-rising > * { transition: none !important; }
/* the entrance as a keyframe, for CSS-driven arrivals (the planner, the
   library's hook): the same rise again */
@keyframes reveal-rise { from { opacity: 0; transform: translateY(var(--reveal-y)); } }

/* ---------- no-JS fallback ---------- */

.noscript-banner {
  background: var(--warn-dim); color: var(--warn);
  border: 1px solid currentColor; border-radius: var(--radius-md);
  padding: 16px 20px; margin: 20px auto; max-width: var(--wrap-max);
  font-size: 14px; line-height: 1.6;
}

/* ── the way back to the company website (js/site.js) ─────────────────
   As the website writes its own back-links ("← Professional Services"):
   small, light on the navy openers, above the title. */
.site-crumb {
  display: inline-flex; align-items: center; gap: 8px; margin: 0 0 18px;
  font-size: 13px; font-weight: 500; text-decoration: none;
  color: rgba(255, 255, 255, .78);
  transition: color .18s var(--ease-out-quart);
}
.site-crumb:hover { color: #fff; }
.site-crumb:focus-visible { outline: 2px solid var(--steel-t40); outline-offset: 4px; border-radius: 2px; }
.site-crumb-arrow { transition: translate .18s var(--ease-out-quart); }
.site-crumb:hover .site-crumb-arrow { translate: -3px 0; }
.plant-opener > .site-crumb { margin-bottom: 14px; }
.footer-base a { color: inherit; }
@media (max-width: 1080px) { .footer-nav { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
