/* =========================================================
   IHOMS Scoreboard — public presentation page
   Self-contained: no CDN, no webfont, no JavaScript.
   Safe to run on an offline/air-gapped presentation PC.
   ========================================================= */

:root {
  --bg-1: #03120d;
  --bg-2: #082419;
  --panel: rgba(5, 28, 19, 0.84);
  --line: rgba(184, 233, 74, 0.16);
  --text: #f2f8ec;
  --muted: #c7d8c8;
  --gold: #ffc94c;
  --green: #85ff9f;
  --cyan: #77dcff;
  --lime: #c8ff74;
  --shadow: 0 24px 70px rgba(0, 0, 0, 0.45);
  --radius-xl: 30px;
  --radius-lg: 22px;
  --radius-md: 16px;
  --container: min(1180px, calc(100% - 22px));

  /* Data-encoding tokens.
     --measure is the ONE hue used for every target meter: all five meters encode
     the same measure (percent of target), so they must not be five colours.
     --status-* are reserved for state only and always ship with a text label and
     an icon, never colour alone. Keeping the measure on cyan and status on
     green/gold keeps every pair distinguishable under protan/deutan CVD. */
  --measure: #77dcff;
  --measure-soft: rgba(119, 220, 255, 0.16);
  --status-done: #85ff9f;
  --status-progress: #ffc94c;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; background: var(--bg-1); }
body {
  margin: 0;
  color: var(--text);
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background:
    radial-gradient(circle at 12% 0%, rgba(86, 255, 145, 0.13), transparent 30%),
    radial-gradient(circle at 82% 10%, rgba(255, 199, 84, 0.11), transparent 26%),
    linear-gradient(180deg, #02100b 0%, #041710 24%, #072016 62%, #04120e 100%);
  overflow-x: hidden;
}
body::before {
  content: ""; position: fixed; inset: 0; pointer-events: none; z-index: -2; opacity: .16;
  background-image:
    linear-gradient(rgba(255, 255, 255, .03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .03) 1px, transparent 1px);
  background-size: 34px 34px;
  mask-image: linear-gradient(to bottom, rgba(0, 0, 0, .9), rgba(0, 0, 0, .25));
}
.ambient {
  position: fixed; inset: -15%; pointer-events: none; z-index: -1; filter: blur(42px);
  background:
    radial-gradient(circle at 16% 18%, rgba(106, 255, 177, 0.12), transparent 20%),
    radial-gradient(circle at 78% 16%, rgba(255, 204, 101, 0.09), transparent 18%),
    radial-gradient(circle at 52% 65%, rgba(110, 220, 255, 0.08), transparent 20%);
  animation: ambientFloat 14s ease-in-out infinite alternate;
}
.page { width: var(--container); margin: 0 auto; padding: 16px 0 38px; }

.board-shell {
  position: relative; overflow: hidden; border-radius: 34px; min-height: calc(100vh - 34px);
  /* 100vh grows and shrinks as a mobile browser's URL bar hides and reappears,
     which reflows the whole board mid-scroll. svh is the stable small-viewport
     height, so the layout holds still. */
  padding: clamp(14px, 2.2vw, 24px);
  border: 1px solid rgba(198, 235, 84, 0.22);
  background: linear-gradient(180deg, rgba(5, 35, 24, 0.92), rgba(3, 18, 13, 0.95));
  box-shadow: var(--shadow), inset 0 0 0 1px rgba(255, 255, 255, 0.03);
  isolation: isolate;
}
@supports (height: 100svh) {
  .board-shell { min-height: calc(100svh - 34px); }
}
.board-shell::before {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0; opacity: .46;
  background:
    repeating-linear-gradient(0deg, transparent 0 36px, rgba(179, 224, 66, .045) 36px 37px),
    repeating-linear-gradient(90deg, transparent 0 36px, rgba(179, 224, 66, .045) 36px 37px);
}
.board-shell::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  background: radial-gradient(circle at 50% -8%, rgba(255, 216, 99, 0.12), transparent 26%);
  mix-blend-mode: screen;
}

.board-panel {
  position: relative; z-index: 1; overflow: hidden; border-radius: var(--radius-xl);
  padding: clamp(10px, 1.6vw, 14px);
  border: 1px solid rgba(255, 205, 88, 0.18);
  background:
    radial-gradient(circle at 16% 0%, rgba(255, 213, 98, 0.08), transparent 22%),
    linear-gradient(180deg, rgba(7, 34, 23, 0.9), rgba(3, 19, 14, 0.96));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.03), 0 16px 40px rgba(0, 0, 0, .34);
}
.board-panel::before {
  content: ""; position: absolute; inset: 10px; border-radius: 24px; pointer-events: none;
  border: 1px solid rgba(172, 241, 86, 0.10);
}

