/* ========================================
   GLOBAL / BASE - VistaFly
   Reset, typography primitives, surfaces,
   layout primitives, shared buttons.

   Consumes tokens.css. Load tokens.css first.
   ======================================== */

/* ============================================================
   RESET
   ============================================================ */
*,
*::before,
*::after {
    box-sizing: border-box;
}

* {
    margin: 0;
    padding: 0;
}

html {
    scroll-behavior: smooth;
    -webkit-text-size-adjust: 100%;

    /* The only horizontal-overflow guard on the page, and a safety
       net rather than a substitute for fixing overflow at its source.

       This used to sit on body as `hidden` too. That was removed:
       `hidden` makes an element a scroll container, and once body
       became one, every section belonged to body's scroller rather
       than the viewport's.

       That matters for the pinned CTA in components.css. A sticky
       element resolves against its nearest scrolling ancestor, so
       with body scrolling it would have pinned to body instead of
       the viewport and never held position on screen.

       `clip` is the right tool precisely because it cannot create a
       scroll container. Do not put `hidden` or `auto` back on body
       or on any wrapper around the sections; the pin depends on this
       staying the only overflow declaration in the chain. */
    overflow-x: clip;
}

body {
    /* --scrim is tuned per page; heroes darken, dense pages darken more */
    --scrim: 0.42;

    background-color: var(--ground-000);
    color: var(--text-hi);
    font-family: var(--font-body);
    font-size: var(--fs-body);
    font-weight: var(--weight-normal);
    line-height: var(--leading-body);
    letter-spacing: var(--track-normal);

    /* No overflow-x here. It used to carry `hidden`, which is now
       both redundant and actively harmful.

       Redundant because html already carries `clip`, and the note in
       that rule explains why it is the stronger guard.

       Harmful because of the propagation rule: the root's overflow
       goes to the viewport only while the root's own overflow is
       `visible`. html is `clip`, so that no longer happens — and
       body's `hidden` made body a scroll container in its own right.
       Every section then belonged to body's scroller rather than the
       viewport's, which silently broke scroll-snap: the
       scroll-snap-type declared on html could never reach a snap
       area sitting inside body. The CTA simply did not snap, with
       nothing in devtools to show why.

       `clip` on html cannot create a scroll container. That is the
       whole reason it is the right tool, and it is why removing this
       line loses no horizontal-overflow protection. */

    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}

img,
svg,
video,
canvas {
    display: block;
    max-width: 100%;
}

img,
video {
    height: auto;
}

button,
input,
select,
textarea {
    font: inherit;
    color: inherit;
}

button {
    background: none;
    border: none;
    cursor: pointer;
}

ul,
ol {
    list-style: none;
}

a {
    color: inherit;
    text-decoration: none;
}

/* Anchor offset for the fixed nav */
[id] {
    scroll-margin-top: calc(var(--nav-height) + var(--space-5));
}

::selection {
    background: var(--edge-bone-hot);
    color: var(--text-hi);
}

/* Visible focus for keyboard users only */
:focus-visible {
    outline: 2px solid var(--bone-300);
    outline-offset: 3px;
    border-radius: var(--radius-xs);
}

:focus:not(:focus-visible) {
    outline: none;
}


/* ============================================================
   BACKGROUND VIDEO GROUND
   One fixed video sits behind every page. Sections choose how
   much of it shows through via the surface classes below.
   ============================================================ */
.local-video-bg,
.bg-video {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: var(--z-video);
    pointer-events: none;
    background-color: var(--ground-000);

    /* Held at 0 until bg-video.js has restored the saved
       timestamp — prevents a visible jump-cut on navigation. */
    opacity: 0;
    transition: opacity var(--dur-slow) var(--ease-out);
}

.local-video-bg.is-ready,
.bg-video.is-ready {
    opacity: 1;
}

/* Poster holds the first paint while the video seeks. */
.bg-poster {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: var(--z-video);
    pointer-events: none;
    background-color: var(--ground-000);
}

/* Scrim — readability floor over the video */
body::after {
    content: '';
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, var(--scrim));
    z-index: var(--z-scrim);
    pointer-events: none;
}


/* ============================================================
   LAYOUT PRIMITIVES
   ============================================================ */
section,
footer {
    position: relative;
    z-index: var(--z-base);
}

