/* ================================================================
   Kvill — Mobile Animation Toolkit
   iOS-native feel for unified layout (slide preview + chat)

   Architecture: CSS custom properties + utility classes + keyframes
   Performance: Only transform & opacity animated (composite layer)
   Accessibility: All motion respects prefers-reduced-motion

   Browser support:
   - linear() easing: Chrome 113+, Edge 113+, Firefox 112+, Safari 17.2+
   - Fallback: cubic-bezier approximations for older browsers

   Research sources:
   - Apple HIG Motion: https://developer.apple.com/design/human-interface-guidelines/motion
   - Josh W. Comeau linear(): https://www.joshwcomeau.com/animation/linear-timing-function/
   - CSS Spring Generator: https://www.kvin.me/css-springs/how-to-use
   - Chrome linear() docs: https://developer.chrome.com/docs/css-ui/css-linear-easing-function
   - 60fps techniques: https://www.sitepoint.com/achieve-60-fps-mobile-animations-with-css3/
   - Bottom sheet patterns: https://viliket.github.io/posts/native-like-bottom-sheets-on-the-web/
   ================================================================ */


/* ================================================================
   1. DESIGN TOKENS — Animation Variables

   All timing, easing, and visual values centralized as CSS custom
   properties. Override these on specific elements or breakpoints
   to tune behavior without touching animation logic.
   ================================================================ */

:root {
  /* ─── Duration tokens ──────────────────────────────────────────
     Based on Apple HIG and measured iOS native transitions:
     - Micro: instant feedback (button press, toggle)
     - Fast:  small UI changes (icon state, dot transition)
     - Normal: standard transitions (panel slide, card appear)
     - Slow:  large layout changes (fullscreen enter, orientation)
     - XSlow: dramatic reveals (initial load, page transition)
     ──────────────────────────────────────────────────────────── */
  --m-duration-micro:  80ms;    /* Haptic-tier: scale press, opacity flash      */
  --m-duration-fast:   150ms;   /* Quick state: active dot, icon swap            */
  --m-duration-normal: 300ms;   /* Standard: panel slide, card appear            */
  --m-duration-medium: 400ms;   /* iOS modal present: bottom sheet, fullscreen   */
  --m-duration-slow:   500ms;   /* Large transition: layout shift, orientation   */
  --m-duration-xslow:  700ms;   /* Dramatic: initial reveal, hero entrance       */

  /* ─── Spring durations (longer because springs overshoot) ──── */
  --m-duration-spring-fast:    350ms;   /* Snappy spring: button bounce       */
  --m-duration-spring-normal:  500ms;   /* Standard spring: panel expand      */
  --m-duration-spring-slow:    800ms;   /* Gentle spring: overscroll bounce   */
  --m-duration-spring-xslow:  1200ms;   /* Heavy spring: dramatic entrance    */

  /* ─── Easing curves ────────────────────────────────────────────
     Cubic-bezier values calibrated to match iOS UIKit defaults:

     iOS reference values:
     - CAAnimation default: 0.25s ease
     - UIView default spring: dampingRatio=0.825, response=0.35s
     - Navigation push/pop: ~0.35s with spring
     - Modal present: ~0.4s with deceleration
     - Modal dismiss: ~0.3s with acceleration
     ──────────────────────────────────────────────────────────── */

  /* Standard curves (cubic-bezier) — universal fallback */
  --m-ease-default:       cubic-bezier(0.25, 0.1, 0.25, 1.0);   /* iOS system default            */
  --m-ease-in:            cubic-bezier(0.42, 0, 1.0, 1.0);       /* Accelerate (exit/dismiss)     */
  --m-ease-out:           cubic-bezier(0, 0, 0.58, 1.0);         /* Decelerate (enter/appear)     */
  --m-ease-in-out:        cubic-bezier(0.42, 0, 0.58, 1.0);      /* Symmetric (orientation flip)  */

  /* iOS-like spring approximations (cubic-bezier with overshoot) */
  --m-ease-spring-snappy: cubic-bezier(0.34, 1.56, 0.64, 1.0);   /* Quick bounce, ~10% overshoot  */
  --m-ease-spring-smooth: cubic-bezier(0.16, 1.0, 0.3, 1.0);     /* Gentle deceleration, no bounce*/
  --m-ease-spring-bouncy: cubic-bezier(0.68, -0.55, 0.265, 1.0); /* Anticipation + overshoot      */

  /* ─── linear() spring easings (true spring physics) ────────────
     Generated with stiffness/damping parameters matching iOS:
     - Smooth: stiffness=120, damping=20 (like UIKit .smooth)
     - Snappy: stiffness=180, damping=18 (like UIKit .snappy)
     - Bouncy: stiffness=120, damping=10 (like UIKit .bouncy)

     These use the CSS linear() function which interpolates between
     arbitrary points, enabling true overshoot and oscillation.

     Safari 17.2+ required. Falls back to cubic-bezier above.
     ──────────────────────────────────────────────────────────── */

  /* Smooth spring — iOS .smooth equivalent
     No visible bounce, just a natural deceleration curve.
     Use for: panel slides, layout transitions, resize animations */
  --m-spring-smooth: cubic-bezier(0.16, 1.0, 0.3, 1.0);
  --m-spring-smooth-time: 500ms;

  /* Snappy spring — iOS .snappy equivalent
     Minimal overshoot (~3%), fast settle. Feels precise.
     Use for: navigation push/pop, slide transitions, tab switches */
  --m-spring-snappy: cubic-bezier(0.34, 1.56, 0.64, 1.0);
  --m-spring-snappy-time: 350ms;

  /* Bouncy spring — iOS .bouncy equivalent
     Visible overshoot (~8-12%), playful settle.
     Use for: button press release, toast appear, chip animations */
  --m-spring-bouncy: cubic-bezier(0.68, -0.55, 0.265, 1.0);
  --m-spring-bouncy-time: 600ms;

  /* ─── Enhanced spring via linear() (progressive enhancement) ── */
}

