/* ==========================================================================
   motion.css — animation layer. Loaded after style.css on every page.
   --------------------------------------------------------------------------
   Purely additive: remove this file and the site is fully functional, just
   static. Three gates decide what a device receives:

     html.js-motion    JavaScript is running. Pre-animation hidden states live
                       behind this class, so without JS everything is visible.
     html.motion-rich  Set by the head loader only for fine-pointer devices
                       with no reduced-motion or Save-Data preference. Gates
                       the opening curtain, hero entrance, aurora, grain and
                       any blur/filter work. Touch-only phones never pay for
                       these; they get the lightweight IntersectionObserver
                       reveals below, which are opacity/transform only.
     html.gsap-ready   Added by motion-desktop.js once GSAP has loaded. Gates
                       the GSAP-owned pre-animation states.

   Easing/duration tokens (--ease-out, --ease-soft, --dur-*) are defined with
   the other design tokens in style.css.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Opening curtain — rich-motion devices, first visit per session only.
   -------------------------------------------------------------------------- */
#intro-curtain { display: none; }

html.motion-rich #intro-curtain {
    position: fixed;
    inset: 0;
    z-index: 9999;
    background: var(--ink, #0a0a0a);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: opacity 0.7s var(--ease-soft), visibility 0.7s;
}
#intro-curtain .curtain-mark {
    font-family: 'Marcellus', 'Marcellus Fallback', serif;
    font-size: clamp(18px, 3.4vw, 30px);
    letter-spacing: 0.42em;
    text-indent: 0.42em;
    color: #f2f2f2;
    opacity: 0;
    animation: markIn 1.15s var(--ease-out) 0.15s forwards;
}
#intro-curtain .curtain-rule {
    position: absolute;
    top: calc(50% + 34px);
    left: 50%;
    width: 0;
    height: 1px;
    background: var(--gold, #c9a962);
    transform: translateX(-50%);
    animation: ruleOut 0.9s var(--ease-out) 0.5s forwards;
}
@keyframes markIn {
    from { opacity: 0; transform: translateY(14px); filter: blur(6px); }
    to   { opacity: 1; transform: translateY(0);    filter: blur(0); }
}
@keyframes ruleOut { to { width: 132px; } }

html.motion-rich body.curtain-up #intro-curtain { opacity: 0; visibility: hidden; }
html.motion-rich body.curtain-skip #intro-curtain { display: none; }

/* --------------------------------------------------------------------------
   2. Hero entrance — rich-motion devices only. Phones paint the hero at once.
   -------------------------------------------------------------------------- */
html.motion-rich.js-motion .intro > *,
html.motion-rich.js-motion .right-section .portrait:not(.portrait-cutout) img {
    opacity: 0;
    transform: translateY(26px);
}
html.motion-rich body.hero-in .intro > * {
    animation: riseIn 0.85s var(--ease-out) forwards;
}
html.motion-rich body.hero-in .intro > *:nth-child(1) { animation-delay: 0.05s; }
html.motion-rich body.hero-in .intro > *:nth-child(2) { animation-delay: 0.15s; }
html.motion-rich body.hero-in .intro > *:nth-child(3) { animation-delay: 0.25s; }
html.motion-rich body.hero-in .intro > *:nth-child(4) { animation-delay: 0.35s; }
@keyframes riseIn { to { opacity: 1; transform: translateY(0); } }

/* Kinetic mask-reveal on the hero headline. */
html.motion-rich.js-motion .intro .intro-heading {
    overflow: hidden;
    opacity: 1;
    transform: none;
    animation: none;
}
html.motion-rich.js-motion .intro .intro-heading .hero-line {
    display: block;
    transform: translateY(115%);
}
html.motion-rich body.hero-in .intro .intro-heading .hero-line {
    animation: heroLineRise 0.9s var(--ease-out) 0.15s forwards;
}
@keyframes heroLineRise {
    from { transform: translateY(115%); }
    to   { transform: translateY(0); }
}

