/* Doorway Odd and Even — Coconut shy sub-activity styles.
 * Carved out of odd-and-even.css to keep per-sub-activity CSS independent.
 */
/* =========================================================
   Sub-activity E: Coconut shy
   =========================================================
   Flash frame 8 background (shape0):
     - Sky:       #d3dffe,  Flash y   0–362 → CSS  0–57% of scene
     - Horizon:   #df6262,  Flash y 362–390 → CSS 57–60% of scene (3% band)
     - Ground:    radial gradient #f0cf9f → #cdb554, Flash y 390–600 → CSS 60–100%
   Green curtains at top corners are decorative — not reproduced in CSS.
   Lives (hearts) at bottom-left, level icons at bottom-right.

   Coconut positions (JS-set percentage left/top within .oe-coconut-scene):
     Row 1 (odd coconuts 1,3,5,7,9): y≈9.79em in 20em scene → top≈49%
     Row 2 (even coconuts 2,4,6,8,10): y≈12.14em → top≈61%
     x-spacing: 7.04em, 14.54em, 22.04em, 29.54em, 37.04em (row 1)
*/

.oe-coconut-shy-area {
    background-color: #ffffff;
    /* positioning context for the absolute game-over / congrats overlays
       so they sit on top of the scene rather than pushing it up. */
    position: relative;

    /* Shape-hug bloom tuning for the coconut fruit sprite scan-highlight
       — the shared `--dw-scan-shape-hug-filter` (shared/doorway.css)
       draws a three-stop bloom + three-stop halo. A fatter halo lifts
       the dark-brown coconut off the ochre ground / red horizon / blue
       sky the scene stripes across. */
    --dw-scan-bloom: 0.22em;
    --dw-scan-halo: 0.2em;
}

/* Coconut shy scene — sky / horizon / ground background matching Flash.
   Sky (#d3dffe) covers top ~60%, red horizon band at ~60-63%, ground
   (ochre) covers bottom 37%. Green curtain SVG drapes over the top.
   overflow: hidden so ::before / ::after ground/horizon layers clip correctly. */
.oe-coconut-scene {
    position: relative;
    flex: 1;
    min-height: 18em;
    border-radius: 0.3em;
    overflow: hidden;
    background-color: #d3dffe;
}

/* Ground — broad ochre band covers bottom ~45% of scene with a subtle
   radial-style gradient (Flash y 390-600 of 600 = 35% of height, but with
   stretched HTML5 16:9 stage we get a slightly larger relative band).
   A second linear gradient overlays faint wood-plank stripes to hint at
   the planks visible in the Flash ground. */
.oe-coconut-scene::before {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 45%;
    background:
        repeating-linear-gradient(
            to bottom,
            rgba(110, 70, 30, 0.0)  0,
            rgba(110, 70, 30, 0.0)  3em,
            rgba(110, 70, 30, 0.08) 3em,
            rgba(110, 70, 30, 0.08) 3.05em
        ),
        radial-gradient(ellipse at 50% 0%, #f0cf9f 0%, #d9b770 70%, #cdb554 100%);
    z-index: 0;
}

/* Horizon dividing band — red strip at the sky/ground boundary. Sized
   as a thin band (7.5%) with lighter top/bottom rims so it reads as a
   horizon line rather than a heavy bar. */
.oe-coconut-scene::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    top: 52.25%;
    height: 7.5%;
    background-color: #df6262;
    border-top: 0.15em solid #b94d4d;
    border-bottom: 0.15em solid #b94d4d;
    z-index: 1;
}

/* Green curtain (Flash sprite_311) — drapes across top of scene, and
   the same element slides down to cover the scene during level
   transitions. Rendered at its natural aspect ratio (801:784, near-
   square). At the scene's 800×450 stage this makes the rendered curtain
   taller than the stage; the parked pose translates it up by
   `--oe-curtain-parked-y` so only the scalloped bottom drape and the
   folds above it show. The remainder of the sprite hangs above the
   visible viewport and slides in during a transition. */
