/* FILE: viewer/assets/pixel/cave.css
   Verbatim copy of the live app's own caveTest.css + caveDashboard.css
   (frontend/src/caveTest/caveTest.css, frontend/src/cave/
   caveDashboard.css), read-only, so this plugin's cave renders with the
   exact same visual rules as the live cave - no reinterpretation. The
   live source files are untouched; this is an independent copy owned by
   this plugin. One addition: --radius-sm below, which caveDashboard.css
   references but only defines in the main app's separate global
   stylesheet - not part of the copied files themselves. */
:root { --radius-sm: 6px; }

/* Cave Test UI (2026-09-05 "Cave Test UI" task) - fully self-contained
   stylesheet. Deliberately does NOT reuse ../index.css or PixelMiner's
   CSS - "the test UI must not modify the current live dashboard" holds
   more strongly if there is no shared stylesheet either bundle could
   accidentally disturb. */

:root {
  --ct-bg-0: #0b0f14;
  --ct-bg-1: #12181f;
  --ct-bg-2: #1a222b;
  --ct-border: #26313d;
  --ct-text-0: #eef2f6;
  --ct-text-1: #a9b6c4;
  --ct-text-2: #6f7f90;
  --ct-accent: #38bdf8;
  --ct-green: #22c55e;
  --ct-red: #ef4444;
}

.ct-page {
  min-height: 100vh;
  background: var(--ct-bg-0);
  color: var(--ct-text-0);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  font-size: 13.5px;
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  box-sizing: border-box;
}
.ct-page * {
  box-sizing: border-box;
}

.ct-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}
.ct-title {
  font-size: 19px;
  font-weight: 700;
}
.ct-subtitle {
  font-size: 12px;
  color: var(--ct-text-2);
  margin-top: 2px;
  max-width: 640px;
}
.ct-header-stats {
  font-size: 12.5px;
  color: var(--ct-accent);
  font-weight: 600;
}

/* ---------- scene canvas -----------------------------------------
   One fixed design-space viewBox (caveMap.js's DESIGN_WIDTH x
   DESIGN_HEIGHT) native-scaled by the SVG's own preserveAspectRatio -
   this is what keeps every mapped coordinate (miner, gold rock, cart,
   ladder) pixel-consistent with the background art at ANY screen size,
   with no JS measurement needed (task's own "use a scalable coordinate
   system based on the original background dimensions"). Two stacked
   SVGs (background + actors) share the identical viewBox/
   preserveAspectRatio/box size, so they scale identically and align
   exactly. */
.ct-scene-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 1600 / 480;
  max-height: 62vh;
  margin: 0 auto;
  background: #0a0704;
  border: 1px solid var(--ct-border);
  border-radius: 10px;
  overflow: hidden;
}
.ct-scene-svg,
.ct-actors-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}
.ct-actors-svg {
  pointer-events: none;
}

@media (max-width: 640px) {
  .ct-scene-wrap {
    max-height: 42vh;
  }
}
@media (max-width: 640px) and (orientation: landscape) {
  .ct-scene-wrap {
    max-height: 70vh;
  }
}

/* ---------- gold rock (2026-09-06 redesign) --------------------------
   Section 6: "highly recognizable... when mined, show visible pickaxe
   impact and small gold or rock particles." */
.ct-gold-rock-intact {
  animation: ctGoldShimmer 2.4s ease-in-out infinite;
}
@keyframes ctGoldShimmer {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.85; }
}

/* Impact flash + spark burst. 2026-09-02: these used to loop `infinite`
   on a 0.62s period borrowed from the LEGACY articulated rig's CSS swing,
   which had nothing to do with the pixel-grid rig that actually renders
   in the live cave (5 authored frames at 170ms = an 850ms swing), so the
   "impact" drifted straight through the cycle. GoldRock.jsx now mounts
   this group ONLY while the swing is on its real impact frame, so each of
   these plays exactly once per strike (`forwards`, no repeat) and the
   duration is one frame of that swing rather than a period of its own. */
.ct-impact-flash {
  transform-box: fill-box;
  transform-origin: center;
  animation: ctImpactFlash 0.17s ease-out forwards;
}
@keyframes ctImpactFlash {
  0% { opacity: 0.95; transform: scale(0.5); }
  45% { opacity: 0.9; transform: scale(1.1); }
  100% { opacity: 0; transform: scale(1.4); }
}
.ct-impact-spark {
  transform-box: fill-box;
  transform-origin: center;
  animation: ctImpactSpark 0.17s ease-out forwards;
}
.ct-impact-spark-0 { --sx: -9px; --sy: -8px; }
.ct-impact-spark-1 { --sx: 8px; --sy: -10px; }
.ct-impact-spark-2 { --sx: -7px; --sy: 6px; }
.ct-impact-spark-3 { --sx: 9px; --sy: 5px; }
@keyframes ctImpactSpark {
  0% { opacity: 1; transform: translate(0, 0) scale(0.6); }
  100% { opacity: 0; transform: translate(var(--sx), var(--sy)) scale(1); }
}

