/* ==========================================================
   RESET & BASE
========================================================== */

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    background: #0a0a0a;
    font-family: 'Nunito', sans-serif;
    height: 100vh;
    overflow: hidden;
    user-select: none;
}

/* ==========================================================
   START SCREEN -- index.html
========================================================== */

#startScreen {
    position: fixed;
    inset: 0;
    overflow-y: auto;   /* safety-net fallback only -- layout below is sized to avoid needing this */
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 3vh 20px;
    background:
        linear-gradient(rgba(198, 100, 255, 0), rgba(95, 14, 141, 0.795)),
        url('images/backgrounds/background.webp') center/cover no-repeat;
}

.start-box {
    background: linear-gradient(to bottom, #dd9ef7a8 0%, #66008fa8 100%);
    border: 4px solid #0a0a0a;
    box-shadow: 8px 8px 0 #66008fa8;
    padding: clamp(20px, 4vh, 48px) clamp(24px, 5vw, 64px) clamp(22px, 4.2vh, 50px);
    width: 100%;
    /* Grows with the viewport (both vw and vh play in, via the vh-driven
       padding/font clamps below) up to a generous cap, instead of sitting
       at a small fixed width regardless of how much screen is available. */
    max-width: clamp(620px, 64vw, 1240px);
    max-height: 94vh;
    overflow-y: auto;    /* fallback if a very short viewport still can't fit it all */
    text-align: center;
}

.start-header {
    margin-bottom: clamp(10px, 2.4vh, 26px);
}

.start-title {
    font-family: 'Press Start 2P', monospace;
    font-size: clamp(1.1rem, 4vh, 2.2rem);
    color: #ffffff;
    text-shadow: 3px 3px 0 #0a0a0a;
    letter-spacing: 2px;
    margin-bottom: 8px;
}

.start-subtitle {
    font-family: 'Press Start 2P', monospace;
    font-size: clamp(0.5rem, 1.1vh, 0.7rem);
    color: #ffffff;
    letter-spacing: 2px;
}

/* --- Two-column grid: UNIT on the left, HERO/NAME/DIFFICULTY on the right ---
   Keeps the box from growing tall by putting the two biggest chunks of
   content (unit buttons, hero avatars) side by side instead of stacked. */

.start-grid {
    display: flex;
    gap: clamp(16px, 5vw, 48px);
    text-align: left;
    margin-bottom: clamp(10px, 2.4vh, 22px);
}

.start-col {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.start-section {
    text-align: left;
    margin-bottom: clamp(10px, 2.4vh, 22px);
}

.start-section-last {
    margin-bottom: 0;
}

.start-section-label {
    font-family: 'Press Start 2P', monospace;
    font-size: clamp(0.45rem, 1vw, 0.6rem);
    color: #ffffff;
    letter-spacing: 1px;
    margin-bottom: 8px;
    padding-bottom: 5px;
    border-bottom: 2px solid #ffffff;
}

/* --- Year dropdown --- */

.year-select-row {
    margin-bottom: 8px;
}

.year-dropdown {
    font-family: 'Press Start 2P', monospace;
    font-size: clamp(0.45rem, 1vw, 0.65rem);
    background: #ffffff;
    color: #0a0a0a;
    border: 3px solid #0a0a0a;
    padding: clamp(8px, 1.4vh, 14px) clamp(12px, 1.6vw, 18px);
    cursor: pointer;
    letter-spacing: 0.5px;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%230a0a0a' d='M0 0l6 8 6-8z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
    padding-right: 32px;
    width: 100%;
}

.year-dropdown:focus {
    outline: 3px solid #8300c0;
    outline-offset: 2px;
}

/* --- Unit buttons row --- */

.unit-btn-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 2px;
}

/* Locked (not-yet-created) unit -- visually present but disabled */
.option-btn.unit-locked {
    opacity: 0.35;
    cursor: not-allowed;
    border-style: dashed;
}

/* --- Generic option row (unit buttons) --- */

.option-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

.option-btn {
    font-family: 'Press Start 2P', monospace;
    font-size: clamp(0.4rem, 0.85vw, 0.55rem);
    background: #ffffff;
    color: #0a0a0a;
    border: 2px solid #0a0a0a;
    padding: clamp(6px, 1.2vh, 11px) clamp(8px, 1.2vw, 15px);
    cursor: pointer;
    letter-spacing: 0.5px;
    line-height: 1.6;
    transition: background 0.12s, color 0.12s, transform 0.1s;
}

.option-btn:hover:not(:disabled) {
    background: #0a0a0a;
    color: #e8e0c8;
}

.option-btn.selected {
    background: #8300c0;
    color: #ffffff;
    border-color: #8300c0;
    box-shadow: 3px 3px 0 #0a0a0a;
    transform: translate(-1px, -1px);
}

/* --- Hero avatar row: portrait crop ---
   Shows just the top portion of the sprite (head/face area).
   object-fit:cover + object-position:top crops from the top
   down, so whatever is at the top of the sprite image (the
   character's head) fills the square. */

.hero-row {
    gap: clamp(6px, 1.2vw, 12px);
    flex-wrap: wrap;
}

.hero-btn {
    background: #ffffff;
    border: 3px solid #0a0a0a;
    padding: 0;
    width: clamp(46px, 6vw, 76px);
    height: clamp(46px, 6vw, 76px);
    cursor: pointer;
    overflow: hidden;    /* clips the image to the button's square */
    display: block;
    transition: transform 0.1s, border-color 0.12s, box-shadow 0.12s;
}

.hero-btn img {
    width: 100%;
    height: 160%;          /* taller than the button so only the top portion shows */
    object-fit: cover;
    object-position: top;  /* anchor to the top edge -- shows the head of the sprite */
    image-rendering: pixelated;
    display: block;
}

.hero-btn:hover {
    transform: translateY(-2px);
    box-shadow: 3px 3px 0 #0a0a0a;
}

.hero-btn.selected {
    border-color: #8300c0;
    box-shadow: 0 0 0 3px #8300c0, 3px 3px 0 #0a0a0a;
    transform: translate(-1px, -1px);
}

/* --- Difficulty: three buttons in a row, labels only --- */

.diff-row {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
}

.diff-btn {
    font-family: 'Press Start 2P', monospace;
    font-size: clamp(0.42rem, 0.9vw, 0.6rem);
    background: #ffffff;
    color: #0a0a0a;
    border: 2px solid #0a0a0a;
    padding: clamp(8px, 1.6vh, 14px) clamp(6px, 1vw, 12px);
    cursor: pointer;
    letter-spacing: 0.5px;
    flex: 1;               /* equal width within the row */
    text-align: center;
    transition: background 0.12s, color 0.12s, transform 0.1s;
}

.diff-btn:hover {
    background: #0a0a0a;
    color: #e8e0c8;
}

.diff-btn.selected {
    background: #8300c0;
    color: #ffffff;
    border-color: #8300c0;
    box-shadow: 3px 3px 0 #0a0a0a;
    transform: translate(-1px, -1px);
}

/* --- Begin button + hint --- */

.start-btn {
    font-family: 'Press Start 2P', monospace;
    font-size: clamp(0.55rem, 1.3vw, 0.8rem);
    background: #8300c0;
    color: #ffffff;
    border: 3px solid #0a0a0a;
    padding: clamp(11px, 2.2vh, 18px) clamp(26px, 4vw, 46px);
    cursor: pointer;
    letter-spacing: 1px;
    box-shadow: 4px 4px 0 #0a0a0a;
    transition: background 0.12s, transform 0.1s;
    margin-top: 4px;
}

.start-btn:hover:not(:disabled) {
    background: #5c0085;
    transform: translate(-1px, -1px);
    box-shadow: 5px 5px 0 #0a0a0a;
}

.start-btn:disabled {
    background: #cccccc;
    color: #888888;
    border-color: #999999;
    box-shadow: 4px 4px 0 #66666688;
    cursor: not-allowed;
}

.start-hint {
    font-family: 'Press Start 2P', monospace;
    font-size: clamp(0.35rem, 0.75vw, 0.5rem);
    color: #888;
    margin-top: 10px;
    letter-spacing: 0.5px;
    line-height: 1.6;
}

/* --- Narrow viewports: drop back to a single stacked column --- */
@media (max-width: 520px) {
    .start-grid {
        flex-direction: column;
    }
}

/* ==========================================================
   ARENA -- two-tone Pokemon-style split
========================================================== */

#arena {
    position: relative;
    width: 100vw;
    height: 100vh;
    display: flex;
    flex-direction: column;
    /* Single source of truth for the boss sprite's rendered size (it's
       square, so this is both its height and width). Both #bossSprite
       and #bossBubble reference this instead of each hardcoding the
       same clamp() separately -- keeps their edges locked together. */
    --boss-sprite-size: clamp(250px, 50vh, 550px);
    /* ^ this is the single source of truth for boss sprite size now --
       change it here only, both #bossSprite and #bossBubble read it */
    /* Space reserved on the right for #missCounter (its right:28px
       offset + its own rendered width) so #bossBubble's right edge
       never grows underneath it. */
    --miss-counter-reserve: 190px;
    /* Single source of truth for #typingUI's right offset -- reused in
       its width calc below so its left edge can be kept on-screen. */
    --typing-ui-right: clamp(180px, 22vw, 320px);
    /* Minimum breathing room to always leave on the left edge of the
       viewport, so #typingUI can never overflow/hang off-screen. */
    --screen-edge-margin: 24px;

    /* --- Vertical budget split between #bossBubble and #typingUI ---
       Previously both had independent, fixed max-heights (36vh / 39vh)
       with no relationship to each other. Boss dialogue is normally
       much shorter than the full sentence/paragraph the hero has to
       type, so #bossBubble was getting more vertical weight than it
       needs -- and whatever it didn't use just became dead space,
       instead of being handed to #typingUI (which actually needs it
       for long paragraphs). Now #typingUI's max-height is *derived*
       from the boss bubble's budget below, so the gap between the two
       stays locked to --bubble-gap regardless of how tall either one's
       actual content is. */
    --boss-bubble-top:    12vh;
    --boss-bubble-max-h:  26vh;   /* was 36vh -- boss dialogue needs less room */
    --bubble-gap:         24px;   /* same breathing room as #missCounter's top offset */
    --typing-ui-bottom:   5vh;
}

