/* AI motion (docs/motion.md): the aura on the region the model is working on and the desk
   analyst's state mark. Nothing here matches until aura.js runs with the bootstrap's
   features.ai_motion on: every rule is keyed to an element or attribute aura.js adds.
   Only opacity and transform animate. Color comes from the page's own tokens (--brand,
   --neg, the --glow stops), so both themes follow the toggle. */

/* ── Motion tokens: used by the aura and the state mark, nothing else yet ──
   Registered as <time> so aura.js reads the resolved value (scaled for review). */
@property --motion-fast { syntax: '<time>'; inherits: true; initial-value: 120ms; }
@property --motion-enter { syntax: '<time>'; inherits: true; initial-value: 200ms; }
@property --motion-exit { syntax: '<time>'; inherits: true; initial-value: 240ms; }
@property --motion-settle { syntax: '<time>'; inherits: true; initial-value: 560ms; }
@property --motion-hold { syntax: '<time>'; inherits: true; initial-value: 1800ms; }
@property --motion-cycle { syntax: '<time>'; inherits: true; initial-value: 1600ms; }
:root {
  --motion-scale: 1; /* review multiplier: the storyboard's slow mode sets 4 */
  --motion-fast: calc(120ms * var(--motion-scale));
  --motion-enter: calc(200ms * var(--motion-scale));
  --motion-exit: calc(240ms * var(--motion-scale));
  --motion-settle: calc(560ms * var(--motion-scale));
  --motion-hold: calc(1800ms * var(--motion-scale));
  --motion-cycle: calc(1600ms * var(--motion-scale));
  --motion-ease-enter: cubic-bezier(0, 0, .2, 1);
  --motion-ease-exit: cubic-bezier(.4, 0, 1, 1);
  --motion-ease-standard: cubic-bezier(.2, 0, 0, 1);
  --motion-ease-loop: cubic-bezier(.45, 0, .55, 1);
  --motion-scan-width: 40%;
  --motion-scan-travel: 150%;
  --motion-scan-peak: .55;
  --motion-rise: 3px;
  --motion-curve-low: .55;
  --motion-breathe-low: .6;
  --motion-ring: 1.5px;
  --motion-glow-blur: 14px;
  --motion-glow: 22%;
  --motion-accent: var(--brand);
  --motion-error: var(--neg);
}
[data-theme="dark"] { --motion-glow: 32%; }

/* ── The aura: an overlay inside the region (aura.js gives a static region position) ── */
.ai-aura {
  --ai-ring: var(--motion-accent);
  --ai-glow: color-mix(in srgb, var(--motion-accent) var(--motion-glow), transparent);
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  pointer-events: none;
  contain: layout style;
}
.ai-aura::before, .ai-aura::after { content: ""; position: absolute; box-sizing: border-box; pointer-events: none; }
/* The ring and the soft glow, inside and outside the border. */
.ai-aura::before {
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 var(--motion-ring) var(--ai-ring), inset 0 0 var(--motion-glow-blur) 0 var(--ai-glow), 0 0 var(--motion-glow-blur) 0 var(--ai-glow);
  will-change: opacity;
}
/* The scan: a light that crosses the top edge while the work runs. */
.ai-aura::after {
  top: 0;
  left: 0;
  width: var(--motion-scan-width);
  height: 2px;
  background: linear-gradient(90deg, transparent, var(--glow-a, var(--ai-ring)), var(--glow-b, var(--ai-ring)), var(--glow-c, var(--ai-ring)), transparent);
  opacity: 0;
  will-change: transform, opacity;
}

.ai-aura[data-state="working"] { animation: ai-in var(--motion-enter) var(--motion-ease-enter) both; }
.ai-aura[data-state="working"]::before { animation: ai-breathe calc(var(--motion-cycle) / 2) var(--motion-ease-loop) var(--ai-phase, 0ms) infinite alternate; }
.ai-aura[data-state="working"]::after { animation: ai-scan var(--motion-cycle) var(--motion-ease-loop) var(--ai-phase, 0ms) infinite; }
/* Done: the ring brightens once and fades. */
.ai-aura[data-state="settle"] { animation: ai-settle var(--motion-settle) var(--motion-ease-exit) both; }
.ai-aura[data-state="settle"]::before { box-shadow: inset 0 0 0 var(--motion-ring) var(--ai-ring), inset 0 0 calc(var(--motion-glow-blur) * 1.6) 0 var(--ai-glow); }
/* Error: a still, dashed ring in the loss color that holds, then fades. */
.ai-aura[data-state="error"] {
  --ai-ring: var(--motion-error);
  --ai-glow: color-mix(in srgb, var(--motion-error) 16%, transparent);
  animation: ai-hold var(--motion-hold) var(--motion-ease-exit) both;
}
.ai-aura[data-state="error"]::before { border: var(--motion-ring) dashed var(--ai-ring); box-shadow: inset 0 0 var(--motion-glow-blur) 0 var(--ai-glow); }
/* Canceled or moved away: the glow fades with no settle. */
.ai-aura[data-state="cancel"] { animation: ai-out var(--motion-exit) var(--motion-ease-exit) both; }
.ai-aura:is([data-state="settle"], [data-state="error"], [data-state="cancel"])::after { display: none; }
/* Stacking: a working region inside another keeps the motion; the outer one holds a still ring. */
[data-ai-aura="working"]:has([data-ai-aura="working"]) > .ai-aura::before { animation: none; opacity: var(--motion-breathe-low); }
[data-ai-aura="working"]:has([data-ai-aura="working"]) > .ai-aura::after { display: none; }

