/* Moringa motion layer.
   Loaded after styles.css. Every rule below is gated by `.motion`, which the inline
   head script only sets when the visitor has NOT asked for reduced motion. Without the
   class (or without motion.js) the page is fully static and complete.

   Budget: one rehearsed entrance (the hero), one signature transition per world
   (the banner wipe), scroll-linked depth on a few large shapes, quick feedback
   elsewhere. Durations: feedback 150-250 ms, state 250-400 ms, entrances 600-1000 ms.
   Curves: exponential ease-out only, no bounce. */

:root {
  --ease-quart: cubic-bezier(0.25, 1, 0.5, 1);
  --ease-in: cubic-bezier(0.7, 0, 0.84, 0);
  --dir: 1;
}
:root[dir='rtl'] { --dir: -1; }

/* ================================================================
   1. Hero: rehearsed entrance
   ================================================================ */
.motion:not(.ready) .hero .status,
.motion:not(.ready) .hero .wm,
.motion:not(.ready) .hero .wm-sub,
.motion:not(.ready) .hero .lead,
.motion:not(.ready) .hero .cta > *,
.motion:not(.ready) .hero__art .plate__f,
.motion:not(.ready) .hero__sprig { visibility: hidden; }

.motion.ready .hero .status { animation: ribbon-in 0.8s var(--ease) 0.05s backwards; }
@keyframes ribbon-in { from { clip-path: polygon(50% 0, 50% 0, 50% 50%, 50% 100%, 50% 100%, 50% 50%); } }
/* The wordmark is Latin in every language. Its letters are atomic boxes, so under dir=rtl they would
   lay out right to left and spell it backwards; keep it ltr and pinned to the line start. */
.motion .hero .wm { white-space: nowrap; direction: ltr; align-self: flex-start; }
.motion .wm .ch { display: inline-block; }
.motion.ready .hero .wm .ch { animation: letter-in 0.95s var(--ease) calc(var(--i, 0) * 60ms + 0.1s) backwards; }
.motion.ready .hero .wm-sub { animation: pop-in 0.7s var(--ease) 0.6s backwards; }
.motion.ready .hero .lead { animation: rise-in 0.8s var(--ease) 0.72s backwards; }
.motion.ready .hero .cta > :nth-child(1) { animation: rise-in 0.8s var(--ease) 0.86s backwards; }
.motion.ready .hero .cta > :nth-child(2) { animation: rise-in 0.8s var(--ease) 0.96s backwards; }

@keyframes letter-in { from { opacity: 0; transform: translateY(0.5em) rotate(6deg); } }
@keyframes pop-in { from { opacity: 0; transform: translateY(0.4rem) scale(0.92); } }
@keyframes rise-in { from { opacity: 0; transform: translateY(1rem); } }
@keyframes fade-in { from { opacity: 0; } }

/* Plates land one after another, then hover in place. The outer wrapper is reserved for
   scroll parallax, the inner one carries the entrance and the idle float. Delayed loops use
   `both` so their first keyframe already applies during the delay; without it they snap on start. */
.motion .hero__art .plate--1 { --d: 0s; --fd: 8s; --py: -2.5rem; }
.motion .hero__art .plate--2 { --d: 0.1s; --fd: 6.5s; --py: 2.2rem; }
.motion .hero__art .plate--3 { --d: 0.2s; --fd: 7.4s; --py: -4.2rem; }
.motion .hero__art .plate--4 { --d: 0.3s; --fd: 6s; --py: -1.4rem; }
.motion .hero__art .plate--5 { --d: 0.4s; --fd: 7s; --py: 3.2rem; }
.motion.ready .hero__art .plate__f {
  animation:
    plate-in 1s var(--ease) calc(0.4s + var(--d)) backwards,
    float var(--fd) ease-in-out calc(1.8s + var(--d)) 8 alternate both;
}
@keyframes plate-in { from { opacity: 0; transform: translateY(2rem) scale(0.84); } }
@keyframes float { from { translate: 0 -0.3rem; } to { translate: 0 0.55rem; } }

