/* Micro-Graphics component styles. Each block is also duplicated inline in the
   copy-paste snippet (js/snippets.js) so a component works standalone with no
   dependency on this file — the var(..., fallback) pairs are the contract.

   Two layers here:
   - Primitives (.mg-frame, .mg-glyph, .mg-rolling-number, .mg-barcode,
     .mg-pulse, .mg-divider, .mg-label) — small reusable pieces.
   - Modules — a .mg-frame composed with a handful of primitives, where
     usually only 1–2 of them animate and the rest are static dressing.
     Modules live in index.html / snippets.js, not as their own CSS class. */

/* ---- Frame: corner brackets only, no full outline — brackets pull apart
   on hover instead of scaling the whole module ---- */
.mg-frame {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 0.75rem 0.9rem;
  background: var(--mg-panel-bg, rgba(10, 22, 24, 0.55));
}

.mg-frame::before,
.mg-frame::after {
  content: '';
  position: absolute;
  width: 10px;
  height: 10px;
  border: 2px solid var(--mg-accent, #0ff);
  opacity: 0.85;
  transition: transform 0.35s ease, opacity 0.35s ease;
}

.mg-frame::before {
  top: -1px;
  left: -1px;
  border-right: none;
  border-bottom: none;
  transform: translate(0, 0);
}

.mg-frame::after {
  bottom: -1px;
  right: -1px;
  border-left: none;
  border-top: none;
  transform: translate(0, 0);
}

.mg-frame:hover::before {
  transform: translate(-4px, -4px);
  opacity: 1;
}

.mg-frame:hover::after {
  transform: translate(4px, 4px);
  opacity: 1;
}

/* ---- Module title: bold header at the top of a module ---- */
.mg-module__title {
  font-family: var(--mg-font, 'Courier New', monospace);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--mg-accent, #0ff);
}

/* ---- Layout helpers for arranging primitives inside a module ---- */
.mg-module__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem;
}

