/* ==========================================================================
   Motion V1 (frozen scope). Loaded after every page stylesheet.
   Rule: one operation, one response. Nothing moves forever except the two
   confirmed ambient pieces (B1 star drift, B2 loading shimmer while loading).
   Part A reuses #59 (user motion v1), adapted to the integrated pages.
   Part B/C use Uiverse Galaxy elements (MIT) where one fits, recoloured to
   the wine / night palette; credits are next to each block.
   CSS only (CSP style-src 'self'); app.js only toggles one-shot classes.
   The immersive star-map renderer is not touched.
   ========================================================================== */

:root {
  --motion-ease: cubic-bezier(.2, .7, .2, 1);
  --motion-spring: cubic-bezier(.34, 1.56, .64, 1);
}

@keyframes motion-fade { from { opacity: 0; } }
@keyframes motion-rise { from { opacity: 0; transform: translateY(12px); } }
@keyframes motion-pop { 0% { opacity: 0; transform: scale(.92); } 70% { opacity: 1; transform: scale(1.02); } 100% { transform: none; } }

/* A1 (#59) Page transitions: the panel fades in, its blocks rise in turn. */
.panel.active { animation: motion-fade 200ms var(--motion-ease) backwards; }
.panel.active > *,
.panel.active .quick-grid > * { animation: motion-rise 380ms var(--motion-ease) backwards; }
.panel.active > :nth-child(2), .panel.active .quick-grid > :nth-child(1) { animation-delay: 50ms; }
.panel.active > :nth-child(3), .panel.active .quick-grid > :nth-child(2) { animation-delay: 100ms; }
.panel.active > :nth-child(4), .panel.active .quick-grid > :nth-child(3) { animation-delay: 150ms; }
.panel.active > :nth-child(n + 5), .panel.active .quick-grid > :nth-child(n + 4) { animation-delay: 200ms; }
body[data-view^="memory-universe"] #memoryPanel.active,
body[data-view^="memory-universe"] #memoryPanel.active > * { animation: none; }

/* A2 (#59) Bottom nav: one highlight slides to the active tab. Keyed by
   data-go instead of nth-child, and kept above the home-indicator inset. */
.bottom-nav button { position: relative; z-index: 1; transition: transform 220ms var(--motion-spring), color 160ms ease; }
.bottom-nav button:active { transform: scale(.9); }
.bottom-nav .nav-main:active { transform: scale(.86) rotate(-6deg); }
.bottom-nav button.active span { animation: motion-nav-hop 420ms var(--motion-spring); }
@keyframes motion-nav-hop { 0% { transform: none; } 40% { transform: translateY(-4px) scale(1.12); } 100% { transform: none; } }

@supports selector(:has(*)) {
  .bottom-nav::before {
    content: "";
    position: absolute;
    top: 6px;
    bottom: calc(6px + env(safe-area-inset-bottom));
    left: 10px;
    width: calc((100% - 20px) / 6);
    border-radius: 16px;
    background: rgba(141, 38, 55, .07);
    transform: translateX(calc(var(--nav-index, 0) * 100%));
    transition: transform 360ms var(--motion-spring), opacity 160ms ease;
    pointer-events: none;
  }
  body[data-view^="memory-universe"] .bottom-nav::before { bottom: 6px; background: rgba(158, 135, 222, .2); }
  .bottom-nav:not(:has(> [data-go].active))::before { opacity: 0; }
  .bottom-nav:has(> [data-go="roles"].active) { --nav-index: 1; }
  .bottom-nav:has(> [data-go="device"].active) { --nav-index: 3; }
  .bottom-nav:has(> [data-go="history"].active) { --nav-index: 4; }
  .bottom-nav:has(> [data-go="memory"].active) { --nav-index: 5; }
  .bottom-nav button.active,
  body[data-view^="memory-universe"] .bottom-nav button.active { background: transparent; }
}

/* A3 (#59) Hero settle, replayed when the user returns to 首页 after a role
   switch (the switch itself stays on the role library with #61's seal). */
