/* ==========================================================================
   heli.css — the oil-and-gas inspection figure: a helicopter over a pipeline.

   The picture: a slab of low-poly terrain scrolling under a hovering
   helicopter, a camera cone from its belly to a footprint on the ground, and
   a frame snapping onto whatever enters that footprint. Beside it, the log
   of what the chip on board found.

   Division of labour, the same one the other figures use:

     this file           the look — what the slab, the frames, the cone and
                         the log are made of
     heli-timeline.css   the ground's run, the rotors' turn, and when every
                         frame and every row arrives — generated from the one
                         schedule in tools/heli.py, as is the geometry it
                         moves through

   What is different from the other figures: the solids. Every face on the
   helicopter and on the ground is a flat polygon with its own colour, worked
   out once in tools/heli.py from the face's normal and one light. The
   browser draws polygons; the three dimensions were spent at build time.

   This one keeps moving below 1000px. The stage is a fixed-ratio picture
   with nothing running sideways in it, so it reads at a phone's width, and
   the log simply moves under it. Under prefers-reduced-motion it holds the
   finished frame: everything found, the report ready.
   ========================================================================== */

.heli {
  --heli-cycle: 25.2s;        /* heli-timeline.css sets the real ones */
  --heli-ratio: 840 / 600;
  --heli-bob: 3.6s;
  margin: var(--s-5) 0 var(--s-6);
}
.heli, .heli figure { margin-inline: 0; }

/* --- the caption ---------------------------------------------------------- */
.heli__cap {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--s-2);
  margin-bottom: var(--s-4);
}
.heli__h {
  font-weight: 640; font-size: var(--fs-md); letter-spacing: -.015em;
  color: var(--ink);
}
.heli__note {
  flex: 1 1 22ch; min-width: 0;
  font-size: var(--fs-3xs); line-height: 1.45; color: var(--ink-3);
}

/* --- the stage and the log ------------------------------------------------ */
.heli__body {
  display: grid; gap: var(--s-4);
  grid-template-columns: minmax(0, 1fr) minmax(220px, 30%);
  align-items: stretch;
}
.heli__stage {
  position: relative; aspect-ratio: var(--heli-ratio);
  border: 1px solid var(--line); border-radius: var(--r-md);
  background:
    radial-gradient(120% 80% at 70% 0%, rgba(36, 150, 155, .10), transparent 60%),
    var(--bg-2);
  overflow: hidden;
}
.heli__art {
  position: absolute; inset: 0; width: 100%; height: 100%; display: block;
}

/* --- the chip callout ----------------------------------------------------- */
/* A card off the helicopter's flank, on a leader with one elbow: the chip
   that does the looking. The card's left-middle is the anchor the leader
   starts from, placed in stage percentages from the same number the line is
   drawn with, so the two cannot drift apart. */
.heli__chip {
  position: absolute; left: var(--x); top: var(--y);
  transform: translateY(-26px);
  display: grid; gap: 6px; width: 168px;
}
.heli-chip {
  position: relative; display: block; width: 52px; height: 52px;
  border: 1px solid var(--line-strong); border-radius: var(--r-sm);
  background: var(--surface);
  box-shadow: 0 0 0 1px rgba(36, 150, 155, .12), 0 0 18px rgba(36, 150, 155, .18);
}
.heli-chip__svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.heli-chip__pin { fill: none; stroke: var(--ink-3); stroke-width: 1.2; stroke-opacity: .55; }
/* Data going in: dashes running along every pin towards the die. */
.heli-chip__flow {
  fill: none; stroke: var(--c-teal); stroke-width: 1.2; stroke-linecap: round;
  stroke-dasharray: 2 6;
  animation: heli-chip-flow 1.1s linear infinite;
}
@keyframes heli-chip-flow { from { stroke-dashoffset: 8; } to { stroke-dashoffset: 0; } }
.heli-chip__die { fill: var(--bg-2); stroke: var(--ink-3); stroke-width: 1; }
.heli-chip__core {
  fill: var(--c-teal); opacity: .45;
  animation-duration: var(--heli-cycle);
  animation-timing-function: linear; animation-iteration-count: infinite;
}
/* One line reading the core, top to bottom, over and over. */
.heli-chip__scan {
  fill: #fff; opacity: .5;
  animation: heli-chip-scan 1.6s linear infinite;
}
@keyframes heli-chip-scan {
  from { transform: translateY(0); opacity: .55; }
  85% { opacity: .55; }
  to { transform: translateY(12.5px); opacity: 0; }
}
.heli-chip__ai {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  letter-spacing: .06em; color: var(--ink); text-shadow: 0 0 6px rgba(6, 10, 20, .8);
}
.heli__chip-cap {
  display: grid; gap: 1px;
  font-family: var(--font-mono); font-size: var(--fs-3xs); line-height: 1.35;
  letter-spacing: .03em; color: var(--ink-3);
}
.heli__chip-cap > b { color: var(--ink-2); font-weight: 600; }
.heli-lead {
  fill: none; stroke: var(--ink-3); stroke-width: 1; stroke-opacity: .6;
  vector-effect: non-scaling-stroke;
}
.heli-lead__end { fill: var(--c-teal); }

