/* ===========================================================================
   crt.css - the tube.

   Everything in here is a port, in CSS, of the post-process pass in
   HeadlessTerm.Retro's `Shaders.cs`: scanlines, an RGB aperture-grille mask,
   phosphor glow, a drifting hum bar, flicker, static and a vignette. The
   colours are lifted verbatim from `TerminalPalette.cs` so the site and the
   game agree on what "green" means.

   The effect is a stack of fixed, pointer-events:none overlays over the whole
   document, so the page itself stays ordinary HTML: selectable, searchable,
   and readable with the tube switched off (F9).
   =========================================================================== */

:root {
    /* --- Core defaults (TerminalPalette.DefaultForeground/Background) ------ */
    --bg: #080a08;
    --bg-deep: #040504;
    --bg-panel: #0d110d;
    --fg: #d9ded9;

    /* --- xterm ANSI 0-7 (TerminalPalette ansi[]) -------------------------- */
    --ansi-black: #000000;
    --ansi-red: #cc241d;
    --ansi-green: #26a64c;
    --ansi-yellow: #c79a22;
    --ansi-blue: #235a97;
    --ansi-magenta: #883f9f;
    --ansi-cyan: #148a8a;
    --ansi-white: #c0c0c0;

    /* --- xterm ANSI 8-15, the bright half --------------------------------- */
    --ansi-bright-black: #606060;
    --ansi-bright-red: #f24b3e;
    --ansi-bright-green: #39d353;
    --ansi-bright-yellow: #eac14f;
    --ansi-bright-blue: #428bdd;
    --ansi-bright-magenta: #b96bd6;
    --ansi-bright-cyan: #2ec8c8;
    --ansi-bright-white: #f2f2f2;

    /* --- Semantic aliases -------------------------------------------------- */
    --accent: var(--ansi-bright-green);
    --accent-dim: var(--ansi-green);
    --warn: var(--ansi-bright-yellow);
    --danger: var(--ansi-bright-red);
    --link: var(--ansi-bright-cyan);
    --muted: #7f8a7f;
    --rule: rgba(57, 211, 83, 0.22);
    --rule-soft: rgba(217, 222, 217, 0.12);

    /* --- Effect strength. `uCrt` in the shader; F9 drives it. ------------- */
    --crt: 1;
    --scan-opacity: 0.28;
    --grille-opacity: 0.5;
    --noise-opacity: 0.035;

    /* --- Type ------------------------------------------------------------- */
    --mono: "Cascadia Mono", "Cascadia Code", "JetBrains Mono", "Fira Code",
        "SF Mono", Menlo, Consolas, "DejaVu Sans Mono", "Liberation Mono", monospace;
    --cell: 1ch;
    --leading: 1.55;

    /* --- Layout ------------------------------------------------------------ */
    --page: 1180px;
    --gap: clamp(1rem, 2.5vw, 2rem);
    --radius: 4px;
}

/* The tube switched off: no overlays, no glow, plain high-contrast page. */
body[data-crt="off"] {
    --crt: 0;
    --scan-opacity: 0;
    --grille-opacity: 0;
    --noise-opacity: 0;
}

/* ---------------------------------------------------------------------------
   The overlay stack. One fixed layer per shader effect, cheapest first.
   --------------------------------------------------------------------------- */
/* `display: contents` matters here. mix-blend-mode blends an element with its
   backdrop *within the nearest isolating group*, and any element that forms a
   stacking context is such a group - so a positioned, z-indexed wrapper would
   have quietly confined the grille and the phosphor tint to blending against
   their own transparent parent. Dissolving the wrapper puts every layer in the
   root group, where the page it is meant to tint actually lives. */
.crt {
    display: contents;
}

.crt > * {
    position: fixed;
    inset: 0;
    z-index: 900;
    pointer-events: none;
}

/* Scanlines: one dark band per virtual row.
   `scan = 1.0 - 0.28 * uCrt * (0.5 + 0.5 * cos(...))` */
.crt__scanlines {
    background: repeating-linear-gradient(to bottom,
            rgba(0, 0, 0, var(--scan-opacity)) 0px,
            rgba(0, 0, 0, var(--scan-opacity)) 1px,
            transparent 1px,
            transparent 3px);
    opacity: calc(0.9 * var(--crt));
}

