:root {
    --void: #12070f;
    --wine: #241120;
    --wine-2: #2f1628;
    --parchment: #efe1cc;
    --parchment-dim: #b6a48f;
    --blood: #9c1338;
    --blood-bright: #e0355f;
    --gold: #c8a24a;
    --gold-dim: #8a713a;
    --moss: #5f6f45;
    --moss-bright: #8ba35c;
    --bone: #b8c9c2;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    min-height: 100%;
    background: var(--void);
    color: var(--parchment);
    font-family: 'EB Garamond', Garamond, Georgia, serif;
}

button { font-family: inherit; }
:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

#app {
    max-width: 1040px;
    margin: 0 auto;
    padding: clamp(16px, 4vw, 40px) clamp(12px, 3vw, 24px) 48px;
    min-height: 100vh;
}

/* ---------------- Start screen ---------------- */
.start-screen {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 78vh;
    text-align: center;
    gap: 8px;
}
.start-screen .eyebrow, .rpg-screen .eyebrow {
    font-family: 'Cinzel', serif;
    font-size: 0.72rem;
    letter-spacing: 0.24em;
    text-transform: uppercase;
    color: var(--gold);
    margin-bottom: 6px;
}
.start-screen h1, .rpg-screen h1 {
    font-family: 'Pirata One', 'Cinzel', serif;
    font-weight: 400;
    font-size: clamp(2.2rem, 6vw, 3.2rem);
    letter-spacing: 0.03em;
    margin: 0;
    color: var(--parchment);
    text-shadow: 0 0 30px rgba(224, 53, 95, 0.32);
}
.subtitle { color: var(--parchment-dim); margin: 6px 0 26px; max-width: 46ch; font-size: 1.05rem; line-height: 1.6; }
.encounter-btn {
    background: none;
    border: 28px solid transparent;
    border-image: url('assets/btn-normal.png') 90 150 fill / 28px 46px / 0 stretch;
    color: var(--parchment);
    padding: 6px 10px;
    font-family: 'Cinzel', serif;
    font-size: 0.92rem;
    letter-spacing: 0.02em;
    cursor: pointer;
    min-width: 340px;
    max-width: 92vw;
    transition: filter 0.15s, transform 0.15s;
}
.encounter-btn + .encounter-btn { margin-top: 12px; }
.encounter-btn:hover:not(:disabled) { border-image-source: url('assets/btn-hover.png'); transform: translateY(-1px); }
.encounter-btn:active:not(:disabled) { border-image-source: url('assets/btn-pressed.png'); transform: translateY(0); }
.encounter-btn:disabled { border-image-source: url('assets/btn-disabled.png'); color: var(--parchment-dim); cursor: default; }

/* ---------------- Milestone 2: character creation / camp / progression screens ---------------- */
.rpg-screen {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 10px;
    padding: clamp(24px, 6vh, 60px) 0;
}
/* Camp is the main hub between encounters - a traveler's waystation
   (weapon rack, map table) rather than a menu screen. */
.rpg-screen.camp-screen { border-radius: 4px; }
/* The photo + character + mission panel live in their own fixed-aspect
   box (same pattern as the battle .stage), NOT smeared across the whole
   growing .rpg-screen panel - that was the actual bug behind "he isn't
   fitting into the background": the character's position had no fixed
   relationship to where the ground/path actually is in the photo, since
   the panel's height (and so the visible crop of a background-size:cover
   image) shifted with content. Pinning both the photo and the character
   to percentages of the SAME fixed box lets the character's feet land on
   the path consistently. */
.camp-stage {
    position: relative;
    width: 100%;
    max-width: 680px;
    aspect-ratio: 1376 / 768;
    margin: 4px 0 8px;
    border-radius: 4px;
    overflow: hidden;
    background-image: url('assets/bg-adventure-camp.jpg');
    background-size: cover;
    background-position: center;
    container-type: size; /* so .mission-panel's cqw padding below resolves */
}
.camp-screen .eyebrow, .camp-screen h1, .camp-screen .subtitle, .camp-screen .text-link-btn {
    text-shadow: 0 2px 10px rgba(0,0,0,0.85), 0 1px 3px rgba(0,0,0,0.9);
}
/* Feet pinned near the bottom of the dirt path, not floating up in the
   tents/treeline. */
/* Centered on the dirt path, which runs straight down the middle of the
   photo. Shifted below the frame (negative bottom) and enlarged so the
   feet crop off below .camp-stage's overflow:hidden edge - a close-up
   portrait crop (waist-up), not full-body-to-feet. */