.poster-stage {
  position: relative; min-height: 860px; border-radius: 24px; overflow: hidden;
  border: 1px solid rgba(175, 240, 87, .18);
  background:
    radial-gradient(circle at 50% 12%, rgba(11, 72, 46, 0.84), rgba(2, 14, 11, 0.97)),
    linear-gradient(180deg, rgba(6, 31, 21, 0.82), rgba(3, 17, 12, 0.92));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.03), 0 18px 44px rgba(0, 0, 0, .44);
  display: flex; align-items: center; justify-content: center; isolation: isolate;
}
.poster-stage::before {
  content: ""; position: absolute; inset: 0; z-index: 0; pointer-events: none; mix-blend-mode: screen;
  background: linear-gradient(110deg, transparent 18%, rgba(255, 255, 255, 0.055) 34%, transparent 46%);
  background-size: 180% 100%; animation: shineSweep 7.4s linear infinite;
}
.poster-stage::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
  box-shadow: inset 0 0 70px rgba(112, 255, 165, .14), inset 0 0 95px rgba(255, 193, 77, 0.10);
}
.poster-halo {
  position: absolute; inset: 2.8% 7.2%; z-index: 0; pointer-events: none; border-radius: 30px;
  box-shadow: 0 0 50px rgba(123, 255, 142, 0.30), 0 0 88px rgba(255, 195, 74, 0.16);
}
.poster-art {
  position: relative; width: min(100%, 610px); aspect-ratio: 1024 / 1536; z-index: 1;
  filter: drop-shadow(0 28px 44px rgba(0, 0, 0, .52));
  animation: posterFloat 8s ease-in-out infinite alternate;
}
.poster-art img { width: 100%; height: 100%; object-fit: contain; display: block; }

/* =========================================================
   Running lights, aligned to the artwork by construction.

   The previous version drew hand-authored SVG polylines over the poster and they
   did not sit on the printed traces — they cut across the seal, the CPU and the
   text. Instead, ihoms_scoreboard_traces.png is a mask extracted from the
   poster's own pixels (saturated green/cyan/gold trace pixels, with the text,
   seal, CPU face and frames excluded). Masking the glow layer with it means light
   can only ever appear exactly on the real traces, LEDs and component columns.

   One mask on the container clips all three flow layers, so the browser
   composites a single mask rather than three.
   ========================================================= */

.trace-lights {
  position: absolute; inset: 0; z-index: 2; pointer-events: none;
  mix-blend-mode: screen;

  -webkit-mask-image: url("../assets/ihoms/ihoms_scoreboard_traces.png");
          mask-image: url("../assets/ihoms/ihoms_scoreboard_traces.png");
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: center;
          mask-position: center;
}

/* --- Multi-source pulses -------------------------------------------------
   Each pulse is a ring of light centred on a real component of the poster
   (the CPU, the seal, a target chip, an accomplishment panel). The ring
   expands outward from that origin and then travels back inward, because
   `animation-direction: alternate` plays the keyframes forward then reversed.

   The mask still does all the alignment work, so a ring can only illuminate
   the traces it actually crosses — it reads as signal running out along the
   real circuitry from that component, then returning to it.

   Randomness: CSS has no RNG, so instead every pulse gets a duration that
   shares no small common factor with the others (6.1, 7.9, 9.3, 11.1, 12.7,
   6.9, 8.7, 10.9, 13.9s) plus a negative start delay. Their combined cycle is
   hours long, so the board never visibly repeats a pattern.
   ------------------------------------------------------------------------ */

.trace-pulse {
  position: absolute; inset: 0; display: block;
  transform-origin: var(--x) var(--y);
  mix-blend-mode: screen;
  /* Deliberately NO will-change here. Nine full-size layers pinned to the GPU
     exhausts a phone's compositor budget, and the resulting tile eviction shows
     up as flicker while scrolling. The animation alone gets these promoted when
     promotion is actually worth it. */
  animation: tracePulse var(--dur, 9s) ease-in-out infinite alternate;
  animation-delay: var(--delay, 0s);

  /* cool signal (default) */
  background: radial-gradient(circle at var(--x) var(--y),
    transparent 0 34%,
    rgba(119, 220, 255, .30) 43%,
    rgba(198, 255, 231, .90) 50%,
    rgba(133, 255, 159, .34) 57%,
    transparent 68%);
}

