:root {
    /* Rust taken from the Dead Zone logotype */
    --rust: #a8301a;
    --rust-bright: #cd4522;
    --rust-deep: #5f1b09;

    --steel-900: #0a0908;
    --steel-800: #121010;
    --steel-700: #1b1817;

    --text: #ded6ce;
    --muted: #8c8178;
    --line: rgba(255, 255, 255, 0.07);

    --font-display: "Bahnschrift", "Segoe UI Semibold", "Segoe UI", sans-serif;
    --font-mono: "Consolas", "Cascadia Mono", ui-monospace, monospace;

    --input-height: 42px;
    --transition: 200ms cubic-bezier(.2, .9, .3, 1);

    /* Film stock. Dial these down to calm the effect, up to make it grubbier. */
    --film-grain: 0.16;
    --film-scanline: rgba(0, 0, 0, 0.28);
    --film-scratch: rgba(255, 255, 255, 0.5);
    --film-gate: rgba(0, 0, 0, 0.5);

    font-synthesis: none;
}

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html,
body {
    height: 100vh;
    font-family: var(--font-display);
    color: var(--text);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

html {
    background: var(--steel-900);
}

/* Must stay transparent: the art and vignette below are negative-z pseudo
   elements, and an opaque body background would paint straight over them. */
body {
    background: transparent;
}

/* Promo art plate. Kept behind everything so the artwork never fights the form. */
body::before {
    content: "";
    position: fixed;
    inset: -2%;
    /* Anchored to the bottom so the artwork's own logotype crops out of frame -
       the panel carries the wordmark, and a half-cut logo reads as a mistake. */
    background: url("../img/login-bg.webp") center bottom / cover no-repeat;
    /* Slow drift keeps the still frame feeling alive without distracting */
    animation: driftIn 44s ease-in-out infinite alternate;
    z-index: -2;
}

/* Vignette plus a darker right flank, where the form sits */
body::after {
    content: "";
    position: fixed;
    inset: 0;
    background:
        linear-gradient(0deg, rgba(6, 5, 5, 0.97) 0%, rgba(6, 5, 5, 0.5) 12%, transparent 26%),
        linear-gradient(90deg, rgba(10, 9, 8, 0.15) 0%, rgba(10, 9, 8, 0.55) 45%, rgba(10, 9, 8, 0.93) 100%),
        radial-gradient(120% 90% at 30% 45%, transparent 30%, rgba(6, 5, 5, 0.85) 100%);
    z-index: -1;
}

@keyframes driftIn {
    from { transform: scale(1.06) translate3d(0, 0, 0); }
    to   { transform: scale(1.13) translate3d(-1.4%, 0.6%, 0); }
}

/* Film grain + scanlines, purely decorative.
   Everything in this block is fixed, pointer-events:none and animates only
   opacity, transform and background-position, so nothing here can catch a click
   or force a relayout while the form is being typed into. */
.grain {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 50;
    opacity: var(--film-grain);
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
    /* The tile repeats seamlessly, so shifting the background re-scatters the
       noise with no visible seam. steps() makes it jump between frames the way
       grain does instead of sliding, which would read as a moving texture. */
    animation: grainJitter 0.5s steps(1, end) infinite;
}

/* Emulsion scratches: a couple of bright hairlines that hold for a few frames
   and then jump elsewhere. Kept sparse - constant scratches read as a filter,
   occasional ones read as film. */
.grain::after {
    content: "";
    position: absolute;
    inset: 0;
    background:
        linear-gradient(90deg, transparent 21.7%, var(--film-scratch) 21.7%, var(--film-scratch) 21.85%, transparent 21.85%),
        linear-gradient(90deg, transparent 63.4%, var(--film-scratch) 63.4%, var(--film-scratch) 63.5%, transparent 63.5%);
    opacity: 0;
    animation: scratchPass 7.3s steps(1, end) infinite;
}

.scanlines {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 49;
    opacity: 0.35;
    background: repeating-linear-gradient(
        180deg,
        transparent 0 2px,
        var(--film-scanline) 2px 3px
    );
}

/* Gate flicker: the projector lamp never holds a perfectly even level. Two
   overlapping cycles with lengths that do not divide into each other, so the
   pattern does not visibly loop. */
.scanlines::after {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--film-gate);
    opacity: 0;
    animation:
        gateFlicker 2.7s ease-in-out infinite,
        gateDip 11s ease-in-out infinite;
}

@keyframes grainJitter {
    0%   { background-position: 0 0; }
    20%  { background-position: -37px 19px; }
    40%  { background-position: 22px -43px; }
    60%  { background-position: -13px -27px; }
    80%  { background-position: 41px 31px; }
    100% { background-position: -29px 11px; }
}

@keyframes scratchPass {
    0%, 88%   { opacity: 0; transform: translate3d(0, 0, 0); }
    89%, 91%  { opacity: 0.5; transform: translate3d(-6vw, 0, 0); }
    92%, 94%  { opacity: 0.35; transform: translate3d(11vw, 0, 0); }
    95%, 100% { opacity: 0; transform: translate3d(0, 0, 0); }
}

@keyframes gateFlicker {
    0%, 100% { opacity: 0; }
    45%      { opacity: 0.05; }
    62%      { opacity: 0.01; }
    78%      { opacity: 0.06; }
}

/* The occasional deeper dip, as if a frame were underexposed */
@keyframes gateDip {
    0%, 92%, 100% { opacity: 0; }
    95%           { opacity: 0.13; }
}

@media (prefers-reduced-motion: reduce) {
    body::before,
    .grain,
    .grain::after,
    .scanlines::after {
        animation: none;
    }
}