.ct-break-flash {
  transform-box: fill-box;
  transform-origin: center;
  animation: ctBreakFlash 0.5s ease-out forwards;
}
@keyframes ctBreakFlash {
  0% { opacity: 0.95; transform: scale(0.3); }
  100% { opacity: 0; transform: scale(1.6); }
}
.ct-shard {
  transform-box: fill-box;
  transform-origin: center;
  animation: ctShardBurst 0.7s ease-out forwards;
}
.ct-shard-0 { --sx: -16px; --sy: -13px; }
.ct-shard-1 { --sx: 13px; --sy: -15px; }
.ct-shard-2 { --sx: -12px; --sy: 11px; }
.ct-shard-3 { --sx: 14px; --sy: 9px; }
.ct-shard-4 { --sx: -19px; --sy: 2px; }
.ct-shard-5 { --sx: 17px; --sy: -3px; }
@keyframes ctShardBurst {
  0% { transform: translate(0, 0) rotate(0deg) scale(1); opacity: 1; }
  100% { transform: translate(var(--sx), var(--sy)) rotate(140deg) scale(0.6); opacity: 0; }
}
.ct-gold-rock-pieces {
  animation: ctPiecesSettle 0.4s ease-out;
}
@keyframes ctPiecesSettle {
  0% { opacity: 0; transform: translateY(-8px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* ---------- shared cart ---------------------------------------------- */
.ct-cart {
  transition: none;
}
.ct-cart-full {
  animation: ctCartFullPulse 0.9s ease-in-out infinite;
}
@keyframes ctCartFullPulse {
  0%, 100% { filter: none; }
  50% { filter: drop-shadow(0 0 4px rgba(255, 210, 26, 0.6)); }
}
.ct-cart-sparkle {
  transform-box: fill-box;
  transform-origin: center;
  animation: ctCartSparkle 1.1s ease-in-out infinite;
}
@keyframes ctCartSparkle {
  0%, 100% { opacity: 0.3; transform: scale(0.7); }
  50% { opacity: 1; transform: scale(1.2); }
}

/* ---------- miner: articulated rig (2026-09-06 rebuild) -------------
   Same discovered-this-project rule throughout: the OUTER <g
   transform="translate/scale"> in Miner.jsx carries only the per-frame
   position/pivot ATTRIBUTE; every rule below targets INNER groups only
   via className, never re-declaring a transform attribute on the
   positioned element itself. Every joint (2 hips, 2 knees, 2 shoulders,
   2 elbows, the torso, the head) follows this split.

   Design goal for this pass: every pose below must be recognizable from
   its SILHOUETTE ALONE, at a glance, without reading the state label -
   so joint rotations here are deliberately large (30-100+deg swings),
   not the 5-10deg "subtle sway" the previous version used. */

.ct-m-carried-gold {
  opacity: 0;
}
.ct-tears {
  opacity: 0;
}
.ct-tears-hidden {
  opacity: 0 !important;
}
.ct-pose-cry .ct-tears,
.ct-pose-walk-cry .ct-tears,
.ct-pose-ride .ct-tears {
  opacity: 1;
  animation: ctTearDrop 1.1s ease-in infinite;
}
@keyframes ctTearDrop {
  0% { opacity: 0; transform: translateY(0); }
  25% { opacity: 1; }
  85% { opacity: 1; transform: translateY(5px); }
  100% { opacity: 0; transform: translateY(6px); }
}

/* Riding the cart: the whole rig shifts up slightly so the visible
   portion above the cart's front rim (drawn on top, see Cart.jsx/
   CaveTestPage's paint order) reads as "seated," and legs fold into the
   sit-like bent pose so nothing pokes out below the rim at an
   unnatural angle. */
.ct-m-riding {
  transform: translateY(6px);
}

/* Overhead status icons - the fastest possible "what is happening"
   read, used for the poses the task calls "an obvious reaction". */
.ct-icon-pop { animation: ctIconPop 0.4s ease-out; }
.ct-icon-zzz { animation: ctZzzFloat 2.4s ease-in-out infinite; }
.ct-icon-sweat { animation: ctSweatDrip 1.3s ease-in infinite; }
@keyframes ctIconPop {
  0% { opacity: 0; transform: translateY(6px) scale(0.5); }
  60% { opacity: 1; transform: translateY(-2px) scale(1.15); }
  100% { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes ctZzzFloat {
  0% { opacity: 0; transform: translateY(4px); }
  20% { opacity: 1; }
  80% { opacity: 1; transform: translateY(-6px); }
  100% { opacity: 0; transform: translateY(-9px); }
}
@keyframes ctSweatDrip {
  0% { opacity: 0; transform: translateY(0); }
  20% { opacity: 1; }
  100% { opacity: 0; transform: translateY(10px); }
}

/* ===================== IDLE / LOOKING AROUND =====================
   Gentle bob + a big, unmistakable head sweep left-right-center. */
.ct-m-rig.ct-pose-idle { animation: ctIdleBob 3.2s ease-in-out infinite; }
.ct-m-head-turn.ct-pose-idle { animation: ctLookAround 5.5s ease-in-out infinite; transform-origin: 0px -62px; }
@keyframes ctIdleBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2.4px); } }
@keyframes ctLookAround {
  0%, 30% { transform: rotate(0deg); }
  42%, 58% { transform: rotate(-26deg); }
  70%, 88% { transform: rotate(22deg); }
  100% { transform: rotate(0deg); }
}

/* ===================== SLEEPING =====================
   Whole body tips over onto a slouched lean, eyebrows drop flat over
   the eyes, mouth closes - plus the floating "Z" icon (Miner.jsx) makes
   this unmistakable even at a glance. */
.ct-m-rig.ct-pose-sleep { animation: ctSleepBreathe 2.8s ease-in-out infinite; }
.ct-m-torso.ct-pose-sleep { transform: rotate(-16deg); transform-origin: 0px -34px; }
.ct-m-head-turn.ct-pose-sleep { transform: rotate(-14deg); transform-origin: 0px -62px; }
.ct-m-brow-l.ct-pose-sleep { transform: translateY(2.4px) rotate(-6deg); }
.ct-m-brow-r.ct-pose-sleep { transform: translateY(2.4px) rotate(6deg); }
.ct-m-mouth.ct-pose-sleep { transform: scale(0.5); transform-origin: 0px -63.5px; }
.ct-m-shoulder-left.ct-pose-sleep,
.ct-m-shoulder-right.ct-pose-sleep { transform: rotate(8deg); }
.ct-m-hip-left.ct-pose-sleep,
.ct-m-hip-right.ct-pose-sleep { transform: rotate(-8deg); }
.ct-m-knee-left.ct-pose-sleep,
.ct-m-knee-right.ct-pose-sleep { transform: rotate(10deg); }
@keyframes ctSleepBreathe { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(2.2px); } }

/* ===================== TIRED =====================
   Standing but heavily slouched forward with the head hanging - a sweat
   drop (Miner.jsx icon) makes the "exhausted" read immediate and keeps
   this visually distinct from Sleep (which lies over further and adds
   the Z icon instead) and from Rest (which sits down). */
.ct-m-rig.ct-pose-tired { animation: ctTiredSway 2.4s ease-in-out infinite; }
.ct-m-torso.ct-pose-tired { transform: rotate(18deg) translateY(3px); transform-origin: 0px -34px; }
.ct-m-head-turn.ct-pose-tired { animation: ctTiredNod 2.4s ease-in-out infinite; transform-origin: 0px -62px; }
.ct-m-shoulder-left.ct-pose-tired,
.ct-m-shoulder-right.ct-pose-tired { transform: rotate(20deg); }
.ct-m-elbow-left.ct-pose-tired,
.ct-m-elbow-right.ct-pose-tired { transform: rotate(10deg); }
.ct-m-knee-left.ct-pose-tired,
.ct-m-knee-right.ct-pose-tired { transform: rotate(8deg); }
@keyframes ctTiredSway { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(3deg); } }
@keyframes ctTiredNod { 0%, 100% { transform: rotate(24deg); } 50% { transform: rotate(34deg); } }

