/* ──────────────────────────────────────────────────────────────────────────
 * /css/_lib/signal-radar.css
 *
 * MANIFEST
 * Purpose: The Signal Leads radar scope — ONE shared decorative component
 *          rendering identically on the sales page hero banner
 *          (index.html) and the dashboard Marketplace tile
 *          (dashboard.html). Pairs with /js/_lib/signal-radar.js
 *          (window.TG.signalRadar), which places + recycles the contact
 *          blips. Before C135 the two surfaces carried independently
 *          authored copies that diverged in sweep direction, period,
 *          construction, and colour; this module is the single source.
 *
 * Size:    The ONLY permitted difference between surfaces. `--radar-size`
 *          drives EVERY internal dimension via calc() — core, ring travel,
 *          blip diameter, blip glow. 56px on index.html, 44px on
 *          dashboard.html. Nothing inside is a bare px value, so the
 *          component is the same design at any scale.
 *
 * Sweep:   CLOCKWISE with the BRIGHT EDGE LEADING (the C135 fix). Conic
 *          angles increase clockwise from 12 o'clock and the element
 *          rotates clockwise, so a feature at local gradient angle L
 *          appears at screen angle L + rotation. The band runs
 *          transparent 0deg -> peak -> transparent (peak + 22deg): the
 *          long ramp BEHIND the peak is phosphor decay trailing the beam,
 *          the short 22deg lead-in sits ahead of it. The pre-C135
 *          dashboard gradient was bright at local 0deg fading FORWARD to
 *          85deg — bright edge trailing, fade leading — which read as a
 *          beam travelling counterclockwise on a clockwise disc.
 *
 * Colour:  Platform green tokens ONLY (--green + color-mix alphas), per
 *          Sean's C135 one-green-platform-wide ruling. NO component-local
 *          hex, NO --signal-green token. The alpha percentages (40 border /
 *          14 disc / 32 sweep / 70 ring + blip glow) are carried over from
 *          the pre-C135 sales-page source so the treatment is unchanged
 *          apart from the hue.
 *
 * Drift:   `--radar-sweep-peak-deg` is the SINGLE authoritative home of the
 *          bright-peak angle. The conic-gradient's bright stop derives from
 *          it, and /js/_lib/signal-radar.js READS IT BACK off the computed
 *          style to phase-lock the blips. Change it here and the blips
 *          follow automatically — do NOT duplicate the number in JS.
 *          `--radar-sweep-seconds` is unitless for the same reason (JS
 *          parses it directly; CSS multiplies by 1s).
 *
 * Cascade: A /css/_lib/ shared module (LOCKED C82-A). Loaded AFTER the
 *          shared base layer and BEFORE the page CSS. Single-class
 *          `.tg-radar-*` rules — no page CSS competes, so no specificity
 *          scoping is needed. Page stylesheets own POSITION/LAYOUT of the
 *          scope only, never its internals. NB the two hosts are positioned
 *          differently: the hero banner scope is a FLEX CHILD (the `flex:
 *          none` below is what holds it at size), while the dashboard tile
 *          scope is `position: absolute` in the tile corner — on that host
 *          `flex: none` is inert. Both are correct; neither page overrides
 *          anything but position + `--radar-size`.
 *
 * A11y:    Decorative. The host element carries aria-hidden="true" on both
 *          surfaces; the meaning lives in the adjacent copy. No roles, no
 *          labels, no live regions. prefers-reduced-motion freezes the
 *          sweep, drops the expanding rings, stills the core, and shows
 *          every blip at a static mid opacity so the scope reads as an
 *          intentional static graphic rather than an empty circle.
 *
 * Precache: Added to sw.js PRECACHE_URLS (loaded by 2 pages — the
 *          cis-form.css / dropdown.css / dialog.css precedent).
 *
 * Consumed by: index.html (56px, Signal Leads hero banner),
 *          dashboard.html (44px, Marketplace tile corner).
 *
 * Shipped: C135 (Signal Radar unification).
 * ────────────────────────────────────────────────────────────────────────── */

/* ── Scope ───────────────────────────────────────────────────────────────── *
 * The clipping disc. Every child dimension resolves off --radar-size, so the
 * 56px and 44px instances are the same drawing at two scales. */
.tg-radar {
  /* Per-surface override; this is the fallback if a host sets nothing. */
  --radar-size: 44px;

  /* Bright-peak angle of the sweep gradient, in degrees, UNITLESS.
     AUTHORITATIVE — /js/_lib/signal-radar.js reads this property off the
     computed style to phase-lock the blips. See the Drift note above. */
  --radar-sweep-peak-deg: 44;

  /* Sweep period in seconds, UNITLESS (JS parses it; CSS multiplies by 1s).
     The blips share this period exactly — that is what makes the phase-lock
     hold for the life of the page. */
  --radar-sweep-seconds: 5;

  /* Derived internals — the whole point of the component. */
  --radar-core: calc(var(--radar-size) * 0.143);   /* 8.0px @56 / 6.3px @44 */
  --radar-ring-end: calc(var(--radar-size) * 0.94);/* 52.6px @56 / 41.4px @44 */
  --radar-blip: calc(var(--radar-size) * 0.09);    /* 5.0px @56 / 4.0px @44 */

  position: relative;
  flex: none;
  width: var(--radar-size);
  height: var(--radar-size);
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--green) 40%, transparent);
  background: radial-gradient(
    circle,
    color-mix(in srgb, var(--green) 14%, transparent),
    transparent 70%
  );
  overflow: hidden;                       /* clips the sweep + ring travel */
}

