/* ═══════════════════════════════════════════════════════════════════════════
   VideoForge — MOTION
   The site had almost no motion: things appeared, and that was it. This file
   is the whole movement system, in four parts:

     1. The opening — a title card that parts like a curtain
     2. Entrances   — how content arrives on a screen
     3. Reactions   — what happens under the cursor and the finger
     4. Transitions — moving between steps and opening panels

   House rules, applied throughout:
     · One easing for entrances (--m-out), a faster one for exits. Things
       should leave quicker than they arrive.
     · 150–260ms for reactions, 300–520ms for entrances. Nothing slower
       unless it is the opening, which is allowed one moment.
     · Only transform and opacity are animated. Both composite on the GPU;
       animating width, height or top forces layout on every frame.
     · Motion carries meaning — where a thing came from, what is loading,
       what responded. Decoration that moves is just noise.
     · Every one of these is switched off under prefers-reduced-motion at the
       bottom of the file.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
    --m-out:  cubic-bezier(.22, 1, .36, 1);      /* entrances: fast out, soft land */
    --m-in:   cubic-bezier(.4, 0, 1, 1);         /* exits: accelerate away         */
    --m-soft: cubic-bezier(.4, 0, .2, 1);        /* neutral, for colour + size     */
    --m-pop:  cubic-bezier(.34, 1.56, .64, 1);   /* one overshoot, for confirmation */
}

/* ═══════════════════════════════════════════════════════════════
   1 — THE OPENING
   A full-screen title card that splits horizontally and pulls away,
   top half up and bottom half down, revealing the page behind it.

   It runs once per browser session, not once per navigation: seeing it
   on every page load would be a tax on the person using the app rather
   than a welcome. The markup is injected by motion.js.
   ═══════════════════════════════════════════════════════════════ */
#vf-curtain {
    position: fixed; inset: 0; z-index: 10000;
    pointer-events: none;
}

/* The two halves. Each is the full viewport height with its own half of the
   screen visible through a clip, so the wordmark is drawn once and split
   between them — the letterforms tear apart exactly on their centre line. */
#vf-curtain .vf-c-half {
    position: absolute; left: 0; right: 0; height: 50%;
    overflow: hidden; background: var(--bg);
    display: flex; justify-content: center;
    will-change: transform;
}
#vf-curtain .vf-c-top    { top: 0;    align-items: flex-end; }
#vf-curtain .vf-c-bottom { bottom: 0; align-items: flex-start; }

/* Each half holds a full-height copy of the wordmark, offset so that the top
   half shows its bottom edge and the bottom half shows its top edge. */
#vf-curtain .vf-c-word {
    font-family: var(--display, 'Archivo', sans-serif);
    font-weight: 900; font-stretch: 106%;
    font-size: clamp(38px, 8vw, 104px);
    letter-spacing: -.045em; line-height: 1;
    color: var(--text); white-space: nowrap;
    transform: translateY(50%);
}
#vf-curtain .vf-c-bottom .vf-c-word { transform: translateY(-50%); }

/* A hairline on the split, so the two halves read as one object about to
   come apart rather than as two panels that happen to touch. */
#vf-curtain .vf-c-top::after,
#vf-curtain .vf-c-bottom::before {
    content: ''; position: absolute; left: 0; right: 0; height: 1px;
    background: var(--border-2);
    opacity: 0; transition: opacity .3s;
}
#vf-curtain .vf-c-top::after { bottom: 0; }
#vf-curtain .vf-c-bottom::before { top: 0; }
#vf-curtain.vf-c-armed .vf-c-top::after,
#vf-curtain.vf-c-armed .vf-c-bottom::before { opacity: 1; }

/* The wordmark rises into place, holds, then the halves part. */
#vf-curtain .vf-c-word { animation: vf-c-rise .62s var(--m-out) both; }

#vf-curtain.vf-c-open .vf-c-top    { animation: vf-c-part-up   .82s var(--m-out) forwards; }
#vf-curtain.vf-c-open .vf-c-bottom { animation: vf-c-part-down .82s var(--m-out) forwards; }