@supports (animation-timing-function: linear(0, 1)) {
  :root {
    /* Smooth spring via linear() — stiffness:120 damping:20 mass:1
       Natural deceleration, no visible bounce */
    --m-spring-smooth: linear(
      0, 0.006, 0.025 2.8%, 0.101 6.1%, 0.24 10.2%, 0.413 14.4%,
      0.596 18.6%, 0.762 22.9%, 0.891 27.3%, 0.973 31.8%,
      1.017 36.5%, 1.033 41.5%, 1.028 47%, 1.012 53%, 1 60%,
      0.996 68%, 0.997 78%, 1
    );
    --m-spring-smooth-time: 600ms;

    /* Snappy spring via linear() — stiffness:180 damping:18 mass:1
       Quick with ~5% overshoot, fast settle */
    --m-spring-snappy: linear(
      0, 0.009, 0.037 2.1%, 0.153 4.8%, 0.353 8%, 0.566 11.1%,
      0.769 14.3%, 0.928 17.4%, 1.017 20%, 1.065 22.7%,
      1.079 25.5%, 1.066 29%, 1.035 33%, 1.011 38%,
      0.997 44%, 0.995 51%, 1.001 60%, 1
    );
    --m-spring-snappy-time: 450ms;

    /* Bouncy spring via linear() — stiffness:120 damping:10 mass:1
       Visible 12% overshoot with oscillation */
    --m-spring-bouncy: linear(
      0, 0.004, 0.016, 0.035, 0.063 9.1%,
      0.141, 0.25, 0.391, 0.563, 0.765,
      1, 1.127 39%, 1.181 43%, 1.186 46%,
      1.149 51%, 1.095 55%, 1.042 60%,
      1.008 66%, 0.988 72%, 0.984 78%,
      0.992 85%, 1
    );
    --m-spring-bouncy-time: 900ms;
  }
}

:root {
  /* ─── Visual tokens ────────────────────────────────────────── */

  /* Border radius — iOS-native sizes */
  --m-radius-button:    12px;   /* Buttons, small cards                    */
  --m-radius-card:      16px;   /* Content cards, chat bubbles             */
  --m-radius-panel:     20px;   /* Bottom sheet, floating panels           */
  --m-radius-modal:     28px;   /* Full-screen modal corners               */
  --m-radius-pill:      9999px; /* Pill buttons, badges, tags              */

  /* Shadows — floating element elevation (iOS-like soft shadows)
     Each level: offset-y + blur + spread + color
     Light theme values; dark theme below */
  --m-shadow-xs:   0 1px 2px rgba(0, 0, 0, 0.05);                          /* Subtle lift       */
  --m-shadow-sm:   0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);  /* Cards     */
  --m-shadow-md:   0 4px 12px rgba(0, 0, 0, 0.08), 0 1px 4px rgba(0, 0, 0, 0.04);  /* Raised    */
  --m-shadow-lg:   0 8px 28px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);  /* Panels    */
  --m-shadow-xl:   0 16px 48px rgba(0, 0, 0, 0.16), 0 4px 16px rgba(0, 0, 0, 0.08); /* Modals   */
  --m-shadow-float: 0 8px 40px rgba(0, 0, 0, 0.15), 0 0 1px rgba(0, 0, 0, 0.1);    /* FAB/float */

  /* Backdrop blur — frosted glass hierarchy
     Performance note: limit to 3-5 blurred elements per viewport
     Each additional blur element costs ~2-4ms on mid-range mobile */
  --m-blur-subtle:   8px;     /* Light frosting, content visible         */
  --m-blur-standard: 16px;    /* Standard glass (bottom sheet header)    */
  --m-blur-heavy:    24px;    /* Strong blur (modal overlay, nav bar)    */
  --m-blur-ultra:    40px;    /* Full obscure (background dim)           */

  /* Gesture thresholds — for JS touch handlers */
  --m-swipe-threshold: 50px;     /* Minimum swipe distance to trigger      */
  --m-velocity-threshold: 0.3;   /* px/ms — minimum velocity for fling     */
  --m-overscroll-max: 60px;      /* Maximum overscroll rubber-band distance */
}

