/* state-keyframes.css — the Maind X state set (A1..E5): the spec states plus a
   clock (A4_clock) and three height-only expressions (E3..E5).
   Per state: class .st-<ID> on the .stage; animations on the 3 layers
   (.eyes = group, .eye = per-eye, .dot = blink/dim).
   Loops (A3, B1, B2, B3): seamless, 0% == 100%.
   One-shots: the real action sits at the start of the cycle, then hold at
   rest and a quick reset at the end — that's just the preview convention;
   on the device you use only the action window noted in each comment.
   All durations scale with --spd (global multiplier). */

/* ============ A · System & power ============ */

/* === A1_off — Off — static: dots off, dark screen === */
.st-A1_off .dot { opacity: 0; }
.st-A1_off .eye { filter: none; }

/* === A2_wake — Wake — one-shot: ~700ms fade-in + one slow waking blink.
   Action: 0–37% of the 4000ms cycle (~1.5s total). === */
@keyframes A2w-grp {
  0%        { opacity: 0; transform: scale(.92); }
  17%       { opacity: 1; transform: none; }
  90%       { opacity: 1; transform: none; }
  97%, 100% { opacity: 0; transform: scale(.92); }
}
@keyframes A2w-dot {
  0%, 21%   { transform: scaleY(1); }
  26%       { transform: scaleY(.08); }   /* slow close ~200ms */
  28.5%     { transform: scaleY(.08); }   /* sleepy micro pause */
  34%       { transform: scaleY(1.05); }  /* soft reopen */
  37%, 100% { transform: scaleY(1); }
}
.st-A2_wake .eyes {
  animation-name: A2w-grp;
  animation-duration: calc(var(--spd) * 4000ms);
  animation-timing-function: cubic-bezier(.22, 1, .36, 1);
}
.st-A2_wake .dot {
  animation-name: A2w-dot;
  animation-duration: calc(var(--spd) * 4000ms);
  animation-timing-function: cubic-bezier(.45, 0, .3, 1);
}

/* === A3_idle — Idle — loop: ±8% breath over ~4.6s + natural blink every 5–8s
   (13s blink track with two blinks: ~6.3s / ~6.7s intervals). === */
@keyframes A3i-grp {
  0%   { transform: scale(1); }
  50%  { transform: scale(1.08); }
  100% { transform: scale(1); }
}
@keyframes A3i-dot {
  0%, 38.5%  { transform: scaleY(1); }
  39.06%     { transform: scaleY(.07); }   /* ~73ms close + ~73ms open = ~146ms (spec: crisp) */
  39.62%     { transform: scaleY(1); }
  86.4%      { transform: scaleY(1); }
  86.96%     { transform: scaleY(.07); }
  87.52%     { transform: scaleY(1); }
  100%       { transform: scaleY(1); }
}
.st-A3_idle .eyes {
  animation-name: A3i-grp;
  animation-duration: calc(var(--spd) * 4600ms);
  animation-timing-function: ease-in-out;
}
.st-A3_idle .dot {
  animation-name: A3i-dot;
  animation-duration: calc(var(--spd) * 13000ms);
  animation-timing-function: cubic-bezier(.4, 0, .6, 1);
}
/* continuous look-around drift (EMO is never perfectly still): a slow gaze that
   eases left, holds, comes back, eases right — on the .eye layer so it has its
   OWN 7.2s rhythm independent of the 4.6s breath and 13s blink. ±11–12px. */
@keyframes A3i-drift {
  0%   { transform: translateX(0); }
  18%  { transform: translateX(-11px); }
  32%  { transform: translateX(-11px); }   /* small hold looking left */
  50%  { transform: translateX(0); }
  68%  { transform: translateX(12px); }
  82%  { transform: translateX(12px); }     /* small hold looking right */
  100% { transform: translateX(0); }
}
.st-A3_idle .eye {
  animation-name: A3i-drift;
  animation-duration: calc(var(--spd) * 7200ms);
  animation-timing-function: ease-in-out;
}

/* === A4_clock — Clock — display: the eyes give way to a live glowing digital
   clock (HH:MM, colon blinking once per second). The "on the shelf" idle face.
   No eye motion — states.js feeds the real time into every .clock element. === */