/* --- the ground ----------------------------------------------------------- */
.heli-ground {
  animation: heli-ground var(--heli-cycle) linear infinite;
  will-change: transform;
}
.heli-grid__l { fill: none; stroke: rgba(120, 170, 255, .09); stroke-width: 1; }
.heli-slab, .heli-slab-side, .heli-road { shape-rendering: crispEdges; }

/* --- the camera ----------------------------------------------------------- */
/* The cone is what the chip can see; it is drawn faint, because it is the
   frame the picture is about rather than a thing in it. */
.heli-cone, .heli-foot {
  fill: var(--c-teal);
  animation-duration: var(--heli-cycle);
  animation-timing-function: linear; animation-iteration-count: infinite;
}
/* Fill only on the sides, no stroke. Each side's transform passes through a
   singular moment when its base crosses the apex on screen, and a
   non-scaling stroke on a singular transform is a renderer that stops
   answering — reproducibly, in Chromium. The footprint only ever
   translates, so it keeps its outline. */
.heli-cone { fill-opacity: .07; }
.heli-foot {
  fill-opacity: .08;
  stroke: var(--c-teal); stroke-width: 1; stroke-opacity: .45;
  vector-effect: non-scaling-stroke;
}
.heli-shadow {
  fill: #000; opacity: .42;
  transform-box: fill-box; transform-origin: center;
  animation: heli-shadow var(--heli-bob) ease-in-out infinite;
}

/* --- the frames ----------------------------------------------------------- */
/* Coral, the one colour allowed to shout: a detection is the event. The frame
   scales about its own box, which is what lets it snap on. */
.heli-det {
  fill: rgba(246, 119, 108, .10);
  stroke: var(--c-coral); stroke-width: 1.5; stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  opacity: 0;
  transform-box: fill-box; transform-origin: center;
  animation-duration: var(--heli-cycle);
  animation-timing-function: linear; animation-iteration-count: infinite;
  filter: drop-shadow(0 0 4px rgba(246, 119, 108, .55));
}

/* --- the helicopter ------------------------------------------------------- */
.heli-craft {
  animation: heli-hover var(--heli-bob) ease-in-out infinite;
  filter: drop-shadow(0 8px 10px rgba(0, 0, 0, .35));
}
/* The tip path of each rotor, and the blur its blades leave behind them —
   the sweeps turn with the blades, and are what makes the turning visible. */
.heli-disc {
  fill: rgba(237, 243, 245, .035);
  stroke: rgba(237, 243, 245, .12); stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.heli-ring { fill: none; stroke: rgba(237, 243, 245, .26); stroke-width: 1; vector-effect: non-scaling-stroke; }
.heli-sweep { fill: rgba(237, 243, 245, .045); }
.heli-rotor, .heli-tail { will-change: transform; }

/* --- the log -------------------------------------------------------------- */
.heli__log {
  display: flex; flex-direction: column; min-width: 0;
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--surface);
  padding: var(--s-3) var(--s-3) var(--s-2);
}
.heli__loghead {
  display: flex; flex-direction: column; gap: 2px;
  padding-bottom: var(--s-2); margin-bottom: var(--s-2);
  border-bottom: 1px solid var(--line);
}
.heli__loghead > b { font-size: var(--fs-2xs); color: var(--ink); font-weight: 640; }
/* Two lines in one slot, cross-fading on the clock: the flight, then base. */
.heli__status {
  position: relative; display: block; min-height: 1.4em;
  font-family: var(--font-mono); font-size: var(--fs-3xs); letter-spacing: .03em;
  color: var(--ink-3);
}
.heli__status-fly, .heli__status-base {
  position: absolute; left: 0; top: 0; right: 0;
  animation-duration: var(--heli-cycle);
  animation-timing-function: linear; animation-iteration-count: infinite;
}
.heli__status-base { color: var(--c-teal); }
.heli__status-base::before {
  content: '✓ '; font-weight: 700;
}

