/* ============================================================
   BrokerageLoop — ambient background effects & micro-interactions

   Two separate systems live in this file, and they are deliberately kept
   apart:

   1. AMBIENT — slow, low-contrast, decorative. Runs on its own without the
      reader doing anything, over 9–19s. Never carries meaning, always
      aria-hidden, always the first thing dropped under reduced motion.

   2. RESPONSE — fast, cause-and-effect. Only ever fires because the reader
      pointed at something, scrolled to something or hovered something.

   The budget rule (never animate more than one or two things at once in a
   given view) is met by keeping AMBIENT to a single coordinated system per
   band — a sweep plus its packets read as one machine, not five effects —
   and by making everything in RESPONSE strictly on-demand.

   Everything here animates transform/opacity only, so it composites on the
   GPU and cannot trigger layout. base.css already neutralises animations
   under prefers-reduced-motion; the block at the foot of this file also
   removes the moving layers outright rather than leaving them frozen
   mid-travel.
   ============================================================ */

/* ================= 1. AMBIENT =================
   .band-fx is one empty decorative child dropped into a dark band. It owns
   three layers: the pointer spotlight (its own background), the scan sweep
   (::before) and the horizon glow (::after). Its <i> children are the data
   packets. One element, one line of markup, no interference with the band's
   own ::before (aurora) and ::after (grain), which are already spoken for. */
.band-fx,
.grain > .band-fx{
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  /* Parallax: JS nudges this layer against the scroll direction so the
     background sits behind the content plane rather than glued to it.
     Kept to a few dozen pixels — enough to read as depth, not as drift. */
  transform: translate3d(0, var(--fx-py, 0px), 0);
}

/* ---- Pointer spotlight ----
   Follows the cursor across a dark band like a torch over a blueprint. This
   is the one ambient layer that is actually a response: it only appears once
   the reader moves a fine pointer over the band (JS sets .fx-live), so it
   never animates on its own and never appears on touch. */
.band-fx{
  background: radial-gradient(
    520px 520px at var(--fx-x, 50%) var(--fx-y, 0%),
    rgba(3, 105, 161, 0.20),
    rgba(233, 185, 73, 0.05) 42%,
    transparent 68%
  );
  opacity: 0;
  transition: opacity 600ms var(--ease-standard);
}
.band-fx.fx-live{ opacity: 1; }

/* ---- Scan sweep ----
   A single band of light crossing the section top to bottom. This is the
   effect that makes a static grid read as a surface being *read* by
   something. Long duration and a long rest at each end so it registers as
   an occasional pass, not a pulse. */
.band-fx::before{
  content: "";
  position: absolute;
  inset-inline: -10%;
  top: 0;
  height: 200px;
  background: linear-gradient(
    180deg,
    transparent,
    rgba(125, 211, 252, 0.055) 38%,
    rgba(233, 185, 73, 0.05) 62%,
    transparent
  );
  animation: fx-sweep var(--dur-sweep) cubic-bezier(0.5, 0, 0.5, 1) infinite;
}
@keyframes fx-sweep{
  0%      { transform: translateY(-220px); opacity: 0; }
  8%      { opacity: 1; }
  62%     { opacity: 1; }
  70%,
  100%    { transform: translateY(calc(100% + 240px)); opacity: 0; }
}

/* ---- Horizon glow ----
   A wide, very soft light along the band's lower edge. Gives dark sections a
   floor so they end in a gradient rather than a hard cut into the next band.
   Static — no animation cost, pure depth. */
.band-fx::after{
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 55%;
  background: radial-gradient(75% 110% at 50% 100%, rgba(3, 105, 161, 0.32), rgba(3, 105, 161, 0.08) 45%, transparent 72%);
}

/* ---- Data packets ----
   Thin light streaks travelling down the 80px blueprint rules. Positioned on
   physical left offsets that are multiples of 80 so they land exactly on a
   grid line — background-image tiles from the left edge in both LTR and RTL,
   so physical offsets are correct here and logical ones would not be. */