/* Cutout portrait: rises in, halo fades up behind it. */
html.motion-rich .right-section .portrait-cutout::before {
    filter: blur(18px);
    opacity: 0;
}
html.motion-rich body.hero-in .right-section .portrait-cutout::before {
    animation: haloIn 1.6s var(--ease-out) 0.5s forwards;
}
@keyframes haloIn { to { opacity: 1; } }

html.motion-rich.js-motion .right-section .portrait-cutout img {
    opacity: 0;
    transform: translateY(26px) scale(1.01);
}
html.motion-rich body.hero-in .right-section .portrait-cutout img {
    animation: cutoutIn 1.35s var(--ease-out) 0.35s forwards;
}
@keyframes cutoutIn { to { opacity: 1; transform: translateY(0) scale(1); } }

/* Plain portrait (about.html): left-to-right wipe with a gold hairline. */
html.motion-rich.js-motion .right-section .portrait:not(.portrait-cutout) img {
    transform: none;
    opacity: 0;
    clip-path: inset(0 100% 0 0);
}
html.motion-rich body.hero-in .right-section .portrait:not(.portrait-cutout) img {
    animation: portraitWipe 1.15s var(--ease-out) 0.4s forwards;
}
@keyframes portraitWipe {
    0%   { opacity: 0; clip-path: inset(0 100% 0 0); }
    12%  { opacity: 1; }
    100% { opacity: 1; clip-path: inset(0 0 0 0); }
}
html.motion-rich .right-section .portrait:not(.portrait-cutout)::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    width: 2px;
    background: linear-gradient(180deg, transparent, var(--gold, #c9a962) 18%, var(--gold, #c9a962) 82%, transparent);
    opacity: 0;
    pointer-events: none;
}
html.motion-rich body.hero-in .right-section .portrait:not(.portrait-cutout)::after {
    animation: portraitSweep 1.15s var(--ease-out) 0.4s forwards;
}
@keyframes portraitSweep {
    0%   { opacity: 0; left: 0; }
    12%  { opacity: 1; }
    88%  { opacity: 1; left: calc(100% - 2px); }
    100% { opacity: 0; left: calc(100% - 2px); }
}

/* --------------------------------------------------------------------------
   3. Scroll reveals — IntersectionObserver adds .is-visible / .is-in.
   Opacity and transform only: cheap to composite on any phone.
   -------------------------------------------------------------------------- */
html.js-motion .reveal {
    opacity: 0;
    transform: translateY(24px);
    transition: opacity var(--dur-mid) var(--ease-out), transform var(--dur-mid) var(--ease-out);
}
html.js-motion .reveal.is-visible { opacity: 1; transform: translateY(0); }

/* The gold rule under the contact heading draws in as its section reveals. */
html.js-motion .reveal .contact-closer::after {
    width: 0;
    transition: width 0.8s var(--ease-out) 0.2s;
}
html.js-motion .reveal.is-visible .contact-closer::after { width: 46px; }

html.js-motion .reveal-up {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity var(--dur-mid) var(--ease-out), transform var(--dur-mid) var(--ease-out);
}
html.js-motion .reveal-up.is-in { opacity: 1; transform: translateY(0); }

/* Project case studies */
html.js-motion .case__index {
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out);
}
html.js-motion .case.is-in .case__index { opacity: 1; transform: translateY(0); }
html.js-motion .case__media {
    clip-path: inset(0 0 100% 0);
    transition: clip-path 0.9s var(--ease-out);
}
html.js-motion .case.is-in .case__media { clip-path: inset(0 0 0 0); }
html.js-motion .case__media img {
    transform: scale(1.08);
    transition: transform 1.2s var(--ease-out);
}
html.js-motion .case.is-in .case__media img { transform: scale(1); }
html.js-motion .case__body > *:not(.case__index) {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out);
}
html.js-motion .case.is-in .case__body > *:not(.case__index) { opacity: 1; transform: translateY(0); }
html.js-motion .case.is-in .case__body > *:nth-child(2) { transition-delay: 0.06s; }
html.js-motion .case.is-in .case__body > *:nth-child(3) { transition-delay: 0.12s; }
html.js-motion .case.is-in .case__body > *:nth-child(4) { transition-delay: 0.18s; }
html.js-motion .case.is-in .case__body > *:nth-child(5) { transition-delay: 0.24s; }
html.js-motion .case .case__stack li {
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 0.4s var(--ease-out), transform 0.4s var(--ease-out);
}
html.js-motion .case.is-in .case__stack li { opacity: 1; transform: translateY(0); }
html.js-motion .case.is-in .case__stack li:nth-child(1) { transition-delay: 0.20s; }
html.js-motion .case.is-in .case__stack li:nth-child(2) { transition-delay: 0.26s; }
html.js-motion .case.is-in .case__stack li:nth-child(3) { transition-delay: 0.32s; }
html.js-motion .case.is-in .case__stack li:nth-child(4) { transition-delay: 0.38s; }
html.js-motion .case.is-in .case__stack li:nth-child(5) { transition-delay: 0.44s; }
html.js-motion .case.is-in .case__stack li:nth-child(6) { transition-delay: 0.50s; }

