/* Motion start states and helpers (MOTION.md). html.hm-pending comes from the inline head script (only without
   reduced motion) and is removed by assets/hero-motion.js or by its 3 s fail-safe, so the static hero always returns. */

/* No browser scroll anchoring: in Firefox it moved the page up ~70 px during the Deck plan entrance right after a menu
   link jump (§17). Photos carry their sizes and Show / Hide keeps its own position, so nothing else relies on it. */
html { overflow-anchor: none; }

/* Hero: the frame is clipped by --hm-r (entrance, left → right) and --hm-l (desktop scroll exit). */
html.hm-pending .hero-image { --hm-r: 101%; }
html.hm-pending .hero-image,
html.hm-on .hero-image { clip-path: inset(0 var(--hm-r, 0%) 0 var(--hm-l, 0%)); }
html.hm-pending .hero-title,
html.hm-pending .hero-side { opacity: 0; }

/* Name line masks: no padding or margins (they collapse with the title margins and switch off text-box-trim); the
   clip reaches .3em past the line box with overflow-clip-margin, so letters are not cut and the layout is identical. */
.hero-title .hm-mask { display: block; overflow: hidden; overflow: clip; overflow-clip-margin: .3em; }
.hero-title .hm-in { display: block; will-change: transform; }
.hero.hm-run .hero-image img { will-change: transform; }

/* Key figures: html.fm-pending holds the row until assets/figures-motion.js sets inline start states (3 s fail-safe).
   While a fact is animating (li.fm-run) its hairline scales from the top by --fm-l; the number rises in a line mask
   (same as the hero name: no padding or margins, the clip reaches .3em past the line box). */
html.fm-pending .figures-list li::before { transform: scaleY(0); transform-origin: top; }
html.fm-pending .figures-value,
html.fm-pending .figures-label { opacity: 0; }
.figures-list li.fm-run::before { transform: scaleY(var(--fm-l, 0)); transform-origin: top; }
.figures-value.fm-mask { overflow: hidden; overflow: clip; overflow-clip-margin: .3em; }
.figures-value .fm-in { display: block; will-change: transform; }

/* History: html.hs-pending holds the section until assets/history-motion.js sets inline start states (3 s fail-safe).
   While a year group is animating (.hs-run) its hairline scales from the left by --hs-l. */
html.hs-pending .history-title,
html.hs-pending .history-intro,
html.hs-pending .history-year,
html.hs-pending .history-events li { opacity: 0; }
html.hs-pending .history-group::before { transform: scaleX(0); transform-origin: left; }
.history-group.hs-run::before { transform: scaleX(var(--hs-l, 0)); transform-origin: left; }

/* The All-Season Terrace: html.ts-pending holds the section until assets/terrace-motion.js sets inline start states
   (3 s fail-safe). While a photo is opening (.ts-run) its frame is clipped by --tr (opens left → right) or --tl
   (right → left); the photo settles inside the clip. */
html.ts-pending .terrace-title,
html.ts-pending .terrace-part { opacity: 0; }
html.ts-pending .terrace-image { clip-path: inset(0 101% 0 0); }
.terrace-image.ts-run { clip-path: inset(0 var(--tr, 0%) 0 var(--tl, 0%)); }
.terrace-image.ts-run img { will-change: transform; }

/* On board: html.ob-pending holds the section until assets/onboard-motion.js sets inline start states (3 s fail-safe);
   the photos already wait zoomed in (no jump when the script starts). While a photo settles (.ob-run) its frame clips
   it. Desktop scroll shift: the lower deck's children follow --ob-y with the translate property (separate from the
   transforms of the photo and the block text), the deck element itself stays in place. */
html.ob-pending .onboard-title,
html.ob-pending .onboard-lead,
html.ob-pending .onboard-block > * { opacity: 0; }
html.ob-pending .onboard-image,
.onboard-image.ob-run { overflow: hidden; overflow: clip; }
html.ob-pending .onboard-image img { transform: scale(1.05); }
.onboard-image.ob-run img { will-change: transform; }
.onboard-deck-lower.ob-drift > * { translate: 0 var(--ob-y, 0px); }

/* Deck plan: html.dp-pending holds the section until assets/deckplan-motion.js sets inline start states (3 s
   fail-safe). While the drawing rises (.dp-run) its frame clips it (the frame does not move); each legend level's
   hairline scales from the left by --dp-l. */
