/* ─────────────────────────────────────────────────────────────────────────────────────────────────────────────
   NAVR — Experience layer (global, NOT Blazor-scoped).

   Two jobs, both additive:

   1. TOKENS. Every value below is EXTRACTED from a literal that already exists in Home.razor.css,
      CmsPage.razor.css, PublicHeader.razor.css or CustomBlockSection.razor.css. No new colour, radius, shadow
      or timing is invented here. Custom properties inherit through Blazor's CSS isolation (isolation rewrites
      SELECTORS, not inherited property values), so a scoped component can read var(--navr-*) without this file
      needing to know its scope id. Existing literals are intentionally left in place — this file gives new and
      refined rules one source of truth without a risky find-and-replace across four working stylesheets.

   2. EXPERIENCE UTILITIES. A scroll-reveal system and the motion/focus rules that go with it. The public site
      had no animation infrastructure at all (no GSAP, no library, no JS beyond App.razor's muted-autoplay fix),
      so this is the smallest possible addition rather than a new framework.

   NO-JS SAFETY (important): content is VISIBLE BY DEFAULT. The hidden start state applies only under
   `.navr-js`, a class that the head script in App.razor adds only when IntersectionObserver exists AND the
   visitor has not asked for reduced motion. A blocked script, an old browser, or a JS error therefore leaves
   every section fully readable — a reveal system can never hide NAVR's content.
   ───────────────────────────────────────────────────────────────────────────────────────────────────────────── */

:root {
    /* ── Palette (deep leaf green → gold accent → leaf-tinted papers → cream → ink) ───────────────────────── */
    --navr-green-900: #12241a; /* Why-band top stop */
    --navr-green-850: #163d24; /* CTA gradient start */
    --navr-green-800: #10231a; /* video letterbox ground */
    --navr-green-700: #1f5130; /* primary deep leaf green */
    --navr-green-600: #2e7d4f;
    --navr-green-500: #3f9d63;
    --navr-gold: #c9a54a;
    --navr-paper: #f5f8f3; /* soft leaf-tinted paper (About) */
    --navr-paper-2: #f0f5ec; /* second tint (Testimonials) */
    --navr-surface: #ffffff;
    --navr-cream: #f2ede1;
    --navr-cream-strong: #f6f1e6;
    --navr-ink: #1c2b1f;
    --navr-ink-soft: #2b2b2b;

    /* ── Radius ───────────────────────────────────────────────────────────────────────────────────────────── */
    --navr-radius-sm: 0.5rem;
    --navr-radius-md: 0.75rem;
    --navr-radius-lg: 0.9rem;
    --navr-radius-xl: 1rem;
    --navr-radius-pill: 999px;

    /* ── Elevation ────────────────────────────────────────────────────────────────────────────────────────── */
    --navr-shadow-sm: 0 6px 20px rgba(20, 44, 28, 0.12);
    --navr-shadow-md: 0 10px 30px rgba(20, 44, 28, 0.15);
    --navr-shadow-lg: 0 18px 48px rgba(20, 44, 28, 0.20);

    /* ── Rhythm (the section padding pair already repeated in every public stylesheet) ─────────────────────── */
    --navr-section-y: clamp(2.5rem, 7vw, 6rem);
    --navr-section-x: clamp(1.25rem, 5vw, 4rem);

    /* ── Focal point ──────────────────────────────────────────────────────────────────────────────────────────
       The default anchor for any container that crops with `object-fit: cover`.

       READ THE PERCENTAGE CAREFULLY — it is the opposite of the intuition. `object-position: 50% Y%` aligns the
       Y% point OF THE IMAGE with the Y% point of the box, so with a portrait source overflowing a landscape box
       the vertical offset is (boxHeight - scaledHeight) * Y. A LOWER Y keeps MORE of the top:

           Y = 0%    image top meets box top   -> the head is fully kept
           Y = 50%   centred (the CSS default) -> top and bottom trimmed equally
           Y = 100%  bottom aligned            -> the head is gone

       This token previously said 30%, chosen on the mistaken reading that "faces are in the upper third, so
       anchor at a third". 30% actually DISCARDS the first 30% of the overflow from the top. Simulating the real
       card box (480x360 from --cb-card-h 360 and --cb-ratio 4/3) against a 800x1200 portrait source, with the
       head occupying the top 15% of the frame:

           50%  ->   0% of the head survives
           30%  ->   0% of the head survives   <- the value that was shipped
           12%  ->  60%
            8%  ->  73%
            0%  -> 100%

       So 30% was no better than the browser default, which is why heads kept being cut after the "focal anchor"
       was added. Top-aligned (0%) is the only value that guarantees the head at every source ratio, and on a
       landscape source in a landscape box the vertical overflow is small enough that top-aligning is visually
       indistinguishable from centring. The crop now comes entirely off the bottom.

       Site-wide via a token, not a per-image hack: it holds for whatever the CMS contains, now and after the
       next upload, and needs no stored image dimensions. */
    --navr-focal: 50% 0%;

    /* ── Motion ───────────────────────────────────────────────────────────────────────────────────────────── */
    --navr-ease: cubic-bezier(0.22, 0.61, 0.36, 1); /* decelerating; no overshoot, no bounce */
    --navr-dur: 0.7s;
    --navr-dur-fast: 0.35s;
    --navr-stagger-step: 90ms;
}