.st-A4_clock .eyes  { display: none; }
.st-A4_clock .clock { display: flex; }
@keyframes clkColon {
  0%, 45%   { opacity: 1; }
  55%, 100% { opacity: .12; }     /* the colon ticks: 1 Hz, independent of --spd feel */
}
.st-A4_clock .clock .cl {
  animation-name: clkColon;
  animation-duration: 1000ms;
  animation-iteration-count: infinite;
  animation-timing-function: steps(1, end);
}

/* ============ B · Conversation turn-taking ============ */

/* === B1_speaking — Speaking — loop: breath at speech rhythm (simulated TTS
   envelope, irregular peaks <=8%), tiny drift, blink every ~6s on its own track. === */
@keyframes B1s-grp {
  0%   { transform: scale(1)     translate(0, 0); }
  7%   { transform: scale(1.055) translate(0, -1.5px); }
  13%  { transform: scale(1.02)  translate(.5px, 0); }
  21%  { transform: scale(1.075) translate(1px, -2px); }
  28%  { transform: scale(1.03)  translate(.5px, -.5px); }
  36%  { transform: scale(1.06)  translate(0, -1px); }
  44%  { transform: scale(1.015) translate(-.5px, 0); }
  52%  { transform: scale(1.08)  translate(-1px, -2px); }
  60%  { transform: scale(1.035) translate(-.5px, -.5px); }
  68%  { transform: scale(1.07)  translate(0, -1.5px); }
  76%  { transform: scale(1.02)  translate(.5px, 0); }
  84%  { transform: scale(1.06)  translate(.5px, -1px); }
  92%  { transform: scale(1.025) translate(0, 0); }
  100% { transform: scale(1)     translate(0, 0); }
}
@keyframes B1s-dot {
  0%, 70% { transform: scaleY(1); }
  71.2%   { transform: scaleY(.07); }
  72.4%   { transform: scaleY(1); }
  100%    { transform: scaleY(1); }
}
.st-B1_speaking .eyes {
  animation-name: B1s-grp;
  animation-duration: calc(var(--spd) * 3400ms);
  animation-timing-function: ease-in-out;
}
.st-B1_speaking .dot {
  animation-name: B1s-dot;
  animation-duration: calc(var(--spd) * 6100ms);
  animation-timing-function: cubic-bezier(.4, 0, .6, 1);
}

/* === B2_listening — Listening — loop: dots +12%, HELD STILL, rare blink
   (1 every ~9s). The stillness is the signal: your turn. === */
.st-B2_listening .eyes { transform: scale(1.12); }
@keyframes B2l-dot {
  0%, 54% { transform: scaleY(1); }
  54.8%   { transform: scaleY(.07); }   /* ~72ms close + ~72ms open = ~144ms (crisp) */
  55.6%   { transform: scaleY(1); }
  100%    { transform: scaleY(1); }
}
.st-B2_listening .dot {
  animation-name: B2l-dot;
  animation-duration: calc(var(--spd) * 9000ms);
  animation-timing-function: cubic-bezier(.4, 0, .6, 1);
}

/* === B3_thinking — Thinking/processing — loop ~2s: the gaze slips away and
   comes back, with a slow half-blink on a 3.4s track. No audio sync. === */
@keyframes B3t-grp {
  0%   { transform: none; }
  28%  { transform: translate(10px, -7px); }
  55%  { transform: translate(11px, -6px); }   /* micro-hold "elsewhere" */
  78%  { transform: none; }
  100% { transform: none; }
}
@keyframes B3t-dot {
  0%, 42% { transform: scaleY(1); }
  47%     { transform: scaleY(.3); }    /* slow half blink */
  49%     { transform: scaleY(.3); }
  55%     { transform: scaleY(1); }
  100%    { transform: scaleY(1); }
}
.st-B3_thinking .eyes {
  animation-name: B3t-grp;
  animation-duration: calc(var(--spd) * 2000ms);
  animation-timing-function: cubic-bezier(.4, 0, .2, 1);
}
.st-B3_thinking .dot {
  animation-name: B3t-dot;
  animation-duration: calc(var(--spd) * 3400ms);
  animation-timing-function: ease-in-out;
}

/* ============ C · Feedback & confirmation ============ */

/* === C1_confirm — Confirm — one-shot: the eyes morph into a CHECK ✓, hold,
   then return to eyes. Action ~0–80% of the 2400ms cycle. === */