/* Sprigs: stem draws, leaflets open from the bottom up, then the whole thing sways. */
.motion .sprig { animation: sway 10s ease-in-out 6 alternate forwards; }
@keyframes sway { from { transform: rotate(-1.8deg); } to { transform: rotate(2.4deg); } }
.motion.ready .hero__sprig .sprig { animation: fade-in 0.6s ease-out 0.25s backwards, sway 10s ease-in-out 0.8s 6 alternate both; }
.motion.ready .hero__sprig .stem { stroke-dasharray: 1; animation: draw 1.6s var(--ease) 0.3s backwards; }
.motion.ready .hero__sprig .lf { animation: leaf-in 0.9s var(--ease) calc(var(--i, 0) * 55ms + 0.5s) backwards; }
@keyframes draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes leaf-in { from { opacity: 0; transform: scale(0.4); } }

/* The open/closed dot breathes so it reads as live. */
.motion .status[data-open='true'] .dot { animation: pulse 2.4s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 55%, transparent); } 60% { box-shadow: 0 0 0 0.55rem transparent; } }

/* ================================================================
   2. Scroll-linked depth (progressive: Chromium, Safari 26+; static elsewhere)
   ================================================================ */
@supports (animation-timeline: scroll()) {
  .motion .progress {
    display: block; position: fixed; inset: 0 0 auto 0; height: 3px; z-index: 80; background: var(--coral);
    transform-origin: left; transform: scaleX(0); animation: progress linear both; animation-timeline: scroll(root);
  }
  :root[dir='rtl'].motion .progress { transform-origin: right; }
  @keyframes progress { to { transform: scaleX(1); } }

  .motion .hero__art .plate { animation: drift linear both; animation-timeline: scroll(root); animation-range: 0 110vh; }
  .motion .hero__sprig { animation: sprig-out linear both; animation-timeline: scroll(root); animation-range: 0 110vh; }
  .motion .hero__copy { animation: copy-out linear both; animation-timeline: scroll(root); animation-range: 0 75vh; }
  @keyframes drift { to { translate: 0 var(--py, -3rem); } }
  @keyframes sprig-out { to { rotate: 36deg; translate: 0 4rem; } }
  @keyframes copy-out { to { opacity: 0.15; translate: 0 -2rem; } }

  .motion .world__art .disc { animation: spin linear both; animation-timeline: view(); animation-range: entry 0% exit 100%; }
  @keyframes spin { from { rotate: -55deg; } to { rotate: 55deg; } }
  .motion .world__cluster .plate { animation: drift linear both; animation-timeline: view(); animation-range: entry 0% exit 100%; }
  .motion .world__cluster .plate--1 { --py: -2.2rem; }
  .motion .world__cluster .plate--2 { --py: 2.4rem; }
  .motion .world__cluster .plate--3 { --py: -3rem; }
  .motion .world__banner::before { animation: sun linear both; animation-timeline: view(); animation-range: entry 0% exit 100%; }
  @keyframes sun { from { translate: 0 3.5rem; scale: 0.88; } to { translate: 0 -3.5rem; scale: 1.1; } }
}

/* ================================================================
   3. World banners: a colour wipe, then words, description and art
   ================================================================ */
.motion .world__banner.reveal { clip-path: inset(0 0 100% 0); transition: clip-path 1s var(--ease); }
.motion .world__banner.reveal.is-in { clip-path: inset(0 0 0 0); }
.motion .world__desc { opacity: 0; translate: 0 0.6rem; }
.motion .is-in .world__desc { opacity: 1; translate: 0 0; transition: opacity 0.7s var(--ease) 0.5s, translate 0.7s var(--ease) 0.5s; }
.motion .world__art, .motion .world__cluster { opacity: 0; scale: 0.86; }
.motion .is-in .world__art, .motion .is-in .world__cluster { opacity: 1; scale: 1; transition: opacity 0.8s var(--ease) 0.3s, scale 1s var(--ease) 0.3s; }
.motion .world__banner:not(.is-in) .lf { opacity: 0; }
.motion .world__banner.is-in .lf { animation: leaf-in 0.9s var(--ease) calc(var(--i, 0) * 55ms + 0.4s) backwards; }