/* ═══ SCROLL REVEAL ══════════════════════════════════════════════════════════════════════════════════════════
   Only `opacity` and `transform` are animated — both GPU-composited, neither triggers layout.
   `will-change` is deliberately NOT used: the hidden state applies to every unrevealed section at once, so the
   hint would ask the compositor for a layer per block on a long CMS page before any of them is near the
   viewport. The transition is composited without it. */

.navr-js .navr-reveal {
    opacity: 0;
    transform: translate3d(0, 18px, 0);
}

/* Horizontal variants. Available, but NOT used anywhere yet — they are the vocabulary for the later storytelling
   step. Before applying one, check that the element cannot cause horizontal scroll: a negative translate on a
   full-width section extends the scrollable area even while it is off-position. Vertical is the safe default,
   which is why the base reveal above uses it. */
.navr-js .navr-reveal--left { transform: translate3d(-24px, 0, 0); }
.navr-js .navr-reveal--right { transform: translate3d(24px, 0, 0); }
.navr-js .navr-reveal--zoom { transform: scale(1.035); }

.navr-js .navr-reveal.is-in {
    opacity: 1;
    transform: none;
    transition: opacity var(--navr-dur) var(--navr-ease), transform var(--navr-dur) var(--navr-ease);
}

/* Staggered children — for a grid or card row, so the group arrives as a sequence rather than a single slab.
   Delays are capped at the 8th child: beyond that the wait would read as "broken", not "considered". */
.navr-js .navr-stagger > * {
    opacity: 0;
    transform: translate3d(0, 14px, 0);
}

.navr-js .navr-stagger.is-in > * {
    opacity: 1;
    transform: none;
    transition: opacity var(--navr-dur) var(--navr-ease), transform var(--navr-dur) var(--navr-ease);
}

.navr-js .navr-stagger.is-in > *:nth-child(1) { transition-delay: 0ms; }
.navr-js .navr-stagger.is-in > *:nth-child(2) { transition-delay: calc(var(--navr-stagger-step) * 1); }
.navr-js .navr-stagger.is-in > *:nth-child(3) { transition-delay: calc(var(--navr-stagger-step) * 2); }
.navr-js .navr-stagger.is-in > *:nth-child(4) { transition-delay: calc(var(--navr-stagger-step) * 3); }
.navr-js .navr-stagger.is-in > *:nth-child(5) { transition-delay: calc(var(--navr-stagger-step) * 4); }
.navr-js .navr-stagger.is-in > *:nth-child(6) { transition-delay: calc(var(--navr-stagger-step) * 5); }
.navr-js .navr-stagger.is-in > *:nth-child(7) { transition-delay: calc(var(--navr-stagger-step) * 6); }
.navr-js .navr-stagger.is-in > *:nth-child(n + 8) { transition-delay: calc(var(--navr-stagger-step) * 7); }

/* ═══ FOCUS ══════════════════════════════════════════════════════════════════════════════════════════════════
   An explicit, high-contrast ring for the public site's own interactive elements. Applied by class rather than
   to every `a`/`button` on the page, so MudBlazor's and Radzen's own focus treatments are left completely
   alone. Gold on green and gold on cream both clear WCAG AA for a non-text UI component (3:1). */
