/* ============================================================
   OFF-WORLD CRT EFFECTS — cinematic layer
   Rule: set pieces loud, reading quiet.
   One attribute on #bladerunner-view drives every effect:
     data-crt-intensity = "setpiece" | "settling" | "reading"
   Effects read the custom properties below; JS only flips the
   attribute (power-on, boot, sweep, mode select, power-off).
   Spec: docs/superpowers/specs/2026-09-21-cinematic-crt-layer-design.md
   ============================================================ */

#bladerunner-view {
  --ph: 255, 176, 64;            /* phosphor rgb (MU-TH-UR amber) */
  --lit: 0.45;                   /* 0..1 lit-area estimate, published by JS */
  --bloom: 1;                    /* text bloom multiplier */
  --spill: 1;                    /* ambient spill outside the glass */
  --grain: 0.03;                 /* film grain opacity */
  --sway-gain: 1;                /* pointer sway response of the glass highlight */
  --scan-alpha: 0.12;            /* scanline darkness */
  --tube-radius: clamp(10px, 1.6vw, 22px);
}

#bladerunner-view[data-crt-intensity="setpiece"] {
  --bloom: 1.6;
  --spill: 1.7;
  --grain: 0.06;
  --sway-gain: 1.4;
  --scan-alpha: 0.24;
}

#bladerunner-view[data-crt-intensity="settling"] {
  --bloom: 1.2;
  --spill: 1.2;
  --grain: 0.04;
  --sway-gain: 1.1;
  --scan-alpha: 0.18;
}

#bladerunner-view[data-crt-intensity="reading"] {
  --bloom: 1;
  --spill: 0.85;
  --grain: 0.015;
  --sway-gain: 1;
  --scan-alpha: 0.08;
}

/* Long idle (45s+): the room takes over a little before the screensaver. */
#bladerunner-view[data-crt-idle="1"] {
  --spill: 0.55;
  --bloom: 0.9;
}

/* ---- Property transitions: everything settles, nothing snaps ---- */
#bladerunner-view .br-terminal,
#bladerunner-view .br-screen,
#bladerunner-view .br-noise,
#bladerunner-view .br-scanlines {
  transition:
    box-shadow 1.2s ease-out,
    opacity 1.2s ease-out,
    filter 1.2s ease-out;
}

/* ============================================================
   THE ROOM — a dark cockpit/desk behind the tube.
   Placeholder built from gradients, sized to the same 1200x896
   frame as the Minitel diorama so pixel art can drop in later as
   assets/images/offworld-scene.* via image-set with no code change.
   ============================================================ */
/* Room art and tube placement live in br-scene.css (ROOM ART block). */

/* ============================================================
   THE TUBE — rounded glass, bezel lip, phosphor spill that
   tracks how much of the screen is lit (--lit).
   ============================================================ */
body[data-view="bladerunner"] .br-screen {
  border-radius: var(--tube-radius);
  box-shadow:
    /* bezel lip and inner recess */
    inset 0 0 0 1px rgba(2, 6, 4, 0.95),
    inset 0 2px 0 rgba(255, 255, 255, 0.05),
    inset 0 -6px 18px rgba(0, 0, 0, 0.5),
    inset 0 0 70px rgba(0, 0, 0, 0.42),
    /* glass edge catching the room */
    0 0 0 2px rgba(60, 70, 60, 0.35),
    0 0 0 6px rgba(12, 14, 12, 0.9),
    0 6px 24px rgba(0, 0, 0, 0.8);
  /* Beam-current sag is kept tiny: legibility beats physics. */
  filter: brightness(calc(1.04 - var(--lit) * 0.03));
}

/* Ambient spill outside the glass lives on the frame, scaled by --spill and --lit. */
#bladerunner-view .br-terminal {
  border-radius: calc(var(--tube-radius) + 6px);
  box-shadow:
    inset 0 0 0 1px rgba(var(--ph), 0.08),
    0 0 calc(18px * var(--spill)) rgba(var(--ph), calc(0.16 * var(--spill) * (0.6 + var(--lit)))),
    0 0 calc(70px * var(--spill)) rgba(var(--ph), calc(0.12 * var(--spill) * (0.6 + var(--lit)))),
    0 0 calc(180px * var(--spill)) rgba(var(--ph), calc(0.08 * var(--spill) * (0.6 + var(--lit)))),
    0 0 calc(420px * var(--spill)) rgba(var(--ph), calc(0.045 * var(--spill) * (0.6 + var(--lit))));
  filter: none;
}