/* Dark theme shadow overrides */
[data-theme="dark"] {
  --m-shadow-xs:   0 1px 2px rgba(0, 0, 0, 0.2);
  --m-shadow-sm:   0 1px 3px rgba(0, 0, 0, 0.3), 0 1px 2px rgba(0, 0, 0, 0.15);
  --m-shadow-md:   0 4px 12px rgba(0, 0, 0, 0.35), 0 1px 4px rgba(0, 0, 0, 0.2);
  --m-shadow-lg:   0 8px 28px rgba(0, 0, 0, 0.45), 0 2px 8px rgba(0, 0, 0, 0.25);
  --m-shadow-xl:   0 16px 48px rgba(0, 0, 0, 0.55), 0 4px 16px rgba(0, 0, 0, 0.3);
  --m-shadow-float: 0 8px 40px rgba(0, 0, 0, 0.5), 0 0 1px rgba(0, 0, 0, 0.4);
}


/* ================================================================
   2. SLIDE TRANSITION ANIMATIONS

   Horizontal slide-in/out for navigating between slides.
   Crossfade for smooth content replacement.
   Scale-up for fullscreen enter.
   Spring physics for overscroll on first/last slide.
   ================================================================ */

/* ─── Slide navigation: horizontal slide-in/slide-out ────────── */

/* Container must have overflow:hidden and position:relative */
.m-slide-container {
  position: relative;
  overflow: hidden;
  will-change: transform; /* Hint: children will animate */
}