/* --------------------------------------------------------------------------
   3b. PHOTOGRAPHY PAGE — entrance choreography.
   --------------------------------------------------------------------------
   This is the baseline every device gets: the frame's mask wipes upward while
   the image inside settles from an overscale, and the caption follows a beat
   later. Two layers moving at different rates is what reads as depth rather
   than as a card fading in.

   On rich-motion desktops motion-desktop.js takes the same elements further
   with scrubbed parallax and split-line headings; these states still apply as
   the pre-animation resting position, so a mid-load GSAP failure leaves
   nothing hidden. All of it is opacity/transform/clip-path only.
   -------------------------------------------------------------------------- */

/* Hero title lines, revealed as soon as the page paints (no scroll needed).
   Scoped to :not(.gsap-ready): when the GSAP layer is active it drives these
   same elements with inline transforms, and a stylesheet rule on the same
   property would fight the tween (an inline style wins, but GSAP's own
   from-state is written inline too, so the two would compose unpredictably).
   One owner per property, decided by which layer is live. */
html.js-motion:not(.gsap-ready) .ph-hero__title .ph-line > span,
html.js-motion:not(.gsap-ready) .ph-hero__eyebrow,
html.js-motion:not(.gsap-ready) .ph-hero__lede,
html.js-motion:not(.gsap-ready) .ph-hero__foot {
    opacity: 0;
    transform: translateY(var(--ph-rise, 108%));
    transition: opacity 0.9s var(--ease-out), transform 1.1s var(--ease-out);
}
html.js-motion .ph-hero__eyebrow,
html.js-motion .ph-hero__lede,
html.js-motion .ph-hero__foot { --ph-rise: 18px; }
html.js-motion:not(.gsap-ready) .ph-hero.is-in .ph-hero__title .ph-line > span,
html.js-motion:not(.gsap-ready) .ph-hero.is-in .ph-hero__eyebrow,
html.js-motion:not(.gsap-ready) .ph-hero.is-in .ph-hero__lede,
html.js-motion:not(.gsap-ready) .ph-hero.is-in .ph-hero__foot {
    opacity: 1;
    transform: translateY(0);
}
/* Sequencing: eyebrow, then each title line, then lede and footer. */
html.js-motion:not(.gsap-ready) .ph-hero.is-in .ph-hero__eyebrow { transition-delay: 0.05s; }
html.js-motion:not(.gsap-ready) .ph-hero.is-in .ph-line:nth-child(1) > span { transition-delay: 0.14s; }
html.js-motion:not(.gsap-ready) .ph-hero.is-in .ph-line:nth-child(2) > span { transition-delay: 0.24s; }
html.js-motion:not(.gsap-ready) .ph-hero.is-in .ph-hero__lede { transition-delay: 0.42s; }
html.js-motion:not(.gsap-ready) .ph-hero.is-in .ph-hero__foot { transition-delay: 0.52s; }

/* The hero photograph settles from a slow push-in. */
html.motion-rich.js-motion:not(.gsap-ready) .ph-hero__media img {
    transform: scale(1.08);
    transition: transform 2.4s var(--ease-out);
}
html.motion-rich.js-motion:not(.gsap-ready) .ph-hero.is-in .ph-hero__media img { transform: scale(1); }