/* Word-by-word headline reveal. Words sit in a mask and rise; state lives on an ancestor
   (.is-in / .in), so headings rebuilt after a language switch appear already settled. */
.motion .w { display: inline-block; overflow: hidden; vertical-align: top; padding: 0.04em 0.06em 0.24em 0; margin: -0.04em -0.06em -0.24em 0; }
.motion:lang(ar) .w, .motion:lang(hi) .w { padding-block: 0.3em; margin-block: -0.3em; }
.motion .w > span { display: inline-block; transform: translateY(110%); }
.motion .is-in .w > span,
.motion .in .w > span { transform: none; transition: transform 0.85s var(--ease) calc(var(--i, 0) * 70ms + var(--wd, 0ms)); }
.motion .world__banner .w > span { --wd: 250ms; }
.motion .quote--lead .w > span { --wd: 350ms; }

/* ================================================================
   4. Sections and dishes
   ================================================================ */
.motion .sec__head:not(.in) .flat { opacity: 0; scale: 0.6; rotate: -100deg; }
.motion .flat { transform: none; rotate: -8deg; transition: opacity 0.4s ease-out 0.35s, scale 0.7s var(--ease) 0.35s, rotate 0.7s var(--ease) 0.35s; }
.motion .sec__head:not(.in) .sec__note { opacity: 0; }
.motion .sec__note { transition: opacity 0.6s ease-out 0.45s; }

.motion .item:not(.in) { opacity: 0; }
.motion .item.in { animation: item-in 0.65s var(--ease) var(--d, 0ms) backwards; }
.motion .item--pic.in .item__pic { animation: pic-in 0.85s var(--ease) calc(var(--d, 0ms) + 90ms) backwards; }
@keyframes item-in { from { opacity: 0; translate: 0 0.9rem; } }
@keyframes pic-in { from { opacity: 0; scale: 0.82; rotate: -10deg; } }
/* Filtering and searching rebuild the list; that should feel like a quick refresh, not a replay. */
.motion .settled .item:not(.in) { opacity: 1; }
.motion .settled .item.in, .motion .settled .item--pic.in .item__pic, .motion .settled .world__banner .lf { animation: none; }

.item__pic { transition: scale 0.35s var(--ease-quart), rotate 0.35s var(--ease-quart); }
@media (hover: hover) and (pointer: fine) {
  .motion .item--pic:hover .item__pic { scale: 1.07; rotate: -5deg; }
}
.motion .item--pic:active .item__pic { scale: 0.96; }

/* ================================================================
   5. Category rail: one pill that glides between chips
   ================================================================ */
.motion .rail__scroll { position: relative; }
.motion .chip { position: relative; z-index: 1; }
.motion .rail__ind {
  display: block; position: absolute; left: 0; top: 0.55rem; height: 2.4rem; z-index: 0; pointer-events: none;
  background: var(--ink); opacity: 0; transition: clip-path 0.42s var(--ease), opacity 0.2s;
}
.motion .rail__ind.on { opacity: 1; }
.motion .rail__ind.snap { transition: none; }
.motion:not([dir='rtl']) .chip[aria-current='true'] { background: transparent; border-color: transparent; transition: color 0.25s ease 0.12s, border-color 0.2s; }
.motion:not([dir='rtl']) .chip[aria-current='true']::before { box-shadow: 0 0 0 1.5px var(--ink); }
/* Sections of a world that opens slide out from its chip. */
.motion .chip--group[aria-expanded='true'] ~ .chip:not(.chip--group):not([hidden]) { animation: chip-in 0.32s var(--ease) both; }
@keyframes chip-in { from { opacity: 0; translate: -0.6rem 0; } }
.motion[dir='rtl'] .chip--group[aria-expanded='true'] ~ .chip:not(.chip--group):not([hidden]) { animation-name: chip-in-rtl; }
@keyframes chip-in-rtl { from { opacity: 0; translate: 0.6rem 0; } }