@keyframes C1eyes {            /* dots shrink + fade out while the check shows, then back */
  0%   { opacity: 1; transform: scale(1); }
  12%  { opacity: 0; transform: scale(.5); }
  80%  { opacity: 0; transform: scale(.5); }
  92%  { opacity: 1; transform: scale(1); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes C1check {           /* the check draws in, holds, fades out */
  0%   { stroke-dashoffset: 80; opacity: 0; }
  12%  { stroke-dashoffset: 80; opacity: 1; }
  40%  { stroke-dashoffset: 0;  opacity: 1; }
  70%  { stroke-dashoffset: 0;  opacity: 1; }
  82%  { stroke-dashoffset: 0;  opacity: 0; }
  100% { stroke-dashoffset: 0;  opacity: 0; }
}
.st-C1_confirm .check { display: flex; }
.st-C1_confirm .dot {
  animation-name: C1eyes;
  animation-duration: calc(var(--spd) * 2400ms);
  animation-timing-function: cubic-bezier(.4, 0, .3, 1);
}
.st-C1_confirm .check path {
  animation-name: C1check;
  animation-duration: calc(var(--spd) * 2400ms);
  animation-timing-function: cubic-bezier(.45, 0, .25, 1);
  animation-iteration-count: infinite;
  animation-fill-mode: both;
}

/* === C2_didnt_catch — Didn't catch that — one-shot ~600ms: slow blink + gentle
   tilt (left up, right down), a silent "hm?", then back. Action: 0–17% of 3600ms. === */
@keyframes C2d-eye {
  0%    { transform: none; }
  8%    { transform: translateY(var(--ty)); }   /* tilt in ~290ms */
  11%   { transform: translateY(var(--ty)); }   /* micro-hold */
  16.7% { transform: none; }                      /* back to rest (~600ms total) */
  100%  { transform: none; }
}
@keyframes C2d-dot {
  0%, 2% { transform: scaleY(1); }
  6.5%   { transform: scaleY(.1); }    /* slow blink (intentional), ~160ms close */
  11%    { transform: scaleY(1); }
  100%   { transform: scaleY(1); }
}
.st-C2_didnt_catch .eye {
  animation-name: C2d-eye;
  animation-duration: calc(var(--spd) * 3600ms);
  animation-timing-function: cubic-bezier(.34, 1.05, .5, 1);
}
.st-C2_didnt_catch .eye.left  { --ty: -13px; }
.st-C2_didnt_catch .eye.right { --ty: 13px; }
.st-C2_didnt_catch .dot {
  animation-name: C2d-dot;
  animation-duration: calc(var(--spd) * 3600ms);
  animation-timing-function: ease-in-out;
}

/* ============ D · Proactive moments ============ */

/* === D1_reminder — Reminder — one-shot: the eyes morph into a little BELL that
   RINGS (rotates back and forth), holds, then return to eyes. Calm, not alarming.
   Action ~0–82% of the 3200ms cycle. === */
@keyframes D1eyes {           /* eyes shrink + fade while the bell rings, then back */
  0%   { opacity: 1; transform: scale(1); }
  12%  { opacity: 0; transform: scale(.5); }
  82%  { opacity: 0; transform: scale(.5); }
  94%  { opacity: 1; transform: scale(1); }
  100% { opacity: 1; transform: scale(1); }
}
@keyframes D1ring {           /* bell appears, rings (damped swing), holds, fades */
  0%   { opacity: 0; transform: rotate(0); }
  10%  { opacity: 1; transform: rotate(0); }
  16%  { transform: rotate(17deg); }
  23%  { transform: rotate(-15deg); }
  30%  { transform: rotate(12deg); }
  37%  { transform: rotate(-9deg); }
  44%  { transform: rotate(6deg); }
  51%  { transform: rotate(-3deg); }
  58%  { transform: rotate(0); }
  82%  { opacity: 1; transform: rotate(0); }
  92%  { opacity: 0; }
  100% { opacity: 0; transform: rotate(0); }
}
.st-D1_reminder .bell { display: flex; }
.st-D1_reminder .dot {
  animation-name: D1eyes;
  animation-duration: calc(var(--spd) * 3200ms);
  animation-timing-function: cubic-bezier(.4, 0, .3, 1);
}
.st-D1_reminder .bell svg {
  animation-name: D1ring;
  animation-duration: calc(var(--spd) * 3200ms);
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-fill-mode: both;
}

/* === D2_wakeword — Wake word — one-shot ~400ms: attentive pop (scale up with
   overshoot) settling at the listening size (1.12 = B2).
   Action: 0–15% of 3400ms, holds the LISTEN pose until 86%. === */
@keyframes D2w-grp {
  0%   { transform: scale(1); }
  7%   { transform: scale(1.19); }
  11.8%{ transform: scale(1.095); }
  15%  { transform: scale(1.12); }
  86%  { transform: scale(1.12); }     /* holds the listening pose */
  96%  { transform: scale(1); }
  100% { transform: scale(1); }
}
@keyframes D2w-dot {
  0%   { filter: brightness(1); }
  7%   { filter: brightness(1.22); }   /* subtle attentive flash */
  16%  { filter: brightness(1); }
  100% { filter: brightness(1); }
}
.st-D2_wakeword .eyes {
  animation-name: D2w-grp;
  animation-duration: calc(var(--spd) * 3400ms);
  animation-timing-function: cubic-bezier(.22, 1.3, .36, 1);
}
.st-D2_wakeword .dot {
  animation-name: D2w-dot;
  animation-duration: calc(var(--spd) * 3400ms);
  animation-timing-function: ease-out;
}

/* ============ E · Personality / easter eggs ============ */

/* === E2_quirk — Idle micro-quirk — rare one-shot ~1.3s: slowly looks around
   (left, then right) and back to center, with one natural blink on the way home.
   Spec offers "double-blink OR look-around" — we take the look-around branch so
   it stays ~1s and reads as the slower, more distinctive quirk.
   Action: 0–30% of 4200ms. === */
@keyframes E2q-grp {
  0%   { transform: none; }
  9%   { transform: translate(-17px, 2px); }    /* look left */
  16%  { transform: translate(-15px, 3px); }    /* small hold */
  24%  { transform: translate(15px, -2px); }    /* look right */
  30%  { transform: none; }                      /* back to center (~1260ms) */
  100% { transform: none; }
}
@keyframes E2q-dot {
  0%, 26% { transform: scaleY(1); }
  27.7%   { transform: scaleY(.07); }    /* one natural blink ~143ms as it re-centers */
  29.4%   { transform: scaleY(1); }
  100%    { transform: scaleY(1); }
}
.st-E2_quirk .eyes {
  animation-name: E2q-grp;
  animation-duration: calc(var(--spd) * 4200ms);
  animation-timing-function: cubic-bezier(.4, 0, .2, 1);
}
.st-E2_quirk .dot {
  animation-name: E2q-dot;
  animation-duration: calc(var(--spd) * 4200ms);
  animation-timing-function: cubic-bezier(.4, 0, .6, 1);
}

/* === Listening notepad (B2) — fixed-time, not spd-scaled. White pad, spiral on
   the right; lines write themselves in from the BOTTOM upward. No hand. === */
.st-B2_listening .notes { display: block; }
@keyframes lineDraw {                   /* a line writes itself in, left→right (quick) */
  0%   { transform: scaleX(0); opacity: 0; }
  3%   { opacity: 1; }
  14%  { transform: scaleX(1); opacity: 1; }
  94%  { transform: scaleX(1); opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}
@keyframes penWrite {                   /* the pen writes each line, then steps UP to the next */
  0%   { transform: translate(0, 0)      rotate(26deg); }   /* bottom line, start */
  12%  { transform: translate(46px, 0)   rotate(26deg); }   /* sweep right */
  14%  { transform: translate(2px, -18px) rotate(26deg); }  /* carriage-return up a line */
  25%  { transform: translate(56px, -18px) rotate(26deg); }
  27%  { transform: translate(2px, -36px) rotate(26deg); }
  39%  { transform: translate(60px, -36px) rotate(26deg); }
  41%  { transform: translate(2px, -54px) rotate(26deg); }
  53%  { transform: translate(50px, -54px) rotate(26deg); } /* top line done */
  60%  { transform: translate(74px, -60px) rotate(18deg); } /* lift away */
  90%  { transform: translate(74px, -60px) rotate(18deg); } /* rest while the lines hold */
  100% { transform: translate(0, 0)      rotate(26deg); }   /* back down to start */
}
@keyframes noteBob {                    /* tiny lively bob of the whole pad */
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-3px); }
}
.st-B2_listening .notes { animation: noteBob 2.6s ease-in-out infinite; }
/* the lines fill from the BOTTOM upward, in sequence; the pen writes each one */
.st-B2_listening .ln    { animation: lineDraw 4.4s ease-in-out infinite both; }
.st-B2_listening .ln.l4 { animation-delay: 0s; }
.st-B2_listening .ln.l3 { animation-delay: .6s; }
.st-B2_listening .ln.l2 { animation-delay: 1.2s; }
.st-B2_listening .ln.l1 { animation-delay: 1.8s; }
.st-B2_listening .pen   { animation: penWrite 4.4s ease-in-out infinite both; }

/* === Ambient light feedback (Maind X §0) — EXTERNAL halo, animates --lumeLevel
   on the .stage box-shadow (outer glow). Speak pulses IN TIME with the eyes:
   same 3400 ms + same peak %s as the B1 breath. Never bouncy. === */
@keyframes lumIdle    { 0% { --lumeLevel: .10; } 50% { --lumeLevel: .26; } 100% { --lumeLevel: .10; } }
@keyframes lumSpeak   {   /* peaks aligned to B1s-grp (the speaking eye breath) */
  0%  { --lumeLevel: .20; }  7% { --lumeLevel: .46; } 13% { --lumeLevel: .28; }
  21% { --lumeLevel: .54; } 28% { --lumeLevel: .33; } 36% { --lumeLevel: .47; }
  44% { --lumeLevel: .26; } 52% { --lumeLevel: .56; } 60% { --lumeLevel: .35; }
  68% { --lumeLevel: .52; } 76% { --lumeLevel: .28; } 84% { --lumeLevel: .47; }
  92% { --lumeLevel: .30; } 100% { --lumeLevel: .20; } }
@keyframes lumListen  { 0% { --lumeLevel: .34; } 50% { --lumeLevel: .44; } 100% { --lumeLevel: .34; } }
@keyframes lumWake    { 0% { --lumeLevel: 0; } 28% { --lumeLevel: .40; } 45% { --lumeLevel: .22; } 100% { --lumeLevel: .22; } }
@keyframes lumConfirm { 0% { --lumeLevel: .18; } 9% { --lumeLevel: .52; } 26% { --lumeLevel: .20; } 100% { --lumeLevel: .20; } }
.st-A2_wake        { animation: lumWake    calc(var(--spd) * 4000ms) ease-out    infinite; }
.st-A3_idle,
.st-B3_thinking,
.st-E2_quirk,
.st-E4_wink        { animation: lumIdle    calc(var(--spd) * 4600ms) ease-in-out infinite; }
.st-B1_speaking    { animation: lumSpeak   calc(var(--spd) * 3400ms) ease-in-out infinite; }
.st-C2_didnt_catch { animation: lumSpeak   calc(var(--spd) * 3400ms) ease-in-out infinite; }
.st-B2_listening   { animation: lumListen  calc(var(--spd) * 3000ms) ease-in-out infinite; }
.st-D2_wakeword    { animation: lumListen  calc(var(--spd) * 3000ms) ease-in-out infinite; }
.st-C1_confirm     { animation: lumConfirm calc(var(--spd) * 2400ms) ease-in-out infinite; }
/* A1_off and A4_clock: no stage animation → --lumeLevel 0 = light off (quiet deactivated clock) */

/* ============ E · Personality — expression (height-only) ============ */

/* === E4_wink — Wink — one-shot ~2.6s: the right eye flattens to a line and
   holds shut while the left does a small sympathetic squint. Action: 0–56%. === */
@keyframes E4w-dotR {
  0%, 20% { transform: scaleY(1); }
  23%     { transform: scaleY(.07); }   /* fast snap shut */
  31%     { transform: scaleY(.07); }   /* short hold (was held far too long) */
  37%     { transform: scaleY(1); }     /* quick reopen */
  100%    { transform: scaleY(1); }
}
@keyframes E4w-dotL {
  0%, 18% { transform: scaleY(1); }
  30%     { transform: scaleY(.78); }   /* slight cheek-raise squint */
  46%     { transform: scaleY(.78); }
  56%     { transform: scaleY(1); }
  100%    { transform: scaleY(1); }
}
.st-E4_wink .eye.right .dot {
  animation-name: E4w-dotR;
  animation-duration: calc(var(--spd) * 2600ms);
  animation-timing-function: cubic-bezier(.4, 0, .35, 1);
}
.st-E4_wink .eye.left .dot {
  transform-origin: center bottom;
  animation-name: E4w-dotL;
  animation-duration: calc(var(--spd) * 2600ms);
  animation-timing-function: ease-in-out;
}