.heli__rows {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--s-2); align-content: start; flex: 1 1 auto;
}
.heli-row {
  display: grid; grid-template-columns: 44px minmax(0, 1fr); gap: var(--s-2);
  align-items: center;
  padding: 6px 8px 6px 6px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--bg-2);
  opacity: 0;
  animation-duration: var(--heli-cycle);
  animation-timing-function: var(--ease-out); animation-iteration-count: infinite;
}
.heli-row__pic {
  width: 44px; height: 44px; display: block;
  border-radius: var(--r-sm); background: rgba(246, 119, 108, .08);
  outline: 1px solid rgba(246, 119, 108, .35); outline-offset: -1px;
}
.heli-row__txt { display: grid; gap: 2px; min-width: 0; }
.heli-row__n { font-size: var(--fs-2xs); color: var(--ink); line-height: 1.25; }
.heli-row__m {
  font-family: var(--font-mono); font-size: var(--fs-3xs); color: var(--ink-3);
  letter-spacing: .03em;
}
/* Many frames, one event. The bar fills for as long as the object is in
   view; the count beside it never changes. */
.heli-row__ded {
  display: flex; align-items: center; gap: 6px; margin-top: 2px;
  font-family: var(--font-mono); font-size: var(--fs-3xs); color: var(--ink-3);
}
.heli-row__bar {
  flex: 1 1 40px; height: 4px; border-radius: 2px;
  background: rgba(120, 170, 255, .12); overflow: hidden; display: block;
}
.heli-row__bar > i {
  display: block; height: 100%; background: var(--ink-3);
  transform-origin: left; transform: scaleX(0);
  animation-duration: var(--heli-cycle);
  animation-timing-function: linear; animation-iteration-count: infinite;
}
.heli-row__fr { flex: 0 0 auto; }
.heli-row__one {
  flex: 0 0 auto; padding: 1px 6px; border-radius: var(--r-pill);
  background: var(--accent-soft); color: var(--accent-hot);
  opacity: 0;
  animation-duration: var(--heli-cycle);
  animation-timing-function: var(--ease-out); animation-iteration-count: infinite;
}
.heli__total {
  margin: var(--s-2) 0 0; padding-top: var(--s-2);
  border-top: 1px solid var(--line);
  font-family: var(--font-mono); font-size: var(--fs-3xs); letter-spacing: .03em;
  color: var(--ink-2);
  opacity: 0;
  animation-duration: var(--heli-cycle);
  animation-timing-function: linear; animation-iteration-count: infinite;
}

/* --------------------------------------------------------------------------
   Narrow: the log goes under the picture, two rows across.
   -------------------------------------------------------------------------- */
@media (max-width: 1000px) {
  .heli__body { grid-template-columns: minmax(0, 1fr); }
  .heli__rows { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .heli__rows { grid-template-columns: minmax(0, 1fr); }
  .heli__chip, .heli-lead, .heli-lead__end { display: none; }
}

/* --------------------------------------------------------------------------
   The finished frame, held: the flight is over, everything was found.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .heli-ground, .heli-rotor, .heli-tail, .heli-craft, .heli-shadow, .heli-foot, .heli-cone,
  .heli-det, .heli-row, .heli-row__bar > i, .heli-row__one,
  .heli-chip__flow, .heli-chip__scan, .heli-chip__core,
  .heli__status-fly, .heli__status-base, .heli__total {
    animation: none;
  }
  .heli-sweep { display: none; }
  .heli-disc { fill: rgba(237, 243, 245, .08); }
  .heli-det, .heli-row, .heli-row__one, .heli__status-base, .heli__total, .heli-chip__core { opacity: 1; }
  .heli-chip__scan { display: none; }
  .heli-det { transform: none; }
  .heli-row__bar > i { transform: none; }
  .heli__status-fly { opacity: 0; }
}
