/* Copyright (c) 2026 Bc. Jan Krejčí. All rights reserved.
   Proprietary and confidential. See LICENSE in the project root. */

/* ==========================================================================
   Motion language of the hall
   ==========================================================================

   The building is a long, calm, well-lit space. Sport inside it is not calm:
   it is a run-up, a sudden stop, a change of direction, a landing. The motion
   language takes the physics and leaves the props — there are no bouncing balls
   and no sport icons flying about, only the way things accelerate and settle.

   Three rules govern everything below.

   1. Energy at the start, composure at the end. A sprinter leaves the blocks
      fast and finishes controlled. So does every element here: the curves are
      front-loaded, never symmetrical, and nothing overshoots more than a
      hair's breadth.

   2. One movement beats five. A page has at most one thing that moves
      noticeably. Everything else supports it or stays still.

   3. Motion explains a change of state. If removing an animation costs the
      visitor nothing in orientation or character, the animation should not
      exist.

   Categories, and what each is for:

     MICRO      120-200 ms   the answer to a finger or a cursor. Must feel
                             instant; anything slower reads as lag.
     UI         220-340 ms   menus, toggles, selections. Long enough to follow,
                             short enough never to wait for.
     CONTENT    420-620 ms   text and photographs arriving. Reading speed, not
                             machine speed.
     PAGE       480-720 ms   one page becoming another.
     CINEMATIC  900-1400 ms  reserved. Three places on the whole site.
   ========================================================================== */

:root {
    /* --- Durations ------------------------------------------------------ */
    --m-micro: 150ms;
    --m-micro-out: 110ms;   /* leaving is always faster than arriving */
    --m-ui: 280ms;
    --m-ui-out: 200ms;
    --m-content: 520ms;
    --m-page: 560ms;
    --m-page-out: 260ms;
    --m-cinematic: 1100ms;

    /* --- Easing ---------------------------------------------------------
       Named for what they do, not for where they came from. */

    /* The default. Leaves immediately, arrives without a bump. Everything
       that simply needs to feel good uses this. */
    --m-ease: cubic-bezier(0.22, 0.61, 0.24, 1);

    /* The run-up. Slow to commit, then quick — for things that grow or open
       out of a small origin. */
    --m-ease-launch: cubic-bezier(0.62, 0.02, 0.32, 1);

    /* The landing. Most of the distance is covered in the first third, then
       the element settles like a jump landing on a sprung floor. Used for
       anything that travels a visible distance. */
    --m-ease-land: cubic-bezier(0.16, 0.84, 0.22, 1);

    /* The stop. A near-instant arrival with the faintest settle. Used for
       micro feedback where any visible travel would feel slow. */
    --m-ease-snap: cubic-bezier(0.3, 1.4, 0.5, 1);

    /* Symmetrical, for crossfades only. Never for anything that moves. */
    --m-ease-fade: cubic-bezier(0.4, 0, 0.6, 1);

    /* --- Distances -------------------------------------------------------
       Small. The eye reads 8-20 px as "it arrived"; 40 px reads as "it flew
       in from somewhere", which is a different, cheaper statement. */
    --m-rise: 14px;
    --m-rise-lg: 22px;
    --m-nudge: 2px;

    /* --- Stagger ---------------------------------------------------------
       Long enough to read as a sequence, short enough that the last item is
       not perceptibly late. Six items at 60 ms is 300 ms of sequence. */
    --m-stagger: 60ms;
}

/* ==========================================================================
   Reveal — content arriving as the visitor reaches it
   ==========================================================================
   Deliberately not the usual "every paragraph fades up 30 px". Body text is
   present immediately; only structural openings and photographs are revealed,
   and they are revealed by uncovering rather than by flying.

   The .is-in class is added by chropyne-motion.js through an
   IntersectionObserver. If the script never runs, everything below is visible
   and static — the initial state is only applied when the script has announced
   itself by putting .motion-ready on <html>.
   ========================================================================== */

.motion-ready [data-reveal] {
    opacity: 0;
    will-change: opacity, transform;
}

/* Rise: a heading or a block arriving. The travel is small and the settle is
   the point. */
.motion-ready [data-reveal="rise"] {
    transform: translate3d(0, var(--m-rise), 0);
}

/* Uncover: the element is already in place and a mask retreats off it, the way
   a light comes up on a court rather than an object being carried in. This is
   the signature reveal of the site and is reserved for display type. */
.motion-ready [data-reveal="uncover"] {
    opacity: 1;
    clip-path: inset(0 0 100% 0);
}

/* Photograph: no travel at all. The frame holds still and the image inside
   settles out of a very slight enlargement, like a camera finding focus. */
.motion-ready [data-reveal="photo"] {
    opacity: 0;
}

