/* =====================================================================
   LIGHTFIELD — the body layer (10/13)
   Every page already opens on a live hero. This file carries that light
   down through the rest of the page, so the sections below stop reading
   as flat boxes on black:

     1. the field      a slow aurora behind the whole page (js/glow.js)
     2. glass cards    the field shows through faintly; the edge catches
                       the light, a runner of light circles it on hover
     3. headlines      one sweep of light across each section heading
     4. portals        closing call-to-action cards become a lit doorway
     5. details        chips, accordions, steps, lists, checkboxes
     6. learn          the shared look of the explanatory diagrams
                       (js/learn.js + js/learn/*.js)

   Loaded after boise.css on every public page. Everything here is
   decoration: with this file missing the site still reads correctly.
   ===================================================================== */

@property --lf-a { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
@property --lf-p { syntax: "<percentage>"; inherits: false; initial-value: 0%; }

:root {
  --lf-edge-hi: oklch(86% .07 215 / .30);    /* the lit top of a glass edge */
  --lf-edge-lo: oklch(72% .05 220 / .09);    /* the unlit rest of it */
  --lf-glass-1: oklch(19% .019 252 / .80);
  --lf-glass-2: oklch(15.5% .016 251 / .88);
}

/* ---------------------------------------------------------------------
   1. THE FIELD
   One fixed canvas behind everything (z-index -1 paints above the page
   background, below every section). js/glow.js draws it; without WebGL,
   or with reduced motion, the static pools below stand in.
   --------------------------------------------------------------------- */
.lf-field {
  position: fixed; inset: 0; width: 100%; height: 100%;
  z-index: -1; pointer-events: none; display: block;
  opacity: 0; transition: opacity 1.6s var(--ease-out);
}
.lf-field.on { opacity: 1; }
html.lf-still body {
  background:
    radial-gradient(60vw 50vh at 8% 30%, oklch(55% .21 305 / .07), transparent 70%),
    radial-gradient(55vw 45vh at 92% 70%, oklch(83% .135 211 / .06), transparent 70%),
    var(--color-void);
  background-attachment: fixed;
}

/* ---------------------------------------------------------------------
   2. GLASS CARDS
   The real border stays (featured cards keep their colour). ::before
   becomes a full ring of light laid over it: a soft top-lit edge at
   rest, a runner of the card's own accent circling it on hover.
   --------------------------------------------------------------------- */
.card {
  background:
    radial-gradient(520px circle at var(--gx, 50%) var(--gy, -30%), color-mix(in oklch, var(--accent) 8%, transparent), transparent 62%),
    linear-gradient(165deg, var(--lf-glass-1), var(--lf-glass-2));
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / .045), 0 18px 50px -24px oklch(5% .01 250 / .8);
}
.card::before {
  inset: 0; height: auto; padding: 1px; opacity: 1;
  --lf-p: 0%;
  background:
    conic-gradient(from var(--lf-a), transparent 0 60%, color-mix(in oklch, color-mix(in oklch, var(--accent) 80%, white) var(--lf-p), transparent) 78%, transparent 93%) border-box,
    linear-gradient(180deg, var(--lf-edge-hi), var(--lf-edge-lo) 38%, var(--lf-edge-lo)) border-box;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  transition: opacity var(--dur-3) var(--ease-out), --lf-p .6s var(--ease-out);
}
.card:hover::before, .card:focus-visible::before, .card.lf-lit-edge::before {
  --lf-p: 100%;
  animation: lfRun 3.2s linear infinite;
}
@keyframes lfRun { to { --lf-a: 360deg; } }
/* touch screens: one lap of the runner as a grid arrives (js/glow.js .lf-lap) */
.card.lf-lap::before { --lf-p: 100%; animation: lfRun 3.2s linear 1; }
.card:hover {
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / .06), 0 24px 60px -26px oklch(5% .01 250 / .9),
              0 0 42px -6px color-mix(in oklch, var(--accent) 22%, transparent);
}
/* A card's icon tile glows in its accent. */
.card .cicon, .feat-card .ic {
  box-shadow: 0 0 22px -4px color-mix(in oklch, var(--accent, var(--color-accent)) 45%, transparent),
              inset 0 0 12px color-mix(in oklch, var(--accent, var(--color-accent)) 14%, transparent);
}
/* 10/17 holo-glyph tiles (js/glow.js adds .lf-glyph, then .lf-drawn on arrival):
   the strokes draw on one after another; a glint circles the tile once, and
   again on hover; on tilt cards the tile floats above the card face. The
   dash pattern lives only inside the animation, so dashed icons keep theirs. */
