/* ==========================================================================
   Mall Security — Signal system (v4 integration)
   ==========================================================================
   Derived from the approved ntsc-monitor-prototype v4 `monitor.css`.

   The live DOM is deliberately almost untouched: no full-page SVG filter, no
   per-frame style writes, no per-frame DOM mutation. Everything expensive
   lives in two WebGL canvases that the page never has to know about.

   Only the signal layer lives here. Layout, design tokens, branding and
   component styles stay in crt.css.
   ========================================================================== */

/* Cross-document view transitions power the Retune. Same-origin documents
   only; opted out entirely under reduced motion (see the bottom of the file). */
@view-transition { navigation: auto; }

:root {
  /* Signal master + reconstruction */
  --ambient: 1;
  --soft: 1;

  /* Retune / view-transition */
  --vt-dur: 190ms;
  --old-op: 1;
  --new-op: 0;

  /* Typography signal treatment. Base values come from the controller and
     change only when a control moves. --t-boost and --t-vert change ONLY at
     the start and end of a disturbance: two property writes per event, never
     per frame. */
  --t-smear: 1;
  --t-chroma: 1.25;
  --t-sharp: 1;
  --t-body: .70;      /* higher = more protected */
  --t-body-distort: 1;
  --t-boost: 1;       /* 1 while locked, >1 during a disturbance */
  --t-vert: 0;        /* vertical chroma misalignment, disturbance only */

  /* Output-stage scan lines */
  --scan-lines: .65;
}

/* ---------------------------------------------------------------------------
   THE OVERLAY CANVASES
   Fixed to the VIEWPORT, not the document. Direct children of <body>, so no
   page wrapper, overflow rule, border radius or ancestor transform can clip
   them — and #bezel's disturbance transform is not an ancestor of them.

   #bezel is given `z-index: 0` in crt.css so it forms a stacking context;
   that keeps every in-page layer (sticky header at 300, sub-nav at 400, …)
   underneath these canvases without renumbering the site.
   --------------------------------------------------------------------------- */
#signal-gl,
#signal-snow {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  width: 100vw; height: 100vh;
  margin: 0; padding: 0; border: 0; border-radius: 0;
  pointer-events: none;
  opacity: 1;              /* master is applied once per shader uniform */
  clip-path: none;
  transform: none;
}

/* Broad signal: rendered well below viewport resolution and upscaled by the
   compositor. The upscale IS the pixelisation and the softness. */
#signal-gl {
  z-index: 100;
  image-rendering: auto;
}

/* Crisp screen-space snow / interference / head switching, 1:1 CSS pixels. */
#signal-snow {
  z-index: 101;
  image-rendering: pixelated;
}

/* Fine output-stage scan lines. A static compositor layer, so it covers every
   signal effect without adding another animated shader pass. Sits below the
   debug panel (z-index 200) so tuning controls stay clean. */
body::before {
  content: "";
  position: fixed; inset: 0; z-index: 103;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    rgba(255,255,255,.025) 0,
    rgba(255,255,255,.025) 1px,
    transparent 1px,
    transparent 3px,
    rgba(0,0,0,.28) 3px,
    rgba(0,0,0,.28) 4px
  );
  opacity: calc(var(--scan-lines) * .25);
}

/* Optional. Costs a real per-frame blur pass, so it is OFF until measured. */
body.bloom-on::after {
  content: "";
  position: fixed; inset: 0;
  pointer-events: none;
  z-index: 102;
  backdrop-filter: blur(2px) brightness(1.3);
  -webkit-backdrop-filter: blur(2px) brightness(1.3);
  mix-blend-mode: screen;
  opacity: .1;
}

/* ---------------------------------------------------------------------------
   DISTURBANCE — the DOM half
   One class, one transform and one filter, set at event start and cleared at
   event end. Never per frame. will-change keeps the picture on its own layer
   so the twitch does not repaint the document.
   --------------------------------------------------------------------------- */
#bezel { will-change: transform; }
#bezel.signal-disturbed { transition: none; }

