/* ==================================================================================================
   site-tech.css — THE SHARED TECHNICAL LAYER
   round 2026-10-16-tech-v4-five-directions · added 2026-10-17 for Jason's techie pass

   Jason, 2026-10-17: "dark backgrounds only · animated beacons with rings · animated icons when
   possible · pages listed in the header, not just within the hamburger menu (except for mobile) ·
   animate the security and trust visual that compares public vs. private and make the private/local
   LLM stand out more · more techie, use more of what is in borja's repo."

   This file is linked BEFORE a direction's skin.css, and it carries the six requirements that are the
   same in every direction. A skin styles *how* those devices look; it may not remove them. Where a skin
   could drop one by accident, the rule here is written at a higher specificity (a `body` prefix) so the
   requirement survives the skin — no `!important` except the reduced-motion kill at the end.

   Pair: tools/tech-runtime.js does the driving (rings, icon draw-in, the dataflow, the step beat, the
   hero field). Nothing here writes, hides or rewords text — the content gate must stay green.
   ================================================================================================== */

:root {
  --tech-ease: cubic-bezier(.22, 1, .36, 1);   /* Liberty Ease — identical to the brand's */
  --tech-beat: 4.5s;                            /* beatdeck's tempo: one beat ≈ 4.5s */
  --tech-fast: .24s;
  --tech-ink: #0A1628; --tech-navy: #1B3A6B; --tech-red: #C8102E;
  --tech-beacon: #F0455F; --tech-red-text: #E5708A;
  --tech-mist: #C9D1E0; --tech-paper: #F4F5F8; --tech-steel: #5A6783;
  --tech-ok: #22B35E;
}

/* ==================================================================================================
   A. THE HEADER — every page in the header, two rows, hamburger only below 1001px
   Row 1 (`.nav-strip`): the tagline, then the company/legal set the header never carried.
   Row 2 (the site's own `<div class="container">`): the existing logo · links · Chat or Book · beacons.
   ================================================================================================== */

body nav .nav-strip {
  position: relative;
  border-bottom: 1px solid rgba(201, 209, 224, .10);
  background: rgba(201, 209, 224, .02);
}
body nav .nav-strip .container {
  display: flex; align-items: center; justify-content: space-between; gap: 24px;
  min-height: 30px; padding-top: 6px; padding-bottom: 6px;
}
/* The INLINE padding is deliberately not reset here. This rule is (0,2,3) and outranks base-landing's
   designed inset for the divider — `border-left: 1px solid rgba(255,255,255,0.16); padding-left: 22px`
   (plus the matching right side, both inside its >=1420px block, (0,1,0)) — so the earlier `padding: 0`
   silently dropped the 22px and left the stroke touching the "Y". Only the block axis is zeroed now: the
   inline axis belongs to the rule that draws the divider, so stroke and inset cannot separate again. */