html.dp-pending .deckplan-title,
html.dp-pending .deckplan-intro,
html.dp-pending .deckplan-level > *,
html.dp-pending .deckplan-pdf { opacity: 0; }
html.dp-pending .deckplan-drawing,
.deckplan-drawing.dp-run { overflow: hidden; overflow: clip; }
html.dp-pending .deckplan-drawing img { transform: translateY(101%); }
.deckplan-drawing.dp-run img { will-change: transform; }
html.dp-pending .deckplan-level::before { transform: scaleX(0); transform-origin: left; }
.deckplan-level.dp-run::before { transform: scaleX(var(--dp-l, 0)); transform-origin: left; }

/* Design: html.ds-pending holds the section until assets/design-motion.js sets inline start states (3 s fail-safe).
   While a photo opens (.ds-run) its frame is clipped from the centre by --dy (height) and --dx (width); each register
   row's hairline scales from the left by --ds-l. */
html.ds-pending .design-title,
html.ds-pending .design-text,
html.ds-pending .design-materials > div > * { opacity: 0; }
html.ds-pending :is(.design-sideboard, .design-stair) { clip-path: inset(50%); }
:is(.design-sideboard, .design-stair).ds-run { clip-path: inset(var(--dy, 0%) var(--dx, 0%)); }
html.ds-pending .design-materials > div::before { transform: scaleX(0); transform-origin: left; }
.design-materials > div.ds-run::before { transform: scaleX(var(--ds-l, 0)); transform-origin: left; }

/* Breath: while the sentence fills (assets/breath-motion.js) its words are inline spans; the line spans' own rule
   (.breath-line span { display: block }) must not reach them, or every word would take its own line. */
.breath-line .bw { display: inline; }

/* Engineering: html.en-pending holds the section until assets/engineering-motion.js sets inline start states (3 s
   fail-safe); the photo already waits zoomed in. While the photo settles (.en-run) both slide layers follow --en-s;
   each register row's hairline scales from the left by --en-l. */
html.en-pending .eng-title,
html.en-pending .eng-system,
html.en-pending .eng-register > div > * { opacity: 0; }
html.en-pending .eng-photo img { transform: scale(1.05); }
.eng-photo.en-run img { transform: scale(var(--en-s, 1)); will-change: transform; }
html.en-pending .eng-register > div::before { transform: scaleX(0); transform-origin: left; }
.eng-register > div.en-run::before { transform: scaleX(var(--en-l, 0)); transform-origin: left; }

/* 3D tour: html.tu-pending holds the content until assets/tour-motion.js sets inline start states (3 s fail-safe);
   the photo already waits zoomed in. While the entrance runs (.tu-run) the photo follows --tu-s inside the frame. */
html.tu-pending .tour-line,
html.tu-pending .tour-open,
html.tu-pending .tour-play { opacity: 0; }
html.tu-pending .tour-image img { transform: scale(1.05); }
.tour-media.tu-run .tour-image img { transform: scale(var(--tu-s, 1)); will-change: transform; }

/* Specification: html.sp-pending holds the title, "As Offered" and the button until assets/spec-motion.js sets inline
   start states (3 s fail-safe). While the price rises, its value cell clips it (clip-path does not change alignment or
   the baseline; the clip reaches .3 × the price size past the cell, as the other line masks). */
html.sp-pending .spec-title,
html.sp-pending .spec-register > .spec-group:first-child,
html.sp-pending .spec-more { opacity: 0; }
.spec-row-price dd.sp-mask { clip-path: inset(calc(var(--t-price) * -.3)); }
.spec-row-price dd.sp-mask .spec-price { will-change: transform; }

/* Gallery: html.gl-pending holds the title, the photos (below their frame's bottom edge; each photo button already
   clips) and the nav until assets/gallery-motion.js sets inline start states for the visible photos (3 s fail-safe).
   While the entrance runs (.gl-run) the hover's CSS transition is off so it cannot smooth the rise. */
html.gl-pending .gallery-title,
html.gl-pending .gallery-nav { opacity: 0; }
html.gl-pending .gallery-photo img { transform: translateY(101%); }
html.gl-pending .gallery-progress { transform: scaleX(0); transform-origin: left center; }
.gallery-track.gl-run .gallery-photo img { transition: none !important; will-change: transform; }

/* Ownership: html.ow-pending holds the column until assets/ownership-motion.js sets inline start states (3 s
   fail-safe). While a fact row animates (.ow-run) its top hairline (and the last row's bottom line) scales from the
   left by --ow-l. */