/* Figures: mask wipe + overscale settle. The frame clips, the image moves. */
html.js-motion .ph-figure__frame,
html.js-motion .ph-bleed__frame {
    clip-path: inset(0 0 100% 0);
    transition: clip-path 1.1s var(--ease-out);
}
html.js-motion .ph-figure.is-in .ph-figure__frame,
html.js-motion .ph-bleed__fig.is-in .ph-bleed__frame { clip-path: inset(0 0 0 0); }

html.js-motion .ph-figure__frame img,
html.js-motion .ph-bleed__frame img {
    transform: scale(1.14);
    transition: transform 1.4s var(--ease-out);
}
html.js-motion .ph-figure.is-in .ph-figure__frame img,
html.js-motion .ph-bleed__fig.is-in .ph-bleed__frame img { transform: scale(1); }

/* Captions arrive just behind their image, staggered by line. */
html.js-motion .ph-cap > *,
html.js-motion .ph-figure__link,
html.js-motion .ph-bleed__cap > * {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity 0.7s var(--ease-out), transform 0.7s var(--ease-out);
}
html.js-motion .ph-figure.is-in ~ .ph-figure__link,
html.js-motion .ph-figure.is-in .ph-cap > *,
html.js-motion .ph-bleed__fig.is-in .ph-bleed__cap > * {
    opacity: 1;
    transform: translateY(0);
}
html.js-motion .ph-figure.is-in .ph-cap > *:nth-child(1),
html.js-motion .ph-bleed__fig.is-in .ph-bleed__cap > *:nth-child(1) { transition-delay: 0.30s; }
html.js-motion .ph-figure.is-in .ph-cap > *:nth-child(2),
html.js-motion .ph-bleed__fig.is-in .ph-bleed__cap > *:nth-child(2) { transition-delay: 0.36s; }
html.js-motion .ph-figure.is-in .ph-cap > *:nth-child(3),
html.js-motion .ph-bleed__fig.is-in .ph-bleed__cap > *:nth-child(3) { transition-delay: 0.42s; }
html.js-motion .ph-bleed__fig.is-in .ph-bleed__cap > *:nth-child(4) { transition-delay: 0.48s; }
html.js-motion .ph-figure.is-in ~ .ph-figure__link { transition-delay: 0.46s; }

/* The display bleed's masked title lines ride its own reveal, exactly like the
   hero. Scoped to :not(.gsap-ready) so only one layer owns the transform. */
html.js-motion:not(.gsap-ready) .ph-bleed__title--display .ph-line > span {
    transform: translateY(108%);
    transition: transform 1.1s var(--ease-out);
}
html.js-motion:not(.gsap-ready) .ph-bleed__fig.is-in .ph-bleed__title--display .ph-line > span {
    transform: translateY(0);
}
html.js-motion:not(.gsap-ready) .ph-bleed__fig.is-in .ph-bleed__title--display .ph-line:nth-child(2) > span { transition-delay: 0.1s; }

/* The title's own lines are masked, so the <h2> block itself must not also be
   translated by the generic caption rule — that offset would park the second
   line below its mask. */
html.js-motion .ph-bleed__cap > .ph-bleed__title--display { transform: none; }

/* Statement and archive rows. */
html.js-motion .ph-statement__inner > *,
html.js-motion .ph-upcoming li {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 0.8s var(--ease-out), transform 0.8s var(--ease-out);
}
html.js-motion .ph-statement.is-in .ph-statement__inner > *,
html.js-motion .ph-upcoming.is-in li { opacity: 1; transform: translateY(0); }
html.js-motion .ph-statement.is-in .ph-statement__inner > *:nth-child(2) { transition-delay: 0.10s; }
html.js-motion .ph-statement.is-in .ph-statement__inner > *:nth-child(3) { transition-delay: 0.20s; }
html.js-motion .ph-upcoming.is-in li:nth-child(2) { transition-delay: 0.09s; }

/* Scroll-scrubbed drift: JS writes --drift (-1..1) from the element's position
   in the viewport. Rich-motion devices only — a per-frame custom-property
   write is not work a phone should do for a decorative effect. */