/* ===================== SITTING =====================
   Real bent-knee sit: thighs swing forward to horizontal, shins fold
   back down - an unmistakably different silhouette from standing. */
.ct-m-rig.ct-pose-sit { transform: translateY(13px); animation: ctSitBreathe 3s ease-in-out infinite; }
.ct-m-hip-left.ct-pose-sit,
.ct-m-hip-right.ct-pose-sit { transform: rotate(-92deg); }
.ct-m-knee-left.ct-pose-sit,
.ct-m-knee-right.ct-pose-sit { transform: rotate(96deg); }
.ct-m-shoulder-left.ct-pose-sit { transform: rotate(-24deg); }
.ct-m-shoulder-right.ct-pose-sit { transform: rotate(-30deg); }
.ct-m-elbow-left.ct-pose-sit,
.ct-m-elbow-right.ct-pose-sit { transform: rotate(60deg); }
@keyframes ctSitBreathe { 0%, 100% { transform: translateY(13px); } 50% { transform: translateY(11.5px); } }

/* ===================== GOLFING =====================
   A big two-armed club swing with body follow-through - windup high
   behind the shoulder, strike across the front, exaggerated like a
   real golf swing rather than a small arm twitch. */
.ct-m-rig.ct-pose-golf { animation: ctGolfBob 1.6s ease-in-out infinite; }
.ct-m-torso.ct-pose-golf { animation: ctGolfTorso 1.6s ease-in-out infinite; transform-origin: 0px -34px; }
.ct-m-shoulder-right.ct-pose-golf { animation: ctGolfSwingR 1.6s ease-in-out infinite; }
.ct-m-shoulder-left.ct-pose-golf { animation: ctGolfSwingL 1.6s ease-in-out infinite; }
.ct-m-elbow-right.ct-pose-golf,
.ct-m-elbow-left.ct-pose-golf { transform: rotate(14deg); }
.ct-m-hip-left.ct-pose-golf { transform: rotate(-10deg); }
.ct-m-hip-right.ct-pose-golf { transform: rotate(10deg); }
@keyframes ctGolfBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-1.5px); } }
@keyframes ctGolfTorso {
  0%, 15% { transform: rotate(-22deg); }
  50% { transform: rotate(28deg); }
  70%, 100% { transform: rotate(-22deg); }
}
@keyframes ctGolfSwingR {
  0%, 15% { transform: rotate(-95deg); }
  50% { transform: rotate(85deg); }
  70%, 100% { transform: rotate(-95deg); }
}
@keyframes ctGolfSwingL {
  0%, 15% { transform: rotate(-70deg); }
  50% { transform: rotate(60deg); }
  70%, 100% { transform: rotate(-70deg); }
}

/* ===================== DANCING =====================
   2026-09-02. Replaces the old TWERKING block, whose whole animation was
   a +-12deg hip oscillation with the head, arms and feet held still - at
   this character size that read as standing, which is exactly what the
   project owner reported. This is the legacy articulated rig's version of
   the same six-beat dance the pixel-grid rig now plays (frameDance in
   minerPixelFrames.js): the whole rig bounces, the torso sways and
   counter-rotates, both arms swing up overhead and back down in
   opposition, and the legs kick out alternately. Four moving part groups,
   not one. The two rigs are kept deliberately in step at 0.9s per full
   cycle - six frames at 170ms is 1.02s, close enough that switching rigs
   in the dev harness reads as the same dance. */