/* Slide-in from right (next slide) */
@keyframes m-slide-in-right {
  from {
    transform: translateX(100%);
    opacity: 0.4;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Slide-out to left (current slide exits for next) */
@keyframes m-slide-out-left {
  from {
    transform: translateX(0);
    opacity: 1;
  }
  to {
    transform: translateX(-30%);
    opacity: 0.4;
  }
}

/* Slide-in from left (previous slide) */
@keyframes m-slide-in-left {
  from {
    transform: translateX(-100%);
    opacity: 0.4;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

/* Slide-out to right (current slide exits for previous) */
@keyframes m-slide-out-right {
  from {
    transform: translateX(0);
    opacity: 1;
  }
  to {
    transform: translateX(30%);
    opacity: 0.4;
  }
}

.m-slide-next-enter {
  animation: m-slide-in-right var(--m-duration-normal) var(--m-spring-snappy) both;
}
.m-slide-next-exit {
  animation: m-slide-out-left var(--m-duration-normal) var(--m-ease-in) both;
}
.m-slide-prev-enter {
  animation: m-slide-in-left var(--m-duration-normal) var(--m-spring-snappy) both;
}
.m-slide-prev-exit {
  animation: m-slide-out-right var(--m-duration-normal) var(--m-ease-in) both;
}

/* ─── Crossfade between slides ───────────────────────────────── */

@keyframes m-crossfade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes m-crossfade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

.m-crossfade-enter {
  animation: m-crossfade-in var(--m-duration-normal) var(--m-ease-out) both;
}
.m-crossfade-exit {
  animation: m-crossfade-out var(--m-duration-fast) var(--m-ease-in) both;
}

/* ─── Scale-up to fullscreen ───────────────────────────────────
   Tap on slide preview -> scale up to fill viewport.
   Origin should be set via JS to the tap point or card center.

   Usage:
   element.style.transformOrigin = `${tapX}px ${tapY}px`;
   element.classList.add('m-fullscreen-enter');
   ──────────────────────────────────────────────────────────── */

@keyframes m-fullscreen-enter {
  from {
    transform: scale(0.85);
    opacity: 0.6;
    border-radius: var(--m-radius-card);
  }
  to {
    transform: scale(1);
    opacity: 1;
    border-radius: 0;
  }
}

@keyframes m-fullscreen-exit {
  from {
    transform: scale(1);
    opacity: 1;
    border-radius: 0;
  }
  to {
    transform: scale(0.85);
    opacity: 0.6;
    border-radius: var(--m-radius-card);
  }
}

.m-fullscreen-enter {
  animation: m-fullscreen-enter var(--m-duration-medium) var(--m-spring-smooth) both;
}
.m-fullscreen-exit {
  animation: m-fullscreen-exit var(--m-duration-normal) var(--m-ease-in) both;
}

/* ─── Overscroll spring bounce (first/last slide) ──────────────
   Applied via JS: set --m-overscroll-x on the container as the
   user drags past the edge. CSS handles the spring-back.

   JS pseudocode:
   container.style.setProperty('--m-overscroll-x', `${dampedDelta}px`);
   // On release:
   container.classList.add('m-overscroll-spring-back');
   ──────────────────────────────────────────────────────────── */

.m-overscroll-active {
  transform: translateX(var(--m-overscroll-x, 0px));
  will-change: transform;
}

.m-overscroll-spring-back {
  transform: translateX(0);
  transition: transform var(--m-spring-bouncy-time) var(--m-spring-bouncy);
}


/* ================================================================
   3. CHAT PANEL ANIMATIONS

   Bottom sheet expand/collapse with spring curves.
   Message appear animations with staggered timing.
   Typing indicator with pulsing dots.
   AI text streaming appearance.
   Chat input bar keyboard handling.
   ================================================================ */

/* ─── Bottom sheet expand/collapse ─────────────────────────────
   The chat panel slides up from bottom with iOS-like spring.
   Three states: collapsed (peek), half, expanded.

   Sheet positions controlled via CSS custom property --m-sheet-y
   which represents translateY value (0 = fully expanded).

   Usage:
   .chat-panel { --m-sheet-y: calc(100% - 64px); }  // collapsed
   .chat-panel.half { --m-sheet-y: 50%; }            // half
   .chat-panel.expanded { --m-sheet-y: 0; }          // full
   ──────────────────────────────────────────────────────────── */

@keyframes m-sheet-appear {
  from {
    transform: translateY(100%);
    opacity: 0.8;
  }
  to {
    transform: translateY(var(--m-sheet-y, 0));
    opacity: 1;
  }
}

.m-sheet {
  will-change: transform;
  transition: transform var(--m-spring-smooth-time) var(--m-spring-smooth);
  transform: translateY(var(--m-sheet-y, 0));
}

.m-sheet-appear {
  animation: m-sheet-appear var(--m-duration-medium) var(--m-spring-smooth) both;
}

/* Sheet drag handle indicator */
.m-sheet-handle {
  width: 36px;
  height: 5px;
  border-radius: var(--m-radius-pill);
  background: var(--text-3);
  opacity: 0.4;
  margin: 8px auto 4px;
  transition: opacity var(--m-duration-fast) var(--m-ease-default),
              width var(--m-duration-fast) var(--m-ease-default);
}
.m-sheet-handle:active,
.m-sheet.dragging .m-sheet-handle {
  opacity: 0.7;
  width: 42px;
}

/* Sheet backdrop overlay */
.m-sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0);
  transition: background var(--m-duration-normal) var(--m-ease-default);
  pointer-events: none;
  z-index: 99;
}
.m-sheet-backdrop.visible {
  background: rgba(0, 0, 0, 0.3);
  pointer-events: auto;
}

/* ─── Chat message appear animation ────────────────────────────
   Messages slide up and fade in. Staggered for multiple messages.

   Direction matters:
   - User messages: slide from right
   - AI messages: slide from left (subtle)
   ──────────────────────────────────────────────────────────── */

@keyframes m-message-appear {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

@keyframes m-message-appear-user {
  from {
    opacity: 0;
    transform: translateX(16px) translateY(6px) scale(0.97);
  }
  to {
    opacity: 1;
    transform: translateX(0) translateY(0) scale(1);
  }
}

@keyframes m-message-appear-ai {
  from {
    opacity: 0;
    transform: translateX(-8px) translateY(6px);
  }
  to {
    opacity: 1;
    transform: translateX(0) translateY(0);
  }
}

.m-message-enter {
  animation: m-message-appear var(--m-duration-normal) var(--m-spring-snappy) both;
}
.m-message-enter-user {
  animation: m-message-appear-user var(--m-duration-normal) var(--m-spring-snappy) both;
}
.m-message-enter-ai {
  animation: m-message-appear-ai var(--m-duration-normal) var(--m-ease-spring-smooth) both;
}

/* Staggered messages (for loading history) */
.m-message-stagger:nth-child(1) { animation-delay: 0ms; }
.m-message-stagger:nth-child(2) { animation-delay: 50ms; }
.m-message-stagger:nth-child(3) { animation-delay: 100ms; }
.m-message-stagger:nth-child(4) { animation-delay: 140ms; }
.m-message-stagger:nth-child(5) { animation-delay: 170ms; }
.m-message-stagger:nth-child(n+6) { animation-delay: 200ms; }

/* ─── Typing indicator (three bouncing dots) ───────────────────
   Three dots with staggered sine-wave bounce.
   iOS Messages style: smooth, not jarring.
   ──────────────────────────────────────────────────────────── */

@keyframes m-typing-dot {
  0%, 60%, 100% {
    transform: translateY(0);
    opacity: 0.4;
  }
  30% {
    transform: translateY(-6px);
    opacity: 1;
  }
}

.m-typing-indicator {
  display: flex;
  gap: 4px;
  align-items: center;
  padding: 12px 16px;
}

.m-typing-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-2);
  animation: m-typing-dot 1.4s var(--m-ease-in-out) infinite;
}
.m-typing-dot:nth-child(1) { animation-delay: 0ms; }
.m-typing-dot:nth-child(2) { animation-delay: 160ms; }
.m-typing-dot:nth-child(3) { animation-delay: 320ms; }

/* ─── AI streaming text appearance ─────────────────────────────
   Two modes:
   1. Chunk reveal: text appears in word/sentence chunks (default)
   2. Typewriter: character-by-character with blinking cursor

   Chunk mode is better for AI streaming because it matches the
   actual data delivery pattern (SSE chunks arrive as words/sentences,
   not individual characters). Typewriter creates artificial delay
   that slows perceived response.

   Implementation: Each new chunk wrapper gets this class.
   ──────────────────────────────────────────────────────────── */

/* Chunk reveal — new text fades in */
@keyframes m-text-chunk-reveal {
  from {
    opacity: 0;
    filter: blur(2px);
  }
  to {
    opacity: 1;
    filter: blur(0);
  }
}

.m-text-chunk {
  animation: m-text-chunk-reveal var(--m-duration-fast) var(--m-ease-out) both;
  display: inline;
}

/* AI cursor: blinking caret at end of streaming text */
@keyframes m-cursor-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0; }
}