html.motion-rich.js-motion .ph-figure.is-in .ph-figure__frame img,
html.motion-rich.js-motion .ph-bleed__fig.is-in .ph-bleed__frame img {
    transform: scale(1.06) translate3d(0, calc(var(--drift, 0) * 2.2%), 0);
    transition: none;
}

/* Timeline, info cards, mission blocks */
html.js-motion .timeline__item,
html.js-motion .info-card,
html.js-motion .mission-block {
    opacity: 0;
    transform: translateY(14px);
    transition: opacity 0.6s var(--ease-out), transform 0.6s var(--ease-out);
}
html.js-motion .timeline__item.is-in,
html.js-motion .info-card.is-in,
html.js-motion .mission-block.is-in { opacity: 1; transform: translateY(0); }

/* --------------------------------------------------------------------------
   4. Typography and micro-interactions
   -------------------------------------------------------------------------- */
body {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
}

/* Rotating hero line: .swapping lifts the old phrase out; .swap-in parks the
   fresh one just below with no transition so it can rise into place. */
.animated-text {
    transition: opacity 0.34s var(--ease-out), transform 0.34s var(--ease-out);
}
.animated-text.swapping { opacity: 0; transform: translateY(-7px); }
.animated-text.swap-in  { opacity: 0; transform: translateY(7px); transition: none; }

.btn-outline,
.btn-solid {
    transition: background-color var(--dur-fast) var(--ease-soft),
                color var(--dur-fast) var(--ease-soft),
                border-color var(--dur-fast) var(--ease-soft),
                transform var(--dur-fast) var(--ease-soft),
                box-shadow var(--dur-fast) var(--ease-soft);
}
@media (hover: hover) and (pointer: fine) {
    .btn-outline:hover,
    .btn-solid:hover,
    .btn-primary:hover {
        transform: translateY(-2px);
        box-shadow: 0 10px 24px rgba(0, 0, 0, 0.45);
    }
    .btn-outline:active,
    .btn-solid:active,
    .btn-primary:active {
        transform: translateY(0);
        box-shadow: 0 3px 10px rgba(0, 0, 0, 0.35);
    }
}

/* Phone link underline wipes in from the left on hover. */
.contact-info a { transition: color var(--dur-fast) var(--ease-soft); }
.contact-info a::after {
    content: "";
    position: absolute;
    left: 0;
    bottom: -2px;
    width: 100%;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform 0.35s var(--ease-out);
}
.contact-info a:hover::after { transform: scaleX(1); }

/* --------------------------------------------------------------------------
   5. Scroll progress bar and page-exit fade
   -------------------------------------------------------------------------- */
#scroll-progress {
    position: fixed;
    top: 0;
    left: 0;
    height: 2px;
    width: 100%;
    transform: scaleX(0);
    transform-origin: left;
    background: linear-gradient(90deg, rgba(201, 169, 98, 0.35), var(--gold, #c9a962));
    z-index: 201;
    pointer-events: none;
}
body.page-leaving .container {
    opacity: 0;
    transform: translateY(-10px);
    transition: opacity 0.26s var(--ease-soft), transform 0.26s var(--ease-soft);
}

/* --------------------------------------------------------------------------
   6. Ambient background — rich-motion devices only. The aurora is a blurred,
   animated full-viewport layer and the grain a large tiled SVG filter; both
   are too expensive for phones, which keep the static gradients on <body>.
   -------------------------------------------------------------------------- */
#motion-bg {
    position: fixed;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    pointer-events: none;
}
html.motion-rich #motion-bg::before {
    content: "";
    position: absolute;
    inset: -35%;
    background:
        radial-gradient(38% 46% at 22% 30%, rgba(201, 169, 98, 0.10), transparent 70%),
        radial-gradient(34% 42% at 82% 72%, rgba(201, 169, 98, 0.07), transparent 70%),
        radial-gradient(52% 56% at 60% 10%, rgba(120, 90, 40, 0.06), transparent 72%);
    filter: blur(22px);
    animation: auroraDrift 36s var(--ease-soft) infinite alternate;
}
/* The single film-grain treatment for the site (the former .grain-layer
   duplicate has been removed). */
