/* Character rig for the hero's layered SVG characters.
   CSS owns the always-on loops (bob, breath, tail, whiskers), the hop and levitation, the face
   swaps (eyes, lids, brows, mouths, glow) and the ground shadows. js/characters.js uses Motion
   for the poses: arms, waving, head, ears, tail swish and the yawn. */
:root {
  /* 3D look tuning knobs: dial the effect up or down. */
  --shade: 0.38;
  --spec: 0.55;
  --rim-o: 0.55;
  /* The room's light, keyed to the paper (the shared gradients in index.html read these):
     warm paper light on the rims, green-black shade, and the sage desk bouncing up from below. */
  --rim-c: #f4ecd8;
  --shade-c: #1d2a22;
  --bounce-c: #c3ccb6;
}

/* Eyelids: --lid is where a character's lids rest (0 open, 1 shut); --blink shuts them on top. */
@property --lid {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

@property --blink {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

.shared-defs {
  position: absolute;
}

/* The character takes whatever height the panel has left after the role button
   (--room, set with the panel layout in styles.css), up to 32rem, or 64% of the panel on large
   screens. js/characters.js writes its lean and lift into `translate` every frame. */
.character {
  position: relative;
  z-index: 2; /* above the panel grain and light */
  height: clamp(6rem, min(100cqh - var(--room), (100cqw - 3rem) * 1.4167), max(32rem, 64cqh));
  aspect-ratio: 240 / 340;
  cursor: pointer;
  will-change: translate;
  transition: --lid 0.12s var(--ease-out), --blink 0.12s var(--ease-out);
  -webkit-tap-highlight-color: transparent;
}

[data-fig="mentee"] {
  --ground-c: #1f3a2d;
  --lid-travel: 22px;
  --hop: 0.78s; /* one hop; js/characters.js times the waving arms to it */
}

[data-fig="mentor"] {
  --ground-c: #4a3420;
  --lid-travel: 23px;
  --lid: 0.14;
}

.character > svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* Motion pivots SVG parts around their own box by default; these parts are drawn around
   points in the character's coordinate space (their inline transform-origin), so keep that. */
.arm, .wag, .nod, .ear, .arms-folded, .arms-prayer, .tail-swish { transform-box: view-box !important; }
.m-yawn { transform-box: fill-box !important; transform-origin: 50% 0 !important; }

/* Shading layers */
.shade { opacity: var(--shade); }
.spec { opacity: var(--spec); }
.spec-soft { opacity: calc(var(--spec) * 0.5); }
.rim { opacity: var(--rim-o); transition: opacity 0.3s; }
[data-state="hover"] .rim { opacity: calc(var(--rim-o) * 1.7); }

/* Ground: a wide ambient shadow plus a tight contact under each foot. */
.gshadow, .contact { fill: var(--ground-c); }
.gshadow { transform-origin: 124px 334px; }
.contact { opacity: 0.42; transform-origin: 120px 334px; transition: opacity 0.3s ease; }

/* Idle life. Shifu breathes slower than Po, and out of step with him. */
.bob { animation: bob 3.4s ease-in-out infinite; transform-origin: 120px 336px; }
.torso, .breath { transform-origin: 120px 330px; }
.breath { animation: breath 3.4s ease-in-out infinite; transition: transform 0.4s ease; }
.tail { transform-origin: 150px 296px; animation: sway 3.8s ease-in-out infinite; }
[data-fig="mentor"] .bob { animation-duration: 4.4s; }
[data-fig="mentor"] .breath { animation-duration: 4.4s; animation-delay: -1.3s; }
.whiskers { transform-origin: 120px 150px; animation: whisk 4.4s ease-in-out -0.6s infinite; }

/* Face */
.eye, .brows { transition: transform 0.18s ease; }
.brow-l, .brow-r { transition: transform 0.5s var(--ease-out); }
[data-fig="mentee"] .brows { transform-origin: 120px 94px; }
.pupil { transition: transform 0.06s linear; }
.lid { transform: translateY(calc((max(var(--lid), var(--blink)) - 1) * var(--lid-travel))); }
.blink { --blink: 1; transition: --lid 0.12s var(--ease-out), --blink 0.06s var(--ease-in); }
.m-smile, .m-yawn, .m-smirk { display: none; }
.lids, .aura { opacity: 0; transition: opacity 0.4s ease; }
.lid-line { transition: opacity 0.4s ease; }
.arms-folded, .arms-prayer, .palm { opacity: 0; }
.palm { transition: opacity 0.15s ease; }
.aura { transform-box: fill-box; transform-origin: center; }

/* Mouths grow into place rather than popping. */
.m-smile, .m-smirk { transform-box: fill-box; transform-origin: 50% 0; animation: mouth-in 0.16s var(--ease-out); }
.m-rest { transform-box: fill-box; transform-origin: 50% 50%; animation: mouth-rest-in 0.14s var(--ease-out); }
[data-fig="mentor"] .m-smirk { transform-origin: 0 50%; animation-name: smirk-in; }

/* Near: eyebrows lift, and Shifu's lids open a touch. */
[data-state="near"] .brows,
[data-fig="mentee"][data-state="hover"] .brows { transform: translateY(-4px); }
[data-fig="mentor"][data-state="near"] { --lid: 0.04; }

/* Po on hover (a click keeps the hover state until the page changes): wide eyes, open smile, palms
   out, hopping. The hop crouches, launches, hangs at the top and lands with a squash; the shadows
   and belly keep the same beat. It takes off from wherever the idle bob was (--bob-from, added on
   top for the first moments), and once he stops, the hop in the air lands first (.landing, until
   the loop comes round; js/characters.js). */
[data-fig="mentee"][data-state="hover"] .eye { transform: scale(1.15); }
[data-fig="mentee"][data-state="hover"] .m-smile { display: inline; }
[data-fig="mentee"][data-state="hover"] .m-rest { display: none; }
[data-fig="mentee"][data-state="hover"] .palm,
[data-fig="mentee"][data-fidget="wave"] .arm-r .palm { opacity: 0.9; transition-delay: 0.08s; }
[data-fig="mentee"]:is([data-state="hover"], .landing) .bob {
  animation: hop var(--hop) infinite, bob-from calc(var(--hop) * 0.22) var(--ease-out);
  animation-composition: replace, add;
}
[data-fig="mentee"]:is([data-state="hover"], .landing) .gshadow { animation: shad var(--hop) infinite; }
[data-fig="mentee"]:is([data-state="hover"], .landing) .breath { animation: belly var(--hop) infinite; transform-origin: 120px 324px; }
[data-fig="mentee"]:is([data-state="hover"], .landing) .contact { animation: contact-hop var(--hop) infinite; }

/* Shifu on hover or click: arms folded, lids half down, one brow up, a smirk and a puffed-out chest. */
[data-fig="mentor"][data-state="hover"] { --lid: 0.4; }
[data-fig="mentor"][data-state="hover"] .brow-l { transform: rotate(-12deg) translateY(-3px); }
[data-fig="mentor"][data-state="hover"] .m-smirk { display: inline; }
[data-fig="mentor"][data-state="hover"] .m-rest { display: none; }
[data-fig="mentor"][data-state="hover"] .breath { animation: none; transform: scale(1.04, 1.02); }

/* The other character watches and smiles along. */
[data-peer="watching"] .m-smile { display: inline; }
[data-peer="watching"] .m-rest { display: none; }

/* Po's fidgets: the face side of each one. */
[data-fidget="yawn"] { --lid: 1; transition: --lid 0.5s ease-in-out, --blink 0.12s var(--ease-out); }
[data-fidget="yawn"] .m-rest { display: none; }
[data-fidget="yawn"] .m-yawn { display: inline; }
[data-fidget="yawn"] .brows { transform: translateY(3px) scaleX(0.94); }
[data-fidget="yawn"] .breath { animation: yawn-breath 2.6s ease-in-out; }
/* After a yawn, finished or cut short: the lids open slowly and the chest settles from where it was. */
.post-yawn { transition: --lid 0.5s var(--ease-out), --blink 0.12s var(--ease-out); }
.post-yawn .breath { animation: breath-from 0.5s var(--ease-out); }
[data-fidget="look"] .brows { transform: translateY(-5px); }
[data-fidget="look"] .eye { transform: scale(1.08); }
[data-fidget="wave"] .m-smile { display: inline; }
[data-fidget="wave"] .m-rest { display: none; }

/* Shifu's meditation: eyes closed to one serene arc, brows eased, prayer hands, rising (from
   wherever the bob was) into a slow float on deep breaths, with a soft glow. */
[data-fidget="meditate"] { --lid: 1; transition: --lid 0.9s ease-in-out, --blink 0.12s var(--ease-out); }
[data-fidget="meditate"] .lids { opacity: 1; }
[data-fidget="meditate"] .lid-line { opacity: 0; }
[data-fidget="meditate"] .brow-l { transform: rotate(-17deg) translateY(-1px); }
[data-fidget="meditate"] .brow-r { transform: rotate(17deg) translateY(-1px); }
[data-fidget="meditate"] .m-smile { display: none; }
[data-fidget="meditate"] .m-rest { display: inline; }
[data-fidget="meditate"] .bob { animation: rise 1.2s cubic-bezier(0.45, 0, 0.55, 1) both, levitate 5s 1.2s ease-in-out infinite; }
[data-fidget="meditate"] .gshadow { animation: shadow-rise 1.2s cubic-bezier(0.45, 0, 0.55, 1) both, float-shadow 5s 1.2s ease-in-out infinite; }
[data-fidget="meditate"] .contact { opacity: 0.06; transition: opacity 1.2s ease; }
[data-fidget="meditate"] .breath { animation: breath-deep 5s ease-in-out infinite; }
[data-fidget="meditate"] .tail { animation-duration: 7s; }
[data-fidget="meditate"] .aura { opacity: 1; animation: aura 5s ease-in-out infinite; transition-duration: 1.6s; }

/* Waking: the eyes snap open, he drops from wherever he had floated to with a squash, then settles,
   and the idle bob starts again from the ground. */
[data-fig="mentor"].waking { --lid: 0; transition: --lid 0.07s ease-out, --blink 0.06s var(--ease-in); }
[data-fig="mentor"].waking .bob { animation: wake-drop 0.55s 1; }
.waking .eye { transform: scale(1.18); transition-duration: 0.08s; }
.waking :is(.brow-l, .brow-r) { transform: translateY(-5px); transition-duration: 0.08s; }
.waking .aura { transition-duration: 0.15s; }
.waking .contact { opacity: 0.42; transition: opacity 0.2s 0.2s; }

/* Notice: a quick brow flick (and wide eyes for Po) when the cursor crosses onto a character's side. */
.notice .brows { transform: translateY(-6px); transition-duration: 0.12s; }
[data-fig="mentee"].notice .eye { transform: scale(1.1); transition-duration: 0.12s; }

/* Covered by the How-it-works sheet: everything holds still (js/characters.js sets the flag). */
.hero[data-asleep] .character * { animation-play-state: paused !important; }

/* The cursor ring, over the stage only (js/characters.js places it with `translate`, so `scale` here
   grows it in place). It takes the ink of the side of the tear it is on, swells over a character,
   steps aside over a button, and gives under a press. */
.cursor-ring {
  --ring-rgb: var(--student-rgb);
  position: fixed;
  z-index: 50;
  top: 0;
  left: 0;
  width: 1.75rem;
  height: 1.75rem;
  border: 1.5px solid rgb(var(--ring-rgb) / 50%);
  border-radius: 50%;
  background-color: rgb(var(--ring-rgb) / 0%);
  pointer-events: none;
  opacity: 0;
  transition: opacity 200ms, scale 320ms var(--ease-out), border-color 240ms, background-color 240ms;
}

.cursor-ring.on { opacity: 1; }
.cursor-ring[data-side="mentor"] { --ring-rgb: var(--mentor-rgb); }

.cursor-ring[data-over="character"] {
  scale: 1.6;
  border-color: rgb(var(--ring-rgb) / 30%);
  background-color: rgb(var(--ring-rgb) / 8%);
}

.cursor-ring.on[data-over="button"] { opacity: 0; scale: 0.4; }
.cursor-ring.is-pressed { scale: 0.82; }
.cursor-ring[data-over="character"].is-pressed { scale: 1.4; }

@keyframes bob { 50% { transform: translateY(-3px); } }
@keyframes breath { 50% { transform: scale(1.015, 1.02); } }
@keyframes breath-deep { 50% { transform: scale(1.03, 1.04); } }
@keyframes sway { 50% { transform: rotate(5deg); } }
@keyframes whisk { 50% { transform: rotate(1.6deg); } }

/* The hop: crouch 12%, launch 22%, apex 46%, fall 66%, impact squash 72%, rebound 86%. */
@keyframes hop {
  0% { transform: translateY(0) scale(1, 1); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); }
  12% { transform: translateY(1px) scale(1.07, 0.91); animation-timing-function: cubic-bezier(0.2, 0.7, 0.3, 1); }
  22% { transform: translateY(-11px) scale(0.95, 1.07); animation-timing-function: cubic-bezier(0.2, 0.6, 0.35, 1); }
  46% { transform: translateY(-26px) scale(0.99, 1.01); animation-timing-function: cubic-bezier(0.55, 0, 0.85, 0.45); }
  66% { transform: translateY(-5px) scale(0.96, 1.05); animation-timing-function: linear; }
  72% { transform: translateY(0) scale(1.09, 0.9); animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1); }
  86% { transform: translateY(0) scale(0.985, 1.015); animation-timing-function: ease-in-out; }
  100% { transform: none; }
}