@keyframes vf-c-rise {
    from { opacity: 0; transform: translateY(calc(50% + 14px)); }
    to   { opacity: 1; }
}
@keyframes vf-c-part-up   { to { transform: translateY(-100%); } }
@keyframes vf-c-part-down { to { transform: translateY(100%); } }

/* While the curtain is up the page must not scroll behind it. */
html.vf-curtain-on, html.vf-curtain-on body { overflow: hidden; }

/* The page itself settles in behind the parting halves, so the reveal has
   something arriving rather than something already sitting there. */
html.vf-curtain-on .app-container,
html.vf-curtain-on .vf-ambient ~ main,
html.vf-curtain-on nav.nav { animation: vf-c-settle .9s var(--m-out) both; }
@keyframes vf-c-settle {
    from { opacity: 0; transform: scale(.985); }
    to   { opacity: 1; transform: none; }
}

/* Entrance animations beneath the curtain are held until it lifts. Without
   this the hero choreographs itself while it is still hidden, and by the time
   the halves part everything has already finished — the page reads as static
   after paying for a full second of opening. Pausing costs nothing: the class
   comes off when the curtain tears down and every sequence starts from frame
   zero, in view. */
html.vf-curtain-on .app-container *,
html.vf-curtain-on .vf-ambient ~ main *,
html.vf-curtain-on nav.nav * { animation-play-state: paused; }

/* ═══════════════════════════════════════════════════════════════
   2 — ENTRANCES
   One shape, reused: rise 12px and fade in. Consistency is what makes
   a set of arrivals read as one page loading rather than six unrelated
   animations firing at once.
   ═══════════════════════════════════════════════════════════════ */
@keyframes vf-rise {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: none; }
}
@keyframes vf-rise-sm {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}
@keyframes vf-fade { from { opacity: 0; } to { opacity: 1; } }

.vf-enter    { animation: vf-rise .52s var(--m-out) both; }
.vf-enter-sm { animation: vf-rise-sm .38s var(--m-out) both; }

/* Stagger: children arrive in sequence, ~55ms apart. Applied by adding
   .vf-stagger to a parent — no per-child markup needed. */
.vf-stagger > * { animation: vf-rise .5s var(--m-out) both; }
.vf-stagger > *:nth-child(1) { animation-delay: .04s; }
.vf-stagger > *:nth-child(2) { animation-delay: .09s; }
.vf-stagger > *:nth-child(3) { animation-delay: .14s; }
.vf-stagger > *:nth-child(4) { animation-delay: .19s; }
.vf-stagger > *:nth-child(5) { animation-delay: .24s; }
.vf-stagger > *:nth-child(6) { animation-delay: .29s; }
.vf-stagger > *:nth-child(7) { animation-delay: .34s; }
.vf-stagger > *:nth-child(n+8) { animation-delay: .38s; }

/* ═══════════════════════════════════════════════════════════════
   3 — REACTIONS
   Every interactive thing should answer within 200ms. A control that
   looks identical before and after the cursor reaches it reads as dead.
   ═══════════════════════════════════════════════════════════════ */

/* Buttons: lift on hover, sit down on press. The press is the important
   half — it is the only feedback available on a touchscreen. */
.cta-btn, .btn, .ghost-btn, .qp-btn, .vf-submit, .modal-agree {
    transition: transform .16s var(--m-soft), box-shadow .2s var(--m-soft),
                background .2s var(--m-soft), border-color .2s var(--m-soft),
                opacity .2s;
}
.cta-btn:active:not(:disabled), .btn:active, .ghost-btn:active,
.vf-submit:active:not(:disabled), .modal-agree:active {
    transform: translateY(1px) scale(.985);
    transition-duration: .07s;
}

/* The arrow in a call to action leans toward where it is taking you. */
.cta-btn svg, .btn svg { transition: transform .24s var(--m-out); }
.cta-btn:hover svg, .btn:hover svg { transform: translateX(3px); }