/* Elements that may be nudged and fringed when a tracking band crosses them.
   Headings only, and only during an event. The controller caches this list at
   boot — it never queries or measures per frame. */
[data-signal-fringe] { will-change: transform; }

/* Displaced glass slices during a disturbance: two inert, aria-hidden clones
   of the picture, clipped to thin horizontal bands and pushed sideways. */
.signal-glass {
  position: fixed; inset: 0; z-index: 99;
  margin: 0;
  pointer-events: none; overflow: hidden;
  clip-path: inset(var(--glass-y) 0 calc(100vh - var(--glass-y) - var(--glass-h)) 0);
  transform: translate3d(var(--glass-x), 0, 0) skewX(.06deg);
  filter: brightness(1.02) contrast(1.03);
}
.signal-glass-1 { opacity: .62; }

/* ---------------------------------------------------------------------------
   SIGNAL TYPOGRAPHY
   Layered text-shadow only: no SVG, no filter, no rasterisation. The glyph
   itself stays the sharp core and stays selectable, focusable and in the
   accessibility tree.

   Applied through a shared selector set rather than a class on every text
   node, so page markup needs no decoration. Scoped to #bezel, which excludes
   the debug panel entirely. Form fields are deliberately not included —
   typed input must stay perfectly clean.
   --------------------------------------------------------------------------- */
#bezel :is(
  h1, h2, h3, h4, p, li, dt, dd, summary, button, label,
  .page-title, .page-subtitle,
  .menu-item-label, .menu-sub-link, .soon-tag,
  .header-nav a, .header-nav summary, .header-nav .is-soon,
  .hud-tag, .site-footer,
  .mailing-list-label, .menu-mailing-label,
  .accordion-content, .contact-row-label, .contact-row-addr,
  .contact-mailto, .contact-note,
  .show-date, .show-city, .ticket-link, .show-ticket-link,
  .merch-item-name, .merch-item-price, .merch-item-link, .merch-placeholder,
  .journal-entry-meta, .journal-entry-title, .journal-entry-body,
  .epk-section-title, .epk-download-link,
  .not-found-code, .not-found-title, .not-found-subtitle, .not-found-link
) {
  --w: 1;                                    /* per-element weight */
  --cx: calc(1.15px * var(--t-chroma) * var(--t-boost) * var(--w));
  --cy: calc(0.3px * var(--t-chroma) * var(--t-boost) * var(--w) * var(--t-vert));
  --sl: calc(3.8px * var(--t-smear) * var(--t-boost) * var(--w));
  --so: calc(0.12 * var(--t-smear) * var(--w));
  --ha: calc(0.48 * var(--t-sharp) * var(--w));
  text-shadow:
    -1px 0 rgba(0,0,0,var(--ha)), 1px 0 rgba(0,0,0,var(--ha)),
    0 -1px rgba(0,0,0,calc(var(--ha)*.55)), 0 1px rgba(0,0,0,calc(var(--ha)*.55)),
    calc(var(--cx)*-1) 0 rgba(0,215,235,.34),
    var(--cx) var(--cy) rgba(255,45,50,.38),
    calc(var(--sl)*.55) 0 rgba(255,255,255,var(--so)),
    var(--sl) 0 rgba(255,255,255,calc(var(--so)*.65)),
    calc(var(--sl)*2.2) 0 rgba(255,255,255,calc(var(--so)*.28)),
    0 0 2px rgba(235,245,255,calc(var(--so)*1.4)),
    calc(var(--sl)*1.1) 0 3px rgba(220,235,255,calc(var(--so)*.55));
}

/* Bigger, brighter type carries more separation. */
#bezel :is(h1, .page-title, .not-found-code)            { --w: 1.55; }
#bezel :is(h2, h3, .journal-entry-title,
           .epk-section-title, .not-found-title)         { --w: 1.15; }