.camp-figure {
    position: absolute;
    left: 50%;
    bottom: -55%;
    height: 145%;
    transform: translateX(-50%);
    display: flex;
    align-items: flex-end;
}
.camp-figure img {
    height: 100%;
    width: auto;
    max-width: none;
    object-fit: contain;
    filter: drop-shadow(0 14px 14px rgba(0,0,0,0.55));
}
.mission-panel {
    position: absolute;
    right: 5%;
    bottom: 8%;
    width: 42%;
    max-width: 320px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    text-align: left;
    background: linear-gradient(180deg, rgba(47,26,41,0.9), rgba(24,12,20,0.9));
    border: 1px solid var(--gold-dim);
    border-radius: 6px;
    padding: clamp(8px, 1.8cqw, 14px) clamp(10px, 2.2cqw, 16px);
    color: var(--parchment);
    font-family: 'EB Garamond', Garamond, Georgia, serif;
    cursor: pointer;
    transition: border-color 150ms ease, transform 150ms ease;
}
.mission-panel:hover { border-color: var(--gold); transform: translateY(-2px); }
.mission-panel:active { transform: translateY(0); }
.mission-title {
    font-family: 'Cinzel', serif;
    font-size: 0.7rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--gold);
}
.mission-label { font-size: 0.95rem; line-height: 1.3; }
.mission-quest {
    font-weight: 600;
    color: var(--gold);
    letter-spacing: 0.02em;
}
.mission-complete {
    cursor: default;
    text-align: center;
    align-items: center;
    color: var(--parchment-dim);
}
.mission-panel.mission-complete:hover { border-color: var(--gold-dim); transform: none; }
.button-column {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    margin-top: 8px;
}
.text-link-btn {
    background: none;
    border: none;
    color: var(--parchment-dim);
    font-family: 'EB Garamond', Garamond, serif;
    font-size: 0.9rem;
    text-decoration: underline;
    cursor: pointer;
    margin-top: 10px;
}
.text-link-btn:hover { color: var(--gold); }
.text-link-btn.danger { color: var(--blood); }
.text-link-btn.danger:hover { color: var(--blood-bright); }

.choice-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 16px;
    width: 100%;
    max-width: 760px;
    margin-top: 10px;
}
.choice-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    text-align: left;
    background: linear-gradient(180deg, var(--wine-2), var(--wine));
    border: 1px solid var(--gold-dim);
    border-radius: 6px;
    padding: 18px 20px;
    cursor: pointer;
    transition: border-color 0.15s, transform 0.15s;
}
.choice-card:hover { border-color: var(--gold); transform: translateY(-2px); }
.choice-name {
    font-family: 'Cinzel', serif;
    font-weight: 700;
    font-size: 1.1rem;
    color: var(--parchment);
}
.choice-flavor {
    font-style: italic;
    color: var(--parchment-dim);
    font-size: 0.92rem;
    line-height: 1.4;
}
.choice-mechanics {
    font-family: 'Cinzel', serif;
    font-size: 0.72rem;
    letter-spacing: 0.02em;
    color: var(--gold);
    line-height: 1.5;
}
.slot-card-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: 4px;
}
/* .encounter-btn's 340px min-width is meant for the big standalone buttons
   on Camp/Origin/Age/Title - inside a choice-grid card (as narrow as
   240px, see .choice-grid above) that same min-width overflows the card
   instead of fitting it. Scoped to this context only. */
.choice-card .encounter-btn { min-width: 0; width: 100%; }
.slot-card-actions .encounter-btn { flex: 1; }
.slot-card-actions .text-link-btn { margin-top: 0; white-space: nowrap; }

.name-field {
    width: 100%;
    max-width: 360px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 4px;
    margin-top: 4px;
}
.name-label {
    font-family: 'Cinzel', serif;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    color: var(--gold);
}
.name-input {
    width: 100%;
    background: rgba(0,0,0,0.35);
    border: 1px solid var(--gold-dim);
    border-radius: 4px;
    padding: 8px 12px;
    color: var(--parchment);
    font-family: 'EB Garamond', Garamond, serif;
    font-size: 1rem;
}
.name-input:focus { outline: none; border-color: var(--gold); }

.character-sheet {
    width: 100%;
    max-width: 460px;
    background: linear-gradient(180deg, rgba(47,26,41,0.7), rgba(24,12,20,0.7));
    border: 1px solid var(--gold-dim);
    border-radius: 6px;
    padding: 16px 20px;
    margin-top: 8px;
}
.sheet-row {
    display: flex;
    justify-content: space-between;
    padding: 6px 0;
    border-bottom: 1px solid rgba(200, 162, 74, 0.15);
    font-family: 'Cinzel', serif;
    font-size: 0.85rem;
}
.sheet-row:last-of-type { border-bottom: none; }
.sheet-row span:first-child { color: var(--parchment-dim); }
.sheet-row span:last-child { color: var(--parchment); }
.sheet-note {
    margin: 10px 0 0;
    font-size: 0.82rem;
    color: var(--parchment-dim);
    line-height: 1.5;
    text-align: left;
}
.xp-meter {
    height: 10px;
    border-radius: 5px;
    background: rgba(0,0,0,0.55);
    border: 1px solid rgba(0,0,0,0.6);
    overflow: hidden;
    margin: 4px 0 10px;
}
.xp-meter > span {
    display: block;
    height: 100%;
    background: linear-gradient(90deg, var(--gold-dim), var(--gold));
}

