/* ============================================================
   effects.css: the atmosphere. Plate tone, paper grain, the plate
   mark, particle canvas, impact flash, screen shake, struck type.
   ============================================================ */

/*
 * The plate the whole interface is printed on. Flat stock: the ruling lives on
 * the single layer below, not here.
 */
#ambience {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-color: var(--ink-900);
}

/*
 * One layer, one design, covering the whole viewport exactly once.
 *
 * It used to be three positioned patches at different angles, which overlapped
 * each other in the corners and crossed into plaid where they met. A single
 * inset-0 layer cannot overlap itself, so that whole class of problem is gone.
 *
 * The mask does the work the patches were trying to do: fully out in the
 * middle, ramping up toward the edges, so the ruling frames the screen and
 * leaves the table and panels sitting on clean stock. Ellipse rather than
 * circle so the falloff tracks the viewport's aspect instead of running out
 * early on the short axis.
 */
.amb-glow--key {
    position: absolute;
    inset: 0;
    background-image: repeating-linear-gradient(90deg,
            rgba(201, 169, 97, .17) 0 1px,
            transparent 1px 9px);
    -webkit-mask-image: radial-gradient(ellipse 75% 75% at 50% 50%,
            transparent 30%, rgba(0, 0, 0, .5) 68%, #000 100%);
    mask-image: radial-gradient(ellipse 75% 75% at 50% 50%,
            transparent 30%, rgba(0, 0, 0, .5) 68%, #000 100%);
    animation: plateBreathe 11s var(--ease-in-out) infinite;
    will-change: opacity;
}

/* the other two carried the competing designs; the plate only wants the one */
.amb-glow--red,
.amb-glow--cyan {
    display: none;
}

/* the plate is still, but not dead still */
@keyframes plateBreathe {

    0%,
    100% {
        opacity: .85;
    }

    50% {
        opacity: 1;
    }
}

/* Film grain: a tiny inline SVG noise tile, cheap and tileable */
/* Paper tooth. Carries more of the look now that the lighting is gone, so it
   sits a little heavier than it did. */
#grain {
    position: absolute;
    inset: -50%;
    width: 200%;
    height: 200%;
    opacity: .07;
    mix-blend-mode: overlay;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
    animation: grainDrift .6s steps(3) infinite;
}

@keyframes grainDrift {
    0% {
        transform: translate(0, 0);
    }

    33% {
        transform: translate(-2%, 1.5%);
    }

    66% {
        transform: translate(1.5%, -2%);
    }

    100% {
        transform: translate(0, 0);
    }
}

/*
 * Was a soft radial vignette. It is now the plate mark: the impression the
 * edge of an engraving plate leaves on the sheet. A hard rule inset from the
 * viewport edge, doubled, with the corners left open the way a real plate
 * mark wears away first at the corners.
 */
#vignette {
    position: absolute;
    inset: 0;
    pointer-events: none;
}

/*
 * Flush to the viewport edge. Inset from it, the dead band outside the rule
 * read as a black border framing the interface rather than as the edge of a
 * plate, which is the opposite of the intent.
 */
#vignette::before {
    content: '';
    position: absolute;
    inset: 0;
    border: 1px solid var(--rule);
}

/* the faint inner rule of the doubled plate mark. Safe to have back now that
   the outer one is flush to the edge: the gap sits between the two rules
   rather than outside them, so it reads as a double rule and not as a border
   around the whole interface. */
#vignette::after {
    content: '';
    position: absolute;
    inset: 4px;
    border: 1px solid var(--rule-faint);
}

/* ---------------- Particle canvas ---------------- */
/*
   Above .modal (1000), below the server pill (1100) and the toasts (1200).
   Everything this canvas draws is thrown by something happening inside a
   dialog: the muzzle flash comes off the revolver on the roulette stage, the
   confetti off the game-over box. At 900 all of it painted under the dialog and
   its near-opaque backdrop, so the shot had no particles at all and the smoke
   only became visible when Continue took the modal away, seconds late and
   already fading. This canvas is the top of the effects stack, not the bottom.
*/

#fx-canvas {
    position: fixed;
    inset: 0;
    z-index: 1010;
    width: 100%;
    height: 100%;
    pointer-events: none;
}

/* ---------------- Impact flash ---------------- */

#fx-flash {
    position: fixed;
    inset: 0;
    z-index: 1030;
    opacity: 0;
    pointer-events: none;
    background: #fff;
    mix-blend-mode: screen;
}

/* ---------------- Blood vignette ---------------- */
/*
   The seconds after a lethal shot. Sits above the particle canvas but below the
   impact flash, and is clear through the middle so the table stays readable
   while the edges close in. Driven by fx.bloodVignette().

   Above the modal for the same reason the canvas is: it plays out while the
   roulette dialog is still up, and a vignette underneath a backdrop that is
   already 90% black is a vignette nobody sees.
*/

#fx-blood {
    position: fixed;
    inset: 0;
    z-index: 1020;
    opacity: 0;
    pointer-events: none;
    background:
        radial-gradient(ellipse 78% 68% at 50% 50%,
            transparent 34%,
            rgba(88, 6, 18, .5) 72%,
            rgba(52, 2, 10, .92) 100%);
}

/* ---------------- Screen shake ---------------- */
/*
   The offsets come from fx.shake(), one frame at a time, because a keyframe
   plays the same wobble every shot. All this does is apply them: the class is
   the on switch and the vars are the position.
*/

html.is-shaking #app,
html.is-shaking #ambience {
    transform:
        translate3d(var(--shake-x, 0px), var(--shake-y, 0px), 0)
        rotate(var(--shake-r, 0deg));
    will-change: transform;
}

/* ---------------- Struck lettering ----------------
   These replace the old .neon classes. Same job (the title has to carry the
   screen) done by cutting the letterforms rather than lighting them: a hard
   offset in black behind the glyph reads as a second impression of the plate,
   which is the letterpress tell. Nothing glows. */

.struck {
    color: var(--gold-hot);
    text-shadow:
        2px 2px 0 #000,
        3px 3px 0 rgba(0, 0, 0, .5);
}

.struck--red {
    color: var(--oxblood-hot);
    text-shadow:
        2px 2px 0 #000,
        3px 3px 0 rgba(0, 0, 0, .5);
}

/* Shared entrance helpers */

@keyframes riseIn {
    from {
        opacity: 0;
        transform: translateY(14px);
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

@keyframes fadeIn {
    from {
        opacity: 0;
    }
}

@keyframes popIn {
    from {
        opacity: 0;
        transform: scale(.86);
    }

    to {
        opacity: 1;
        transform: scale(1);
    }
}