#bezel :is(.page-subtitle, .menu-item-label, .menu-sub-link,
           .header-nav a, .header-nav summary, .header-nav .is-soon,
           summary, button, label,
           .mailing-list-label, .menu-mailing-label,
           .contact-mailto, .contact-row-label, .contact-row-addr,
           .merch-item-link, .ticket-link, .show-ticket-link,
           .epk-download-link, .not-found-link,
           .show-date, .show-city)                       { --w: .8; }

/* Body copy is protected. `--t-body` (body readability) controls the entire
   body-copy treatment: 0 is roughly 10x dirtier than the approved .70, and
   1 is genuinely clean. */
#bezel :is(p, li, dt, dd, .accordion-content,
           .journal-entry-body, .not-found-subtitle,
           .merch-item-name, .merch-placeholder) {
  --body-grunge: max(0, calc(1 + (.70 - var(--t-body)) * 14.2857));
  --w: calc(.57 * var(--body-grunge) * var(--t-body-distort));
  --sl: calc(6px * var(--t-smear) * var(--t-boost) * var(--t-body-distort) * var(--body-grunge));
  --so: calc(.22 * var(--t-smear) * var(--t-body-distort) * var(--body-grunge));
}

/* Small type stays legible: the site's --fs-xs tier and the HUD chrome carry
   a fraction of the treatment. */
#bezel :is(.hud-tag, .soon-tag, .site-footer, .contact-note,
           .merch-item-price, .journal-entry-meta) {
  --w: .5;
  --sl: calc(2.4px * var(--t-smear) * var(--t-boost));
  --so: calc(.09 * var(--t-smear));
}

/* HUD chrome (CAM 01, the clock, REC, the corner tag) reads as camera overlay
   sitting on the glass rather than as part of the picture. A fraction of a
   pixel of blur is enough to stop it looking like crisp UI text on top of a
   soft signal. Kept deliberately sub-pixel: these sit at --fs-xs (~10px), and
   a full 1px starts costing legibility. */
#bezel .hud-tag { filter: blur(0.4px); }

/* Never treat typed input, and never treat the tuning panel. */
#bezel :is(input, textarea, select),
#devpanel, #devpanel * { text-shadow: none; }

/* ---------------------------------------------------------------------------
   MEDIA — one shared content-reactive renderer, attached on demand.
   Global snow, scan lines, interference, flicker, dot crawl and head
   switching are NOT duplicated here; they arrive from the global canvases,
   which sit above the media too.
   --------------------------------------------------------------------------- */
.signal-media-host { position: relative; }
.signal-media-canvas {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
  pointer-events: none;
  z-index: 1;
}
.signal-media-on > :is(img, video) { visibility: hidden; }
/* Overlay layers live outside the media host, so they are hidden by class
   rather than by descendant rule. Layout box, focus ring and accessible name
   are all preserved — only the pixels move into the texture. */
.signal-media-overlay-on { visibility: hidden; }

/* ---------------------------------------------------------------------------
   RETUNE — snapshots only. The one place where distorting the whole page is
   both legitimate and free: the browser has already rasterised both documents
   and nothing is interactive for the duration.
   --------------------------------------------------------------------------- */
#devpanel { view-transition-name: devpanel; }
::view-transition-group(devpanel) { animation: none; }
::view-transition-old(devpanel), ::view-transition-new(devpanel) { animation: none; opacity: 0; }