.cicon.lf-glyph { position: relative; }
/* armed (js/glow.js will draw it): the strokes wait undrawn, so a tile never shows drawn, blinks out, then draws */
.cicon.lf-armed:not(.lf-drawn) svg * { stroke-dasharray: 1; stroke-dashoffset: 1; }
.cicon.lf-drawn svg * { animation: lfDraw .95s var(--ease-out) backwards; animation-delay: calc(.1s + var(--gi, 0) * .09s); }
@keyframes lfDraw { from { stroke-dasharray: 1; stroke-dashoffset: 1; } to { stroke-dasharray: 1; stroke-dashoffset: 0; } }
.cicon .lf-glint {
  position: absolute; inset: -1px; border-radius: inherit; padding: 1px; pointer-events: none; opacity: 0;
  background: conic-gradient(from var(--lf-a), transparent 0 64%, color-mix(in oklch, var(--accent, var(--color-accent)) 75%, white) 84%, transparent 96%) border-box;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0); -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}
.cicon.lf-drawn .lf-glint { animation: lfGlint 1.5s .25s var(--ease-out) both; }
.card:hover .cicon.lf-glyph .lf-glint, .card:focus-visible .cicon.lf-glyph .lf-glint { animation: lfGlint 1.5s var(--ease-out) both; }
@keyframes lfGlint { 0% { --lf-a: 0deg; opacity: 0; } 12% { opacity: 1; } 80% { opacity: 1; } 100% { --lf-a: 360deg; opacity: 0; } }
.tilt > .cicon.lf-glyph, .tilt .cicon.lf-glyph { transform: translateZ(40px); }
.tilt:hover .cicon.lf-glyph {
  box-shadow: 0 0 22px -4px color-mix(in oklch, var(--accent, var(--color-accent)) 55%, transparent),
              0 14px 18px -10px color-mix(in oklch, var(--accent, var(--color-accent)) 45%, transparent),
              inset 0 0 12px color-mix(in oklch, var(--accent, var(--color-accent)) 18%, transparent);
}

/* Featured plans hold a steady halo and a slow runner, not just a 1px line. */
.card.featured::before, .card.lf-featured::before { --lf-p: 100%; animation: lfRun 7s linear infinite; }
.card.featured, .card.lf-featured {
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / .06), 0 30px 80px -30px color-mix(in oklch, var(--accent) 50%, transparent),
              0 0 60px -10px color-mix(in oklch, var(--accent) 22%, transparent);
}

/* Lists inside cards that carry no class of their own get the site's
   diamond bullets instead of the browser's default discs. */
.card ul:not([class]) { list-style: none; padding: 0; margin: 0 0 1em; display: grid; gap: 10px; }
.card ul:not([class]) > li { position: relative; padding-left: 24px; color: var(--color-ink-dim); }
.card ul:not([class]) > li::before {
  content: ""; position: absolute; left: 3px; top: .62em; width: 7px; height: 7px; rotate: 45deg;
  border: 1.5px solid var(--color-accent); box-shadow: 0 0 8px oklch(83% .135 211 / .6);
}
.card ul:not([class]) > li b, .card ul:not([class]) > li strong { color: var(--color-ink); }

/* Grid cards rise in one after another when their grid scrolls in
   (js/glow.js adds .lf-stage, then .lf-in). `translate` composes with the
   tilt cards' own transform instead of fighting it. */
.lf-stage > .card, .lf-stage > .lf-rise {
  opacity: 0; translate: 0 22px;
  transition: opacity .7s var(--ease-out), translate .9s var(--ease-out), transform var(--dur-3) var(--ease-out),
              border-color var(--dur-3) var(--ease-out), box-shadow var(--dur-3) var(--ease-out);
  transition-delay: calc(var(--lf-i, 0) * 70ms);
}
.lf-stage.lf-in > .card, .lf-stage.lf-in > .lf-rise { opacity: 1; translate: none; }

/* ---------------------------------------------------------------------
   3. HEADLINES — one pass of light across each section heading as it
   arrives, then it rests as plain ink. Gradient-text headings keep theirs.
   --------------------------------------------------------------------- */