#arenaTop {
    position: absolute;
    top: 0; left: 0; right: 0;
    height: 70%;
    background: linear-gradient(rgba(198, 100, 255, 0), #2400388a), url('images/backgrounds/wall.webp') center bottom / cover no-repeat;
    border-bottom: 4px solid #8300c0;
    z-index: 0;
}

#arenaBottom {
    position: absolute;
    bottom: 0; left: 0; right: 0;
    height: 30%;
    background: linear-gradient(#2400388a, #00000000), url('images/backgrounds/floor.webp') center top / cover no-repeat;
    filter: brightness(0.6);
    z-index: 0;
}

/* ==========================================================
   DUNGEON / DIFFICULTY BADGES -- top centre
========================================================== */

#dungeonBadge {
    position: absolute;
    top: 24px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 10;
    font-family: 'Press Start 2P', monospace;
    font-size: 0.55rem;
    color: #ffffff;
    background: #8300c0;
    border: 3px solid #0a0a0a;
    box-shadow: 4px 4px 0 #66008fa8;
    padding: 8px 16px;
    letter-spacing: 1px;
    white-space: nowrap;
}

#diffBadge {
    position: absolute;
    top: 64px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 10;
    font-family: 'Press Start 2P', monospace;
    font-size: 0.4rem;
    color: #0a0a0a;
    background: #ffffff;
    border: 2px solid #0a0a0a;
    padding: 4px 10px;
    letter-spacing: 1px;
    white-space: nowrap;
}

/* ==========================================================
   BOSS INFO -- top left card
========================================================== */

#bossInfo {
    position: absolute;
    top: 24px;
    left: 28px;
    z-index: 10;
    background: #ffffff;
    border: 3px solid #0a0a0a;
    box-shadow: 4px 4px 0 #66008fa8;
    padding: 10px 14px 8px 14px;
    min-width: 220px;
}

#bossLabel {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: 6px;
}

#bossName {
    font-family: 'Press Start 2P', monospace;
    font-size: 0.65rem;
    color: #0a0a0a;
    letter-spacing: 0.5px;
}

#bossLevel {
    font-family: 'Press Start 2P', monospace;
    font-size: 0.45rem;
    color: #444;
}

#bossHPRow {
    display: flex;
    align-items: center;
    gap: 6px;
}

.hp-label {
    font-family: 'Press Start 2P', monospace;
    font-size: 0.5rem;
    color: #0a0a0a;
    font-weight: bold;
}

#bossHPBar {
    flex: 1;
    height: 8px;
    background: #aaa;
    border: 2px solid #0a0a0a;
}

#bossHP {
    height: 100%;
    width: 100%;
    background: #19c8fd;
    transition: width 0.6s ease, background 0.4s ease;
}

/* Red-zone (<=25% HP) bar pulse, applied via JS class on #bossHP */
#bossHP.hp-critical {
    animation: hpCriticalPulse 0.9s ease-in-out infinite;
}

@keyframes hpCriticalPulse {
    0%, 100% { filter: brightness(1); }
    50%       { filter: brightness(1.5); }
}

#bossHPNumbers {
    font-family: 'Press Start 2P', monospace;
    font-size: 0.45rem;
    color: #555;
    text-align: right;
    margin-top: 4px;
}

