/* Base styles for Moonfall game */

/* Safe-area tokens — ONE global source of truth.
   index.html ships `viewport-fit=cover`, so on a notched phone the page runs
   under the Dynamic Island / notch and the home indicator. Anything the player
   must see or tap has to keep out of those bands. These are declared globally
   (not inside the phone media query) so any stylesheet can consume them
   without first proving it is in the mobile layer; on a device with no notch
   every one of them is 0px and nothing moves. */
:root {
    --mf-safe-top: env(safe-area-inset-top, 0px);
    --mf-safe-right: env(safe-area-inset-right, 0px);
    --mf-safe-bottom: env(safe-area-inset-bottom, 0px);
    --mf-safe-left: env(safe-area-inset-left, 0px);

    /* THE HONEST vmin. Use this, never bare `vmin`, for anything sized against
       the page box a phone player is actually looking at.

       `vmin` is `min(vw, vh)`, and `vh` is the LARGE viewport — the page with
       the browser's bars HIDDEN. This repo already knows that about `vh` and
       bans it for full-screen surfaces; `vmin` inherits the identical defect
       and CSS ships no `dvmin`, so it has to be written out.

       In landscape the lie is at its worst, because `vh` is the term that
       wins: an iPhone 13 hands the page 844x280 while `vh` still resolves
       against 390, so every `vmin`-derived size is computed against a viewport
       110px taller than the one on screen. Measured on the owner's device
       against this repo's own CSS: `clamp(100px, 33vmin, 128px)` gave the
       summoner crest its 128px CAP instead of its 100px floor, the crest stood
       107px tall instead of 86px, and the seam between the two crests fell to
       32px — 12px short of the 44px End Turn that has to sit in it, so the
       button was drawn 14px INTO the opponent's crest. Reported three times.

       Every capture missed it because Playwright has no large viewport: there
       `vh == dvh == the page box`, so the harness's `vmin` was already honest
       and the defect could not exist in it. That is also why this token is a
       provable no-op everywhere the suite runs, and on every desktop and
       tablet browser — `min(1vw, 1dvh)` differs from `vmin` only where a
       dynamic browser toolbar makes `vh` untrue.

       Declared globally, like the safe-area tokens above and for the same
       reason: a `--phone-*` token read from a block a landscape TABLET can
       match resolves to nothing, and an invalid `var()` does not fall back —
       it voids the whole declaration. That failure has shipped twice. */
    --mf-dvmin: min(1vw, 1dvh);

    /* The top-right chrome rail — ONE global source of truth.
       Reading outward from the page: the Language pill, then the round glass
       triggers (audio, then full screen) marching to the viewport edge.

       This geometry used to be re-typed at six call sites, each spelling out
       `calc(edge + button + 0.75rem)` by hand to clear the trigger fixed above
       it, and the tablet band had already drifted to a hardcoded 44px. Adding
       one control to the rail therefore meant finding and editing all six.
       Declared here, a control costs a token and a size change costs one
       override. `--mf-rail-gap` is the tighter spacing BETWEEN round triggers,
       which read as one cluster; the Language pill keeps its wider 0.75rem. */
    --mf-rail-edge: clamp(0.9rem, 1.3vmin, 1.8rem);
    --mf-rail-btn: clamp(34px, 3.1vmin, 60px);
    --mf-rail-gap: 0.5rem;

    /* SQUARE CORNER: the top inset IS the edge inset.

       This used to be `max(0.25rem, calc(0.75rem + 15px - btn / 2))` — the
       Language pill's midline, so the round triggers would sit on the same row
       as the pill beside them. There is no pill any more; the corner holds one
       button. That derivation left the desktop button 10px from the top and
       14.4px from the right at 1400x640, which is exactly the "not evenly
       padded to top and right" the owner reported: a corner control reads as
       misaligned unless its two gaps match.

       Narrow layouts still drop the whole row by overriding THIS one
       declaration — writing the drop as `top` on a single control is what once
       left the full-screen trigger behind on row one, printing it over the
       audio trigger at 1024x768. */
    --mf-rail-top: var(--mf-rail-edge);

    /* MATCH COUNTDOWN SURFACE — one design, two phases.

       Moonfall counts down twice: the turn timer above End Turn, and the
       mulligan countdown inside the mulligan overlay. They were written months
       apart and shared nothing — the turn timer a bordered pill with tabular
       numerals and a depleting bar, the mulligan a bare 70%-white monospace
       number that read as a debug readout beside it. Same signal, two designs,
       and the warning tier was `#ff8844` on one and `#ffe4b5` on the other.

       Declared here so a third countdown costs a token and not a palette.
       Sizes and seats stay per-timer: one rides the command rail for a whole
       match, the other lives inside a full-screen overlay. */
    --mf-timer-bg: rgba(16, 12, 26, 0.72);
    --mf-timer-border: rgba(190, 170, 220, 0.35);
    --mf-timer-ink: #e8dcf5;
    --mf-timer-radius: 1vmin;
    --mf-timer-track: rgba(255, 255, 255, 0.14);
    --mf-timer-fill: linear-gradient(90deg, #8f6fd0, #c8a7ff);
    --mf-timer-urgent-ink: #ffe4b5;
    --mf-timer-urgent-border: rgba(255, 200, 120, 0.6);
    --mf-timer-urgent-fill: linear-gradient(90deg, #d09a4a, #ffd08a);
    --mf-timer-critical-ink: #ffd2d2;
    --mf-timer-critical-border: rgba(255, 130, 130, 0.75);
    --mf-timer-critical-fill: linear-gradient(90deg, #c04a4a, #ff9a9a);


    /* Width the full-screen trigger claims at the outer edge — zero until it
       actually paints. fullscreen-toggle.js marks the body capable ONLY where
       the Fullscreen API exists, which is never on an iPhone (iOS Safari has
       no element-level fullscreen at all), so those players keep the rail at
       its old width instead of a gap reserved for a button that never comes. */
    --mf-rail-fullscreen-slot: 0px;

    /* The vertical equivalent, for the phone battle command column: the height
       one 36px rail trigger claims there, plus its gap. Zero until the trigger
       paints, so a phone that cannot go full screen keeps its old column. */
    --mf-battle-stack: 0px;

    /* THE STARDUST GLYPH — one drawing, every counter.

       Stardust used to be spelled with the text character ☆, which is
       whatever the platform's fallback font has for U+2606: a thin outline on
       Linux, a heavier one on iOS, a coloured emoji-adjacent shape on some
       Android faces, and never anything that looks like the gold sparkles in
       the store's own artwork. The owner asked for "its own asset ... small,
       but more charismatic". This is a four-point sparkle with one satellite
       spark, drawn to match the dust in the Bag/Chest/Vault art, with the gold
       gradient inside the SVG so no per-site colouring is needed.

       Inline as a data URI, not a file: the lobby chip paints on the first
       frame of the lobby, and a glyph fetched from the network would arrive
       a beat after the number — the exact late swap the asset doctrine
       forbids. 660 bytes, cached with the stylesheet.

       Read by `#store-stardust-balance::before`, `.user-stardust::before` and
       `.stardust-glyph`. Sized in em by each site so it follows its number. */
    --mf-stardust-glyph: url("data:image/svg+xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 24 24%27%3E%3Cdefs%3E%3ClinearGradient id=%27g%27 x1=%270%27 y1=%270%27 x2=%271%27 y2=%271%27%3E%3Cstop offset=%270%27 stop-color=%27%23fff6c8%27/%3E%3Cstop offset=%27.5%27 stop-color=%27%23f6d96a%27/%3E%3Cstop offset=%271%27 stop-color=%27%23d9a53a%27/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath fill=%27url(%23g)%27 d=%27M10.6 3.4C10.6 10.8 13.2 13.4 20.6 13.4C13.2 13.4 10.6 16 10.6 23.4C10.6 16 8 13.4 0.6 13.4C8 13.4 10.6 10.8 10.6 3.4ZM19.6 2C19.6 4.52 20.68 5.6 23.2 5.6C20.68 5.6 19.6 6.68 19.6 9.2C19.6 6.68 18.52 5.6 16 5.6C18.52 5.6 19.6 4.52 19.6 2Z%27/%3E%3C/svg%3E");
}

/* An inline Stardust mark beside a number: `<span class="stardust-glyph"
   aria-hidden="true"></span>`. The number's font size sets its size. */
.stardust-glyph {
    display: inline-block;
    flex: none;
    width: 1em;
    height: 1em;
    vertical-align: -0.12em;
    background: var(--mf-stardust-glyph) center / contain no-repeat;
}

/* SPOKEN, NOT SEEN. A glyph says "Stardust" to the eye and nothing to a
   screen reader, and an `aria-label` on a plain <div> is name-prohibited
   (ARIA `generic`) — the accessibility tree drops it, measured with an aria
   snapshot in Chromium and WebKit. So wherever a glyph replaced a word, the
   word stays in the DOM in this box: 1px, clipped, off the layout, read
   aloud. The standard pattern; `clip-path` for the engines that dropped
   `clip`, `clip` for the ones that have not picked up `clip-path` yet. */
.mf-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    border: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
}

/* BOTH SLOTS ARE PERMANENTLY ZERO NOW, and the `body.fullscreen-capable`
   override that used to fill them is gone.

   They existed to reserve room for a SECOND control at the corner: the
   full-screen trigger sat outboard of mute, so every other element in the rail
   had to spell out `calc(edge + btn + slot)` to clear it, and the phone battle
   column had to leave `--mf-battle-stack` of vertical room for it. The corner
   holds exactly one button now — the chrome menu — and full screen is a row
   inside it, so there is nothing outboard to reserve for and the reservations
   collapse to what one control actually occupies.

   The tokens themselves are kept rather than deleted: roughly twenty call
   sites across six sheets read them, they all reduce correctly at 0px, and
   removing them would be a large mechanical edit whose only benefit is
   shortening some `calc()` expressions. `body.fullscreen-capable` is still
   applied by fullscreen-toggle.js and is still the honest signal that the
   Fullscreen API exists — it simply no longer moves any geometry. */

/* `font-family` deliberately does NOT live here.
   Setting it on `*` sets it on EVERY element, which defeats inheritance: an
   element created at runtime inside a themed surface was force-set to Arial
   instead of inheriting the surface's face, and no amount of styling the
   ancestor fixed it. Declared once on `body` below so it inherits normally;
   form controls, which do not inherit font by default, opt in explicitly. */
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;

    /* Prevent text selection during gameplay */
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    -webkit-user-select: none;
    user-select: none;
}

/* `pointer-events` deliberately does NOT ride the universal selector either.
   It is an INHERITED property, but `* { pointer-events: auto }` re-declares it
   on every element, and a declaration beats an inherited value — so
   `pointer-events: none` on an overlay ROOT protected nothing and its children
   kept eating hover and clicks. Five rules across fx.css, tutorial.css and
   cards.css had to fight back with `pointer-events: none !important` on
   `<layer> *`, and fx.css documents the trap in full. Removing the universal
   declaration restores the normal semantics those layers were written to
   expect: a `none` container now actually disables its subtree, and anything
   that genuinely needs to stay clickable inside one says so itself. */

/* Form controls do not inherit `font-family` from their ancestors; the old
   universal rule was the only thing giving them one. Opt them in so removing
   it changes nothing about how a button or input reads. */
input,
button,
select,
textarea,
optgroup {
    font-family: inherit;
}

body {
    font-family: Arial, sans-serif;
    color: #fff;
    /* svh, not vh: on a phone `100vh` is the LARGE viewport (browser bars
       hidden), so `min-height:100vh` invents a scrollbar's worth of page that
       does not exist while the bars are showing. */
    min-height: 100svh;
    display: flex;
    justify-content: center;
    align-items: center;
    overflow: visible;
    /* Changed from hidden to visible to prevent content cropping */
    position: relative;
    background-color: #000;
    /* Fallback color */
    font-size: clamp(13px, 1.85vmin, 30px);
}

/* Ultrawide background implementation */
body::before {
    content: '';
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: -1;
    background-image: url('../assets/images/background.webp');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}

#app {
    width: 100%;
    /* The app shell must be exactly as tall as the page box the browser
       actually gave us. `100vh` on iOS/Android resolves to the viewport with
       the URL bar HIDDEN, so with the bars showing #app was taller than the
       screen and — with overflow:hidden — the bottom of the lobby was cut off
       and unreachable. dvh tracks the real visible height. */
    height: 100vh;
    height: 100dvh;
    max-width: 100%;
    margin: 0 auto;
    padding: 0;
    position: relative;
    overflow: hidden;
}