/* The ambient shadow shrinks and fades at the apex and spreads on impact. */
@keyframes shad {
  0% { transform: none; opacity: 0.2; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); }
  12% { transform: scale(1.04, 1); opacity: 0.22; animation-timing-function: cubic-bezier(0.2, 0.7, 0.3, 1); }
  22% { transform: scale(0.9); opacity: 0.17; animation-timing-function: cubic-bezier(0.2, 0.6, 0.35, 1); }
  46% { transform: scale(0.66); opacity: 0.09; animation-timing-function: cubic-bezier(0.55, 0, 0.85, 0.45); }
  66% { transform: scale(0.92); opacity: 0.16; animation-timing-function: linear; }
  72% { transform: scale(1.08, 1); opacity: 0.24; animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1); }
  86% { transform: none; opacity: 0.2; animation-timing-function: ease-in-out; }
  100% { transform: none; opacity: 0.2; }
}

/* The contact darkens and spreads in the crouch, is gone while airborne, and returns on landing. */
@keyframes contact-hop {
  0% { transform: none; opacity: 0.42; animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); }
  12% { transform: scaleX(1.12); opacity: 0.55; animation-timing-function: cubic-bezier(0.2, 0.7, 0.3, 1); }
  22% { transform: scaleX(0.9); opacity: 0.2; }
  30%, 60% { transform: scaleX(0.7); opacity: 0; }
  72% { transform: scaleX(1.16); opacity: 0.6; animation-timing-function: cubic-bezier(0.2, 0.8, 0.3, 1); }
  86% { transform: none; opacity: 0.45; }
  100% { transform: none; opacity: 0.42; }
}

