/* The aperture: one continuous camera frame across native navigations.
   Load only on public HTML routes that opt into document transitions.
   The outgoing world takes weight against the touch point, hinges away from
   that axis and sinks into defocused darkness; the incoming world
   materializes from the same singularity through clearing depth-of-field.
   No glow, no rings — only exposure, focus and physical depth. */
@view-transition { navigation: auto; }

:root[data-page-transitions] {
    background: var(--canvas, #090a0c);
    /* Touch point handed over before first paint; center for unscheduled swaps. */
    --vt-x: 50vw;
    --vt-y: 50vh;
    --vt-ry: 0deg;
    --vt-rx: 0deg;
}

/* The header is the viewfinder: it holds while the world underneath folds. */
:root[data-page-transitions] .site-header { view-transition-name: portfolio-header; }

::view-transition { pointer-events: none; background: var(--canvas, #090a0c); }

/* Ground plane behind every gap the fold opens; never leaks live content. */
::view-transition-group(root) { background: var(--canvas, #090a0c); }
::view-transition-group(root),
::view-transition-group(portfolio-header) { animation: none; }
::view-transition-group(portfolio-header) { z-index: 2; }

::view-transition-image-pair(root),
::view-transition-image-pair(portfolio-header) { isolation: isolate; }
::view-transition-old(root),
::view-transition-new(root),
::view-transition-old(portfolio-header),
::view-transition-new(portfolio-header) { mix-blend-mode: normal; }

/* Old world: a press against the touch point, then the page hinges away
   from that axis — moving first, dissolving into defocus last, like a
   subject falling beyond the focal plane into an unlit room. */
::view-transition-old(root) {
    transform-origin: var(--vt-x) var(--vt-y);
    animation: vt-world-out 720ms both;
}
@keyframes vt-world-out {
    0% {
        transform: perspective(1200px) rotateY(0deg) rotateX(0deg) scale(1) translateZ(0);
        filter: brightness(1) saturate(1) blur(0px);
        animation-timing-function: cubic-bezier(.5, 0, .3, 1);
    }
    14% {
        transform: perspective(1200px) rotateY(calc(var(--vt-ry) * .1)) rotateX(calc(var(--vt-rx) * .1)) scale(1.02) translateZ(36px);
        filter: brightness(1.12) saturate(1.06) blur(0px);
        animation-timing-function: cubic-bezier(.58, .02, .3, 1);
    }
    55% {
        transform: perspective(1200px) rotateY(calc(var(--vt-ry) * .68)) rotateX(calc(var(--vt-rx) * .68)) scale(.78) translateZ(-260px);
        filter: brightness(.5) saturate(.78) blur(4px);
        animation-timing-function: cubic-bezier(.6, 0, .85, .4);
    }
    78% {
        transform: perspective(1200px) rotateY(var(--vt-ry)) rotateX(var(--vt-rx)) scale(.42) translateZ(-560px);
        filter: brightness(.05) saturate(.4) blur(18px);
        animation-timing-function: linear;
    }
    100% {
        /* Locked twin of 78%: the outgoing frame holds perfectly still so the
           handoff can never flash, no matter when the browser swaps. */
        transform: perspective(1200px) rotateY(var(--vt-ry)) rotateX(var(--vt-rx)) scale(.42) translateZ(-560px);
        filter: brightness(.05) saturate(.4) blur(18px);
    }
}

/* New world: materializes from the same point through a soft radial field —
   no hard edge, no ring — while heavy defocus pulls into focus and the
   exposure settles from blown highlights to its true grade. */
::view-transition-new(root) {
    transform-origin: var(--vt-x) var(--vt-y);
    animation: vt-world-in 820ms both;
}
@keyframes vt-world-in {
    0% {
        -webkit-mask-image: radial-gradient(circle at var(--vt-x) var(--vt-y), #000 -32%, transparent 0%);
        mask-image: radial-gradient(circle at var(--vt-x) var(--vt-y), #000 -32%, transparent 0%);
        transform: perspective(1200px) rotateY(calc(var(--vt-ry) * -1)) rotateX(calc(var(--vt-rx) * -1)) scale(1.2);
        filter: blur(20px) brightness(1.14) saturate(.85);
        animation-timing-function: cubic-bezier(.3, 0, .18, 1);
    }
    55% {
        -webkit-mask-image: radial-gradient(circle at var(--vt-x) var(--vt-y), #000 38%, transparent 70%);
        mask-image: radial-gradient(circle at var(--vt-x) var(--vt-y), #000 38%, transparent 70%);
        transform: perspective(1200px) rotateY(calc(var(--vt-ry) * -.2)) rotateX(calc(var(--vt-rx) * -.2)) scale(1.04);
        filter: blur(6px) brightness(1.04) saturate(.97);
        animation-timing-function: cubic-bezier(.25, 0, .2, 1);
    }
    82% {
        -webkit-mask-image: radial-gradient(circle at var(--vt-x) var(--vt-y), #000 128%, transparent 160%);
        mask-image: radial-gradient(circle at var(--vt-x) var(--vt-y), #000 128%, transparent 160%);
        transform: perspective(1200px) rotateY(0deg) rotateX(0deg) scale(1);
        filter: blur(0px) brightness(1) saturate(1);
        animation-timing-function: linear;
    }
    100% {
        /* Locked twin of 82%: pixel-identical to the live document, so the
           snapshot dissolving into the real page is literally invisible. */
        -webkit-mask-image: radial-gradient(circle at var(--vt-x) var(--vt-y), #000 128%, transparent 160%);
        mask-image: radial-gradient(circle at var(--vt-x) var(--vt-y), #000 128%, transparent 160%);
        transform: perspective(1200px) rotateY(0deg) rotateX(0deg) scale(1);
        filter: blur(0px) brightness(1) saturate(1);
    }
}

/* Header holds absolutely still — the frame of the camera never flinches. */
::view-transition-old(portfolio-header),
::view-transition-new(portfolio-header) { animation: none; }

/* Unsupported engines get one unobtrusive step, with no link interception. */
@supports not (view-transition-name: none) {
    :root[data-page-transitions] #main-content { animation: vt-entry-nudge 200ms ease both; }
}
:root.vt-fallback[data-page-transitions] #main-content { animation: vt-entry-nudge 200ms ease both; }
@keyframes vt-entry-nudge {
    from { opacity: .94; transform: translateY(3px); }
    to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    @view-transition { navigation: none; }
    ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) { animation: none !important; }
    :root[data-page-transitions] #main-content { opacity: 1; transform: none; transition: none; animation: none; }
}
@media print {
    @view-transition { navigation: none; }
    :root[data-page-transitions] #main-content { transition: none; animation: none; }
}