html.motion-rich #motion-bg::after {
    content: "";
    position: absolute;
    inset: 0;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
    background-size: 140px 140px;
    opacity: 0.035;
}
@keyframes auroraDrift {
    0%   { transform: translate3d(-3%, -2%, 0) scale(1.05) rotate(0deg); }
    50%  { transform: translate3d(2%, 1%, 0)  scale(1.12) rotate(1.4deg); }
    100% { transform: translate3d(3%, 3%, 0)  scale(1.06) rotate(-1deg); }
}
@media (prefers-reduced-data: reduce) {
    #motion-bg::before, #motion-bg::after { display: none; }
}

/* --------------------------------------------------------------------------
   7. GSAP layer (motion-desktop.js) — every hidden state requires
   html.gsap-ready, added only after GSAP has loaded on a rich-motion device.
   -------------------------------------------------------------------------- */
html.gsap-ready [data-motion="fade-up"] { opacity: 0; }

/* Masked line reveal: .gsap-line clips, .gsap-line__inner moves. */
.gsap-line {
    display: block;
    overflow: hidden;
    padding-bottom: 0.08em;   /* keeps descenders clear of the mask */
    margin-bottom: -0.08em;
}
.gsap-line__inner { display: block; }
html.gsap-ready .gsap-line__inner { transform: translateY(110%); }

/* Image clip reveal wrapper (display:block, no size of its own). */
.gsap-clip { display: block; overflow: hidden; }
html.gsap-ready .gsap-clip { clip-path: inset(100% 0 0 0); }
html.gsap-ready .gsap-clip > img,
html.gsap-ready .gsap-clip > picture > img { transform: scale(1.2); }

/* Magnetic elements: GSAP drives transform, so no CSS transition on it. */
html.gsap-ready [data-motion-magnetic] {
    transition-property: background-color, color, border-color, box-shadow;
}

/* Gallery tile hover lift, fine pointers only. */
@media (hover: hover) and (pointer: fine) {
    html.gsap-ready [data-motion-hover] { transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1); }
    html.gsap-ready [data-motion-hover]:hover { transform: scale(1.04); }
}