.ct-m-rig.ct-pose-dance { animation: ctDanceBounce 0.45s ease-in-out infinite; }
.ct-m-torso.ct-pose-dance { transform-origin: 0px -34px; animation: ctDanceTorso 0.9s ease-in-out infinite; }
.ct-m-shoulder-left.ct-pose-dance { animation: ctDanceArmL 0.9s ease-in-out infinite; }
.ct-m-shoulder-right.ct-pose-dance { animation: ctDanceArmR 0.9s ease-in-out infinite; }
.ct-m-elbow-left.ct-pose-dance,
.ct-m-elbow-right.ct-pose-dance { animation: ctDanceElbow 0.9s ease-in-out infinite; }
.ct-m-hip-left.ct-pose-dance { animation: ctDanceLegL 0.9s ease-in-out infinite; }
.ct-m-hip-right.ct-pose-dance { animation: ctDanceLegR 0.9s ease-in-out infinite; }
.ct-m-knee-left.ct-pose-dance,
.ct-m-knee-right.ct-pose-dance { animation: ctDanceKnee 0.9s ease-in-out infinite; }
@keyframes ctDanceBounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-4px); } }
@keyframes ctDanceTorso {
  0%, 100% { transform: rotate(-9deg); }
  50% { transform: rotate(9deg); }
}
@keyframes ctDanceArmL {
  0%, 100% { transform: rotate(20deg); }
  30% { transform: rotate(-120deg); }
  60% { transform: rotate(-165deg); }
}
@keyframes ctDanceArmR {
  0%, 100% { transform: rotate(-165deg); }
  30% { transform: rotate(-120deg); }
  60% { transform: rotate(20deg); }
}
@keyframes ctDanceElbow {
  0%, 100% { transform: rotate(10deg); }
  45% { transform: rotate(-40deg); }
}
@keyframes ctDanceLegL {
  0%, 100% { transform: rotate(-14deg); }
  35% { transform: rotate(-46deg); }
  70% { transform: rotate(-6deg); }
}
@keyframes ctDanceLegR {
  0%, 100% { transform: rotate(6deg); }
  35% { transform: rotate(14deg); }
  70% { transform: rotate(46deg); }
}
@keyframes ctDanceKnee {
  0%, 100% { transform: rotate(6deg); }
  50% { transform: rotate(26deg); }
}

/* ===================== WALKING FAMILY =====================
   walk / walk-carry (to cart with gold) / walk-cry (to cart, crying).
   Left/right hips share one keyframe but the RIGHT side runs it in
   `reverse`, which is what actually produces obvious ALTERNATING leg
   movement instead of both legs swinging in lockstep. Knees only bend
   on the back-swing (the classic walk-cycle "trailing leg lifts" cue). */
.ct-m-rig.ct-pose-walk,
.ct-m-rig.ct-pose-walk-carry,
.ct-m-rig.ct-pose-walk-cry { animation: ctWalkBob 0.46s ease-in-out infinite; }
.ct-m-hip-left.ct-pose-walk,
.ct-m-hip-left.ct-pose-walk-carry,
.ct-m-hip-left.ct-pose-walk-cry { animation: ctWalkHip 0.46s ease-in-out infinite; }
.ct-m-hip-right.ct-pose-walk,
.ct-m-hip-right.ct-pose-walk-carry,
.ct-m-hip-right.ct-pose-walk-cry { animation: ctWalkHip 0.46s ease-in-out infinite reverse; }
.ct-m-knee-left.ct-pose-walk,
.ct-m-knee-left.ct-pose-walk-carry,
.ct-m-knee-left.ct-pose-walk-cry { animation: ctWalkKnee 0.46s ease-in-out infinite; }
.ct-m-knee-right.ct-pose-walk,
.ct-m-knee-right.ct-pose-walk-carry,
.ct-m-knee-right.ct-pose-walk-cry { animation: ctWalkKnee 0.46s ease-in-out infinite reverse; }
.ct-m-shoulder-left.ct-pose-walk,
.ct-m-shoulder-left.ct-pose-walk-cry { animation: ctWalkArm 0.46s ease-in-out infinite reverse; }
.ct-m-shoulder-right.ct-pose-walk,
.ct-m-shoulder-right.ct-pose-walk-cry { animation: ctWalkArm 0.46s ease-in-out infinite; }
.ct-m-torso.ct-pose-walk-cry { transform: rotate(10deg); transform-origin: 0px -34px; }
.ct-m-head-turn.ct-pose-walk-cry { transform: rotate(10deg) translateY(1px); transform-origin: 0px -62px; }
/* carrying gold to the cart: both hands cradle it in front, so both
   arms hold a fixed forward-carry angle instead of swinging. */
.ct-m-shoulder-left.ct-pose-walk-carry,
.ct-m-shoulder-right.ct-pose-walk-carry { transform: rotate(-80deg); }
.ct-m-elbow-left.ct-pose-walk-carry,
.ct-m-elbow-right.ct-pose-walk-carry { transform: rotate(70deg); }
.ct-pose-walk-carry .ct-m-carried-gold { opacity: 1; }
@keyframes ctWalkBob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-3.5px); } }
@keyframes ctWalkHip { 0%, 100% { transform: rotate(-38deg); } 50% { transform: rotate(38deg); } }
@keyframes ctWalkKnee { 0%, 20% { transform: rotate(45deg); } 50%, 100% { transform: rotate(0deg); } }
@keyframes ctWalkArm { 0%, 100% { transform: rotate(-42deg); } 50% { transform: rotate(42deg); } }

/* ===================== LADDER CLIMBING =====================
   Arms reach up and alternate overhead (big shoulder swing, near-
   straight elbow) while the opposite-side leg steps up - vertical
   travel itself comes from the state machine's own y interpolation, so
   this only needs to sell "climbing," not actually move the sprite. */