.lf-sweep {
  background: linear-gradient(100deg, var(--color-ink) 0 38%, oklch(92% .09 205) 46%, oklch(80% .16 330) 50%, var(--color-ink) 58% 100%);
  background-size: 260% 100%; background-position: 100% 0;
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.lf-sweep.lf-go { animation: lfSweep 1.9s var(--ease-out) .1s both; }
@keyframes lfSweep { from { background-position: 100% 0; } to { background-position: 0% 0; } }
.lf-sweep .accent, .lf-sweep .ignite, .lf-sweep .grad-text { -webkit-text-fill-color: initial; }

/* Seams (10/17, js/glow.js): one horizon of light on each boundary between
   two body sections, in the incoming section's aura (--tone from data-tone).
   It takes no space: a 0-height marker whose line sits on the boundary. */
.lf-seam {
  display: block; position: relative; height: 0; z-index: 0; pointer-events: none;
  width: min(var(--wrap), calc(100% - 40px)); margin: 0 auto; --tone: var(--color-accent);
}
.lf-seam::before {
  content: ""; position: absolute; left: 0; right: 0; top: -.5px; height: 1px;
  background: linear-gradient(90deg, transparent, color-mix(in oklch, var(--tone) 70%, white) 34%, var(--tone) 58%, transparent);
  box-shadow: 0 0 12px color-mix(in oklch, var(--tone) 50%, transparent), 0 0 46px 3px color-mix(in oklch, var(--tone) 13%, transparent);
  transform: scaleX(0); transform-origin: 40% 50%; opacity: .8;
  transition: transform 1.1s var(--ease-out);
}
.lf-seam.lf-same::before { opacity: .38; }
.lf-seam.lf-go::before { transform: scaleX(1); }
.lf-seam::after {
  content: ""; position: absolute; top: -2.5px; left: 0; width: 72px; height: 5px; border-radius: 5px; opacity: 0;
  background: radial-gradient(closest-side, #fff, color-mix(in oklch, var(--tone) 80%, white) 45%, transparent);
  filter: drop-shadow(0 0 6px var(--tone));
}
.lf-seam.lf-go:not(.lf-done)::after { animation: lfBead 1.9s .35s var(--ease-in-out) both; }
.lf-seam.lf-same::after { display: none; }
@keyframes lfBead { 0% { left: 8%; opacity: 0; } 14% { opacity: 1; } 86% { opacity: 1; } 100% { left: calc(92% - 72px); opacity: 0; } }
@media (max-width: 720px) { .lf-seam { width: calc(100% - 32px); } }

/* Eyebrows carry a small diamond of light. */
.eyebrow.lf-pip::before {
  content: ""; display: inline-block; width: 6px; height: 6px; rotate: 45deg; margin: 0 .9em .18em 0;
  background: currentColor; box-shadow: 0 0 10px currentColor, 0 0 2px currentColor; vertical-align: middle;
}

/* ---------------------------------------------------------------------
   4. PORTALS — the closing call-to-action card on a page.
   Add .lf-portal to the card. A slow aurora turns behind the heading,
   a horizon of light sits under it, the ring runs constantly.
   --------------------------------------------------------------------- */
.card.lf-portal {
  --accent: var(--color-ignition);
  overflow: hidden; isolation: isolate; text-align: center;
  padding-block: clamp(48px, 7vw, 92px);
  background:
    radial-gradient(70% 55% at 50% 118%, oklch(67% .24 340 / .26), transparent 66%),
    radial-gradient(44% 30% at 50% 108%, oklch(90% .08 210 / .12), transparent 70%),
    conic-gradient(from var(--lf-a) at 50% 130%, transparent 0 8%, oklch(83% .135 211 / .16) 18%, transparent 30%,
                   oklch(55% .21 305 / .18) 46%, transparent 58%, oklch(67% .24 340 / .14) 72%, transparent 86%),
    radial-gradient(1.2px 1.2px at 12% 22%, oklch(95% .02 220 / .7), transparent 60%),
    radial-gradient(1px 1px at 27% 64%, oklch(95% .02 220 / .5), transparent 60%),
    radial-gradient(1.4px 1.4px at 71% 18%, oklch(95% .02 220 / .65), transparent 60%),
    radial-gradient(1px 1px at 86% 48%, oklch(95% .02 220 / .5), transparent 60%),
    radial-gradient(1px 1px at 58% 34%, oklch(95% .02 220 / .45), transparent 60%),
    linear-gradient(180deg, oklch(13% .02 262 / .9), oklch(16% .03 290 / .92));
  animation: lfRun 26s linear infinite;
}
.card.lf-portal::before { --lf-p: 100%; animation: lfRun 5.5s linear infinite; }
.card.lf-portal::after {
  content: ""; position: absolute; left: 6%; right: 6%; bottom: 0; height: 1px; z-index: -1; pointer-events: none;
  background: linear-gradient(90deg, transparent, oklch(90% .08 210 / .9) 30%, oklch(80% .16 330 / .9) 70%, transparent);
  box-shadow: 0 0 18px oklch(83% .135 211 / .7), 0 0 60px 6px oklch(67% .24 340 / .35);
}
:where(.card.lf-portal) > * { position: relative; }
.card.lf-portal h2 { text-shadow: 0 0 40px oklch(83% .135 211 / .35); }

/* 10/17: the portal as real light (js/fx/portal.js, added by js/glow.js).
   It fills the card behind the words; the card's own CSS aurora steps back
   once the ring is live, so the two lights never fight. */
.card.lf-portal > .lf-portal-fx {
  position: absolute; inset: 0; z-index: 0; width: auto; max-width: none; aspect-ratio: auto; margin: 0;
  pointer-events: none; opacity: 0; transition: opacity 1.4s var(--ease-out);
}
.card.lf-portal > .lf-portal-fx.live { opacity: 1; }
/* 10/18: no dark veil over the ring any more (Robyn: the black fog covered the visuals);
   the words carry a crisp dark outline instead, the same as the hero copy (css/sh.css) */
.card.lf-portal.lf-has-fx :is(h2, h3, p) { text-shadow: 0 0 1px #04060b, 0 0 2px #04060b, 0 0 3px oklch(8% .02 255 / .9), 0 1px 6px oklch(8% .02 255 / .7); }
.lf-portal-fx .fx-stage, .lf-portal-fx .fx-stage canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.lf-portal-fx .fx-labels { display: none; }
.lf-portal-fx.nogl { display: none; }
.card.lf-portal.lf-has-fx:has(> .lf-portal-fx.live) {
  background:
    radial-gradient(60% 50% at 50% 50%, oklch(11% .02 262 / .55), transparent 75%),
    linear-gradient(180deg, oklch(12% .02 262 / .94), oklch(14% .03 290 / .95));
}
.card.lf-portal.lf-has-fx:has(> .lf-portal-fx.live)::after { opacity: .35; }
.card.lf-portal .hero-cta, .card.lf-portal .lf-cta-row { justify-content: center; }
/* 10/17 pass 2: the plain links in a portal's fine print (a phone number, an
   email, a price) were ~18px-tall tap targets on phones. Vertical padding on
   an inline link grows its tap area to ~44px without taking room in the line,
   so nothing moves. Styled links (chips, pills) carry a class and are left alone. */
@media (max-width: 720px), (pointer: coarse) {
  .card.lf-portal p a:not([class]) { padding-block: 13px; }
}

/* 10/17 pass 2: when the live ring cannot run (no WebGL2, a failed scene,
   a low-end or save-data device, ?noportal), the doorway still gets its
   ring, drawn in CSS: the top and bottom arcs of a wide ellipse that runs
   off the card's sides, so at any card size it frames the words and never
   crosses them (the arcs sit in the card's top and bottom padding). Line,
   sheath and outer halo are three scalings of one ellipse, so the line is
   thicker toward the sides, as a ring lying back is. Colour follows the
   section's data-aura, like the live ring; magenta when there is none.
   404's card brings its own fallback art (the hex tunnel) and keeps it
   instead. Still, so it needs no reduced-motion rule. */
.card.lf-portal { --pr-a: var(--color-ignition); --pr-b: oklch(74% .13 235); --pr-rx: 72%; --pr-ry: 47%; }
[data-aura="cyan"] .card.lf-portal { --pr-a: var(--color-accent); --pr-b: oklch(62% .2 300); }
[data-aura="violet"] .card.lf-portal { --pr-a: oklch(66% .2 300); --pr-b: var(--color-ignition); }
[data-aura="ember"] .card.lf-portal { --pr-a: oklch(78% .15 58); --pr-b: var(--color-ignition); }
[data-aura="ok"] .card.lf-portal { --pr-a: var(--color-ok); --pr-b: var(--color-accent); }
@media (max-width: 720px) { .card.lf-portal { --pr-rx: 100%; --pr-ry: 49%; } }
.card.lf-portal.lf-ring-css:not(:has(> .exit-hex)),
.card.lf-portal:has(> .lf-portal-fx.nogl):not(:has(> .exit-hex)) {
  background:
    /* the white-hot thread */
    radial-gradient(ellipse var(--pr-rx) var(--pr-ry) at 50% 50%, transparent 99.05%, color-mix(in oklch, var(--pr-a) 40%, white) 99.55%, transparent 100.05%),
    /* the line in its own colour */
    radial-gradient(ellipse var(--pr-rx) var(--pr-ry) at 50% 50%, transparent 97.6%, color-mix(in oklch, var(--pr-a) 85%, transparent) 99.5%, transparent 101.3%),
    /* the dark pool the words sit on */
    radial-gradient(58% 46% at 50% 50%, oklch(10% .022 270 / .7), transparent 78%),
    /* the soft sheath and the halo outside it */
    radial-gradient(ellipse var(--pr-rx) var(--pr-ry) at 50% 50%, transparent 91%, color-mix(in oklch, var(--pr-a) 24%, transparent) 99.3%, color-mix(in oklch, var(--pr-b) 18%, transparent) 101%, transparent 111%),
    /* light gathered on the near (lower) arc, as the live ring's tube glow does */
    radial-gradient(ellipse 52% 24% at 50% 99%, color-mix(in oklch, var(--pr-a) 20%, transparent), transparent 72%),
    radial-gradient(ellipse 40% 14% at 50% 1%, color-mix(in oklch, var(--pr-b) 12%, transparent), transparent 72%),
    linear-gradient(180deg, oklch(12% .02 262 / .94), oklch(14% .03 290 / .95));
  /* the turning aurora this replaces is gone, so its clock stops too (the edge ring keeps its own) */
  animation: none;
}
@media (forced-colors: active) { .card.lf-portal.lf-ring-css, .card.lf-portal:has(> .lf-portal-fx.nogl) { background: Canvas; } }

/* ---------------------------------------------------------------------
   5. DETAILS
   --------------------------------------------------------------------- */
.chip {
  background: linear-gradient(180deg, oklch(24% .025 250 / .7), oklch(18% .02 252 / .7));
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / .05);
}

/* Accordions: the open row is lit along its left edge. */
.acc { background: linear-gradient(165deg, var(--lf-glass-1), var(--lf-glass-2)); box-shadow: 0 18px 50px -28px oklch(5% .01 250 / .8); }
.acc details { position: relative; transition: background var(--dur-3) var(--ease-out); }
.acc details::before {
  content: ""; position: absolute; left: 0; top: 12px; bottom: 12px; width: 2px; border-radius: 2px;
  background: linear-gradient(180deg, var(--color-accent), var(--color-ignition));
  box-shadow: 0 0 12px oklch(83% .135 211 / .7); opacity: 0; scale: 1 .3;
  transition: opacity var(--dur-3) var(--ease-out), scale var(--dur-4) var(--ease-out);
}
.acc details[open] { background: linear-gradient(90deg, oklch(83% .135 211 / .06), transparent 60%); }
.acc details[open]::before { opacity: 1; scale: 1 1; }
/* 10/17: an answer unfolds instead of snapping open (browsers with
   ::details-content; elsewhere it opens at once, as before), and its words
   rise a few pixels into place */
.acc { interpolate-size: allow-keywords; }
.acc details::details-content { block-size: 0; overflow-y: clip; transition: block-size .5s var(--ease-out), content-visibility .5s allow-discrete; }
.acc details[open]::details-content { block-size: auto; }
.acc details[open] > :not(summary) { animation: lfRiseIn .55s var(--ease-out) .05s both; }
@keyframes lfRiseIn { from { opacity: 0; translate: 0 6px; } }
@media (prefers-reduced-motion: reduce) {
  .acc details::details-content { transition: none; }
  .acc details[open] > :not(summary) { animation: none; }
}
.acc details[open] summary { color: var(--color-accent); }

/* Numbered steps: lit glass beads. */
.steps .step .n, .how-num {
  box-shadow: inset 0 0 10px oklch(83% .135 211 / .14), 0 0 0 4px oklch(13.5% .014 250);
}

/* Checkboxes drawn in the site's own material instead of a white OS square. */
.checks input[type="checkbox"] {
  appearance: none; -webkit-appearance: none; margin: 0; padding: 0; cursor: pointer;
  border: 1.5px solid oklch(78% .08 215 / .45); border-radius: 5px; background: var(--color-well);
  display: inline-grid; place-content: center;
  transition: background var(--dur-2) var(--ease-out), border-color var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}
.checks input[type="checkbox"]::after {
  content: ""; width: 10px; height: 6px; margin-top: -2px; rotate: -45deg; scale: 0;
  border-left: 2px solid var(--color-void); border-bottom: 2px solid var(--color-void);
  transition: scale var(--dur-2) var(--ease-out);
}
.checks input[type="checkbox"]:hover { border-color: var(--color-accent); }
.checks input[type="checkbox"]:checked { background: var(--color-accent); border-color: var(--color-accent); box-shadow: 0 0 14px oklch(83% .135 211 / .55); }
.checks input[type="checkbox"]:checked::after { scale: 1; }
.checks input[type="checkbox"]:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

/* Inputs: a faint inner light when focused. */
input:focus, select:focus, textarea:focus {
  box-shadow: 0 0 0 3px oklch(83% .135 211 / .16), 0 0 24px -6px oklch(83% .135 211 / .5);
}

/* ---------------------------------------------------------------------
   6. LEARN — the explanatory diagrams (js/learn.js)
   HTML nodes laid out with ordinary CSS grid (so they reflow on phones),
   with an SVG layer of routed links and travelling pulses on top.
   --------------------------------------------------------------------- */
:where(.ln) { position: relative; }
.ln { isolation: isolate; }
.ln-links { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; z-index: 0; }
.ln-links path { fill: none; }
/* 10/17: routes read as live wires (base was .22 and vanished on the dark
   ground; the 2/12 flow dashes read as dotted noise) */
.ln-links .ln-base { stroke: oklch(74% .06 220 / .36); stroke-width: 1.5; }
.ln-links .ln-flow { stroke-width: 1.6; stroke-linecap: round; stroke-dasharray: 8 14; animation: lnFlow 1.6s linear infinite; opacity: .8; }
.ln-links .ln-halo { stroke-width: 7; opacity: .2; }
@keyframes lnFlow { to { stroke-dashoffset: -22; } }
.ln-links g.ln-off > path:not(.ln-tail) { opacity: .25; }
.ln-links .ln-tail { fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; filter: drop-shadow(0 0 5px currentColor); pointer-events: none; }
/* a pulse lands: the node it reaches flares, its kicker diamond flashes white, a ring ripples out */
.ln-node.ln-hit { animation: lnHit .7s var(--ease-out); }
.ln-node.ln-hit .ln-k::before { animation: lnHitPip .7s var(--ease-out); }
.ln-ripple {
  position: absolute; inset: -1px; border-radius: inherit; pointer-events: none; z-index: 2;
  border: 1px solid var(--tone, var(--color-accent)); animation: lnRipple .7s var(--ease-out) forwards;
}
@keyframes lnHit { 0% { border-color: var(--tone); box-shadow: inset 0 1px 0 oklch(100% 0 0 / .07), 0 0 0 1px color-mix(in oklch, var(--tone) 45%, transparent), 0 0 44px -2px color-mix(in oklch, var(--tone) 55%, transparent); } }
@keyframes lnHitPip { 0% { background: #fff; box-shadow: 0 0 12px #fff, 0 0 18px var(--tone); } }
@keyframes lnRipple { from { opacity: .8; inset: -1px; } to { opacity: 0; inset: -9px; } }
.ln:not(.ln-vis) .ln-flow { animation-play-state: paused; }
.ln-links .ln-pulse { filter: drop-shadow(0 0 4px currentColor) drop-shadow(0 0 10px currentColor); }
.ln [data-tone="cyan"],   .ln-links [data-tone="cyan"]   { --tone: var(--color-accent); color: var(--color-accent); }
.ln [data-tone="ember"],  .ln-links [data-tone="ember"]  { --tone: var(--color-ember); color: var(--color-ember); }
.ln [data-tone="magenta"],.ln-links [data-tone="magenta"]{ --tone: var(--color-ignition); color: var(--color-ignition); }
.ln [data-tone="violet"], .ln-links [data-tone="violet"] { --tone: oklch(72% .17 300); color: oklch(72% .17 300); }
.ln [data-tone="ok"],     .ln-links [data-tone="ok"]     { --tone: var(--color-ok); color: var(--color-ok); }
.ln-links [data-tone] .ln-flow, .ln-links [data-tone] .ln-halo, .ln-links .ln-flow[data-tone], .ln-links .ln-halo[data-tone] { stroke: currentColor; }
/* 10/17 pass 1: a warm route's grey-blue base under its amber halo read as
   khaki mud; warm and green routes carry their own tone in the base line and
   a thinner, brighter halo, so they read as gold and green wires */
.ln-links [data-tone="ember"] .ln-base, .ln-links [data-tone="ok"] .ln-base, .ln-links [data-tone="magenta"] .ln-base {
  stroke: color-mix(in oklch, currentColor 70%, oklch(88% .03 220)); stroke-opacity: .5;
}
.ln-links [data-tone="ember"] .ln-halo, .ln-links [data-tone="ok"] .ln-halo { opacity: .12; stroke-width: 5; }
.ln-links [data-tone="ember"] .ln-flow { opacity: .95; }
.ln-links .ln-pulse { fill: currentColor; }

/* the tone tints a node's kicker, dot and edge; its words stay ink */
.ln .ln-node[data-tone] { color: var(--color-ink-dim); }
.ln-node {
  --tone: var(--color-accent);
  position: relative; z-index: 1; min-width: 0;
  border-radius: var(--radius-lg); padding: 16px 18px;
  background: linear-gradient(165deg, oklch(20% .02 252 / .92), oklch(15% .016 251 / .95));
  border: 1px solid color-mix(in oklch, var(--tone) 22%, oklch(72% .05 220 / .14));
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / .05), 0 16px 40px -22px oklch(5% .01 250 / .9);
  transition: border-color var(--dur-3) var(--ease-out), box-shadow var(--dur-4) var(--ease-out), opacity var(--dur-3) var(--ease-out), translate var(--dur-4) var(--ease-out);
}
.ln-node.on {
  border-color: color-mix(in oklch, var(--tone) 70%, transparent);
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / .07), 0 0 0 1px color-mix(in oklch, var(--tone) 25%, transparent),
              0 0 38px -4px color-mix(in oklch, var(--tone) 40%, transparent);
}
/* a dimmed node steps back in its frame and accent, never in its words (AA holds) */
.ln-node.dim { border-color: oklch(72% .05 220 / .12); box-shadow: none; }
.ln-node.dim .ln-dot, .ln-node.dim .ln-k::before { opacity: .5; }
.ln-node.dim .ln-k { color: var(--color-ink-faint); }
.ln-node.dim .ln-t { color: var(--color-ink-dim); }
.ln .ln-k, .ln-k { display: flex; align-items: center; gap: 8px; margin: 0 0 6px; font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: .14em; text-transform: uppercase; color: var(--tone); }
.ln-k::before { content: ""; width: 6px; height: 6px; rotate: 45deg; background: var(--tone); box-shadow: 0 0 8px var(--tone); flex: none; }
.ln .ln-t, .ln-t { margin: 0; font-family: var(--font-display); font-weight: 700; font-size: var(--text-lg); line-height: 1.2; color: var(--color-ink); }
.ln .ln-d, .ln-d { margin: 6px 0 0; font-size: var(--text-sm); color: var(--color-ink-dim); line-height: 1.5; }
.ln .ln-cap, .ln-cap { text-wrap: balance; margin: var(--space-md) 0 0; font-family: var(--font-mono); font-size: var(--text-xs); letter-spacing: .08em; color: var(--color-ink-faint); }
.ln-cap::before { content: "◇ "; color: var(--color-accent); }
.ln-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--tone); box-shadow: 0 0 10px var(--tone), 0 0 24px var(--tone); }