/* Links and quiet controls */
a, .nav-links a, .foot-col a { transition: color .18s var(--m-soft); }

/* Rows in a list: a small shift toward the reader on hover. */
.project-item, .voice-row, .mtrack, .faq-item {
    transition: transform .2s var(--m-out), background .2s var(--m-soft),
                border-color .2s var(--m-soft), box-shadow .2s var(--m-soft);
}
.project-item:hover, .voice-row:hover, .mtrack:hover { transform: translateX(2px); }

/* ═══════════════════════════════════════════════════════════════
   4 — TRANSITIONS
   ═══════════════════════════════════════════════════════════════ */

/* Modals: the backdrop fades, the panel rises and scales up from slightly
   under its resting size, so it reads as coming toward the reader.

   These are scoped to the state that means "shown" — .open for the landing
   modal, and mount-time for the two that are created on demand. An entrance
   animation with fill-mode:both pins its final keyframe permanently, so
   applying one to an element that hides itself with opacity:0 makes that
   element visible forever. That is what a blanket .modal-backdrop rule did
   here: it put the terms dialog on screen on every page load. */
.modal-backdrop.open,
.vf-modal-backdrop {
    animation: vf-fade .22s var(--m-soft) both;
}
.qp-overlay .qp-modal,
.modal-backdrop.open .modal,
.vf-modal {
    animation: vf-modal-in .38s var(--m-out) both;
}
@keyframes vf-modal-in {
    from { opacity: 0; transform: translateY(14px) scale(.97); }
    to   { opacity: 1; transform: none; }
}

/* The picker's three options arrive in sequence behind the panel itself. */
.qp-options > * { animation: vf-rise .42s var(--m-out) both; }
.qp-options > *:nth-child(1) { animation-delay: .10s; }
.qp-options > *:nth-child(2) { animation-delay: .16s; }
.qp-options > *:nth-child(3) { animation-delay: .22s; }
.qp-options > *:nth-child(4) { animation-delay: .28s; }
.qp-detail-pane.active { animation: vf-fade .34s var(--m-soft) .12s both; }

/* Step changes: the incoming step rises into place. Because only the active
   section is in flow, this reads as the workflow advancing. */
.step-section.active { animation: vf-rise .44s var(--m-out) both; }

/* The step rail's current chip slides rather than cutting between positions. */
.step-chip { transition: background .3s var(--m-soft); }
.step-dot  { transition: background .3s var(--m-soft), border-color .3s var(--m-soft),
                         color .3s var(--m-soft), transform .3s var(--m-pop); }
.step-dot.current { transform: scale(1.12); }

/* The projects drawer already slides; its rows should follow it in rather
   than being fully drawn the instant it opens. */
.project-drawer.active .project-item { animation: vf-rise-sm .4s var(--m-out) both; }
.project-drawer.active .project-item:nth-child(1) { animation-delay: .06s; }
.project-drawer.active .project-item:nth-child(2) { animation-delay: .10s; }
.project-drawer.active .project-item:nth-child(3) { animation-delay: .14s; }
.project-drawer.active .project-item:nth-child(4) { animation-delay: .18s; }
.project-drawer.active .project-item:nth-child(n+5) { animation-delay: .22s; }

/* Progress bars should ease toward a new value, not jump to it. */
.progress-fill, .render-fill, .auto-progress-fill {
    transition: width .45s var(--m-soft);
}

/* ═══════════════════════════════════════════════════════════════
   REDUCED MOTION
   Everything above is either decorative or has a static equivalent, so
   this turns all of it off rather than merely shortening it. The curtain
   never mounts at all — motion.js checks the same query.
   ═══════════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
    }
    .vf-enter, .vf-enter-sm, .vf-stagger > *,
    .qp-options > *, .step-section.active,
    .project-drawer.active .project-item { opacity: 1; transform: none; }
    .project-item:hover, .voice-row:hover, .mtrack:hover { transform: none; }
    .step-dot.current { transform: none; }
}