/* ==========================================================
   MISS COUNTER -- top right
========================================================== */

#missCounter {
    position: absolute;
    top: 24px;
    right: 28px;
    z-index: 10;
    background: #ffffff;
    border: 3px solid #0a0a0a;
    box-shadow: 4px 4px 0 #66008fa8;
    padding: 10px 16px;
    text-align: center;
}

.miss-label {
    font-family: 'Press Start 2P', monospace;
    font-size: 0.55rem;
    color: #0a0a0a;
    margin-bottom: 8px;
    letter-spacing: 1px;
}

#missPips {
    display: flex;
    gap: 8px;
    justify-content: center;
}

.miss-pip {
    width: 18px;
    height: 18px;
    border: 3px solid #0a0a0a;
    background: #ffffff;
    transition: background 0.2s;
}

.miss-pip.charged {
    background: #ff22aa;
}

/* ==========================================================
   BOSS SPRITE + SPEECH BUBBLE
========================================================== */

#bossBubble {
    position: absolute;
    top: var(--boss-bubble-top);
    /* left offset = sprite's own left (2vw) + sprite's rendered width
       (--boss-sprite-size, shared with #bossSprite below) + a fixed gap */
    left: calc(2vw + var(--boss-sprite-size) + 32px);
    /* Width is the smallest of: a comfortable vw-based size, a hard
       pixel cap, OR whatever space is actually left between the
       bubble's left edge and #missCounter on the right -- so on
       narrower/wider windows the bubble shrinks instead of sliding
       under the miss counter. */
    width: min(
        56vw,
        820px,
        calc(100vw - (2vw + var(--boss-sprite-size) + 32px) - var(--miss-counter-reserve))
    );
    height: none;
    max-height: var(--boss-bubble-max-h);  /* fixed, not just capped -- JS shrinks bossText to fit this */
    min-height: 0;            /* required so the flex child below can be correctly measured/shrunk */
    display: flex;
    flex-direction: column;
}

#bossSprite {
    position: absolute;
    left: 2vw;
    /* Boss art is now square (1200x1200, 1:1 ratio) instead of the old
       portrait crop. A square sprite is much wider than the old portrait
       one at the same height, so it's sized down and bottom-anchored
       (instead of top-anchored) so it always "stands" right at the
       arenaTop/arenaBottom divider line regardless of its rendered size,
       and stays clear of #bossBubble's left edge above. */
    bottom: 30vh;
    height: var(--boss-sprite-size);
    width: auto;
    opacity: 1;
    animation: bossFloat 3s ease-in-out infinite;
    transform-origin: center bottom;
}

@keyframes bossFloat {
    0%   { transform: translateY(0); }
    50%  { transform: translateY(-8px); }
    100% { transform: translateY(0); }
}

/* Red zone struggle -- layers on top of the float, applied via JS class */
#bossSprite.boss-struggling {
    animation: bossFloat 3s ease-in-out infinite, bossStruggle 0.35s ease-in-out infinite;
    filter: drop-shadow(0 8px 16px rgba(58, 0, 92, 0.637)) drop-shadow(0 0 10px rgba(255, 0, 40, 0.7));
}

@keyframes bossStruggle {
    0%, 100% { transform: translateX(0) rotate(0deg); }
    25%       { transform: translateX(-3px) rotate(-1.2deg); }
    75%       { transform: translateX(3px) rotate(1.2deg); }
}

/* Boss death -- spin and shrink to the point it's standing on */
#bossSprite.boss-dying {
    animation: bossDeathSpin 0.7s cubic-bezier(0.55, 0, 0.85, 0.35) forwards;
}

@keyframes bossDeathSpin {
    0%   { transform: scale(1) rotate(0deg);     opacity: 1; }
    70%  { transform: scale(0.3) rotate(540deg); opacity: 1; }
    100% { transform: scale(0) rotate(720deg);   opacity: 0; }
}

/* Ghost replacement -- grows out from the same point the sprite shrunk into,
   then settles into a continuous fade-bob idle loop */
#bossSprite.boss-ghost {
    animation: ghostAppear 0.5s ease-out forwards;
}

#bossSprite.boss-ghost.ghost-idle {
    animation: ghostIdleBob 2.4s ease-in-out infinite;
}

@keyframes ghostAppear {
    0%   { transform: scale(0);    opacity: 0; }
    60%  { transform: scale(1.15); opacity: 0.9; }
    100% { transform: scale(1);    opacity: 0.85; }
}

@keyframes ghostIdleBob {
    0%, 100% { transform: translateY(0)     scale(1);    opacity: 0.85; }
    50%       { transform: translateY(-10px) scale(1.03); opacity: 0.55; }
}

/* ==========================================================
   SPEECH BUBBLES -- pixel comic style
========================================================== */

.speech-bubble {
    background: #ffffff;
    border: 3px solid #0a0a0a;
    box-shadow: 4px 4px 0 #66008fa8;
    padding: 14px 16px;
    position: relative;
}

/* Tail pointing left (from boss) */
.bubble-right::before {
    content: '';
    position: absolute;
    left: -26px;
    top: 24px;
    border: 13px solid transparent;
    border-right-color: #0a0a0a;
}

.bubble-right::after {
    content: '';
    position: absolute;
    left: -17px;
    top: 27px;
    border: 10px solid transparent;
    border-right-color: #ffffff;
}

/* Tail pointing right (from hero) */
.bubble-left::before {
    content: '';
    position: absolute;
    right: -26px;
    top: 24px;
    border: 13px solid transparent;
    border-left-color: #0a0a0a;
}

.bubble-left::after {
    content: '';
    position: absolute;
    right: -17px;
    top: 27px;
    border: 10px solid transparent;
    border-left-color: #ffffff;
}

#bossText {
    font-family: 'Press Start 2P', monospace;
    color: #0a0a0a;
    line-height: 1.8;
    letter-spacing: 0.5px;
    word-break: break-word;
    overflow: hidden;
    flex: 1 1 auto;
}

/* ==========================================================
   BOSS POWERS -- applied to BOTH .speech-bubble elements
   (#bossBubble and #heroBubble) by game.js, so any power a
   boss uses disrupts the player's view of both what they need
   to type and what they've typed so far.
========================================================== */

/* Dungeon 2 (Goblin): quick violent shake */
/* Dungeon 2 (Goblin): aggressive, disorienting shake -- bigger
   displacement than a simple wobble, with a touch of skew so it
   reads as a genuine hit rather than a gentle nudge. */
.speech-bubble.power-shake {
    animation: bubbleShake 0.85s cubic-bezier(.36,.07,.19,.97);
}