/* The belly keeps moving a moment after the feet land. */
@keyframes belly {
  0%, 70%, 100% { transform: none; }
  77% { transform: scale(1.035, 0.955); }
  85% { transform: scale(0.985, 1.02); }
  93% { transform: scale(1.006, 0.996); }
}

@keyframes yawn-breath { 40%, 65% { transform: scale(1.04, 1.05); } }

/* Settling from a part's last pose (--bob-from / --breath-from, read by js/characters.js). */
@keyframes bob-from {
  from { transform: var(--bob-from, none); }
  to { transform: none; }
}
@keyframes breath-from { from { transform: var(--breath-from, none); } }

/* Meditation: rise into the float, then drift between -9 and -13 without a pop at the hand-over. */
@keyframes rise {
  from { transform: var(--bob-from, none); }
  to { transform: translateY(-9px); }
}
@keyframes levitate {
  0%, 100% { transform: translateY(-9px); }
  50% { transform: translateY(-13px); }
}
@keyframes shadow-rise { to { transform: scale(0.86); opacity: 0.15; } }
@keyframes float-shadow {
  0%, 100% { transform: scale(0.86); opacity: 0.15; }
  50% { transform: scale(0.8); opacity: 0.12; }
}
@keyframes aura {
  0%, 100% { transform: scale(0.94); }
  50% { transform: scale(1.04); }
}

@keyframes wake-drop {
  0% { transform: var(--bob-from, translateY(-11px)); animation-timing-function: ease-in; }
  45% { transform: translateY(0) scale(1.05, 0.94); animation-timing-function: ease-out; }
  70% { transform: translateY(-3px) scale(0.98, 1.03); }
  100% { transform: none; }
}

@keyframes mouth-in { from { transform: scale(0.7, 0.25); } }
@keyframes smirk-in { from { transform: scaleX(0.5); } }
@keyframes mouth-rest-in { from { transform: scaleX(0.75); } }

@media (hover: none), (pointer: coarse) {
  .cursor-ring { display: none; }
}

/* Reduced motion: no loops, beats or growing mouths; the lids, brows and poses still change state. */
@media (prefers-reduced-motion: reduce) {
  .bob, .breath, .tail, .gshadow, .contact, .aura, .ear, .whiskers, .m-smile, .m-smirk, .m-rest { animation: none !important; }
  .character { transition: none; }
  .waking .eye { transform: none; }
  .cursor-ring { transition-property: opacity, border-color, background-color; }
}