/* ================================================================
   6. Small state changes
   ================================================================ */
/* Language panel grows out of its button; closing is quicker than opening. */
.motion .langpanel {
  transform-origin: top right; transition: opacity 0.22s var(--ease), transform 0.22s var(--ease), display 0.22s allow-discrete;
}
:root[dir='rtl'].motion .langpanel { transform-origin: top left; }
.motion .langpanel[hidden] { display: none; opacity: 0; transform: scale(0.94) translateY(-0.4rem); transition-duration: 0.15s; }
@starting-style { .motion .langpanel:not([hidden]) { opacity: 0; transform: scale(0.94) translateY(-0.4rem); } }

.motion .totop {
  display: inline-flex; opacity: 0; visibility: hidden; translate: 0 0.75rem; scale: 0.9;
  transition: opacity 0.25s ease, translate 0.3s var(--ease), scale 0.3s var(--ease), visibility 0s linear 0.3s;
}
.motion .totop[data-show='true'] { opacity: 1; visibility: visible; translate: 0 0; scale: 1; transition-delay: 0s; }
.motion .totop:active { scale: 0.94; }

/* Theme and language changes run through the View Transitions API when available. */
::view-transition-old(root), ::view-transition-new(root) { animation: none; mix-blend-mode: normal; }
.vt-theme::view-transition-old(root) { z-index: 1; }
.vt-theme::view-transition-new(root) { z-index: 2; }
.vt-lang::view-transition-old(root) { animation: vt-out 0.16s ease both; }
.vt-lang::view-transition-new(root) { animation: vt-in 0.32s var(--ease) both; }
@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; filter: blur(4px); } }

/* ================================================================
   7. Marquee: every section name, drifting slowly
   ================================================================ */
