/* Motion batch D: feedback (banners, toasts, save bars, loading, badges, call
   controls) and mobile (drawers, sheets, gestures, back navigation).
   See docs/motion-spec.md and docs/agent-reports/motion-batch-D.md. */

@keyframes md-drop-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
@keyframes md-drop-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(-8px); } }
@keyframes md-rise-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes md-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes md-fade-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes md-conn-in { from { opacity: 0; transform: translate(-50%, -8px); } to { opacity: 1; transform: translate(-50%, 0); } }
@keyframes md-toast-in { from { opacity: 0; transform: translateX(-50%) translateY(10px); } to { opacity: 1; transform: translateX(-50%); } }
@keyframes md-toast-out { from { opacity: 1; transform: translateX(-50%); } to { opacity: 0; transform: translateX(-50%) translateY(8px); } }
@keyframes md-sheet-fling { to { opacity: 0; transform: translateY(calc(var(--mob-sheet-y, 0px) + 40%)); } }

/* ── Banners ────────────────────────────────────────────────────────────────
   Top-anchored bars used the shared toast-in, which rises from 14px BELOW, so
   they slid up out of the content toward the screen edge. They drop in from
   their own edge instead, 220ms in / 160ms out. */
.app-page :is(.announce-banner, .update-banner):not(.anim-out) {
  animation: md-drop-in 220ms var(--ease-out);
}
.app-page .announce-banner.anim-out {
  animation: md-drop-out 160ms var(--ease-exit) forwards;
  pointer-events: none;
}
.app-page .connection-banner {
  animation: md-conn-in 220ms var(--ease-out);
}
.app-page .connection-banner.connection-banner-out {
  animation: none;
  opacity: 0; transform: translate(-50%, -8px);
  transition: opacity 160ms var(--ease-exit), transform 160ms var(--ease-exit);
  pointer-events: none;
}

/* The only real toast (easter-egg party toast): 220ms in, 160ms out. */
.app-page .egg-toast:not(.anim-out) { animation: md-toast-in 220ms var(--ease-out); }
.app-page .egg-toast.anim-out { animation: md-toast-out 160ms var(--ease-exit) forwards; }

/* ── Settings save bar ──────────────────────────────────────────────────────
   Server settings create the bar hidden and unhide it on the first edit; it
   used to pop in at full size. JS adds .savebar-enter only on that reveal, so
   the always-visible profile save bar never replays an entrance on re-render. */
.app-page .settings-savebar.savebar-enter { animation: md-rise-in 220ms var(--ease-out); }

/* ── Loading ────────────────────────────────────────────────────────────────
   Button pending spinner: fade in instead of flashing on at full opacity. */
.app-page .is-pending::after {
  animation: pending-spin .7s linear infinite, md-fade-in 120ms var(--ease-out);
}
/* Images used to run a 180-280ms fade on INSERT, which finished long before a
   slow image arrived (so it still popped) and replayed on every re-render of
   cached images. Now: transparent only while actually loading (JS marks
   .media-loading on insert when !img.complete), then a 140ms fade. Cached
   images are complete on insert and show immediately. */
.app-page :is(.attachments img, .inline-gif img) {
  animation: none;
  transition: opacity 140ms var(--ease-out), filter 150ms var(--ease-out);
}
.app-page :is(.attachments img, .inline-gif img).media-loading { opacity: 0; transition: none; }
/* Skeleton -> content: the first real content after a loading silhouette fades
   in once (JS adds .sk-reveal only on that swap). */
.app-page .sk-reveal { animation: md-fade-in 140ms var(--ease-out); }

/* ── Badges ─────────────────────────────────────────────────────────────────
   polish.css gave unread/rail badges a springy scale(.8) pop that replayed on
   every sidebar/rail re-render (each render makes new nodes), so badges kept
   bouncing on unrelated updates. Counts are feedback: update in place, no pop. */
.app-page :is(.unread-badge, .rail-badge, .rail-dot, .mobile-tab-badge, .ft-folder-count) { animation: none; }

/* ── Call / voice controls ──────────────────────────────────────────────────
   call-motion.css added transform: scale(.94) with a 180ms press-IN on top of
   the batch-A press (scale: .92 in 60ms), so call buttons compounded to ~.86
   and sank slowly. style.css/.mcc-btn also had their own transform scales.
   Leave press to the shared `scale` property only. */