/* Bezel edge shading inside the glass */
body[data-view="bladerunner"] .br-screen::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 9;
  pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.04) 0%, rgba(255, 255, 255, 0) 8%, rgba(0, 0, 0, 0) 72%, rgba(0, 0, 0, 0.45) 100%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.02),
    inset 0 0 40px rgba(0, 0, 0, 0.35),
    inset 0 -20px 50px rgba(0, 0, 0, 0.4);
}

/* ---- Scanlines: static, DPR-exact (from applyScanlinePitch) ---- */
body[data-view="bladerunner"] .br-scanlines {
  position: absolute;
  inset: 0;
  z-index: 10;
  pointer-events: none;
  border-radius: inherit;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent var(--scan-lit, 1px),
    rgba(0, 0, 0, var(--scan-alpha)) var(--scan-lit, 1px),
    rgba(0, 0, 0, var(--scan-alpha)) var(--scan-dark-end, 3px),
    transparent var(--scan-dark-end, 3px),
    transparent var(--scan-pitch, 4px)
  );
}

/* ---- Slow scanning bar: felt, not seen ---- */
body[data-view="bladerunner"] .br-scanline-bar {
  position: absolute;
  left: 0;
  right: 0;
  height: 12px;
  z-index: 11;
  pointer-events: none;
  background: linear-gradient(180deg, transparent, rgba(var(--ph), 0.025) 50%, transparent);
  animation: br-scanbar 6s linear infinite;
}
@keyframes br-scanbar { 0% { top: -12px; } 100% { top: 100%; } }

/* ---- Vignette + curvature: darkening sells the curve, glyphs stay flat ---- */
body[data-view="bladerunner"] .br-vignette {
  position: absolute;
  inset: 0;
  z-index: 12;
  pointer-events: none;
  border-radius: inherit;
  background: radial-gradient(ellipse 78% 72% at 50% 50%, transparent 55%, rgba(0, 0, 0, 0.16) 78%, rgba(0, 0, 0, 0.45) 94%, rgba(0, 0, 0, 0.7) 100%);
}
body[data-view="bladerunner"] .br-curvature {
  position: absolute;
  inset: 0;
  z-index: 15;
  pointer-events: none;
  border-radius: inherit;
  background:
    radial-gradient(circle at 0% 0%, rgba(0, 0, 0, 0.3) 0%, transparent 28%),
    radial-gradient(circle at 100% 0%, rgba(0, 0, 0, 0.3) 0%, transparent 28%),
    radial-gradient(circle at 0% 100%, rgba(0, 0, 0, 0.3) 0%, transparent 28%),
    radial-gradient(circle at 100% 100%, rgba(0, 0, 0, 0.3) 0%, transparent 28%);
}

/* ---- Glass: specular highlight that moves with pointer sway ---- */
body[data-view="bladerunner"] .br-glass {
  position: absolute;
  inset: 0;
  z-index: 14;
  pointer-events: none;
  border-radius: inherit;
  overflow: hidden;
  background: linear-gradient(125deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.018) 22%, transparent 44%);
  transform: translate(calc(var(--sway-x, 0px) * -0.6 * var(--sway-gain)), calc(var(--sway-y, 0px) * -0.6 * var(--sway-gain)));
  will-change: transform;
}

/* Rain outside the window, seen only as a reflection on the glass:
   two soft streak layers drifting down, with a slow neon colour drift. */