.section {
    position: relative;
    z-index: var(--z-base);
    padding-block: var(--section-py);
    padding-inline: var(--gutter);
}

.section--lg {
    padding-block: var(--section-py-lg);
}

.section--flush-top {
    padding-block-start: 0;
}

.section--flush-bottom {
    padding-block-end: 0;
}

.shell {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
}

.shell--wide {
    max-width: var(--container-wide);
}

.shell--narrow {
    max-width: var(--container-narrow);
}


/* ============================================================
   SURFACES
   The fix for "every section looks like the same grey card".

   void   — transparent. Video reads at full strength.
   veil   — glass. Video diffused behind blur.
   solid  — opaque. Video fully hidden; content takes over.
   ============================================================ */
.surface-void {
    background: transparent;
}

.surface-veil {
    background: rgba(11, 11, 11, 0.72);
    backdrop-filter: var(--blur-veil);
    -webkit-backdrop-filter: var(--blur-veil);
}

.surface-solid {
    background: var(--ground-100);
}

.surface-solid-alt {
    background: var(--ground-200);
}

/* ---- Seal the seam between sections ----

   Section heights are fractional: --section-py is clamp(4rem, 7vw,
   7rem), so on most viewport widths a section's box ends on a
   fraction of a pixel. Two adjacent boxes rounding opposite ways
   leave a hairline between them, and behind that hairline is the
   fixed video at --z-video with nothing in between — so the gap does
   not show the page ground, it shows the footage at full strength.
   Bright, moving, and against both sections at once, which is why a
   sub-pixel gap reads as an obvious strip.

   Each surface extends its own background one pixel DOWNWARD to
   cover it. box-shadow, because it paints outside the border box
   without touching layout: no negative margins, nothing shifts, and
   the fix cannot compound down the page.

   Downward only, deliberately. Ringing all four sides — or adding an
   upward shadow to the section below — would have both sections
   painting into the same gap, and two translucent veils stacked in
   one pixel is a dark line where there used to be a bright one. One
   side fills it exactly once.

   Nothing is needed above the first section either: what sits above
   it is the hero, which shows the same video, so a gap there is
   invisible by definition.

   The 1px is hidden whenever the next section actually meets this
   one — every .section is its own stacking context at --z-base, so
   the later sibling paints over it. It is only ever seen in a gap
   that should not exist. */
.surface-veil {
    box-shadow: 0 1px 0 0 rgba(11, 11, 11, 0.72);
}

.surface-solid {
    box-shadow: 0 1px 0 0 var(--ground-100);
}

.surface-solid-alt {
    box-shadow: 0 1px 0 0 var(--ground-200);
}

/* A hairline between two same-treatment sections keeps the seam
   deliberate instead of accidental. Without this, adjacent veils
   blur into one undifferentiated translucent band, and adjacent
   solids into one slab. */
.surface-solid + .surface-solid,
.surface-solid + .surface-solid-alt,
.surface-solid-alt + .surface-solid,
.surface-veil + .surface-veil {
    border-top: 1px solid var(--edge-faint);
}

/* Gradient fade from solid into the live video — used where a
   section should dissolve rather than cut. */
.surface-fade-down {
    background: linear-gradient(to bottom, var(--ground-100) 0%, rgba(11, 11, 11, 0) 100%);
}

.surface-fade-up {
    background: linear-gradient(to top, var(--ground-100) 0%, rgba(11, 11, 11, 0) 100%);
}


/* ============================================================
   TYPOGRAPHY PRIMITIVES
   ============================================================ */
.display,
h1,
h2,
h3 {
    font-family: var(--font-display);
    font-variation-settings: var(--display-axes);
    font-weight: var(--weight-semi);
    line-height: var(--leading-tight);
    letter-spacing: var(--track-display);
    color: var(--text-hi);
    text-wrap: balance;
}

h4,
h5,
h6 {
    font-family: var(--font-body);
    font-weight: var(--weight-semi);
    line-height: var(--leading-snug);
    letter-spacing: var(--track-tight);
    color: var(--text-hi);
}

h1 { font-size: var(--fs-5xl); }
h2 { font-size: var(--fs-3xl); }
h3 { font-size: var(--fs-2xl); letter-spacing: var(--track-tighter); }
h4 { font-size: var(--fs-lg); }
h5 { font-size: var(--fs-base); }
h6 { font-size: var(--fs-sm); }

