/* ==========================================================================
   motion.css — yui540-inspired micro-motion system (BOLD pass)
   --------------------------------------------------------------------------
   Shared, page-agnostic animation layer. Linked from components/head.html so
   it reaches every built page, and hand-linked into the case-studies/ deep
   dives (which the build copies verbatim, no head injection).

   Design contract (see DESIGN.md → Motion):
     - GREMLIN gets the full squash / stretch / overshoot / bounce, PLUS
       always-on ambient motion (looping ripples, idle button shimmer) so the
       page visibly "lives" the instant it loads — no scroll or hover needed.
     - PRO gets the SAME choreography with ease-out curves and NO overshoot,
       and NO ambient looping (DESIGN.md bans bouncy/elastic motion in pro).
     - Transform + opacity only. Never animate layout.
     - EVERYTHING that moves lives inside
       @media (prefers-reduced-motion: no-preference); js/motion.js also bails
       early on reduce, so armed/hidden states are never even applied.
   ========================================================================== */

:root {
    --ease-dramatic: cubic-bezier(0.87, 0.05, 0.02, 0.97); /* slow-in, hard-out */
    --ease-pop:      cubic-bezier(0, 0.31, 0.18, 0.99);    /* snappy settle */
    --ease-back:     cubic-bezier(0.34, 1.7, 0.5, 1);      /* strong overshoot */
    --ease-out-soft: cubic-bezier(0.22, 0.61, 0.36, 1);    /* pro, monotonic */
}

/* ==========================================================================
   Shared keyframes
   ========================================================================== */

/* Chip entrance — big squash/stretch pop, gremlin. */
@keyframes chip-pop {
    0%   { opacity: 0; transform: scale(0) rotate(-8deg); }
    45%  { opacity: 1; transform: scale(1.4, 1.45) rotate(3deg); }
    68%  { transform: scale(0.86, 0.9) rotate(-2deg); }
    84%  { transform: scale(1.08, 1.05) rotate(1deg); }
    100% { opacity: 1; transform: scale(1) rotate(0); }
}

/* Chip entrance — pro variant: fade + rise, no overshoot. */
@keyframes chip-in-pro {
    0%   { opacity: 0; transform: scale(0.8) translateY(6px); }
    100% { opacity: 1; transform: scale(1) translateY(0); }
}

/* Big expanding radar ring for pseudo-element ripples. */
@keyframes ripple-ring {
    0%   { transform: scale(0.4); opacity: 0.9; }
    60%  { opacity: 0.35; }
    100% { transform: scale(3.4); opacity: 0; }
}

/* Bright box-shadow ping — the active-badge dot (its ::before is the dot). */
@keyframes ring-ping {
    0%       { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.95); }
    70%, 100% { box-shadow: 0 0 0 12px rgba(255, 255, 255, 0); }
}

/* Gloss that sweeps across a button. */
@keyframes chase-sweep {
    0%   { transform: translateX(-170%) skewX(-18deg); }
    100% { transform: translateX(320%) skewX(-18deg); }
}

/* Ambient auto-sweep — gloss off-screen most of the cycle, quick sweep at end. */
@keyframes auto-sweep {
    0%, 78%  { transform: translateX(-170%) skewX(-18deg); }
    92%, 100% { transform: translateX(320%) skewX(-18deg); }
}

/* (Curtain-pull keyframes removed 2026-07-19 — the Skill Sky curtain was
   built, reviewed live, and rejected: looked sloppy in situ. Don't re-add
   full-surface cover-and-reveal treatments without a demo preview first.) */