.ct-m-shoulder-left.ct-pose-climb { animation: ctClimbArmL 0.6s ease-in-out infinite; }
.ct-m-shoulder-right.ct-pose-climb { animation: ctClimbArmR 0.6s ease-in-out infinite; }
.ct-m-elbow-left.ct-pose-climb,
.ct-m-elbow-right.ct-pose-climb { transform: rotate(-10deg); }
.ct-m-hip-left.ct-pose-climb { animation: ctClimbLegR 0.6s ease-in-out infinite; }
.ct-m-hip-right.ct-pose-climb { animation: ctClimbLegL 0.6s ease-in-out infinite; }
.ct-m-knee-left.ct-pose-climb { animation: ctClimbKneeR 0.6s ease-in-out infinite; }
.ct-m-knee-right.ct-pose-climb { animation: ctClimbKneeL 0.6s ease-in-out infinite; }
@keyframes ctClimbArmL { 0%, 100% { transform: rotate(-150deg); } 50% { transform: rotate(-40deg); } }
@keyframes ctClimbArmR { 0%, 100% { transform: rotate(-40deg); } 50% { transform: rotate(-150deg); } }
@keyframes ctClimbLegL { 0%, 100% { transform: rotate(-30deg); } 50% { transform: rotate(20deg); } }
@keyframes ctClimbLegR { 0%, 100% { transform: rotate(20deg); } 50% { transform: rotate(-30deg); } }
@keyframes ctClimbKneeL { 0%, 100% { transform: rotate(50deg); } 50% { transform: rotate(0deg); } }
@keyframes ctClimbKneeR { 0%, 100% { transform: rotate(0deg); } 50% { transform: rotate(50deg); } }

/* ===================== FOUND GOLD =====================
   A hard upward hop with BOTH arms thrown straight overhead, mouth
   wide open, brows shot up - plus the "!" icon (Miner.jsx). This is the
   single punchiest pose in the set on purpose: it needs to read as
   unambiguous excitement in under a second. */
.ct-m-rig.ct-pose-found { animation: ctFoundPop 0.5s ease-out; }
.ct-m-shoulder-left.ct-pose-found,
.ct-m-shoulder-right.ct-pose-found { transform: rotate(-175deg); }
.ct-m-elbow-left.ct-pose-found,
.ct-m-elbow-right.ct-pose-found { transform: rotate(10deg); }
.ct-m-brow-l.ct-pose-found { transform: translateY(-2px) rotate(6deg); }
.ct-m-brow-r.ct-pose-found { transform: translateY(-2px) rotate(-6deg); }
.ct-m-mouth.ct-pose-found { transform: scale(1.6, 2.1) translateY(0.4px); transform-origin: 0px -63.5px; }
@keyframes ctFoundPop {
  0% { transform: translateY(0); }
  30% { transform: translateY(-11px); }
  55% { transform: translateY(-11px); }
  100% { transform: translateY(0); }
}

/* ===================== GOLD VANISHED (loss - shocked) =====================
   A sharp recoil-back with a startled "?!" icon - visually the OPPOSITE
   read of Found Gold's forward jump, so profit vs. loss never look
   ambiguous even in a quick glance. */
.ct-m-rig.ct-pose-shocked { animation: ctShockRecoil 0.45s ease-out; }
.ct-m-torso.ct-pose-shocked { transform: rotate(-14deg); transform-origin: 0px -34px; }
.ct-m-shoulder-left.ct-pose-shocked,
.ct-m-shoulder-right.ct-pose-shocked { transform: rotate(-60deg); }
.ct-m-brow-l.ct-pose-shocked { transform: translateY(-2.4px); }
.ct-m-brow-r.ct-pose-shocked { transform: translateY(-2.4px); }
.ct-m-mouth.ct-pose-shocked { transform: scale(0.7, 1.8); transform-origin: 0px -63.5px; }
@keyframes ctShockRecoil {
  0% { transform: translateX(0); }
  40% { transform: translateX(-7px); }
  100% { transform: translateX(0); }
}

/* ===================== MINING =====================
   A full, wide pickaxe arc: windup high overhead, hard strike down and
   across, with the torso leaning into the swing and a visible recoil -
   "clear repeated pickaxe swing" from the task's own requirement. */
.ct-m-rig.ct-pose-mine { animation: ctMineRecoil 0.62s ease-in-out infinite; }
.ct-m-torso.ct-pose-mine { animation: ctMineTorso 0.62s ease-in-out infinite; transform-origin: 0px -34px; }
.ct-m-shoulder-right.ct-pose-mine { animation: ctMineSwing 0.62s ease-in-out infinite; }
.ct-m-elbow-right.ct-pose-mine { animation: ctMineElbow 0.62s ease-in-out infinite; }
.ct-m-hip-left.ct-pose-mine { transform: rotate(-12deg); }
.ct-m-hip-right.ct-pose-mine { transform: rotate(12deg); }
.ct-m-knee-left.ct-pose-mine,
.ct-m-knee-right.ct-pose-mine { transform: rotate(14deg); }
@keyframes ctMineRecoil { 0%, 38%, 100% { transform: translateX(0); } 48% { transform: translateX(-3px); } }
@keyframes ctMineTorso {
  0%, 30% { transform: rotate(-18deg); }
  48% { transform: rotate(10deg); }
  70%, 100% { transform: rotate(-18deg); }
}
@keyframes ctMineSwing {
  0%, 30% { transform: rotate(-140deg); }
  48% { transform: rotate(15deg); }
  70%, 100% { transform: rotate(-140deg); }
}
@keyframes ctMineElbow {
  0%, 30% { transform: rotate(60deg); }
  48% { transform: rotate(0deg); }
  70%, 100% { transform: rotate(60deg); }
}

/* ===================== RESTING (mining break) =====================
   Sits down (reuses the sit leg fold) with arms relaxed on the knees
   and a slow breathing bob - deliberately a SEATED pose so it never
   reads the same as standing-slouched Tired. */
.ct-m-rig.ct-pose-rest { transform: translateY(13px); animation: ctSitBreathe 2.4s ease-in-out infinite; }
.ct-m-hip-left.ct-pose-rest,
.ct-m-hip-right.ct-pose-rest { transform: rotate(-88deg); }
.ct-m-knee-left.ct-pose-rest,
.ct-m-knee-right.ct-pose-rest { transform: rotate(92deg); }
.ct-m-torso.ct-pose-rest { transform: rotate(6deg); transform-origin: 0px -34px; }
.ct-m-shoulder-left.ct-pose-rest,
.ct-m-shoulder-right.ct-pose-rest { transform: rotate(-16deg); }
.ct-m-elbow-left.ct-pose-rest,
.ct-m-elbow-right.ct-pose-rest { transform: rotate(50deg); }
.ct-m-head-turn.ct-pose-rest { animation: ctLookAround 6s ease-in-out infinite; transform-origin: 0px -62px; }