/* ── Sweep ───────────────────────────────────────────────────────────────── *
 * Bright edge LEADS the clockwise rotation. The gradient band occupies local
 * 0deg .. (peak + 22deg); the peak sits at --radar-sweep-peak-deg with a long
 * decay ramp trailing it down to transparent at local 0. */
.tg-radar-sweep {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: conic-gradient(
    from 0deg,
    transparent 0deg,
    color-mix(in srgb, var(--green) 32%, transparent)
      calc(var(--radar-sweep-peak-deg) * 1deg),
    transparent calc((var(--radar-sweep-peak-deg) + 22) * 1deg)
  );
  animation: tgRadarSweep calc(var(--radar-sweep-seconds) * 1s) linear infinite;
}

@keyframes tgRadarSweep {
  to { transform: rotate(360deg); }
}

/* ── Origin core ─────────────────────────────────────────────────────────── *
 * An INDEPENDENT pulse, deliberately not phase-locked to the sweep — it reads
 * as "system alive" at the origin, not as a contact. */
.tg-radar-core {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--radar-core);
  height: var(--radar-core);
  margin: calc(var(--radar-core) / -2);
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 calc(var(--radar-size) * 0.143) var(--green);
  animation: tgRadarCorePulse 2s ease-in-out infinite;
}

@keyframes tgRadarCorePulse {
  0%, 100% { transform: scale(1);   opacity: 1;  }
  50%      { transform: scale(1.4); opacity: .7; }
}

/* ── Expanding rings ─────────────────────────────────────────────────────── *
 * Two staggered emitter rings. Travel runs from the core diameter out to
 * --radar-ring-end (94% of the scope): with the 1px border the outer edge
 * lands at 54.6px inside the 56px scope and 43.4px inside the 44px scope, so
 * neither instance touches the rim. The pre-C135 rule hardcoded 8px -> 54px,
 * which would have overshot a 44px scope by ~10px. */
.tg-radar-ring {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--radar-core);
  height: var(--radar-core);
  margin: calc(var(--radar-core) / -2);
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--green) 70%, transparent);
  animation: tgRadarRing 2.4s ease-out infinite;
}

.tg-radar-ring-2 { animation-delay: 1.2s; }

@keyframes tgRadarRing {
  0% {
    width: var(--radar-core);
    height: var(--radar-core);
    margin: calc(var(--radar-core) / -2);
    opacity: .8;
  }
  100% {
    width: var(--radar-ring-end);
    height: var(--radar-ring-end);
    margin: calc(var(--radar-ring-end) / -2);
    opacity: 0;
  }
}

/* ── Contact blips ───────────────────────────────────────────────────────── *
 * ONE-SHOT CONTACTS (Sean, C135 revision). A blip is NOT a persistent object
 * the beam keeps finding — it is a NEW contact each time. /js/_lib/
 * signal-radar.js places a dot (--blip-left / --blip-top) and adds .tg-hit at
 * the exact moment the beam reaches that angle; the dot flares, stays STATIC
 * in place, fades to fully dark, and never re-lights there. The JS then picks
 * a fresh random position and a random 0-2 revolution skip before the next
 * appearance — so consecutive sweeps paint different contacts in different
 * places, not the same set wandering around.
 *
 * CSS still owns every animated frame — the JS only schedules the moment a
 * hit begins (one timeout per appearance, no per-frame JS anywhere). */
.tg-radar-blips {
  position: absolute;
  inset: 0;
  border-radius: 50%;
}

.tg-radar-blip {
  position: absolute;
  left: var(--blip-left, 50%);
  top: var(--blip-top, 50%);
  width: var(--radar-blip);
  height: var(--radar-blip);
  margin: calc(var(--radar-blip) / -2);
  border-radius: 50%;
  background: var(--green);
  box-shadow: 0 0 calc(var(--radar-size) * 0.11)
              color-mix(in srgb, var(--green) 70%, transparent);
  opacity: 0;
}

/* The hit: sharp flare, brief hold, fade to NOTHING — all within ~22% of a
   revolution so the scope is empty well before the beam returns. Runs ONCE
   per .tg-hit application; the base opacity above returns the dot to fully
   dark when the animation ends, and the JS removes the class on animationend
   so the next appearance can re-trigger it. */
.tg-radar-blip.tg-hit {
  animation: tgRadarBlip calc(var(--radar-sweep-seconds) * 0.22 * 1s) ease-out 1;
}

@keyframes tgRadarBlip {
  0%   { opacity: 1;   transform: scale(1.35); }
  18%  { opacity: .95; transform: scale(1);    }
  55%  { opacity: .45; transform: scale(1);    }
  100% { opacity: 0;   transform: scale(1);    }
}

/* ── Reduced motion ──────────────────────────────────────────────────────── *
 * Covers ALL animated parts. The sweep freezes as a dimmed static wedge, the
 * emitter rings drop out, the core stills, and every blip shows at a steady
 * mid opacity — a static scope with visible contacts, which reads as
 * intentional. /js/_lib/signal-radar.js additionally never schedules any
 * contact appearances under this preference, so nothing ever changes. */
@media (prefers-reduced-motion: reduce) {
  .tg-radar-sweep { animation: none; opacity: .35; }
  .tg-radar-ring  { display: none; }
  .tg-radar-core  { animation: none; }
  .tg-radar-blip,
  .tg-radar-blip.tg-hit { animation: none; opacity: .55; transform: scale(1); }
}
