/* Tunable knobs — play with these in devtools.
   --mascot-size: overall box, needs a unit.
   --stroke-width: the line weight of every glyph stroke, needs a unit.
   --body-roundness: the head rect's corner radius, needs a unit — the CSS `rx`
     property requires a <length>, unlike the old unitless SVG attribute.
   --antenna-size: the antenna dot's radius, needs a unit.
   --eye-wideness: 1 = as-drawn, >1 wider, <1 narrower.
   --eye-ovalness: 1 = as-drawn, >1 taller/thinner, <1 rounder.
   --eye-gap: unitless number, px pushed outward from each eye's drawn position (0 = as-drawn).
   --eye-crescent: the happy/hover moon only, 0 = thin sharp crescent (as-drawn), higher
     fills the band in toward a fuller, rounder moon. Needs a unit. */
.mascot {
  --mascot-size: 34px;
  --stroke-width: 1px;
  --body-roundness: 4px;
  --antenna-size: 1.5px;
  --eye-wideness: 1;
  --eye-ovalness: 1;
  --eye-gap: 0;
  --eye-crescent: 1px;
  position: relative;
  display: grid; place-items: center; width: var(--mascot-size); height: var(--mascot-size); color: var(--ink);
}
/* overflow: visible — the ping ring (transform-origin right at the antenna
   tip, near y=0 in the 24-unit viewBox) scales up past the viewBox's own top
   edge at full size; without this its top gets clipped to the glyph's
   nominal box instead of bleeding out above Daneel's head as intended. */
.mascot-glyph { width: 100%; height: 100%; overflow: visible; fill: none; stroke: currentColor; stroke-width: var(--stroke-width, 1px); stroke-linecap: round; stroke-linejoin: round; transform-origin: center; }
.mascot-head { rx: var(--body-roundness, 4px); }
.mascot-antenna-group { transform-origin: 12px 6px; transition: transform .15s, rotate .25s ease-out; rotate: calc(var(--gaze-x-raw, 0) * 14deg); }
.mascot-antenna, .mascot-ping { r: var(--antenna-size, 1.5px); }
/* transform-origin needs each eye's own explicit coordinate, not the "center" keyword —
   an SVG <g>'s default transform-box is the view-box, so "center" pivots around the
   whole 24x24 glyph instead of the eye's own center, scaling both eyes apart. */
.mascot-eye-pos { transition: translate .1s ease-out, scale .15s; scale: var(--eye-wideness, 1) var(--eye-ovalness, 1); }
.mascot-eye-pos-l { transform-origin: 9px 12.5px; translate: calc(var(--gaze-x, 0px) - var(--eye-gap, 0) * 1px) var(--gaze-y, 0px); }
.mascot-eye-pos-r { transform-origin: 15px 12.5px; translate: calc(var(--gaze-x, 0px) + var(--eye-gap, 0) * 1px) var(--gaze-y, 0px); }
.mascot-eye { fill: currentColor; stroke: none; stroke-width: 0; transform-origin: center; transition: transform .15s, d .25s ease-out, stroke-width .2s ease-out; }
.mascot-antenna { fill: currentColor; stroke: none; transition: fill .15s; }
.mascot-ping { fill: none; stroke: var(--red); opacity: 0; transform-origin: 12px 1.6px; }

.mascot[data-mascot-state="idle"] .mascot-eye { animation: mascot-blink 5s ease-in-out infinite; }
.mascot[data-mascot-state="idle"] .mascot-antenna-group { animation: mascot-antenna-idle-wiggle 7s ease-in-out infinite; }
.mascot[data-mascot-state="idle"] .mascot-glyph { animation: mascot-head-tilt 9s ease-in-out infinite; animation-delay: 2s; }

.mascot[data-mascot-state="working"] .mascot-glyph { animation: mascot-rock .7s ease-in-out infinite; }
.mascot[data-mascot-state="working"] .mascot-antenna { fill: var(--blue); }
.mascot[data-mascot-state="working"] .mascot-antenna-group { animation: mascot-wobble .5s ease-in-out infinite; }

.mascot[data-mascot-state="error"] .mascot-eye { transform: scaleY(.2); }
.mascot[data-mascot-state="error"] .mascot-antenna { fill: var(--red); }
.mascot[data-mascot-state="error"] .mascot-antenna-group { transform: rotate(16deg); }
.mascot[data-mascot-state="error"] .mascot-glyph { animation: mascot-shake .45s; }

.mascot[data-mascot-state="alert"] .mascot-antenna { fill: var(--red); animation: mascot-pulse 1s ease-in-out infinite; }
.mascot[data-mascot-state="alert"] .mascot-ping { animation: mascot-ping 1.4s ease-out infinite; }

/* One-shot, via instance.signal() — a keystroke, say. Same ring as the alert
   pulse above but once, faster, and blue instead of red: attentive, not urgent. */
.mascot.is-signaling .mascot-ping { animation: mascot-ping .6s ease-out; stroke: var(--signal-color, var(--blue)); }

/* A standing mood, not a state — same morph technique as the happy hover (two cubics
   sharing endpoints, bowed to one side), just aimed the other way: peak down, prongs up.
   Declared before :hover below so hovering a sad Daneel still wins and cheers him up. */