/* ===================== DRINKING (mining break) =====================
   Mug raised in a wide, obvious arc from the hip up to the mouth and
   held there, head tipped back to "drink" - clearly not just an idle
   arm wiggle. */
.ct-m-rig.ct-pose-drink { animation: ctIdleBob 2.4s ease-in-out infinite; }
.ct-m-shoulder-right.ct-pose-drink { animation: ctDrinkArm 1.7s ease-in-out infinite; }
.ct-m-elbow-right.ct-pose-drink { animation: ctDrinkElbow 1.7s ease-in-out infinite; }
.ct-m-head-turn.ct-pose-drink { animation: ctDrinkHead 1.7s ease-in-out infinite; transform-origin: 0px -62px; }
@keyframes ctDrinkArm { 0%, 25%, 100% { transform: rotate(-30deg); } 55%, 80% { transform: rotate(-165deg); } }
@keyframes ctDrinkElbow { 0%, 25%, 100% { transform: rotate(20deg); } 55%, 80% { transform: rotate(95deg); } }
@keyframes ctDrinkHead { 0%, 25%, 100% { transform: rotate(0deg); } 55%, 80% { transform: rotate(-22deg); } }

/* ===================== COLLECTING / CARRYING / DEPOSITING GOLD =====================
   A clear crouch-down-and-lift: knees bend, torso dips forward to
   "pick up," then both arms come up cradling the visible gold. */
.ct-m-rig.ct-pose-collect { animation: ctCollectCrouch 0.9s ease-in-out infinite; }
.ct-m-torso.ct-pose-collect { animation: ctCollectTorso 0.9s ease-in-out infinite; transform-origin: 0px -34px; }
.ct-m-hip-left.ct-pose-collect,
.ct-m-hip-right.ct-pose-collect { transform: rotate(-30deg); }
.ct-m-knee-left.ct-pose-collect,
.ct-m-knee-right.ct-pose-collect { transform: rotate(38deg); }
.ct-m-shoulder-left.ct-pose-collect,
.ct-m-shoulder-right.ct-pose-collect { transform: rotate(-95deg); }
.ct-m-elbow-left.ct-pose-collect,
.ct-m-elbow-right.ct-pose-collect { transform: rotate(80deg); }
.ct-pose-collect .ct-m-carried-gold { opacity: 1; }
@keyframes ctCollectCrouch { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(4px); } }
@keyframes ctCollectTorso { 0%, 100% { transform: rotate(14deg); } 50% { transform: rotate(2deg); } }

/* ===================== WAITING ON THE CART =====================
   Arms crossed, weight shifting foot to foot, an impatient head-tap -
   distinct from plain Idle by the crossed-arm silhouette. */
.ct-m-rig.ct-pose-wait { animation: ctWaitFidget 1.6s ease-in-out infinite; }
.ct-m-shoulder-left.ct-pose-wait { transform: rotate(-100deg); }
.ct-m-shoulder-right.ct-pose-wait { transform: rotate(-100deg); }
.ct-m-elbow-left.ct-pose-wait,
.ct-m-elbow-right.ct-pose-wait { transform: rotate(90deg); }
.ct-m-hip-left.ct-pose-wait,
.ct-m-hip-right.ct-pose-wait { animation: ctWaitWeight 1.6s ease-in-out infinite; }
.ct-m-head-turn.ct-pose-wait { animation: ctTiredNod 1.6s ease-in-out infinite; transform-origin: 0px -62px; }
@keyframes ctWaitFidget { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(2deg); } }
@keyframes ctWaitWeight { 0%, 100% { transform: rotate(-6deg); } 50% { transform: rotate(6deg); } }

/* ===================== PICKAXE BREAKING =====================
   A sharp recoil as the pickaxe head visibly snaps and flies off -
   "break the miner's pickaxe" needs the tool itself to visibly fail,
   not just a generic flinch. */
.ct-m-rig.ct-pose-pickaxe-break { animation: ctPickaxeRecoil 0.6s ease-out; }
.ct-m-shoulder-right.ct-pose-pickaxe-break { transform: rotate(-50deg); }
.ct-m-elbow-right.ct-pose-pickaxe-break { transform: rotate(40deg); }
.ct-m-pickaxe-head.ct-pose-pickaxe-break { animation: ctPickaxeSnap 0.6s ease-out forwards; }
@keyframes ctPickaxeRecoil {
  0% { transform: translateX(0) rotate(0deg); }
  35% { transform: translateX(-5px) rotate(-6deg); }
  100% { transform: translateX(0) rotate(0deg); }
}
@keyframes ctPickaxeSnap {
  0% { transform: rotate(0deg) translate(0, 0); opacity: 1; }
  55% { transform: rotate(75deg) translate(6px, 3px); opacity: 1; }
  100% { transform: rotate(100deg) translate(12px, 9px); opacity: 0; }
}

/* ===================== CRYING =====================
   Hunched-forward torso, drooped head, one hand wiping an eye, corners
   of the mouth turned down - paired with the streaming tears already
   wired via .ct-tears above. */