/* warm signal, used for the gold side of the board */
.trace-pulse--warm {
  background: radial-gradient(circle at var(--x) var(--y),
    transparent 0 34%,
    rgba(255, 178, 60, .28) 43%,
    rgba(255, 233, 158, .86) 50%,
    rgba(255, 201, 76, .32) 57%,
    transparent 68%);
}

@keyframes tracePulse {
  0%   { transform: scale(.06); opacity: 0; }
  10%  { opacity: .95; }
  65%  { opacity: .60; }
  100% { transform: scale(1.95); opacity: 0; }
}
/* Masks are widely supported but not universally; without one the bands would
   wash over the whole poster. Hide them rather than show a misaligned effect. */
@supports not ((mask-image: url("#")) or (-webkit-mask-image: url("#"))) {
  .trace-lights { display: none; }
}

.pillars-panel, .description-panel, .metrics-panel, .scorecard-panel {
  position: relative; z-index: 1; overflow: hidden; margin-top: 18px;
  border-radius: var(--radius-lg); padding: 18px;
  border: 1px solid rgba(180, 235, 82, .16);
  background: linear-gradient(180deg, rgba(6, 31, 21, .84), rgba(3, 17, 12, .94));
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .02);
}
.pillars-panel::before, .description-panel::before,
.metrics-panel::before, .scorecard-panel::before {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(125deg, rgba(255, 255, 255, 0.03), transparent 42%);
}
.pillars-panel h2, .description-panel h2,
.metrics-panel h2, .scorecard-panel h2 {
  position: relative; margin: 0 0 12px; color: var(--gold); line-height: 1.1;
  font-size: clamp(1.4rem, 2vw, 1.9rem); letter-spacing: -.02em;
}

.pillars-grid {
  position: relative; display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px;
}
.pillar {
  border-radius: 16px; border: 1px solid rgba(180, 235, 82, .16);
  background: rgba(8, 34, 23, .52); padding: 14px;
  display: grid; grid-template-columns: auto 1fr; gap: 10px; align-items: start;
}
.pillar .badge {
  width: 38px; height: 38px; border-radius: 12px; display: grid; place-items: center;
  font-weight: 800; background: rgba(255, 205, 86, .14); color: var(--gold);
  border: 1px solid rgba(255, 205, 86, .18);
}
.pillar strong { display: block; margin-bottom: 4px; font-size: .93rem; text-transform: uppercase; }
.pillar span { display: block; color: var(--muted); font-size: .87rem; line-height: 1.45; }

.description-panel p {
  position: relative; color: #dde8db; line-height: 1.78; font-size: 1rem; margin: 0;
}
.description-panel p + p { margin-top: 14px; }

@keyframes ambientFloat { from { transform: translate3d(-2%, -1%, 0); } to { transform: translate3d(2%, 1%, 0); } }
@keyframes shineSweep { from { background-position: 180% 0; } to { background-position: -30% 0; } }
@keyframes posterFloat { from { transform: translateY(0px); } to { transform: translateY(-6px); } }
/* =========================================================
   Decorative PCB backdrop behind the board shell.

   Unrelated to the poster's own traces — this is the surrounding frame
   ornamentation, so hand-authored paths are correct here.
   ========================================================= */

.circuit-backdrop {
  position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; opacity: .62;
}
.circuit-backdrop svg { display: block; width: 100%; height: 100%; }

.backdrop-trace {
  fill: none; stroke: rgba(192, 232, 72, .14); stroke-width: 1.25;
  stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 7 12;
  animation: backdropDash 22s linear infinite;
}
.backdrop-trace.warm { stroke: rgba(255, 202, 76, .16); animation-duration: 27s; }