/* ---------------------------------------------------------------------
   7. THE SIGN-OFF — the footer motto sits on a horizon of light, and the
   empty half beside it holds the mark as a beacon (the same hex, ring and
   core as the logo), its outer ring turning slowly. On every page.
   --------------------------------------------------------------------- */
.foot-statement { position: relative; }
.foot-statement::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px; pointer-events: none;
  background: linear-gradient(90deg, transparent, oklch(83% .135 211 / .8) 28%, oklch(67% .24 340 / .8) 72%, transparent);
  box-shadow: 0 0 14px oklch(83% .135 211 / .45), 0 0 40px 2px oklch(67% .24 340 / .18);
}
/* 10/17 pass 1: phones and tablets get the beacon too (they had a text-only
   sign-off), smaller and tucked top-right beside the motto's two short first
   lines (measured: they end by 205px at 360-430px, 286px at 540-720px), so it
   never touches a word; the smallest phones (under 350px) keep the text alone */
@media (min-width: 350px) {
  .foot-statement::after {
    content: ""; position: absolute; right: 0; top: calc(var(--space-2xl) - 2px); width: clamp(84px, 16vw, 132px); aspect-ratio: 1;
    pointer-events: none; opacity: .9;
    background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cdefs%3E%3CradialGradient id='g'%3E%3Cstop offset='0' stop-color='%2357d5ef' stop-opacity='.32'/%3E%3Cstop offset='.6' stop-color='%238a5cf0' stop-opacity='.08'/%3E%3Cstop offset='1' stop-color='%2357d5ef' stop-opacity='0'/%3E%3C/radialGradient%3E%3ClinearGradient id='s' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%2357d5ef'/%3E%3Cstop offset='1' stop-color='%23c06ff2'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cstyle%3E.r%7Btransform-origin:100px 100px%3Banimation:t 40s linear infinite%7D%40keyframes t%7Bto%7Btransform:rotate(360deg)%7D%7D%40media (prefers-reduced-motion:reduce)%7B.r%7Banimation:none%7D%7D%3C/style%3E%3Ccircle cx='100' cy='100' r='98' fill='url(%23g)'/%3E%3Cg class='r'%3E%3Cpath d='M100 8L179.7 54V146L100 192L20.3 146V54Z' fill='none' stroke='url(%23s)' stroke-width='.9' stroke-dasharray='3 9' stroke-linecap='round'/%3E%3C/g%3E%3Cpath d='M100 32L158.9 66V134L100 168L41.1 134V66Z' fill='none' stroke='url(%23s)' stroke-width='1.2' opacity='.55'/%3E%3Cpath d='M100 54L139.8 77V123L100 146L60.2 123V77Z' fill='none' stroke='%2357d5ef' stroke-width='2.2'/%3E%3Ccircle cx='100' cy='100' r='20.5' fill='none' stroke='%2357d5ef' stroke-width='1.8'/%3E%3Ccircle cx='100' cy='100' r='7.5' fill='%23e7559f'/%3E%3Ccircle cx='100' cy='100' r='13' fill='%23e7559f' opacity='.18'/%3E%3C/svg%3E") center/contain no-repeat;
    filter: drop-shadow(0 0 10px oklch(83% .135 211 / .45)) drop-shadow(0 0 34px oklch(67% .24 340 / .2));
  }
}
@media (min-width: 881px) {
  .foot-statement::after { right: 2%; top: 50%; width: clamp(180px, 20vw, 280px); translate: 0 -50%; opacity: .8; }
}
/* 10/17 pass 2: the reduced-motion rule inside the beacon's SVG does not reach
   it (an image document; measured: its dashed ring still turned with reduced
   motion on), so reduced motion swaps in the same drawing without the turn */