.band-fx i{
  position: absolute;
  top: 0;
  width: 1px;
  height: 160px;
  background: linear-gradient(180deg, transparent, var(--fx-packet, rgba(56, 189, 248, 0.85)), transparent);
  opacity: 0;
  animation: fx-packet var(--dur-packet) linear infinite;
}
.band-fx i:nth-child(1){ left: 240px;  animation-delay: 0s;    animation-duration: calc(var(--dur-packet) * 1);    }
.band-fx i:nth-child(2){ left: 640px;  animation-delay: -3.4s; animation-duration: calc(var(--dur-packet) * 1.45); --fx-packet: rgba(233, 185, 73, 0.7); }
.band-fx i:nth-child(3){ left: 1040px; animation-delay: -6.1s; animation-duration: calc(var(--dur-packet) * 0.8);  }
.band-fx i:nth-child(4){ left: 1440px; animation-delay: -1.8s; animation-duration: calc(var(--dur-packet) * 1.2);  }
/* Under ~900px only the first two rules are on screen, and four streaks in a
   narrow column reads as rain. Drop to one. */
@media (max-width: 900px){
  .band-fx i:nth-child(n + 2){ display: none; }
  .band-fx i:nth-child(1){ left: 160px; }
}
@keyframes fx-packet{
  0%   { transform: translateY(-180px); opacity: 0; }
  12%  { opacity: 0.9; }
  78%  { opacity: 0.9; }
  100% { transform: translateY(105vh); opacity: 0; }
}

/* ---- Emblem orbit ----
   Two nodes circling the wireframe mark at different radii and speeds, one
   cerulean and one gold. transform-box defaults to view-box for SVG
   elements, so transform-origin is given in viewBox units. */
.emb-orbit{
  transform-origin: 100px 100px;
  animation: emb-spin var(--dur-orbit) linear infinite;
}
.emb-orbit-slow{
  transform-origin: 100px 100px;
  animation: emb-spin calc(var(--dur-orbit) * 1.9) linear infinite reverse;
}
@keyframes emb-spin{ to{ transform: rotate(360deg); } }

/* ================= 2. RESPONSE ================= */

/* ---- Scroll progress ----
   A hairline across the foot of the fixed header, cerulean running into
   gold. Zero markup: JS writes --scroll-progress on :root and the header's
   own ::after reveals that much of it.

   Revealed with clip-path, not scaleX. scaleX would squeeze the whole
   cerulean→gold ramp into whatever width the bar currently has, so an
   early-page reader would see a finished gradient in a 2cm stub. Clipping
   keeps the ramp anchored to the viewport, so the colour at the leading
   edge actually reports how far down the page you are. */
.site-header::after{
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: -1px;
  height: 2px;
  background: linear-gradient(90deg, var(--brand-cerulean), var(--brand-gold-lifted));
  clip-path: inset(0 calc((1 - var(--scroll-progress, 0)) * 100%) 0 0);
  opacity: 0.9;
  pointer-events: none;
}
html[dir="rtl"] .site-header::after{
  background: linear-gradient(270deg, var(--brand-cerulean), var(--brand-gold-lifted));
  clip-path: inset(0 0 0 calc((1 - var(--scroll-progress, 0)) * 100%));
}

/* ---- Feature-card spotlight ----
   The hairline grid has no gaps between cards, so a lift or shadow on hover
   would break the shared edges. Instead the card lights up from where the
   cursor actually is, and a bronze rule grows along its leading edge. Both
   are pure cause-and-effect: nothing moves until the reader points at it. */
.feature-grid .feature-card::before{
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(
    260px 220px at var(--card-x, 50%) var(--card-y, 50%),
    rgba(3, 105, 161, 0.09),
    transparent 72%
  );
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-standard);
}
.feature-grid .feature-card:hover::before,
.feature-grid .feature-card:focus-within::before{ opacity: 1; }
.feature-grid .feature-card > *{ position: relative; z-index: 1; }

/* Leading-edge rule. Drawn as a second inset box-shadow on the card rather
   than a third pseudo-element (::before is the spotlight, ::after the index
   number), which also means it can never affect layout. The offset is
   flipped for RTL so the rule always sits on the reading-start edge. */
.feature-grid .feature-card{
  --card-edge-x: 0px;
  box-shadow: 0 0 0 1px var(--color-border),
              inset var(--card-edge-x) 0 0 var(--brand-bronze);
  transition: background-color var(--dur-base) var(--ease-standard),
              box-shadow var(--dur-base) var(--ease-out);
}
.feature-grid .feature-card:hover{ --card-edge-x: 3px; }
html[dir="rtl"] .feature-grid .feature-card:hover{ --card-edge-x: -3px; }