.motion-ready [data-reveal="photo"] img {
    transform: scale(1.035);
    transition: transform var(--m-cinematic) var(--m-ease-land);
    will-change: transform;
}

.motion-ready [data-reveal].is-in {
    opacity: 1;
    transform: none;
    clip-path: inset(0 0 0 0);
    transition:
        opacity var(--m-content) var(--m-ease) var(--m-delay, 0ms),
        transform var(--m-content) var(--m-ease-land) var(--m-delay, 0ms),
        clip-path var(--m-content) var(--m-ease-land) var(--m-delay, 0ms);
}

.motion-ready [data-reveal="photo"].is-in img {
    transform: none;
}

/* Already in front of the visitor when the page opened: simply there, with no
   movement. The page entrance has already said that something arrived, and an
   animation the visitor sits and watches is one that got in the way. Movement
   is for content they travel to. */
.motion-ready [data-reveal].is-instant,
.motion-ready [data-reveal].is-instant img {
    transition: none;
}

/* The observer stops watching an element once it has arrived; dropping
   will-change afterwards releases the layer. */
.motion-ready [data-reveal].is-done,
.motion-ready [data-reveal].is-done img {
    will-change: auto;
}

/* A page's own title is never withheld, whatever the markup around it says.
   The visitor asked for this page; its h1 is the answer, and an answer that
   waits for an observer is a worse page for a rule the visitor did not ask
   for. The reveal attributes are placed to respect this already — this rule
   is here so a later edit cannot quietly take it away. */
.motion-ready [data-reveal]:has(> h1),
.motion-ready [data-reveal]:has(> * > h1) {
    opacity: 1;
    transform: none;
    clip-path: none;
    will-change: auto;
}

/* ==========================================================================
   Page entrance choreography
   ==========================================================================
   Plays on first load and on every internal navigation. Order is deliberate:
   the photograph or surface is there first so the page has a place, the display
   type uncovers into it, then supporting text and actions follow.
   ========================================================================== */

@keyframes m-page-in {
    from { opacity: 0; transform: translate3d(0, 8px, 0); }
    to { opacity: 1; transform: none; }
}

@keyframes m-uncover-up {
    from { clip-path: inset(0 0 100% 0); }
    to { clip-path: inset(0 0 -2% 0); }
}

@keyframes m-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}

@keyframes m-hero-settle {
    /* The hero photograph does not slide or fade in — it is already there. It
       releases a fraction of a percent of scale, which reads as the room
       settling rather than as an animation starting. */
    from { transform: scale(1.04); }
    to { transform: scale(1); }
}

/* The page's entrance.

   `backwards`, not `both`, and the difference is load bearing. With `both` the
   animation keeps its final value applied for ever, and an animated
   `transform: none` computes to the identity matrix rather than to none. A
   transform of any kind — even one that moves nothing — makes the element a
   containing block for `position: fixed`, so every fixed thing inside the page
   silently becomes absolute. That is how the booking summary ended up pinned to
   the bottom of the document instead of to the bottom of the window.

   `backwards` fills only before the animation starts, and afterwards the
   element returns to its own styles, which already are the finished state.

   Every delay below is written as `var(--m-in-shift) + its own delay`. The
   shift is zero except for one moment: when Blazor swaps the server-rendered
   page for the interactive one, every element here is new, and new elements
   start their animations from the beginning. That was the second of the two
   blinks on every refresh. chropyne-motion.js measures how far the entrance
   had got and sets the shift to minus that, on the new <main>, so the new
   elements continue from where the old ones were instead of starting again. */
.motion-ready .ch-main {
    animation: m-page-in var(--m-page) var(--m-ease-land) backwards;
    animation-delay: var(--m-in-shift, 0ms);
}

.motion-ready .ch-hero__bg img {
    animation: m-hero-settle var(--m-cinematic) var(--m-ease-land) backwards;
    animation-delay: var(--m-in-shift, 0ms);
}

.motion-ready .ch-hero h1 {
    animation: m-uncover-up var(--m-content) var(--m-ease-land) backwards;
    animation-delay: calc(var(--m-in-shift, 0ms) + 80ms);
}

.motion-ready .ch-hero__lead {
    animation: m-page-in var(--m-content) var(--m-ease-land) backwards;
    animation-delay: calc(var(--m-in-shift, 0ms) + 200ms);
}

.motion-ready .ch-hero__actions {
    animation: m-page-in var(--m-content) var(--m-ease-land) backwards;
    animation-delay: calc(var(--m-in-shift, 0ms) + 300ms);
}

.motion-ready .ch-hero__facts > * {
    animation: m-page-in var(--m-content) var(--m-ease-land) backwards;
}