.oe-curtain-bg {
    --oe-curtain-parked-y: -35em;

    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    aspect-ratio: 801 / 784;
    pointer-events: none;
    /* Above every gameplay layer so the transition reads as a full
       cover of coconuts, ball arc, level/lives badges. The persistent
       instruction bar (z:51) and congrats overlay (z:65) sit above. */
    z-index: 50;
    transform: translateY(var(--oe-curtain-parked-y));
    transition: transform 0.6s ease-in-out;
}
/* Fully-down pose: no vertical offset, so the curtain fabric spans the
   top of the scene down past its bottom edge — coconut row covered. */
.oe-curtain-bg.oe-curtain-down {
    transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
    .oe-curtain-bg {
        transition-duration: 0.01s;
    }
}

/* Instruction box — green-bordered, just below the curtain drape.
   Sits above the curtain (z:50) so the mid-level "Knock down all the
   even numbered coconuts" prompt stays legible while the curtain drops
   between levels, and the congrats "Well done!" copy stays visible
   during the celebration. Font sized 20% smaller (was 1em) per the
   visual-fidelity pass. */
.oe-coconut-instruction {
    position: absolute;
    top: 0.6em;
    left: 50%;
    transform: translateX(-50%);
    background: #ffffff;
    border: 2px solid #4a8e3a;
    border-radius: 0.4em;
    padding: 0.3em 1em;
    font-size: 0.8em;
    margin: 0;
    z-index: 51;
    max-width: 60%;
    text-align: center;
}

/* Lives / level wrappers — sit at bottom on the ochre ground. The lives
   label sits centred under the hearts (Flash frame_08 has the label tag
   pinned beneath the heart row); the level label keeps its own column. */
.oe-coconut-lives-wrap {
    position: absolute;
    bottom: 0.3em;
    left: 0.4em;
    display: flex;
    flex-direction: column;
    align-items: center;
    z-index: 3;
}
.oe-coconut-level-wrap {
    position: absolute;
    bottom: 0.3em;
    right: 0.4em;
    display: flex;
    flex-direction: column;
    align-items: center;
    z-index: 3;
}
.oe-coconut-status-label {
    font-weight: bold;
    font-size: 0.9em;
    color: #1a1a1a;
    margin-top: 0.1em;
    /* Keep "Lives: N" / "Level: N" on one line — at narrow widths the
       wrap shrank to label width and the count truncated off the right
       edge. */
    white-space: nowrap;
    /* Centre the label under the hearts / pyramid. The wrap is a flex
       column with align-items:center, so the span itself sits centred;
       text-align here covers locales whose label wraps to two words. */
    text-align: center;
}

.oe-lives-display {
    display: flex;
    gap: 0.1em;
    align-items: center;
}

/* Life heart icons — Flash life1–life3 sprites at ~44px wide.
   In Flash the three hearts are prominent at the bottom-left of the
   ground (large bright red). Each life is an <img> of pictures/heart.svg
   (the Flash sprite is baked into the timeline so this SVG is a hand-drawn
   equivalent: red fill, dark red outline). */
