/**
 * AMBIORA HOTELS — motion primitives
 * ---------------------------------------------------------------------
 * One place for everything that moves. The site had a reveal-on-scroll
 * rule living in v2.css and a cinematic drift living beside the hero;
 * the drift is specific to the reel and stays there, but the reveal has
 * moved here, so there is exactly one definition of what a reveal is.
 *
 * There is no animation library in this project and this file does not
 * add one. Everything below is transform and opacity driven by a class
 * the script sets once, which is what the browser is already good at.
 *
 * Every primitive is opt-in through a data attribute, so a template
 * asks for motion rather than inheriting it. Nothing here is required
 * for the page to be readable: with the script absent, `.js` is never
 * set on <html>, none of the lowered start states apply, and the page
 * renders exactly as it does today.
 */

:root {
  /* The brief's timings, named so a component asks for a role rather
     than a number and the scale stays consistent across pages. */
  --m-micro:  220ms;   /* taps, marks, small state changes   180–300 */
  --m-hover:  380ms;   /* hover lift, image zoom             300–450 */
  --m-text:   620ms;   /* text and section reveals           500–700 */
  --m-image:  880ms;   /* photographs settling               700–1000 */
  --m-page:   420ms;   /* document transitions               350–500 */

  /* The house easing, already used throughout, plus a softer landing
     for anything that travels far enough to need one. */
  --m-ease:     var(--ease);
  --m-ease-out: cubic-bezier(0.16, 1, 0.30, 1);

  /* One step of a stagger. Twelve cards at 60ms is 720ms from first to
     last, which is long enough to read as a sequence and short enough
     that the last card is not still waiting when the eye reaches it. */
  --m-step: 60ms;
}

/* ====================================================================
   Reveal — the base primitive everything else is built on.

   Moved here from v2.css. The lowered start state is scoped to `.js`
   so a script that never arrives cannot leave content invisible; that
   guarantee is the reason this is a class on <html> and not a plain
   rule.
==================================================================== */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--m-text) var(--m-ease),
              transform var(--m-text) var(--m-ease);
}
.js [data-reveal].is-in { opacity: 1; transform: none; }

/* Directional variants. The attribute carries the direction so a
   template reads `data-reveal="left"` rather than carrying a class
   whose name means nothing on its own. */
.js [data-reveal="fade"]  { transform: none; }
.js [data-reveal="left"]  { transform: translateX(-24px); }
.js [data-reveal="right"] { transform: translateX(24px); }
.js [data-reveal="up"]    { transform: translateY(28px); }

/* Slower and longer for anything image-led. */
.js [data-reveal="image"] {
  transform: translateY(24px);
  transition-duration: var(--m-image);
  transition-timing-function: var(--m-ease-out);
}

/* will-change is deliberately NOT declared here. The old rule set it on
   every [data-reveal] on the page at once — twenty-seven of them on
   some templates — which asks the compositor to hold a layer for each
   before any of them has moved. It is set by the script on the element
   about to animate and dropped when that element lands. */

/* ====================================================================
   Stagger — a group whose children arrive in sequence.

   The container is what gets observed, not the children: nine cards
   each firing on their own intersection is nine separate arrivals that
   happen to overlap, which reads as noise. One trigger with a delay per
   child reads as a sequence.
==================================================================== */
.js [data-stagger] > * {
  transition-delay: calc(var(--i, 0) * var(--m-step));
}

/* Wellness asks for a slower hand than the rest of the site: a longer
   step between children and the image duration rather than the text
   one. Carried on the attribute's value so the page requests a pace
   rather than owning a stylesheet of its own. */
.js [data-stagger="slow"] > * {
  transition-duration: var(--m-image);
  transition-delay: calc(var(--i, 0) * 100ms);
}

/* A stagger that has already played must not re-delay if the class is
   toggled again — the delay is only meaningful on the way in. */
.js [data-stagger].is-done > * { transition-delay: 0ms; }

/* ====================================================================
   Image reveal — the photograph settles rather than appears.

   A scale of 1.04 over 880ms is about the smallest move that still
   reads as intentional. Anything larger starts to look like a zoom
   effect, which is the opposite of what an image-led page wants.
==================================================================== */
.js [data-img-reveal] { overflow: hidden; }
.js [data-img-reveal] img {
  transform: scale(1.04);
  transition: transform var(--m-image) var(--m-ease-out);
}
.js [data-img-reveal].is-in img { transform: scale(1); }

/* ====================================================================
   Parallax — the slowest thing on the site.

   The script writes a pixel offset into --py; this is the only rule
   that consumes it. Kept to transform so it never touches layout, and
   confined to wide viewports: on a phone the travel is a large part of
   the element's own height and the effect becomes a wobble.
==================================================================== */
@media (min-width: 62em) {
  .js [data-parallax] img { transform: translate3d(0, var(--py, 0px), 0) scale(1.08); }
}

/* Tablet keeps a trace of it — half the travel, via the script, which
   reads this breakpoint too — but nothing below that. */
@media (min-width: 48em) and (max-width: 61.99em) {
  .js [data-parallax] img { transform: translate3d(0, var(--py, 0px), 0) scale(1.05); }
}

/* An element doing both is a contradiction: the reveal wants to land on
   scale(1) and the parallax wants to hold a scale to crop against. The
   parallax wins, and the reveal on that element becomes a plain fade. */