.motion-ready .ch-hero__facts > *:nth-child(1) { animation-delay: calc(var(--m-in-shift, 0ms) + 380ms); }
.motion-ready .ch-hero__facts > *:nth-child(2) { animation-delay: calc(var(--m-in-shift, 0ms) + 440ms); }
.motion-ready .ch-hero__facts > *:nth-child(3) { animation-delay: calc(var(--m-in-shift, 0ms) + 500ms); }

/* The colour band across the top of the hero draws itself once, on the home
   page only. It is the one place the façade is quoted, so it earns the gesture. */
@keyframes m-band-draw {
    from { transform: scaleX(0); }
    to { transform: scaleX(1); }
}

.motion-ready .ch-hero--photo::after {
    transform-origin: left center;
    animation: m-band-draw var(--m-cinematic) var(--m-ease-launch) backwards;
    animation-delay: calc(var(--m-in-shift, 0ms) + 120ms);
}

/* ==========================================================================
   Why there are no view transitions here
   ==========================================================================
   There used to be. `document.startViewTransition()` paints a snapshot of the
   page in the top layer and holds it there for the length of the animation, and
   an element that carries a `view-transition-name` is not painted at all while
   that runs — its snapshot is painted instead. So for those few hundred
   milliseconds the header on screen is a photograph taken before the cursor
   moved: it cannot show a hover state, and it cannot show the cursor changing
   shape, however the events are routed. `::view-transition { pointer-events:
   none }` does get the click through to the real element underneath, and the
   click did work — but the visitor, seeing nothing respond, has no way to know
   that, and clicking through the menu quickly feels broken.

   Taking the name off the header does not help. It is then inside the root
   snapshot, which is equally frozen, and it cross-fades with itself on every
   navigation.

   The entrance choreography above does the whole job on its own, in every
   browser rather than three of them, with nothing ever laid over the page. The
   full reasoning, and what was given up, is at the top of chropyne-motion.js.
   ========================================================================== */

/* ==========================================================================
   Micro-interactions
   ==========================================================================
   The rule for every hover below: the element acknowledges the cursor, it does
   not perform for it. Nothing scales, because a scaling card announces itself
   as a widget; these are supposed to feel like objects on a surface.
   ========================================================================== */

.ch-cta {
    transition:
        background-color var(--m-micro) var(--m-ease),
        border-color var(--m-micro) var(--m-ease),
        transform var(--m-micro-out) var(--m-ease-snap),
        box-shadow var(--m-ui) var(--m-ease);
}

.ch-cta:hover {
    transform: translate3d(0, calc(var(--m-nudge) * -1), 0);
    box-shadow: 0 6px 18px rgba(35, 40, 43, .16);
}

/* Pressing must feel like contact with a floor: it arrives instantly and stops
   dead. This is the one place in the whole site with no easing curve at all. */
.ch-cta:active {
    transform: translate3d(0, 1px, 0);
    transition-duration: 40ms;
    box-shadow: none;
}

/* Navigation: the indicator under the active item travels between items rather
   than disappearing here and reappearing there, so the eye keeps hold of where
   it is in the site. The travelling element is a single bar positioned by the
   script; the per-link border is only its resting state on a small screen. */
.ch-nav__link {
    transition: color var(--m-micro) var(--m-ease);
}

.ch-nav__indicator {
    position: absolute;
    bottom: 0;
    height: 2px;
    background: var(--ch-wood-400);
    border-radius: 2px;
    opacity: 0;
    pointer-events: none;
    transform-origin: left center;
    transition:
        transform var(--m-ui) var(--m-ease-land),
        width var(--m-ui) var(--m-ease-land),
        opacity var(--m-micro) var(--m-ease);
}

.ch-nav__indicator.is-visible { opacity: 1; }

/* With the travelling bar present the static underline would double up. */
.motion-ready .ch-nav .ch-nav__link.active { border-bottom-color: transparent; }

/* Panels and gallery tiles: the photograph inside moves a little further than
   the frame that holds it. That difference in rate is what makes a flat card
   read as having depth, and it costs one transform. */
.ch-panel--link,
.ch-gallery__item {
    transition:
        border-color var(--m-ui) var(--m-ease),
        box-shadow var(--m-ui) var(--m-ease),
        transform var(--m-ui) var(--m-ease-land);
}

.ch-gallery__item img {
    transition: transform var(--m-content) var(--m-ease-land);
}

@media (hover: hover) and (pointer: fine) {
    .ch-gallery__item:hover {
        transform: translate3d(0, -3px, 0);
    }

    .ch-gallery__item:hover img {
        transform: scale(1.03);
    }

    .ch-panel--link:hover {
        transform: translate3d(0, -2px, 0);
    }
}