@keyframes bubbleShake {
    0%   { transform: translate(0, 0) rotate(0deg) skew(0deg); }
    8%   { transform: translate(-14px, 6px) rotate(-3deg) skew(-2deg); }
    16%  { transform: translate(13px, -7px) rotate(3deg) skew(2deg); }
    24%  { transform: translate(-12px, 5px) rotate(-2.5deg) skew(-1.5deg); }
    32%  { transform: translate(12px, -6px) rotate(2.5deg) skew(1.5deg); }
    40%  { transform: translate(-10px, 5px) rotate(-2deg); }
    48%  { transform: translate(10px, -4px) rotate(2deg); }
    56%  { transform: translate(-7px, 3px) rotate(-1.2deg); }
    64%  { transform: translate(7px, -3px) rotate(1.2deg); }
    72%  { transform: translate(-5px, 2px); }
    80%  { transform: translate(5px, -2px); }
    88%  { transform: translate(-2px, 1px); }
    96%  { transform: translate(2px, -1px); }
    100% { transform: translate(0, 0) rotate(0deg) skew(0deg); }
}

/* Dungeon 3 (Boo): ghostly fade -- the bubble itself dims and
   wavers in and out like a spectral flicker, rather than a flat
   pulse-to-black overlay. Animates the bubble's own opacity and
   a slight scale/blur "breath" instead of a separate stacked
   element, which avoids any seam/edge artefacts against the
   border (previously caused by a positioned ::after overlay not
   fully covering the border's inner edge). */
.speech-bubble.power-blackout {
    animation: ghostlyFlicker 2.6s ease-in-out;
}

@keyframes ghostlyFlicker {
    0%   { opacity: 1;    filter: blur(0); }
    12%  { opacity: 0.15; filter: blur(1px); }
    20%  { opacity: 0.55; filter: blur(0.5px); }
    30%  { opacity: 0.1;  filter: blur(1.5px); }
    42%  { opacity: 0.4;  filter: blur(1px); }
    55%  { opacity: 0.08; filter: blur(2px); }
    68%  { opacity: 0.5;  filter: blur(0.5px); }
    80%  { opacity: 0.2;  filter: blur(1px); }
    100% { opacity: 1;    filter: blur(0); }
}

/* Dungeon 4 (Dragon): rapid jittery shudder, layered with shake/blackout
   when multiple powers fire close together */
.speech-bubble.power-jitter {
    animation: bubbleJitter 0.1s linear infinite;
}

@keyframes bubbleJitter {
    0%   { transform: translate(0, 0); }
    25%  { transform: translate(2px, -1px); }
    50%  { transform: translate(-2px, 1px); }
    75%  { transform: translate(1px, 1px); }
    100% { transform: translate(0, 0); }
}

/* Tanuki: belly-drum rhythmic pulse -- a slow, deliberate "thump...thump"
   double-beat, distinct from bubbleJitter's rapid shudder and
   screenShake's single jolt. Meant to feel like a drum being struck twice. */
.speech-bubble.power-drum {
    animation: drumPulse 1.6s ease-in-out;
}

@keyframes drumPulse {
    0%   { transform: scale(1)     translateY(0); }
    8%   { transform: scale(1.18)  translateY(-4px); }
    16%  { transform: scale(1)     translateY(0); }
    24%  { transform: scale(1.18)  translateY(-4px); }
    32%  { transform: scale(1)     translateY(0); }
    40%  { transform: scale(1.1)   translateY(-2px); }
    48%  { transform: scale(1)     translateY(0); }
    56%  { transform: scale(1.1)   translateY(-2px); }
    64%  { transform: scale(1)     translateY(0); }
    100% { transform: scale(1)     translateY(0); }
}

/* ==========================================================
   SCORE FLASH -- centre screen
========================================================== */

#scoreFlash {
    position: fixed;
    top: 42%;
    left: 50%;
    transform: translateX(-50%);
    font-family: 'Press Start 2P', monospace;
    font-size: 1.4rem;
    color: #f542b0;
    text-shadow: 3px 3px 0 #0a0a0a;
    pointer-events: none;
    opacity: 0;
    z-index: 20;
    white-space: nowrap;
}

@keyframes scoreFlashAnim {
    0%   { opacity: 1; transform: translateX(-50%) scale(1.3) translateY(0); }
    60%  { opacity: 1; transform: translateX(-50%) scale(1)   translateY(-10px); }
    100% { opacity: 0; transform: translateX(-50%) scale(0.9) translateY(-20px); }
}

/* ==========================================================
   COMBO COUNTER

   FIX: comboDisplay used to sit in normal flex flow below heroBubble
   inside #typingUI (a bottom-anchored column). When comboNumber or
   comboDamageBonus content changed, the row's height changed, which
   shifted the whole column upward to keep its bottom edge anchored --
   pushing the typing box up.

   Fix: give the row a fixed height so its own content can never resize
   it, and let comboDamageBonus reserve its line-height even when empty.
========================================================== */

#comboDisplay {
    display: flex;
    align-items: baseline;
    gap: 6px;
    justify-content: center;
    height: 34px;        /* fixed -- content changes no longer resize this row */
    margin-top: 16px;
    opacity: 0;
    transition: opacity 0.3s;
}

#comboDisplay.visible { opacity: 1; }

#comboNumber {
    font-family: 'Press Start 2P', monospace;
    font-size: 1.6rem;
    color: #00fff2;
    text-shadow: 3px 3px 0 #000000;
    line-height: 1;
}

.combo-label {
    font-family: 'Press Start 2P', monospace;
    font-size: 0.8rem;
    color: #00fff2;
    text-shadow: 2px 2px 0 #0a0a0a;
}

#comboDamageBonus {
    font-family: 'Press Start 2P', monospace;
    font-size: 0.55rem;
    color: #870099;
    background: #ffffff;
    border: 2px solid #0a0a0a;
    padding: 4px 4px;
    margin-left: 4px;
    min-height: 1em;    /* reserve space so appear/disappear never changes row height */
}

/* Combo-break feedback flash, toggled briefly via JS */
#comboDisplay.combo-broken {
    filter: hue-rotate(180deg);
}

/* ==========================================================
   HERO AREA -- bottom right
========================================================== */

#heroPanel {
    position: absolute;
    right: 3vw;
    bottom: 3vh;
    display: flex;
    flex-direction: column-reverse;
    align-items: center;
    gap: 8px;
}

#heroSprite {
    width: clamp(150px, 20vw, 220px);
    animation: heroIdle 2s ease-in-out infinite;
    filter: drop-shadow(0 6px 12px #66008fa8);
    transform-origin: center bottom;
}

@keyframes heroIdle {
    0%   { transform: translateY(0); }
    50%  { transform: translateY(-4px); }
    100% { transform: translateY(0); }
}

/* Final heart -- sprite pulses red and struggles, Zelda low-health style.
   Mirrors the boss's struggle treatment: float + glow pulse + a small shake/wobble. */
