/* Motion layer: one set of timings and curves, and the few motions every
 * screen shares. Loaded after the component stylesheets.
 *
 * Rules kept here (review 2026-10-03):
 *  - animate transform and opacity only (no layout properties);
 *  - entrances 160-240 ms with an ease-out; presses under 120 ms;
 *  - lists stagger only their first 8 items (no long cascades);
 *  - nothing moves under prefers-reduced-motion (rule at the end and the
 *    global one in animations.css).
 */
:root {
    --motion-fast: 120ms;
    --motion-base: 200ms;
    --motion-slow: 320ms;
    --ease-standard: cubic-bezier(.2, 0, 0, 1);
    --ease-emphasized: cubic-bezier(.2, .8, .2, 1);
    --ease-spring: cubic-bezier(.3, 1.4, .5, 1);
}

/* View switch: the new view fades up into place. "backwards" leaves no
 * transform behind, so fixed children keep the viewport as their box. */
.view.active { animation: motion-view-in var(--motion-base) var(--ease-emphasized) backwards; }

@keyframes motion-view-in {
    from { opacity: 0; transform: translateY(6px); }
}

/* Lists: items arrive with a short stagger. */
.feed-content-card,
.notifications-list > .notification-item,
.messenger-conv-item,
.mv-item,
.search-result-item,
.pf-friend {
    animation: motion-item-in var(--motion-base) var(--ease-emphasized) backwards;
}
:is(.feed-content-card, .notification-item, .messenger-conv-item, .mv-item, .search-result-item, .pf-friend):nth-child(2) { animation-delay: 25ms; }
:is(.feed-content-card, .notification-item, .messenger-conv-item, .mv-item, .search-result-item, .pf-friend):nth-child(3) { animation-delay: 50ms; }
:is(.feed-content-card, .notification-item, .messenger-conv-item, .mv-item, .search-result-item, .pf-friend):nth-child(4) { animation-delay: 75ms; }
:is(.feed-content-card, .notification-item, .messenger-conv-item, .mv-item, .search-result-item, .pf-friend):nth-child(5) { animation-delay: 100ms; }
:is(.feed-content-card, .notification-item, .messenger-conv-item, .mv-item, .search-result-item, .pf-friend):nth-child(6) { animation-delay: 125ms; }
:is(.feed-content-card, .notification-item, .messenger-conv-item, .mv-item, .search-result-item, .pf-friend):nth-child(7) { animation-delay: 150ms; }
:is(.feed-content-card, .notification-item, .messenger-conv-item, .mv-item, .search-result-item, .pf-friend):nth-child(n+8) { animation-delay: 175ms; }

@keyframes motion-item-in {
    from { opacity: 0; transform: translateY(8px); }
}

/* Presses: a quick, small give on the controls people tap most. */
.rel-btn:active:not(:disabled),
.dropdown-action-btn:active,
.mv-icon-btn:active,
.feed-privacy-btn:active,
.cr-action-btn:active,
.mv-folder:active {
    transform: scale(.96);
    transition-duration: 80ms;
}

/* Keyboard focus rings fade in rather than snap. */
:focus-visible { transition: outline-offset var(--motion-fast) var(--ease-standard); }

@media (prefers-reduced-motion: reduce) {
    .view.active,
    .feed-content-card,
    .notifications-list > .notification-item,
    .messenger-conv-item,
    .mv-item,
    .search-result-item,
    .pf-friend { animation: none; }
}

/* ── Content update pulses (js/components/live-updates.js) ─────────────────
 * A counter whose number changes flashes its own colour and gives a small
 * bump: likes pink, comments blue, views violet, followers and friends
 * green, everything else the accent. Toggles pulse when their state flips;
 * edited text glows once; content that disappears collapses. */