/* Links in running text: the rule grows from the side the reader came from,
   which is cheaper to read than a rule that fades in along its whole length. */
.ch-footer a,
.ch-note a {
    background-image: linear-gradient(currentColor, currentColor);
    background-repeat: no-repeat;
    background-position: 0 100%;
    background-size: 0 1px;
    text-decoration: none;
    transition: background-size var(--m-ui) var(--m-ease-land), color var(--m-micro) var(--m-ease);
}

.ch-footer a:hover,
.ch-note a:hover {
    background-size: 100% 1px;
    text-decoration: none;
}

/* ==========================================================================
   Mobile menu
   ==========================================================================
   Not a panel sliding in from the side. The surface arrives first so there is
   somewhere to look, then the items land in sequence — the same order a person
   would read them. Closing reverses the idea but takes half the time, because
   waiting to leave is the most common way a menu feels slow.
   ========================================================================== */

@keyframes m-menu-surface {
    from { opacity: 0; transform: translate3d(0, -8px, 0) scaleY(.98); }
    to { opacity: 1; transform: none; }
}

@keyframes m-menu-item {
    from { opacity: 0; transform: translate3d(0, -6px, 0); }
    to { opacity: 1; transform: none; }
}

@media (max-width: 860px) {
    .motion-ready .ch-nav.is-open {
        transform-origin: top center;
        animation: m-menu-surface var(--m-ui) var(--m-ease-land) backwards;
    }

    .motion-ready .ch-nav.is-open > * {
        animation: m-menu-item var(--m-ui) var(--m-ease-land) backwards;
    }

    .motion-ready .ch-nav.is-open > *:nth-child(1) { animation-delay: 40ms; }
    .motion-ready .ch-nav.is-open > *:nth-child(2) { animation-delay: 70ms; }
    .motion-ready .ch-nav.is-open > *:nth-child(3) { animation-delay: 100ms; }
    .motion-ready .ch-nav.is-open > *:nth-child(4) { animation-delay: 130ms; }
    .motion-ready .ch-nav.is-open > *:nth-child(5) { animation-delay: 160ms; }
    .motion-ready .ch-nav.is-open > *:nth-child(6) { animation-delay: 190ms; }
    .motion-ready .ch-nav.is-open > *:nth-child(7) { animation-delay: 210ms; }
    .motion-ready .ch-nav.is-open > *:nth-child(8) { animation-delay: 230ms; }

    /* The burger's three bars become a cross by travelling, not by swapping. */
    .ch-burger span,
    .ch-burger span::before,
    .ch-burger span::after {
        transition:
            transform var(--m-ui) var(--m-ease-land),
            opacity var(--m-micro) var(--m-ease);
    }

    .ch-burger[aria-expanded="true"] span { background: transparent; }
    .ch-burger[aria-expanded="true"] span::before { transform: translateY(6px) rotate(45deg); }
    .ch-burger[aria-expanded="true"] span::after { transform: translateY(-6px) rotate(-45deg); }
}

/* ==========================================================================
   Reduced motion
   ==========================================================================
   Not "animations off". Travel, parallax and scale go; the crossfades that tell
   a visitor a page has changed stay, because removing those would leave someone
   who relies on them with less information, not more.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
    .motion-ready [data-reveal],
    .motion-ready [data-reveal="uncover"] {
        opacity: 1;
        transform: none;
        clip-path: none;
    }

    .motion-ready [data-reveal="photo"] img { transform: none; }

    .motion-ready .ch-main,
    .motion-ready .ch-hero h1,
    .motion-ready .ch-hero__lead,
    .motion-ready .ch-hero__actions,
    .motion-ready .ch-hero__facts > *,
    .motion-ready .ch-hero__bg img,
    .motion-ready .ch-hero--photo::after,
    .motion-ready .ch-nav.is-open,
    .motion-ready .ch-nav.is-open > * {
        animation: m-fade-in 120ms var(--m-ease-fade) both;
    }

    .motion-ready .ch-hero__bg img,
    .motion-ready .ch-hero--photo::after {
        animation: none;
        transform: none;
    }

    /* The staggered entrance of the hero facts is the point of the delays; with
       the movement gone they would only be a wait. Matched selector by selector
       because the delays are set per child and would otherwise outrank a
       shorter rule. */
    .motion-ready .ch-hero__facts > *:nth-child(1),
    .motion-ready .ch-hero__facts > *:nth-child(2),
    .motion-ready .ch-hero__facts > *:nth-child(3) {
        animation-delay: 0s;
    }

    .ch-cta:hover,
    .ch-gallery__item:hover,
    .ch-gallery__item:hover img,
    .ch-panel--link:hover {
        transform: none;
    }
}