/* The index number is the card's quietest element; letting it come up to
   bronze on hover ties the number, the edge rule and the icon chip together
   as one hover state instead of three unrelated tints. */
.feature-grid .feature-card::after{
  transition: color var(--dur-base) var(--ease-standard),
              opacity var(--dur-base) var(--ease-standard);
}
.feature-grid .feature-card:hover::after{ color: var(--color-accent); opacity: 1; }

/* ---- Lifecycle flow ----
   The dashes travel down each phase's connector, so the column reads as
   something with throughput rather than a static list. This is the one place
   ambient motion carries meaning, which is why it is gated on the section
   being on screen (JS adds .in-view) and stops the moment it leaves. */
.lifecycle-phases.in-view .lifecycle-stage:not(:last-child)::before{
  animation: fx-flow-down var(--dur-flow) linear infinite;
}
@keyframes fx-flow-down{ to{ background-position-y: 11px; } }

/* Stage nodes wake as the section arrives, one after the next, walking the
   eye down each phase in reading order. Runs once per page view. */
.lifecycle-phases.in-view .stage-node{
  animation: fx-node-wake 700ms var(--ease-out) backwards;
  animation-delay: calc(var(--step-i, 0) * 70ms);
}
@keyframes fx-node-wake{
  0%  { transform: scale(0.86); opacity: 0; }
  60% { transform: scale(1.04); }
  100%{ transform: scale(1); opacity: 1; }
}

/* ---- Button sheen ----
   A single pass of light across the solid buttons on hover. Hover-only, so
   it can never fire at a reader who has not asked for it. */
.btn{ position: relative; overflow: hidden; }
.btn-gold::after,
.btn-primary::after{
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: -40%;
  width: 40%;
  background: linear-gradient(100deg, transparent, rgba(255, 255, 255, 0.45), transparent);
  transform: translateX(0);
  opacity: 0;
  pointer-events: none;
}
.btn-gold:hover::after,
.btn-primary:hover::after{
  opacity: 1;
  animation: fx-sheen 750ms var(--ease-standard);
}
@keyframes fx-sheen{
  from{ transform: translateX(0); }
  to  { transform: translateX(400%); }
}
html[dir="rtl"] .btn-gold:hover::after,
html[dir="rtl"] .btn-primary:hover::after{ animation-name: fx-sheen-rtl; }
@keyframes fx-sheen-rtl{
  from{ transform: translateX(0); }
  to  { transform: translateX(-400%); }
}

/* ---- Staggered reveal ----
   The existing .reveal observer already fades elements in; JS now also writes
   --reveal-i (position among its revealing siblings, capped) so a group
   arrives as a cascade instead of a block. 45ms per item sits inside the
   30–50ms window where a stagger reads as sequence rather than lag. */
.js .reveal{ transition-delay: calc(var(--reveal-i, 0) * var(--stagger-step)); }

/* ---- Icon chip press/hover ----
   Chips lift very slightly and warm up when their card is engaged. Bounded
   by the card, so no layout shift. */
.feature-card .icon-tile,
.ai-feature-card .icon-tile{
  transition: transform var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-standard);
}
.feature-card:hover .icon-tile{ transform: translateY(-2px); }

/* Deliberately NOT added: a pulsing dot on the trust bar. It sat in the same
   view as the aurora, the emblem, the sweep and the packets, on its own
   unrelated clock, and a blinking icon next to "Hosted on Microsoft Azure"
   implies a live status it is not reporting. Ambient motion in a view has to
   read as one system; a fifth independent timer just reads as fidget. */

/* ================= REDUCED MOTION =================
   base.css already collapses every animation to ~0ms, which stops motion but
   can leave a travelling layer parked mid-screen. Remove the moving layers
   outright and keep the static atmosphere (spotlight, horizon glow, aurora
   tint) so the page still looks designed rather than stripped. */
@media (prefers-reduced-motion: reduce){
  .band-fx{ transform: none !important; }
  .band-fx::before,
  .band-fx i{ display: none; }
  .emb-orbit,
  .emb-orbit-slow{ display: none; }
  .btn-gold::after,
  .btn-primary::after{ display: none; }
  .site-header::after{ transition: none; }
  .js .reveal{ transition-delay: 0ms; }
}
