/* Hushly Calm: motion. Keyframes and transitions only, all timed from the tokens.
   Nothing here scales past 1.05 apart from the listening orb (1.15 at most). Nothing flashes.

   Motion modes work through custom properties, so every rule below stays written once:
     Full    : the defaults on :root
     Gentle  : :root[data-motion="Gentle"] or the Gentle radio. Scale amounts are halved, ripples are dropped.
     Off     : :root[data-motion="Off"], the Off radio, or prefers-reduced-motion while nothing is chosen.
               Nothing scales or slides. Short opacity fades stay. Loops stop.
   The JavaScript sets data-motion on :root. While no attribute is set and no radio is chosen,
   the phone's reduce-motion setting decides. */

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

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

:root {
  --AnimScreenIn: ScreenIn;
  --AnimOrbBreath: OrbBreath;
  --AnimHalo: HaloBreath;
  --AnimRipple: RippleOut;
  --AnimOrbit: OrbitDots;
  --AnimLevel: LevelDemo;
  --AnimBlink: PipBlink;
  --AnimBreatheOrb: BreatheOrb;
  --AnimWave: WaveDrift;
  --AnimSleepZ: SleepZ;
  --AnimPulse: SkeletonPulse;
  --AnimRise: RiseIn;
  --AnimPop: IconPop;
  --AnimSceneIn: FadeIn;
  --RippleOn: 1;
  --DurScreenIn: var(--DurCalm);
  --DurTheme: var(--DurGentle);
  --DurStepCheck: 300ms;
  --TransitionTheme:
    background-color var(--DurTheme) var(--EaseBreathe),
    color var(--DurTheme) var(--EaseBreathe),
    border-color var(--DurTheme) var(--EaseBreathe);
}

:root[data-motion="Gentle"],
:root:not([data-motion]):has(input[name="Motion"][value="Gentle"]:checked) {
  --MotionScale: 0.5;
  --SlideDistance: 8px;
  --TapScale: 0.985;
  --RippleOn: 0;
  --AnimRipple: none;
}

:root[data-motion="Off"],
:root:not([data-motion]):has(input[name="Motion"][value="Off"]:checked) {
  --MotionScale: 0;
  --SlideDistance: 0px;
  --TapScale: 1;
  --TapOpacity: 0.85;
  --RippleOn: 0;
  --AnimRipple: none;
  --AnimOrbBreath: none;
  --AnimHalo: none;
  --AnimOrbit: none;
  --AnimLevel: none;
  --AnimBlink: none;
  --AnimBreatheOrb: none;
  --AnimWave: none;
  --AnimSleepZ: none;
  --AnimPulse: none;
  --AnimSceneIn: none;
  --DurScreenIn: var(--DurQuick);
  --DurTheme: var(--DurQuick);
  --DurStepCheck: 0ms;
}

@media (prefers-reduced-motion: reduce) {
  :root:not([data-motion]):not(:has(input[name="Motion"]:checked)) {
    --MotionScale: 0;
    --SlideDistance: 0px;
    --TapScale: 1;
    --TapOpacity: 0.85;
    --RippleOn: 0;
    --AnimRipple: none;
    --AnimOrbBreath: none;
    --AnimHalo: none;
    --AnimOrbit: none;
    --AnimLevel: none;
    --AnimBlink: none;
    --AnimBreatheOrb: none;
    --AnimWave: none;
    --AnimSleepZ: none;
    --AnimPulse: none;
  --AnimSceneIn: none;
    --DurScreenIn: var(--DurQuick);
    --DurTheme: var(--DurQuick);
    --DurStepCheck: 0ms;
  }
}

/* ---------- Keyframes ---------- */