/* Initial Loading Screen - shown while determining auth state */
#app-loading {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: linear-gradient(135deg, #0a0a1a 0%, #1a1a3a 50%, #0a0a1a 100%);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    z-index: 10000;
    transition: opacity 0.3s ease-out;
}

#app-loading.fade-out {
    opacity: 0;
    pointer-events: none;
}

#app-loading .loading-logo {
    max-width: 200px;
    margin-bottom: 30px;
    animation: loading-pulse 2s ease-in-out infinite;
}

#app-loading .loading-spinner {
    width: 40px;
    height: 40px;
    border: 3px solid rgba(138, 100, 200, 0.2);
    border-top-color: #8a64c8;
    border-radius: 50%;
    animation: loading-spin 1s linear infinite;
}

@keyframes loading-pulse {

    0%,
    100% {
        opacity: 0.8;
        transform: scale(1);
    }

    50% {
        opacity: 1;
        transform: scale(1.02);
    }
}

@keyframes loading-spin {
    to {
        transform: rotate(360deg);
    }
}

h1 {
    text-align: center;
    color: #eedbf1;
    margin-bottom: 3.5vmin;
    font-size: clamp(24px, 5.55vmin, 88px);
    text-shadow: 0 0 1.16vmin rgba(238, 219, 241, 0.5);
}

