ui/napalmoverlay.razor.scss
// NAPALM OVERLAY — the game's own fullscreen fire, played as a sprite sheet.
//
// ⛔ THIS IS THE REAL ASSET AND THE FIRST VERSION OF THIS FILE WAS NOT. I wrote a pair of CSS
// radial gradients and called it fire; the pack ships `nz_moo/overlay/fullscreen_fire` — the actual
// texture, whose own .vmt is a SpriteCard with `animatedTextureFrameRate 30`. Its VTF header says
// 256x128 and SIXTEEN frames, and the shipped PNG is 1024x512: a 4x4 sheet, exactly.
//
// ⚠️ THE MOD ITSELF ONLY EVER DRAWS IT 40x40. Every `zmhud_player_onfire` call in every one of the
// nine HUD variants is a small icon on the player-status card — so the fullscreen use here is OURS,
// built on their texture rather than copied from their code. The texture is plainly authored for it:
// nothing needs a 16-frame 30fps animation to fill forty pixels.
//
// ⚠️ ADDITIVE, AS THE .vmt ASKS (`$Additive 1`). Fire adds light, it does not tint — over a dark map
// the difference is the whole look, and `mix-blend-mode` is what carries it here.

.napalm
{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;

    // ⚠️ ABOVE the ash and the damage vignette, below anything the player reads. Heat is the most
    // urgent of the three ambient overlays because it is a warning rather than a report.
    z-index: 42;

    .layer
    {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        opacity: 0;
    }

    // ── the sheet ────────────────────────────────────────────────────────────
    //
    // ⛔ `background-size: 400% 400%` IS WHAT MAKES ONE FRAME FILL THE SCREEN. The sheet is four
    // frames wide and four tall, so scaling it to four screens in each axis puts exactly one frame
    // in view, and the keyframes below pick which. Getting this wrong shows all sixteen at once,
    // which reads as a broken texture rather than as a wrong number.
    //
    // ⚠️ POSITIONS ARE 0/33.33/66.67/100%, NOT 0/25/50/75. Percentage background-position is a
    // RATIO of the leftover space, not a distance — with a 4-wide sheet the four frames sit at
    // thirds. This is the classic sprite-sheet trap and it looks like a slight drift, not a bug.
    .flame
    {
        background-image: url( "ui/nz/napalm/fullscreen_fire.png" );
        background-size: 400% 400%;
        background-repeat: no-repeat;

        // Fire adds light rather than covering what is behind it.
        mix-blend-mode: lighten;

        // 16 frames at 30fps = 0.5333s, the rate the .vmt states.
        animation: napalm-fire 0.5333s step-end infinite;
    }

    // A slow swell underneath, so the heat reads as building rather than just flickering.
    .wash
    {
        background-image: radial-gradient(
            ellipse at center,
            rgba(255, 90, 20, 0) 42%,
            rgba(255, 80, 12, 0.45) 78%,
            rgba(190, 35, 0, 0.85) 100%
        );

        animation: napalm-breathe 1.9s ease-in-out infinite;
    }
}

// Row 0 then row 1, 2, 3 — reading order, which is how the VTF stacks its frames.
@keyframes napalm-fire
{
    0.00%  { background-position:   0%   0%; }
    6.25%  { background-position: 33.33%   0%; }
    12.50% { background-position: 66.67%   0%; }
    18.75% { background-position: 100%   0%; }

    25.00% { background-position:   0%  33.33%; }
    31.25% { background-position: 33.33%  33.33%; }
    37.50% { background-position: 66.67%  33.33%; }
    43.75% { background-position: 100%  33.33%; }

    50.00% { background-position:   0%  66.67%; }
    56.25% { background-position: 33.33%  66.67%; }
    62.50% { background-position: 66.67%  66.67%; }
    68.75% { background-position: 100%  66.67%; }

    75.00% { background-position:   0% 100%; }
    81.25% { background-position: 33.33% 100%; }
    87.50% { background-position: 66.67% 100%; }
    93.75% { background-position: 100% 100%; }

    100%   { background-position:   0%   0%; }
}

@keyframes napalm-breathe
{
    0%   { transform: scale(1.00); }
    50%  { transform: scale(1.06); }
    100% { transform: scale(1.00); }
}