body.app-page :is(.call-controls, .vp-controls, .user-panel, .mobile-call-controls) :is(button, .mcc-btn):active {
  transform: none;
}

/* ── Mobile ─────────────────────────────────────────────────────────────── */
@media (max-width: 800px) {
  /* Pop back to the list: exits use the short exit curve (was 260ms ease-out,
     same as the push). The push in stays 260ms ease-out. */
  .app-page .app:not(.chat-open):not(.tab-screen) .main {
    transition: transform var(--dur-sheet-out) var(--ease-exit), visibility 0s linear var(--dur-sheet-out);
  }
  /* Edge-swipe back committed: the screen is already moving with the finger,
     so it continues off-screen with a decelerating curve instead of easing in. */
  .app-page .app.mob-back-release:not(.chat-open):not(.tab-screen) .main {
    transition: transform var(--dur-sheet-out) var(--ease-out), visibility 0s linear var(--dur-sheet-out);
  }
  /* Swipe released without committing: settle back with --ease-out. */
  .app-page .app.chat-open .main,
  .app-page .app.tab-screen .main {
    transition: transform var(--dur-panel) var(--ease-out), visibility 0s;
  }
  .app-page .app.mob-back-dragging .main { transition: none; }
  /* Bottom tab switches (Home <-> Friends/Inbox/You) are tab swaps, not pushes:
     no 260ms slide in or out. The arriving tab screen already gets the shared
     120ms .view-enter fade from animateViewIn. */
  .app-page .app.mob-tab-switch:not(.chat-open) .main { transition: none; }

  /* Member drawer: 240ms ease-out in, 180ms ease-exit out (was 240ms out). */
  .app-page .app .right { transition: transform var(--dur-sheet-out) var(--ease-exit); }
  .app-page .app.with-right.show-right .right { transition: transform var(--dur-panel) var(--ease-out); }
  .app-page .right-scrim { transition: opacity var(--dur-sheet-out) var(--ease-exit), visibility 0s linear var(--dur-sheet-out); }
  .app-page .app.with-right.show-right .right-scrim { transition: opacity var(--dur-panel) var(--ease-out), visibility 0s; }

  /* Bottom sheets (scrim fade is batch B's, motion-b.css). */
  /* Handle drag uses the independent `translate` property, so it never fights
     the entrance `transform` animation, and releasing no longer restarts the
     entrance (the old rule switched animation to none during the drag, which
     replayed sheet-in on release). Release settles back with --ease-out. */
  body.app-page .floating-menu.mob-sheet {
    translate: 0 var(--mob-sheet-y, 0px);
    transition: translate 220ms var(--ease-out);
  }
  body.app-page .floating-menu.mob-sheet.mob-sheet-dragging { transition: none; }
  /* Dragged past the threshold: leave from where the finger let go. */
  body.app-page .floating-menu.mob-sheet.mob-sheet-flung.anim-out {
    translate: none; transition: none;
    transform: translateY(var(--mob-sheet-y, 0px));
    animation: md-sheet-fling var(--dur-sheet-out) var(--ease-out) forwards;
  }

  /* Swipe-to-reply: the arrow tracks the finger with no lag while dragging, and
     the row settles back with --ease-out. */
  .app-page .msg-swiping > :not(.msg-swipe-reply) { transition: translate var(--dur-sheet-out) var(--ease-out); }
  .app-page .msg-dragging > :not(.msg-swipe-reply) { transition: none; }
  .app-page .msg .msg-swipe-reply { transition: opacity 160ms var(--ease-exit); }
  .app-page .msg.msg-dragging .msg-swipe-reply { transition: none; }
}

/* ── Reduced motion ─────────────────────────────────────────────────────────
   Global rules already zero durations with !important; also drop transforms
   from the pieces above so nothing is left mid-offset. */
body.app-page.reduce-motion :is(.announce-banner, .update-banner, .connection-banner, .egg-toast, .settings-savebar, .sk-reveal) {
  animation: none !important;
}
@media (prefers-reduced-motion: reduce) {
  .app-page :is(.announce-banner, .update-banner, .connection-banner, .egg-toast, .settings-savebar, .sk-reveal) {
    animation: none !important;
  }
}