#heroSprite.hero-critical {
    animation: heroIdle 2s ease-in-out infinite,
               heroCriticalPulse 0.9s ease-in-out infinite,
               heroStruggle 0.35s ease-in-out infinite;
}

@keyframes heroCriticalPulse {
    0%, 100% { filter: drop-shadow(0 6px 12px #66008fa8) drop-shadow(0 0 0px rgba(255,0,40,0)); }
    50%       { filter: drop-shadow(0 6px 12px #66008fa8) drop-shadow(0 0 14px rgba(255,0,40,0.9)); }
}

@keyframes heroStruggle {
    0%, 100% { transform: translateX(0) rotate(0deg); }
    25%       { transform: translateX(-3px) rotate(1.2deg); }
    75%       { transform: translateX(3px) rotate(-1.2deg); }
}

/* Hero death -- same spin/shrink treatment as the boss */
#heroSprite.hero-dying {
    animation: heroDeathSpin 0.7s cubic-bezier(0.55, 0, 0.85, 0.35) forwards;
}

@keyframes heroDeathSpin {
    0%   { transform: scale(1) rotate(0deg);      opacity: 1; }
    70%  { transform: scale(0.3) rotate(-540deg); opacity: 1; }
    100% { transform: scale(0) rotate(-720deg);   opacity: 0; }
}

#heroSprite.hero-ghost {
    animation: ghostAppear 0.5s ease-out forwards;
}

#heroSprite.hero-ghost.ghost-idle {
    animation: ghostIdleBob 2.4s ease-in-out infinite;
}

#heroInfo {
    background: transparent;
    border: none;
    box-shadow: none;
    padding: 0;
    text-align: center;
    min-width: 140px;
}

#heroName {
    font-family: 'Press Start 2P', monospace;
    font-size: clamp(0.7rem, 1.5vw, 1rem);
    color: #ffffff;
    text-shadow: 
    -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000, 
    2px 2px 0 #67008fee;
    margin-bottom: 6px;
    overflow: hidden;
    white-space: nowrap;
    width: 100%;
    text-align:center;
}

#heartsDisplay {
    display: flex;
    justify-content: center;
    gap: 4px;
}

.heart img {
    width: 28px;
    height: 28px;
    image-rendering: pixelated;
}

.heart {
    font-size: 1.4rem;
    line-height: 1;
    transition: all 0.3s ease;
    filter: drop-shadow(2px 2px 0 #0a0a0a);
}

.heart.full  { color: #ff16f3; }
.heart.half  { color: #ff16f3; opacity: 0.5; }
.heart.empty { color: #d3d3d3; }

/* Final heart pulse/glow -- applied via JS when heroHearts <= 1 (last half-heart) */
.heart.heart-critical img {
    animation: heartCriticalPulse 0.7s ease-in-out infinite;
    filter: drop-shadow(0 0 6px rgba(255, 0, 60, 0.9));
}

@keyframes heartCriticalPulse {
    0%, 100% { transform: scale(1); }
    50%       { transform: scale(1.25); }
}

/* ==========================================================
   HERO SPEECH BUBBLE -- typing area
========================================================== */

#typingUI {
    position: absolute;
    bottom: var(--typing-ui-bottom);
    right: var(--typing-ui-right);
    display: flex;
    flex-direction: column;
    /* Width is the smallest of: a comfortable vw-based size, a hard
       pixel cap, enough space before the left edge of the viewport,
       OR enough space before #bossSprite's right edge.
       That last one matters because #bossSprite is bottom-anchored at
       70vh-from-top (fixed) while #typingUI's top edge can rise close
       to #bossBubble's bottom edge when both are near max-height -- so
       there's a band where the two can share vertical space. Without
       this term, a wide #typingUI could clip over the boss in that
       band (most visible on dungeons 3-4, where extra powers nudge the
       bubbles around inside that already-tight margin). */
    width: min(
        70vw,
        900px,
        calc(100vw - var(--typing-ui-right) - var(--screen-edge-margin)),
        calc(98vw - var(--typing-ui-right) - var(--boss-sprite-size) - 40px)
    );
    height: auto;
    /* Default is shrink-to-content (right for dungeons 1-2, words/combos --
       a tiny prompt shouldn't get a giant box). For sentences/paragraphs
       tiers, game.js explicitly sets this to a fixed pixel height (see
       buildDisplayDOM) so the box consistently uses its full available
       budget instead of reshrinking between prompts -- that's the tier
       where a shrink-wrapped box was actually a problem. max-height still
       applies as a ceiling in both cases. */
    max-height: calc(
        100vh - var(--boss-bubble-top) - var(--boss-bubble-max-h)
        - var(--bubble-gap) - var(--typing-ui-bottom)
    );
}

#heroBubble {
    width: 100%;
    flex: 1 1 auto;   /* fills available space in #typingUI, leaving room for #comboDisplay below it */
    min-height: 0;     /* allows flex child to shrink below content size, required for the height:100% chain inside to work */
    display: flex;
    flex-direction: column;
}

/* Real-time letter display.
   Letter colour states (.lt-correct / .lt-wrong / .lt-pending / .lt-extra)
   plus the typing caret are injected at runtime by game.js, in the same
   <style> block that defines the caret's keyframes -- that's the single
   source of truth for letter colours now, so this file doesn't duplicate
   them. (Old .letter-correct / .letter-wrong / .letter-pending / etc.
   rules that used to live here were dead code from a pre-rewrite version
   and have been removed.)

   On long prompts (paragraphs), this becomes the scrollable element so
   the bubble itself stays capped by #heroBubble's max-height instead of
   growing tall enough to overlap #bossBubble. Scrolling here doesn't
   affect the caret's getBoundingClientRect() math in game.js -- it still
   measures the actual letter span being typed, which stays in view since
   that's the word the player is actively on. */
#heroTypingDisplay {
    font-family: 'Courier New', monospace;
    font-size: clamp(1rem, 2.2vw, 2rem);
    line-height: 1.7;
    color: #0a0a0a;
    flex: 1 1 auto;
    min-height: 0;   /* required for this to actually shrink/fill correctly within #heroBubble, now that #typingUI has a definite (not auto) height above it */
    overflow: hidden;
    word-break: break-word;
    letter-spacing: 1px;
    position: relative;   /* anchors the absolutely-positioned caret correctly */
}

/* Hidden actual input */
#typingInput {
    position: absolute;
    opacity: 0;
    pointer-events: none;
    width: 1px;
    height: 1px;
}

/* ==========================================================
   END OVERLAYS
========================================================== */

.end-overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.85);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 100;
}

.end-box {
    background: #ffffff;
    border: 4px solid #0a0a0a;
    box-shadow: 8px 8px 0 #66008fa8;
    padding: 40px 48px;
    text-align: center;
    min-width: 340px;
    position: relative;
    z-index: 2;   /* sits above the falling confetti */
}