/* Page-transition curtain (separate from the intro curtain). */
.gsap-curtain {
    position: fixed;
    inset: 0;
    z-index: 9998;
    background: var(--ink, #0a0a0a);
    pointer-events: none;
    transform: scaleY(0);
    transform-origin: 0 100%;
}

/* Lenis host styles. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }
.lenis.lenis-smooth iframe { pointer-events: none; }
html.lenis { scroll-behavior: auto; }

/* Technology chips inside the timeline ride along with their item. */
html.gsap-ready .timeline__item .case__stack li { transition: none; }

/* --------------------------------------------------------------------------
   8. Booking flow motion (the component itself is styled in style.css)
   -------------------------------------------------------------------------- */
html.gsap-ready .booking__meta .dot-live { animation: bookPulse 2s ease-in-out infinite; }
@keyframes bookPulse {
    0%, 100% { opacity: 1;    transform: scale(1); }
    50%      { opacity: 0.35; transform: scale(0.72); }
}
html.gsap-ready .badge-spin__ring { animation: badgeSpin 20s linear infinite; }
@keyframes badgeSpin { to { transform: rotate(360deg); } }

/* Success state pre-animation, only when GSAP will draw it. */
html.gsap-ready .check-circle { transform: scale(0); }
html.gsap-ready .booker__success-text,
html.gsap-ready .booker__success-meta { opacity: 0; }

/* --------------------------------------------------------------------------
   9. Reduced motion — everything collapses to its final state.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }

    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }

    #intro-curtain, .gsap-curtain, .ph-cursor { display: none !important; }

    html.js-motion .reveal,
    html.js-motion .reveal-up,
    html.js-motion .case__index,
    html.js-motion .case__media,
    html.js-motion .case__media img,
    html.js-motion .case__body > *,
    html.js-motion .case__stack li,
    html.js-motion .ph-hero__title .ph-line > span,
    html.js-motion .ph-hero__eyebrow,
    html.js-motion .ph-hero__lede,
    html.js-motion .ph-hero__foot,
    html.motion-rich.js-motion .ph-hero__media img,
    html.js-motion .ph-figure__frame,
    html.js-motion .ph-bleed__frame,
    html.js-motion .ph-figure__frame img,
    html.js-motion .ph-bleed__frame img,
    html.motion-rich.js-motion .ph-figure.is-in .ph-figure__frame img,
    html.motion-rich.js-motion .ph-bleed__fig.is-in .ph-bleed__frame img,
    html.js-motion .ph-cap > *,
    html.js-motion .ph-bleed__title--display .ph-line > span,
    html.js-motion .ph-figure__link,
    html.js-motion .ph-bleed__cap > *,
    html.js-motion .ph-statement__inner > *,
    html.js-motion .ph-upcoming li,
    html.js-motion .timeline__item,
    html.js-motion .info-card,
    html.js-motion .mission-block,
    html.js-motion .intro > *,
    html.js-motion .intro .intro-heading .hero-line,
    html.js-motion .right-section img,
    html.gsap-ready [data-motion="fade-up"],
    html.gsap-ready .gsap-line__inner,
    html.gsap-ready .gsap-clip,
    html.gsap-ready .gsap-clip > img,
    html.gsap-ready .gsap-clip > picture > img,
    html.gsap-ready [data-motion-hover]:hover,
    html.gsap-ready .check-circle,
    html.gsap-ready .booker__success-text,
    html.gsap-ready .booker__success-meta,
    body.page-leaving .container {
        opacity: 1 !important;
        transform: none !important;
        clip-path: none !important;
        transition: none !important;
    }
    html.js-motion .reveal .contact-closer::after { width: 46px !important; }

    /* The halo keeps its centring transform; only its fade-in is motion. */
    html.motion-rich .right-section .portrait-cutout::before { opacity: 1 !important; }
    /* The cutout's drop-shadow is styling, not motion — keep it. */
    .right-section .portrait-cutout img {
        filter: drop-shadow(0 26px 45px rgba(0, 0, 0, 0.55)) !important;
    }
    .right-section .portrait:not(.portrait-cutout)::after { display: none !important; }
    #motion-bg::before { animation: none !important; }

    .process .stage { opacity: 1 !important; transform: none !important; }
    .process__line-fill { transform: none !important; transition: none !important; inset: 0 !important; }
    .process__pulse { display: none !important; }
    .stage__num,
    .process.is-drawn .stage.is-on .stage__num { background: var(--gold) !important; color: #101010 !important; }
    .timeline__progress { transition: none !important; }
    .site-nav__links { transition: none !important; }

    .btn-outline:hover, .btn-solid:hover, .btn-primary:hover { transform: none !important; }
    .booking, .booker__dot, .day-pill, .slot, .booker__field-line, .btn-book { transition: none !important; }
    .badge-spin__ring, .booking__meta .dot-live { animation: none !important; }
    .booking.is-focused { background-color: transparent !important; }
}

@media print {
    #motion-bg, #intro-curtain, #scroll-progress, .gsap-curtain { display: none !important; }
}

/* GSAP owns these properties on desktop; CSS transitions must not chase it. */
html.gsap-ready .case__media,
html.gsap-ready .case__body > *,
html.gsap-ready .info-card,
html.gsap-ready .timeline__item,
html.gsap-ready .mission-block { transition: none; }
@media (prefers-reduced-motion: no-preference) {
    html.js-motion:not(.motion-rich) .case__media { clip-path: none; opacity: 0; transform: translateY(22px); transition: opacity .7s var(--ease-out), transform .7s var(--ease-out); }
    html.js-motion:not(.motion-rich) .case.is-in .case__media { opacity: 1; transform: none; }
    .case__media--designed .scan-line { transition: transform 1.2s var(--ease-out); }
    .case__media--designed:hover .scan-line { transform: translateY(35px); }
}
@media (prefers-reduced-motion: reduce) {
    .project-art, .portrait, .case__body > *, .contact-section .col-md-5, .contact-section .col-md-7 { transform: none !important; opacity: 1 !important; }
    .chart-signal, .ride-route { stroke-dashoffset: 0 !important; }
}