@media (prefers-reduced-motion: reduce) and (min-width: 350px) {
  .foot-statement::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Cdefs%3E%3CradialGradient id='g'%3E%3Cstop offset='0' stop-color='%2357d5ef' stop-opacity='.32'/%3E%3Cstop offset='.6' stop-color='%238a5cf0' stop-opacity='.08'/%3E%3Cstop offset='1' stop-color='%2357d5ef' stop-opacity='0'/%3E%3C/radialGradient%3E%3ClinearGradient id='s' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0' stop-color='%2357d5ef'/%3E%3Cstop offset='1' stop-color='%23c06ff2'/%3E%3C/linearGradient%3E%3C/defs%3E%3Ccircle cx='100' cy='100' r='98' fill='url(%23g)'/%3E%3Cg class='r'%3E%3Cpath d='M100 8L179.7 54V146L100 192L20.3 146V54Z' fill='none' stroke='url(%23s)' stroke-width='.9' stroke-dasharray='3 9' stroke-linecap='round'/%3E%3C/g%3E%3Cpath d='M100 32L158.9 66V134L100 168L41.1 134V66Z' fill='none' stroke='url(%23s)' stroke-width='1.2' opacity='.55'/%3E%3Cpath d='M100 54L139.8 77V123L100 146L60.2 123V77Z' fill='none' stroke='%2357d5ef' stroke-width='2.2'/%3E%3Ccircle cx='100' cy='100' r='20.5' fill='none' stroke='%2357d5ef' stroke-width='1.8'/%3E%3Ccircle cx='100' cy='100' r='7.5' fill='%23e7559f'/%3E%3Ccircle cx='100' cy='100' r='13' fill='%23e7559f' opacity='.18'/%3E%3C/svg%3E"); }
}