.logo-container {
    text-align: center;
    margin-bottom: 2.78vmin;
}

#game-logo {
    /* Trimmed from 46.3vmin: still the dominant hero, but the smaller footprint
       gives the panel column headroom to fit above the footer on short/verbose
       viewports without scrolling. */
    max-width: 42vmin;
    width: 100%;
    height: auto;
    border-radius: 0.93vmin;
}

h2 {
    color: #eedbf1;
    margin-bottom: 1.74vmin;
    font-size: clamp(18px, 3.5vmin, 56px);
}

.panel {
    background-color: #16213e;
    border-radius: clamp(6px, 0.93vmin, 14px);
    padding: clamp(14px, 2.3vmin, 36px);
    margin-bottom: clamp(14px, 2.3vmin, 36px);
    box-shadow: 0 0.46vmin 0.93vmin rgba(0, 0, 0, 0.2);
}

button {
    background-color: rgba(74, 63, 53, 0.7);
    color: #eedbf1;
    border: 1px solid rgba(238, 219, 241, 0.5);
    padding: clamp(8px, 1.16vmin, 18px) clamp(12px, 1.74vmin, 28px);
    margin: clamp(4px, 0.58vmin, 10px);
    border-radius: clamp(4px, 0.46vmin, 8px);
    /* NO `!important` HERE. This declaration used to be repeated as
       `cursor: pointer !important` — a duplicate of the line above, added to
       "prevent button dragging", which `cursor` cannot do. What it did do was
       outrank every non-important rule in the codebase, so `button:disabled`
       twenty lines below and `.game-command-btn:disabled` in
       game-controls.css both declared `cursor: not-allowed` and NEITHER ever
       applied: every disabled button in the game showed a clickable cursor.
       Caught by the runtime cursor assertion in
       tests/e2e-playwright/real/gameplay-extra.real.spec.js, which reported
       `pointer` on a visibly greyed-out End Turn. Locked by
       tests/unit/styles/actionIntentHeldBoard.test.js. */
    cursor: pointer;
    transition: all 0.3s ease;
    -webkit-backdrop-filter: blur(0.35vmin);
    backdrop-filter: blur(0.35vmin);
    font-size: clamp(13px, 1.85vmin, 28px);
}