/* Aperture grille: the vertical RGB stripe mask, in output pixels. */
.crt__grille {
    background: repeating-linear-gradient(to right,
            rgba(255, 96, 96, 0.10) 0px,
            rgba(255, 96, 96, 0.10) 1px,
            rgba(96, 255, 96, 0.10) 1px,
            rgba(96, 255, 96, 0.10) 2px,
            rgba(96, 96, 255, 0.10) 2px,
            rgba(96, 96, 255, 0.10) 3px);
    mix-blend-mode: screen;
    opacity: calc(var(--grille-opacity) * var(--crt));
}

/* Static: `col += (hash(...) - 0.5) * 0.025 * uCrt` */
.crt__noise {
    /* Bled past the viewport: the drift animation translates this layer by a
       few percent, and a fixed layer pinned at inset:0 would expose a strip. */
    inset: -6%;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.6'/%3E%3C/svg%3E");
    opacity: calc(var(--noise-opacity) * var(--crt));
    animation: crt-static 0.6s steps(4) infinite;
}

/* The hum bar drifting down the tube, plus the per-frame flicker. */
.crt__hum {
    background: linear-gradient(to bottom,
            transparent 0%,
            rgba(190, 255, 200, 0.045) 42%,
            rgba(190, 255, 200, 0.075) 50%,
            rgba(190, 255, 200, 0.045) 58%,
            transparent 100%);
    height: 46vh;
    inset: auto 0 auto 0;
    mix-blend-mode: screen;
    opacity: calc(1 * var(--crt));
    animation: crt-hum 9s linear infinite;
}

.crt__flicker {
    background: #cfe8d0;
    mix-blend-mode: multiply;
    opacity: 0;
    animation: crt-flicker 4.2s steps(1) infinite;
}

/* Vignette: `1.0 - dot(centreDist, centreDist) * 1.1 * uCrt` */
.crt__vignette {
    background:
        radial-gradient(120% 100% at 50% 50%,
            transparent 42%,
            rgba(0, 0, 0, 0.32) 78%,
            rgba(0, 0, 0, 0.72) 100%);
    opacity: calc(1 * var(--crt));
}

/* Monochrome phosphor modes. The shader tints by luminance; `mix-blend-mode:
   color` is the same operation - hue and saturation from this layer, luminance
   from the page underneath. F10 cycles colour -> P1 green -> P3 amber. */
.crt__phosphor {
    mix-blend-mode: color;
    opacity: 0;
    transition: opacity 260ms ease;
}

body[data-phosphor="green"] .crt__phosphor {
    /* vec3(0.25, 1.0, 0.35) */
    background: #40ff59;
    opacity: 1;
}

body[data-phosphor="amber"] .crt__phosphor {
    /* vec3(1.0, 0.72, 0.18) */
    background: #ffb82e;
    opacity: 1;
}

body[data-phosphor="green"] {
    --link: #b9ffc6;
    --accent: #7dff96;
}

body[data-phosphor="amber"] {
    --link: #ffdca4;
    --accent: #ffcf72;
}

@keyframes crt-hum {
    0% { transform: translateY(-60vh); }
    100% { transform: translateY(120vh); }
}

@keyframes crt-flicker {
    0%, 97% { opacity: 0; }
    97.5% { opacity: 0.055; }
    98% { opacity: 0; }
    98.5% { opacity: 0.03; }
    99% { opacity: 0; }
}

@keyframes crt-static {
    0% { transform: translate3d(0, 0, 0); }
    25% { transform: translate3d(-3%, 2%, 0); }
    50% { transform: translate3d(2%, -3%, 0); }
    75% { transform: translate3d(-2%, -2%, 0); }
    100% { transform: translate3d(3%, 1%, 0); }
}

/* Phosphor glow on text: the 9-tap bloom, squared so only bright things bloom. */
.glow {
    text-shadow:
        0 0 1px rgba(217, 255, 220, 0.45),
        0 0 8px rgba(57, 211, 83, 0.30),
        0 0 22px rgba(57, 211, 83, 0.12);
}

body[data-crt="off"] .glow {
    text-shadow: none;
}

/* ---------------------------------------------------------------------------
   Nobody should get motion sickness from a marketing page.
   --------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {

    .crt__noise,
    .crt__hum,
    .crt__flicker {
        animation: none;
    }

    .crt__hum,
    .crt__flicker {
        opacity: 0;
    }

    * {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}