/* Eyebrow — the wide-tracked micro label. Used above section
   titles, on badges, and as stat captions. */
.eyebrow {
    display: inline-block;
    font-family: var(--font-body);
    font-size: var(--fs-micro);
    font-weight: var(--weight-semi);
    letter-spacing: var(--track-micro);
    text-transform: uppercase;
    color: var(--bone-400);
    line-height: var(--leading-flat);
}

.eyebrow--muted {
    color: var(--text-lo);
}

/* Section heading block */
.section-head {
    max-width: var(--container-narrow);
    margin-inline: auto;
    margin-block-end: var(--space-9);
    text-align: center;
}

.section-head--start {
    text-align: left;
    margin-inline: 0;
}

.section-head .eyebrow {
    margin-block-end: var(--space-4);
}

.section-title {
    font-family: var(--font-display);
    font-variation-settings: var(--display-axes);
    font-size: var(--fs-3xl);
    font-weight: var(--weight-semi);
    line-height: var(--leading-tight);
    letter-spacing: var(--track-display);
    color: var(--text-hi);
    text-wrap: balance;
    margin: 0;
}

/* The old markup put .section-title directly in a section with
   .section-subtitle after it. Both still work. */
.section-title + .section-subtitle {
    margin-block-start: var(--space-4);
}

.section-subtitle,
.section-lede {
    font-family: var(--font-body);
    font-size: var(--fs-lg);
    font-weight: var(--weight-normal);
    line-height: var(--leading-body);
    letter-spacing: var(--track-normal);
    color: var(--text-mid);
    max-width: var(--measure);
    margin-inline: auto;
    text-wrap: pretty;
}

.section-head--start .section-subtitle,
.section-head--start .section-lede {
    margin-inline: 0;
}

p {
    text-wrap: pretty;
}

.lede {
    font-size: var(--fs-lg);
    color: var(--text-mid);
    line-height: var(--leading-body);
    max-width: var(--measure);
}

/* Numerals in stats — tabular so columns align */
.numeral {
    font-family: var(--font-display);
    font-variation-settings: var(--display-axes);
    font-variant-numeric: tabular-nums;
    letter-spacing: var(--track-tighter);
    line-height: var(--leading-flat);
}


/* ============================================================
   BUTTONS
   ============================================================ */
.btn-primary,
.btn-secondary,
.btn-ghost {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: 0.95rem 2rem;
    border-radius: var(--radius-pill);
    font-family: var(--font-body);
    font-size: var(--fs-base);
    font-weight: var(--weight-semi);
    letter-spacing: var(--track-wide);
    line-height: var(--leading-flat);
    text-align: center;
    cursor: pointer;
    border: 1px solid transparent;
    transition:
        transform var(--dur-mid) var(--ease-out),
        box-shadow var(--dur-mid) var(--ease-out),
        background-color var(--dur-mid) var(--ease-out),
        border-color var(--dur-mid) var(--ease-out),
        color var(--dur-mid) var(--ease-out);
    will-change: transform;
}

.btn-primary {
    background: var(--bone-300);
    color: var(--text-on-bone);
    box-shadow: var(--shadow-md);
}

.btn-primary:hover {
    background: var(--bone-200);
    transform: translateY(-2px);
    box-shadow: var(--shadow-lg), var(--shadow-bone);
}

.btn-primary:active {
    transform: translateY(0);
}

.btn-secondary {
    background: transparent;
    color: var(--text-hi);
    border-color: var(--edge-firm);
}

.btn-secondary:hover {
    border-color: var(--edge-bone-hot);
    color: var(--bone-300);
    background: var(--fill-bone);
    transform: translateY(-2px);
}

.btn-secondary:active {
    transform: translateY(0);
}

.btn-ghost {
    background: transparent;
    color: var(--text-mid);
    padding-inline: var(--space-4);
}

.btn-ghost:hover {
    color: var(--bone-300);
}

.btn-sm {
    padding: 0.7rem 1.4rem;
    font-size: var(--fs-sm);
}

.btn-block {
    display: flex;
    width: 100%;
}

