/* ============================================================
   MOTION.CSS — "Wow" interaction layer (Phase 1)
   Additive, opt-in motion utilities. Built on the design tokens
   in global.css (--transition-spring, --shadow-glow, --glass-*).
   Everything here degrades to static under prefers-reduced-motion.
   ============================================================ */

/* ----------------------------------------------------------------
   1. VIEW TRANSITIONS — smooth crossfade+slide between SPA routes.
   The router wraps the #spa-content swap in document.startViewTransition().
   Browsers without support fall back to the existing .route-enter class.
   ---------------------------------------------------------------- */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 260ms;
  animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
::view-transition-old(root) {
  animation-name: mx-vt-out;
}
::view-transition-new(root) {
  animation-name: mx-vt-in;
}
@keyframes mx-vt-out {
  to {
    opacity: 0;
    transform: translateY(-6px) scale(0.995);
  }
}
@keyframes mx-vt-in {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.995);
  }
}

/* The sidebar is app chrome, not page content. It is the same element before
   and after a navigation, so it has nothing to transition INTO — but the root
   snapshot above covers the whole viewport, so the 8px slide was being applied
   to the nav as well and it hopped on every click.

   Naming it lifts it out of the root capture into a group of its own, and that
   group is given no animation at all: the page still fades and slides, the
   sidebar is drawn once, in place. */
.app-sidebar {
  view-transition-name: ssp-sidebar;
}
::view-transition-group(ssp-sidebar) {
  animation: none;
}
::view-transition-old(ssp-sidebar),
::view-transition-new(ssp-sidebar) {
  animation: none;
  mix-blend-mode: normal;
}
/* Old and new sit stacked in the same group; with the cross-fade gone they
   would both paint at full opacity and the translucent borders would double.
   Only the incoming copy is kept. On the attempt screens, which hide the
   sidebar outright, there IS no incoming copy and it simply goes — which is
   what switching modes should look like. */
::view-transition-old(ssp-sidebar) {
  display: none;
}

/* The HUD is chrome for the same reason and had the same fault: it is fixed to
   the viewport and identical before and after a navigation, but the root
   snapshot animates from opacity 0, so it faded out and back in on every route
   change -- reading as the HUD blinking rather than the page changing.

   Lifting it into its own group with no animation leaves it drawn once, in
   place. Dropping the outgoing copy matters more here than it does for the
   sidebar: the whole layer is translucent strokes, and two of them stacked at
   full opacity would show as the chrome briefly doubling in weight. */
.hud-layer {
  view-transition-name: ssp-hud;
}
::view-transition-group(ssp-hud) {
  animation: none;
}
::view-transition-old(ssp-hud),
::view-transition-new(ssp-hud) {
  animation: none;
  mix-blend-mode: normal;
}
::view-transition-old(ssp-hud) {
  display: none;
}

/* ----------------------------------------------------------------
   2. SCROLL REVEAL — elements with [data-reveal] fade/slide in once
   they enter the viewport (driven by Motion.scan() + IntersectionObserver).
   Optional [data-reveal-delay] adds a stagger via --mx-delay.
   ---------------------------------------------------------------- */
[data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition:
    opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: var(--mx-delay, 0ms);
  will-change: opacity, transform;
}
[data-reveal='left'] {
  transform: translateX(-24px);
}
[data-reveal='right'] {
  transform: translateX(24px);
}
[data-reveal='scale'] {
  transform: scale(0.94);
}
[data-reveal].mx-revealed {
  opacity: 1;
  transform: none;
}

/* ----------------------------------------------------------------
   3. SKELETON LOADERS — shimmer placeholders for async surfaces.
   ---------------------------------------------------------------- */
.mx-skel {
  position: relative;
  overflow: hidden;
  background: var(--bg-surface-hover, rgba(255, 255, 255, 0.04));
  border-radius: var(--radius-md, 8px);
}
.mx-skel::after {
  content: '';
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.08),
    transparent
  );
  animation: mx-shimmer 1.4s infinite;
}
.mx-skel-text {
  height: 0.8em;
  margin: 0.4em 0;
}
.mx-skel-line {
  height: 14px;
  margin: 10px 0;
}
.mx-skel-block {
  height: 96px;
}
@keyframes mx-shimmer {
  100% {
    transform: translateX(100%);
  }
}

/* ----------------------------------------------------------------
   4. MICRO-INTERACTIONS — opt-in hover/press affordances.
   ---------------------------------------------------------------- */
.mx-lift {
  transition:
    transform var(--transition-spring, 300ms cubic-bezier(0.34, 1.56, 0.64, 1)),
    box-shadow var(--transition-base, 250ms ease);
}
.mx-lift:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg, 0 12px 28px rgba(0, 0, 0, 0.25));
}
.mx-lift:active {
  transform: translateY(-1px) scale(0.997);
}

/* Tactile press for buttons/cards */
.mx-press {
  transition: transform 120ms ease;
}
.mx-press:active {
  transform: scale(0.96);
}