@keyframes ScreenIn {
  from {
    opacity: 0;
    transform: translateY(var(--SlideDistance));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes FadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes RiseIn {
  from {
    opacity: 0;
    transform: translateY(var(--SlideDistance));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes IconPop {
  from {
    opacity: 0.6;
    transform: scale(calc(1 - 0.08 * var(--MotionScale)));
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Idle orb: 5s cycle, 2s in and 3s out. Easing is set per segment by animation-timing-function. */
@keyframes OrbBreath {
  0% { transform: scale(1); }
  40% { transform: scale(calc(1 + 0.05 * var(--MotionScale))); }
  100% { transform: scale(1); }
}

@keyframes HaloBreath {
  0% { opacity: 0.35; }
  40% { opacity: 0.6; }
  100% { opacity: 0.35; }
}

/* Listening ripples: scale out and fade, linear */
@keyframes RippleOut {
  from {
    transform: scale(1);
    opacity: calc(0.45 * var(--RippleOn));
  }
  to {
    transform: scale(calc(1 + 0.9 * var(--MotionScale)));
    opacity: 0;
  }
}

/* A stand-in for live loudness in the static prototype. The wired app sets --Level itself. */
@keyframes LevelDemo {
  0% { --Level: 0.15; }
  18% { --Level: 0.7; }
  30% { --Level: 0.35; }
  52% { --Level: 0.85; }
  64% { --Level: 0.3; }
  82% { --Level: 0.6; }
  100% { --Level: 0.15; }
}

/* The progress ring fills over 15s. Around 10s it turns from teal to sage. */
@keyframes ListenRing {
  0% { stroke-dashoffset: 1; stroke: var(--ColorPrimary); }
  64% { stroke: var(--ColorPrimary); }
  70% { stroke: var(--NeedWantsCloseness); }
  100% { stroke-dashoffset: 0; stroke: var(--NeedWantsCloseness); }
}

@keyframes ThinkShrink {
  from { transform: scale(1); }
  to { transform: scale(calc(1 - 0.15 * var(--MotionScale))); }
}

@keyframes OrbitDots {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

/* Pip's slow blink: a lid shape fades in over each eye */
@keyframes PipBlink {
  0%, 92%, 100% { opacity: 0; }
  94%, 96% { opacity: 1; }
}

/* Breathe: 4s in, 6s out, one 10s cycle */
@keyframes BreatheOrb {
  0% { transform: scale(var(--BreatheMin)); animation-timing-function: var(--EaseBreathe); }
  40% { transform: scale(1); animation-timing-function: var(--EaseBreathe); }
  100% { transform: scale(var(--BreatheMin)); }
}

@keyframes BreatheArc {
  0% { stroke-dashoffset: 1; }
  40% { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: 1; }
}

@keyframes CueIn {
  0%, 38% { opacity: 1; }
  42%, 98% { opacity: 0; }
  100% { opacity: 1; }
}

@keyframes CueOut {
  0%, 38% { opacity: 0; }
  42%, 98% { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes WaveDrift {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

@keyframes SleepZ {
  0% { opacity: 0; transform: translate(0, 8px); }
  40% { opacity: 1; transform: translate(0, 0); }
  85% { opacity: 1; transform: translate(4px, -10px); }
  100% { opacity: 0; transform: translate(6px, -14px); }
}

@keyframes SkeletonPulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.55; }
}

/* ---------- Screens ---------- */

.Screen {
  animation: var(--AnimScreenIn) var(--DurScreenIn) var(--EaseSettle);
}

.ResultScreen {
  animation: FadeIn var(--DurQuick) var(--EaseSettle);
}

/* The result card rises and fades in over 500ms. Rows follow 60ms apart. */
.ResultBody > * {
  animation: var(--AnimRise) 500ms var(--EaseSettle) backwards;
  animation-delay: calc(var(--Row, 0) * 60ms);
}

.ResultBody > :nth-child(1) { --Row: 0; }
.ResultBody > :nth-child(2) { --Row: 1; }
.ResultBody > :nth-child(3) { --Row: 2; }
.ResultBody > :nth-child(4) { --Row: 3; }
.ResultBody > :nth-child(5) { --Row: 4; }
.ResultBody > :nth-child(6) { --Row: 5; }
.ResultBody > :nth-child(7) { --Row: 6; }
.ResultBody > :nth-child(8) { --Row: 7; }
.ResultBody > :nth-child(9) { --Row: 8; }

.ResultHero .PipHold {
  animation: var(--AnimPop) 700ms var(--EaseSpring) backwards;
  animation-delay: 120ms;
}

/* The baby's scene fades in beside Pip over --DurCalm. Never under Motion Off (--AnimSceneIn is none there). */
.ResultHero .BabyScene {
  animation-name: var(--AnimSceneIn);
  animation-duration: var(--DurCalm);
  animation-timing-function: var(--EaseSettle);
  animation-fill-mode: backwards;
  animation-delay: 160ms;
}

.Timeline > .TimelineItem {
  animation: var(--AnimRise) var(--DurCalm) var(--EaseSettle) backwards;
  animation-delay: calc(var(--Row, 0) * 60ms);
}

.Timeline > :nth-child(1) { --Row: 0; }
.Timeline > :nth-child(2) { --Row: 1; }
.Timeline > :nth-child(3) { --Row: 2; }
.Timeline > :nth-child(4) { --Row: 3; }
.Timeline > :nth-child(5) { --Row: 4; }
.Timeline > :nth-child(6) { --Row: 5; }
.Timeline > :nth-child(7) { --Row: 6; }
.Timeline > :nth-child(8) { --Row: 7; }

/* ---------- Orb ---------- */

.Orb[data-state="Idle"] .OrbCore {
  animation: var(--AnimOrbBreath) 5s var(--EaseBreathe) 6;
}

.Orb[data-state="Idle"] .OrbHalo {
  animation: var(--AnimHalo) 5s var(--EaseBreathe) 6;
}

/* Listening: scale follows --Level (0 to 1) up to 1.15. The colour never changes with loudness. */
.Orb[data-state="Listening"] .OrbCore {
  transform: scale(calc(1 + 0.15 * var(--Level) * var(--MotionScale)));
  transition: transform 100ms linear;
}

.Orb[data-state="Listening"] .OrbHalo {
  opacity: calc(0.35 + 0.4 * var(--Level));
}

.Orb[data-state="Listening"]:not([data-driven]) {
  animation: var(--AnimLevel) 3.6s ease-in-out infinite;
}

.Ripple {
  animation: var(--AnimRipple) 1.6s linear infinite;
}

.Ripple:nth-of-type(1) { animation-delay: 0s; }
.Ripple:nth-of-type(2) { animation-delay: 0.5s; }
.Ripple:nth-of-type(3) { animation-delay: 1s; }

.OrbStage .ProgressRing:not([data-driven]) .ProgressFill {
  animation: ListenRing 15s linear forwards;
}

.Orb[data-state="Thinking"] .OrbCore {
  animation: ThinkShrink 600ms var(--EaseSettle) both;
}

.Orb[data-state="Thinking"] .OrbDots {
  animation: var(--AnimOrbit) 6s linear infinite;
}

/* ---------- Pip ---------- */

.PipLid {
  opacity: 0;
  animation: var(--AnimBlink) 6s infinite;
}

.SleepZ svg {
  animation: var(--AnimSleepZ) 4s ease-in-out 3;
}

.SleepZ svg:nth-child(2) { animation-delay: 0.6s; }
.SleepZ svg:nth-child(3) { animation-delay: 1.2s; }

/* ---------- Breathe ---------- */

.BreatheStage {
  --BreatheMin: calc(1 - 0.24 * var(--MotionScale));
}

.BreatheOrb {
  animation: var(--AnimBreatheOrb) 10s linear infinite;
}

.BreatheFill {
  stroke-dasharray: 1;
  animation: BreatheArc 10s linear infinite;
}

.CueIn {
  animation: CueIn 10s linear infinite;
}

.CueOut {
  animation: CueOut 10s linear infinite;
}

/* Pause: the Pause checkbox in the static prototype, data-paused on #Breathe in the wired app */
#Breathe:has(#BreathePause:checked) .BreatheStage *,
#Breathe[data-paused="true"] .BreatheStage * {
  animation-play-state: paused;
}

/* Wired app: when the JavaScript drives the phase it sets data-driven and data-phase="In" or "Out" */
.BreatheStage[data-driven] .BreatheOrb,
.BreatheStage[data-driven] .BreatheFill,
.BreatheStage[data-driven] .CueIn,
.BreatheStage[data-driven] .CueOut {
  animation: none;
}

.BreatheStage[data-driven] .BreatheOrb {
  transform: scale(var(--BreatheMin));
  transition: transform 6s var(--EaseBreathe);
}

.BreatheStage[data-driven][data-phase="In"] .BreatheOrb {
  transform: scale(1);
  transition-duration: 4s;
}

.BreatheStage[data-driven] .CueIn,
.BreatheStage[data-driven] .CueOut {
  transition: opacity 400ms var(--EaseBreathe);
}

.BreatheStage[data-driven] .CueIn { opacity: 0; }
.BreatheStage[data-driven] .CueOut { opacity: 1; }
.BreatheStage[data-driven][data-phase="In"] .CueIn { opacity: 1; }
.BreatheStage[data-driven][data-phase="In"] .CueOut { opacity: 0; }

/* ---------- Soothe wave ---------- */

.WaveLine {
  animation: var(--AnimWave) 12s linear infinite;
}

/* ---------- Step check-off and loading ---------- */

.StepTick .Icon {
  transition: stroke-dashoffset var(--DurStepCheck) var(--EaseSettle);
}

.Skeleton,
.ResultBody[aria-busy="true"] {
  animation: var(--AnimPulse) 1.6s var(--EaseBreathe) infinite;
}

/* ---------- Tap feedback: scale to 0.97 in 120ms, spring back. Off drops opacity instead. ---------- */

.Button,
.TextLink,
.IconButton,
.HelpButton,
.Tab,
.Orb:not(.IsStatic),
.ChipLabel,
.SegmentedLabel,
.SoundLabel,
.PipCard,
.GalleryLink,
.StepBox,
.CheckRow,
.SwitchRow,
.HelpLine,
.TimelineRemove,
.SheetClose,
.AskSend {
  transition:
    transform 320ms var(--EaseSpring),
    opacity 320ms var(--EaseSpring),
    var(--TransitionTheme);
}

.Button:active,
.TextLink:active,
.IconButton:active,
.HelpButton:active,
.Tab:active,
.Orb:not(.IsStatic):active,
.ChipLabel:active,
.SegmentedLabel:active,
.SoundLabel:active,
.PipCard:active,
.GalleryLink:active,
.StepBox:active,
.CheckRow:active,
.SwitchRow:active,
.HelpLine:active,
.TimelineRemove:active,
.SheetClose:active,
.AskSend:active {
  transform: scale(var(--TapScale));
  opacity: var(--TapOpacity);
  transition-duration: var(--DurTap);
}

/* ---------- Theme change: tokens crossfade over 700ms ---------- */

html,
body,
.Shell,
.Screen,
.TopBar,
.TabBar,
.Card,
.AltRow,
.Stat,
.FieldInput,
.Segmented,
.Sheet,
.ResultHero,
.ChipStatic {
  transition: var(--TransitionTheme);
}

/* ---------- Sheets: slide up and fade in over 400ms, leave in 200ms ---------- */

.Sheet {
  opacity: 0;
  transform: translateY(calc(var(--SlideDistance) * 3));
  transition:
    transform var(--DurQuick) var(--EaseRelease),
    opacity var(--DurQuick) var(--EaseRelease),
    overlay var(--DurQuick) allow-discrete,
    display var(--DurQuick) allow-discrete;
}

.Sheet:popover-open {
  opacity: 1;
  transform: none;
  transition:
    transform var(--DurCalm) var(--EaseSettle),
    opacity var(--DurCalm) var(--EaseSettle),
    overlay var(--DurCalm) allow-discrete,
    display var(--DurCalm) allow-discrete;
}

@starting-style {
  .Sheet:popover-open {
    opacity: 0;
    transform: translateY(calc(var(--SlideDistance) * 3));
  }
}

.Sheet::backdrop {
  opacity: 0;
  transition:
    opacity var(--DurQuick) var(--EaseRelease),
    overlay var(--DurQuick) allow-discrete,
    display var(--DurQuick) allow-discrete;
}

.Sheet:popover-open::backdrop {
  opacity: 1;
  transition-duration: var(--DurCalm);
}

@starting-style {
  .Sheet:popover-open::backdrop {
    opacity: 0;
  }
}

/* ---------- Safety surfaces: no animation at all, in every motion setting ----------
   .IsSafety is on the Help now sheet, Emergency, Urgent, Take a break, the Quick check and the Check
   baby result (with its danger card). Nothing here is scoped to a motion mode, so Full, Gentle and Off
   all look the same: the surface is simply there. The sheet's backdrop is a pseudo-element of the
   sheet, so it is named on its own. */

.IsSafety,
.IsSafety *,
.IsSafety *::before,
.IsSafety *::after,
.IsSafety::backdrop {
  animation: none !important;
  transition: none !important;
}

/* A sheet that is a safety surface opens fully shown, with no starting opacity or slide. */
.IsSafety.Sheet,
.IsSafety.Sheet:popover-open {
  opacity: 1;
  transform: none;
}

.IsSafety.Sheet::backdrop,
.IsSafety.Sheet:popover-open::backdrop {
  opacity: 1;
}

@starting-style {
  .IsSafety.Sheet:popover-open {
    opacity: 1;
    transform: none;
  }

  .IsSafety.Sheet:popover-open::backdrop {
    opacity: 1;
  }
}

/* Help now is pressed with an instant change, never a scale or a fade, on every screen. */
.HelpButton,
.HelpLine {
  transition: none;
}

.HelpButton:active,
.HelpLine:active {
  transform: none;
  opacity: 0.85;
}
