/* ============================================================
   OrbitPanel · Page Entrance Animations (shared across pages)
   GPU-optimized (only transform / opacity / filter).
   ============================================================ */

/* ----- Performance utilities (containment + offscreen pause) ----- */

/* Cards/chips become independent paint layers — sibling re-layout
   no longer triggers their repaint. */
.card, .msg-card, .profile-chip, .stat-card, .hub-card,
.feature-card, .conn-card, .saved-card, .summary-card,
.glass-card, .panel {
  contain: layout style paint;
}

/* Long lists (messages, devices) skip rendering of off-screen rows.
   IMPORTANT: `contain-intrinsic-size: 1px 200px` collapses the list to 1px
   wide on first paint and causes scroll-position jumps as items pop in —
   especially on phones. We give a sensible width-100% placeholder instead.
   Also: limit content-visibility to wide screens; mobile gets a stable
   layout (no scroll jank). */
@media (min-width: 901px) {
  .msg-list, .device-list, .clients-list, .grid-list {
    content-visibility: auto;
    contain-intrinsic-size: auto 200px;
  }
}

/* When the IntersectionObserver in enhance.js marks a card as
   off-screen, drop its expensive backdrop-filter — invisible
   anyway, so users don't notice; GPU saves ~10-20%. */
.is-offscreen {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}
.is-offscreen::before,
.is-offscreen::after {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}

/* --- keyframes --- */
@keyframes pe-mask-reveal {
  from { opacity: 0; clip-path: inset(0 100% 0 0); }
  to   { opacity: 1; clip-path: inset(0 0 0 0); }
}
@keyframes pe-blur-in {
  from { opacity: 0; filter: blur(14px); transform: translate3d(0,12px,0); }
  to   { opacity: 1; filter: blur(0);    transform: translate3d(0,0,0); }
}
@keyframes pe-slide-down {
  from { opacity: 0; transform: translate3d(0,-22px,0); filter: blur(6px); }
  to   { opacity: 1; transform: translate3d(0,0,0);     filter: blur(0); }
}
@keyframes pe-slide-up {
  from { opacity: 0; transform: translate3d(0,22px,0); }
  to   { opacity: 1; transform: translate3d(0,0,0); }
}
@keyframes pe-slide-right {
  from { opacity: 0; transform: translate3d(-26px,0,0); filter: blur(4px); }
  to   { opacity: 1; transform: translate3d(0,0,0);     filter: blur(0); }
}
@keyframes pe-bounce-pop {
  0%   { opacity: 0; transform: scale3d(.4,.4,1) translate3d(0,-18px,0); }
  60%  { opacity: 1; transform: scale3d(1.06,1.06,1) translate3d(0,0,0); }
  100% { transform: scale3d(1,1,1); }
}
@keyframes pe-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* --- base utility (added by JS) ---
   Element is invisible until its delay starts, so the page doesn't flash. */
[data-pe]            { opacity: 0; will-change: transform, opacity, filter; }
[data-pe].pe-ready   { animation-duration: 880ms; animation-fill-mode: both; animation-timing-function: cubic-bezier(.22,.7,.2,1); }

[data-pe="mask"].pe-ready   { animation-name: pe-mask-reveal; animation-duration: 1100ms; }
[data-pe="blur"].pe-ready   { animation-name: pe-blur-in; }
[data-pe="down"].pe-ready   { animation-name: pe-slide-down; }
[data-pe="up"].pe-ready     { animation-name: pe-slide-up; }
[data-pe="right"].pe-ready  { animation-name: pe-slide-right; }
[data-pe="pop"].pe-ready    { animation-name: pe-bounce-pop; animation-duration: 720ms; }
[data-pe="fade"].pe-ready   { animation-name: pe-fade; animation-duration: 700ms; }

/* Once the animation finishes, drop the will-change hint to save GPU memory. */
[data-pe].pe-done {
  opacity: 1; will-change: auto; animation: none;
}

/* Accessibility — bail out when the user prefers reduced motion. */
@media (prefers-reduced-motion: reduce) {
  [data-pe], [data-pe].pe-ready, [data-pe].pe-done {
    animation: none !important;
    opacity: 1 !important;
    filter: none !important;
    transform: none !important;
    clip-path: none !important;
  }
}