/* ---------------- Stage frame ---------------- */
.stage-frame {
    border: 1px solid rgba(200, 162, 74, 0.35);
    border-radius: 6px;
    padding: 8px;
    background: linear-gradient(180deg, #1a0d16, #150a12);
    box-shadow: 0 30px 70px -30px rgba(0,0,0,0.85);
}

.stage {
    position: relative;
    width: 100%;
    aspect-ratio: 1376 / 768;
    border-radius: 3px;
    overflow: hidden;
    isolation: isolate;
    container-type: size;
    container-name: stage;
}

/* --- illustrated per-encounter background --- */
.backdrop {
    position: absolute;
    inset: 0;
    background-size: cover;
    background-position: center;
}
/* Day/night curse toggle: most locations only have one photo (not a
   day/night pair), so instead of crossfading to a second image, a dark
   blue-black wash fades in over whatever the current encounter's photo
   is - reads as a real mood/lighting shift without needing source art
   for every location in both states. */
.night-tint {
    position: absolute;
    inset: 0;
    background: rgba(6,8,26,0.5);
    mix-blend-mode: multiply;
    opacity: 0;
    transition: opacity 900ms ease;
    pointer-events: none;
}
.stage.is-night .night-tint { opacity: 1; }
/* Subtle scrim so UI chrome (log, nameplates, panel) stays readable over
   whatever's happening in the painted sky beneath it. */
.scrim {
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(10,4,8,0.32) 0%, rgba(10,4,8,0) 22%, rgba(10,4,8,0) 68%, rgba(10,4,8,0.55) 100%);
    pointer-events: none;
}