html.ow-pending .ownership-title,
html.ow-pending .ownership-statement,
html.ow-pending .ownership-text,
html.ow-pending .ownership-facts > div > * { opacity: 0; }
html.ow-pending .ownership-facts > div::before,
html.ow-pending .ownership-facts > div:last-child::after { transform: scaleX(0); transform-origin: left; }
.ownership-facts > div.ow-run::before,
.ownership-facts > div.ow-run:last-child::after { transform: scaleX(var(--ow-l, 0)); transform-origin: left; }

/* Viewing and purchase: html.cl-pending holds the title, contact, Viewing / Purchase and the footer text until
   assets/closing-motion.js sets inline start states (3 s fail-safe); the photo already waits zoomed in, anchored at the
   bottom like its object-position. While the entrance runs (.cl-run) the photo follows --cl-s, the footer line --cl-l. */
html.cl-pending .closing-title,
html.cl-pending .closing-contact,
html.cl-pending .closing-viewing,
html.cl-pending .closing-purchase,
html.cl-pending .closing-foot > * { opacity: 0; }
html.cl-pending .closing-image img { transform: scale(1.05); transform-origin: 50% 100%; }
.closing-shot.cl-run .closing-image img { transform: scale(var(--cl-s, 1)); transform-origin: 50% 100%; will-change: transform; }
html.cl-pending .closing-foot::before { transform: scaleX(0); transform-origin: left; }
.closing-foot.cl-run::before { transform: scaleX(var(--cl-l, 0)); transform-origin: left; }

/* Side panels — menu D, enquiry A, FAQ A (assets/panel-motion.js): a panel fades in place as one piece (inline opacity /
   transform); while the dim fades (.pn-run) its tint and blur follow custom properties; .is-leaving keeps a panel, the
   dim or a closing FAQ answer displayed (not clickable) for the short fade after the layer script has already hidden
   them; a running answer clips its text (.fa-run). The menu button's lines move to the X and back. The Ask button fades
   in .3 s / out .2 s (visibility switches at the end of the fade-out); the transition list repeats the button's hover
   transitions (this file loads after the page styles). The +/− bar fades. Lightbox A: the layer fades as a whole, the
   old photo's copy (.lb-ghost) fades out over the new one, the progress segment slides to its place. */
.menu[hidden].is-leaving, .lightbox[hidden].is-leaving { display: grid; pointer-events: none; }
.lb-ghost { pointer-events: none; }
.enquire[hidden].is-leaving, .faq[hidden].is-leaving { display: block; pointer-events: none; }
.faq-a[hidden].is-leaving { display: block; }
.faq-a.fa-run { overflow: hidden; }
.overlay-dim.pn-run { background: rgb(0 0 0 / var(--pn-a, .45)); -webkit-backdrop-filter: blur(var(--pn-b, 6px)); backdrop-filter: blur(var(--pn-b, 6px)); }
.overlay-dim[hidden].is-leaving { display: block; pointer-events: none; }
@media (prefers-reduced-motion: no-preference) {
  .nav-toggle-line { transition: top .3s cubic-bezier(.65, 0, .35, 1), right .3s cubic-bezier(.65, 0, .35, 1), width .3s cubic-bezier(.65, 0, .35, 1), transform .3s cubic-bezier(.65, 0, .35, 1); }
  .faq-btn { transition: opacity .3s cubic-bezier(.33, 1, .68, 1), visibility 0s, transform 250ms cubic-bezier(.25, 1, .5, 1), color 300ms ease, border-color 300ms ease; }
  .faq-btn.is-off, html.tour-fullscreen .faq-btn { transition: opacity .2s cubic-bezier(.32, 0, .67, 0), visibility 0s linear .2s, transform 250ms cubic-bezier(.25, 1, .5, 1), color 300ms ease, border-color 300ms ease; }
  .faq-q .pm .v { transition: opacity .2s ease; }
  .lightbox-progress span { transition: left .35s cubic-bezier(.33, 1, .68, 1); }
}

/* Section title lines (infMotion.maskLines): a clip box per line without padding or margins (they would collapse with
   the title margins); the clip reaches .3em past the line box, so letters are not cut and the layout is identical.
   Each line's text stays on one line while wrapped. */
.lm-mask { display: block; overflow: hidden; overflow: clip; overflow-clip-margin: .3em; }
.lm-line { display: block; white-space: nowrap; will-change: transform; }

/* Lenis */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