.js [data-parallax][data-img-reveal] img,
.js [data-parallax].is-in img { transition: transform var(--m-image) var(--m-ease-out); }

/* ====================================================================
   Hover lift — pointer devices only.

   Behind a hover query because on a touch screen :hover latches after a
   tap and the card stays raised until something else is touched, which
   looks like a selection state the page does not have.
==================================================================== */
@media (hover: hover) and (pointer: fine) {
  [data-lift] {
    transition: transform var(--m-hover) var(--m-ease),
                box-shadow var(--m-hover) var(--m-ease);
  }
  [data-lift]:hover {
    transform: translateY(-4px);
    box-shadow: 0 18px 40px -28px rgba(42, 33, 28, 0.55);
  }
  /* The photograph inside a lifted card leans in very slightly. Scoped
     to a figure so it cannot catch an icon or a logo. */
  [data-lift] .figure { overflow: hidden; }
  [data-lift] .figure img { transition: transform var(--m-hover) var(--m-ease); }
  [data-lift]:hover .figure img { transform: scale(1.03); }

  /* An arrow in a lifted card travels with it. */
  [data-lift] .link--arrow::after { transition: transform var(--m-hover) var(--m-ease); }
  [data-lift]:hover .link--arrow::after { transform: translateX(4px); }
}

/* ====================================================================
   Header — bone bar becomes glass once the page has moved.

   .is-stuck already carried a hairline and a shadow. The blur is added
   behind a support query: where backdrop-filter is unavailable the
   header stays a solid bone bar, which is the current behaviour and is
   perfectly good.
==================================================================== */
.hdr {
  transition: background-color var(--m-hover) var(--m-ease),
              box-shadow var(--m-hover) var(--m-ease),
              border-color var(--m-hover) var(--m-ease);
}
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
  .hdr.is-stuck {
    background: color-mix(in srgb, var(--bone) 82%, transparent);
    -webkit-backdrop-filter: blur(10px) saturate(1.1);
            backdrop-filter: blur(10px) saturate(1.1);
  }
  /* The open mobile panel must stay opaque — translucency over a
     photograph turns seven links into an unreadable stack. */
  .hdr.is-open.is-stuck { background: var(--bone); backdrop-filter: none; }
}

/* ====================================================================
   Smooth accordion.

   grid-template-rows 0fr -> 1fr is the one way to transition to a
   content-derived height without measuring it in script. <details>
   needs its content wrapped in a single grid child, which .qa__a
   already is.
==================================================================== */
.js .qa__item > .qa__a {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--m-hover) var(--m-ease);
}
.js .qa__item[open] > .qa__a { grid-template-rows: 1fr; }
.js .qa__item > .qa__a > * { overflow: hidden; min-height: 0; }

/* The mark already rotates between a cross and a minus; it just never
   had a duration on it. */
.qa__mark::before, .qa__mark::after {
  transition: transform var(--m-micro) var(--m-ease),
              opacity var(--m-micro) var(--m-ease);
}

/* ====================================================================
   Count-up — the figures on the about page.

   The script counts to whatever the server already printed; it never
   invents a target. Tabular figures so the width does not jitter while
   the number climbs.
==================================================================== */
[data-countup] { font-variant-numeric: tabular-nums; }

/* ====================================================================
   Page transitions.

   This started as cross-document view transitions — `@view-transition`
   plus a name on the header — and they were removed for two reasons.

   The first is measurable: a transition the browser decides to skip
   rejects its own `ready` and `finished` promises, and since nothing
   awaits them the rejection lands in the console as an uncaught
   AbortError on every navigation. It cannot be caught from page script
   either; `pagereveal` fires on the incoming document before a deferred
   script runs, and an inline guard in the head did not catch it.

   The second matters more. Cross-document view transitions are a Chrome
   feature. Safari and Firefox navigate with no transition at all, which
   for a hospitality site means the iPhone half of the audience gets
   none of it — and a flourish that only some visitors see is a worse
   answer than a quieter one everybody gets.

   So the document fades in on arrival. Opacity only, and deliberately
   not transform: a transform on <body> creates a containing block and
   the sticky header stops sticking. It is not gated on `.js`, because
   the class arrives with a deferred script and the page would flash
   visible before it did — the stylesheet is render-blocking, so the
   first paint is already at zero.
==================================================================== */
@keyframes page-in { from { opacity: 0; } to { opacity: 1; } }

body { animation: page-in var(--m-page) var(--m-ease) both; }

/* ====================================================================
   Reduced motion.

   Everything decorative stops. Nothing here is load-bearing: reveals
   resolve to their finished state, the accordion opens and closes
   instantly, parallax is inert because the script never writes --py,
   and the document transition is turned off entirely.
==================================================================== */
@media (prefers-reduced-motion: reduce) {
  .js [data-reveal],
  .js [data-reveal].is-in {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .js [data-stagger] > * { transition-delay: 0ms; }
  .js [data-img-reveal] img,
  .js [data-img-reveal].is-in img { transform: none; transition: none; }
  .js [data-parallax] img { transform: none; }

  [data-lift],
  [data-lift]:hover { transform: none; box-shadow: none; transition: none; }
  [data-lift]:hover .figure img { transform: none; }

  .js .qa__item > .qa__a { transition: none; }

  body { animation: none; }
}