.lu-count-pulse { --pulse: #a855f7; animation: lu-color-pulse .85s var(--ease-emphasized, cubic-bezier(.2, .8, .2, 1)); }
[data-component="heart-react"] .lu-count-pulse, .lu-count-pulse[data-component="heart-react"], .cr-reaction-count.lu-count-pulse { --pulse: #ec4899; }
[data-component="comment-button"] .lu-count-pulse { --pulse: #3b82f6; }
.cr-views .lu-count-pulse, [data-profile-stat="views"].lu-count-pulse { --pulse: #8b5cf6; }
[data-profile-stat="followers"].lu-count-pulse, [data-profile-stat="following"].lu-count-pulse, .pf-count.lu-count-pulse { --pulse: #10b981; }

@keyframes lu-color-pulse {
    0%   { color: inherit; transform: scale(1); }
    30%  { color: var(--pulse); transform: scale(1.22); text-shadow: 0 0 10px color-mix(in srgb, var(--pulse) 55%, transparent); }
    100% { color: inherit; transform: scale(1); text-shadow: none; }
}
.lu-count-pulse { display: inline-block; }

/* Badges keep their red: the ring breathes instead. */
.badge.lu-count-pulse, .dock-badge.lu-count-pulse, .messenger-conv-badge.lu-count-pulse, .notification-stack-count.lu-count-pulse, .dock-pill.lu-count-pulse {
    animation: lu-badge-pop .7s var(--ease-spring, cubic-bezier(.3, 1.4, .5, 1));
}
@keyframes lu-badge-pop {
    0% { transform: scale(1); }
    35% { transform: scale(1.3); box-shadow: 0 0 0 4px rgba(239, 68, 68, .28); }
    100% { transform: scale(1); }
}

/* Follow / friend toggles: a ring in the new state's colour. */
.rel-btn.lu-rel-pulse { animation: lu-rel-pulse .9s ease-out; }
.rel-btn.is-incoming.lu-rel-pulse { --ring: 16, 185, 129; }
@keyframes lu-rel-pulse {
    0% { box-shadow: 0 0 0 0 rgba(var(--ring, 168, 85, 247), .55); transform: scale(.94); }
    40% { transform: scale(1.06); }
    100% { box-shadow: 0 0 0 10px rgba(var(--ring, 168, 85, 247), 0); transform: scale(1); }
}

/* Edited text: a soft highlight that fades. */
.lu-updated { animation: lu-updated 1.2s ease-out; border-radius: 8px; }
@keyframes lu-updated {
    0% { background-color: rgba(168, 85, 247, .22); box-shadow: 0 0 0 4px rgba(168, 85, 247, .12); }
    100% { background-color: transparent; box-shadow: 0 0 0 0 transparent; }
}

/* Content that is no longer visible to the viewer. */
.lu-leaving { animation: lu-leaving .32s ease-in forwards; overflow: hidden; }
@keyframes lu-leaving { to { opacity: 0; transform: scale(.97); max-height: 0; margin: 0; padding-top: 0; padding-bottom: 0; } }

@media (prefers-reduced-motion: reduce) {
    .lu-count-pulse, .badge.lu-count-pulse, .rel-btn.lu-rel-pulse, .lu-updated, .lu-leaving { animation: none; }
}

/* ── Calm at rest; one short animation per event (PR #61) ────────────────
 * Decorative loops off (loading indicators keep animating); count badges
 * move once when the count changes; one dropdown entrance; new-posts
 * banner and new feed card; all transform/opacity, at most --motion-slow,
 * and none under prefers-reduced-motion. tools/web/test-motion.js. */
:root {
    --ease-out: cubic-bezier(0.2, 0, 0, 1);    /* things arriving */
    --ease-in: cubic-bezier(0.3, 0, 1, 1);     /* things leaving */
    /* Used where a rule said `transition: all`: paint and composite
       properties only, so layout properties never animate. */
    --transition-safe: color, background-color, border-color, outline-color,
        text-decoration-color, fill, stroke, opacity, box-shadow, transform,
        filter, backdrop-filter;
}

@keyframes motion-pop-in {
    from { opacity: 0; transform: scale(0.6); }
    to   { opacity: 1; transform: none; }
}

@keyframes motion-bump {
    0%   { transform: none; }
    40%  { transform: scale(1.15); }
    100% { transform: none; }
}

@keyframes motion-drop-in {
    from { opacity: 0; transform: translateY(-4px) scale(0.98); }
    to   { opacity: 1; transform: none; }
}

@keyframes motion-rise-in {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
}

/* ── Calm at rest ───────────────────────────────────────────────────────── */

/* Decorative loops: the gradients stay, they just stop moving. */
body::after,
.header::before,
.header::after,
.sidebar::before,
.card::before,
.modal::before {
    animation: none;
}

/* Count badges do not pulse forever; they move once, when the count changes. */
.badge:not(.hidden),
.side-menu-badge:not(.hidden),
.notification-badge {
    animation: none;
}

/* ── Events ─────────────────────────────────────────────────────────────── */

.badge-enter {
    animation: motion-pop-in var(--motion-base) var(--ease-out) !important;
}

.badge-bounce,
.cr-count-bump {
    animation: motion-bump var(--motion-base) var(--ease-out) !important;
}

/* One entrance per dropdown: the container moves, its rows do not. */
.navbar-dropdown:not(.hidden),
.dropdown-menu,
.nav-dropdown,
.cr-menu,
.profile-dropdown {
    transform-origin: top right;
    animation: motion-drop-in var(--motion-base) var(--ease-out) both;
}

[class*="dropdown-"]:not(.dropdown-menu) {
    animation: none;
}

.feed-new-posts-banner {
    animation: motion-drop-in var(--motion-base) var(--ease-out);
}

.feed-card-new {
    animation: motion-rise-in var(--motion-slow) var(--ease-out) both;
}

/* Feed card hover accent: a 3px bar that scales in (it grew `height` from 0,
   which re-ran layout every frame). */
.feed-content-card::before {
    height: 3px;
    transform: scaleY(0);
    transform-origin: top;
    transition: opacity var(--motion-base) var(--ease-out),
                transform var(--motion-base) var(--ease-out);
}

.feed-content-card:hover::before {
    height: 3px;
    transform: none;
}

/* Reduced motion: no movement for events either. The global reduced-motion
   rule in animations.css shortens durations with !important, but the badge
   classes above (and the older ones they replace) are !important too and
   more specific, so they would still animate without this. */
@media (prefers-reduced-motion: reduce) {
    .badge-enter,
    .badge-bounce,
    .cr-count-bump {
        animation: none !important;
    }

    .navbar-dropdown:not(.hidden),
    .dropdown-menu,
    .nav-dropdown,
    .cr-menu,
    .profile-dropdown,
    .feed-new-posts-banner,
    .feed-card-new {
        animation: none;
    }

    .feed-content-card::before {
        transition: none;
    }
}

/* ── Navbar shine (js/components/navbar-shine.js) ─────────────────────────
 * One sweep of light across the top bar at random intervals. Its own
 * clipped layer, so the header keeps overflow for its dropdowns; transform
 * and opacity only; runs once per firing; none under reduced motion. */
.navbar-shine-layer {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: 0;
    border-radius: inherit;
}

.navbar-shine {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 35%;
    opacity: 0;
    transform: translateX(-120%) skewX(-18deg);
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(255, 255, 255, 0.10) 35%,
        rgba(255, 255, 255, 0.28) 50%,
        rgba(255, 255, 255, 0.10) 65%,
        transparent 100%);
    will-change: transform, opacity;
}

[data-theme='light'] .navbar-shine {
    background: linear-gradient(90deg,
        transparent 0%,
        rgba(255, 255, 255, 0.35) 35%,
        rgba(255, 255, 255, 0.75) 50%,
        rgba(255, 255, 255, 0.35) 65%,
        transparent 100%);
}

.navbar-shine.is-sweeping {
    animation: navbar-shine-sweep 1100ms var(--ease-standard, cubic-bezier(.2, 0, 0, 1)) 1;
}

@keyframes navbar-shine-sweep {
    0%   { opacity: 0; transform: translateX(-120%) skewX(-18deg); }
    15%  { opacity: 1; }
    85%  { opacity: 1; }
    100% { opacity: 0; transform: translateX(380%) skewX(-18deg); }
}

@media (prefers-reduced-motion: reduce) {
    .navbar-shine.is-sweeping { animation: none; }
}