button:hover:not([disabled]) {
    background-color: rgba(238, 219, 241, 0.3);
    color: #fff;
    box-shadow: 0 0 1.16vmin rgba(238, 219, 241, 0.5);
}

button:disabled {
    background-color: rgba(51, 51, 51, 0.5);
    cursor: not-allowed;
    opacity: 0.5;
    border-color: rgba(238, 219, 241, 0.2);
}

input[type="text"] {
    width: 100%;
    padding: clamp(8px, 1.16vmin, 18px);
    margin-bottom: clamp(8px, 1.16vmin, 18px);
    border-radius: clamp(4px, 0.46vmin, 8px);
    border: 1px solid rgba(238, 219, 241, 0.5);
    background-color: rgba(116, 89, 128, 0.5);
    color: #fff;
    -webkit-backdrop-filter: blur(0.35vmin);
    backdrop-filter: blur(0.35vmin);
    font-size: clamp(13px, 1.85vmin, 28px);
}

/* Animation keyframes */
@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(10px);
    }

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

/* Action in progress indicator */
.action-in-progress {
    cursor: wait !important;
}

.action-in-progress .card.draggable {
    /* Keep interactions disabled, but do not ghost visuals */
    cursor: not-allowed !important;
    pointer-events: none;
}

/* Processing message is now handled by JS with i18n support via .game-notification */