.mg-module__col {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

/* ---- Divider: a dotted HUD tick-rule between module sections ---- */
.mg-divider {
  height: 2px;
  background-image: linear-gradient(to right, var(--mg-border, rgba(0, 229, 255, 0.35)) 1px, transparent 1px);
  background-size: 5px 100%;
  background-repeat: repeat-x;
  opacity: 0.8;
}

/* ---- Label: small letter-spaced caption / readout text ---- */
.mg-label {
  font-family: var(--mg-font, 'Courier New', monospace);
  font-size: 0.56rem;
  letter-spacing: 0.12em;
  color: var(--mg-text-dim, #6f9aa1);
  white-space: nowrap;
}

/* ---- Pulse: a blinking status dot + label, e.g. "LIVE" ---- */
.mg-pulse {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

.mg-pulse__dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--mg-accent, #0ff);
  box-shadow: 0 0 4px var(--mg-accent-glow, rgba(0, 229, 255, 0.6));
  animation: mg-pulse-anim 1.6s ease-in-out infinite;
}

.mg-pulse__label {
  font-family: var(--mg-font, 'Courier New', monospace);
  font-size: 0.56rem;
  letter-spacing: 0.15em;
  color: var(--mg-text-dim, #6f9aa1);
}

@keyframes mg-pulse-anim {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.7); }
}

/* ---- Barcode: uniform-height bars, widths + cluster gaps randomized once by JS ---- */
.mg-barcode {
  display: flex;
  align-items: stretch;
  height: 15px;
}

.mg-barcode span {
  display: block;
  background: var(--mg-accent, #0ff);
}

/* ---- Glyph decode ---- */
.mg-glyph {
  font-family: var(--mg-font, 'Courier New', monospace);
  font-size: 0.78rem;
  letter-spacing: 0.05em;
  color: var(--mg-accent, #0ff);
  text-shadow: 0 0 6px var(--mg-accent-glow, rgba(0, 255, 255, 0.6));
  white-space: pre;
}

.mg-glyph__char--locked {
  color: var(--mg-text, #eafeff);
  text-shadow: none;
}

.mg-glyph--lg {
  font-size: 1.05rem;
  font-weight: 700;
}

/* ---- Rolling number ---- */
.mg-rolling-number {
  display: inline-flex;
  font-family: var(--mg-font, 'Courier New', monospace);
  font-size: 1.3rem;
  color: var(--mg-accent, #0ff);
  text-shadow: 0 0 8px var(--mg-accent-glow, rgba(0, 255, 255, 0.6));
}

.mg-rolling-number--sm {
  font-size: 0.85rem;
  color: var(--mg-text-dim, #6f9aa1);
  text-shadow: none;
}

.mg-rolling-number__digit {
  position: relative;
  width: 0.62em;
  height: 1.2em;
  overflow: hidden;
}

.mg-rolling-number__strip {
  position: absolute;
  top: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  transition: transform var(--mg-roll-speed, 0.6s) cubic-bezier(0.22, 0.99, 0.36, 1);
}

.mg-rolling-number__strip span {
  height: 1.2em;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mg-rolling-number__sep {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 0.5em;
  opacity: 0.6;
}

.mg-rolling-number__caption {
  font-size: 0.65rem;
  letter-spacing: 0.2em;
  color: var(--mg-text-dim, #6f9aa1);
  text-align: center;
  margin-top: 0.5rem;
}

/* ---- Amplifier: bars bounce continuously, durations/delays randomized once by JS ---- */
.mg-amp {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 16px;
}

.mg-amp span {
  display: block;
  width: 3px;
  height: 100%;
  background: var(--mg-accent, #0ff);
  transform-origin: bottom;
  animation-name: mg-amp-bounce;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

@keyframes mg-amp-bounce {
  0%, 100% { transform: scaleY(0.15); }
  50% { transform: scaleY(1); }
}

/* ---- Radar: rotating conic sweep + static pulsing blips ---- */
.mg-radar {
  position: relative;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid var(--mg-border, rgba(0, 229, 255, 0.35));
  flex-shrink: 0;
  overflow: hidden;
}

.mg-radar::before {
  content: '';
  position: absolute;
  inset: 0;
  background: conic-gradient(from 0deg, var(--mg-accent, #0ff) 0deg, transparent 40deg 360deg);
  opacity: 0.55;
  animation: mg-radar-spin 3s linear infinite;
}

.mg-radar::after {
  content: '';
  position: absolute;
  inset: 8px;
  border: 1px solid var(--mg-border, rgba(0, 229, 255, 0.25));
  border-radius: 50%;
}

.mg-radar__blip {
  position: absolute;
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--mg-accent, #0ff);
  box-shadow: 0 0 4px var(--mg-accent-glow, rgba(0, 229, 255, 0.6));
  animation: mg-pulse-anim 2.2s ease-in-out infinite;
}

@keyframes mg-radar-spin {
  to { transform: rotate(360deg); }
}

/* ---- CLI: sequential character typing + blinking cursor, static prompt via ::before ---- */
.mg-cli {
  font-family: var(--mg-font, 'Courier New', monospace);
  font-size: 0.72rem;
  color: var(--mg-accent, #0ff);
  white-space: pre;
}

.mg-cli::before {
  content: attr(data-prompt);
  color: var(--mg-text-dim, #6f9aa1);
  margin-right: 0.5em;
}

.mg-cli--plain::before {
  content: none;
  margin-right: 0;
}

.mg-cli--sm {
  font-size: 0.58rem;
  color: var(--mg-text-dim, #6f9aa1);
}

.mg-cli--xs {
  font-size: 0.5rem;
  color: var(--mg-text-dim, #6f9aa1);
}

.mg-cli__cursor {
  display: inline-block;
  width: 0.5em;
  height: 0.95em;
  background: var(--mg-accent, #0ff);
  margin-left: 2px;
  vertical-align: text-bottom;
  animation: mg-cli-blink 1s step-end infinite;
}

@keyframes mg-cli-blink {
  50% { opacity: 0; }
}

/* ---- Matrix rain: a static grid of characters where JS sweeps brightness
   down each column (bright head, fading trail) and independently flickers
   a subset of characters — no CSS animation, every frame is JS-driven. ---- */
.mg-matrix {
  display: flex;
  gap: 3px;
}

.mg-matrix__col {
  display: flex;
  flex-direction: column;
}

.mg-matrix__col span {
  display: block;
  width: 9px;
  height: 9px;
  font-size: 8px;
  line-height: 9px;
  text-align: center;
  font-family: var(--mg-font, 'Courier New', monospace);
  color: var(--mg-accent, #0ff);
  opacity: 0.12;
}

/* ---- Wave: two SVG sine-wave layers scrolling horizontally, opposite
   direction + different speed for parallax — pure CSS/SVG, no JS at all ---- */
.mg-wave {
  position: relative;
  width: 100%;
  height: 20px;
  overflow: hidden;
}

.mg-wave__layer {
  position: absolute;
  inset: 0;
  width: 200%;
  height: 100%;
  fill: none;
  stroke-width: 1.5;
}

.mg-wave__layer--primary {
  stroke: var(--mg-accent, #0ff);
  animation: mg-wave-scroll 2.4s linear infinite;
}

.mg-wave__layer--secondary {
  stroke: var(--mg-accent-glow, rgba(0, 229, 255, 0.5));
  opacity: 0.6;
  animation: mg-wave-scroll 3.6s linear infinite reverse;
}

@keyframes mg-wave-scroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* ---- Icon: inline SVG glyph, currentColor-based so it inherits theme
   accent automatically. .mg-icon--pulse adds a slow breathing glow. ---- */
.mg-icon {
  width: 20px;
  height: 20px;
  color: var(--mg-accent, #0ff);
  flex-shrink: 0;
}

.mg-icon--pulse {
  animation: mg-icon-pulse 2.2s ease-in-out infinite;
}

.mg-icon--lg {
  width: 40px;
  height: 40px;
}

@keyframes mg-icon-pulse {
  0%, 100% { opacity: 1; filter: drop-shadow(0 0 0 transparent); }
  50% { opacity: 0.55; filter: drop-shadow(0 0 4px var(--mg-accent-glow, rgba(0, 229, 255, 0.6))); }
}

/* .mg-icon--heartbeat: a double-beat (lub-dub) scale pulse, distinct from
   the generic breathing .mg-icon--pulse — for an actual heart-rate feel. */
.mg-icon--heartbeat {
  animation: mg-icon-heartbeat 1.8s ease-in-out infinite;
}

@keyframes mg-icon-heartbeat {
  0%, 100% { transform: scale(1); }
  10% { transform: scale(1.18); }
  20% { transform: scale(1); }
  30% { transform: scale(1.18); }
  42% { transform: scale(1); }
}

/* Generic tight inline grouping — icon+label, but also reused for e.g. a
   rolling-number sitting right next to its unit label (HZ, BPM). */
.mg-icon-label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.mg-icon-label--tight {
  gap: 0.15rem;
}

/* ---- Scan box: a 4-corner bracket target (pure CSS background layers, no
   extra DOM) wrapping a single icon — reads as "this is being scanned". ---- */
.mg-scan-box {
  position: relative;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  background:
    linear-gradient(var(--mg-accent, #0ff), var(--mg-accent, #0ff)) top left / 8px 1.5px no-repeat,
    linear-gradient(var(--mg-accent, #0ff), var(--mg-accent, #0ff)) top left / 1.5px 8px no-repeat,
    linear-gradient(var(--mg-accent, #0ff), var(--mg-accent, #0ff)) top right / 8px 1.5px no-repeat,
    linear-gradient(var(--mg-accent, #0ff), var(--mg-accent, #0ff)) top right / 1.5px 8px no-repeat,
    linear-gradient(var(--mg-accent, #0ff), var(--mg-accent, #0ff)) bottom left / 8px 1.5px no-repeat,
    linear-gradient(var(--mg-accent, #0ff), var(--mg-accent, #0ff)) bottom left / 1.5px 8px no-repeat,
    linear-gradient(var(--mg-accent, #0ff), var(--mg-accent, #0ff)) bottom right / 8px 1.5px no-repeat,
    linear-gradient(var(--mg-accent, #0ff), var(--mg-accent, #0ff)) bottom right / 1.5px 8px no-repeat;
  opacity: 0.85;
}

/* ---- Mosaic: static NxN grid, each cell a random opacity shade of the
   theme accent — generated once by JS, a texture not an animated element.
   Reads as an "ID verification" swatch without pretending to be a real,
   scannable QR code. ---- */
.mg-mosaic {
  display: grid;
  width: 40px;
  height: 40px;
  gap: 2px;
}

.mg-mosaic span {
  background: var(--mg-accent, #0ff);
}

/* ---- Levels: stacked horizontal bars whose fill grows/shrinks continuously,
   each out of phase — a vitals/level-meter read, e.g. next to a BPM stat. ---- */
.mg-levels {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 100%;
}

.mg-levels__bar {
  position: relative;
  height: 4px;
  background: var(--mg-border, rgba(0, 229, 255, 0.2));
  overflow: hidden;
}

.mg-levels__bar span {
  display: block;
  height: 100%;
  background: var(--mg-accent, #0ff);
  animation-name: mg-levels-pulse;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
}

@keyframes mg-levels-pulse {
  0%, 20% { width: 20%; }
  45% { width: 85%; }
  60% { width: 85%; }
  85%, 100% { width: 20%; }
}

/* ---- Signal: ascending-height bars, a random subset lit at a time —
   ticks to a new discrete "strength level" every 1.6-2.4s, not a continuous
   bounce like the amp primitive. ---- */
.mg-signal {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 16px;
}

.mg-signal span {
  display: block;
  width: 4px;
  background: var(--mg-border, rgba(0, 229, 255, 0.25));
  transition: background 0.4s ease;
}

.mg-signal span.is-active {
  background: var(--mg-accent, #0ff);
}

/* ---- Glow text: static text (never scrambles/cycles) with a breathing
   glow pulse — for scripts where decode-scramble wouldn't apply (e.g. Arabic). ---- */
.mg-glow-text {
  font-size: 1.9rem;
  font-weight: 700;
  color: var(--mg-accent, #0ff);
  text-align: center;
  animation: mg-glow-pulse 2.4s ease-in-out infinite;
}

@keyframes mg-glow-pulse {
  0%, 100% { text-shadow: 0 0 6px var(--mg-accent-glow, rgba(0, 229, 255, 0.6)); opacity: 1; }
  50% { text-shadow: 0 0 16px var(--mg-accent-glow, rgba(0, 229, 255, 0.9)); opacity: 0.85; }
}

/* ---- Stream: a single line of hex/binary characters, a random few of which
   flicker to a new value on a steady interval — a "live data feed" texture,
   distinct from .mg-cli's deliberate typing and .mg-matrix's falling columns. ---- */
.mg-stream {
  font-family: var(--mg-font, 'Courier New', monospace);
  font-size: 0.6rem;
  letter-spacing: 0.08em;
  color: var(--mg-accent, #0ff);
  white-space: pre;
}

/* ---- Compass: a circular dial with a rotating needle — pure CSS, no JS. ---- */
.mg-compass {
  position: relative;
  width: 42px;
  height: 42px;
  border-radius: 50%;
  border: 1px solid var(--mg-border, rgba(0, 229, 255, 0.35));
  flex-shrink: 0;
}

.mg-compass::before {
  content: '';
  position: absolute;
  top: 2px;
  left: 50%;
  width: 1px;
  height: 6px;
  background: var(--mg-accent, #0ff);
  transform: translateX(-50%);
}

.mg-compass__needle {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 2px;
  height: 15px;
  background: linear-gradient(to bottom, var(--mg-accent, #0ff), transparent);
  transform-origin: bottom center;
  transform: translate(-50%, -100%) rotate(-30deg);
  animation: mg-compass-spin 6s ease-in-out infinite;
}

@keyframes mg-compass-spin {
  0%, 100% { transform: translate(-50%, -100%) rotate(-30deg); }
  50% { transform: translate(-50%, -100%) rotate(55deg); }
}

/* ---- Battery: segmented cells that fill then drain in a charge/discharge
   loop, generated once by JS — cell count is static, only fill state ticks. ---- */
.mg-battery {
  position: relative;
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 2px;
  border: 1.5px solid var(--mg-border, rgba(0, 229, 255, 0.35));
  border-radius: 2px;
  flex-shrink: 0;
}

.mg-battery::after {
  content: '';
  position: absolute;
  right: -4px;
  top: 50%;
  width: 3px;
  height: 8px;
  background: var(--mg-border, rgba(0, 229, 255, 0.35));
  transform: translateY(-50%);
}

.mg-battery span {
  width: 6px;
  height: 12px;
  background: var(--mg-accent, #0ff);
  opacity: 0.15;
  transition: opacity 0.3s ease;
}

.mg-battery span.is-on {
  opacity: 1;
}

/* ── World ─────────────────────────────────────────────────────────────────
   Dot-matrix world map scrolling endlessly sideways. The bitmap covers a full
   360 degrees of longitude, so laying two copies side by side and shifting by
   exactly one map width loops with no seam — 180E and 180W are the same
   meridian, so the wrap is the real geography rather than a trick.
   The static dot grid underneath is a background-image, so the ocean costs
   nothing: only land is drawn as elements. */
.mg-world {
  position: relative;
  width: 100%;
  overflow: hidden;
  background-image: radial-gradient(
    circle,
    var(--mg-border, rgba(0, 229, 255, 0.35)) 0.5px,
    transparent 0.6px
  );
  background-size: var(--mg-world-pitch, 3px) var(--mg-world-pitch, 3px);
  opacity: 0.95;
}

.mg-world__track {
  position: absolute;
  top: 0;
  left: 0;
  animation-name: mg-world-scroll;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  will-change: transform;
}

/* One node per hemisphere: every land cell is a box-shadow on this element,
   and currentColor keeps the whole map themeable without a redraw. */
.mg-world__ink {
  position: absolute;
  top: 0;
  display: block;
  background: transparent;
  color: var(--mg-accent, #0ff);
}

@keyframes mg-world-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(var(--mg-world-shift, -216px)); }
}

@media (prefers-reduced-motion: reduce) {
  .mg-world__track { animation: none; }
}

/* ── Gauge ─────────────────────────────────────────────────────────────────
   Semicircular arc gauge. The only primitive that uses --mg-accent-alt: every
   theme defines a secondary/alert accent and, until this one, nothing showed it. */
.mg-gauge {
  position: relative;
  width: 100%;
  max-width: 116px;
  color: var(--mg-accent, #0ff);
}

.mg-gauge__svg {
  display: block;
  width: 100%;
  overflow: visible;
}

.mg-gauge__track {
  fill: none;
  stroke: var(--mg-border, rgba(0, 229, 255, 0.35));
  stroke-width: 5;
}

.mg-gauge__fill {
  fill: none;
  stroke: currentColor;
  stroke-width: 5;
  transition: stroke 0.35s ease;
}

.mg-gauge__tick {
  stroke: var(--mg-border, rgba(0, 229, 255, 0.35));
  stroke-width: 1;
}

.mg-gauge__needle {
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  transition: stroke 0.35s ease;
}

.mg-gauge__hub {
  fill: currentColor;
  transition: fill 0.35s ease;
}

.mg-gauge__value {
  margin-top: -0.35rem;
  text-align: center;
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  color: currentColor;
  transition: color 0.35s ease;
}

.mg-gauge.is-warn {
  color: var(--mg-accent-alt, #ff4d6d);
}

/* ── Spark ─────────────────────────────────────────────────────────────────
   Sparkline that gains a point and scrolls left. The one plotted-over-time
   shape in the set — everything else is magnitude, coordinate or text. */
.mg-spark {
  position: relative;
  width: 100%;
  display: flex;
  align-items: flex-end;
  gap: 0.4rem;
}

.mg-spark__svg {
  display: block;
  flex: 1 1 auto;
  height: 32px;
}

.mg-spark__line {
  fill: none;
  stroke: var(--mg-accent, #0ff);
  stroke-width: 1.2;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.mg-spark__area {
  fill: var(--mg-accent, #0ff);
  opacity: 0.14;
  stroke: none;
}

.mg-spark__head {
  fill: var(--mg-accent, #0ff);
}

.mg-spark__value {
  flex: 0 0 auto;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  color: var(--mg-text-dim, #6f9aa1);
}

/* ── Queue ─────────────────────────────────────────────────────────────────
   Structured rows advancing through states — the shape real dashboards are
   mostly made of, and the only one the library was missing. */
.mg-queue {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.mg-queue__row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  color: var(--mg-text-dim, #6f9aa1);
  border-left: 2px solid var(--mg-border, rgba(0, 229, 255, 0.35));
  padding-left: 0.4rem;
  transition: color 0.25s ease, border-color 0.25s ease;
}

.mg-queue__state {
  font-size: 0.55rem;
  opacity: 0.75;
}

.mg-queue__row.is-running {
  color: var(--mg-accent, #0ff);
  border-left-color: var(--mg-accent, #0ff);
}

.mg-queue__row.is-running .mg-queue__state {
  opacity: 1;
  animation: mg-queue-blink 0.9s steps(1) infinite;
}

.mg-queue__row.is-done {
  color: var(--mg-text, #d8fbff);
  border-left-color: var(--mg-accent, #0ff);
}

.mg-queue__row.is-done .mg-queue__state {
  opacity: 1;
}

@keyframes mg-queue-blink {
  0%, 60% { opacity: 1; }
  61%, 100% { opacity: 0.25; }
}

@media (prefers-reduced-motion: reduce) {
  .mg-queue__row.is-running .mg-queue__state { animation: none; }
}