.motion .marquee {
  display: block; overflow: hidden; height: calc(clamp(1.15rem, 2.4vw, 1.6rem) * 1.5 + 1.8rem + 1px); padding-block: 0.9rem; box-sizing: border-box; user-select: none;
  border-block: 1px solid var(--line); margin-block-start: -1px;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
.marquee__track { display: flex; width: max-content; animation: marquee 90s linear infinite; }
.mq__g { display: flex; align-items: center; flex: none; }
.mq__i { padding-inline: 1.1rem; font-weight: 800; font-size: clamp(1.15rem, 2.4vw, 1.6rem); letter-spacing: -0.02em; white-space: nowrap; }
.mq__leaf { width: 1.7rem; height: 1.7rem; flex: none; fill: var(--coral); }
@keyframes marquee { to { transform: translateX(-50%); } }
:root[dir='rtl'] .marquee__track { animation-name: marquee-rtl; }
@keyframes marquee-rtl { to { transform: translateX(50%); } }
.marquee.paused .marquee__track { animation-play-state: paused; }
@media (hover: hover) and (pointer: fine) { .marquee:hover .marquee__track { animation-play-state: paused; } }

/* ================================================================
   8. Reviews
   ================================================================ */
.motion .reviews:not(.in) .reviews__sprig { opacity: 0; }
.motion .reviews.in .reviews__sprig { animation: fade-in 0.8s ease-out backwards; }
.motion .reviews:not(.in) .lf { opacity: 0; }
.motion .reviews.in .lf { animation: leaf-in 0.9s var(--ease) calc(var(--i, 0) * 55ms + 0.3s) backwards; }

.motion .stars svg { transition: opacity 0.4s ease-out, scale 0.6s var(--ease); }
.motion .reviews:not(.in) .rating .stars svg { opacity: 0; scale: 0.5; }
.motion .rating .stars svg:nth-child(2) { transition-delay: 70ms; }
.motion .rating .stars svg:nth-child(3) { transition-delay: 140ms; }
.motion .rating .stars svg:nth-child(4) { transition-delay: 210ms; }
.motion .rating .stars svg:nth-child(5) { transition-delay: 280ms; }
.motion .quote:not(.in) .stars svg { opacity: 0; scale: 0.5; }

.motion .dist .bar i { transform-origin: left; transition: transform 1.1s var(--ease); }
:root[dir='rtl'].motion .dist .bar i { transform-origin: right; }
.motion .reviews:not(.in) .dist .bar i { transform: scaleX(0); }
.motion .dist div:nth-child(2) .bar i { transition-delay: 90ms; }
.motion .dist div:nth-child(3) .bar i { transition-delay: 180ms; }
.motion .dist div:nth-child(4) .bar i { transition-delay: 270ms; }
.motion .dist div:nth-child(5) .bar i { transition-delay: 360ms; }

.motion .topics li { transition: opacity 0.5s ease-out, translate 0.6s var(--ease); }
.motion .reviews:not(.in) .topics li { opacity: 0; translate: 0 0.7rem; }
.motion .topics li:nth-child(2) { transition-delay: 70ms; }
.motion .topics li:nth-child(3) { transition-delay: 140ms; }
.motion .topics li:nth-child(4) { transition-delay: 210ms; }

/* Quotes: the rule draws itself, then the text settles. */
.motion .quote { position: relative; border-block-start-color: transparent; transition: opacity 0.7s ease-out var(--d, 0ms), translate 0.7s var(--ease) var(--d, 0ms); }
.motion .quote:not(.in) { opacity: 0; translate: 0 1rem; }
.motion .quote::before {
  content: ''; position: absolute; inset: -2px 0 auto 0; height: 2px; background: currentColor;
  transform-origin: left; transition: transform 1s var(--ease) var(--d, 0ms);
}
:root[dir='rtl'].motion .quote::before { transform-origin: right; }
.motion .quote:not(.in)::before { transform: scaleX(0); }
.motion .quote--lead::before { display: none; }

/* ================================================================
   9. Visit: the map draws itself and the pin drops
   ================================================================ */
.motion .visit:not(.in) .fact { opacity: 0; translate: 0 1rem; }
.motion .fact { transition: opacity 0.6s ease-out, translate 0.7s var(--ease); }
.motion .fact:nth-child(2) { transition-delay: 70ms; }
.motion .fact:nth-child(3) { transition-delay: 140ms; }
.motion .fact:nth-child(4) { transition-delay: 210ms; }
.motion .fact:nth-child(5) { transition-delay: 280ms; }

.motion .mapdisc { transition: opacity 0.6s ease-out 0.2s, scale 0.9s var(--ease) 0.2s, rotate 0.9s var(--ease) 0.2s, transform 0.4s var(--ease); }
.motion .visit:not(.in) .mapdisc { opacity: 0; scale: 0.88; rotate: -12deg; }
.motion .mapdisc g path { stroke-dasharray: 1; transition: stroke-dashoffset 1.4s var(--ease) 0.35s; }
.motion .visit:not(.in) .mapdisc g path { stroke-dashoffset: 1; }
.motion .mapdisc .garden { transition: opacity 0.8s ease-out 0.9s; }
.motion .visit:not(.in) .mapdisc .garden { opacity: 0; }
.motion .mapdisc .pin { transition: translate 0.8s cubic-bezier(0.22, 1, 0.36, 1) 0.75s, opacity 0.25s ease-out 0.75s; }
.motion .visit:not(.in) .mapdisc .pin { opacity: 0; translate: 0 -4rem; }
.motion .ripple { display: inline; fill: none; stroke: oklch(0.98 0.02 92); stroke-width: 2; opacity: 0; transform-box: fill-box; transform-origin: center; }
.motion .visit.in .ripple { animation: ripple 2.8s var(--ease) 1.6s 6; }
.motion .visit.in .ripple + .ripple { animation-delay: 3s; }
@keyframes ripple { from { opacity: 0.75; scale: 0.3; } to { opacity: 0; scale: 2.8; } }

/* ---------------- Print: never hide content behind an animation ---------------- */
@media print {
  .item, .quote, .fact, .world__desc, .world__art, .world__cluster { opacity: 1 !important; translate: none !important; scale: none !important; animation: none !important; }
  .world__banner.reveal { clip-path: none !important; }
  .w > span { transform: none !important; }
  .marquee, .progress { display: none !important; }
}
