/* ==========================================================================
   GLOBAL STRUCTURAL THEME EFFECTS  (task 1997 Phase 2)
   ==========================================================================
   The one place the STRUCTURAL layer of the correctness ("game") themes lives.

   Before Phase 2 the geometry/animation of each theme (active-cell border-radius/
   scale/rotate, the neonFlicker / glitchShake keyframes) was authored per-game
   inside each *.razor.css, keyed off the `.wf-theme-*` class. This file lifts
   those STRUCTURAL rules out into ONE global, UNSCOPED stylesheet keyed off the
   stable `data-wf-effect` attribute that each game's board root now emits (driven
   by Coterie.Shared ThemeCatalog's ThemeDefinition.Effects). Colours stay behind
   as `--local-*` blocks in each scoped *.razor.css (generalised in Phase 4).

   WHY THIS IS A NORMAL <link>, NOT A *.razor.css:
   Blazor CSS isolation rewrites every scoped selector to require a generated
   `b-*` attribute that is emitted ONLY on markup rendered by that component. A
   scoped rule therefore cannot match another component's markup. This file must
   match FOUR different games from ONE place, so it must be global and its
   selectors must target STABLE global hooks only: the `data-wf-effect` attribute
   plus each game's board-root class and cell classes — never a `b-*` attribute.

   SPECIFICITY / ORDER — load-bearing:
   Blazor compiles a scoped generic rule like `.board-section .grid-cell.active`
   into `.board-section .grid-cell.active[b-xxxx]` (specificity 0,4,0), and that
   generic rule sets `transform: scale(1.02)`. To win the same contest the shapes
   rule used to (it was `.board-section.wf-theme-shapes .grid-cell.active`), each
   global structural rule KEEPS the board-root class in front of the attribute —
   `.board-section[data-wf-effect~="shape-morph"] .grid-cell.active` (0,4,0) — so
   it ties the generic rule, and this file is linked AFTER Coterie.Client.styles.css
   in index.html so ties resolve here. Do not drop the root class or reorder the
   link, or shapes' scale(0.9) silently loses to scale(1.02).

   @keyframes NOTE: Blazor scopes keyframe NAMES per component (`neonFlicker` ->
   `neonFlicker-b-xxxx`), so the per-game copies never collided. In this ONE
   global file they would, so they are given distinct names here. `wfGlitchShake`
   is shared because every game's glitchShake body was byte-identical; the two
   neonFlicker variants differ (text-shadow vs box-shadow) and are kept separate.
   ========================================================================== */

/* --- Shared animations ------------------------------------------------------ */

/* Identical translate-jitter across Grid / Span / Lattice (was glitchShake /
   glitchShakeWs in each scoped file). */
@keyframes wfGlitchShake {
    0% {
        transform: translate(0)
    }

    20% {
        transform: translate(-2px, 2px)
    }

    40% {
        transform: translate(-2px, -2px)
    }

    60% {
        transform: translate(2px, 2px)
    }

    80% {
        transform: translate(2px, -2px)
    }

    100% {
        transform: translate(0)
    }
}

/* Text-glow flicker used by Grid (.grid-cell.active) and Lattice (.ws-cell.found)
   — identical body in both scoped files (was neonFlicker / neonFlickerWs). */
@keyframes wfNeonFlickerText {

    0%,
    19%,
    21%,
    23%,
    25%,
    54%,
    56%,
    100% {
        opacity: 1;
        text-shadow: 0 0 8px #22c55e;
    }

    20%,
    24%,
    55% {
        opacity: 0.5;
        text-shadow: none;
    }
}

/* Box-glow flicker used by Span (.island.active) — box-shadow rather than
   text-shadow because an island is a filled shape, not a glyph (was neonFlicker
   in SpanGame.razor.css). */
@keyframes wfNeonFlickerGlow {

    0%,
    19%,
    21%,
    23%,
    25%,
    54%,
    56%,
    100% {
        opacity: 1;
        box-shadow: 0 0 8px #22c55e, inset 0 0 8px #22c55e;
    }

    20%,
    24%,
    55% {
        opacity: 0.5;
        box-shadow: none;
    }
}

/* Text-glow flicker used by LEX (.wf-cell.correct under kinetic-glitch). SAME timing/opacity body as
   wfNeonFlickerText, but its glow colour is `var(--local-correct-glow)` (= #4ade80 for Lex's Cyber
   Fill) rather than the shared #22c55e Grid/Lattice bake in — Lex's scoped neonFlicker genuinely used
   the colour token, so reusing wfNeonFlickerText would have SHIFTED the glow #4ade80 -> #22c55e and
   broken exact parity. The custom property resolves per-element against the animated cell's inherited
   value (set by the scoped `.wf-board-container.wf-theme-kinetic` `--local-correct-glow` block), so a
   global keyframe reaches it correctly. Kept var-based (not baked) so it stays reusable by any game
   that defines --local-correct-glow. */
@keyframes wfNeonFlickerTextVar {

    0%,
    19%,
    21%,
    23%,
    25%,
    54%,
    56%,
    100% {
        opacity: 1;
        text-shadow: 0 0 8px var(--local-correct-glow);
    }

    20%,
    24%,
    55% {
        opacity: 0.5;
        text-shadow: none;
    }
}

/* ==========================================================================
   GRID — root `.board-section`, cells `.grid-cell`
   ========================================================================== */

/* shape-morph */
.board-section[data-wf-effect~="shape-morph"] .grid-cell.active {
    border-radius: 50%;
    transform: scale(0.9);
}

/* kinetic-glitch */
.board-section[data-wf-effect~="kinetic-glitch"] .grid-cell.active {
    animation: wfNeonFlickerText 3s infinite alternate;
}