.navr-focus-ring:focus-visible {
    outline: 3px solid var(--navr-gold);
    outline-offset: 3px;
}

/* ═══ REDUCED MOTION ═════════════════════════════════════════════════════════════════════════════════════════
   Belt and braces. The head script already refuses to add `.navr-js` when reduce is requested, so these rules
   normally have nothing to act on; they still exist for the case where the preference is switched on AFTER the
   page has loaded (the media query re-evaluates live, the one-shot script does not).

   Deliberately NOT a blanket `*{animation:none}` reset: that would also freeze the Hero and CmsPage loading
   spinners mid-rotation, which reads as a hung page rather than as a calmer one. Only this file's own motion
   and the specific hover transforms named in the scoped stylesheets are neutralised. */
@media (prefers-reduced-motion: reduce) {
    .navr-js .navr-reveal,
    .navr-js .navr-stagger > * {
        opacity: 1 !important;
        transform: none !important;
        transition: none !important;
    }
}

/* ═══ 5B-3 — STORYTELLING LAYER ══════════════════════════════════════════════════════════════════════════════
   Four mechanisms, all built on the reveal system above rather than beside it. Every one is opt-in by class, so
   a section that is not marked keeps behaving exactly as it did after 5B-2 — this is why the page does not end
   up applying the same effect to everything (§4, §18).

   MECHANISM            DRIVEN BY                     USED FOR
   .navr-settle         one-shot CSS animation        hero media coming to rest on arrival
   .navr-figure         the binary `is-in` class      a large image easing down to its true size
   .navr-scene          --navr-p, 0 -> 1 on scroll    parallax and progressive emphasis
   .navr-lift           --navr-p                      a quote/story moment gaining presence mid-viewport

   Only `transform` and `opacity` are animated anywhere below. Nothing animates width, height, top or left.
   ═══════════════════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── 1. HERO SETTLE (§7) ─────────────────────────────────────────────────────────────────────────────────────
   The hero media comes to rest once, on arrival, instead of being static from the first painted frame. A single
   1.4s ease with `both` fill — it ends and never runs again, so nothing is left animating behind the content. */
.navr-js .navr-settle {
    animation: navr-settle 1400ms var(--navr-ease) both;
}

/* Opacity only. It used to scale the media from 1.055 inside the clipped hero, which hid the frame's edges for
   the whole 1.4s — the media invariant (navr-media.css) forbids transforming content media at all. */
@keyframes navr-settle {
    from { opacity: 0.55; }
    to   { opacity: 1; }
}

/* ── 2. FIGURE ENTRANCE (§8) ─────────────────────────────────────────────────────────────────────────────────
   A large image eases from 1.04 to its exact layout size as it arrives.

   DELIBERATELY NO `overflow: hidden` ON A WRAPPER. Scaling inside a clipped box would crop 4% off every edge
   during the entrance — which is precisely the head-cropping regression 5B-2 was spent eliminating (§8 forbids
   reintroducing it). Scaling the image itself, unclipped, means the picture is momentarily 4% larger and then
   settles to exactly the box the 5B-2 crop rules computed. The resting frame is byte-identical to today's. */
.navr-js .navr-figure {
    transform: scale(1.04);
    opacity: 0;
}

.navr-js .navr-figure.is-in {
    transform: scale(1);
    opacity: 1;
    transition: transform 1100ms var(--navr-ease), opacity 800ms var(--navr-ease);
}

/* ── 3. SCENE PARALLAX (§12) ─────────────────────────────────────────────────────────────────────────────────
   Total travel is 28px across an entire viewport crossing — roughly 14px either side of centre. That is enough
   for the eye to register depth and far too little to unsettle text or drag the image away from its neighbour.

   DESKTOP ONLY. Below 1024px the rule does not exist, so phones and tablets do no transform work at all (§13)
   and there is no chance of a translate widening the scrollable area on a narrow screen. */
@media (min-width: 1024px) {
    .navr-js .navr-parallax {
        transform: translate3d(0, calc((var(--navr-p, 0.5) - 0.5) * -28px), 0);
    }
}