/* Media queries for responsive design */
@media screen and (max-width: 768px) {
    #app {
        padding: 10px;
    }

    .panel {
        padding: 15px;
    }

    button {
        padding: 8px 12px;
    }
}

@media screen and (max-width: 480px) {
    h1 {
        font-size: 2rem;
    }

    .panel {
        padding: 10px;
    }

    button {
        padding: 6px 10px;
        font-size: 0.9rem;
    }
}

/* Unsupported-desktop-browser block overlay — on-brand with the marketing
   landing (cosmic violet + Cinzel display) so a blocked visitor sees Moonfall,
   not a cold error box. Fires before i18n init, so the copy is English. */
#browser-unsupported-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100vw;
    height: 100vh;
    height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    z-index: 999999;
    background:
        radial-gradient(120% 90% at 50% -10%, rgba(74, 50, 112, 0.55), transparent 60%),
        radial-gradient(90% 70% at 50% 120%, rgba(123, 104, 238, 0.20), transparent 60%),
        #07050d;
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}

.browser-unsupported-card {
    background: linear-gradient(180deg, rgba(16, 12, 28, 0.96), rgba(9, 7, 18, 0.96));
    border: 1px solid rgba(123, 104, 238, 0.35);
    color: #eedbf1;
    padding: 34px 30px 30px;
    max-width: 460px;
    width: 100%;
    border-radius: 18px;
    box-shadow:
        0 30px 80px -24px rgba(0, 0, 0, 0.85),
        0 0 40px -8px rgba(123, 104, 238, 0.35),
        inset 0 1px 0 rgba(238, 219, 241, 0.08);
    text-align: center;
    font-family: 'Spectral', Georgia, serif;
}

.block-moon {
    width: 42px;
    height: 42px;
    margin: 0 auto 14px;
    color: #b9a6ff;
    filter: drop-shadow(0 0 14px rgba(123, 104, 238, 0.6));
}
.block-moon svg {
    width: 100%;
    height: 100%;
    display: block;
}

.browser-unsupported-title {
    font-family: 'Cinzel', serif;
    font-weight: 700;
    font-size: 1.4rem;
    line-height: 1.15;
    letter-spacing: 0.5px;
    margin-bottom: 12px;
    color: #f4ecff;
}

.browser-unsupported-body {
    font-size: 1rem;
    line-height: 1.55;
    color: #cdbce0;
}

