@view-transition {
    navigation: auto;
}

:root {
    view-transition-name: root;
    --page-enter-x: 100%;
    --page-enter-animation: page-slide-in;
}

html:has(> body.home) {
    --page-enter-x: -100%;
}

html:has(> body.project-page) {
    --page-enter-animation: page-fade-in;
}

.topnav {
    view-transition-name: site-nav;
}

::view-transition {
    overflow: clip;
}

::view-transition-group(root) {
    animation: none;
    z-index: 0;
}

::view-transition-old(root),
::view-transition-new(root) {
    height: auto;
    object-fit: none;
    object-position: left top;
    mix-blend-mode: normal;
    opacity: 1;
    animation-duration: var(--duration-page);
    animation-timing-function: var(--ease-glass);
    animation-fill-mode: both;
}

::view-transition-old(root) {
    /* Fade in place beneath the incoming page for the full transition. */
    z-index: 0;
    animation-name: page-fade-out;
    animation-timing-function: linear;
}

::view-transition-new(root) {
    z-index: 1;
    animation-name: var(--page-enter-animation);
}

::view-transition-group(site-nav) {
    animation: none;
    z-index: 5;
    border-radius: var(--glass-radius);
    clip-path: inset(0 round var(--glass-radius));
}

::view-transition-old(site-nav),
::view-transition-new(site-nav) {
    height: auto;
    animation: none;
    opacity: 1;
    mix-blend-mode: normal;
}

::view-transition-old(site-nav) {
    display: none;
}

::view-transition-old(site-nav):only-child {
    display: block;
}

@keyframes page-slide-in {
    from {
        transform: translateX(var(--page-enter-x));
    }

    to {
        transform: translateX(0);
    }
}

@keyframes page-fade-in {
    from {
        opacity: 0;
    }

    75%,
    to {
        opacity: 1;
    }
}

@keyframes page-fade-out {
    from {
        opacity: 1;
    }

    to {
        opacity: 0;
    }
}

/* Ordinary CSS entrances also work on local files and without native page transitions. */
html,
body {
    overflow-x: clip;
}

main,
.footer {
    animation: var(--page-enter-animation) var(--duration-page) var(--ease-glass) backwards;
}

/* Start immediately on a fresh load, or resume as soon as a native transition ends.
   The image keeps its parallax timeline; CSS timing cannot detect image readiness. */
.home > .background {
    animation: background-in var(--duration-background) var(--ease-glass) var(--delay-background) backwards;
}

/* Pause rather than reset: a visible outgoing wallpaper must stay visible in its snapshot. */
:root:active-view-transition .home > .background {
    animation-play-state: paused;
}

@keyframes background-in {
    from {
        opacity: 0;
    }

    to {
        opacity: 1;
    }
}

/* Keep the entrance clock running beneath native snapshots so it cannot replay afterward. */
:root:active-view-transition :is(main, .footer) {
    transform: none !important;
    opacity: 1 !important;
}

/* Browsers without scroll timelines leave content visible and navigation stationary. */
@supports (animation-timeline: view(block 0px)) and (animation-range: entry 0% exit 100%) {
    .reveal,
    .fade {
        --reveal-lift: 0px;
        --reveal-range: min(var(--scroll-reveal-range), 33vh);
        animation: reveal-in 1ms ease-out both,
            reveal-out 1ms ease-in forwards;
        animation-timeline: view(block 0px), view(block 0px);
        animation-range: entry 0% entry var(--reveal-range),
            exit calc(100% - var(--reveal-range)) exit 100%;
    }

    .reveal {
        --reveal-lift: var(--scroll-reveal-offset);
    }
}

@supports (animation-timeline: scroll(root block)) {
    @media (width > 640px) and (hover: hover) and (pointer: fine) {
        .home > .background img {
            animation: background-scroll 1ms linear both;
            animation-timeline: scroll(root block);
        }
    }

    @media (width > 640px) {
        .topnav {
            animation: nav-scroll 1ms linear both;
            animation-timeline: scroll(root block);
        }
    }
}

@keyframes reveal-in {
    from {
        opacity: 0;
        transform: translateY(var(--reveal-lift));
    }

    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* No backwards fill: the exit must not override the entrance before it starts. */
@keyframes reveal-out {
    from {
        opacity: 1;
        transform: translateY(0);
    }

    to {
        opacity: 0;
        transform: translateY(calc(-1 * var(--reveal-lift)));
    }
}

/* Traverse only the hidden portion of the image over the entire page scroll. */
@keyframes background-scroll {
    from {
        transform: translateY(0);
    }

    to {
        transform: translateY(calc((var(--background-visible) - 1) * 100%));
    }
}

@keyframes nav-scroll {
    0%, 5%, 95%, 100% {
        opacity: 1;
        transform: translateY(0);
        pointer-events: auto;
    }

    10%, 90% {
        opacity: 0;
        transform: translateY(calc(-100% - var(--header-height)));
        pointer-events: none;
    }
}

/* Keyboard navigation and fragment links always expose their destination. */
:is(.reveal, .fade):is(:focus-within, :target, :has(:target)),
:target :is(.reveal, .fade),
.topnav:focus-within {
    animation: none;
    opacity: 1;
    transform: none;
    pointer-events: auto;
}