body nav .nav-strip .nav-tagline {
  position: static; grid-column: auto; grid-row: auto; margin: 0; padding-block: 0; order: 0;
  flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-size: 14px; line-height: 1.25;
}
body nav .nav-utility { display: flex; align-items: center; gap: 22px; flex: none; }
body nav .nav-utility a {
  font-family: 'JetBrains Mono', var(--mono, monospace);
  font-size: 14px; font-weight: 500; letter-spacing: .06em; line-height: 1.2;
  color: rgba(201, 209, 224, .80); text-decoration: none; white-space: nowrap;
  border-bottom: 1px solid transparent; transition: color var(--tech-fast) var(--tech-ease),
  border-color var(--tech-fast) var(--tech-ease);
}
body nav .nav-utility a:hover,
body nav .nav-utility a:focus-visible { color: #fff; border-bottom-color: var(--tech-beacon); }
body nav .nav-utility a[aria-current="page"] { color: #fff; border-bottom-color: var(--tech-red); }
/* the strip's right-hand mark: three hairlines that sweep — no words, pure furniture */
body nav .nav-strip-mark { display: inline-flex; align-items: center; gap: 4px; flex: none; }
body nav .nav-strip-mark i {
  display: block; width: 14px; height: 2px; background: rgba(201, 209, 224, .28);
}
body nav .nav-strip-mark i:nth-child(1) { animation: techSweep 4.5s var(--tech-ease) infinite; }
body nav .nav-strip-mark i:nth-child(2) { animation: techSweep 4.5s var(--tech-ease) .15s infinite; }
body nav .nav-strip-mark i:nth-child(3) { animation: techSweep 4.5s var(--tech-ease) .30s infinite; }

/* the header's beacon pair must survive a skin that tidied them away (v8, v9 did) */
body nav .nav-cta .beacon-nav { display: inline-block; }
/* Above the mobile breakpoint the nine page links are REQUIRED to be visible. Several skins hid
   `.nav-links` below their own (1080–1180px) breakpoint and left only a hamburger — which is exactly
   what Jason saw. This forces the row back and sends the hamburger back to being mobile-only, at a
   specificity a skin cannot outrank. */
@media (min-width: 1001px) {
  body nav .nav-links { display: flex; }
  body nav .menu-toggle { display: none; }
  body nav .nav-cta { margin-left: auto; }
}
@media (max-width: 1000px) {
  body nav .nav-strip { display: none; }
  body nav .nav-cta .beacon-nav:first-child { display: none; }
}
/* 1001–1280px: the full header, tightened so the row holds nine pages and two buttons. Type stays at
   the brand floor of 14px — nothing is shrunk below it and nothing is dropped. */
@media (min-width: 1001px) and (max-width: 1320px) {
  body nav .nav-links { gap: 14px; }
  body nav .nav-cta { gap: 8px; }
  body nav .nav-cta .btn { padding: 9px 13px; }
  body nav .nav-cta .beacon-nav { width: 20px; height: 20px; }
}
/* the tightest band: the strip mark is decoration, so it is the first thing to go, before any label
   loses a pixel of size or a character of its text */
@media (min-width: 1001px) and (max-width: 1180px) {
  body nav .nav-strip-mark { display: none; }
  body nav .nav-strip .container { flex-wrap: wrap; row-gap: 2px; }
  body nav .nav-utility { margin-left: auto; }
  body nav .nav-strip .container,
  body nav .nav-utility { gap: 14px; }
  body nav .nav-utility a { letter-spacing: .02em; }
  /* v6's main row is a hard five-column grid; in this band nine page links plus the lockup and two
     buttons cannot sit in fixed columns without two of them intersecting (measured: 2 overlapping
     children at exactly 1024). Flex + wrap is the honest fix: the row reflows instead of colliding,
     and every label keeps its size and its text. */
  body nav > .container {
    display: flex; flex-wrap: wrap; align-items: center; gap: 8px 18px;
    grid-template-columns: none;
  }
  body nav .nav-links { order: 3; }
  body nav .nav-cta { order: 4; margin-left: auto; }
}
/* below 1001px the whole page set lives in the hamburger (the runtime moves the strip's five links in) */
/* DESKTOP ONLY. Below 1001px the nav-links are a dropdown panel that base-landing paints `var(--ink)`, and
   this bare `background: inherit` wiped it — measured on the phone as `rgba(0,0,0,0)`, with the hero headline
   showing straight through the open menu. Jason: "the hamburger menu isn't easy to read when selected ... needs
   a solid background i guess". */
@media (min-width: 1001px) { body nav.open .nav-links { background: inherit; } }

/* ==================================================================================================
   B. BEACONS — the brand's own ring system, emitting
   The markup is the brand's (circle.ring / .ring2 / .ring3 + the centre node). The base sheet already
   runs `beaconPulse`; this layer adds the wave that leaves the beacon and the halo that holds it, so a
   beacon reads as *live* rather than decorated. Runtime adds `.tech-live`; CSS does the rest.
   ================================================================================================== */

@keyframes techEmit {
  /* HOLDS ITS INK WHILE IT TRAVELS. The old curve was bright only at the beacon and down to .16 by 70% of
     the cycle, so the outward movement — the part that reads as "it loops" — was the faintest part of the
     animation; Jason saw one pass and nothing after it. Held near .55 through the middle, fading at the end. */
  0%   { transform: scale(.45); opacity: .80; }
  9%   { opacity: .95; }
  50%  { opacity: .58; }
  78%  { opacity: .30; }
  100% { transform: scale(4.2); opacity: 0; }
}
@keyframes techHalo {
  0%, 100% { opacity: .28; }
  50%      { opacity: .62; }
}
@keyframes techNode {
  0%, 100% { opacity: .92; }
  50%      { opacity: 1; }
}
@keyframes techSweep {
  0%, 62%, 100% { opacity: .22; }
  18%           { opacity: .9; }
}
@keyframes techCorner { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes techDevicePulse {
  0%, 100% { opacity: .62; transform: scale(1); }
  50%      { opacity: 1; transform: scale(1.12); }
}

/* The homepage's four explore cards carry the site's own icon devices (.sector-orbit, .flow-dots,
   .shield-pulse, .flatline). The base sheet animates some of them; this pins all four as *live* on every
   page that has them, so "animated icons" is true on the page Jason looks at most, in every direction. */
body .sec-card .sector-orbit, body .sec-card .shield-pulse, body .sec-card .flatline,
body .sec-card .flow-dots { animation: techDevicePulse 4.5s var(--tech-ease) infinite; }
body .sec-card .sector-orbit { animation: techCorner 26s linear infinite; transform-origin: 50% 50%; }
body .sec-card .shield-pulse { animation-delay: .9s; }
body .sec-card .flatline { animation-delay: 1.8s; }
body .sec-card .flow-dots { animation-delay: 2.7s; }

body .beacon-nav, body .beacon-mini, body .beacon-inline, body .step-beacon { position: relative; }
body .beacon-nav svg circle.ring, body .beacon-mini svg circle.ring,
body .beacon-inline svg circle.ring, body .step-beacon svg circle.ring {
  transform-box: view-box; transform-origin: 100px 100px;
  animation: beaconPulse 3.6s ease-out infinite, techNode 4.5s var(--tech-ease) infinite;
}
/* the emitting wave: a ring of the same geometry, scaled out and gone, on the beat */
body .tech-emit {
  transform-box: view-box; transform-origin: 100px 100px;
  animation: techEmit 4.5s var(--tech-ease) infinite;
}
body .tech-emit-2 { animation-delay: 1.5s; }
body .tech-emit-3 { animation-delay: 3s; }
body .beacon-nav.tech-live::after, body .beacon-mini.tech-live::after {
  content: ''; position: absolute; inset: -35%; border-radius: 50%;
  border: 1px solid rgba(240, 69, 95, .30); animation: techHalo 4.5s var(--tech-ease) infinite;
  pointer-events: none;
}
/* the hero's corner beacon: the brand's own four-ring composition, turning slowly */
body .beacon-corner svg { animation: techCorner 90s linear infinite; transform-origin: 0% 0%; }

/* ==================================================================================================
   C. ICONS — drawn on entry, then alive
   Jason: "animated icons when possible". Every inline icon on the page draws its own strokes on first
   view, then holds a live state: `.tech-ok` where the icon means protection/verification, `.tech-warn`
   where it means exposure, `.tech-spin` for the processor. The drawing is stroke-dashoffset only, so
   the icon is fully painted the moment it is on screen and nothing about the words changes.
   ================================================================================================== */

@keyframes techDraw { to { stroke-dashoffset: 0; } }
@keyframes techLiveOk {
  0%, 100% { opacity: .78; }
  50%      { opacity: 1; }
}
@keyframes techLiveWarn {
  0%, 100% { opacity: .95; }
  50%      { opacity: .45; }
}
@keyframes techSpin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes techCore {
  0%, 100% { opacity: .35; }
  50%      { opacity: 1; }
}

body .sec-icon svg, body .df-band-ic svg, body .df-ext-ic svg, body .df-llm-ic svg, body .df-foot svg,
body .df-wall .x svg, body .principle h4 svg, body .step svg, body .hc-callout svg {
  overflow: visible;
}
body .sec-icon.tech-draw svg [stroke]:not([stroke="none"]),
body .df-band-ic.tech-draw svg [stroke]:not([stroke="none"]),
body .df-ext-ic.tech-draw svg [stroke]:not([stroke="none"]),
body .df-llm-ic.tech-draw svg [stroke]:not([stroke="none"]),
body .df-wall .x.tech-draw svg [stroke]:not([stroke="none"]),
body .principle.tech-draw h4 svg [stroke]:not([stroke="none"]) { animation: techDraw 1.0s var(--tech-ease) forwards; }

body .sec-icon.tech-ok svg { animation: techLiveOk 4.5s var(--tech-ease) infinite; }
body .df-llm-ic.tech-spin svg { animation: techSpin 26s linear infinite; transform-origin: 50% 50%; }
body .df-ext-ic.tech-warn svg { animation: techLiveWarn 3s var(--tech-ease) infinite; }
body .df-band-ic.tech-warn svg { animation: techLiveWarn 3s var(--tech-ease) infinite; }
body .df-band-ic.tech-ok svg { animation: techLiveOk 4.5s var(--tech-ease) infinite; }
body .df-llm-ic .tech-core { animation: techCore 4.5s var(--tech-ease) infinite; }

/* ==================================================================================================
   D. THE SECURITY & TRUST VISUAL — public vs. private, animated, with the private side the answer
   Structure (frozen content, never edited): .dataflow > .df-grid > .df-panel.bad | .df-panel.good.
   Each panel: .df-flow > [.df-box.yours] [.df-track out|loop] [.df-box.ext|.df-box.llm].
   What this layer adds: the perimeter, the packets the runtime moves, the chip states, the
   containment sweep, and the weight — the private panel is the answer, so it is the heavier object.
   ================================================================================================== */

body .dataflow .df-track { position: relative; }
body .dataflow .df-grid { align-items: start; }

/* — the packet: the request itself, travelling. Runtime appends it; CSS decides the journey. — */
body .dataflow .tech-packet {
  position: absolute; top: 50%; left: 2px; width: 7px; height: 7px; margin-top: -3.5px;
  border-radius: 50%; background: var(--tech-beacon);
  box-shadow: 0 0 0 3px rgba(240, 69, 95, .16);
}
body .dataflow .tech-packet.tech-ok { background: var(--tech-ok); box-shadow: 0 0 0 3px rgba(34, 179, 94, .18); }
@keyframes techPacketOut {
  0%   { left: 2px; opacity: 0; }
  8%   { opacity: 1; }
  78%  { left: calc(100% - 9px); opacity: 1; }
  100% { left: calc(100% - 9px); opacity: 0; }
}
@keyframes techPacketLoop {
  0%   { left: 2px; opacity: 0; }
  10%  { opacity: 1; }
  46%  { left: calc(100% - 9px); opacity: 1; }
  54%  { left: calc(100% - 9px); opacity: 1; }
  92%  { left: 2px; opacity: 1; }
  100% { left: 2px; opacity: 0; }
}
body .dataflow .tech-packet[data-leg="out"] { animation: techPacketOut 1.15s var(--tech-ease) infinite; }
body .dataflow .tech-packet[data-leg="loop"] { animation: techPacketLoop 9s var(--tech-ease) infinite; }

/* — the public side: what happens to a chip once it has been sent — */
@keyframes techSent {
  0%, 12%   { opacity: 1; }
  22%, 100% { opacity: .42; }
}
body .dataflow .df-panel.bad .df-chip { transition: opacity var(--tech-fast) var(--tech-ease); }
body .dataflow .df-panel.bad .df-chip.tech-sent { animation: techSent 9s var(--tech-ease) infinite; }
body .dataflow .df-panel.bad .df-chip.tech-sent::after {
  content: '›'; margin-left: 6px; color: var(--tech-beacon); font-family: 'JetBrains Mono', monospace;
}
body .dataflow .df-panel.bad .df-track .dot { animation: techTravelDots 1.15s linear infinite; }
@keyframes techTravelDots {
  0%, 100% { opacity: .25; }
  50%      { opacity: .95; }
}
body .dataflow .df-panel.bad .df-box.ext { animation: techReceive 4.5s var(--tech-ease) infinite; }
@keyframes techReceive {
  0%, 100% { border-color: rgba(240, 69, 95, .22); }
  16%      { border-color: rgba(240, 69, 95, .62); }
}
/* the wall the data crosses: drawn, and it stays crossed */
body .dataflow .df-panel.bad .df-perim { background-image: repeating-linear-gradient(
  90deg, rgba(240, 69, 95, .55) 0 1px, transparent 1px 4px); }

/* — the private side: a perimeter, a closed loop, a verified model. Nothing leaves. — */
body .dataflow .df-panel.good .df-env { position: relative; }
body .dataflow .df-panel.good .df-env::before {
  content: ''; position: absolute; inset: -14px -12px -10px; border: 1px solid rgba(34, 179, 94, .28);
  border-radius: 4px; pointer-events: none;
}
body .dataflow .df-panel.good .df-env::after {
  content: ''; position: absolute; left: -12px; right: -12px; top: -14px; height: 1px;
  background: linear-gradient(90deg, transparent, rgba(34, 179, 94, .75), transparent);
  animation: techContain 4.5s var(--tech-ease) infinite; pointer-events: none;
}
@keyframes techContain {
  0%   { transform: translateY(0); opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { transform: translateY(var(--tech-env-h, 220px)); opacity: 0; }
}
body .dataflow .df-panel.good .df-box.yours .df-chip::before {
  content: ''; display: inline-block; width: 5px; height: 5px; margin-right: 6px; border-radius: 50%;
  background: rgba(34, 179, 94, .55); vertical-align: middle;
}
body .dataflow .df-panel.good .df-box.llm { animation: techCompute 4.5s var(--tech-ease) infinite; }
@keyframes techCompute {
  0%, 100% { border-color: rgba(201, 209, 224, .18); }
  48%      { border-color: rgba(34, 179, 94, .55); }
}

/* — THE WEIGHT: the private panel is the answer, so it leads. Measured by tools/tech-audit.py.
     The heading sizes are pinned here rather than left to the skin: "make the private/local LLM stand
     out more" is a requirement of this round, and a skin that happened to set the public heading larger
     (v8 did) would invert it. The private heading is always the larger of the two. — */
body .dataflow .df-panel .df-band h4 { font-size: clamp(17px, 1.35vw, 22px); }
body .dataflow .df-panel.good.tech-answer .df-band h4 { font-size: clamp(19px, 1.55vw, 26px); }
body .dataflow .df-panel.good.tech-answer {
  transform: translateY(-8px);
  border-width: 2px;
  border-image: linear-gradient(160deg, #1B3A6B 0%, #21406F 45%, #33508A 100%) 1;
  box-shadow: 0 30px 60px -34px rgba(2, 6, 14, .95), 0 0 0 .5px rgba(201, 209, 224, .10);
  z-index: 2;
}
body .dataflow .df-panel.good.tech-answer .df-band h4 { font-size: clamp(19px, 1.55vw, 26px); }
body .dataflow .df-panel.good.tech-answer .df-tag.ok { background: rgba(34, 179, 94, .16); }
body .dataflow .df-panel.bad { opacity: .84; }
body .dataflow .df-panel.bad.tech-answer { opacity: .84; filter: saturate(.86); }

/* the ring system behind the private model — geometry, not a glow */
body .dataflow .df-box.llm.tech-rings { position: relative; }
body .dataflow .df-box.llm.tech-rings .tech-ring {
  position: absolute; left: 50%; top: 50%; width: 96px; height: 96px; margin: -48px 0 0 -48px;
  border: 1px solid rgba(240, 69, 95, .22); border-radius: 50%; pointer-events: none;
}
body .dataflow .df-box.llm.tech-rings .tech-ring:nth-child(2) {
  width: 150px; height: 150px; margin: -75px 0 0 -75px; border-color: rgba(201, 209, 224, .14);
  animation: techHalo 4.5s var(--tech-ease) 1.5s infinite;
}
body .dataflow .df-box.llm.tech-rings .tech-ring:nth-child(3) {
  width: 210px; height: 210px; margin: -105px 0 0 -105px; border-color: rgba(27, 58, 107, .55);
  animation: techHalo 4.5s var(--tech-ease) 3s infinite;
}
body .dataflow .df-answer-mark {
  position: absolute; right: -8px; top: -8px; width: 10px; height: 10px; border-radius: 50%;
  background: var(--tech-beacon); animation: techHalo 4.5s var(--tech-ease) infinite;
}

/* ==================================================================================================
   E. THE STEPS — how-it-works' six numbered stages, wired and beating
   The orchestration dot below still belongs to tools/behaviour.js (scroll-driven). This is the six-step
   rail above it: a drawn wire between the stages and one stage lit per beat.
   ================================================================================================== */

body .steps { position: relative; }
body .steps .step { transition: border-color var(--tech-fast) var(--tech-ease); }
body .steps .step.tech-live { border-color: rgba(240, 69, 95, .55); }
body .steps .step.tech-live .step-num { color: var(--tech-red-text); }
body .steps .step .tech-wire {
  position: absolute; right: -1px; top: 50%; width: 100%; height: 1px; transform-origin: left center;
  background: linear-gradient(90deg, rgba(201, 209, 224, .06), rgba(240, 69, 95, .55));
  animation: techWire 2.4s var(--tech-ease) infinite; pointer-events: none;
}
@keyframes techWire {
  0%   { transform: scaleX(0); opacity: 0; }
  18%  { opacity: 1; }
  70%  { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}
body .steps .step.tech-live .step-beacon { animation: techHalo 2.4s var(--tech-ease) infinite; }

/* ==================================================================================================
   F. THE HERO FIELD — the machine idling behind the headline
   A canvas the runtime paints (drifting nodes, hairlines under 120px). It sits behind everything, is
   never interactive, never carries words, and is dropped below 700px and under Reduce Motion.
   ================================================================================================== */

body .tech-field {
  position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0;
  opacity: .55;
}
body .hero > .container { position: relative; z-index: 1; }
/* The field used to be dropped below 700px, and the runtime refused to draw it there either — which is why
   the constellation simply was not on a phone. Jason: "i don't see the constellations or other animations.
   this should be fixed." It runs on phones now at a lighter density (the runtime scales the node count and
   caps the pixel ratio); Reduce Motion still removes it. */
@media (max-width: 700px) {
  /* Jason: "make the constellation items more visible" — the phone field was held at half the desktop's
     opacity, which is most of why it read as barely there. The drift is also calmer on phones now: see the
     velocity note in heroField(). */
  body .tech-field { display: block; opacity: .52; }
}

/* ==================================================================================================
   G. THE TECHNICAL FURNITURE every direction inherits: a mono rail, corner marks, a scope sweep.
   ================================================================================================== */

@keyframes techScan {
  0%   { transform: translateY(-100%); opacity: 0; }
  10%  { opacity: .9; }
  90%  { opacity: .9; }
  100% { transform: translateY(1000%); opacity: 0; }
}
body .tech-corner-marks::before, body .tech-corner-marks::after {
  content: ''; position: absolute; width: 10px; height: 10px; border: 1px solid rgba(201, 209, 224, .34);
  pointer-events: none;
}
body .tech-corner-marks::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; }
body .tech-corner-marks::after { bottom: -1px; right: -1px; border-left: 0; border-top: 0; }

/* ==================================================================================================
   H. REDUCE MOTION — LAST, AND IT MUST STOP EVERYTHING
   Written with `!important` because this sheet loads before a direction's skin: an `!important`
   declaration beats any later non-important one, so a skin cannot leave a loop running past it.
   tools/motion-audit.py is the instrument that proves it: 0 animating, 0 transitioning.
   ================================================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    animation-delay: 0s !important;
    animation-play-state: paused !important;
    transition-duration: .001ms !important;
    transition-delay: 0s !important;
    scroll-behavior: auto !important;
  }
  body .tech-field, body .dataflow .tech-packet, body .tech-emit { display: none !important; }
  body .dataflow .df-panel.good .df-env::after { display: none !important; }
}

/* ==================================================================================================
   I. THE PULSE IS SLIM, AND A BEACON REPLACES A BULLET
   Jason, 2026-10-17: "reduce the thickness of the pulsing beacon rings across the pages" and "if there is
   a bullet and a beacon ring, remove the bullet and only use the beacon ring with animation".

   (a) THE THINNING. The brand's rings carry stroke-widths of 4.4-12 units in a 200-unit viewBox and the
   pulse scales them up to 5x, so the wave left the beacon as a band roughly 5x thicker than the ring it
   came from — at a 24px host that is a ~5px stripe. This replaces the pulse on the four beacon hosts with
   one that THINS AS IT EXPANDS (transform scale .5 -> 4.4, stroke-width 6.4 -> 2.0): the ring keeps the
   weight it needs to be seen at rest (0.9px at 28px, the base sheet's own conclusion after 3 units proved
   invisible on ink) and the wave is a hairline by the time it is wide. Written at a specificity a skin
   cannot outrank, because the base sheet and v11 both raise these strokes to 9 and 12.

   (b) THE BULLET. Where a mark is drawn *and* a beacon ring sits in the same element, the mark goes: the
   ring is the live indicator and two markers in one place is one too many. This is written with `:has()`
   so it holds for any element that carries a beacon-inline, not just the ones flagged today. */
@keyframes techBeaconSlim {
  /* THE WEIGHT IS NOW PER ELEMENT. Jason: "reduce the thickness of the rings and then try to fit them on
     each side." These two values ARE the ring weight, and an animation outranks a normal rule — which is
     why a stylesheet override thinned the beacon's static ring and left the animated ones untouched.
     `--ring-k` defaults to 1, so every existing beacon renders exactly as before; a host that sets it
     gets thinner rings without losing the pulse. */
  0%   { transform: scale(.5);  opacity: .88; stroke-width: calc(6.4px * var(--ring-k, 1)); }
  55%  { opacity: .32; }
  100% { transform: scale(4.4); opacity: 0;   stroke-width: calc(2px * var(--ring-k, 1)); }
}
body .beacon-nav svg circle.ring, body .beacon-mini svg circle.ring,
body .beacon-inline svg circle.ring, body .step-beacon svg circle.ring,
body .beacon-nav svg circle.ring2, body .beacon-mini svg circle.ring2,
body .beacon-inline svg circle.ring2, body .step-beacon svg circle.ring2,
body .beacon-nav svg circle.ring3, body .beacon-mini svg circle.ring3,
body .beacon-inline svg circle.ring3, body .step-beacon svg circle.ring3 {
  animation: techBeaconSlim 3.6s ease-out infinite, techNode 4.5s var(--tech-ease) infinite;
}
body .beacon-nav svg circle.ring2, body .beacon-mini svg circle.ring2,
body .beacon-inline svg circle.ring2, body .step-beacon svg circle.ring2 { animation-delay: 1.2s, 1.2s; }
body .beacon-nav svg circle.ring3, body .beacon-mini svg circle.ring3,
body .beacon-inline svg circle.ring3, body .step-beacon svg circle.ring3 { animation-delay: 2.4s, 2.4s; }

/* the bullet goes where the ring is */
.principle h4:has(.beacon-inline)::before,
.usecase-line:has(.beacon-inline)::before,
.uc-card:has(.beacon-inline)::before,
.eyebrow:has(.beacon-inline)::before,
h3:has(.beacon-inline)::before, h4:has(.beacon-inline)::before, p:has(.beacon-inline)::before,
li:has(.beacon-inline)::before {
  content: none !important; display: none !important; width: 0 !important; background: none !important;
}

/* ==================================================================================================
   J. A CARD'S ACTION ROW SITS ON ONE LINE ACROSS THE ROW
   Jason, 2026-10-17: "particularly when you are looking at a series of cards like on the homepage, 'see
   the process' is at the same line level as the other directions on the other cards." Measured on the
   homepage: the four explore cards' action rows were at 1016 / 1041 / 1016 / 1016 — 25px apart, because
   one description wraps to an extra line. The card is a column, and its trailing LINK is pushed to the
   bottom, so the action row aligns whatever the copy above it does. Only a trailing link is pushed: a
   card whose last child is body copy must not gain a gap between its heading and its text. */
   NOTE: the flex-column + `margin-top:auto` mechanics for this live in the direction's own skin
   (`directions/v11-operator/skin.css` §21), not here. It is a structural change to every card in a
   system, and it was measured on v11; pushing it into twelve skins nobody has re-run would be an
   unverified refactor. The ring thinning and the bullet rule above are presentational and stay shared.

/* ==================================================================================================
   K. SLOWER RINGS, AND RINGS IN EVERY PAGE'S INTRO
   Jason, 2026-10-17: "add the moving rings to the intro section of each page too. i like them, but slow
   them down a little too."

   (a) SLOWED. Every ring beat is now 6.4s instead of 4.5s (+42%) and the corner composition turns over
   140s instead of 90 (+56%): the pulse reads as a slow instrument breathing rather than a blinking
   indicator. Nothing else changes — the sequence, the thinning and the stagger all stay.

   (b) THE INTRO RINGS. The runtime clones the brand's own four-ring composition (`.beacon-corner`'s own
   SVG — the artwork that already sits in the hero and the closing band) into the first content section of
   every page, as a decorative layer behind the heading block. It is masked and dies out before the
   section's bottom edge, so it reads as the room's atmosphere rather than a graphic dropped on the page.
   Dropped below 701px, and stopped entirely under Reduce Motion. */
html:root { --tech-beat: 6.4s; }   /* `html:root` out-specifies the `:root` declared in section B */

body .beacon-nav svg circle.ring, body .beacon-mini svg circle.ring,
body .beacon-inline svg circle.ring, body .step-beacon svg circle.ring,
body .beacon-nav svg circle.ring2, body .beacon-mini svg circle.ring2,
body .beacon-inline svg circle.ring2, body .step-beacon svg circle.ring2,
body .beacon-nav svg circle.ring3, body .beacon-mini svg circle.ring3,
body .beacon-inline svg circle.ring3, body .step-beacon svg circle.ring3 {
  animation: techBeaconSlim 6.4s ease-out infinite, techNode 6.4s var(--tech-ease) infinite;
}
body .beacon-nav svg circle.ring2, body .beacon-mini svg circle.ring2,
body .beacon-inline svg circle.ring2, body .step-beacon svg circle.ring2 { animation-delay: 2.13s, 2.13s; }
body .beacon-nav svg circle.ring3, body .beacon-mini svg circle.ring3,
body .beacon-inline svg circle.ring3, body .step-beacon svg circle.ring3 { animation-delay: 4.26s, 4.26s; }
body .tech-emit { animation-duration: 6.4s; }
body .tech-emit-2 { animation-delay: 2.13s; }
body .tech-emit-3 { animation-delay: 4.26s; }
body .beacon-nav.tech-live::after, body .beacon-mini.tech-live::after { animation-duration: 6.4s; }
body .beacon-corner svg { animation-duration: 140s; }
body .sec-card .sector-orbit, body .sec-card .shield-pulse, body .sec-card .flatline,
body .sec-card .flow-dots { animation-duration: 6.4s; }
body nav .nav-strip-mark i { animation-duration: 6.4s; }

body .tech-intro-rings {
  position: absolute; top: -86px; right: 0; width: min(620px, 68%); height: 620px;
  overflow: hidden;                 /* the layer can never paint outside the page box */
  z-index: 0; pointer-events: none; opacity: .42;
  mask-image: radial-gradient(closest-side at 42% 58%, #000 26%, rgba(0, 0, 0, .45) 62%, transparent 100%);
  -webkit-mask-image: radial-gradient(closest-side at 42% 58%, #000 26%, rgba(0, 0, 0, .45) 62%, transparent 100%);
}
body .tech-intro-rings svg {
  width: 100%; height: 100%; display: block; overflow: hidden;
  animation: techCorner 140s linear infinite; transform-origin: 98% 2%;
}
body .tech-intro-rings + .container, body .tech-intro-rings ~ .container { position: relative; z-index: 1; }
@media (max-width: 900px) {
  body .tech-intro-rings { width: min(420px, 72%); height: 420px; top: -60px; right: 0; opacity: .34; }
}
/* and the top section's rings were dropped below 700px too; they run at a phone size now */
@media (max-width: 700px) {
  body .tech-intro-rings { display: block; width: min(320px, 80%); height: 320px; top: -44px; right: 0; opacity: .5; }
}


/* ==================================================================================================
   L. THE SECURITY VISUAL, AS JASON ASKED (2026-10-17)

   "on secure/trust page, move this text, 'With a public AI service, every prompt and file you send leaves
   your network.', over the public box and put a red shadow around it and a green shadow around the
   private"  ·  "and make the scanner bar slower and green too"

   THE TEXT OVER THE PUBLIC BOX. That sentence and its private twin are ONE paragraph in the frozen
   content, so they cannot be moved independently without splitting the copy — and the gate compares the
   word sequence. Multicolumn does it without touching the DOM: the intro block becomes one full-width
   column and its paragraph flows into TWO columns, so the public sentence sits over the public panel and
   the private sentence over the private one, in reading order, word order untouched.

   THE SHADOWS. Red around the public panel, green around the private one — a coloured shadow is a shadow,
   i.e. light rather than type, so it does not touch the contrast maths. (Flagged once: the CMO ruling
   forbids "glow on cards"; this is Jason's explicit call for it and the brand's red/green semantics.)

   THE SCANNER BAR. The only travelling bar on this page is the private panel's containment line, which
   sweeps down the sealed environment. It is now 9s instead of 4.5s — clearly slower, in step with the
   slowed rings — and unmistakably green: full-alpha #22B35E at 2px instead of a 75% fade at 1px. */
body .dataflow .df-head { display: block; max-width: none; }
body .dataflow .df-head p {
  max-width: none; width: 100%; column-count: 2; column-gap: 40px; column-rule: 1px solid rgba(201, 209, 224, .14);
}
body .dataflow .df-head { border-left: 2px solid rgba(200, 16, 46, .55); border-right: 2px solid rgba(34, 179, 94, .55); padding: 0 clamp(12px, 1.6vw, 22px); }
body .dataflow .df-panel.bad {
  box-shadow: 0 0 0 1px rgba(200, 16, 46, .40), 0 22px 60px -30px rgba(200, 16, 46, .55);
}
body .dataflow .df-panel.good, body .dataflow .df-panel.good.tech-answer {
  box-shadow: 0 0 0 1px rgba(34, 179, 94, .40), 0 26px 70px -32px rgba(34, 179, 94, .55);
}
body .dataflow .df-panel.good .df-env::after {
  height: 2px; background: linear-gradient(90deg, rgba(34, 179, 94, 0), #22B35E 30%, #22B35E 70%, rgba(34, 179, 94, 0));
  animation-duration: 9s;
}

/* ==================================================================================================
   M. THE SCANNER BAR — a real element, green, 9s
   The private environment's sweep was a pseudo-element and the skin's own `.df-env::after` was beating it
   (measured: no animation, no background — the bar Jason is watching for was not the one being driven).
   It is now `.tech-scan`, appended by the runtime, so nothing can collide with it.
   Green: full-alpha #22B35E, the same green the panel's edge and the containment note use. Slow: 9s, in
   step with the slowed ring beat. */
body .dataflow .df-panel.good .df-env { position: relative; }
body .dataflow .tech-scan {
  position: absolute; left: 0; right: 0; top: 0; height: 2px; z-index: 2; pointer-events: none;
  background: linear-gradient(90deg, rgba(34, 179, 94, 0), #22B35E 26%, #22B35E 74%, rgba(34, 179, 94, 0));
  box-shadow: 0 0 12px rgba(34, 179, 94, .55);
  animation: techContain 9s var(--tech-ease) infinite;
}

/* ==================================================================================================
   N. THE INTRO RINGS MOVE THE WAY THE BOTTOM BAND DOES
   Jason, 2026-10-17: "i don't see the rings moving in the various page sections like it is in the bottom
   one." Measured: the intro layer was rotating at 140s/revolution — 10.3 degrees in four seconds, real
   motion and far too slow to notice. The closing band reads as alive because its beacons emit a ring every
   beat (6.4s). So the intro now does the same: the rotation comes down to 48s (still calm — 7.5 deg/s) and
   two waves leave the ring centre on a 12.8s stagger, expanding and fading. Same geometry as the art's own
   innermost ring; nothing redrawn. */
@keyframes techIntroWave {
  /* the slim variant — same reasoning as techEmit: legible through the travel, fading only at the end */
  0%   { transform: scale(1);    opacity: .52; stroke-width: 3.2; }
  10%  { opacity: .74; }
  52%  { opacity: .52; stroke-width: 2.6; }
  80%  { opacity: .28; stroke-width: 1.8; }
  100% { transform: scale(4.6);  opacity: 0;   stroke-width: 1.1; }
}
body .tech-intro-rings svg { animation-duration: 48s; }
body .tech-intro-emit {
  transform-box: view-box; transform-origin: 1180px 1180px;
  animation: techIntroWave 12.8s var(--tech-ease) infinite;
}
body .tech-intro-emit-2 { animation-delay: 6.4s; }
@media (prefers-reduced-motion: reduce) { body .tech-intro-emit { display: none !important; } }

/* ==================================================================================================
   O. THE COMPARISON'S OWN SPACING (measured at 1440 on the security page)
   The two panels were FLUSH — `column-gap: 0` — so a red-bordered box and a green-bordered box shared one
   edge and read as a single box divided in half. The row now has the same 32px gap the intro paragraph's
   two columns use, so each intro sentence sits over its own panel, and the panels stretch to a shared
   bottom edge instead of ending 109px apart.
   The private panel's 8px lift is gone with it: a comparison whose two halves start on different lines is
   not a comparison. The private side leads on weight instead — 2px gradient edge, larger heading, the
   ring system, and now the green cast. */
body .dataflow .df-grid { column-gap: 32px; align-items: stretch; }
body .dataflow .df-panel.good, body .dataflow .df-panel.good.tech-answer { transform: none; }
body .dataflow .df-head p { column-gap: 32px; }

/* P. THE SPLIT IS FORCED, NOT MEASURED
   §L used `column-count: 2` alone, which balances by height and broke the intro mid-sentence, orphaning
   the private clause over the public panel. §O measured an offset, which overflowed into a third column
   when sentence two ran longer than sentence one. The runtime now wraps the second sentence in
   `.tech-split` (textContent byte-identical; the gate proves it) and the break is declared: sentence two
   starts column two at every width. The column gap matches the panel gap, so each sentence sits over its
   own panel. */
body .dataflow .df-head p { column-fill: balance; column-gap: 32px; max-height: none; }
body .dataflow .tech-split { break-before: column; }
/* `break-before` is ignored by an inline box, so the wrapper is a block: sentence one is an anonymous
   block, the break lands between the two blocks, and sentence two opens column two. Same words, same
   order — the gate still has to prove it. */
body .dataflow .tech-split { display: block; break-before: column; }

/* Below 1024 the two panels stack, so a two-column intro would sit over the wrong thing: the paragraph
   goes back to one column and the wrapper back to inline (no forced break, no line of its own). */
@media (max-width: 1023px) {
  body .dataflow .df-head p { column-count: 1; }
  body .dataflow .tech-split { display: inline; break-before: auto; }
}

/* ==================================================================================================
   Q. THE SCANNER SCANS THE SYSTEMS BOX — down, then back up, looping
   Jason, 2026-10-17: "have it start at the top of the your systems box and then stop at the bottom of
   that box and scan back up, then loop that." The bar lives inside `.df-box.yours` now and travels that
   box's own inner height (`--tech-scan-h`, set by the runtime and refreshed on resize), so it starts on
   the box's top edge and turns around at its bottom edge. `alternate` + `infinite` is the loop; 4.5s per
   leg keeps it in step with the slowed ring beat. */
body .dataflow .df-box.yours { position: relative; }
@keyframes techScanBox {
  from { transform: translateY(0); }
  to   { transform: translateY(var(--tech-scan-h, 120px)); }
}
body .dataflow .tech-scan {
  top: 0; left: 0; right: 0; height: 2px; z-index: 3;
  /* Jason: "slow down the bar scanning animation." 4.5s -> 7.5s, both widths, so it stays as consistent
     across phone and desktop as the rest of the motion layer. */
  animation: techScanBox 7.5s var(--tech-ease) infinite alternate;
}

/* R. EACH INTRO SENTENCE IS CENTRED OVER ITS OWN PANEL
   Jason, 2026-10-17: "the text directly above each of the prive/public boxes needs to be aligned better.
   thinking they should be centered over their related boxed". The paragraph's two columns already share
   the panels' columns and 32px gutter, so centring inside each column centres each sentence over the
   panel it belongs to — at every width, because the columns follow the panels. */
body .dataflow .df-head p { text-align: center; }

/* S. THE PUBLIC SIDE'S CONNECTOR IS MISSING
   Found while checking the security page after the alignment pass: the private panel's `.df-track` carries
   a gradient line (`.df-panel.good .df-track::before`) and the public panel's carries NOTHING — measured:
   `.df-track 494x46 display=block bg=none`, no `::before`. In a two-sided comparison an arrow on one side
   and a bare gap on the other reads as a broken diagram, which is what the review flagged as "a small red
   tick instead of the centred dotted arrow". The public side gets the same line in the public tone. */
body .dataflow .df-panel.bad .df-track { position: relative; }
body .dataflow .df-panel.bad .df-track::before {
  content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 1px;
  background: linear-gradient(180deg, rgba(200, 16, 46, .55), rgba(200, 16, 46, .12));
}

/* ==================================================================================================
   T. THE TOP-SECTION RINGS ARE NOW ACTUALLY VISIBLE (Jason: "i can't see the traveling beacon rings on
   the pages' top section")
   Measured on the shipped build: the layer sat at 42% opacity, its seven rings each carried their own
   opacity (1 · .7 · .45 · .4 · .3 · .22) THROUGH that 42%, and every stroke was 2 units in a 1200 viewBox
   drawn into a 620px box — so the outer rings painted at about 9% alpha, and the two travelling waves
   animated 3.2 → 1.1 units, i.e. 1.65px → 0.57px. Faint strokes at 9% on a dark ground with a grid over
   them do not read as motion, which is exactly what he reported rather than a bug in the animation.
   Nothing about the rotation or the wave timing changes — only how much ink the rings put down. */
body .tech-intro-rings { opacity: .82; }
body .tech-intro-rings svg circle:not(.tech-intro-emit) { stroke-width: 3; }
@keyframes techIntroWave {
  /* the stroke thins far less than it did (8 -> 3.2 before): in this 1200-unit composition a 3-unit stroke
     drawn at scale 4 is about 1.5 device pixels, so the far half of the travel was legible only up close. */
  0%   { transform: scale(1);   opacity: .85; stroke-width: 8; }
  10%  { opacity: .95; }
  52%  { opacity: .62; stroke-width: 7; }
  80%  { opacity: .38; stroke-width: 6; }
  100% { transform: scale(4.6); opacity: 0;   stroke-width: 5.5; }
}

/* ==================================================================================================
   U. THE CORNER RINGS TURN A LITTLE FASTER ON THE HOMEPAGE (Jason: "on the landing page, speed up the
   rings coming down from the top just a little bit.")
   The brief that set these rings asked for them SLOW, so they went 90s -> 140s per revolution. Measured at
   140s that is 2.6 deg/s: the arcs sweeping down across the hero read as almost still. "A little bit" is
   taken literally — 110s, a 27% increase in speed, still far slower than the 90s they started at, so the
   calm the earlier pass asked for is kept. */
body .beacon-corner svg { animation-duration: 110s; }

/* ==================================================================================================
   V. THE CORNER RINGS COME DOWN FASTER (Jason, again: "the homepage rings coming down from the top. they
   need to come in a little faster")
   90s originally, then 140s for the "slow them down" pass, then 110s when he asked for a little faster.
   Measured at 110s that is 3.28 deg/s and he is still seeing them as slow, so this pass takes them to 80s:
   4.50 deg/s, a further 37%. This is the third value this device has carried, so it is worth saying plainly
   — it is now faster than the 90s it started at, which is where the two requests have led. */
body .beacon-corner svg { animation-duration: 80s; }

/* ==================================================================================================
   W. THE HALOS THAT COULD NOT BE RE-PHASED (Jason: "i am not seeing the flying/traveling beacons looping
   with random starting points in the top area of the sub pages")
   Measured on two loads of a sub page: the travelling rings inside the beacon SVGs DID re-phase
   (`-3.70/-1.78/-0.12` then `-1.83/1.08/-1.94`) and so did the intro waves (`-6.38/5.54` then `-9.07/2.54`),
   but three devices came back identical every time:
     · `div.beacon-nav.tech-live::after` → techHalo 6.4s, delay 0s
     · `span.tech-ring` → techHalo 4.5s, delays 1.5s / 3s
     · `span.df-answer-mark` → techHalo 4.5s, delay 0s
   The first is a PSEUDO-ELEMENT: an inline `animation-delay` on the host never reaches its own `::after`,
   so the delay has to travel through a custom property, which is what these rules read. The other two are
   ordinary elements that simply were not in the runtime's selector list. */
body .beacon-nav.tech-live::after,
body .beacon-mini.tech-live::after,
body .beacon-inline.tech-live::after { animation-delay: var(--tech-rand, 0s); }

/* ----------------------------------------------------------------------------------------------------
   THE FOOTER TAGLINE'S "YOUR FREEDOM." IS SIGNAL RED ON EVERY PAGE
   Jason: "check the about, privacy, terms etc, footers and make your freedom red". The skin colours it
   through `.footer-bottom .tag-line .accent-signal`, which the doc-layout footers (overview, privacy,
   terms, contact) are not inside — so the rule is restated here without that scope. The runtime supplies
   the span on the pages whose footer text does not carry one. Weight stays 400: Jason asked for the tagline
   as a whole to be unbold, and only its last two words take the colour. FLAGGED ONCE, unchanged from the
   earlier pass: Signal Red as type on Ink measures 3.08:1, so this is display furniture rather than body
   copy, which is how it is already treated on the other six pages. */
body footer .tag-line .accent-signal,
body .doc footer .tag-line .accent-signal,
body footer .tag-line .tech-footer-accent { color: #C8102E; font-weight: 400; }

@media (prefers-reduced-motion: reduce) {
  body footer .tag-line .tech-footer-accent { color: #C8102E; }
}

/* ----------------------------------------------------------------------------------------------------
   THE CLOSING BAND'S RING COMPOSITION (added by the runtime's `ctaRings` block)
   Jason: "I see the traveling rings in the top areas, but not in the CTA area; please fix." The band's art
   is the same composition as the intro's, sized for a shallower box — the closing band is ~500-700px tall
   against a section's thousands, so a 620px layer would dominate it. Content inside the box is lifted above
   the layer so the words and buttons are never behind it. */
body .tech-cta-rings {
  position: absolute; top: -90px; right: 0; width: min(460px, 62%); height: 460px;
  overflow: hidden; z-index: 0; pointer-events: none; opacity: .34;
  mask-image: radial-gradient(closest-side at 68% 34%, #000 30%, rgba(0,0,0,.5) 66%, transparent 100%);
  -webkit-mask-image: radial-gradient(closest-side at 68% 34%, #000 30%, rgba(0,0,0,.5) 66%, transparent 100%);
}
body .cta-box > *:not(.tech-cta-rings) { position: relative; z-index: 1; }
@media (max-width: 900px) {
  body .tech-cta-rings { width: min(340px, 78%); height: 340px; top: -70px; opacity: .32; }
}
@media (max-width: 700px) {
  body .tech-cta-rings { display: block; width: min(280px, 84%); height: 280px; top: -46px; opacity: .38; }
}