/* ==========================================================
   CONFETTI -- victory celebration. Pieces are built in JS as
   <img> elements (see spawnConfetti/CONFETTI_IMAGES in game.js)
   each piece just needs the .confetti-piece class + inline
   --fall-duration/--fall-delay/--drift/--rotate custom props.
========================================================== */

#confettiContainer {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: 1;
}

.confetti-piece {
    position: absolute;
    top: 42%;
    left: 50%;
    width: var(--piece-size, 24px);
    height: var(--piece-size, 24px);
    object-fit: contain;
    opacity: 0;
    animation:
        confettiPop   var(--pop-duration, 0.35s) cubic-bezier(0.15, 0.86, 0.2, 1) var(--fall-delay, 0s)  forwards,
        confettiFloat var(--float-duration, 3s)  linear                          var(--float-delay, 0.35s) forwards,
        confettiGlow  1s ease-in-out var(--fall-delay, 0s) infinite;
}

@keyframes confettiPop {
    0%   { opacity: 0; transform: translate(-50%, -50%) translate(0, 0) scale(0.2) rotate(0deg); }
    100% { opacity: 1; transform: translate(-50%, -50%) translate(var(--burst-x), var(--burst-y)) scale(1.15) rotate(calc(var(--rotate) * 0.2)); }
}

@keyframes confettiFloat {
    0%   { opacity: 1;   transform: translate(-50%, -50%) translate(var(--burst-x), var(--burst-y)) scale(1.1); }
    16%  { opacity: 1;   transform: translate(-50%, -50%) translate(calc(var(--burst-x) + var(--drift) * 0.15), calc(var(--burst-y) + 14vh)) scale(1)    rotate(calc(var(--rotate) * 0.3)); }
    34%  { opacity: 1;   transform: translate(-50%, -50%) translate(calc(var(--burst-x) - var(--drift) * 0.1),  calc(var(--burst-y) + 32vh)) scale(0.98) rotate(calc(var(--rotate) * 0.5)); }
    52%  { opacity: 1;   transform: translate(-50%, -50%) translate(calc(var(--burst-x) + var(--drift) * 0.3),  calc(var(--burst-y) + 52vh)) scale(0.96) rotate(calc(var(--rotate) * 0.65)); }
    70%  { opacity: 1;   transform: translate(-50%, -50%) translate(calc(var(--burst-x) + var(--drift) * 0.05), calc(var(--burst-y) + 74vh)) scale(0.94) rotate(calc(var(--rotate) * 0.8)); }
    88%  { opacity: 0.9; transform: translate(-50%, -50%) translate(calc(var(--burst-x) + var(--drift) * 0.4),  calc(var(--burst-y) + 100vh)) scale(0.9)  rotate(calc(var(--rotate) * 0.92)); }
    100% { opacity: 0;   transform: translate(-50%, -50%) translate(calc(var(--burst-x) + var(--drift)),        calc(var(--burst-y) + 130vh)) scale(0.85) rotate(var(--rotate)); }
}