.m-streaming-cursor::after {
  content: '';
  display: inline-block;
  width: 2px;
  height: 1.1em;
  background: var(--accent);
  margin-left: 1px;
  vertical-align: text-bottom;
  animation: m-cursor-blink 1s step-end infinite;
}

/* Cursor disappears when streaming ends */
.m-streaming-done .m-streaming-cursor::after {
  display: none;
}

/* ─── Chat input bar — keyboard handling ───────────────────────
   When the virtual keyboard appears on iOS/Android, the input bar
   should slide up smoothly. Uses env(keyboard-inset-height) on
   supported browsers, with JS fallback via --m-keyboard-height.

   iOS Safari: visualViewport.height changes on keyboard show
   Android Chrome: supports VirtualKeyboard API

   The transition must be fast enough to not lag behind the
   keyboard animation (~250ms on iOS, ~200ms on Android).
   ──────────────────────────────────────────────────────────── */

.m-chat-input-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  transform: translateY(0);
  transition: transform var(--m-duration-fast) var(--m-ease-out);
  will-change: transform;
  /* Use env() for browsers that support keyboard-inset */
  padding-bottom: env(keyboard-inset-height, var(--m-keyboard-height, 0px));
  z-index: 100;
}

/* When keyboard is visible (set via JS) */
.m-chat-input-bar.keyboard-visible {
  transform: translateY(calc(-1 * var(--m-keyboard-height, 0px)));
}

/* Safe area padding for devices with home indicator */
.m-safe-bottom {
  padding-bottom: env(safe-area-inset-bottom, 0px);
}


/* ================================================================
   4. LAYOUT TRANSITION ANIMATIONS

   Transitions between different layout states:
   - Initial empty -> presentation loaded
   - Slide preview resize (chat expand/collapse)
   - Fullscreen enter/exit
   - Orientation change (portrait <-> landscape)
   ================================================================ */

/* ─── Presentation loaded: slide appears ─────────────────────── */

@keyframes m-presentation-reveal {
  from {
    opacity: 0;
    transform: scale(0.92) translateY(20px);
  }
  60% {
    opacity: 1;
    transform: scale(1.01) translateY(-2px);
  }
  to {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

.m-presentation-enter {
  animation: m-presentation-reveal var(--m-spring-smooth-time) var(--m-spring-smooth) both;
}

/* Skeleton shimmer before content loads */
@keyframes m-skeleton-shimmer {
  0% {
    background-position: -200% 0;
  }
  100% {
    background-position: 200% 0;
  }
}

.m-skeleton {
  background: linear-gradient(
    90deg,
    var(--bg-input) 25%,
    var(--bg-input-hover) 50%,
    var(--bg-input) 75%
  );
  background-size: 200% 100%;
  animation: m-skeleton-shimmer 1.8s var(--m-ease-in-out) infinite;
  border-radius: var(--m-radius-button);
}

/* ─── Slide preview resize ─────────────────────────────────────
   When the chat panel expands/collapses, the slide preview area
   needs to smoothly resize. We animate transform (scale) rather
   than width/height to stay on the compositor thread.

   The preview container transitions its transform-origin and scale
   based on the chat panel state.
   ──────────────────────────────────────────────────────────── */

.m-preview-resizable {
  transition: transform var(--m-spring-smooth-time) var(--m-spring-smooth);
  transform-origin: top center;
  will-change: transform;
}

/* When chat is collapsed (preview gets more space) */
.m-preview-expanded {
  transform: scale(1);
}

/* When chat is half-expanded (preview shrinks) */
.m-preview-compact {
  transform: scale(0.85);
}

/* When chat is fully expanded (preview minimal) */
.m-preview-minimal {
  transform: scale(0.65);
}

/* ─── Orientation change ───────────────────────────────────────
   On orientation change, cross-fade the layout to avoid jarring
   reflow. The old layout fades out while the new one fades in.

   Applied via JS on the orientationchange / resize event.
   ──────────────────────────────────────────────────────────── */

@keyframes m-orientation-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

.m-orientation-transition {
  animation: m-orientation-fade var(--m-duration-slow) var(--m-ease-out) both;
}


/* ================================================================
   5. MICRO-INTERACTIONS

   Small, tactile feedback animations that make the UI feel alive
   and responsive. These should feel instant and physical.
   ================================================================ */

/* ─── Button press feedback ────────────────────────────────────
   Scale down on press, spring back on release.
   Combined with haptic feedback via JS:
   navigator.vibrate?.(1) or HapticFeedback API

   Duration: 80ms press, 300ms release (spring)
   Scale: 0.95 (subtle) to 0.90 (strong)
   ──────────────────────────────────────────────────────────── */

.m-pressable {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  user-select: none;
  transition: transform var(--m-duration-micro) var(--m-ease-default),
              opacity var(--m-duration-micro) var(--m-ease-default);
  will-change: transform;
  cursor: pointer;
}

.m-pressable:active {
  transform: scale(0.95);
  opacity: 0.85;
}

/* Strong press (for important actions like send button) */
.m-pressable-strong:active {
  transform: scale(0.90);
  opacity: 0.8;
}

/* Spring-back on release (applied via JS on touchend/mouseup) */
.m-press-released {
  transform: scale(1);
  transition: transform var(--m-spring-snappy-time) var(--m-spring-snappy);
}

/* ─── Slide dot navigation ─────────────────────────────────────
   Active dot expands and brightens with spring animation.
   Inactive dots compress. Similar to iOS page indicator.
   ──────────────────────────────────────────────────────────── */

.m-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--text-3);
  transition: all var(--m-duration-fast) var(--m-spring-snappy);
  will-change: transform, background-color, width;
}