/* 10/17: the mark in glass (js/crystal.js via js/glow.js) takes the drawn
   hexagon's place once it has compiled and drawn */
.foot-crystal {
  position: absolute; right: 1%; top: 50%; width: clamp(200px, 22vw, 310px); aspect-ratio: 1; translate: 0 -50%;
  pointer-events: none; opacity: 0; transition: opacity 1.6s var(--ease-out);
}
.foot-crystal .pr-stage, .foot-crystal canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.foot-statement.lf-crystal .foot-crystal { opacity: 1; }
.foot-statement.lf-crystal::after { opacity: 0; transition: opacity 1.2s var(--ease-out); }
@media (max-width: 880px) { .foot-crystal { display: none; } }

/* a section that is off screen pauses every loop inside it (js/glow.js sets .lf-idle) */
.lf-idle *, .lf-idle *::before, .lf-idle *::after { animation-play-state: paused !important; }

/* the closing call-to-action row, shared by every portal */
.lf-cta-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; margin-top: var(--space-lg); }
@media (max-width: 520px) { .lf-cta-row { flex-direction: column; align-items: stretch; } .lf-cta-row .btn { width: 100%; justify-content: center; } }

/* rings and aurora only turn while the card is on screen (js/glow.js sets .lf-on) */
.card.lf-portal:not(.lf-on), .card.lf-portal:not(.lf-on)::before,
.card.featured:not(.lf-on)::before, .card.lf-featured:not(.lf-on)::before { animation-play-state: paused; }