/* ── 4. STORY LIFT (§10) ─────────────────────────────────────────────────────────────────────────────────────
   A quote or story moment is at full strength while it occupies the middle of the viewport and eases back very
   slightly at the edges, so the eye is drawn to it as it passes rather than being shouted at throughout.
   The opacity floor is 0.86, never near zero: the text stays fully readable at every scroll position, and it is
   never the only way the content is available (§14). */
@media (min-width: 768px) {
    .navr-js .navr-lift {
        opacity: calc(0.86 + 0.14 * (1 - 2 * max(var(--navr-p, 0.5) - 0.5, 0.5 - var(--navr-p, 0.5))));
        transform: scale(calc(0.988 + 0.012 * (1 - 2 * max(var(--navr-p, 0.5) - 0.5, 0.5 - var(--navr-p, 0.5)))));
    }
}

/* ── REDUCED MOTION (§14) ────────────────────────────────────────────────────────────────────────────────────
   Every 5B-3 mechanism is neutralised to its resting state. The head guard in App.razor already withholds
   `.navr-js` when reduce is requested, so these rules are the safety net for the preference being switched on
   after load. Content ends in normal document flow at full opacity and true size — nothing depends on motion. */
@media (prefers-reduced-motion: reduce) {
    .navr-js .navr-settle,
    .navr-js .navr-figure,
    .navr-js .navr-figure.is-in,
    .navr-js .navr-parallax,
    .navr-js .navr-lift {
        animation: none !important;
        transform: none !important;
        opacity: 1 !important;
        transition: none !important;
    }
}

/* ── BACK TO TOP ─────────────────────────────────────────────────────────────────────────────────────────────
   The public site's one floating control, rendered once by MainLayout. Its colours are read entirely from the
   theme custom properties MainLayout republishes on .pub-theme (--navr-theme-*), so switching theme repaints it
   with no JavaScript and no code change here; the --navr-* literals are only the fallback for the instant
   before the wrapper's inline style lands.

   The pairing is deliberate: the SURFACE token is the ground and the PRIMARY token is the icon, because the
   theme engine guarantees those two contrast (Surface is #FFFFFF on light themes and #242424 on dark ones,
   while Primary is always a saturated accent). Hover simply swaps them, so contrast holds in both directions on
   every theme in the catalogue without per-theme rules. */
.navr-totop {
    position: fixed;
    right: clamp(0.9rem, 3vw, 1.75rem);
    bottom: calc(clamp(0.9rem, 3vw, 1.75rem) + env(safe-area-inset-bottom, 0px));
    /* Below the header's stacking context (--nh-z, 1000) so the premium mobile navigation overlay and the
       header always win; above ordinary page content and the footer. */
    z-index: 900;

    width: 3rem;
    height: 3rem;
    display: grid;
    place-items: center;
    padding: 0;
    margin: 0;
    box-sizing: border-box;

    border-radius: var(--navr-radius-pill, 999px);
    border: 1px solid color-mix(in srgb, var(--navr-theme-primary, var(--navr-green-700)) 34%, transparent);
    background: var(--navr-theme-surface, var(--navr-surface));
    color: var(--navr-theme-primary, var(--navr-green-700));
    box-shadow: var(--navr-shadow-md, 0 10px 30px rgba(20, 44, 28, .15));
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;

    /* Hidden resting state. `visibility` is stepped rather than transitioned so the button becomes
       non-focusable immediately when it hides, and focusable the instant it shows. */
    opacity: 0;
    visibility: hidden;
    transform: translateY(10px);
    pointer-events: none;
    transition: opacity var(--navr-dur-fast, .35s) var(--navr-ease, ease),
                transform var(--navr-dur-fast, .35s) var(--navr-ease, ease),
                background .25s ease, color .25s ease, border-color .25s ease, box-shadow .25s ease,
                visibility 0s linear var(--navr-dur-fast, .35s);
}

.navr-totop.is-visible {
    opacity: 1;
    visibility: visible;
    transform: none;
    pointer-events: auto;
    transition: opacity var(--navr-dur-fast, .35s) var(--navr-ease, ease),
                transform var(--navr-dur-fast, .35s) var(--navr-ease, ease),
                background .25s ease, color .25s ease, border-color .25s ease, box-shadow .25s ease,
                visibility 0s linear 0s;
}