.board-section[data-wf-effect~="kinetic-glitch"] .grid-cell.error {
    animation: wfGlitchShake 0.4s infinite;
}

/* ==========================================================================
   SPAN — root `.board-section`, islands `.island`
   ========================================================================== */

/* shape-morph */
.board-section[data-wf-effect~="shape-morph"] .island.active {
    border-radius: 4px;
    transform: scale(1.1) rotate(45deg);
}

.board-section[data-wf-effect~="shape-morph"] .island.satisfied {
    border-radius: 50%;
}

.board-section[data-wf-effect~="shape-morph"] .island.error {
    border-radius: 4px;
}

/* kinetic-glitch */
.board-section[data-wf-effect~="kinetic-glitch"] .island.active {
    animation: wfNeonFlickerGlow 3s infinite alternate;
}

.board-section[data-wf-effect~="kinetic-glitch"] .island.error {
    animation: wfGlitchShake 0.4s infinite;
}

/* ==========================================================================
   LATTICE — root `.ws-board-container`, cells `.ws-cell`
   (these were `::deep` rules in the scoped file; global here, so no ::deep)
   ========================================================================== */

/* shape-morph */
.ws-board-container[data-wf-effect~="shape-morph"] .ws-cell {
    border-radius: 4px;
}

.ws-board-container[data-wf-effect~="shape-morph"] .ws-cell.active {
    border-radius: 50%;
    transform: scale(0.9);
}

.ws-board-container[data-wf-effect~="shape-morph"] .ws-cell.found {
    border-width: 2px !important;
    /* border-radius MUST be re-stated on the `.ws-cell.found` compound (specificity 0,4,0), not
       left to the `.ws-cell` rule above (0,3,0): the scoped LatticeGame.razor.css
       `.ws-cell.found { border-radius: 6px }` is 0,4,0 and would otherwise WIN, regressing found
       (completed-word) cells from their pre-Phase-2 4px to 6px. At an equal 0,4,0 this ties that
       rule and wins by themes.css loading after the scoped bundle — matching how the old scoped
       shapes `.ws-cell` rule (also 0,4,0) beat `.ws-cell.found` by source order. */
    border-radius: 4px;
}

/* kinetic-glitch */
.ws-board-container[data-wf-effect~="kinetic-glitch"] .ws-cell.found {
    animation: wfNeonFlickerText 3s infinite alternate;
}

.ws-board-container[data-wf-effect~="kinetic-glitch"] .ws-cell.active {
    animation: wfGlitchShake 0.4s infinite;
}

/* ==========================================================================
   LEX — root `.wf-board-container`, cells `.wf-cell`
   ==========================================================================
   Lex's deferred structural extraction (task 1997, 2026-08-17 — the one piece Phase 2 left behind;
   Phase 4 colour-generalisation was CANCELLED, so this completes the refactor). Lex FUSES colour +
   geometry inside single `!important` scoped selectors, so only the geometry/motion half is lifted
   here; the colour half (background / border-colour / colour / --local-* box-shadow / font-weight /
   font-style / opacity) stays in LexGame.razor.css.

   TWO `.wf-board-container` EXIST: the real board (`.wf-board-container.relative`, LexGame.razor:61)
   and the toolbar KEY legend (line 34, mini-cells). BOTH carry the theme class AND `data-wf-effect`
   — the legend was given the same attribute so these lifted rules reach its mini-cells exactly as the
   old `.wf-theme-*` class rules did, keeping the legend pixel-identical. The `.wf-cell.mini-cell`
   overrides (border-radius) stay class-scoped in LexGame.razor.css and win by higher specificity, so
   the legend's own shapes are unchanged.

   SPECIFICITY: each moved rule is `.wf-board-container[data-wf-effect~="..."] .wf-cell.<state>` (0,4,0)
   — identical to the old scoped `.wf-board-container.wf-theme-<x> .wf-cell.<state>` (0,4,0) — and wins
   its ties because themes.css links after Coterie.Client.styles.css. The `!important` on border-radius
   is preserved because the scoped colour half of the same cell can carry a `border` shorthand and the
   base `.wf-cell { border-radius: var(--border-radius-sm) }` would otherwise interfere. */

/* shape-morph */
.wf-board-container[data-wf-effect~="shape-morph"] .wf-cell {
    /* Bouncy state-change transition (was `.wf-theme-shapes .wf-cell`); overrides base `all 0.4s`. */
    transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

.wf-board-container[data-wf-effect~="shape-morph"] .wf-cell.correct {
    border-radius: 50% !important;
    /* Circle */
    transform: scale(1.1);
}

.wf-board-container[data-wf-effect~="shape-morph"] .wf-cell.present {
    border-radius: 4px !important;
    transform: rotate(45deg) scale(0.85);
    /* Diamond */
}

.wf-board-container[data-wf-effect~="shape-morph"] .wf-cell.absent {
    transform: scale(0.8);
}

/* kinetic-glitch */
.wf-board-container[data-wf-effect~="kinetic-glitch"] .wf-cell.correct {
    transform: scale(1.2);
    animation: wfNeonFlickerTextVar 3s infinite alternate;
}

.wf-board-container[data-wf-effect~="kinetic-glitch"] .wf-cell.present {
    animation: wfGlitchShake 0.4s infinite;
}

.wf-board-container[data-wf-effect~="kinetic-glitch"] .wf-cell.absent {
    filter: blur(0.6px);
}

/* ==========================================================================
   neon-glow / spectral-glow: no structural rule — their glow is driven entirely
   by the per-game `--local-*` colour tokens in each scoped *.razor.css. The
   effect ids are assigned in ThemeCatalog so the registry is uniform. (Phase 4,
   which would have generalised those colours, was cancelled 2026-08-17; the
   correctness-theme colour layer stays scoped per game.)
   ========================================================================== */