.backdrop-energy {
  fill: none; stroke: url(#backdropEnergy); stroke-width: 2.8;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 4 46; stroke-dashoffset: 100; opacity: .86;
  filter: drop-shadow(0 0 4px rgba(148, 255, 167, .72)) drop-shadow(0 0 9px rgba(119, 220, 255, .38));
  animation: normalizedFlow var(--bg-speed, 7s) linear infinite;
  animation-delay: var(--bg-delay, 0s);
}
.backdrop-energy.warm {
  stroke: url(#backdropWarm);
  filter: drop-shadow(0 0 4px rgba(255, 213, 103, .78)) drop-shadow(0 0 9px rgba(255, 166, 78, .32));
}

.backdrop-node {
  fill: #caff83; opacity: .25; transform-box: fill-box; transform-origin: center;
  filter: drop-shadow(0 0 5px rgba(191, 255, 120, .68));
  animation: backdropNode 4.4s ease-in-out infinite;
  animation-delay: var(--bg-node-delay, 0s);
}
.backdrop-node.warm { fill: #ffd66f; filter: drop-shadow(0 0 5px rgba(255, 214, 111, .72)); }

.board-shell > .shell-sweep {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  overflow: hidden; opacity: .7; mix-blend-mode: screen;
}
.board-shell > .shell-sweep::before,
.board-shell > .shell-sweep::after {
  content: ""; position: absolute; inset: -12% -32%;
  background:
    linear-gradient(106deg, transparent 24%, rgba(111, 231, 255, .05) 34%, rgba(195, 255, 92, .08) 42%, transparent 52%),
    radial-gradient(circle at 50% 48%, rgba(255, 216, 99, .055), transparent 30%);
  animation: shellSweep 13s linear infinite;
}
.board-shell > .shell-sweep::after { opacity: .48; animation-duration: 19s; animation-delay: -8s; }

@keyframes normalizedFlow { to { stroke-dashoffset: 0; } }
@keyframes backdropDash { to { stroke-dashoffset: -180; } }
@keyframes backdropNode {
  0%, 70%, 100% { opacity: .18; transform: scale(.86); }
  78% { opacity: .82; transform: scale(1.42); }
  88% { opacity: .34; transform: scale(1); }
}
@keyframes shellSweep {
  from { transform: translateX(24%) translateY(-3%); }
  to { transform: translateX(-24%) translateY(3%); }
}
/* =========================================================
   Performance summary — a single headline figure.

   Every target reports the same completion, so twelve identical meters would
   encode nothing. One figure carries the headline; the per-item detail lives
   in the two columns below. The number wears a text token; the cyan meter
   carries the encoding.
   ========================================================= */

.summary { position: relative; display: flex; flex-direction: column; gap: 12px; }

.summary-figure { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }

.summary-value {
  font-size: clamp(2.1rem, 4.4vw, 3.1rem); font-weight: 800; line-height: 1;
  color: var(--text); font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
.summary-label { color: var(--muted); font-size: .96rem; }

/* Recessive track, 4px rounded data-end anchored to the baseline. */
.meter {
  position: relative; height: 8px; border-radius: 999px;
  background: var(--measure-soft);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04);
  overflow: hidden;
}
.meter > span {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, rgba(119, 220, 255, .72), var(--measure));
  box-shadow: 0 0 10px rgba(119, 220, 255, .55);
}
.summary-meter { height: 10px; }

/* =========================================================
   Targets / Accomplishments — one grid row per numbered pair.

   Both cells of a pair live in the SAME grid row, so the row takes the height
   of the taller cell and both stretch to fill it. That keeps the two columns
   locked in step: no matter how much text either side carries, item n on the
   left is always level with item n on the right. Two independent lists could
   never guarantee that.

   Column widths are shared by the same track definition, so the header row and
   every pair row align on identical columns.
   ========================================================= */

/* one track definition, used by the header row and every pair row */
.pairs-head,
.pair {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) minmax(0, 1fr);
  gap: 12px;
}

.pairs-head { position: relative; align-items: end; margin-bottom: 10px; }
.pairs-head h2 { margin: 0; }

.pairs { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }

.pair { position: relative; align-items: stretch; }

.pair-no {
  width: 34px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; font-weight: 800; font-size: .82rem;
  background: rgba(255, 205, 86, .14); color: var(--gold);
  border: 1px solid rgba(255, 205, 86, .18);
  font-variant-numeric: tabular-nums;
}

/* stretch is what makes the two cells match height within the row */
.pair-cell {
  display: flex; flex-direction: column; min-width: 0;
  border-radius: 14px; border: 1px solid rgba(180, 235, 82, .16);
  background: rgba(8, 34, 23, .52); padding: 12px 14px;
}

/* column headers cover this on wide screens; the labels return when stacked */
.pair-label { display: none; }

.pair-text { margin: 0; font-size: .92rem; line-height: 1.5; color: var(--text); }

.pair-sub {
  margin: 8px 0 0; padding-left: 16px; list-style: none;
  color: var(--muted); font-size: .85rem; line-height: 1.5;
}
.pair-sub li { position: relative; }
.pair-sub li + li { margin-top: 3px; }
.pair-sub li::before {
  content: ""; position: absolute; left: -14px; top: .62em;
  width: 7px; height: 1px; background: rgba(255, 205, 86, .55);
}

/* margin-top:auto pins the status to the bottom, so badges line up across rows */
.pair-foot {
  margin-top: auto; padding-top: 10px;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap;
}

.pair-date {
  color: var(--muted); font-size: .79rem; letter-spacing: .02em;
  font-variant-numeric: tabular-nums;
}

.status {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .76rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  padding: 3px 9px 3px 7px; border-radius: 999px;
}
.status svg { width: 13px; height: 13px; flex: none; display: block; }

.status.is-done {
  color: var(--status-done);
  background: rgba(133, 255, 159, .12);
  border: 1px solid rgba(133, 255, 159, .30);
}
.status.is-progress {
  color: var(--status-progress);
  background: rgba(255, 201, 76, .12);
  border: 1px solid rgba(255, 201, 76, .32);
}
@media (max-width: 980px) {
  .poster-stage { min-height: 720px; }
  .poster-art { width: min(100%, 520px); }
  .pillars-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pairs-head { display: none; }
  .pair { grid-template-columns: 34px minmax(0, 1fr); }
  .pair-cell { grid-column: 2; }
  .pair-label {
    display: block; margin-bottom: 6px;
    font-size: .7rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .08em; color: var(--gold);
  }
}
@media (max-width: 680px) {
  .page { width: min(100% - 14px, 100%); }
  .board-shell { border-radius: 26px; padding: 12px; min-height: auto; }
  .poster-stage { min-height: 500px; }
  .poster-art { width: min(100%, 330px); }
  .pillars-grid { grid-template-columns: 1fr; }
  .pair { grid-template-columns: 1fr; gap: 8px; }
  .pair-cell { grid-column: 1; }
  .pair-no { width: auto; justify-self: start; padding: 0 10px; }
  .pillars-panel, .description-panel,
  .metrics-panel, .scorecard-panel { padding: 16px; }
  .description-panel p { font-size: .96rem; }
  .circuit-backdrop { opacity: .38; }
}

/* =========================================================
   Small-screen compositing budget.

   Phones flicker when too many large layers are composited at once: each
   blended, masked or filtered layer is a separate GPU texture, and at
   1024x1536 a handful of them exhausts the budget. The browser then evicts
   and re-rasterises tiles, which reads as the background glitching and the
   board blinking while scrolling.

   So on small screens the decorative layers are cut back hard. The poster,
   the trace alignment and all the content are untouched — only the count of
   simultaneously composited effects drops.
   ========================================================= */
@media (max-width: 980px) {
  /* Two full-viewport fixed layers, one blurred at 42px, re-rasterise every
     time a mobile URL bar slides. They are the main scroll-glitch source. */
  .ambient { display: none; }
  body::before { display: none; }

  /* Stop the board resizing against a changing viewport height. */
  .board-shell { min-height: auto; }

  /* A drop-shadow over a 1024x1536 image, repainted by a float animation. */
  .poster-art { filter: none; animation: none; }

  /* Blended sweep layers: cheap on desktop, costly stacked on a phone. */
  .poster-stage::before { display: none; }
  .board-shell > .shell-sweep { display: none; }

  /* Animated SVG drop-shadow filters are among the most expensive things a
     mobile GPU can be asked to do continuously. Keep the static traces. */
  .backdrop-energy { display: none; }
  .backdrop-node { animation: none; }
  .backdrop-trace { animation: none; }

  /* Keep three pulse sources instead of nine, chosen for spread rather than
     DOM order: the processor (centre), a lower-left chip and an upper-right
     panel. Still reads as multi-source activity, at a third of the layer cost. */
  .trace-pulse:nth-of-type(2),
  .trace-pulse:nth-of-type(3),
  .trace-pulse:nth-of-type(4),
  .trace-pulse:nth-of-type(7),
  .trace-pulse:nth-of-type(8),
  .trace-pulse:nth-of-type(9) { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