.ct-m-rig.ct-pose-cry { animation: ctCrySob 0.5s ease-in-out infinite; }
.ct-m-torso.ct-pose-cry { transform: rotate(12deg); transform-origin: 0px -34px; }
.ct-m-head-turn.ct-pose-cry { animation: ctCryHead 0.5s ease-in-out infinite; transform-origin: 0px -62px; }
.ct-m-brow-l.ct-pose-cry { transform: translateY(2px) rotate(14deg); }
.ct-m-brow-r.ct-pose-cry { transform: translateY(2px) rotate(-14deg); }
.ct-m-mouth.ct-pose-cry { transform: scale(0.8, 1.4) translateY(1.2px) rotate(180deg); transform-origin: 0px -63.5px; }
.ct-m-shoulder-right.ct-pose-cry { transform: rotate(-8deg); }
.ct-m-elbow-right.ct-pose-cry { transform: rotate(30deg); }
.ct-m-shoulder-left.ct-pose-cry { transform: rotate(-140deg); }
.ct-m-elbow-left.ct-pose-cry { transform: rotate(80deg); }
@keyframes ctCrySob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(2.4px); } }
@keyframes ctCryHead { 0%, 100% { transform: rotate(16deg); } 50% { transform: rotate(20deg); } }

/* ===================== RIDING THE CART =====================
   Seated leg fold (same as Sit) so the lower body sits naturally inside
   the cart's silhouette; a gentle jostle sells the ride. Crying-while-
   riding (loss flow) is handled automatically - CRYING_POSES in
   Miner.jsx includes "ride", so the same tear/brow/mouth treatment
   layers on top without needing a separate pose. */
.ct-m-rig.ct-pose-ride { animation: ctRideBounce 0.35s ease-in-out infinite; }
.ct-m-hip-left.ct-pose-ride,
.ct-m-hip-right.ct-pose-ride { transform: rotate(-90deg); }
.ct-m-knee-left.ct-pose-ride,
.ct-m-knee-right.ct-pose-ride { transform: rotate(95deg); }
.ct-m-shoulder-left.ct-pose-ride { transform: rotate(-20deg); }
.ct-m-shoulder-right.ct-pose-ride { transform: rotate(-15deg); }
@keyframes ctRideBounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-2.6px); } }

/* ---------- upper-level room labels --------------------------------- */
/* 2026-09-02. The plaque these sit on is real pixel art (caveObjects.js's
   roomPlaqueOfWidth); the lettering is SVG text, exactly like every miner
   name and activity emote already rendered over this cave. Small caps,
   wide tracking and a hard dark outline so two words stay legible burned
   into a plank at this scale. */
.ct-room-label {
  font-family: monospace;
  font-size: 8.5px;
  font-weight: 700;
  letter-spacing: 1.1px;
  fill: #f0d9a8;
  paint-order: stroke;
  stroke: #2a1f14;
  stroke-width: 2.4px;
}

/* ---------- coordinate overlay -------------------------------------- */
.ct-coord-marker text {
  font-family: monospace;
  paint-order: stroke;
  stroke: #06202a;
  stroke-width: 2px;
}
.ct-debug-overlay text {
  font-family: monospace;
  paint-order: stroke;
  stroke: #06202a;
  stroke-width: 2px;
}

/* ---------- dev controls --------------------------------------------- */
.ct-controls {
  background: var(--ct-bg-1);
  border: 1px solid var(--ct-border);
  border-radius: 10px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.ct-controls-global {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--ct-border);
}
.ct-toggle {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12.5px;
  color: var(--ct-text-1);
  cursor: pointer;
}
.ct-cart-summary {
  margin-left: auto;
  font-size: 12px;
  color: var(--ct-text-2);
}
.ct-empty {
  color: var(--ct-text-2);
  font-size: 12.5px;
  padding: 10px 0;
}

.ct-miner-controls-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 10px;
}
.ct-miner-control-card {
  background: var(--ct-bg-2);
  border: 1px solid var(--ct-border);
  border-radius: 8px;
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ct-miner-control-header {
  display: flex;
  align-items: center;
  gap: 8px;
}
.ct-tag {
  font-size: 10.5px;
  color: var(--ct-text-2);
  background: var(--ct-bg-1);
  border: 1px solid var(--ct-border);
  border-radius: 999px;
  padding: 1px 7px;
}
.ct-btn-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.ct-btn-row-wrap {
  padding-top: 6px;
  border-top: 1px dashed var(--ct-border);
}

.ct-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 7px 11px;
  border-radius: 6px;
  border: 1px solid var(--ct-border);
  background: var(--ct-bg-2);
  color: var(--ct-text-0);
  font-weight: 600;
  font-size: 12.5px;
  cursor: pointer;
}
.ct-btn:hover {
  background: #223040;
}
.ct-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.ct-btn-primary {
  background: var(--ct-accent);
  border-color: var(--ct-accent);
  color: #06202a;
}
.ct-btn-green {
  background: rgba(34, 197, 94, 0.16);
  border-color: var(--ct-green);
  color: var(--ct-green);
}
.ct-btn-red {
  background: rgba(239, 68, 68, 0.16);
  border-color: var(--ct-red);
  color: var(--ct-red);
}
.ct-btn-danger {
  background: rgba(239, 68, 68, 0.12);
  border-color: #7a2b2b;
  color: #f3a2a2;
}
.ct-btn-sm {
  padding: 6px 9px;
  font-size: 11.5px;
}
.ct-btn-xs {
  padding: 5px 8px;
  font-size: 11px;
}

/* ---------- dev inspector --------------------------------------------- */
.ct-inspector {
  background: var(--ct-bg-1);
  border: 1px solid var(--ct-border);
  border-radius: 10px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.ct-inspector-title {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ct-text-1);
  margin-bottom: 6px;
}
.ct-inspector-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}
.ct-inspector-table td,
.ct-inspector-table th {
  padding: 5px 8px;
  border-bottom: 1px solid var(--ct-border);
  text-align: left;
}
.ct-inspector-table th {
  color: var(--ct-text-2);
  text-transform: uppercase;
  font-size: 10.5px;
  letter-spacing: 0.04em;
}
.ct-mono {
  font-family: monospace;
}
.ct-inspector-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.ct-inspector-table-wide {
  min-width: 560px;
}