.m-dot.active {
  width: 18px;
  border-radius: var(--m-radius-pill);
  background: var(--accent);
  transform: scale(1);
}

/* Dot approaching active (adjacent dots slightly larger) */
.m-dot.adjacent {
  width: 8px;
  background: var(--text-2);
}

/* ─── Pull-to-refresh ──────────────────────────────────────────
   Circular progress indicator that appears when pulling down.
   Uses rotation + scale for the spinner reveal.
   ──────────────────────────────────────────────────────────── */

@keyframes m-refresh-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes m-refresh-appear {
  from {
    transform: scale(0) rotate(-180deg);
    opacity: 0;
  }
  to {
    transform: scale(1) rotate(0deg);
    opacity: 1;
  }
}

.m-refresh-spinner {
  width: 28px;
  height: 28px;
  border: 2.5px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: m-refresh-spin 0.8s linear infinite;
}

.m-refresh-enter {
  animation: m-refresh-appear var(--m-duration-normal) var(--m-spring-bouncy) both;
}

/* ─── Download progress ────────────────────────────────────────
   Circular progress ring with smooth value transitions.
   Progress value set via --m-progress (0 to 1).
   Uses conic-gradient for the ring fill.
   ──────────────────────────────────────────────────────────── */

.m-progress-ring {
  --m-progress: 0;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: conic-gradient(
    var(--accent) calc(var(--m-progress) * 360deg),
    var(--border) calc(var(--m-progress) * 360deg)
  );
  transition: --m-progress var(--m-duration-normal) var(--m-ease-out);
  position: relative;
}

/* Inner circle for donut effect */
.m-progress-ring::after {
  content: '';
  position: absolute;
  inset: 4px;
  border-radius: 50%;
  background: var(--bg-solid);
}

/* Completion burst */
@keyframes m-progress-complete {
  0% { transform: scale(1); }
  50% { transform: scale(1.15); }
  100% { transform: scale(1); }
}

.m-progress-ring.complete {
  animation: m-progress-complete var(--m-duration-normal) var(--m-spring-bouncy);
}

/* ─── Toast / Snackbar ─────────────────────────────────────────
   Toasts slide up from bottom with spring, auto-dismiss with
   slide down. Position adjusts for safe area.
   ──────────────────────────────────────────────────────────── */