/* Animated gradient glow border (e.g. featured cards / active quest) */
.mx-glow-border {
  position: relative;
  isolation: isolate;
}
.mx-glow-border::before {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(
    120deg,
    var(--color-primary, #6366f1),
    var(--color-accent, #22d3ee),
    var(--color-primary, #6366f1)
  );
  background-size: 300% 300%;
  animation: mx-border-pan 6s linear infinite;
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  z-index: -1;
}
@keyframes mx-border-pan {
  to {
    background-position: 300% 0;
  }
}

/* Ripple (JS adds .mx-ripple spans to .mx-ripple-host on pointerdown) */
.mx-ripple-host {
  position: relative;
  overflow: hidden;
}
.mx-ripple {
  position: absolute;
  border-radius: 50%;
  transform: scale(0);
  background: currentColor;
  opacity: 0.25;
  pointer-events: none;
  animation: mx-ripple-anim 600ms ease-out forwards;
}
@keyframes mx-ripple-anim {
  to {
    transform: scale(2.4);
    opacity: 0;
  }
}

/* Count-up numbers get a subtle tabular alignment so they don't jitter */
.mx-counting {
  font-variant-numeric: tabular-nums;
}

/* Stronger, consistent keyboard focus ring across interactive elements */
a:focus-visible,
button:focus-visible,
[role='button']:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--color-primary, #6366f1);
  outline-offset: 2px;
  border-radius: var(--radius-sm, 6px);
}

/* ----------------------------------------------------------------
   5. TOASTS — stacking notification system (Motion.toast()).
   ---------------------------------------------------------------- */
#mx-toast-container {
  position: fixed;
  bottom: 1.25rem;
  right: 1.25rem;
  z-index: 100000;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  pointer-events: none;
  max-width: min(92vw, 380px);
}
.mx-toast {
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  padding: 0.85rem 1rem;
  border-radius: var(--radius-md, 10px);
  background: var(--bg-elevated, #1b2440);
  color: var(--text-primary, #e8edf7);
  border: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
  border-left: 3px solid var(--color-primary, #6366f1);
  box-shadow: var(--shadow-lg, 0 12px 28px rgba(0, 0, 0, 0.35));
  font-size: 0.875rem;
  line-height: 1.4;
  animation: mx-toast-in 0.32s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.mx-toast.mx-leaving {
  animation: mx-toast-out 0.28s ease forwards;
}
.mx-toast-icon {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  margin-top: 1px;
}
.mx-toast-body {
  flex: 1;
  min-width: 0;
}
.mx-toast-title {
  font-weight: 700;
  margin-bottom: 0.1rem;
}
.mx-toast-close {
  flex-shrink: 0;
  background: none;
  border: none;
  color: var(--text-tertiary, #8a94ab);
  cursor: pointer;
  padding: 2px;
  line-height: 0;
  border-radius: 4px;
}
.mx-toast-close:hover {
  color: var(--text-primary, #fff);
}
.mx-toast.success {
  border-left-color: var(--color-success, #22c55e);
}
.mx-toast.error {
  border-left-color: var(--color-danger, #ef4444);
}
.mx-toast.warning {
  border-left-color: var(--color-warning, #f59e0b);
}
.mx-toast.info {
  border-left-color: var(--color-accent, #22d3ee);
}
.mx-toast-progress {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 2px;
  width: 100%;
  transform-origin: left;
  background: currentColor;
  opacity: 0.35;
  animation: mx-toast-progress linear forwards;
}
@keyframes mx-toast-in {
  from {
    opacity: 0;
    transform: translateX(40px) scale(0.96);
  }
}
@keyframes mx-toast-out {
  to {
    opacity: 0;
    transform: translateX(40px) scale(0.96);
    height: 0;
    margin: 0;
    padding-top: 0;
    padding-bottom: 0;
  }
}
@keyframes mx-toast-progress {
  to {
    transform: scaleX(0);
  }
}

/* ----------------------------------------------------------------
   6. REDUCED MOTION — collapse every effect above to static.
   (global.css already neutralizes animation/transition durations;
   this guarantees reveal elements are visible and effects are calm.)
   ---------------------------------------------------------------- */
/* ----------------------------------------------------------------
   7. ANALYTICS OVERVIEW — inline-SVG trend + distribution charts
   (rendered by analytics-chart.js into the Analytics detail pane).
   ---------------------------------------------------------------- */
.ac-overview {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  padding: 1.25rem;
  height: 100%;
  overflow-y: auto;
}
.ac-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}
.ac-kpis {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 0.75rem;
}
.ac-kpi {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.75rem 0.9rem;
  background: var(--bg-surface, #131c31);
  border: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
  border-radius: var(--radius-md, 10px);
}
.ac-kpi-val {
  font-size: 1.5rem;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--text-primary);
  line-height: 1.1;
}
.ac-kpi-lbl {
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
  font-weight: 600;
}
.ac-card {
  background: var(--bg-surface, #131c31);
  border: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
  border-radius: var(--radius-lg, 14px);
  padding: 1rem 1.1rem 1.2rem;
}
.ac-card-title {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-tertiary);
  font-weight: 700;
  margin-bottom: 0.75rem;
}
.ac-trend {
  width: 100%;
  height: auto;
  display: block;
}
.ac-line {
  stroke-dasharray: 2000;
  stroke-dashoffset: 2000;
  animation: ac-draw 1.4s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes ac-draw {
  to {
    stroke-dashoffset: 0;
  }
}
.ac-dot {
  opacity: 0;
  animation: ac-dot-in 0.3s ease forwards;
  animation-delay: 1.2s;
}
@keyframes ac-dot-in {
  from {
    opacity: 0;
    transform: scale(0);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
.ac-dist {
  display: flex;
  align-items: flex-end;
  justify-content: space-around;
  gap: 0.5rem;
  height: 150px;
}
.ac-dist-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  flex: 1;
  height: 100%;
  justify-content: flex-end;
}
.ac-dist-bar-track {
  width: 60%;
  max-width: 42px;
  flex: 1;
  display: flex;
  align-items: flex-end;
}
.ac-dist-bar {
  width: 100%;
  border-radius: 6px 6px 0 0;
  min-height: 3px;
  animation: ac-bar-grow 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
  transform-origin: bottom;
}
@keyframes ac-bar-grow {
  from {
    transform: scaleY(0);
  }
}
.ac-dist-count {
  font-size: 0.875rem;
  font-weight: 700;
  color: var(--text-primary);
}
.ac-dist-label {
  font-size: 0.625rem;
  color: var(--text-tertiary);
  font-weight: 600;
}
@media (max-width: 560px) {
  .ac-kpis {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  .ac-line {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    animation: none !important;
  }
  .ac-dot {
    opacity: 1;
    animation: none !important;
  }
  .ac-dist-bar {
    animation: none !important;
  }
  .mx-skel::after,
  .mx-glow-border::before,
  .mx-ripple,
  .mx-toast-progress {
    animation: none !important;
  }
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none !important;
  }
  .mx-lift:hover {
    transform: none;
  }
}


/* ============================================================
   FROM THE DESIGN SYSTEM — the active-state rail
   ------------------------------------------------------------
   Imported from the Claude Design export and converted from the kit's React
   inline styles to plain classes. Three parts run together: the rail's
   gradient travels along its length, its glow breathes brighter and dimmer,
   and the row pops forward when selection lands on it.

   The export's other animations are deliberately not here. cellIn, drawLine
   and growBar are re-derivations of things this app already does -- ac-draw
   on .ac-line and ac-bar-grow on .ac-dist-bar, on the same curves -- and
   importing them would put a second animation on the same element.
   ============================================================ */

@keyframes railFlow { 0% { background-position: 0 0; } 100% { background-position: 0 -200%; } }
@keyframes railBreathe {
  0%, 100% { opacity: 0.7; box-shadow: 2px 0 6px rgba(34, 211, 238, 0.35); }
  50% { opacity: 1; box-shadow: 3px 0 14px rgba(34, 211, 238, 0.75), 6px 0 26px rgba(34, 211, 238, 0.25); }
}
@keyframes selectPop {
  0% { transform: translateX(0) scale(1); }
  45% { transform: translateX(7px) scale(1.035); }
  100% { transform: translateX(4px) scale(1); }
}
/* The export runs this 0 -> +4px -> +2px on --ease-spring. Kept smooth here
   instead: that curve overshoots on its own, so a keyframe that also
   overshoots and reverses moves the label twice and rests it 2px off its own
   baseline. One direction, arriving where the label already sits. */
@keyframes labelPop {
  from { transform: translateX(-3px); opacity: 0.55; }
  to   { transform: translateX(0);    opacity: 1; }
}
.label-pop { animation: labelPop 0.36s var(--ease-expo-out) both; }
  to   { transform: translateX(0);    opacity: 1; }
}

/* ── The tree fanning in, row by row ───────────────────────────────────────
   Ends at scale(0.99), which is the row's OWN resting transform, so the last
   frame of the animation and the first frame after it are the same -- ending
   at `none` would have snapped every row 1% larger the moment it finished.

   Selected rows sit this out. They rest at translateX(-3px), and an animation
   owns the transform outright while it runs, so a selected row would have
   arrived at scale(0.99) and then jumped sideways into place. */
@keyframes treeRowIn {
  from { opacity: 0; transform: translateY(-9px) scale(0.99); }
  to   { opacity: 1; transform: scale(0.99); }
}

.tree-animate-in .tree-node-row:not(.active) {
  animation: treeRowIn 0.32s cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
  animation-delay: var(--tree-in-delay, 0ms);
}

@media (prefers-reduced-motion: reduce) {
  .tree-animate-in .tree-node-row:not(.active) { animation: none; }
}