@keyframes confettiGlow {
    0%, 100% {filter: drop-shadow(0 0 3px var(--glow-color, #fff)) brightness(1.1);}
    50% {filter: drop-shadow(0 0 9px var(--glow-color, #fff)) brightness(1.6);}
}

@keyframes confettiFall {
    0%   { opacity: 1;    transform: translate(0, 0)                          rotate(0deg); }
    25%  { opacity: 1;    transform: translate(calc(var(--drift, 0px) * 0.3), 27vh)  rotate(calc(var(--rotate, 360deg) * 0.25)); }
    50%  { opacity: 1;    transform: translate(calc(var(--drift, 0px) * 0.7), 55vh)  rotate(calc(var(--rotate, 360deg) * 0.55)); }
    75%  { opacity: 0.95; transform: translate(calc(var(--drift, 0px) * 0.9), 82vh)  rotate(calc(var(--rotate, 360deg) * 0.8)); }
    100% { opacity: 0.85; transform: translate(var(--drift, 0px), 110vh)             rotate(var(--rotate, 360deg)); }
}

/* ==========================================================
   STAR RATING -- 1-3 stars on the victory screen, based on
   accuracy. Built in JS as <img> elements (see renderStars in
   game.js) using images/ui/fullStar.png and emptyStar.png.
========================================================== */

.star-rating {
    display: flex;
    justify-content: center;
    gap: 6px;
    margin-bottom: 20px;
}

.star-rating .star {
    display: inline-block;
    width: 44px;
    height: 44px;
    object-fit: contain;
    opacity: 0;
    transform: scale(0);
    animation: starPop 0.45s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
}

.star-rating .star.star-dim { opacity: 1; animation: none; transform: scale(1); }

@keyframes starPop {
    0%   { opacity: 0; transform: scale(0) rotate(-20deg); }
    70%  { opacity: 1; transform: scale(1.3) rotate(8deg); }
    100% { opacity: 1; transform: scale(1) rotate(0deg); }
}

.end-subtitle {
    font-family: 'Press Start 2P', monospace;
    font-size: 0.5rem;
    color: #666;
    margin-bottom: 20px;
    line-height: 1.8;
}

.defeat-box .end-subtitle { color: #444; }

.end-title {
    font-family: 'Press Start 2P', monospace;
    font-size: 1.4rem;
    color: #0a0a0a;
    margin-bottom: 24px;
    letter-spacing: 2px;
}

.victory-box .end-title { color: #18d1ff; }
.defeat-box  .end-title { color: #ff2291; }

.end-stats {
    font-family: 'Press Start 2P', monospace;
    font-size: 0.45rem;
    color: #444;
    line-height: 2.5;
    margin-bottom: 28px;
}

.end-btn {
    font-family: 'Press Start 2P', monospace;
    font-size: 0.55rem;
    background: #0a0a0a;
    color: #e8e0c8;
    border: none;
    padding: 14px 28px;
    cursor: pointer;
    letter-spacing: 1px;
    transition: background 0.15s;
}

.end-btn:hover { background: #333; }

.victory-shake {
    animation: victoryShake 0.6s ease-out;
}

@keyframes victoryShake {
    0%, 100% { transform: translateX(0); }
    10%      { transform: translateX(-8px); }
    20%      { transform: translateX(8px); }
    30%      { transform: translateX(-6px); }
    40%      { transform: translateX(6px); }
    50%      { transform: translateX(-4px); }
    60%      { transform: translateX(4px); }
    70%      { transform: translateX(-2px); }
    80%      { transform: translateX(2px); }
}

.hit-shake {
    animation: hitShake 0.42s ease-out;
}

@keyframes hitShake {
    0%   { transform: translate(0, 0); }
    10%  { transform: translate(-6px, 3px); }
    20%  { transform: translate(5px, -4px); }
    32%  { transform: translate(-7px, 2px); }  /* ~110ms in -- lines up with the second splat hit */
    44%  { transform: translate(6px, -3px); }
    58%  { transform: translate(-4px, 2px); }
    72%  { transform: translate(3px, -2px); }
    86%  { transform: translate(-1px, 1px); }
    100% { transform: translate(0, 0); }
}

/* ==========================================================
   RATING OVERLAY FLASH
========================================================== */

#ratingOverlay {
    position: fixed;
    inset: 0;
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 50;
    pointer-events: none;
}

#ratingOverlay .rating-text {
    font-family: 'Press Start 2P', monospace;
    font-size: 2.5rem;
    text-shadow: 4px 4px 0 #0a0a0a;
    animation: ratingPop 0.8s ease forwards;
}

#splatOverlay {
    position: fixed;
    /* Oversized relative to the viewport (inset:0 would be exactly
       viewport-sized), because the animation rotates this element --
       rotating a shape that's exactly the size of the frame it's
       covering always pulls its corners inward and reveals gaps at
       the edges. Sizing it well past the viewport and centering it
       gives the rotation room to spare so it always fully covers the
       screen, at any of the rotation angles --splat-rot can produce. */
    top: -35vh;
    left: -35vw;
    width: 170vw;
    height: 170vh;
    z-index: 95;   /* above #ratingOverlay (50) and the hit-flash (90), below the victory/defeat modal (100) */
    pointer-events: none;
    opacity: 1;
    transform: scale(1) rotate(0deg);
    transform-origin: center center;
}

#splatOverlay.splat-hit {
    animation: splatFlash 0.32s cubic-bezier(0.15, 0.9, 0.25, 1) forwards;
}

@keyframes splatFlash {
    0%   { opacity: 0;    filter: brightness(1);    transform: scale(0.78) rotate(var(--splat-rot, 0deg)) scaleX(var(--splat-flip, 1)); }
    12%  { opacity: 1;    filter: brightness(3.5);  transform: scale(1.06) rotate(var(--splat-rot, 0deg)) scaleX(var(--splat-flip, 1)); }
    35%  { opacity: 0.9;  filter: brightness(1.15); transform: scale(1)    rotate(var(--splat-rot, 0deg)) scaleX(var(--splat-flip, 1)); }
    100% { opacity: 0;    filter: brightness(1);    transform: scale(1.02) rotate(var(--splat-rot, 0deg)) scaleX(var(--splat-flip, 1)); }
}

@keyframes ratingPop {
    0%   { transform: scale(1.6); opacity: 1; }
    60%  { transform: scale(1);   opacity: 1; }
    100% { transform: scale(0.9); opacity: 0; }
}

.rating-PERFECT { color: #ff23c8; }
.rating-GREAT   { color: #21deff; }
.rating-GOOD    { color: #bc12ff; }
.rating-OK      { color: #96ff0c; }
.rating-ALMOST  { color: #ff9500; }
.rating-MISS    { color: #eeff00; }

/* ==========================================================
   BOSS HIT / HERO HIT ANIMATIONS
========================================================== */

@keyframes bossHit {
    0%   { filter: brightness(1)   drop-shadow(0 8px 16px rgba(0,0,0,0.5)); }
    25%  { filter: brightness(4)   drop-shadow(0 0 20px red); transform: translateX(-8px) translateY(0); }
    50%  { filter: brightness(3)   drop-shadow(0 0 12px red); transform: translateX(8px) translateY(-8px); }
    75%  { filter: brightness(2)   drop-shadow(0 0 8px red);  transform: translateX(-4px) translateY(0); }
    100% { filter: brightness(1)   drop-shadow(0 8px 16px rgba(0,0,0,0.5)); transform: translateX(0); }
}

.boss-hit { animation: bossHit 0.5s ease !important; }

@keyframes heroHit {
    0%   { filter: brightness(1); }
    30%  { filter: brightness(3) drop-shadow(0 0 10px red); transform: translateX(8px); }
    60%  { filter: brightness(2) drop-shadow(0 0 6px red);  transform: translateX(-4px); }
    100% { filter: brightness(1); transform: translateX(0); }
}

.hero-hit { animation: heroHit 0.5s ease !important; }

/* Hero lunges toward the boss (boss stands to the left, hero to the
   right -- see #heroPanel) whenever it lands a hit, so combat doesn't
   read as one-sided reactions from the boss only. */
@keyframes heroAttack {
    0%   { transform: translateX(0) rotate(0deg); }
    35%  { transform: translateX(-22px) rotate(-8deg); }
    60%  { transform: translateX(-14px) rotate(-4deg); }
    100% { transform: translateX(0) rotate(0deg); }
}

.hero-attack { animation: heroAttack 0.35s ease-out !important; }

@keyframes heartLoss {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.4); }
    100% { transform: scale(1); }
}

.heart-losing { animation: heartLoss 0.4s ease; }

/* ==========================================================
   SCREEN FLASH
========================================================== */

#screenFlash {
    position: fixed;
    inset: 0;
    background: white;
    opacity: 0;
    pointer-events: none;
    z-index: 90;
}

@keyframes flashAnim {
    0%   { opacity: 0.8; }
    100% { opacity: 0; }
}

.do-flash { animation: flashAnim 0.4s ease forwards; }

/* ==========================================================
   UTILITY
========================================================== */

.hidden { display: none !important; }

#missPips.full-charge .miss-pip { background: #c0392b; }

@keyframes chargeShake {
    0%   { transform: translate(0, 0); }
    20%  { transform: translate(-4px, 2px); }
    40%  { transform: translate(4px, -2px); }
    60%  { transform: translate(-3px, 3px); }
    80%  { transform: translate(3px, -1px); }
    100% { transform: translate(0, 0); }
}

.boss-charging { animation: chargeShake 0.5s ease !important; }

.hero-name-input {
    font-family: 'Press Start 2P', monospace;
    font-size: clamp(0.45rem, 1vw, 0.65rem);

    width: 100%;
    max-width: none;

    padding: clamp(9px, 1.6vh, 14px);
    border: 3px solid #0a0a0a;
    background: white;
    color: #0a0a0a;
}

.hero-name-input:focus {
    outline: 3px solid #8300c0;
    outline-offset: 2px;
}
/* ==========================================================
   WALL DECORATIONS -- random graffiti tags / stickers on the
   wall behind the boss. Purely cosmetic: never intercepts
   clicks, sits behind the boss/bubbles/UI.
========================================================== */

#wallDecorLayer {
    position: absolute;
    inset: 0;
    z-index: 1;      /* above the wall background (0), below boss/UI (10) */
    pointer-events: none;
    overflow: hidden;
}

.wall-decor {
    position: absolute;
    width: auto;
    height: auto;
    opacity: 1;
    /* Anchored at its BOTTOM edge (not center) at the (x, y) point JS
       picks -- same idea as #bossSprite always standing on the divide.
       transform-origin is also bottom-center so rotate/scale pivot
       around that same point, instead of swinging the bottom edge
       further down. This means a piece only ever grows upward into
       the wall -- it can't push its bottom past the wall/floor line,
       no matter how tall max-height allows it to get. */
    transform-origin: 50% 100%;
    transform: translate(-50%, -100%) rotate(var(--decor-rot, 0deg)) scale(var(--decor-scale, 1));
    filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.35));
      user-select: none;
}

/* Graffiti tags -- big, bold spray-painted look */
.wall-decor-tag {
    max-width: 500px;
    max-height: 300px;
}

/* Stickers -- small, slapped-on look */
.wall-decor-sticker {
    max-width: 110px;
    max-height: 110px;
}

#wallLineworkLayer {
    position: absolute;
    inset: 0;
    z-index: 2;   /* above wallDecorLayer (1), still below boss/UI (10) */
    background: url('images/backgrounds/wall-lines.webp') center bottom / cover no-repeat;
    mix-blend-mode: multiply;
    opacity: 0.2;   /* tune this — enough to read as grooves, not enough to grey out the paint */
    pointer-events: none;
}

/* ==========================================================
   TRASH TOSS -- trash.png pieces the boss throws onto the
   screen. They land over the bubbles (obscuring them), can be
   clicked away instantly, or left to fade out on their own.
========================================================== */

#trashLayer {
    position: absolute;
    inset: 0;
    z-index: 45;     /* above bubbles/typing UI (10) and scoreFlash (20),
                         below ratingOverlay (50) and the hit-flash (90) */
    pointer-events: none; /* clicks pass through except on .trash-piece itself */
}

.trash-piece {
    position: absolute;
    transform: translate(-50%, -50%) rotate(var(--trash-rot, 0deg));
    pointer-events: auto;
    cursor: pointer;
    filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.45));
    user-select: none;
    -webkit-user-drag: none;
}

/* Quick drop-and-settle when a piece lands */
.trash-piece.trash-land {
    animation: trashLand 0.35s cubic-bezier(.34, 1.56, .64, 1);
}

@keyframes trashLand {
    0%   { transform: translate(-50%, -50%) rotate(var(--trash-rot, 0deg)) translateY(-40px) scale(0.6); opacity: 0; }
    70%  { opacity: 1; }
    100% { transform: translate(-50%, -50%) rotate(var(--trash-rot, 0deg)) translateY(0) scale(1); opacity: 1; }
}

/* Clicked away -- quick shrink/spin "poof" */
.trash-piece.trash-poof {
    animation: trashPoof 0.25s ease-out forwards;
    pointer-events: none;
}

@keyframes trashPoof {
    0%   { transform: translate(-50%, -50%) rotate(var(--trash-rot, 0deg)) scale(1);   opacity: 1; }
    100% { transform: translate(-50%, -50%) rotate(var(--trash-rot, 0deg)) scale(0.2) rotate(calc(var(--trash-rot, 0deg) + 60deg)); opacity: 0; }
}

/* Left alone -- slow fade-out once its time is up */
.trash-piece.trash-fade-out {
    animation: trashFadeOut 0.9s ease-in forwards;
    pointer-events: none;
}

@keyframes trashFadeOut {
    0%   { opacity: 1; transform: translate(-50%, -50%) rotate(var(--trash-rot, 0deg)) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -50%) rotate(var(--trash-rot, 0deg)) scale(0.85); }
}

/* ==========================================================
   FEATHER STORM -- crow boss power. A gust of feathers sweeps
   left-to-right across the WHOLE arena (unlike trashToss, which
   only lands on the bubbles) -- meant as a brief, chaotic wipe
   that obscures everything mid-storm.
========================================================== */

#featherStormLayer {
    position: absolute;
    inset: 0;
    z-index: 96;   /* above hit-flash (90), below victory/defeat modal (100) --
                       needs to sit above literally everything else while it's active */
    pointer-events: none;
    overflow: hidden;
}