@keyframes motion-art-in { from { opacity: 0; transform: scale(1.06); filter: blur(8px) saturate(.7); } }
@keyframes motion-hero-settle { 0% { transform: scale(.985); } 55% { transform: scale(1.006); } 100% { transform: none; } }
.character-hero.is-role-switched { animation: motion-hero-settle 620ms var(--motion-spring); }
.character-hero.is-role-switched .character-art { animation: motion-art-in 760ms var(--motion-ease) backwards; }
.character-hero.is-role-switched .character-copy > * { animation: motion-rise 520ms var(--motion-ease) backwards; }
.character-hero.is-role-switched .character-copy > :nth-child(1) { animation-delay: 160ms; }
.character-hero.is-role-switched .character-copy > :nth-child(2) { animation-delay: 230ms; }
.character-hero.is-role-switched .character-copy > :nth-child(n + 3) { animation-delay: 300ms; }

/* A4 (#59) Bind success: the dialog pops in with a drawn check and a burst. */
@keyframes motion-check-draw { from { transform: rotate(45deg) scale(0); } to { transform: rotate(45deg) scale(1); } }
@keyframes motion-ring { from { opacity: .55; transform: scale(.6); } to { opacity: 0; transform: scale(1.9); } }
@keyframes motion-sparkle { from { opacity: 0; transform: scale(.4); } 30% { opacity: 1; } to { opacity: 0; transform: scale(1.35); } }
#bindSuccessDialog[open] .bind-success-card { animation: motion-pop 460ms var(--motion-spring) backwards; }
.bind-success-mark { position: relative; display: grid; place-items: center; width: 64px; height: 64px; margin: 8px auto 18px; border-radius: 50%; background: var(--accent); box-shadow: 0 12px 30px rgba(141, 38, 55, .28); }
.bind-success-mark::before { content: ""; width: 14px; height: 26px; margin-top: -6px; border: solid #fff; border-width: 0 4px 4px 0; border-radius: 1px; transform: rotate(45deg); }
.bind-success-mark::after { content: ""; position: absolute; inset: 0; border: 2px solid var(--accent); border-radius: 50%; opacity: 0; }
.bind-success-mark span { position: absolute; inset: -18px; border-radius: 50%; opacity: 0; pointer-events: none;
  background:
    radial-gradient(circle, #e7b85c 0 3px, transparent 4px) 50% 0 / 10px 10px no-repeat,
    radial-gradient(circle, #d98a9a 0 3px, transparent 4px) 100% 50% / 10px 10px no-repeat,
    radial-gradient(circle, #7fb99a 0 3px, transparent 4px) 50% 100% / 10px 10px no-repeat,
    radial-gradient(circle, #e7b85c 0 3px, transparent 4px) 0 50% / 10px 10px no-repeat,
    radial-gradient(circle, #d98a9a 0 2px, transparent 3px) 14% 14% / 8px 8px no-repeat,
    radial-gradient(circle, #7fb99a 0 2px, transparent 3px) 86% 14% / 8px 8px no-repeat,
    radial-gradient(circle, #e7b85c 0 2px, transparent 3px) 86% 86% / 8px 8px no-repeat,
    radial-gradient(circle, #d98a9a 0 2px, transparent 3px) 14% 86% / 8px 8px no-repeat; }
#bindSuccessDialog[open] .bind-success-mark { animation: motion-pop 520ms var(--motion-spring) 120ms backwards; }
#bindSuccessDialog[open] .bind-success-mark::before { animation: motion-check-draw 360ms var(--motion-spring) 380ms backwards; }
#bindSuccessDialog[open] .bind-success-mark::after { animation: motion-ring 900ms var(--motion-ease) 420ms; }
#bindSuccessDialog[open] .bind-success-mark span { animation: motion-sparkle 900ms var(--motion-ease) 460ms; }

/* A5 (#59) Press feedback, retargeted to the integrated controls. */
:is(.primary, .secondary, .ghost, .quick-card, .home-trace, .mp-tile, .mp-moments, .mp-universe, .hp-item, .hp-role,
    .mp-role, .dp-tab, .device-pill, .device-menu-item, .rs-back, .auth-tabs button, .dialog-close) {
  transition-property: transform, background-color, border-color, box-shadow, color, opacity;
  transition-duration: 200ms;
  transition-timing-function: var(--motion-spring);
}
:is(.primary, .secondary, .ghost, .hp-role, .mp-role, .dp-tab, .device-pill, .rs-back, .auth-tabs button, .dialog-close):active:not(:disabled) {
  transform: scale(.96);
  transition-duration: 90ms;
}
:is(.quick-card, .home-trace, .mp-tile, .mp-moments, .mp-universe, .hp-item, .device-menu-item):active:not(:disabled) {
  transform: scale(.985);
  transition-duration: 90ms;
}

/* B1 Galaxy · StealthWorm "pretty-lion-68" (star field drift), recoloured.
   The 进入星图 card gets two slowly drifting star layers (the only ambient loop). */
.mp-universe { position: relative; isolation: isolate; overflow: hidden; }
.mp-universe::before,
.mp-universe::after {
  content: "";
  position: absolute;
  z-index: -1;
  inset: -40% -10%;
  background-image: radial-gradient(rgba(255, 244, 222, .9) 1px, transparent 1.5px);
  background-size: 46px 46px;
  opacity: .45;
  animation: galaxy-star-drift 48s linear infinite;
  pointer-events: none;
}
.mp-universe::after {
  background-image: radial-gradient(rgba(203, 214, 255, .8) .8px, transparent 1.4px);
  background-size: 29px 31px;
  background-position: 11px 7px;
  opacity: .3;
  animation-duration: 76s;
  animation-direction: reverse;
}
.mp-universe em { transition: transform 260ms var(--motion-spring); }
.mp-universe:hover em, .mp-universe:focus-visible em { transform: translateX(4px); }
@keyframes galaxy-star-drift { to { transform: translateY(-46px); } }

/* B2 Galaxy · vk-uiux "neat-goat-26" (skeleton shimmer), recoloured.
   A light sweep crosses the existing loading bars on 首页 · 我们之间. */
.home-trace[data-state="loading"] .home-trace-text { position: relative; overflow: hidden; }
.home-trace[data-state="loading"] .home-trace-text::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent 0, rgba(255, 253, 249, .75) 40px, transparent 80px) no-repeat;
  background-size: 80px 100%;
  animation: galaxy-shimmer 1.5s ease-in-out infinite;
}
@keyframes galaxy-shimmer { from { background-position: -80px 0; } to { background-position: calc(100% + 80px) 0; } }

/* C1 Online / offline: still by default. A real status change plays one
   short cue on the dot (pop + a single fading ring), never a loop. */
.device-pill-dot, .dp-dot { position: relative; }
.status-changed { animation: status-pop 520ms var(--motion-spring); }
.status-changed::after { content: ""; position: absolute; inset: -3px; border: 2px solid currentColor; border-radius: 50%; color: inherit; opacity: 0; animation: status-ring 700ms var(--motion-ease); pointer-events: none; }
.device-pill-dot.status-changed::after, .dp-dot.online.status-changed::after { color: #5f9a78; }
.device-pill-dot.off.status-changed::after, .dp-dot:not(.online).status-changed::after { color: #a8aaa7; }
@keyframes status-pop { 0% { transform: scale(1); } 40% { transform: scale(1.6); } 100% { transform: none; } }
@keyframes status-ring { from { opacity: .7; transform: scale(1); } to { opacity: 0; transform: scale(2.6); } }

/* C2 Role settings saved. Galaxy · guilhermeyohan "white-cheetah-13" (circle
   + check drawn by stroke), recoloured; it takes the slot of the dirty dot. */
.rs-saved-mark { display: none; flex: none; width: 18px; height: 18px; margin-right: -4px; }
.rs-save-bar.is-saved .rs-saved-mark { display: block; }
.rs-saved-ring { fill: rgba(141, 38, 55, .1); stroke: var(--accent); stroke-width: 2px; stroke-dasharray: 100; stroke-dashoffset: 0; }
.rs-saved-check { fill: none; stroke: var(--accent); stroke-width: 2.6px; stroke-linecap: round; stroke-linejoin: round; stroke-dasharray: 22; stroke-dashoffset: 0; }
.rs-save-bar.is-saved-now .rs-saved-ring { animation: saved-ring 560ms var(--motion-ease) backwards; }
.rs-save-bar.is-saved-now .rs-saved-check { animation: saved-check 360ms var(--motion-ease) 380ms backwards; }
@keyframes saved-ring { from { stroke-dashoffset: 100; } }
@keyframes saved-check { from { stroke-dashoffset: 22; } }

/* C3 Image saved: the new Pocket image develops once (brightness + blur
   settle), the same language as the device screen turning on. */
.is-developed { animation: image-develop 640ms var(--motion-ease); }
@keyframes image-develop { from { filter: brightness(1.45) blur(4px); opacity: .55; } }

/* C4 History: open slides the conversation in; back returns the list. */
.hp-enter { animation: hp-enter 260ms var(--motion-ease); }
.hp-return { animation: hp-return 220ms var(--motion-ease); }
@keyframes hp-enter { from { opacity: 0; transform: translateX(18px); } }
@keyframes hp-return { from { opacity: 0; transform: translateX(-14px); } }
@media (min-width: 761px) { @keyframes hp-enter { from { opacity: 0; transform: translateY(8px); } } }

/* C5 Memory added: the new row arrives once (wine rule); a new 共同片段
   gets a small gold star glint instead, echoing the star map. */
.memory-item { position: relative; }
.memory-item.is-arriving { animation: memory-arrive 900ms var(--motion-ease); }
@keyframes memory-arrive { 0% { opacity: 0; transform: translateY(-6px); box-shadow: inset 3px 0 0 var(--accent); background-color: rgba(141, 38, 55, .08); } 40% { opacity: 1; transform: none; } 100% { box-shadow: inset 3px 0 0 transparent; } }
.memory-item.is-arriving-moment { animation: memory-arrive-soft 700ms var(--motion-ease); }
.memory-item.is-arriving-moment::after { content: "✦"; position: absolute; top: 6px; right: 10px; color: #d9a84e; font-size: 14px; opacity: 0; animation: moment-glint 1100ms var(--motion-ease); pointer-events: none; }
@keyframes memory-arrive-soft { from { opacity: 0; transform: translateY(-6px); } }
@keyframes moment-glint { 0% { opacity: 0; transform: scale(.4) rotate(-30deg); } 35% { opacity: 1; transform: scale(1.15) rotate(0); } 100% { opacity: 0; transform: scale(.9) translateY(-6px); } }

/* C6 Delete / forget, after confirming: the row fades and folds away, then
   the list re-renders without it (app.js waits 260ms; skipped when motion is
   reduced). */
.is-leaving { overflow: hidden; pointer-events: none; animation: row-leave 240ms var(--motion-ease) forwards; }
@keyframes row-leave { from { max-height: var(--leave-h, 120px); } to { opacity: 0; transform: translateX(-12px); max-height: 0; margin-top: 0; margin-bottom: 0; padding-top: 0; padding-bottom: 0; } }

/* Reduced motion: no loops and no visible travel. One-off fades may remain
   (the global rule in app.css shortens them to an instant). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-delay: 0s !important; transition-delay: 0s !important; }
  .panel.active, .panel.active > *, .panel.active .quick-grid > *,
  .bottom-nav button.active span, .character-hero.is-role-switched, .character-hero.is-role-switched *,
  #bindSuccessDialog[open] .bind-success-card, #bindSuccessDialog[open] .bind-success-mark,
  #bindSuccessDialog[open] .bind-success-mark::before, #bindSuccessDialog[open] .bind-success-mark::after,
  #bindSuccessDialog[open] .bind-success-mark span,
  .mp-universe::before, .mp-universe::after,
  .home-trace[data-state="loading"] .home-trace-text::after,
  .status-changed, .status-changed::after, .rs-save-bar.is-saved-now *, .is-developed,
  .hp-enter, .hp-return, .memory-item.is-arriving, .memory-item.is-arriving-moment,
  .memory-item.is-arriving-moment::after, .is-leaving { animation: none !important; }
  .bottom-nav::before { transition: none; }
  :is(.primary, .secondary, .ghost, .quick-card, .home-trace, .mp-tile, .mp-moments, .mp-universe, .hp-item, .hp-role,
      .mp-role, .dp-tab, .device-pill, .device-menu-item, .rs-back, .auth-tabs button, .dialog-close, .bottom-nav button):active { transform: none !important; }
  .mp-universe:hover em, .mp-universe:focus-visible em { transform: none; }
}