/* The desk's pending answer: a slot that gives the glow room without moving the layout;
   the answer it becomes has no padding, so its settle ring stands the same distance off. */
.ask-pending { display: grid; gap: 12px; margin: -8px -10px; padding: 8px 10px; border-radius: var(--r, 4px); }
.ask-a > .ai-aura { inset: -8px -10px; border-radius: var(--r, 4px); }
.ask-drv > .ai-aura { inset: -8px -10px 6px; border-radius: var(--r, 4px); }
#scrCoverage > .ai-aura { inset: -4px -8px; border-radius: var(--r, 4px); }

/* ── The desk analyst's state mark, in place of the title's dot ── */
.ask-title.has-ai-mark::before { display: none; }
.ai-mark { position: relative; display: inline-block; flex: none; width: 15px; height: 15px; }
/* Three stacked SVG layers (aura.js render): only whole layers move, which Chrome composites;
   an animated part inside an SVG would lay the page out every frame. */
.ai-mark svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
.ai-mark :is(.ai-chip, .ai-curve, .ai-cap) { fill: none; stroke: var(--motion-accent); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.ai-mark .ai-wash { fill: var(--motion-accent); opacity: 0; transition: opacity var(--motion-fast) var(--motion-ease-standard); }
.ai-mark-curve { transform-origin: 50% 72%; transition: transform var(--motion-enter) var(--motion-ease-standard), opacity var(--motion-fast) var(--motion-ease-standard); }
.ai-mark .ai-peak { fill: var(--motion-accent); }
.ai-mark-peak { transition: opacity var(--motion-fast) var(--motion-ease-standard); }
.ai-mark .ai-cap { opacity: 0; }
.ai-presence-label {
  font: 500 11px/1 var(--mono);
  letter-spacing: 0;
  text-transform: none;
  color: var(--muted);
  white-space: nowrap;
  transition: opacity var(--motion-fast) var(--motion-ease-standard), transform var(--motion-enter) var(--motion-ease-enter);
}
[data-ai-state="idle"] .ai-presence-label { opacity: 0; transform: translateY(var(--motion-rise)); }
[data-ai-state="thinking"] .ai-mark-curve { animation: ai-curve calc(var(--motion-cycle) / 2) var(--motion-ease-loop) infinite alternate; }
[data-ai-state="thinking"] .ai-mark-peak { animation: ai-peak calc(var(--motion-cycle) / 2) var(--motion-ease-loop) infinite alternate; }
[data-ai-state="answering"] .ai-wash, [data-ai-state="done"] .ai-wash { opacity: .22; }
[data-ai-state="done"] .ai-mark { animation: ai-pop var(--motion-settle) var(--motion-ease-standard); }
[data-ai-state="error"] :is(.ai-chip, .ai-curve) { stroke: var(--motion-error); }
[data-ai-state="error"] .ai-chip { stroke-dasharray: 2.4 1.8; }
[data-ai-state="error"] .ai-mark-curve { transform: scaleY(.25); }
[data-ai-state="error"] .ai-mark-peak, [data-ai-state="offline"] :is(.ai-mark-curve, .ai-mark-peak), [data-ai-state="limit"] .ai-mark-peak { opacity: 0; }
[data-ai-state="limit"] .ai-mark-curve { opacity: .4; }
[data-ai-state="limit"] .ai-cap { opacity: 1; }
[data-ai-state="offline"] .ai-chip { stroke: var(--muted); stroke-dasharray: 2.4 1.8; }
[data-ai-state="error"] .ai-presence-label { color: var(--motion-error); }

/* Keyframes hold literal values: a var() in a keyframe keeps the animation off the
   compositor. scripts/tests/aura.test.cjs keeps each literal equal to its token. */
@keyframes ai-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ai-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes ai-breathe { from { opacity: .6; } to { opacity: 1; } }
@keyframes ai-scan {
  0% { opacity: 0; transform: translateX(0); }
  20%, 80% { opacity: .55; }
  100% { opacity: 0; transform: translateX(150%); }
}
@keyframes ai-settle { 0%, 35% { opacity: 1; } 100% { opacity: 0; } }
@keyframes ai-hold { 0%, 85% { opacity: 1; } 100% { opacity: 0; } }
@keyframes ai-curve { from { transform: scaleY(.55); } to { transform: scaleY(1); } }
/* The peak rides the curve's top: at scaleY(.55) about 72% of the 15px mark it sits 2.16px lower. */
@keyframes ai-peak { from { opacity: .35; transform: translateY(2.16px); } to { opacity: 1; transform: none; } }
@keyframes ai-pop { 30% { transform: scale(1.1); } }

/* A hidden tab pauses every loop (aura.js sets the attribute on visibilitychange). */
[data-ai-motion-paused] :is(.ai-aura, .ai-mark, .ai-mark *),
[data-ai-motion-paused] .ai-aura::before,
[data-ai-motion-paused] .ai-aura::after { animation-play-state: paused; }

/* Reduced motion: a still, highlighted ring and the same states, with nothing moving. */
@media (prefers-reduced-motion: reduce) {
  .ai-aura, .ai-aura::before, .ai-aura::after, .ai-mark, .ai-mark *, .ai-presence-label { animation: none !important; transition: none !important; }
  .ai-aura::after { display: none; }
  [data-ai-state] .ai-presence-label { transform: none; }
}