.mascot.is-sad .mascot-eye-l { d: path("M7.9 11.7C8.4 13.7 9.6 13.7 10.1 11.7C9.6 13.1 8.4 13.1 7.9 11.7Z"); }
.mascot.is-sad .mascot-eye-r { d: path("M13.9 11.7C14.4 13.7 15.6 13.7 16.1 11.7C15.6 13.1 14.4 13.1 13.9 11.7Z"); }
.mascot.is-sad .mascot-eye { stroke: currentColor; stroke-width: var(--eye-crescent, 0px); stroke-linejoin: round; }

/* is-greeting (a click on him) and is-focused (a form field he's watching
   has focus — see setFocused) share the hover morph. Clicking is how a
   touch device with no hover ever sees the happy eyes at all; is-focused is
   what makes him read as paying attention while you're typing, alongside
   lookAt() aiming his gaze at the field itself. */
.mascot:hover .mascot-glyph { animation: mascot-startle .4s ease-out; }
.mascot:hover .mascot-eye-l, .mascot.is-greeting .mascot-eye-l, .mascot.is-focused .mascot-eye-l { d: path("M7.9 13.3C8.4 11.3 9.6 11.3 10.1 13.3C9.6 11.9 8.4 11.9 7.9 13.3Z"); }
.mascot:hover .mascot-eye-r, .mascot.is-greeting .mascot-eye-r, .mascot.is-focused .mascot-eye-r { d: path("M13.9 13.3C14.4 11.3 15.6 11.3 16.1 13.3C15.6 11.9 14.4 11.9 13.9 13.3Z"); }
.mascot:hover .mascot-eye, .mascot.is-greeting .mascot-eye, .mascot.is-focused .mascot-eye { stroke: currentColor; stroke-width: var(--eye-crescent, 0px); stroke-linejoin: round; }
.mascot:hover .mascot-antenna-group { animation: mascot-perk .4s ease-out forwards; }

.mascot.is-wince .mascot-eye { animation: mascot-wince .3s ease-out; }

.mascot-hi {
  position: absolute; top: 100%; left: 50%; translate: -50% 0px; scale: .6;
  color: var(--ink); font: 600 11px/1.5 var(--sans, sans-serif);
  white-space: nowrap; pointer-events: none; opacity: 0;
  transition: opacity .15s ease-out, scale .15s ease-out;
}
.mascot.is-greeting .mascot-hi { opacity: 1; scale: 1; }

@keyframes mascot-blink { 0%, 92%, 100% { transform: scaleY(1); } 96% { transform: scaleY(.15); } }
@keyframes mascot-rock { 0%, 100% { transform: rotate(-6deg); } 50% { transform: rotate(6deg); } }
@keyframes mascot-wobble { 0%, 100% { transform: rotate(0); } 50% { transform: rotate(8deg); } }
@keyframes mascot-shake { 0%, 100% { transform: translateX(0); } 25% { transform: translateX(-2px); } 75% { transform: translateX(2px); } }
@keyframes mascot-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
@keyframes mascot-startle { 0% { transform: scale(1); } 40% { transform: scale(1.08) rotate(-4deg); } 100% { transform: scale(1); } }
@keyframes mascot-perk { to { transform: rotate(-10deg); } }
@keyframes mascot-antenna-idle-wiggle { 0%, 88%, 100% { transform: rotate(0); } 91% { transform: rotate(-8deg); } 94% { transform: rotate(6deg); } 97% { transform: rotate(0); } }
@keyframes mascot-head-tilt { 0%, 85%, 100% { transform: rotate(0); } 88% { transform: rotate(-4deg); } 91% { transform: rotate(3deg); } 94% { transform: rotate(0); } }
@keyframes mascot-ping { 0% { transform: scale(1); opacity: .7; } 100% { transform: scale(2.4); opacity: 0; } }
@keyframes mascot-wince { 0%, 100% { transform: scaleY(1); } 45% { transform: scaleY(.25); } }

/* Reusable empty-state wrapper: put a page's existing "nothing here yet" text INSIDE this,
   alongside a `mountMascot()` mount span — don't place them as loose siblings. A parent
   that's already a CSS grid (a poll board, a resources browser) gives an un-wrapped sibling
   its own implicit grid cell instead of the full-width centered spot the lone message used
   to get; `grid-column: 1 / -1` on the wrapper (harmless outside a grid parent) reclaims
   that, and the inner flex column does the centering regardless of what the parent's own
   layout mode is. The wrapped text keeps its own class and styling unchanged — except its
   own top padding, which every page sets generously for when it's the only thing in the
   box (a bare "Loading…"/"No matches" message) and which stacks with the flex `gap` below
   to push Daneel much further from the text than either alone. Overridden here at higher
   specificity (`.mascot-empty-state > p` beats a single class) rather than by editing each
   page's own rule, which every OTHER message using that same class still needs at full size. */
.mascot-empty-state {
  grid-column: 1 / -1;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
}
.mascot-empty-state > p { padding-top: 0; }
.mascot-empty-state .mascot { --mascot-size: 44px; }

@media (prefers-reduced-motion: reduce) {
  .mascot-glyph, .mascot-eye, .mascot-antenna, .mascot-antenna-group, .mascot-ping { animation: none !important; }
  .mascot-eye, .mascot-eye-pos, .mascot-antenna-group, .mascot-hi { transition: none; }
}