/* Arrow link — the inline "Explore →" pattern */
.link-arrow {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--fs-base);
    font-weight: var(--weight-semi);
    letter-spacing: var(--track-wide);
    color: var(--bone-300);
    transition: gap var(--dur-mid) var(--ease-out), color var(--dur-mid) var(--ease-out);
}

.link-arrow:hover {
    gap: var(--space-4);
    color: var(--bone-200);
}

/* ============================================================
   HOVER LIFT — hit-area stabiliser

   Every component on this site that lifts on hover had the same
   fault, and it is the one people actually notice: put the cursor on
   the bottom edge of the card and it buzzes.

   ---- Why ----

   The element carrying :hover is also the element the rule moves.
   Cursor sits at the bottom edge; :hover matches; the element
   translates up four pixels; the cursor is now below it; :hover stops
   matching; the element drops back under the cursor; :hover matches
   again. That is a complete state change per frame, for as long as
   the cursor stays there, and every transition on the element
   restarts each time.

   It is not a timing problem and no easing, delay or debounce fixes
   it — a return delay just slows the oscillation down into a visible
   bob. The hit area is the problem, so the hit area is what changes.

   ---- The fix ----

   A hover-only ::after strip, sitting directly below the element and
   exactly as deep as the lift. While the element is up, that strip
   covers the ground it vacated, and hovering a pseudo-element keeps
   :hover on the element that owns it.

   The guarantee is a set relation, not a tuned value: the hovered
   hit region becomes [top - lift, bottom], which strictly contains
   the resting region [top, bottom]. A point that turns the state on
   therefore cannot be a point that turns it off, at any lift, on any
   element. The oscillation is not damped — it is impossible.

   The strip is placed BELOW the box rather than over it
   (inset-block-start: 100%) and is pointer-transparent until hovered,
   so it never sits between the cursor and a link, a button, or
   anything else inside the card.

   ---- What is deliberately not here ----

   Scale-UP hovers (.modal-close, .close-modal) already satisfy the
   containment rule on their own: a bigger box cannot expose a point
   the smaller one covered. Same for .link-arrow, which grows its gap.
   They need nothing and get nothing.

   .video-container is handled differently — see the note in
   portfolio.css. It has a static wrapper of exactly its own size, so
   its hover moved there instead, which is strictly better where a
   wrapper exists. This block is for the components that have none.

   ---- Two components are NOT in this list, and cannot be ----

   .tile and .cta-button both carry overflow: hidden, and it is load
   bearing in each: the tile clips a poster that zooms to 105% on
   hover, the button clips a 200%-wide rotated sheen that would
   otherwise sweep across the nav bar. A strip placed outside the box
   is clipped away by exactly that rule, so it never gets the chance
   to hold the hover.

   overflow: clip with an overflow-clip-margin the depth of the lift
   would let the strip through, but it lets the poster and the sheen
   through with it — a few pixels of un-rounded image at the tile's
   corners, a pale fringe around the pill. That is a visible change to
   how they look, to fix something that only shows as a wobble on one
   edge, so it is not made here. Both need a static wrapper element
   the way .video-container has one; that is a markup change and it
   belongs in its own pass.
   ============================================================ */
/* The strip is absolutely positioned, so its host has to be a
   containing block. .card, .price-card and .callout-content already
   are in their own stylesheets and are deliberately NOT relisted
   here — .callout-content is position: absolute, and repeating it in
   this group would depend on components.css loading after global.css
   to keep working. It does today. That is not a thing to rely on. */
.btn-primary,
.btn-secondary,
.bundle,
.mobile-book-now,
.error-close-btn,
.footer-social a,
.vf-social a,
.contact-detail {
    position: relative;
}

.btn-primary::after,
.btn-secondary::after,
.card::after,
.price-card::after,
.bundle::after,
.callout-content::after,
.mobile-book-now::after,
.error-close-btn::after,
.footer-social a::after,
.vf-social a::after,
.contact-detail::after {
    content: '';
    position: absolute;
    inset-inline: 0;
    inset-block: 0;
    pointer-events: none;
}

.btn-primary:hover::after,
.btn-secondary:hover::after,
.card:hover::after,
.price-card:hover::after,
.bundle:hover::after,
.callout-content:hover::after,
.mobile-book-now:hover::after,
.error-close-btn:hover::after,
.footer-social a:hover::after,
.vf-social a:hover::after {
    inset-block-start: 100%;
    inset-block-end: calc(-1 * var(--hover-lift, 0px));
    pointer-events: auto;
}