/* --- day/night medallion toggle --- */
.daynight-toggle {
    position: absolute;
    top: 1.5%;
    right: 3%;
    z-index: 8;
    display: flex;
    align-items: center;
    gap: 8px;
    background: linear-gradient(180deg, rgba(47,26,41,0.85), rgba(24,12,20,0.85));
    border: 1px solid var(--gold-dim);
    border-radius: 999px;
    padding: clamp(3px, 0.75cqh, 4px) clamp(8px, 1.25cqw, 12px) clamp(3px, 0.75cqh, 4px) clamp(3px, 0.75cqh, 4px);
    cursor: pointer;
    color: var(--parchment);
    font-family: 'Cinzel', serif;
    font-size: clamp(0.56rem, 1.9cqh, 0.66rem);
    letter-spacing: 0.06em;
}
.daynight-toggle:hover:not(:disabled) { border-color: var(--gold); }
.daynight-toggle:disabled { opacity: 0.45; cursor: default; }
.daynight-toggle .medallion {
    width: clamp(14px, 3.6cqh, 20px); height: clamp(14px, 3.6cqh, 20px); border-radius: 50%;
    flex: none;
    background: radial-gradient(circle at 35% 35%, #fff3c4, #d99a2e 70%);
    box-shadow: 0 0 10px rgba(232,179,74,0.6);
    transition: background 500ms ease, box-shadow 500ms ease;
}
.stage.is-night .daynight-toggle .medallion {
    background: radial-gradient(circle at 35% 35%, #f4f0e8, #9992b8 70%);
    box-shadow: 0 0 10px rgba(190,180,224,0.6);
}

/* --- combat log --- */
/* Lives below the stage as its own panel, not overlaid on the battle art -
   it used to sit top-left over the stage and increasingly collided with
   the characters as they moved closer together and the player nameplate
   grew larger, so it now has its own dedicated space instead of competing
   for room over the artwork. */
.log {
    width: 100%;
    margin-top: 8px;
    background: linear-gradient(180deg, rgba(36,17,32,0.88), rgba(18,7,15,0.9));
    border: 1px solid rgba(200, 162, 74, 0.3);
    border-radius: 4px;
    padding: 8px 12px;
}
.log .log-title {
    font-family: 'Cinzel', serif;
    font-size: 0.62rem;
    letter-spacing: 0.12em;
    color: var(--gold);
    text-transform: uppercase;
    margin-bottom: 4px;
}
.log ul {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 96px;
    overflow-y: auto;
}
.log li {
    font-size: 0.76rem;
    line-height: 1.35;
    color: var(--parchment-dim);
}
.log li.you { color: var(--blood-bright); }
.log li.foe { color: var(--moss-bright); }

/* --- target hint --- */
.target-hint {
    position: absolute;
    top: 4%;
    left: 50%;
    transform: translateX(-50%);
    z-index: 8;
    font-family: 'Cinzel', serif;
    font-size: 0.72rem;
    letter-spacing: 0.08em;
    color: #12070f;
    background: var(--gold);
    padding: 5px 14px;
    border-radius: 3px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.5);
}

/* --- combatants --- */
.combatant {
    position: absolute;
    bottom: 8%;
    display: flex;
    flex-direction: column;
    align-items: center;
    transition: filter 200ms ease, opacity 400ms ease;
}
.combatant.player {
    left: 15%;
    /* 26px lower than the shared bottom:8% baseline (per feedback, a
       remaining feet-alignment nudge - iterated live from 3px to 10px to
       18px to this) - calc() against bottom rather than a transform,
       since the walk-up flourish's own JS-driven transform (see the next
       comment) fully replaces .combatant.player's inline transform on
       every animation frame - stacking this into transform too would
       just get overwritten the instant that animation runs, not
       combined with it. */
    bottom: calc(8% - 26px);
    /* The melee walk-up flourish's forward-and-back step (see
       animatePlayerStep in game.js) writes a plain inline
       transform:translateX(...cqw) directly, re-resolved every
       requestAnimationFrame tick rather than as a CSS class+transition
       - render() fully tears down and rebuilds this element on any
       state change, which broke a CSS transition two different ways
       (reported live as "teleporting"): no "before" frame to animate
       from if the element was rebuilt right as the transition started,
       and an abandoned in-flight transition if it was rebuilt again
       mid-animation. A per-frame JS write just re-applies wherever the
       animation currently is to whatever element exists that frame, so
       neither case can desync it. Nothing is ever left shifted between
       turns either way - every attack starts and ends at this same
       left:15% spot (see animatePlayerStep's own comment). */
}

/* Single enemy slot for now (1-on-1 combat, per feedback) - right-aligned
   in its own column, anchored at the SAME bottom offset (8%) as
   .combatant.player above, resolved against the same .stage height in
   both cases, so feet land on the same ground line regardless of the
   stage's own aspect ratio. The previous version instead anchored the
   enemy via a flex child's own margin-bottom% - CSS always resolves a
   margin percentage against the containing block's WIDTH, even for
   margin-bottom, so that put the enemy's feet at 32% of the (much wider)
   cluster width above the floor rather than 8% of the stage's height
   like the player - the two only coincidentally lined up at one
   specific aspect ratio, and visibly diverged (reported live - a
   screenshot showed the enemy floating well above the player's own
   ground line) at the session's actual widescreen aspect ratio. */
.enemy-cluster {
    position: absolute;
    right: 11%;
    bottom: 8%;
    display: flex;
    align-items: flex-end;
    justify-content: flex-end;
    width: 46%;
}
.enemy-cluster .combatant { position: relative; left: auto; }
.enemy-cluster .combatant .figure { transform-origin: 50% 100%; }
/* Skeleton's defeated pose is a collapsed, wide illustration (aspect ratio
   ~2.05, vs ~0.87 standing) - shown at the standing height it would render
   about 2.3x wider than upright, so it gets a shorter height here (same
   ratio as standing-to-defeated below) to keep roughly the same on-screen
   footprint instead. */
.combatant.dead.has-defeated-pose .figure.char-skeleton { height: 17.3cqh; }

.combatant.dead { opacity: 0.32; filter: grayscale(0.9) brightness(0.7); }
/* A dedicated defeated-pose illustration already tells "this is dead" on
   its own - the heavy fade above would wash it out almost entirely, so it
   gets a much lighter treatment instead. */
.combatant.dead.has-defeated-pose { opacity: 1; filter: brightness(0.82) saturate(0.85); }

.combatant.targetable .figure { cursor: crosshair; }
.combatant.targetable .figure::after {
    content: "";
    position: absolute;
    inset: -6%;
    border-radius: 8px;
    border: 2px solid var(--gold);
    opacity: 0;
    animation: target-pulse 1.1s ease-in-out infinite;
    pointer-events: none;
}
@keyframes target-pulse {
    0%, 100% { opacity: 0.15; transform: scale(0.98); }
    50% { opacity: 0.75; transform: scale(1.02); }
}

.nameplate {
    position: relative;
    margin-bottom: 8px;
    padding: 4px 14px 5px;
    background: linear-gradient(180deg, #2f1a29, #1c0e1a);
    border: 1px solid var(--gold-dim);
    border-radius: 3px;
    text-align: center;
    min-width: 78%;
    z-index: 2;
}
.nameplate .name {
    font-family: 'Cinzel', serif;
    font-weight: 600;
    font-size: 0.86rem;
    letter-spacing: 0.02em;
    color: var(--parchment);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.meter {
    position: relative;
    height: 8px;
    border-radius: 5px;
    background: rgba(0,0,0,0.55);
    border: 1px solid rgba(0,0,0,0.6);
    overflow: hidden;
    margin-top: 4px;
}
.meter > span {
    display: block;
    height: 100%;
    border-radius: 5px 0 0 5px;
    transition: width 400ms ease;
}
.meter.hp.enemy-tone > span { background: linear-gradient(90deg, var(--moss), var(--moss-bright)); }
.meter.hp.player-tone > span { background: linear-gradient(90deg, #7a2d3e, #c24d63); }
.meter.blood > span { background: linear-gradient(90deg, var(--blood), var(--blood-bright)); box-shadow: 0 0 8px rgba(224, 53, 95, 0.7); }
.meter-label {
    font-family: 'Cinzel', serif;
    font-size: 0.54rem;
    letter-spacing: 0.06em;
    color: var(--parchment-dim);
    display: flex;
    justify-content: space-between;
    margin-top: 2px;
}
.stat-icon {
    display: inline-block;
    width: 1.1em;
    height: 1.1em;
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    vertical-align: -0.22em;
    margin-right: 3px;
}
.stat-icon-hp { background-image: url('assets/icon-hp.png'); }
.stat-icon-armor { background-image: url('assets/icon-armor.png'); }
.stat-icon-blood { background-image: url('assets/icon-blood.png'); }
.stat-icon-xp { background-image: url('assets/icon-xp.png'); }
.armor-badge {
    margin-left: 6px;
    color: #a9c2d6;
}

/* The player's own HP/Armor is the number the player cares about turn to
   turn, so it reads bigger and bolder than an enemy's nameplate. */
.player-nameplate {
    padding: 6px 18px 8px;
    min-width: 86%;
}
.player-nameplate .name { font-size: 1.05rem; }
.player-nameplate .meter.hp { height: 13px; margin-top: 6px; }
.player-nameplate .meter-label { font-size: 0.7rem; }
.player-nameplate .armor-badge { font-size: 0.82rem; font-weight: 700; }
.level-badge {
    display: inline-block;
    margin-left: 6px;
    padding: 1px 8px;
    border-radius: 999px;
    background: var(--gold-dim);
    color: var(--void);
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    vertical-align: middle;
}
.meter.xp-meter-mini { height: 5px; margin-top: 5px; }
.meter.xp-meter-mini > span { background: linear-gradient(90deg, var(--gold-dim), var(--gold)); }

/* Character art is height-anchored (via container query units against the
   .stage) rather than boxed to a fixed aspect ratio, since real poses vary
   wildly in width (a tall standing idle vs. a wide lunging attack) but
   should read as the same character at the same in-world height. */
.figure {
    position: relative;
    height: 46cqh;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    animation: idle-bob 4.2s ease-in-out infinite;
    transition: transform 220ms ease;
}
.figure img {
    height: 100%;
    width: auto;
    max-width: none;
    object-fit: contain;
    display: block;
    filter: drop-shadow(0 14px 14px rgba(0,0,0,0.55));
    pointer-events: none;
}
/* Uniform size-down for the player specifically (reported as reading too
   large standing next to the enemies). A dedicated wrapper around .figure (see
   renderCombatant in game.js) rather than a scale() on .figure itself -
   .figure's transform is perpetually claimed by idle-bob (and
   is-acting/is-hit swap out its whole `animation` property while active),
   so a static scale() declared there would just get silently overridden.
   bottom-center origin keeps the feet planted instead of the shrink
   pulling them up off the ground. */
.figure-scale { display: flex; align-items: flex-end; justify-content: center; }
.combatant.player .figure-scale { transform: scale(0.92); transform-origin: bottom center; }
/* Relative in-world scale between characters - tallest to shortest.
   Re-audited together with the player's own true rendered height (the
   base .figure rule below is 46cqh, scaled by .figure-scale's 0.92 above
   -> ~42.3cqh true) via a pixel-alpha content-bounding-box scan of each
   character's actual art (same technique as VAMPIRE_POSE_HEIGHT's own
   comments in game.js), not eyeballed - most of these images are
   cropped edge-to-edge already so their cqh value IS their true height,
   but goblin-archer/goblin-sneak/goblin-warrior carried real transparent
   padding (measured 3-6% of canvas height on top and bottom) that was
   silently shrinking them below their stated height and floating their
   feet above the ground line - cropped out at the source instead of
   compensated here (see assets/*.png), so all eight numbers below are
   now directly comparable "true" heights. Skeleton and Zombie are
   human-sized undead, so sit close to the Vampire's own ~42.3cqh;
   Hobgoblin Warrior/Archer are bulkier elite goblins, taller than the
   base Goblin Grunt tier; Goblin Shaman/Sneak sit at or under Grunt;
   Giant Rat is the smallest, a low quadruped. */
.figure.char-skeleton { height: 41cqh; }
.figure.char-zombie { height: 39cqh; }
.figure.char-hobgoblin-warrior { height: 31cqh; }
.figure.char-hobgoblin-archer { height: 27cqh; }
.figure.char-goblin-grunt { height: 24cqh; }
.figure.char-goblin-shaman { height: 23cqh; }
.figure.char-goblin-sneak { height: 20cqh; }
.figure.char-giant-rat { height: 15cqh; }
/* Smile Golem (the final boss) - deliberately the largest figure in the
   roster, taller even than the Vampire's own ~42.3cqh, so it physically
   dominates the screen the way a final boss should. Its own art is
   edge-to-edge with no transparent padding (measured the same way as
   the rest of this block), so 50cqh here is directly its true height. */
.figure.char-smile-golem { height: 50cqh; }

/* The whole roster has real art now - Goblin Sneak and Goblin Warrior
   (characterId 'hobgoblin-warrior') were the last two tinted
   goblin-grunt.png placeholders. Both fall back to .figure img's plain
   drop-shadow now instead of the old hue-rotate tint. */

/* Idle breathing/sway loop - a subtle standing-in-place motion built from
   the same single illustrated pose, per the "position interpolation and
   scaling, not frame-by-frame sprites" brief. Composed as a proper
   @keyframes animation, not a static transform, so is-acting/is-hit below
   (also animations) can cleanly take over the `transform` property while
   active and hand it back to idle-bob when their class is removed.
   Previously staggered per character (a different animation-delay/
   -duration each) so a multi-enemy group didn't bob in unison - now
   always 1-on-1 (no per-character overrides left below), that stagger
   meant the player and their one opponent were almost never at the same
   point in the cycle at any given instant, which could read as their
   feet floating a couple of px out of alignment with each other even
   though the resting (0%/100%) position itself was correctly lined up
   (reported live via a screenshot) - every combatant now shares the
   exact same cycle, so the two are always mid-breath together, not just
   at rest together. */
@keyframes idle-bob {
    0%, 100% { transform: translateY(0) scale(1); }
    50% { transform: translateY(-1.4%) scale(1.008); }
}

.figure.is-acting { animation: acting-lunge 380ms ease; }
@keyframes acting-lunge {
    0% { transform: translateY(0) scale(1); }
    55% { transform: translateY(-2%) scale(1.06); }
    100% { transform: translateY(0) scale(1); }
}
.figure.is-hit { animation: hit-shake 480ms ease; }
@keyframes hit-shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-3%); }
    75% { transform: translateX(3%); }
}
.figure.is-hit img { animation: hit-flash 480ms ease; }
@keyframes hit-flash {
    /* brightness(0) crushes the art to solid black while keeping its alpha
       shape; invert(1) flips that to solid white - a true white silhouette
       flash, not just a brighten, snapping back to normal by the end. */
    0%, 100% { filter: drop-shadow(0 14px 14px rgba(0,0,0,0.55)) brightness(1) invert(0); }
    35%, 55% { filter: drop-shadow(0 14px 14px rgba(0,0,0,0.55)) brightness(0) invert(1); }
}

/* --- floating damage numbers --- */
.dmg-float {
    position: absolute;
    top: 6%;
    left: 50%;
    z-index: 6;
    font-family: 'Cinzel', serif;
    font-weight: 900;
    font-size: clamp(1.1rem, 2.4vw, 1.6rem);
    pointer-events: none;
    text-shadow: 0 0 6px rgba(0,0,0,0.85), 0 0 14px currentColor;
    animation: float-up 950ms ease-out forwards;
}
.dmg-float.dmg { color: #ffdc6b; }
.dmg-float.heal { color: #8ff0b0; }

/* --- Goblin Archer's flying arrow (fireProjectile in game.js) --- */
.projectile {
    position: absolute;
    height: auto;
    z-index: 6;
    pointer-events: none;
    filter: drop-shadow(0 4px 6px rgba(0,0,0,0.5));
}
@keyframes float-up {
    0% { transform: translate(-50%, 0) scale(0.85); opacity: 0; }
    18% { transform: translate(-50%, -8%) scale(1.08); opacity: 1; }
    100% { transform: translate(-50%, -60%) scale(1); opacity: 0; }
}

/* --- floating status words (STUNNED, CRIT!) --- same mechanism as
   .dmg-float above (showStatusFloat in game.js), sized/positioned for a
   word instead of a number and held noticeably longer (1400ms with a
   plateau, vs. the number's quick 950ms fade) - meant to register as its
   own callout, not blend into an adjacent damage number appearing at the
   same moment (CRIT! shows alongside its own dmg-float hit). Sits above
   the number (top:-8% vs. the number's 6%) for exactly that reason. */
.status-float {
    position: absolute;
    top: -8%;
    left: 50%;
    z-index: 7;
    font-family: 'Cinzel', serif;
    font-weight: 900;
    letter-spacing: 0.04em;
    font-size: clamp(0.95rem, 2.1vw, 1.35rem);
    text-transform: uppercase;
    pointer-events: none;
    white-space: nowrap;
    text-shadow: 0 0 6px rgba(0,0,0,0.9), 0 0 16px currentColor;
    animation: float-up-word 1400ms ease-out forwards;
}
.status-float.crit { color: #ff8a3d; }
.status-float.stunned { color: #c88bff; }
@keyframes float-up-word {
    0% { transform: translate(-50%, 0) scale(0.8); opacity: 0; }
    15% { transform: translate(-50%, -10%) scale(1.12); opacity: 1; }
    75% { transform: translate(-50%, -45%) scale(1); opacity: 1; }
    100% { transform: translate(-50%, -65%) scale(1); opacity: 0; }
}

/* --- bottom control panel --- */
/* Its own panel below the stage now (see the comment on renderBattleScreen
   in game.js for why), not an absolute-positioned overlay pinned inside
   it - a leveled-up character's skill count made that overlay tall enough
   to hide the lower half of the battlefield. */
.panel {
    width: 100%;
    margin-top: 8px;
    padding: 10px 3%;
    background: linear-gradient(180deg, rgba(36,17,32,0.88), rgba(18,7,15,0.9));
    border: 1px solid rgba(200, 162, 74, 0.3);
    border-radius: 4px;
    display: flex;
    justify-content: center;
    gap: clamp(6px, 1.4vw, 14px);
    flex-wrap: wrap;
}
.skill-slot {
    position: relative;
    flex: 1 1 140px;
    max-width: 210px;
}
.skill-btn {
    width: 100%;
    /* border-image doesn't work here: btn-normal.png's ornate frame was
       painted to be seen at ~90-150px thick (see .encounter-btn/.choice-card
       below), and squeezing that same slice down to a few px just turns it
       to mush pressed right up against the text. Using the art as a plain
       stretched background instead - never eats into the box model, so
       padding always gives the text real breathing room regardless of how
       small the button gets. */
    /* Explicit background-color, not just background-image: buttons get a
       default UA background (renders as a bright off-white ~rgb(240,240,240)
       in Chromium) that background-image doesn't reliably paint over in
       every state/timing - confirmed via getComputedStyle that it was
       leaking through. This closes that gap for good instead of hoping the
       image always wins. */
    background-color: var(--wine);
    background-image: url('assets/btn-normal.png');
    background-size: 100% 100%;
    border: 1px solid var(--gold-dim);
    border-radius: 5px;
    padding: clamp(4px, 0.9vw, 7px) clamp(6px, 0.8vw, 10px);
    display: flex;
    align-items: center;
    gap: 9px;
    cursor: pointer;
    transition: border-color 160ms ease, transform 160ms ease;
    text-align: left;
}
.skill-btn:hover:not(:disabled) { background-image: url('assets/btn-hover.png'); border-color: var(--gold); transform: translateY(-2px); }
.skill-btn:active:not(:disabled) { background-image: url('assets/btn-pressed.png'); transform: translateY(0); }
.skill-btn:disabled { background-image: url('assets/btn-disabled.png'); opacity: 0.55; cursor: default; transform: none; }
.skill-icon {
    width: clamp(19px, 4vw, 28px); height: clamp(19px, 4vw, 28px);
    flex: none;
    border-radius: 50%;
    background: radial-gradient(circle at 35% 35%, var(--blood-bright), var(--blood) 70%);
    display: flex; align-items: center; justify-content: center;
    font-size: clamp(0.6rem, 1.9vw, 0.9rem);
}
/* Skills with real portrait art (Bite, Life Rip) get a bigger icon slot
   framed with the ornate skill-icon-frame art - emoji-icon skills stay at
   the smaller base .skill-icon size above, since the frame's fine detail
   needs more room than that to read. */
.skill-icon-framed {
    width: clamp(42px, 8.6vw, 60px); height: clamp(42px, 8.6vw, 60px);
    background: none;
    position: relative;
    overflow: visible;
    /* The frame overlay below bleeds past this box's own right edge (its
       ::after is absolutely positioned, so it reserves no layout space on
       its own) - without this margin its spikes paint straight over the
       skill name text sitting immediately to the right in the flex row.
       Icon is always the first flex child here, so only the right side
       needs protecting. */
    margin-right: clamp(8px, 1.7vw, 16px);
}
.skill-icon-framed img {
    position: absolute;
    inset: 12%;
    width: 76%; height: 76%;
    object-fit: cover;
    border-radius: 6px;
}
.skill-icon-framed::after {
    content: '';
    position: absolute;
    inset: -18%;
    background-image: url('assets/skill-icon-frame.png');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: center;
    pointer-events: none;
}
.skill-text { display: flex; flex-direction: column; gap: 1px; }
.skill-name { font-family: 'Cinzel', serif; font-weight: 600; font-size: clamp(0.64rem, 2vw, 0.82rem); color: var(--parchment); }
.skill-tag { font-size: clamp(0.48rem, 1.5vw, 0.62rem); color: var(--gold); font-family: 'Cinzel', serif; letter-spacing: 0.04em; }

/* Custom stylized tooltip, replacing the native browser title attribute -
   shows on hover AND focus, including on disabled buttons (the Passive
   slot is always disabled but still needs to be readable on hover). */
.skill-tooltip {
    position: absolute;
    bottom: calc(100% + 8px);
    left: 50%;
    transform: translateX(-50%) translateY(4px);
    min-width: 150px;
    max-width: 220px;
    background: linear-gradient(180deg, var(--wine-2), var(--wine));
    border: 1px solid var(--gold);
    border-radius: 5px;
    padding: 7px 10px;
    box-shadow: 0 10px 24px -8px rgba(0,0,0,0.8);
    z-index: 20;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 120ms ease, transform 120ms ease;
    text-align: left;
    white-space: normal;
}
.skill-btn:hover ~ .skill-tooltip,
.skill-btn:focus-visible ~ .skill-tooltip {
    opacity: 1;
    visibility: visible;
    transform: translateX(-50%) translateY(0);
}
.skill-tooltip::after {
    content: '';
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    border: 5px solid transparent;
    border-top-color: var(--gold);
}
.skill-tooltip-line {
    font-family: 'EB Garamond', Garamond, serif;
    font-size: 0.85rem;
    color: var(--parchment);
    line-height: 1.4;
}
.skill-tooltip-line + .skill-tooltip-line { margin-top: 3px; }
.skill-tooltip-line.pierce {
    font-family: 'Cinzel', serif;
    font-size: 0.62rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--gold);
    margin-top: 5px;
    padding-top: 4px;
    border-top: 1px solid rgba(200, 162, 74, 0.3);
}

/* --- level-up celebration sequence (renderLevelUpAnimScreen) --- */
.levelup-anim {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 70vh;
    gap: 20px;
    padding: 24px;
}
.levelup-xp-track {
    width: min(90vw, 420px);
    height: 14px;
    border-radius: 7px;
    background: rgba(0,0,0,0.55);
    border: 1px solid var(--gold-dim);
    overflow: hidden;
}
.levelup-xp-fill {
    height: 100%;
    width: 0%;
    background: linear-gradient(90deg, var(--gold-dim), var(--gold));
    box-shadow: 0 0 12px rgba(200,162,74,0.7);
    transition: width 700ms ease-out;
}
.levelup-stage {
    position: relative;
    width: min(90vw, 420px);
    height: min(58vh, 480px);
    display: flex;
    align-items: flex-end;
    justify-content: center;
}
.levelup-aura {
    position: absolute;
    left: 50%; bottom: 4%;
    width: 92%;
    height: 72%;
    transform: translateX(-50%) scale(0.7);
    background-image: url('assets/level-up-aura.png');
    background-size: contain;
    background-repeat: no-repeat;
    background-position: bottom center;
    opacity: 0;
    animation: levelup-aura-in 900ms ease-out 500ms forwards, levelup-aura-pulse 2.4s ease-in-out 1400ms infinite;
}
@keyframes levelup-aura-in {
    from { opacity: 0; transform: translateX(-50%) scale(0.7); }
    to { opacity: 0.9; transform: translateX(-50%) scale(1); }
}
@keyframes levelup-aura-pulse {
    0%, 100% { opacity: 0.75; }
    50% { opacity: 1; }
}
.levelup-burst {
    position: absolute;
    left: 50%; top: 38%;
    width: 65%;
    transform: translate(-50%, -50%) scale(0.3);
    opacity: 0;
    animation: levelup-burst-flash 900ms ease-out 700ms forwards;
    pointer-events: none;
}
@keyframes levelup-burst-flash {
    0% { opacity: 0; transform: translate(-50%, -50%) scale(0.3); }
    35% { opacity: 1; transform: translate(-50%, -50%) scale(1.25); }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(1.7); }
}
.levelup-figure {
    position: relative;
    z-index: 2;
    height: 100%;
    max-height: 100%;
    width: auto;
    opacity: 0;
    filter: drop-shadow(0 14px 18px rgba(0,0,0,0.6));
    animation: levelup-figure-in 700ms ease-out 200ms forwards;
}
@keyframes levelup-figure-in {
    from { opacity: 0; transform: translateY(24px) scale(0.92); }
    to { opacity: 1; transform: translateY(0) scale(1); }
}
.levelup-banner {
    position: absolute;
    left: 50%; top: 4%;
    z-index: 3;
    transform: translate(-50%, -30%) scale(0.6);
    font-family: 'Pirata One', 'Cinzel', serif;
    font-size: clamp(1.8rem, 6vw, 2.8rem);
    letter-spacing: 0.06em;
    color: var(--gold);
    text-shadow: 0 0 20px rgba(200,162,74,0.8), 0 4px 12px rgba(0,0,0,0.8);
    opacity: 0;
    animation: levelup-banner-in 600ms cubic-bezier(.2,1.5,.4,1) 1300ms forwards;
    white-space: nowrap;
}
@keyframes levelup-banner-in {
    0% { opacity: 0; transform: translate(-50%, -30%) scale(0.5); }
    70% { opacity: 1; transform: translate(-50%, -30%) scale(1.15); }
    100% { opacity: 1; transform: translate(-50%, -30%) scale(1); }
}
.levelup-sublabel {
    position: absolute;
    left: 50%; top: 15%;
    z-index: 3;
    transform: translateX(-50%);
    font-family: 'Cinzel', serif;
    font-size: 0.9rem;
    letter-spacing: 0.1em;
    color: var(--parchment-dim);
    text-transform: uppercase;
    opacity: 0;
    animation: levelup-sublabel-in 500ms ease-out 1700ms forwards;
}
@keyframes levelup-sublabel-in {
    from { opacity: 0; transform: translateX(-50%) translateY(-6px); }
    to { opacity: 1; transform: translateX(-50%) translateY(0); }
}
/* --- game over --- */
.game-over-overlay {
    position: absolute;
    inset: 0;
    z-index: 9;
    background: rgba(5, 2, 8, 0.88);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 18px;
}
.game-over-title {
    font-family: 'Pirata One', 'Cinzel', serif;
    font-weight: 400;
    font-size: clamp(2rem, 6vw, 3rem);
    letter-spacing: 0.08em;
    color: var(--blood-bright);
    text-shadow: 0 0 20px rgba(224, 53, 95, 0.6);
}
.game-over-overlay.win .game-over-title { color: var(--gold); text-shadow: 0 0 20px rgba(200, 162, 74, 0.6); }
.restart-btn {
    background: none;
    border: 22px solid transparent;
    border-image: url('assets/btn-normal.png') 90 150 fill / 22px 36px / 0 stretch;
    color: var(--parchment);
    padding: 4px 8px;
    cursor: pointer;
    font-family: 'Cinzel', serif;
    font-size: 0.85rem;
    letter-spacing: 0.03em;
}
.restart-btn:hover { border-image-source: url('assets/btn-hover.png'); transform: translateY(-1px); }
.restart-btn:active { border-image-source: url('assets/btn-pressed.png'); transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
    .skill-btn { transition: none; }
    .figure { animation: none; }
    .figure.is-hit, .figure.is-hit img { animation: none; }
    .dmg-float { animation: none; opacity: 1; transform: translate(-50%, -40%); }
    .combatant.targetable .figure::after { animation: none; opacity: 0.5; }
}

/* --- small screens: stack the frame more compactly --- */
@media (max-width: 640px) {
    .meter-label { font-size: 0.5rem; }
    .skill-text .skill-tag { display: none; }
    .player-nameplate .meter-label { font-size: 0.6rem; }
    .player-nameplate .meter.hp { height: 10px; }
}