.navr-totop__icon { display: grid; place-items: center; line-height: 0; }

/* Hover / focus: the accent becomes the ground and the surface becomes the icon — the same two theme tokens,
   swapped, so the hover state is as theme-aware as the resting state. */
.navr-totop:hover {
    background: var(--navr-theme-primary, var(--navr-green-700));
    color: var(--navr-theme-surface, var(--navr-surface));
    border-color: transparent;
    transform: translateY(-2px);
    box-shadow: var(--navr-shadow-lg, 0 18px 48px rgba(20, 44, 28, .2));
}

.navr-totop:focus-visible {
    background: var(--navr-theme-primary, var(--navr-green-700));
    color: var(--navr-theme-surface, var(--navr-surface));
    outline: 2px solid var(--navr-theme-primary, var(--navr-green-700));
    outline-offset: 3px;
}

.navr-totop:active { transform: scale(.94); }

/* A dark theme's #242424 surface needs a slightly stronger hairline to read against a dark page ground. */
[data-dark="true"] .navr-totop {
    border-color: color-mix(in srgb, var(--navr-theme-primary, var(--navr-green-500)) 52%, transparent);
    box-shadow: 0 10px 30px rgba(0, 0, 0, .45);
}

/* Comfortable touch target on coarse pointers, kept clear of the safe area. */
@media (pointer: coarse) {
    .navr-totop { width: 3.25rem; height: 3.25rem; }
}

@media (min-width: 1200px) {
    .navr-totop { width: 3.25rem; height: 3.25rem; }
}

/* Narrow phones: tuck in a little so the control never crowds the viewport edge. */
@media (max-width: 359.98px) {
    .navr-totop { right: .75rem; bottom: calc(.75rem + env(safe-area-inset-bottom, 0px)); }
}

/* ── REDUCED MOTION ──────────────────────────────────────────────────────────────────────────────────────────
   The control still appears and disappears — that is its function — but without travel or easing. The
   scroll itself is switched to an instant jump in navr-experience.js under the same preference. */
@media (prefers-reduced-motion: reduce) {
    .navr-totop {
        transition: opacity .01ms linear, visibility 0s linear .01ms;
        transform: none;
    }

    .navr-totop.is-visible {
        transition: opacity .01ms linear, visibility 0s linear 0s;
        transform: none;
    }

    .navr-totop:hover,
    .navr-totop:active { transform: none; }
}

/* ── HEADER EXTRAS (HeaderExtrasDto) — the bars themselves are SharedUI components with their own scoped styles
   (NavrHeaderUtilityBar / NavrHeaderMobileBar); only what reaches OUTSIDE them lives here. ─────────────────────────────────────── */

/* The utility bar and the header stick together: the bar at the very top, the header right below it (wide screens only — the bar is
   hidden on a phone). Only a STICKY header is moved; a normal or overlaying header keeps its own placement. */
@media (min-width: 768px) {
    body:has(.pubhdr-util) .nh.nh--pos-sticky:not(.nh--static) { top: 44px !important; }
}

.nh__cta--second { margin-inline-start: .5rem; }

/* A website with the phone bottom bar and a "drawer-left" header reads like the brochure reference: the burger first, then the brand, and
   the action buttons at the far end (without this the actions group, burger included, comes before the brand). */
body:has(.pubhdr-mbar) .nh.nh--mob-drawer-left.nh--mobile .nh__actions { display: contents; }
body:has(.pubhdr-mbar) .nh.nh--mob-drawer-left.nh--mobile .nh__burger { order: -2; }
body:has(.pubhdr-mbar) .nh.nh--mob-drawer-left.nh--mobile .nh__ctagroup { margin-inline-start: auto; }

/* Room for the phone's bottom action bar, and the back-to-top control above it. */
@media (max-width: 767.98px) {
    body:has(.pubhdr-mbar) { padding-bottom: calc(4.5rem + env(safe-area-inset-bottom, 0px)); }
    body:has(.pubhdr-mbar) .navr-totop { bottom: calc(5.25rem + env(safe-area-inset-bottom, 0px)); }
}