/* "Stay connected" Discord pill — matches the landing's violet CTA. */
.block-discord {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    margin-top: 22px;
    padding: 11px 20px;
    border-radius: 999px;
    font-family: 'Cinzel', serif;
    font-weight: 600;
    font-size: 0.82rem;
    letter-spacing: 0.06em;
    text-decoration: none;
    color: #f4ecff;
    background: linear-gradient(180deg, #8a64c8, #7b68ee);
    box-shadow: 0 10px 24px -10px rgba(123, 104, 238, 0.8);
    transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}
.block-discord svg {
    width: 18px;
    height: 18px;
}
.block-discord:hover {
    transform: translateY(-1px);
    filter: brightness(1.08);
    box-shadow: 0 14px 30px -10px rgba(123, 104, 238, 0.95);
}
.block-discord:focus-visible {
    outline: 2px solid #b9a6ff;
    outline-offset: 3px;
}

/* Soft browser notice — the 'unknown engine' tier of the desktop gate. The app
   boots normally underneath; this is only a compact dismissible hint pinned
   bottom-center, in the block-overlay's cosmic dialect. z-index 9300 sits
   deliberately BELOW the battle overlay ladder (battle veil 9380 < #fx-layer
   9400 < tutorial veil 9420 < turn banner) so gameplay chrome always outranks
   the hint. */
.browser-soft-notice {
    position: fixed;
    bottom: max(16px, env(safe-area-inset-bottom));
    left: 50%;
    transform: translateX(-50%);
    z-index: 9300;
    display: flex;
    align-items: center;
    gap: 12px;
    max-width: min(92vw, 480px);
    padding: 10px 12px 10px 18px;
    border-radius: 999px;
    border: 1px solid rgba(123, 104, 238, 0.35);
    background: linear-gradient(180deg, rgba(16, 12, 28, 0.96), rgba(9, 7, 18, 0.96));
    color: #eedbf1;
    font-family: 'Spectral', Georgia, serif;
    font-size: 0.92rem;
    line-height: 1.35;
    box-shadow:
        0 18px 44px -18px rgba(0, 0, 0, 0.85),
        0 0 24px -8px rgba(123, 104, 238, 0.35),
        inset 0 1px 0 rgba(238, 219, 241, 0.08);
}

.browser-soft-notice-dismiss {
    flex: none;
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid rgba(123, 104, 238, 0.4);
    border-radius: 50%;
    background: rgba(123, 104, 238, 0.16);
    color: #f4ecff;
    font-size: 0.85rem;
    line-height: 1;
    cursor: pointer;
    transition: background 0.2s ease, transform 0.2s ease;
}
.browser-soft-notice-dismiss:hover {
    background: rgba(123, 104, 238, 0.32);
    transform: scale(1.06);
}
.browser-soft-notice-dismiss:focus-visible {
    outline: 2px solid #b9a6ff;
    outline-offset: 2px;
}

/* Legal footer bar. Its rendered height is published to --legal-footer-h by
   legal-footer-fit.js so the lobby column reserves exactly this strip; the
   fallback here keeps things sane before that script runs. Flex + wrap keeps
   the localized links centred and off the screen edges at any width, and the
   small responsive font means even French fits one line down to phone widths.

   COLOUR: the strip paints straight onto the lobby's wood-table art, whose
   planks run from pale cream to dark grain under the same line of text. The
   old neutral greys (#777 links, #555 separators) had no relation to the
   game's palette and measured under 2:1 against both extremes — muddy on the
   dark grain, invisible on the pale planks ("terms and conditions and
   whatever at bottom is ugly"). The links now wear the lobby's own lavender
   (`#cdbce0`, the landing footer's `--lav-dim`; `#eedbf1` on hover — the
   colour every panel subtitle already uses), and a shadow tight to the glyphs
   lifts them off the pale planks. No plate, band or veil behind the text: an
   area treatment reads as an object sitting on the picture. The tablet
   breakpoint used to override these greys with the same lavender — one
   source now. */
:root {
    --legal-footer-h: 40px;
}

.legal-footer-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    box-sizing: border-box;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0 6px;
    padding: 5px 12px;
    line-height: 1.3;
    font-size: clamp(9px, 1.35vmin, 12px);
    color: rgba(238, 219, 241, 0.72);
    text-shadow: 0 1px 2px rgba(7, 5, 13, 0.7), 0 0 6px rgba(7, 5, 13, 0.45);
    z-index: 1001;
    pointer-events: auto;
}

.legal-footer-bar a {
    color: #cdbce0;
    text-decoration: none;
    transition: color 0.2s;
}

.legal-footer-bar a:hover {
    color: #eedbf1;
}

.legal-footer-sep {
    color: rgba(238, 219, 241, 0.45);
}

body.game-active .legal-footer-bar {
    display: none;
}


/* Shared press feel: primary CTAs squash on press. Micro transform only —
   never applied to transform-positioned controls (end-turn has its own
   composed :active rule in game-board.css). */
#solo-adventure-btn:active,
#tutorial-btn:active,
#create-room-btn:active,
#join-room-btn:active,
#copy-code-btn:active,
#copy-link-btn:active,
#go-back-btn:active,
#open-foundry-btn:active,
#open-collection-btn:active,
#mulligan-confirm-btn:active {
    transform: scale(0.96);
}

/* Shared view-transition fade (UI.Effects.viewEnter): entering views fade in
   instead of popping. One-shot; the class self-removes. */
.view-enter {
    animation: view-enter-fade 200ms ease-out;
}

@keyframes view-enter-fade {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    .view-enter {
        animation: none;
    }
}