/* Download sequence — icon drops away, bar fills, icon pops back. */
@keyframes dl-drop {
    from { transform: translateY(0); opacity: 1; }
    to   { transform: translateY(12px); opacity: 0; }
}
@keyframes dl-fill {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}
@keyframes dl-return {
    from { opacity: 0; transform: translateY(-8px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes dl-return-pop {
    0%   { opacity: 0; transform: translateY(-10px) scale(0.4); }
    55%  { opacity: 1; transform: translateY(0) scale(1.3, 1.4); }
    78%  { transform: scale(0.9, 0.94); }
    100% { opacity: 1; transform: scale(1); }
}

/* Sticker-drop burst (ring + particles, nodes injected by motion.js). */
@keyframes ring-grow {
    0%   { opacity: 0.8; transform: scale(0); }
    100% { opacity: 0; transform: scale(4); }
}
@keyframes particle-fly {
    0%   { opacity: 0; transform: translate(0, 0) scale(0); }
    25%  { opacity: 1; }
    80%  { opacity: 1; }
    100% { opacity: 0; transform: translate(var(--tx, 0), var(--ty, -30px)) scale(1); }
}

/* Shutter slat — covers a card thumbnail, then blinks away upward. */
@keyframes slat-out {
    from { transform: scaleY(1); }
    to   { transform: scaleY(0); }
}

/* Skill Sky star pop — holds slightly bigger while the cluster is hovered. */
@keyframes star-pop {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.7, 1.85); }
    70%  { transform: scale(0.82, 0.88); }
    100% { transform: scale(1.15); }
}

/* Heading underline — left-to-right chase wipe (centered variant). */
@keyframes underline-wipe {
    0%   { transform: translateX(-50%) scaleX(0); }
    100% { transform: translateX(-50%) scaleX(1); }
}

/* Left-anchored wipe (no centering offset). */
@keyframes wipe-x {
    0%   { transform: scaleX(0); }
    100% { transform: scaleX(1); }
}

/* Living shimmer that slides along a finished underline (gremlin). */
@keyframes underline-shimmer {
    0%   { background-position: -140% 0; }
    100% { background-position: 240% 0; }
}

/* Hat-capacity bar — scaleX overshoot as it lands (gremlin). */
@keyframes bar-settle {
    0%   { transform: scaleX(1); }
    40%  { transform: scaleX(1.12); }
    70%  { transform: scaleX(0.97); }
    100% { transform: scaleX(1); }
}

/* ==========================================================================
   Motion only runs when the user hasn't asked to reduce it.
   ========================================================================== */
@media (prefers-reduced-motion: no-preference) {

    /* ----------------------------------------------------------------------
       1. Chips — scroll-triggered staggered pop
       ---------------------------------------------------------------------- */
    :is(.skill-tag, .project-tag, .honor-pill, .tchip).motion-armed {
        opacity: 0;
    }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip).motion-in {
        animation: chip-in-pro 0.45s var(--ease-out-soft) both;
        animation-delay: calc(var(--d, 0) * 70ms);
    }
    [data-mode="gremlin"] :is(.skill-tag, .project-tag, .honor-pill, .tchip).motion-in {
        animation: chip-pop 0.62s var(--ease-pop) both;
        animation-delay: calc(var(--d, 0) * 70ms);
        transform-origin: center;
    }

    /* Per-sibling stagger index (chips live in their own rows). */
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(1)  { --d: 0; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(2)  { --d: 1; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(3)  { --d: 2; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(4)  { --d: 3; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(5)  { --d: 4; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(6)  { --d: 5; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(7)  { --d: 6; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(8)  { --d: 7; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(9)  { --d: 8; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(10) { --d: 9; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(11) { --d: 10; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(12) { --d: 11; }
    :is(.skill-tag, .project-tag, .honor-pill, .tchip):nth-child(n+13) { --d: 12; }

    /* ----------------------------------------------------------------------
       2. (Retired slot.) Stats bar is DELIBERATELY static as of 2026-07-19:
       above the fold, first-impression territory — it keeps only the
       site-standard soft section-reveal defined in index.html. Don't re-add
       entrance choreography there. The Skill Sky curtain that briefly lived
       here was reviewed live and removed the same day (looked sloppy).
       ---------------------------------------------------------------------- */

    /* ----------------------------------------------------------------------
       3. Availability ripples — big, bold, always looping
       Targets: next-upcoming talk .tdot-next, active scroll-nav dot, and the
       "Active" status-badge dot. Gremlin adds a third ring for depth.
       ---------------------------------------------------------------------- */
    .tdot-next::before,
    .tdot-next::after {
        content: '';
        position: absolute;
        inset: -4px;
        border-radius: 50%;
        border: 3px solid var(--accent);
        pointer-events: none;
    }
    .tdot-next::before { animation: ripple-ring 1.5s var(--ease-out-soft) infinite; }
    .tdot-next::after  { animation: ripple-ring 1.5s var(--ease-out-soft) 0.5s infinite; }
    /* Gremlin third ring via an animated box-shadow on the dot itself. */
    [data-mode="gremlin"] .tdot-next {
        animation: ring-ping 1.5s var(--ease-out-soft) 1s infinite;
        box-shadow: 0 0 0 0 var(--accent);
    }

    [data-mode] .nav-dot.active { animation: none; }
    .nav-dot.active::before,
    .nav-dot.active::after {
        content: '';
        position: absolute;
        inset: -1px;
        border-radius: 50%;
        border: 2px solid var(--accent);
        pointer-events: none;
    }
    .nav-dot.active::before { animation: ripple-ring 1.5s var(--ease-out-soft) infinite; }
    .nav-dot.active::after  { animation: ripple-ring 1.5s var(--ease-out-soft) 0.5s infinite; }

    /* "Active" status badge — ::before dot pings (ring 1), ::after ring 2. */
    html .active-badge::before {
        animation: ring-ping 2s var(--ease-out-soft) infinite;
        position: relative;
    }
    .active-badge::after {
        content: '';
        position: absolute;
        left: 12px;
        top: 50%;
        width: 8px;
        height: 8px;
        margin-top: -4px;
        border-radius: 50%;
        border: 2px solid rgba(255, 255, 255, 0.9);
        pointer-events: none;
        animation: ripple-ring 2s var(--ease-out-soft) 0.5s infinite;
    }

    /* ----------------------------------------------------------------------
       4. Buttons — hover chase-sweep + gremlin ambient auto-shimmer
       ---------------------------------------------------------------------- */
    .hero-download-btn,
    .filter-btn {
        position: relative;
        overflow: hidden;
    }
    .hero-download-btn::after,
    .filter-btn::after {
        content: '';
        position: absolute;
        top: 0;
        bottom: 0;
        left: 0;
        width: 55%;
        background: linear-gradient(100deg,
            transparent 0%,
            rgba(255, 255, 255, 0.55) 50%,
            transparent 100%);
        transform: translateX(-170%) skewX(-18deg);
        pointer-events: none;
    }
    .hero-download-btn:hover::after,
    .filter-btn:hover::after {
        animation: chase-sweep 0.55s var(--ease-dramatic);
    }
    /* Gremlin: buttons shimmer on their own every few seconds (always-on). */
    [data-mode="gremlin"] .hero-download-btn::after {
        animation: auto-sweep 4.5s ease-in-out infinite;
    }
    [data-mode="gremlin"] .hero-download-btn:nth-child(2)::after {
        animation-delay: 1.2s;
    }
    [data-mode="gremlin"] .filter-btn::after {
        animation: auto-sweep 6s ease-in-out infinite;
    }
    /* Pro: subtle shine, hover-only. */
    [data-mode="pro"] .hero-download-btn::after,
    [data-mode="pro"] .filter-btn::after {
        background: linear-gradient(100deg,
            transparent 0%,
            rgba(255, 255, 255, 0.28) 50%,
            transparent 100%);
    }

    /* ----------------------------------------------------------------------
       5. Keycaps — press-squash (leading `html` beats inline :active)
       ---------------------------------------------------------------------- */
    html .view-key {
        transition: transform 0.16s var(--ease-out-soft), box-shadow 0.1s ease;
    }
    html[data-mode="gremlin"] .view-key {
        transition: transform 0.2s var(--ease-back), box-shadow 0.1s ease;
    }
    html .view-key:active {
        transform: translateY(4px) scale(1.1, 0.8);
    }
    html[data-mode="gremlin"] .view-key:active {
        transform: translateY(4px) scale(1.18, 0.72);
    }

    /* ----------------------------------------------------------------------
       6. Heading underline chase wipe (bold, with gremlin living shimmer)
       ---------------------------------------------------------------------- */
    .motion-wipe { position: relative; }
    .motion-wipe::before,
    .motion-wipe::after {
        content: '';
        position: absolute;
        height: 5px;
        border-radius: 5px;
        pointer-events: none;
    }
    .motion-wipe::before { background: var(--accent); }
    .motion-wipe::after  { background: var(--accent); opacity: 0.4; }

    /* Centered heading (about-page .section-divider). */
    .section-divider.motion-wipe::before,
    .section-divider.motion-wipe::after {
        left: 50%;
        bottom: -0.3em;
        width: 120px;
        transform: translateX(-50%) scaleX(0);
        transform-origin: left center;
    }
    .section-divider.motion-wipe.motion-in::before {
        animation: underline-wipe 0.55s var(--ease-dramatic) forwards;
    }
    .section-divider.motion-wipe.motion-in::after {
        animation: underline-wipe 0.55s var(--ease-dramatic) 0.18s forwards;
    }

    /* Left-aligned heading (case-study detail h2). */
    [data-cs-detail] .section h2.motion-wipe::before,
    [data-cs-detail] .section h2.motion-wipe::after {
        left: 0;
        bottom: -0.24em;
        width: 4em;
        max-width: 280px;
        transform: scaleX(0);
        transform-origin: left center;
    }
    [data-cs-detail] .section h2.motion-wipe.motion-in::before {
        animation: wipe-x 0.55s var(--ease-dramatic) forwards;
    }
    [data-cs-detail] .section h2.motion-wipe.motion-in::after {
        animation: wipe-x 0.55s var(--ease-dramatic) 0.18s forwards;
    }

    [data-mode="gremlin"] .motion-wipe.motion-in::before,
    [data-mode="gremlin"] .motion-wipe.motion-in::after {
        animation-timing-function: var(--ease-pop);
    }
    /* Gremlin: once wiped in, the underline stays alive with a sliding glint. */
    [data-mode="gremlin"] .motion-wipe.motion-in::before {
        background-image: linear-gradient(100deg,
            transparent 30%, rgba(255,255,255,0.85) 50%, transparent 70%);
        background-size: 220% 100%;
        background-repeat: no-repeat;
    }
    [data-mode="gremlin"] .section-divider.motion-wipe.motion-in::before {
        animation: underline-wipe 0.55s var(--ease-pop) forwards,
                   underline-shimmer 2.6s ease-in-out 0.7s infinite;
    }
    [data-mode="gremlin"] [data-cs-detail] .section h2.motion-wipe.motion-in::before {
        animation: wipe-x 0.55s var(--ease-pop) forwards,
                   underline-shimmer 2.6s ease-in-out 0.7s infinite;
    }

    /* ----------------------------------------------------------------------
       7. Hat-capacity bar — dramatic ramp + gremlin overshoot pop
       ---------------------------------------------------------------------- */
    html .hat-capacity-fill {
        transition: width 0.6s var(--ease-dramatic), background 0.3s ease;
        transform-origin: left center;
    }
    [data-mode="gremlin"] .hat-capacity-fill.cap-bumped {
        animation: bar-settle 0.55s var(--ease-back);
    }

    /* ----------------------------------------------------------------------
       9. Download sequence — CV/CSV hero buttons (pop-sampler winner #1)
       Click: icon drops into the page, bar fills on the dramatic curve,
       icon pops back. Fires on a real event (an actual download). The bar
       is ::before (the hover gloss owns ::after). motion.js resets .dl-run.
       ---------------------------------------------------------------------- */
    .hero-download-btn::before {
        content: '';
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 3px;
        background: var(--accent);
        transform: scaleX(0);
        transform-origin: left center;
        pointer-events: none;
    }
    .hero-download-btn.dl-run i,
    .hero-download-btn.dl-run svg {
        animation:
            dl-drop 0.4s var(--ease-dramatic) both,
            dl-return 0.45s var(--ease-out-soft) 1.35s both;
    }
    [data-mode="gremlin"] .hero-download-btn.dl-run i,
    [data-mode="gremlin"] .hero-download-btn.dl-run svg {
        animation:
            dl-drop 0.4s var(--ease-dramatic) both,
            dl-return-pop 0.55s var(--ease-pop) 1.35s both;
    }
    .hero-download-btn.dl-run::before {
        animation: dl-fill 0.85s var(--ease-dramatic) 0.3s both;
    }

    /* ----------------------------------------------------------------------
       10. Shutter thumbnails — project-card images (pop-sampler winner #3)
       Slats are injected by motion.js (generator-owned markup stays
       untouched, per PROJECTS_PLAYBOOK). Armed = covered; .motion-in blinks
       them away with a per-slat stagger.
       ---------------------------------------------------------------------- */
    .pc-hdr-img.shutter-armed {
        position: relative;
        overflow: hidden;
    }
    .shutter-slat {
        position: absolute;
        top: 0;
        bottom: 0;
        left: calc(var(--slat, 0) * 25% - 0.5%);
        width: 26%;
        background: var(--card-bg, var(--bg-2, #fff));
        pointer-events: none;
        z-index: 3;
    }
    .pc-hdr-img.motion-in .shutter-slat {
        animation: slat-out 0.5s var(--ease-dramatic) calc(var(--slat, 0) * 60ms) both;
        transform-origin: center top;
    }

    /* ----------------------------------------------------------------------
       11. Sticker-drop burst — gremlin sticker zones (pop-sampler winner #2)
       Ring + five palette particles at the drop point; nodes injected and
       cleaned up by motion.js, only after a real drag (not a mere click).
       ---------------------------------------------------------------------- */
    .pop-ring {
        position: absolute;
        width: 20px;
        height: 20px;
        margin: -10px 0 0 -10px;
        border-radius: 50%;
        border: 2px solid var(--accent);
        pointer-events: none;
        z-index: 40;
        animation: ring-grow 0.7s cubic-bezier(0, 0.06, 0.29, 1) both;
    }
    .pop-particle {
        position: absolute;
        width: var(--size, 6px);
        height: var(--size, 6px);
        margin: calc(var(--size, 6px) / -2) 0 0 calc(var(--size, 6px) / -2);
        border-radius: 50%;
        background: var(--c, var(--accent));
        pointer-events: none;
        z-index: 40;
        animation: particle-fly 0.6s cubic-bezier(0.63, -0.01, 0.29, 1) var(--pd, 0s) both;
    }

    /* ----------------------------------------------------------------------
       12. Skill Sky star twinkle-pop (pop-sampler winner #4)
       Hover a constellation cluster: its stars pop one by one (per-star
       --sd index set by motion.js) and hold slightly bigger while hovered.
       Gremlin-only by inheritance (the sky map is data-gremlin-only).
       ---------------------------------------------------------------------- */
    .sky-cluster:hover .sky-proj-star {
        transform-box: fill-box;
        transform-origin: center;
        animation: star-pop 0.55s var(--ease-pop) calc(var(--sd, 0) * 70ms) both;
    }
}

/* ==========================================================================
   8. Cross-document view transitions — soft fade between pages
   --------------------------------------------------------------------------
   Pure-CSS MPA transitions (Chrome 126+ / Edge / Safari 18.2+; other browsers
   simply navigate normally — zero-risk progressive enhancement). Both the
   outgoing and incoming page must opt in; motion.css is linked from every
   built page AND hand-linked in the case-studies deep dives, so coverage is
   site-wide. The sticky nav is pinned with a view-transition-name so shared
   chrome holds still while the page content cross-fades underneath — this is
   the "clicking into pages feels weird" fix.
   NOTE: sits at top level, not inside the reduced-motion media block —
   @view-transition is a document opt-in, and the explicit reduced-motion
   override below makes the transition instant for those users.
   ========================================================================== */
@view-transition {
    navigation: auto;
}

/* Nav holds still across navigations (sticky, same geometry on every page). */
.top-nav {
    view-transition-name: site-nav;
}

/* Slightly snappier than the UA default, on brand-standard easing. */
::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 0.25s;
    animation-timing-function: var(--ease-out-soft);
}

/* Reduced motion: snapshots don't animate → transition resolves instantly. */
@media (prefers-reduced-motion: reduce) {
    ::view-transition-group(*),
    ::view-transition-image-pair(*),
    ::view-transition-old(*),
    ::view-transition-new(*) {
        animation: none !important;
    }
}

/* ==========================================================================
   13. Motion switch — the user-facing "motion on / motion off" keycap
   --------------------------------------------------------------------------
   Sara's ask (2026-07-19): a friendly in-page way to turn animations off,
   independent of the OS reduced-motion setting. The keycap is injected into
   .view-controls by motion.js and persisted as localStorage `sara-motion`;
   components/head.html sets data-motion pre-paint. "Off" collapses every
   animation/transition to a frame and disables the page-fade — the site
   still works identically, it just holds still.
   ========================================================================== */
html[data-motion="off"] *,
html[data-motion="off"] *::before,
html[data-motion="off"] *::after {
    animation-duration: 0.001s !important;
    animation-delay: 0s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001s !important;
    transition-delay: 0s !important;
}
html[data-motion="off"]::view-transition-old(root),
html[data-motion="off"]::view-transition-new(root) {
    animation: none !important;
}