/* ---------- responsive (task section 16) ------------------------------
   Desktop / laptop / tablet / phone portrait / phone landscape. The
   scene canvas itself needs no per-breakpoint coordinate logic (see the
   .ct-scene-wrap comment above) - only the surrounding page chrome
   (header, control grid, inspector table) needs to reflow. */
@media (max-width: 900px) {
  .ct-page {
    padding: 10px;
    gap: 10px;
  }
  .ct-miner-controls-grid {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 480px) {
  .ct-title {
    font-size: 16px;
  }
  .ct-btn {
    padding: 9px 12px;
    min-height: 38px;
  }
  .ct-btn-xs {
    padding: 8px 10px;
    min-height: 34px;
  }
  .ct-controls-global {
    gap: 8px;
  }
  .ct-cart-summary {
    margin-left: 0;
    width: 100%;
  }
}

/* ---------------------------------------------------------------------
   Ambient cave-life effects (2026-09-01 "improve the cave mining system
   and NPC actions" task, sections 18-22) - bats, dripping water, hard
   pixel shadows. See caveAmbience.jsx for how each instance below feeds
   its own position/timing into these SAME shared keyframe rules, which
   is what keeps every instance from ever moving in lockstep with any
   other. Both bats and drips spend most of their own cycle length
   sitting hidden (opacity 0) - a real wait, not a fast loop - and only
   move/flash for a short window near the end.
   ------------------------------------------------------------------ */

.ct-bat {
  transform: translate(var(--bx0), var(--by0));
  opacity: 0;
  animation-name: ctBatFly;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
  pointer-events: none;
}

@keyframes ctBatFly {
  0%, 90% {
    opacity: 0;
    transform: translate(var(--bx0), var(--by0));
  }
  91% {
    opacity: 1;
    transform: translate(var(--bx0), var(--by0));
  }
  95% {
    transform: translate(var(--bmx), var(--bmy));
  }
  99% {
    opacity: 1;
    transform: translate(var(--bx1), var(--by1));
  }
  100% {
    opacity: 0;
    transform: translate(var(--bx1), var(--by1));
  }
}

.ct-bat-wing {
  transform-box: fill-box;
  transform-origin: 0 0;
  animation: ctBatWingFlap 0.42s ease-in-out infinite;
}

.ct-bat-wing-r {
  animation-delay: 0.02s;
}

@keyframes ctBatWingFlap {
  0%, 100% {
    transform: scaleY(1) rotate(0deg);
  }
  25% {
    transform: scaleY(0.6) rotate(-10deg);
  }
  50% {
    transform: scaleY(0.2) rotate(-22deg);
  }
  75% {
    transform: scaleY(0.6) rotate(-10deg);
  }
}

.ct-drip {
  opacity: 0;
  animation-name: ctDripFall;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
  pointer-events: none;
}

@keyframes ctDripFall {
  0%, 55% {
    opacity: 0;
    transform: translateY(0);
  }
  58% {
    opacity: 1;
    transform: translateY(0);
  }
  92% {
    opacity: 1;
    transform: translateY(var(--fall));
  }
  94%, 100% {
    opacity: 0;
    transform: translateY(var(--fall));
  }
}

.ct-drip-splash {
  opacity: 0;
  animation-name: ctDripSplashFlash;
  animation-iteration-count: infinite;
  animation-timing-function: steps(1, jump-none);
  pointer-events: none;
}

@keyframes ctDripSplashFlash {
  0%, 90% {
    opacity: 0;
  }
  92%, 96% {
    opacity: 0.85;
  }
  100% {
    opacity: 0;
  }
}
/* Cave Dashboard (2026-09-01 "Cave dashboard rebuild" task) - the
   background and actor SVGs are two SEPARATE, stacked <svg> elements
   sharing the exact same viewBox/preserveAspectRatio so they always
   scale identically together, rather than one giant SVG re-rendering
   the static background every time an actor's position changes.
   ct-* class names (caveTest.css, imported by CaveDashboard.jsx) are
   used unmodified by the reused Cave Test UI components; nothing here
   redefines them. */

.cave-dashboard-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 1600 / 480;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: #0e0905;
}

.cave-dashboard-bg,
.cave-dashboard-actors {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

.cave-dashboard-actors {
  pointer-events: none;
}

.cave-miner-label {
  pointer-events: none;
}

.cave-miner-name {
  font-size: 15px;
  font-weight: 700;
  fill: #e5e7eb;
  paint-order: stroke;
  stroke: #0a0f14;
  stroke-width: 3px;
}

.cave-miner-pnl {
  font-size: 17px;
  font-weight: 800;
  paint-order: stroke;
  stroke: #0a0f14;
  stroke-width: 3px;
}

.cave-miner-pnl-pos {
  fill: #4ade80;
}

.cave-miner-pnl-neg {
  fill: #f87171;
}

.cave-miner-activity {
  font-size: 13px;
  font-weight: 700;
  font-style: italic;
  fill: #f5d97a;
  paint-order: stroke;
  stroke: #0a0f14;
  stroke-width: 3px;
}

.cave-miner-zzz {
  font-size: 13px;
  font-weight: 800;
  fill: #cdd8ff;
  paint-order: stroke;
  stroke: #1c2b4a;
  stroke-width: 2.6px;
}

@media (max-width: 640px) {
  .cave-miner-activity,
  .cave-miner-zzz {
    font-size: 11px;
  }
}

@media (max-width: 640px) {
  .cave-miner-name {
    font-size: 13px;
  }
  .cave-miner-pnl {
    font-size: 15px;
  }
}
