/* Orb mascot: rig styling, poses and face motion.
   States: idle · working · questioning · error (plus absence, "nothing").
   Poses are set per data-state; data-transition marks one-shot moments.
   Everything stops under prefers-reduced-motion. Idle is still (FEEDBACK.md). */

.mascot {
  --bot: #1d4ed8;
  --ink: #27272a;
  --ok: #047857;
  --bad: #b91c1c;
  --ease-pop: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease: cubic-bezier(0.4, 0, 0.2, 1);
  --loop: 2.8s; /* cycle for looping one-shots; keep in sync with LOOP_SECONDS */
  display: block;
  overflow: visible;
}

.mascot * {
  transform-box: fill-box;
  transform-origin: center;
}

/* Body: iridescent glass matched to mascot3.blend. Tones come from palette()
   as --bot-a (sky), --bot-m (centre), --bot-b (lilac). */
.mascot .i-a { stop-color: var(--bot-a); }
.mascot .i-m { stop-color: var(--bot-m); }
.mascot .i-b { stop-color: var(--bot-b); }
.mascot .g-in { stop-color: color-mix(in oklab, var(--bot-m) 55%, white); stop-opacity: 0.5; }
.mascot .g-out { stop-color: var(--bot-m); stop-opacity: 0; }
.mascot .refl-low { fill: white; opacity: 0.42; }
.mascot .refl-disc { fill: white; opacity: 0.24; }
.mascot .glint { fill: white; opacity: 0.92; }
.mascot .crescent { fill: white; opacity: 0.95; }
.mascot .rim { fill: none; stroke: #b8bcc5; stroke-width: 1.3; }
.mascot .rim-inner { fill: none; stroke: white; stroke-opacity: 0.3; stroke-width: 0.7; }
.mascot .shadow { fill: color-mix(in oklab, var(--bot-m) 45%, #27272a); opacity: 0.14; }
/* Attribute transforms here must use SVG's own origin */
.mascot .refl-low, .mascot .glint { transform-box: view-box; transform-origin: 0 0; }
.mascot .body { transition: filter 300ms var(--ease), opacity 300ms var(--ease); }

/* Face placement per silhouette (shader orbs; the SVG body is circle-only). */
.mascot .face-anchor { transition: transform 260ms var(--ease); }
.mascot[data-shape="square"] .face-anchor { transform: translate(0, 0.5px); }
.mascot[data-shape="hexagon"] .face-anchor { transform: translate(0, 1px); }
.mascot[data-shape="triangle"] .face-anchor { transform: translate(0, 6px) scale(0.84); }

/* Eyes */
.mascot .eyes { transition: transform 260ms var(--ease); }
.mascot .eye-open {
  fill: #e9eaee;
  transition: transform 200ms var(--ease), opacity 160ms var(--ease);
}
.mascot .eye-happy {
  fill: none;
  stroke: #e9eaee;
  stroke-width: 3.2;
  stroke-linecap: round;
  opacity: 0;
  transition: opacity 160ms var(--ease);
}

/* Working rim light (replaces the orbit ring) */
.mascot .sweep {
  fill: none;
  stroke: white;
  stroke-width: 4.5;
  stroke-linecap: round;
  stroke-dasharray: 26 74;
  opacity: 0;
  transform-box: view-box;
  transform-origin: 31px 35px;
  transition: opacity 240ms var(--ease);
}

/* Badge */
.mascot .badge {
  transform: scale(0);
  transition: transform 220ms var(--ease);
}
.mascot .badge-disc {
  fill: white;
  stroke: var(--ink);
  stroke-opacity: 0.14;
  stroke-width: 1;
}
.mascot .glyph {
  opacity: 0;
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: opacity 160ms var(--ease);
}
.mascot .g-dots,
.mascot .g-dot { fill: var(--ink); stroke: none; }
.mascot .g-success { stroke: var(--ok); stroke-width: 3; }
.mascot .g-error { stroke: var(--bad); stroke-width: 3; }

/* ---------- States: idle · working · questioning · error ----------
   The renderer sets data-state, and data-transition ("from-to") while a
   transition plays; transition rules below override the destination state. */

.mascot[data-state="working"] .badge,
.mascot[data-state="questioning"] .badge,
.mascot[data-state="error"] .badge,
.mascot[data-transition="working-idle"] .badge {
  transform: scale(1);
  transition: transform 280ms var(--ease-pop);
}
.mascot[data-state="working"]:not([data-transition="working-idle"]) .g-dots,
.mascot[data-state="questioning"] .g-question,
.mascot[data-state="error"] .g-error,
.mascot[data-transition="working-idle"] .g-success { opacity: 1; }

/* working: eyes down and focused; the badge dots ripple */
.mascot[data-state="working"] .eyes { transform: translate(0, 2.5px); }
.mascot[data-state="working"] .eye-open { transform: scaleY(0.8); }
.mascot[data-state="working"] .g-dots circle { animation: m-dot 1.1s var(--ease) infinite; }
.mascot[data-state="working"] .g-dots circle:nth-child(2) { animation-delay: 0.14s; }
.mascot[data-state="working"] .g-dots circle:nth-child(3) { animation-delay: 0.28s; }

/* questioning: looks at you, eyes a little wider, head tilted; "?" bobs */
.mascot[data-state="questioning"] .eyes { transform: translate(-1px, -0.5px) rotate(-8deg) scale(1.08); }
.mascot[data-state="questioning"] .badge { animation: m-bob 1.8s var(--ease) infinite; }

/* error: squint; the × pulses on each shake */
.mascot[data-state="error"] .eye-open { transform: scaleY(0.4); }
.mascot[data-state="error"] .badge { animation: m-badge var(--loop) linear infinite; }

/* Optional, off by default: rare blink while idle */
.mascot.blink[data-state="idle"]:not([data-transition]) .eye-open { animation: m-blink 7s infinite; }

/* ---------- Transitions ---------- */

/* working → idle: done! happy eyes and a ✓, then back to still */
.mascot[data-transition="working-idle"] .eyes { transform: none; }
/* idle jingle and lean (as in the app): glances right while the head is tilted; a leaning worker
   holds the glance for as long as it leans. */
.mascot[data-transition="idle-jingle"] .eyes,
.mascot[data-state="idle"][data-leaning] .eyes { transform: translate(3px, -0.5px) scale(1.04); }
.mascot[data-transition="working-idle"] .eye-open { opacity: 0; }
.mascot[data-transition="working-idle"] .eye-happy { opacity: 1; }

/* idle → working: a quick wake-up blink */
.mascot[data-transition="idle-working"] .eye-open { animation: m-blink-once 320ms var(--ease) 1; }

/* working → questioning: eyes pop wide as it looks up at you */
.mascot[data-transition="working-questioning"] .eye-open { animation: m-widen 520ms var(--ease) 1; }

/* questioning → working / idle: a relaxed blink */
.mascot[data-transition="questioning-working"] .eye-open,
.mascot[data-transition="questioning-idle"] .eye-open { animation: m-blink-once 360ms var(--ease) 1; }

/* working → error: eyes pop in surprise before the squint */
.mascot[data-transition="working-error"] .eye-open { animation: m-surprise 460ms var(--ease) 1; }

/* spawn: eyes open once the body has popped in; despawn: eyes close */
.mascot[data-transition^="nothing-"] .eye-open { animation: m-open 620ms var(--ease) 1 both; }
.mascot[data-transition$="-nothing"] .eye-open { transform: scaleY(0.1); transition-duration: 140ms; }
.mascot[data-transition$="-nothing"] .badge { transform: scale(0); }

@keyframes m-dot {
  0%, 60%, 100% { opacity: 0.25; transform: translateY(0); }
  30% { opacity: 1; transform: translateY(-1.5px); }
}
@keyframes m-bob {
  0%, 100% { transform: scale(1) translateY(0); }
  50% { transform: scale(1) translateY(-1.5px); }
}
@keyframes m-badge {
  0%, 16%, 100% { transform: scale(1); }
  6% { transform: scale(1.18); }
}
@keyframes m-blink {
  0%, 97%, 100% { transform: scaleY(1); }
  98.5% { transform: scaleY(0.1); }
}
@keyframes m-blink-once {
  0%, 100% { transform: scaleY(1); }
  45% { transform: scaleY(0.1); }
}
@keyframes m-widen {
  0% { transform: scaleY(0.8); }
  40% { transform: scale(1.18, 1.22); }
  100% { transform: scale(1); }
}
@keyframes m-surprise {
  0% { transform: scale(1); }
  30% { transform: scale(1.25); }
  100% { transform: scaleY(0.4); }
}
@keyframes m-open {
  0%, 45% { transform: scaleY(0.1); }
  100% { transform: scaleY(1); }
}

@media (prefers-reduced-motion: reduce) {
  .mascot *, .mascot { animation: none !important; transition: none !important; }
}

/* Shader orbs: a ground shadow, then a body (liquid+glass canvas, SVG face)
   that the renderer moves as one unit. */
.orb, .shader-orb { position: relative; }
/* No will-change here: it makes Chrome rasterize the layer once and scale
   the bitmap, so a body that spawns from scale 0 gets a pixelated face. */
.orb > .orb-body { position: absolute; inset: 0; }
/* The canvas is 1.6× the box (PAD in orb-renderer.js) so squashes, droplets
   and the aura have room to draw. */
.orb-body > .orb-liquid { position: absolute; left: -30%; top: -30%; width: 160%; height: 160%; }
.orb-body > .mascot,
.shader-orb > .shader-orb-body, .shader-orb > .mascot { position: absolute; inset: 0; width: 100%; height: 100%; }
/* The face squashes and tilts with the body about the floor contact point
   (SHAPE_BOTTOM in orb-renderer.js, in the 64-unit box). */
.orb-body > .mascot { transform-origin: 48.44% 95.31%; }
.orb-body > .mascot[data-shape="square"] { transform-origin: 48.44% 91.25%; }
.orb-body > .mascot[data-shape="hexagon"] { transform-origin: 48.44% 92.81%; }
.orb-body > .mascot[data-shape="triangle"] { transform-origin: 48.44% 86.41%; }
.orb > .orb-shadow {
  position: absolute;
  left: 23%; width: 54%; top: 92%; height: 8%;
  border-radius: 50%;
  background: radial-gradient(closest-side, color-mix(in oklab, var(--bot-m, #7281d7) 45%, #27272a) 0%, transparent 100%);
  opacity: 1;
  filter: opacity(0.22);
  transform-origin: 50% 50%;
}
.shader-orb--smoke > .shader-orb-body { inset: 14.1% auto auto 7.8%; width: 81.3%; height: 81.3%; }
/* The renderer moves the whole body; the face's own hop/shake would double up. */
.mascot.face-only .rig { animation: none !important; }
.mascot.face-only .body, .mascot.face-only .shadow, .mascot.face-only .sweep { display: none; }