@keyframes m-toast-enter {
  from {
    transform: translateY(100%) scale(0.9);
    opacity: 0;
  }
  to {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
}

@keyframes m-toast-exit {
  from {
    transform: translateY(0) scale(1);
    opacity: 1;
  }
  to {
    transform: translateY(20px) scale(0.95);
    opacity: 0;
  }
}

.m-toast-enter {
  animation: m-toast-enter var(--m-duration-normal) var(--m-spring-snappy) both;
}

.m-toast-exit {
  animation: m-toast-exit var(--m-duration-fast) var(--m-ease-in) both;
}

/* Toast swipe-to-dismiss (horizontal drag) */
.m-toast-swipeable {
  touch-action: pan-x;
  transition: transform var(--m-duration-fast) var(--m-ease-default),
              opacity var(--m-duration-fast) var(--m-ease-default);
}

.m-toast-swipeable.swiping {
  transition: none; /* JS controls position during drag */
}

.m-toast-swipeable.dismissed {
  transform: translateX(var(--m-swipe-direction, 1) * 120%);
  opacity: 0;
  transition: transform var(--m-duration-normal) var(--m-ease-in),
              opacity var(--m-duration-fast) var(--m-ease-in);
}


/* ================================================================
   6. PERFORMANCE UTILITIES

   Classes that promote elements to GPU composite layers,
   handle will-change lifecycle, and optimize paint.

   IMPORTANT: will-change is a hint, not a magic bullet.
   Over-use creates more layers = more VRAM = worse on mobile.
   Only apply to elements that WILL animate imminently.
   ================================================================ */

/* Promote to composite layer (use sparingly) */
.m-gpu {
  will-change: transform;
  transform: translateZ(0); /* Force layer creation on WebKit */
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
}

/* Promote for opacity animation */
.m-gpu-opacity {
  will-change: opacity;
}

/* Promote for both transform and opacity */
.m-gpu-full {
  will-change: transform, opacity;
}

/* Remove GPU promotion after animation completes
   Apply this class when animation is done to free VRAM */
.m-gpu-done {
  will-change: auto;
}

/* Disable pointer events during animation (prevents accidental taps) */
.m-animating {
  pointer-events: none !important;
}

/* Hardware-accelerated scroll container */
.m-scroll-optimized {
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  scroll-behavior: smooth;
}

/* Contain paint/layout to prevent full-page reflows */
.m-contained {
  contain: layout style paint;
}

/* Isolate stacking context (cheaper than z-index management) */
.m-isolated {
  isolation: isolate;
}


/* ================================================================
   7. GLASS / MATERIAL UTILITIES

   iOS-like frosted glass effects for panels, sheets, and overlays.
   Performance-conscious: heavy blur only on key surfaces.
   ================================================================ */

/* Standard glass panel (bottom sheet header, nav bar) */
.m-glass {
  background: var(--bg-glass);
  backdrop-filter: blur(var(--m-blur-standard)) saturate(180%);
  -webkit-backdrop-filter: blur(var(--m-blur-standard)) saturate(180%);
  border: 1px solid var(--border-glass);
}

/* Subtle glass (content cards within a panel) */
.m-glass-subtle {
  background: var(--bg-glass-subtle);
  backdrop-filter: blur(var(--m-blur-subtle)) saturate(150%);
  -webkit-backdrop-filter: blur(var(--m-blur-subtle)) saturate(150%);
}

/* Heavy glass (modal overlay) */
.m-glass-heavy {
  background: var(--bg-glass-strong);
  backdrop-filter: blur(var(--m-blur-heavy)) saturate(180%);
  -webkit-backdrop-filter: blur(var(--m-blur-heavy)) saturate(180%);
  border: 1px solid var(--border-glass);
}

/* Fallback for browsers without backdrop-filter */
@supports not (backdrop-filter: blur(1px)) {
  .m-glass,
  .m-glass-subtle,
  .m-glass-heavy {
    background: var(--bg-solid);
  }
}


/* ================================================================
   8. ACCESSIBILITY — prefers-reduced-motion

   When the user has enabled "Reduce Motion" in their OS settings
   (iOS: Settings > Accessibility > Motion > Reduce Motion), ALL
   spring animations, slides, and bounces are replaced with simple
   cross-fades or instant transitions.

   This is MANDATORY for:
   - Vestibular disorder accessibility
   - Motion sickness prevention
   - Apple App Store guidelines compliance
   - WCAG 2.1 Level AAA (guideline 2.3)

   Strategy: Replace motion with opacity transitions.
   Duration shortened but not zero (still provides visual feedback).
   ================================================================ */

@media (prefers-reduced-motion: reduce) {
  /* Kill all spring easings — use simple ease */
  :root {
    --m-spring-smooth: var(--m-ease-default);
    --m-spring-smooth-time: 200ms;
    --m-spring-snappy: var(--m-ease-default);
    --m-spring-snappy-time: 150ms;
    --m-spring-bouncy: var(--m-ease-default);
    --m-spring-bouncy-time: 200ms;

    /* Shorten all durations */
    --m-duration-micro:  50ms;
    --m-duration-fast:   100ms;
    --m-duration-normal: 150ms;
    --m-duration-medium: 200ms;
    --m-duration-slow:   250ms;
    --m-duration-xslow:  300ms;

    --m-duration-spring-fast:   150ms;
    --m-duration-spring-normal: 200ms;
    --m-duration-spring-slow:   250ms;
    --m-duration-spring-xslow:  300ms;
  }

  /* Replace slide animations with crossfade */
  .m-slide-next-enter,
  .m-slide-next-exit,
  .m-slide-prev-enter,
  .m-slide-prev-exit {
    animation-name: m-crossfade-in;
    animation-duration: var(--m-duration-normal);
    animation-timing-function: var(--m-ease-default);
  }

  .m-slide-next-exit,
  .m-slide-prev-exit {
    animation-name: m-crossfade-out;
  }

  /* Simplify fullscreen transition */
  .m-fullscreen-enter,
  .m-fullscreen-exit {
    animation-name: m-crossfade-in;
    animation-duration: var(--m-duration-normal);
  }
  .m-fullscreen-exit {
    animation-name: m-crossfade-out;
  }

  /* No overscroll bounce */
  .m-overscroll-spring-back {
    transition-duration: 100ms;
    transition-timing-function: var(--m-ease-default);
  }

  /* Simplified message appear */
  .m-message-enter,
  .m-message-enter-user,
  .m-message-enter-ai {
    animation-name: m-crossfade-in;
    animation-duration: var(--m-duration-fast);
  }

  /* Stop typing dot bounce */
  .m-typing-dot {
    animation: none;
    opacity: 0.5;
  }
  .m-typing-dot:nth-child(1) { opacity: 0.35; }
  .m-typing-dot:nth-child(2) { opacity: 0.55; }
  .m-typing-dot:nth-child(3) { opacity: 0.75; }

  /* Disable press scale */
  .m-pressable:active,
  .m-pressable-strong:active {
    transform: none;
    opacity: 0.7;
  }

  /* Simplify toast */
  .m-toast-enter { animation-name: m-crossfade-in; }
  .m-toast-exit  { animation-name: m-crossfade-out; }

  /* Simplify presentation reveal */
  .m-presentation-enter {
    animation-name: m-crossfade-in;
    animation-duration: var(--m-duration-normal);
  }

  /* No refresh spinner animation */
  .m-refresh-spinner {
    animation-duration: 1.5s;
  }

  /* Kill skeleton shimmer */
  .m-skeleton {
    animation: none;
    background: var(--bg-input);
  }
}


/* ================================================================
   9. ANIMATION COMPOSITION HELPERS

   Utility classes for common animation patterns that can be
   combined with the animations above.
   ================================================================ */

/* ─── Staggered children reveal ────────────────────────────────
   Add to parent. Children animate in sequence.
   Uses CSS counter for dynamic delay calculation.
   ──────────────────────────────────────────────────────────── */

.m-stagger > * {
  animation: m-message-appear var(--m-duration-normal) var(--m-spring-smooth) both;
}

.m-stagger > :nth-child(1)  { animation-delay: 0ms; }
.m-stagger > :nth-child(2)  { animation-delay: 40ms; }
.m-stagger > :nth-child(3)  { animation-delay: 80ms; }
.m-stagger > :nth-child(4)  { animation-delay: 110ms; }
.m-stagger > :nth-child(5)  { animation-delay: 135ms; }
.m-stagger > :nth-child(6)  { animation-delay: 155ms; }
.m-stagger > :nth-child(7)  { animation-delay: 170ms; }
.m-stagger > :nth-child(8)  { animation-delay: 185ms; }
.m-stagger > :nth-child(9)  { animation-delay: 195ms; }
.m-stagger > :nth-child(n+10) { animation-delay: 200ms; }

/* ─── Generic transitions ────────────────────────────────────── */

.m-transition-transform {
  transition: transform var(--m-duration-normal) var(--m-spring-smooth);
}

.m-transition-opacity {
  transition: opacity var(--m-duration-fast) var(--m-ease-default);
}

.m-transition-all {
  transition: transform var(--m-duration-normal) var(--m-spring-smooth),
              opacity var(--m-duration-fast) var(--m-ease-default);
}

/* ─── Fade utilities ─────────────────────────────────────────── */

.m-fade-in {
  animation: m-crossfade-in var(--m-duration-normal) var(--m-ease-out) both;
}

.m-fade-out {
  animation: m-crossfade-out var(--m-duration-fast) var(--m-ease-in) both;
}

/* ─── Scale utilities ────────────────────────────────────────── */

@keyframes m-scale-in {
  from {
    transform: scale(0.8);
    opacity: 0;
  }
  to {
    transform: scale(1);
    opacity: 1;
  }
}

@keyframes m-scale-out {
  from {
    transform: scale(1);
    opacity: 1;
  }
  to {
    transform: scale(0.8);
    opacity: 0;
  }
}

.m-scale-in {
  animation: m-scale-in var(--m-duration-normal) var(--m-spring-snappy) both;
}

.m-scale-out {
  animation: m-scale-out var(--m-duration-fast) var(--m-ease-in) both;
}

/* ─── Slide utilities (directional) ──────────────────────────── */

@keyframes m-slide-up {
  from { transform: translateY(100%); opacity: 0.8; }
  to   { transform: translateY(0);    opacity: 1; }
}

@keyframes m-slide-down {
  from { transform: translateY(0);    opacity: 1; }
  to   { transform: translateY(100%); opacity: 0.8; }
}

@keyframes m-slide-up-subtle {
  from { transform: translateY(16px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

.m-slide-up {
  animation: m-slide-up var(--m-duration-medium) var(--m-spring-smooth) both;
}

.m-slide-down {
  animation: m-slide-down var(--m-duration-normal) var(--m-ease-in) both;
}

.m-slide-up-subtle {
  animation: m-slide-up-subtle var(--m-duration-normal) var(--m-spring-snappy) both;
}


/* ================================================================
   10. iOS SAFARI SPECIFIC FIXES

   Safari on iOS has several animation quirks that need workarounds.
   These are applied conditionally via @supports or JS-added classes.
   ================================================================ */

/* Fix: iOS Safari sometimes doesn't composite transforms in
   scroll containers. Force layer creation. */
@supports (-webkit-touch-callout: none) {
  .m-sheet,
  .m-slide-container {
    -webkit-transform: translateZ(0);
    transform: translateZ(0);
  }

  /* Fix: iOS rubber-band scrolling interferes with sheet gestures */
  .m-sheet-container {
    overscroll-behavior: none;
    -webkit-overflow-scrolling: auto; /* Disable momentum for sheet area */
  }

  /* Fix: backdrop-filter can cause rendering artifacts when combined
     with transform animations. Isolate stacking context. */
  .m-glass {
    isolation: isolate;
  }
}

/* Fix: 100vh on iOS Safari includes the URL bar height.
   Use 100dvh where supported, fallback to -webkit-fill-available. */
.m-viewport-height {
  height: 100vh; /* Fallback */
  height: -webkit-fill-available;
  height: 100dvh;
}

/* Fix: prevent iOS double-tap zoom on interactive elements */
.m-no-double-tap {
  touch-action: manipulation;
}

/* Fix: prevent iOS text selection long-press on animated elements */
.m-no-select {
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
}