::view-transition-group(root) { animation-duration: var(--vt-dur); }
::view-transition-old(root), ::view-transition-new(root) {
  mix-blend-mode: normal;
  animation: vt-hold var(--vt-dur) linear both;
}
@keyframes vt-hold { from {} to {} }
::view-transition-old(root) { opacity: var(--old-op); filter: url(#ft-old); }
::view-transition-new(root) { opacity: var(--new-op); filter: url(#ft-new); }

/* Leaving for an external destination: the incoming snapshot is a dead
   picture, so the retune reads as the signal dropping rather than as a
   crossfade into an identical page. */
html.signal-leaving #bezel { opacity: 0; }

html.no-retune ::view-transition-old(root) { opacity: 0; filter: none; }
html.no-retune ::view-transition-new(root) { opacity: 1; filter: none; }
html.no-retune ::view-transition-group(root) { animation-duration: 1ms; }

/* ---------------------------------------------------------------------------
   TUNING PANEL — ?debug=1 only. Absent from the DOM otherwise.
   Rectangular red/white sliders with draggable white range markers, exactly
   as approved.
   --------------------------------------------------------------------------- */
#devpanel {
  position: fixed; right: 10px; top: 10px; bottom: 10px;
  z-index: 200; width: 236px; overflow: auto;
  padding: 10px 12px 14px;
  background: rgba(6,10,14,.94);
  border: 1px solid var(--accent-dim, rgba(255,173,59,.4));
  color: #cfe3ec;
  font: 11px/1.45 ui-monospace, monospace;
}
#devpanel h2 { margin: 0 0 6px; font-size: 10px; letter-spacing: .2em; color: var(--accent); }
#devpanel h3 {
  margin: 11px 0 3px; font-size: 9px; letter-spacing: .18em; color: #7f9aa8;
  text-transform: uppercase; border-top: 1px solid rgba(255,255,255,.1); padding-top: 7px;
}
#devpanel label { display: block; margin: 5px 0 1px; }
#devpanel input[type=range] {
  --range-fill: 0%; width: 100%; height: 24px; margin: 0 0 4px;
  appearance: none; -webkit-appearance: none; background: transparent; cursor: ew-resize;
}
#devpanel input[type=range]::-webkit-slider-runnable-track {
  height: 6px; border: 0; border-radius: 0;
  background: linear-gradient(to right,#ed3f17 0 var(--range-fill),#f4f4f2 var(--range-fill) 100%);
}
#devpanel input[type=range]::-webkit-slider-thumb {
  appearance: none; -webkit-appearance: none; width: 8px; height: 24px;
  margin-top: -9px; border: 0; border-radius: 0; background: #ed3f17;
  box-shadow: none;
}
#devpanel input[type=range]::-moz-range-track { height: 6px; border: 0; border-radius: 0; background: #f4f4f2; }
#devpanel input[type=range]::-moz-range-progress { height: 6px; border-radius: 0; background: #ed3f17; }
#devpanel input[type=range]::-moz-range-thumb { width: 8px; height: 24px; border: 0; border-radius: 0; background: #ed3f17; }
#devpanel .wobble-range { position: relative; height: 24px; margin: 0 0 4px; }
#devpanel .wobble-range input[type=range] { position: absolute; inset: 0; margin: 0; }
#devpanel .wobble-mark {
  position: absolute; z-index: 3; top: 0; width: 4px; height: 24px;
  background: #f4f4f2; box-shadow: none; transform: translateX(-2px);
  cursor: ew-resize; touch-action: none;
}
#devpanel .wobble-mark::after { content: ''; position: absolute; inset: -5px -7px; }
#devpanel .row { display: flex; align-items: center; gap: 6px; margin: 2px 0; }
#devpanel .row label { margin: 0; }
#devpanel .val { float: right; opacity: .6; }
#devpanel button {
  width: 100%; margin-top: 6px; padding: 6px;
  background: transparent; border: 1px solid var(--accent-dim, rgba(255,173,59,.4));
  color: var(--accent); font: inherit; cursor: pointer;
}
#devpanel button:hover { background: var(--accent-tint, rgba(255,173,59,.12)); }
#devpanel .fps-row {
  position: sticky; bottom: -1px; z-index: 4;
  min-height: 25px; margin: 8px -6px -2px; padding: 5px 7px;
  background: rgba(6,10,14,.98);
  border-top: 1px solid rgba(159,232,176,.28);
}
#devpanel #fps { font-variant-numeric: tabular-nums; color: #9fe8b0; }

/* ---------------------------------------------------------------------------
   REDUCED MOTION — no overlay, no retune, no twitching. Fully usable page.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
  #signal-gl, #signal-snow { display: none; }
  body.bloom-on::after { display: none; }
  #bezel { will-change: auto; }
  .signal-glass { display: none; }
}