/* .contact-detail is the one that moves sideways rather than up: it
   translates 3px toward the inline end, so the ground it vacates is
   at its inline START, and that is where its strip goes. */
.contact-detail:hover::after {
    inset-inline-end: 100%;
    inset-inline-start: calc(-1 * var(--hover-lift, 0px));
    pointer-events: auto;
}

/* The depth each one vacates. Kept beside the selectors rather than
   scattered across six stylesheets: the value has to match the
   translate in the component's own :hover rule, and two numbers that
   must agree are far easier to keep in agreement when they are
   readable together. Changing a lift means changing both. */
.btn-primary,
.btn-secondary,
.mobile-book-now,
.error-close-btn  { --hover-lift: 2px; }
.callout-content  { --hover-lift: 2px; }
.bundle,
.footer-social a,
.vf-social a      { --hover-lift: 3px; }
.contact-detail   { --hover-lift: 3px; }
.card,
.price-card       { --hover-lift: 4px; }

/* ---- The strip stays on under reduced motion, deliberately ----

   The reduced-motion block at the foot of this file cuts transition
   DURATIONS to 0.01ms. It does not remove the transforms, so a
   reduced-motion visitor still gets the lift — they get it instantly
   rather than eased. The hit area moves exactly as far, so the edge
   oscillation is exactly as possible, and without the easing it
   snaps rather than glides. If anything it is more visible there,
   not less. So there is no reduced-motion carve-out here. */


/* Button rows */
.btn-row {
    display: flex;
    gap: var(--space-4);
    flex-wrap: wrap;
}

.btn-row--center {
    justify-content: center;
}


/* ============================================================
   COMPONENT PLACEHOLDERS
   Reserve height so injected nav/footer don't shift layout.
   ============================================================ */
#nav-placeholder {
    min-height: var(--nav-height);
}

#footer-placeholder {
    min-height: 320px;
}


/* ============================================================
   UTILITIES
   ============================================================ */
.text-accent  { color: var(--bone-300); }
.text-muted   { color: var(--text-lo); }
.text-mid     { color: var(--text-mid); }
.text-center  { text-align: center; }
.text-start   { text-align: left; }

.stack-2 > * + * { margin-block-start: var(--space-2); }
.stack-3 > * + * { margin-block-start: var(--space-3); }
.stack-4 > * + * { margin-block-start: var(--space-4); }
.stack-5 > * + * { margin-block-start: var(--space-5); }
.stack-6 > * + * { margin-block-start: var(--space-6); }

.divider {
    height: 1px;
    background: var(--edge-faint);
    border: 0;
}

.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border-width: 0;
}

/* Drag affordance for the 3D viewers — one definition, was
   previously duplicated in indoor.css and outdoor.css. */
.drag-hint {
    position: absolute;
    bottom: var(--space-4);
    left: 50%;
    transform: translateX(-50%);
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--fs-micro);
    font-weight: var(--weight-semi);
    letter-spacing: var(--track-label);
    text-transform: uppercase;
    color: var(--text-lo);
    pointer-events: none;
    white-space: nowrap;
    transition: opacity var(--dur-mid) var(--ease-out);
}

.drag-hint svg {
    width: 14px;
    height: 14px;
    animation: drag-nudge 2.4s var(--ease-in-out) infinite;
}

@keyframes drag-nudge {
    0%, 100% { transform: rotate(-90deg) translateY(0); }
    50%      { transform: rotate(-90deg) translateY(4px); }
}

.is-interacting .drag-hint {
    opacity: 0;
}


/* ============================================================
   MOTION PREFERENCES
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }

    html {
        scroll-behavior: auto;
    }

    /* The background video is decorative — stop it entirely and
       let the poster stand in. */
    .local-video-bg,
    .bg-video {
        display: none;
    }
}


/* ============================================================
   PRINT
   ============================================================ */
@media print {
    .local-video-bg,
    .bg-video,
    .bg-poster,
    #navbar,
    .site-footer,
    body::after {
        display: none !important;
    }

    body {
        background: #fff;
        color: #000;
    }
}