@media (min-width: 900px) {
  body[data-view="bladerunner"] .br-glass::before {
    content: '';
    position: absolute;
    inset: -20% 0;
    pointer-events: none;
    opacity: 0.22;
    mix-blend-mode: screen;
    background:
      repeating-linear-gradient(100deg, transparent 0 22px, rgba(220, 180, 120, 0.08) 22px 23px, transparent 23px 41px),
      repeating-linear-gradient(98deg, transparent 0 37px, rgba(120, 200, 200, 0.06) 37px 38px, transparent 38px 66px);
    background-size: 100% 120px, 100% 200px;
    animation: br-rain-fall 7s linear infinite, br-neon-drift 14s ease-in-out infinite;
    /* Only the upper-right quadrant, where the window is */
    -webkit-mask-image: radial-gradient(60% 55% at 80% 20%, #000 0%, transparent 100%);
    mask-image: radial-gradient(60% 55% at 80% 20%, #000 0%, transparent 100%);
  }
}
@keyframes br-rain-fall {
  0% { background-position: 0 0, 0 0; }
  100% { background-position: 0 120px, 0 200px; }
}
@keyframes br-neon-drift {
  0%, 100% { filter: hue-rotate(0deg); }
  50% { filter: hue-rotate(-40deg); }
}

/* ---- Film grain: scales with intensity ---- */
body[data-view="bladerunner"] .br-noise {
  position: absolute;
  inset: -50%;
  z-index: 16;
  pointer-events: none;
  background: url('../assets/images/noise.svg') repeat;
  background-size: 200px 200px;
  opacity: var(--grain);
  animation: br-grain 0.4s steps(5) infinite;
}
@keyframes br-grain {
  0% { transform: translate(0, 0); }
  20% { transform: translate(-8px, 12px); }
  40% { transform: translate(12px, -8px); }
  60% { transform: translate(-4px, -6px); }
  80% { transform: translate(6px, 4px); }
  100% { transform: translate(0, 0); }
}

/* ---- Glitch: event-driven only (mode exit, line noise) ---- */
body[data-view="bladerunner"] .br-glitch {
  position: absolute;
  inset: 0;
  z-index: 17;
  pointer-events: none;
  border-radius: inherit;
  opacity: 0;
}
body[data-view="bladerunner"] .br-screen.is-glitch .br-glitch {
  animation: br-glitch-once 0.32s steps(1) 1;
}
@keyframes br-glitch-once {
  0%   { opacity: 1; clip-path: inset(25% 0 65% 0); transform: translateX(3px); background: rgba(var(--ph), 0.06); }
  40%  { opacity: 1; clip-path: inset(75% 0 15% 0); transform: translateX(-2px); background: rgba(var(--ph), 0.05); }
  70%  { opacity: 1; clip-path: inset(40% 0 52% 0); transform: translateX(2px); background: rgba(var(--ph), 0.04); }
  100% { opacity: 0; clip-path: none; transform: none; }
}

/* ---- Bloom on headings scales with --bloom ---- */
body[data-view="bladerunner"] .double-height {
  text-shadow:
    0 0 2px currentColor,
    0 0 calc(5px * var(--bloom)) currentColor,
    0 0 calc(12px * var(--bloom)) rgba(var(--ph), 0.4),
    0 0 calc(26px * var(--bloom)) rgba(var(--ph), 0.16);
}

/* ============================================================
   SET PIECE 1 — POWER-ON
   Black > overbright horizontal line > raster opens > bloom
   overshoot > settle. Cold: 1.8s. Warm: 0.7s.
   ============================================================ */
body[data-view="bladerunner"] .br-screen.is-powering-on {
  animation: br-power-on 1.8s cubic-bezier(0.2, 0.8, 0.2, 1) both;
}
body[data-view="bladerunner"] .br-screen.is-powering-on.is-warm {
  animation-duration: 0.7s;
}
@keyframes br-power-on {
  0%   { clip-path: inset(50% 0 50% 0); filter: brightness(0); }
  8%   { clip-path: inset(49.6% 0 49.6% 0); filter: brightness(4) saturate(0.2); }
  22%  { clip-path: inset(49.5% 0 49.5% 0); filter: brightness(3.2) saturate(0.3); }
  48%  { clip-path: inset(0 0 0 0); filter: brightness(1.6) saturate(0.7); }
  66%  { clip-path: inset(0 0 0 0); filter: brightness(1.35) saturate(0.9); }
  80%  { filter: brightness(0.92); }
  100% { clip-path: inset(0 0 0 0); filter: brightness(1); }
}

/* Camera push-in across the power-on: scale 1.04 -> 1.0 */
body[data-view="bladerunner"] .br-terminal.is-dolly {
  animation: br-dolly 2.6s cubic-bezier(0.2, 0.7, 0.2, 1) both;
  transform-origin: 50% 46%;
}
@keyframes br-dolly {
  0% { transform: scale(1.04); }
  100% { transform: scale(1); }
}

/* ============================================================
   SET PIECE 2 — POWER-OFF (leaving the mode)
   Collapse to a line, then a dot, with afterglow.
   ============================================================ */
body[data-view="bladerunner"] .br-screen.is-powering-off {
  animation: br-power-off 0.9s cubic-bezier(0.7, 0, 0.9, 0.4) both;
}
@keyframes br-power-off {
  0%   { clip-path: inset(0 0 0 0); filter: brightness(1); }
  30%  { clip-path: inset(0 0 0 0); filter: brightness(1.5); }
  55%  { clip-path: inset(49.5% 0 49.5% 0); filter: brightness(3); }
  75%  { clip-path: inset(49.6% 46% 49.6% 46%); filter: brightness(3); }
  100% { clip-path: inset(49.9% 49.8% 49.9% 49.8%); filter: brightness(0); }
}

/* ============================================================
   SET PIECE 3 — PAGE TRANSITION AS BEAM REDRAW
   .br-ghost holds the outgoing page for ~400ms (phosphor
   persistence); .br-sweep is a bright band wiping top to bottom.
   ============================================================ */
body[data-view="bladerunner"] .br-ghost {
  position: absolute;
  inset: 0;
  z-index: 8;
  pointer-events: none;
  padding: clamp(12px, 2vw, 24px);
  overflow: hidden;
  color: rgba(var(--ph), 0.55);
  filter: blur(0.6px) brightness(0.8);
  animation: br-ghost-fade 0.42s ease-out both;
}
@keyframes br-ghost-fade {
  0% { opacity: 0.9; }
  100% { opacity: 0; }
}

body[data-view="bladerunner"] .br-sweep {
  position: absolute;
  left: 0;
  right: 0;
  top: -12%;
  height: 12%;
  z-index: 18;
  pointer-events: none;
  opacity: 0;
  background: linear-gradient(180deg, transparent, rgba(var(--ph), 0.10) 40%, rgba(var(--ph), 0.28) 85%, rgba(255, 255, 255, 0.35) 100%);
}
body[data-view="bladerunner"] .br-screen.is-sweeping .br-sweep {
  animation: br-sweep-down 0.32s linear both;
}
body[data-view="bladerunner"] .br-screen.is-sweeping.is-sweep-back .br-sweep {
  animation-duration: 0.22s;
  opacity: 0.6;
}
@keyframes br-sweep-down {
  0%   { transform: translateY(0); opacity: 1; }
  100% { transform: translateY(940%); opacity: 1; }
}

/* ESPER enhance on forward navigation only (class applied by JS) */
body[data-view="bladerunner"] .br-screen.esper-scan .videotex-display {
  animation: br-esper 0.42s steps(6) both;
}
@keyframes br-esper {
  0%   { filter: brightness(1.3) contrast(1.4); }
  100% { filter: none; }
}

/* ============================================================
   SET PIECE 4 — MODE SELECT static burst
   A full-viewport canvas the JS fills with noise for 250ms.
   ============================================================ */
.crt-static-burst {
  position: fixed;
  inset: 0;
  z-index: 3900;
  pointer-events: none;
  opacity: 0;
  image-rendering: pixelated;
}
.crt-static-burst.is-live {
  opacity: 0.85;
}

/* ============================================================
   MOBILE (<900px): tube, static scanlines, power-on and sweep only.
   ============================================================ */
@media (max-width: 899px) {
  body[data-view="bladerunner"] .br-glass { transform: none; will-change: auto; }
  body[data-view="bladerunner"] .br-glass::before { content: none; }
  body[data-view="bladerunner"] .br-noise { display: none; }
  body[data-view="bladerunner"] .br-terminal.is-dolly { animation: none; }
  #bladerunner-view { --spill: 0.5; }
}

/* ============================================================
   REDUCED MOTION: no set pieces, no sweeps, no sway, no rain.
   Tube shape, static scanlines and static spill remain.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  body[data-view="bladerunner"] .br-screen,
  body[data-view="bladerunner"] .br-screen.is-powering-on,
  body[data-view="bladerunner"] .br-screen.is-powering-off,
  body[data-view="bladerunner"] .br-terminal.is-dolly,
  body[data-view="bladerunner"] .br-screen.is-sweeping .br-sweep,
  body[data-view="bladerunner"] .br-screen.esper-scan .videotex-display,
  body[data-view="bladerunner"] .br-screen.is-glitch .br-glitch,
  body[data-view="bladerunner"] .br-ghost,
  body[data-view="bladerunner"] .br-noise,
  body[data-view="bladerunner"] .br-glass::before {
    animation: none;
  }
  body[data-view="bladerunner"] .br-screen { clip-path: none; filter: none; }
  body[data-view="bladerunner"] .br-scanline-bar { display: none; }
  body[data-view="bladerunner"] .br-ghost,
  body[data-view="bladerunner"] .br-sweep,
  body[data-view="bladerunner"] .br-glass::before,
  .crt-static-burst { display: none; }
  body[data-view="bladerunner"] .br-glass { transform: none; }
  #bladerunner-view .br-terminal,
  #bladerunner-view .br-screen { transition: none; }
}

/* Legibility: menu labels and headings read at full phosphor. */
body[data-view="bladerunner"] .vtx-menu-label,
body[data-view="bladerunner"] .ow-recent-title { color: var(--br-grn-hi); }
body[data-view="bladerunner"] .videotex-display { text-shadow: 0 0 1px currentColor, 0 0 4px rgba(var(--ph), 0.35); }
body[data-view="bladerunner"] .br-ghost { background: transparent !important; }