.oe-life-icon {
    width: 2.2em;
    height: 2.2em;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.oe-life-icon img {
    width: 100%;
    height: auto;
    display: block;
    pointer-events: none;
}

.oe-life-icon.oe-life-lost {
    opacity: 0.2;
    filter: grayscale(1);
}

/* Level pyramid — 10 mini-coconut icons stacked as a triangle
   (1+2+3+4=10). Mirrors Flash levelIcons.miniNut1..10 (frame_08.as:50-83).
   Each row is a flex strip; rows STACK vertically with a small gap and
   each is independently centred so the icon-count widens row-by-row —
   apex (1 icon) at the top, broad base (4 icons) at the bottom — forming
   a clear triangular silhouette. Rows interlock with a slight vertical
   overlap (negative margin) so the result reads as a heap of stacked
   coconuts rather than a column-aligned grid. */
.oe-level-pyramid {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0;
}
.oe-level-row {
    display: flex;
    justify-content: center;
    gap: 0.05em;
    /* Slight vertical overlap so rows nestle rather than touch — gives a
       coconut-heap feel. The first row has no neighbour above so its
       margin-top stays zero (handled below). */
    margin-top: -0.4em;
}
.oe-level-row:first-child { margin-top: 0; }

.oe-level-icon {
    /* Matches the mini-coconut sprite aspect (20×24, 0.83 wide) so the
       visible coconut isn't squashed horizontally. */
    width: 1.1em;
    height: 1.55em;
    position: relative;
}

.oe-level-icon-img {
    width: 100%;
    height: 100%;
    display: block;
}

/* Completed levels: the mini-nut keeps its natural brown colour — no
   extra filter. Pending levels are greyed out below so the completed
   ones stand out. Completed and current levels look identical
   ("brown for completed OR current"). */
.oe-level-icon.oe-level-done .oe-level-icon-img {
    filter: none;
}

/* Pending levels (= every mini-nut except the one for the current level)
   are greyed out so the active level stands out. JS adds this class to
   every icon, then removes it from the current-level icon. */
.oe-level-icon.oe-level-pending .oe-level-icon-img {
    filter: grayscale(1) opacity(0.4);
}

/* Grouping wrapper for the row of coconut targets. Fills the scene so
   the coconut buttons' existing `left: N%` and `bottom: 0 / 9%` values
   land in the same visual spots they did when parented to the scene.
   Pointer-events pass through so the button-level `.oe-coconut-hit`
   ellipses remain the only clickable surfaces. Sits above the horizon
   band (z:1), below the curtain (z:50) and end-state overlays. The
   scanner treats each `.oe-coconut-target`
   as its own top-level Scannable (see `_rebuildInLevelScanner`), so
   the row itself never receives `.scan_highlight`. */
.oe-coconut-row {
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 4;
}

/* Coconut targets — raised above background layers. Each is a Flash
   coconutHolder.svg sprite (coconut head + blue post + ground shadow as
   a single artwork) bottom-anchored to the scene. The number is overlaid
   as a positioned span (the sprite's #numberText is inside <use>s that
   aren't reachable from outside <img>, so we draw the number ourselves).
   The .oe-coco-back / .oe-coco-front modifier classes share the same
   sprite height (44%) but differ in `bottom` so alternate coconuts
   zig-zag up/down rather than sitting in a flat row.
   The button's `pointer-events` are disabled so the giant invisible
   sprite area (most of which is empty post / shadow space) doesn't
   register clicks. A small hit-target overlay (`.oe-coconut-hit`) sits
   over just the coconut head and re-enables pointer events — keeping
   the visible sprite the same but only the head clickable. */
.oe-coconut-target {
    position: absolute;
    bottom: 0;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    transform: translateX(-50%);
    pointer-events: none;
}

/* Hit target — a coconut-shaped ellipse over the visible head. The
   sprite's head sits at x≈82%, y≈18% (SVG coords: coconut body y=11.65
   to y=~79 of 249.45 total height → 4.7% to 31.7%, plus a hair tuft
   sliver above). Sized to trace the visible silhouette so the hover /
   focus outline hugs just the coconut and doesn't reach down onto the
   blue post beneath. Scan-highlight is drawn on the fruit sprite via
   the shape-hug filter (see further down) rather than on this ellipse. */
.oe-coconut-hit {
    position: absolute;
    /* Vertical span 4%–32% of the sprite: covers the hair tufts at the
       top and the coconut body's lower curve, ending just above the
       blue shy ring so the outline doesn't spill onto the post. */
    top: 4%;
    left: 67%;
    width: 32%;
    height: 28%;
    border-radius: 50% / 50%;
    pointer-events: auto;
    cursor: pointer;
    z-index: 3;
}
/* Both rows share the same sprite height (44%) — Flash's sprite was
   uniform per-coconut, and the compact row reads as a group of shies
   leaving ground/sky breathing room. Back-row coconuts sit ~20% of a
   coconut's height higher so the line zig-zags up/down. */
.oe-coconut-target.oe-coco-back,
.oe-coconut-target.oe-coco-front {
    height: 44%;
}
.oe-coconut-target.oe-coco-back  { bottom: 9%; }   /* lifted ~20% of coconut height */
.oe-coconut-target.oe-coco-front { bottom: 0; }

/* Hover / focus affordances live on the hit-target ellipse rather than
   the button — the button's bounding box includes the wide empty area
   to the LEFT of the head in the sprite canvas plus the full post +
   shadow below, so routing the outline to the hit element keeps the
   ring matching the mouse/touch hit-shape. The button also suppresses
   the shared `button:hover` grey wash (doorway.css) which would
   otherwise tint the whole sprite bounding box on hover.

   Switch-scan uses a different treatment — see the shape-hug filter
   further down, which glows the coconut sprite itself. */
.oe-coconut-target:focus-visible {
    background: none;
    outline: none;
}
@media (hover: hover) {
    .oe-coconut-target:hover {
        background: none;
        outline: none;
    }
}
.oe-coconut-target:focus-visible .oe-coconut-hit {
    outline: 0.2em solid gold;
    outline-offset: 0.1em;
}
@media (hover: hover) {
    .oe-coconut-target:hover .oe-coconut-hit {
        outline: 0.2em solid gold;
        outline-offset: 0.1em;
    }
}

/* Switch-scan highlight — the coconut fruit sprite glows via the shared
   shape-hug filter so the ring traces the visible coconut alpha edge
   rather than the button's oversized bounding box. `.dw-scan-shape-hug`
   (added to the button in coconut-shy.js) already suppresses the shared
   rectangular box-shadow / focus outline on the button element. Fallen
   coconuts are guarded below so a lingering `.scan_highlight` on a
   spent target doesn't paint a glow. */
.oe-coconut-target.scan_highlight .oe-coconut-fruit-img {
    filter: var(--dw-scan-shape-hug-filter);
}
.oe-coconut-target.oe-fallen.scan_highlight .oe-coconut-fruit-img {
    filter: none;
}

.oe-coconut-sprite {
    /* Fill the button's height; width auto preserves the sprite's
       162.85:249.45 aspect ratio (~0.65 wide). */
    height: 100%;
    width: auto;
    display: block;
    pointer-events: none;
}

/* Coconut fruit — the standalone coconut.svg overlaid on the
   holder-empty sprite at the exact position the fruit occupies in the
   original coconutHolder.svg composition. Percentages derive from the
   holder's outer transform (110.85, 11.65) + the fruit's inner
   transform (-3.35, 0) over the 162.85 × 249.45 holder canvas:
     left   = (110.85 - 3.35) / 162.85 ≈ 66.01%
     top    = 0
     width  = 53.35 / 162.85 ≈ 32.76%
     height = 80.3 / 249.45 ≈ 32.19%
   On a correct hit, only this element animates (see .oe-coconut-fall);
   the holder-empty sprite (post, hoops, shadow) stays planted. */
.oe-coconut-fruit {
    position: absolute;
    left: 66.01%;
    top: 0;
    width: 32.76%;
    height: 32.19%;
    pointer-events: none;
    z-index: 2;
}
.oe-coconut-fruit-img {
    width: 100%;
    height: 100%;
    display: block;
}

/* Number sits over the coconut head — positioned within the fruit
   element so it rides along when the fruit falls. Centre of the
   coconut body sits at ~(52%, 62%) of the fruit element's box. */
.oe-coconut-number {
    position: absolute;
    top: 62%;
    left: 52%;
    transform: translate(-50%, -50%);
    /* Sized so the digit sits cleanly inside the coconut head silhouette
       rather than spilling out at the edges. */
    font-size: 1.45em;
    font-weight: bold;
    color: #ffffff;
    text-shadow: 0 0 3px #2d0d09, 0 0 3px #2d0d09;
    pointer-events: none;
    z-index: 1;
}

.oe-coconut-target.oe-fallen {
    /* When fallen, the BUTTON stays fully visible (the post/shy holds
       its place); only the fruit falls (see .oe-coconut-falling below).
       Pointer events disabled on both the button and the hit ellipse
       so the head can't be re-clicked and no hover/cursor affordance
       appears over a knocked-down coconut. */
    pointer-events: none;
    cursor: default;
}
.oe-coconut-target.oe-fallen .oe-coconut-hit {
    pointer-events: none;
    cursor: default;
}
/* Suppress the gold outline that the shared hover / focus rules paint
   on the hit ellipse. A knocked-down coconut is disabled +
   pointer-events: none so it can't legitimately receive hover; if
   focus lands here anyway (e.g. via keyboard navigation before the
   disabled state is honoured) we still want no outline — the coconut
   is a spent target and shouldn't advertise interactivity. */
.oe-coconut-target.oe-fallen:focus-visible .oe-coconut-hit {
    outline: none;
}

/* Game-over panel overlays the scene rather than pushing it up — the
   coconut illustration stays full-bleed underneath the "out of lives"
   bubble + retry button. The overlay captures pointer events across the
   whole area so the user can't keep clicking coconuts after game-over. */
.oe-game-over {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.5em;
    text-align: center;
    z-index: 60;  /* above the curtain (z:50) so retry button is clickable */
    background: rgba(255, 255, 255, 0.25);
}

.oe-game-over.hidden_panel {
    display: none;
}

/* Try Again button — bump the hover contrast so the label stays legible
   when the user is mousing over. The shared .main_button uses a faint
   wash that fell below WCAG AA on the cream activity background; force
   a saturated brown hover so the white label remains clearly readable.
   Keying off the surrounding `.oe-game-over` (and #oeCoconutGameOver)
   keeps this scoped to coconut-shy. */
.oe-game-over .oe-coconut-retry-btn:focus-visible {
    background-color: #6b3a1e;
    color: #ffffff;
    border-color: #3d1d0d;
    outline: 2px solid #f9c44a;
    outline-offset: 2px;
}
@media (hover: hover) {
    .oe-game-over .oe-coconut-retry-btn:hover {
        background-color: #6b3a1e;
        color: #ffffff;
        border-color: #3d1d0d;
        outline: 2px solid #f9c44a;
        outline-offset: 2px;
    }
}

/* Level-complete bubble — overlay across the centre of the scene with a
   solid white background so the "Well done! Level N complete!" message
   sits proudly on top of the coconut graphic. Pinned to the centre via
   absolute positioning. Layered above the gameplay (lives/level badges
   sit at z:3, coconuts at z:4-6) so the message reads cleanly; the
   curtain (z:50) sits above the bubble — but the bubble is hidden
   before the curtain drops so they never visually overlap. */
.oe-coconut-scene .oe-level-complete {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 40;
    background: #ffffff;
    border: 2px solid #3a7a2e;
    border-radius: 0.5em;
    padding: 0.5em 1.2em;
    box-shadow: 0 0.2em 0.6em rgba(0, 0, 0, 0.18);
    /* The shared .oe-feedback baseline uses an SVG bubble frame behind
       the text. We want a clean white card here, so the frame image is
       hidden below and the bubble background comes from the rule above. */
}
.oe-coconut-scene .oe-level-complete.hidden_panel {
    display: none;
}
.oe-coconut-scene .oe-level-complete .oe-feedback-frame {
    display: none;
}
.oe-coconut-scene .oe-level-complete .oe-feedback-text {
    color: #1a1a1a;
    padding: 0;
    font-weight: bold;
}

/* --- Coconut shy: coconut falls when knocked down ---
   Mirrors Flash frame_08 coconutN.gotoAndPlay(2). Outcome is also
   conveyed by .oe-fallen (opacity 0.3, pointer-events: none),
   disabled state, aria-label "fallen", and the audio cue. The
   keyframe finishes in the same opacity/transform end-state as
   .oe-fallen so the visual is stable after the animation. */
/* translateY percentages are relative to the fruit element's own height
   (~32% of the button). The button is bottom-anchored to the scene ground
   with the fruit sitting at top: 0 of the button; travelling ~215% of the
   fruit's own height lands it level with the base of the shy for both
   rows. A brief hop up at the start, then a small bounce on impact
   before it settles — reads as "knocked to the ground" rather than
   "faded". */
@keyframes oe-coconut-fall {
    0%   { transform: translateY(0)    rotate(0deg);   opacity: 1; }
    12%  { transform: translateY(-8%)  rotate(-8deg);  opacity: 1; }
    50%  { transform: translateY(215%) rotate(60deg);  opacity: 1; }
    62%  { transform: translateY(185%) rotate(75deg);  opacity: 1; }
    78%  { transform: translateY(215%) rotate(85deg);  opacity: 1; }
    100% { transform: translateY(215%) rotate(85deg);  opacity: 0.6; }
}

/* The fall animation plays on the fruit only — the post/shy stays
   planted while just the coconut + number tumble to the ground. */
.oe-coconut-target.oe-fallen.oe-coconut-falling .oe-coconut-fruit {
    animation: oe-coconut-fall 1s linear forwards;
}
/* End-state: keep the fruit at the shy base after the animation. */
.oe-coconut-target.oe-fallen .oe-coconut-fruit {
    transform: translateY(215%) rotate(85deg);
    opacity: 0.6;
}

/* --- Coconut shy: life lost flash ---
   Mirrors Flash frame_08 lifeN.gotoAndPlay(2) (lines 226/229/237).
   Outcome is also conveyed by .oe-life-lost (opacity 0.25) and the
   aria-label change. */
@keyframes oe-life-lost {
    0%   { opacity: 1; transform: scale(1); }
    30%  { opacity: 0.8; transform: scale(1.25); color: #e74c3c; }
    100% { opacity: 0.25; transform: scale(1); }
}

.oe-life-icon.oe-life-lost.oe-life-just-lost {
    animation: oe-life-lost 0.5s ease-out forwards;
}

/* --- Coconut shy: level-complete feedback reveal ---
   Mirrors Flash frame_08 messageGreat.gotoAndPlay(2) (lines 150/158).
   Outcome is also conveyed by aria-live announcement + audio + the
   level-icon being marked .oe-level-done. Bespoke keyframe that
   preserves the centering translate(-50%, -50%) — the shared
   oe-feedback-reveal animates transform: translateY(…) which would
   override our centering and pop the bubble to the top-left. */
@keyframes oe-coconut-level-complete-reveal {
    0%   { opacity: 0; transform: translate(-50%, calc(-50% - 0.4em)); }
    100% { opacity: 1; transform: translate(-50%, -50%); }
}
.oe-coconut-scene .oe-level-complete.oe-level-complete-show {
    animation: oe-coconut-level-complete-reveal 0.4s ease-out;
}

/* --- Coconut shy: level icon completion pop ---
   When a level is completed the corresponding mini-nut icon goes brown.
   The pop reinforces the change; .oe-level-done carries the meaning. */
@keyframes oe-level-icon-pop {
    0%   { transform: scale(1); }
    50%  { transform: scale(1.3); }
    100% { transform: scale(1); }
}

.oe-level-icon.oe-level-done.oe-level-just-done {
    animation: oe-level-icon-pop 0.4s ease-out;
}

/* --- Coconut shy: thrown ball arc ---
   Mirrors Flash throwBall (frame_08.as:477-506): ball travels from
   thrower position (bottom-centre) up to the clicked coconut. On a
   correct hit the ball arcs to the target and fades — see oe-ball-throw.
   On a wrong answer the ball arcs to the target then bounces back to
   the source so the user sees the "miss" — see oe-ball-miss. The
   target position is provided by JS via --oe-ball-dx / --oe-ball-dy
   custom props (px relative to the source). */
.oe-ball-layer {
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* Above both coconut rows (back z:4, front z:6, JS-set) so the ball
       flies in front of the shys rather than disappearing behind them.
       Still below the level-complete bubble (z:8) and curtain (z:50). */
    z-index: 7;
}

.oe-thrown-ball {
    position: absolute;
    width: 1.6em;
    height: 1.6em;
    /* JS sets left/top to the thrower position (scene bottom-centre);
       the keyframe translates by --oe-ball-dx / --oe-ball-dy with a
       mid-arc lift so the throw reads as an upward arc, not a slide. */
    transform: translate(-50%, -50%);
    animation: oe-ball-throw 0.55s ease-out forwards;
}

/* Wrong-answer throw: same outbound arc, but the ball returns to the
   thrower instead of fading at the target. */
.oe-thrown-ball.oe-thrown-ball-miss {
    animation: oe-ball-miss 1.1s ease-in-out forwards;
}

@keyframes oe-ball-throw {
    0% {
        transform: translate(-50%, -50%) scale(0.7);
        opacity: 1;
    }
    50% {
        transform:
            translate(-50%, -50%)
            translate(calc(var(--oe-ball-dx, 0px) * 0.5),
                      calc(var(--oe-ball-dy, 0px) * 0.5 - 2.5em))
            scale(1);
        opacity: 1;
    }
    /* End the arc fully opaque — the animationend handler removes the
       ball from the DOM, so it vanishes at impact without needing a fade. */
    100% {
        transform:
            translate(-50%, -50%)
            translate(var(--oe-ball-dx, 0px), var(--oe-ball-dy, 0px))
            scale(0.95);
        opacity: 1;
    }
}

@keyframes oe-ball-miss {
    0% {
        transform: translate(-50%, -50%) scale(0.7);
        opacity: 1;
    }
    25% {
        transform:
            translate(-50%, -50%)
            translate(calc(var(--oe-ball-dx, 0px) * 0.5),
                      calc(var(--oe-ball-dy, 0px) * 0.5 - 2.5em))
            scale(1);
        opacity: 1;
    }
    50% {
        /* Ball reaches the target (no contact — coconut stays up). */
        transform:
            translate(-50%, -50%)
            translate(var(--oe-ball-dx, 0px), var(--oe-ball-dy, 0px))
            scale(0.95);
        opacity: 1;
    }
    75% {
        /* Bounces back along the arc — mirror-image lift on the return. */
        transform:
            translate(-50%, -50%)
            translate(calc(var(--oe-ball-dx, 0px) * 0.5),
                      calc(var(--oe-ball-dy, 0px) * 0.5 - 2.5em))
            scale(0.95);
        opacity: 1;
    }
    100% {
        /* Back at the thrower. */
        transform: translate(-50%, -50%) scale(0.7);
        opacity: 0;
    }
}

/* =========================================================
   Coconut-shy congratulations (game finished)
   =========================================================
   Congrats card overlays the coconut scene with rising balloons in
   the background instead of replacing the scene — same end-state
   shape as the other oae sub-activities (shoe-pairing,
   house-painting, ladybird-sort, tutorial). */
.oe-coconut-congrats-card {
    position: absolute;
    left: 50%;
    /* The "Well done! You have finished the game." copy now lives in the
       persistent instruction bar at the top of the scene rather than
       sitting between the bubble and the Back pencil, so the card only
       has to house the bubble + Back pencil. Bias it into the vertical
       middle of the scene so the bubble sits well clear of the top
       instruction band and the coconut posts / hearts / level pyramid
       below. The wrapper is a positioning shell only — no background
       or border — so the Flash congratulations bubble (with its own
       black outline) reads as a single speech-bubble rather than as a
       bubble nested inside an outer card. */
    top: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.6em;
    pointer-events: auto;
    max-width: 24em;
    text-align: center;
    z-index: 70;
}
.oe-coconut-congrats.oe-coconut-end {
    position: absolute;
    inset: 0;
    /* Extend the overlay upward over the activity-nav band so the
       balloons rise past the top "Well done…" instruction — matches
       the shoes pattern. */
    top: -3.5em;
    z-index: 65;  /* above curtain (50), below game-over wash (60) is fine since they don't co-exist */
    pointer-events: none;
    display: block;
    padding: 0;
}
.oe-coconut-congrats.oe-coconut-end.hidden_panel {
    display: none;
}
/* Suppress the shared .oe-congratulations:not(.hidden_panel) reveal
   animation — it animates transform: translateY which would shift the
   absolutely-positioned card off-centre. Our reveal is the balloons
   themselves. */
.oe-coconut-congrats.oe-coconut-end:not(.hidden_panel) {
    animation: none;
}
.oe-coconut-congrats.oe-coconut-end .oe-balloons {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    /* Balloons render above every other layer at the celebration moment
       — Flash's congratulations clip attaches with a very high depth
       (≈ 100000). The container z-index sits above its sibling layers
       inside the coconut scene. */
    z-index: 700;
}