.feather-piece {
    position: absolute;
    top: var(--feather-y, 50%);
    left: -15vw;
    width: var(--feather-size, 60px);
    opacity: 0;
    animation: featherBlow var(--feather-duration, 1.8s) linear var(--feather-delay, 0s) forwards;
}

@keyframes featherBlow {
    0%   { transform: translateX(0)     translateY(0)                          rotate(var(--feather-rot-start, 0deg)); opacity: 0; }
    8%   { opacity: 1; }
    50%  { transform: translateX(60vw)  translateY(var(--feather-wobble, 20px)) rotate(var(--feather-rot-mid, 180deg)); }
    92%  { opacity: 1; }
    100% { transform: translateX(130vw) translateY(0)                          rotate(var(--feather-rot-end, 360deg)); opacity: 0; }
}

/* ==========================================================
   LEAF PILE -- tanuki boss power. Leaves fall from the top and
   settle in a drift along the bottom of the arena, gradually
   obscuring the lower part of the screen (hero bubble area).
   Same interaction model as trash toss: click a leaf to dismiss
   it instantly, or leave it to fade out on its own.
========================================================== */

#leafLayer {
    position: absolute;
    inset: 0;
    z-index: 45;   /* same layer as trashLayer -- above bubbles/typing UI (10),
                       below ratingOverlay (50) */
    pointer-events: none; /* clicks pass through except on .leaf-piece itself */
}

.leaf-piece {
    position: absolute;
    pointer-events: auto;
    opacity: 0;
    animation: leafFall var(--leaf-duration, 2s) linear forwards;
    animation-delay: var(--leaf-fall-delay, 0s);
}

@keyframes leafFall {
    0%   { transform: translateY(calc(-1 * var(--leaf-drop, 500px)))    translateX(0)                                       rotate(0deg); opacity: 0; }
    6%   { opacity: 1; }
    35%  { transform: translateY(calc(-0.62 * var(--leaf-drop, 500px))) translateX(calc(var(--leaf-drift, 40px) * 0.4))  rotate(calc(var(--leaf-rot-final, 180deg) * 0.4)); }
    65%  { transform: translateY(calc(-0.28 * var(--leaf-drop, 500px))) translateX(calc(var(--leaf-drift, 40px) * 0.72)) rotate(calc(var(--leaf-rot-final, 180deg) * 0.72)); }
    100% { transform: translateY(0)                                     translateX(var(--leaf-drift, 40px))              rotate(var(--leaf-rot-final, 180deg)); opacity: 1; }
}

.leaf-fade-out {
    animation: none;
    transition: opacity 0.9s ease;
    opacity: 0;
}

.leaf-poof {
    animation: none;
    transition: opacity 0.2s ease, transform 0.2s ease;
    opacity: 0;
    transform: scale(0.6);
}