/* tone variables anywhere (a graph's own HTML before learn.js mounts it) */
[data-tone="cyan"] { --tone: var(--color-accent); }
[data-tone="ember"] { --tone: var(--color-ember); }
[data-tone="magenta"] { --tone: var(--color-ignition); }
[data-tone="violet"] { --tone: oklch(72% .17 300); }
[data-tone="ok"] { --tone: var(--color-ok); }

/* ---------------------------------------------------------------------
   REDUCED MOTION / FORCED COLOURS
   --------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .card:hover::before, .card.featured::before, .card.lf-featured::before, .card.lf-portal::before, .card.lf-portal { animation: none; }
  .lf-stage > .card, .lf-stage > .lf-rise { opacity: 1; translate: none; transition: none; }
  .lf-sweep.lf-go { animation: none; background-position: 0 0; }
  .ln-links .ln-flow { animation: none; stroke-dasharray: none; }
}
@media (forced-colors: active) {
  .lf-sweep { background: none; color: CanvasText; }
  .card::before, .lf-field { display: none; }
  /* pure light, no content: out of the way in a forced palette */
  .lf-seam, .cicon .lf-glint, .lf-portal-fx, .foot-crystal, .foot-statement::after, .ln-ripple, .ln-links .ln-tail { display: none; }
  .cicon.lf-armed:not(.lf-drawn) svg * { stroke-dasharray: none; }
}
