/* =====================================================================
   NinetyNine Regent Street: motion
   One considered entrance per page, two signature image reveals, gentle depth.
   Everything is gated on html.motion (JS running, no reduced-motion preference),
   so without JavaScript or with reduced motion the page is simply still.
   ===================================================================== */

/* ---------- Lenis ---------- */
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: clip; }

/* ---------- page to page: the old page dips into the new page's ground, then the new one arrives ----------
   The wordmark alone carries a transition name so it holds still. Never name .site-header: a named element
   becomes a backdrop root and its frosted ::after would have nothing to blur. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  ::view-transition { background: var(--ink); }
  html:not(.vt-from-dark):has(.hero-split.theme-light)::view-transition { background: var(--cream); }
  ::view-transition-old(root) { animation: nn-vt-out 0.24s var(--ease-io) both; }
  ::view-transition-new(root) { animation: nn-vt-in 0.4s var(--ease-out) 0.22s both; }
  .site-header .brand { view-transition-name: nn-brand; }
  ::view-transition-group(nn-brand) { animation-duration: 0.5s; animation-timing-function: var(--ease-out); }
}
@keyframes nn-vt-out { to { opacity: 0; } }
@keyframes nn-vt-in { from { opacity: 0; } }

/* =====================================================================
   HERO: the house lights come up
   ===================================================================== */
.hero-media::after, .hero-split .plate .frame::before { content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none; background: var(--ink); opacity: 0; }
html.motion .hero-media::after { animation: nn-lights 1.8s cubic-bezier(0.33, 0, 0.2, 1) both; animation-delay: var(--intro-delay, 0s); }
html.motion .hero-media img { animation: nn-settle 2.4s var(--ease-out) both; animation-delay: var(--intro-delay, 0s); }
/* the lights come up, and the split-hero curtain opens, only once the photograph has painted */
html.motion .hero-media:has(img:not(.is-ready))::after, html.motion .hero-media:has(img:not(.is-ready)) img,
html.motion .hero-split .plate:has(img:not(.is-ready)) .frame, html.motion .hero-split .plate:has(img:not(.is-ready)) img { animation-play-state: paused; }
@keyframes nn-lights { from { opacity: 0.6; } to { opacity: 0; } }
@keyframes nn-settle { from { transform: scale(1.06); } to { transform: none; } }

/* copy timeline: title lines, lede, actions, side panel */
html.motion .hero .lede, html.motion .hero-split .hero-copy > .prose, html.motion .hero-split .hero-copy > .lede { animation: nn-rise 0.9s var(--ease-out) both; animation-delay: calc(var(--intro-delay, 0s) + 0.85s); }
html.motion .hero .btn-row, html.motion .hero-split .hero-copy > .stack { animation: nn-fade 0.7s ease both; animation-delay: calc(var(--intro-delay, 0s) + 1s); }
html.motion .hero-side { animation: nn-fade 0.9s ease both; animation-delay: calc(var(--intro-delay, 0s) + 0.95s); }
/* phones and landscape: the split-hero prose box dissolves into the grid, so its paragraphs arrive on their own */
@media (max-width: 767.98px), (orientation: landscape) and (max-height: 500px) and (max-width: 1199.98px), (orientation: landscape) and (max-width: 1023.98px) {
  html.motion .hero-split .prose > p { animation: nn-rise 0.9s var(--ease-out) both; animation-delay: calc(var(--intro-delay, 0s) + 0.85s); }
  html.motion .hero-split .prose > p:not(.lede) { animation-delay: calc(var(--intro-delay, 0s) + 1.1s); }
}
/* later pages in the same visit: the same entrance, quicker, so moving around the house never feels slow */
html.motion.nn-quick .hero .lede, html.motion.nn-quick .hero-split .hero-copy > .prose, html.motion.nn-quick .hero-split .hero-copy > .lede, html.motion.nn-quick .hero-split .prose > p { animation-delay: 0.4s; }
html.motion.nn-quick .hero-split .prose > p:not(.lede) { animation-delay: 0.6s; }
html.motion.nn-quick .hero .btn-row, html.motion.nn-quick .hero-split .hero-copy > .stack { animation-delay: 0.5s; }
html.motion.nn-quick .hero-side { animation-delay: 0.45s; }
/* entrances start at 0.01, not 0: the same look, but Chrome still counts the text as painted (Largest Contentful Paint) */
@keyframes nn-fade { from { opacity: 0.01; } to { opacity: 1; } }
@keyframes nn-rise { from { opacity: 0.01; transform: translate3d(0, 8px, 0); } to { opacity: 1; transform: none; } }

/* split heroes: the photograph opens like a curtain */
html.motion .hero-split .plate .frame { animation: nn-curtain 1.4s var(--ease-curtain) both; animation-delay: calc(var(--intro-delay, 0s) + 0.1s); }
html.motion .hero-split .plate img { animation: nn-settle-lg 2.2s var(--ease-out) both; animation-delay: calc(var(--intro-delay, 0s) + 0.1s); }
/* when the intro curtain plays it already reveals the page: one wipe, not two in opposite directions */
html.intro-on .hero-split .plate .frame { animation: none; }
@keyframes nn-curtain { from { clip-path: inset(0 0 100% 0); } to { clip-path: inset(0 0 0 0); } }
@keyframes nn-settle-lg { from { transform: scale(1.1); } to { transform: none; } }

/* =====================================================================
   TITLES: the page title rises line by line; other titles simply arrive
   ===================================================================== */
/* the mask has room above and below the line, so tall capitals and descenders are never clipped */
.w { display: inline-block; overflow: hidden; vertical-align: top; padding-top: 0.12em; margin-top: -0.12em; padding-bottom: 0.14em; margin-bottom: -0.14em; padding-right: 0.03em; margin-right: -0.03em; }
.w > span { display: inline-block; }
html.motion .split .w > span { transform: translate3d(0, calc(100% + 0.32em), 0); transition: transform 1.1s var(--ease-out); transition-delay: calc(var(--base, 0s) + var(--i, 0) * 90ms); }
html.motion .split.is-in .w > span { transform: none; }
html.motion .hero .split, html.motion .hero-split .split { --base: calc(var(--intro-delay, 0s) + 0.45s); }
html.motion.nn-quick .hero .split, html.motion.nn-quick .hero-split .split { --base: 0.2s; }
html.motion .hero h1:not(.split), html.motion .hero-split h1:not(.split) { opacity: 0; }

html.motion .t-arrive { opacity: 0; transform: translate3d(0, 12px, 0); transition: opacity 0.9s ease, transform 1.1s var(--ease-out); }
html.motion .t-arrive.is-in { opacity: 1; transform: none; }
/* the page's one italic line gets its own moment: it comes into focus */
html.motion .t-focus { opacity: 0; filter: blur(6px); transition: opacity 1.4s ease, filter 1.6s var(--ease-out); }
html.motion .t-focus.is-in { opacity: 1; filter: none; }

/* =====================================================================
   IMAGES
   ===================================================================== */
/* default: opens from a slight inset and settles */
html.motion .frame.rv { clip-path: inset(5% 5% 5% 5%); transition: clip-path 1.3s var(--ease-io); }
html.motion .frame.rv img { transform: scale(1.06); transition: transform 1.8s var(--ease-out); }
/* the Club Room: the room opens out from the sun */
/* the iris opens from the sun in each photograph: --sun-x / --sun-y on the frame, -m variants for the phone crop */
.frame.rv-sun { --sx: var(--sun-x, 50%); --sy: var(--sun-y, 30%); }
@media (max-width: 639.98px) { .frame.rv-sun { --sx: var(--sun-x-m, var(--sun-x, 50%)); --sy: var(--sun-y-m, var(--sun-y, 30%)); } }
html.motion .frame.rv-sun { clip-path: circle(16% at var(--sx) var(--sy)); transition: clip-path 1.5s cubic-bezier(0.4, 0, 0.2, 1); }
html.motion .frame.rv-sun img { transform: scale(1.08); transition: transform 2.2s var(--ease-out); }
/* the Vinyl Room: a descent, top to bottom */
html.motion .frame.rv-down { clip-path: inset(0 0 100% 0); transition: clip-path 1.1s var(--ease); }
html.motion .frame.rv-down img { transform: translate3d(0, -8%, 0) scale(1.1); transition: transform 2s var(--ease-out); }
/* arrived */
html.motion .frame.rv.is-in, html.motion .frame.rv-down.is-in { clip-path: inset(0 0 0 0); }
html.motion .frame.rv-sun.is-in { clip-path: circle(90% at var(--sx) var(--sy)); }
html.motion .frame.is-in img { transform: none; }
html.motion .frame.is-done { clip-path: none !important; transition: none; }
/* small pictures just fade */
html.motion .rv-fade { opacity: 0; transition: opacity 0.9s ease; transition-delay: calc(var(--k, 0) * 80ms); }
html.motion .rv-fade.is-in { opacity: 1; }
/* the space panels keep their slow widen on hover alongside the fade */
html.motion .panel.rv-fade { transition: opacity 0.9s ease calc(var(--k, 0) * 80ms), flex-grow 0.8s var(--ease); }

/* hover: the room lights up a little under the cursor */
@media (hover: hover) and (pointer: fine) {
  .room-club .frame img, .room-vinyl .frame img, .pair .frame img, .sit .frame img, .strip .frame img, .close-door .frame img { transition: transform 1.2s var(--ease-out); }
  .room-club:hover .frame.is-done img, .room-vinyl .img:hover .frame.is-done img, .pair figure:hover .frame img, .sit article:hover .frame img, .strip figure:hover .frame img { transform: scale(1.03); }
  .panel .frame img { transition: transform 1.4s var(--ease-out); }
  .panel:hover .frame img { transform: scale(1.04); }
}

/* =====================================================================
   DEPTH (compositor scroll timelines where supported; JS fallback on desktop)
   ===================================================================== */
@supports (animation-timeline: view()) {
  @media (min-width: 900px) {
    html.motion .hero { view-timeline: --hero block; view-timeline-inset: 0; }
    html.motion .hero .hero-media { animation: nn-hero-drift linear both; animation-timeline: --hero; animation-range: exit 0% exit 100%; }
    /* clip, not hidden: a hidden frame becomes its own scroll container and view() would track it instead of the page */
    html.motion .frame.drift { overflow: clip; }
    html.motion .drift picture { scale: 1.1; animation: nn-plate-drift linear both; animation-timeline: view(); animation-range: cover 0% cover 100%; }
  }
}
@keyframes nn-hero-drift { to { translate: 0 14%; } }
/* the record label turns at 33 1/3 as it comes on screen, then runs down to rest upright: two turns in 4.5s, so nothing moves for longer than 5s */
html.motion .record.is-spinning .record-label { animation: nn-spin 4.5s linear both; }
@keyframes nn-spin { 0% { transform: rotate(0deg); } 60% { transform: rotate(540deg); animation-timing-function: cubic-bezier(0.3, 0.6, 0.6, 1); } 100% { transform: rotate(720deg); } }
@keyframes nn-plate-drift { from { translate: 0 -4%; } to { translate: 0 4%; } }

/* =====================================================================
   MENU: the curtain drops, the list rises
   ===================================================================== */
html.motion .site-menu nav li a span { transform: translate3d(0, calc(100% + 0.9rem), 0); transition: transform 0.8s var(--ease-out); }
html.motion.menu-open .site-menu nav li a span { transform: none; transition-duration: 0.6s; transition-delay: calc(0.1s + var(--i, 0) * 40ms); }
html.motion .site-menu .menu-foot { opacity: 0; transition: opacity 0.5s ease; }
html.motion.menu-open .site-menu .menu-foot { opacity: 1; transition-delay: 0.3s; }
@media (hover: hover) { html.motion.menu-open .site-menu nav a:hover span { transform: translateX(6px); transition-delay: 0s; } }

/* =====================================================================
   INTRO: the 99 mark, once per visit, on arrival from outside the site
   ===================================================================== */
.intro { display: none; }
html.intro-on { --intro-delay: 0.95s; }
html.intro-on .intro { display: grid; place-items: center; position: fixed; inset: 0; z-index: 200; pointer-events: none; background: var(--ink); color: var(--cream);
  animation: nn-intro-out 0.7s var(--ease-curtain) 0.9s forwards; }
html.intro-on .intro .mark-99 { width: clamp(4.5rem, 3.5rem + 4vw, 6.5rem); clip-path: inset(100% 0 0 0);
  animation: nn-intro-mark 0.7s var(--ease-out) 0.1s forwards, nn-intro-mark-out 0.45s var(--ease-io) 0.85s forwards; }
html.intro-skip .intro { animation: nn-intro-out 0.25s ease 0s forwards !important; }
@keyframes nn-intro-mark { to { clip-path: inset(0 0 0 0); } }
@keyframes nn-intro-mark-out { to { opacity: 0; transform: translate3d(0, -16px, 0); } }
@keyframes nn-intro-out { from { clip-path: inset(0 0 0 0); } to { clip-path: inset(0 0 100% 0); visibility: hidden; } }

/* phone home hub: the lockup surfaces, then each way in rises and its rule draws out from the centre */
html.motion .hub-brand { animation: nn-hub-brand 1.1s var(--ease-out) both; animation-delay: calc(var(--intro-delay, 0s) + 0.2s); }
html.motion .hub-nav li { animation: nn-rise 0.8s var(--ease-out) both; animation-delay: calc(var(--intro-delay, 0s) + 0.55s + var(--i, 0) * 0.07s); }
html.motion .hub-nav li + li::before { animation: nn-hub-rule 0.8s var(--ease-out) both; animation-delay: calc(var(--intro-delay, 0s) + 0.6s + var(--i, 0) * 0.07s); }
html.motion .hub-foot { animation: nn-fade 0.9s ease both; animation-delay: calc(var(--intro-delay, 0s) + 1.05s); }
html.motion.nn-quick .hub-brand { animation-delay: 0.05s; }
html.motion.nn-quick .hub-nav li { animation-delay: calc(0.2s + var(--i, 0) * 0.05s); }
html.motion.nn-quick .hub-nav li + li::before { animation-delay: calc(0.25s + var(--i, 0) * 0.05s); }
html.motion.nn-quick .hub-foot { animation-delay: 0.5s; }
/* the hub waits for the photograph, like the rest of the hero */
html.motion:not(.hub-go) .hero-home:has(.hero-media img:not(.is-ready)) :is(.hub-brand, .hub-nav li, .hub-foot),
html.motion:not(.hub-go) .hero-home:has(.hero-media img:not(.is-ready)) .hub-nav li + li::before { animation-play-state: paused; }
@keyframes nn-hub-brand { from { opacity: 0.01; transform: translate3d(0, 10px, 0); } to { opacity: 1; transform: none; } }
@keyframes nn-hub-rule { from { transform: scaleX(0); } to { transform: none; } }
@keyframes nn-hub-rule-v { from { transform: scaleY(0); } to { transform: none; } }
/* on landscape screens the rules stand upright between the ways in, so they draw from their middle upwards and down */
@media (orientation: landscape) { html.motion .hub-nav li + li::before { animation-name: nn-hub-rule-v; } }
@media (orientation: landscape) and (max-height: 500px) and (hover: none) and (pointer: coarse) { html.motion .hub-nav li + li::before { animation-name: nn-hub-rule; } }

/* footer: the 99 rises over its sun as the footer arrives */
html.motion .site-footer:not(.is-in) .footer-lockup .mark-99 { opacity: 0; transform: translate3d(0, 18px, 0); }
html.motion .site-footer:not(.is-in) .footer-glow { opacity: 0; transform: translateX(-50%) scale(0.8); }
html.motion .footer-lockup .mark-99 { transition: opacity 0.9s ease, transform 1.1s var(--ease-out); }
html.motion .footer-glow { transition: opacity 1.4s ease, transform 1.6s var(--ease-out); }

/* privacy: each topic slides open where the browser can animate to its natural height (Chromium); others open at once */
@supports (interpolate-size: allow-keywords) {
  html.motion .pv { interpolate-size: allow-keywords; }
  html.motion .pv::details-content { block-size: 0; overflow-y: clip; transition: block-size 0.5s var(--ease-io), content-visibility 0.5s allow-discrete; }
  html.motion .pv[open]::details-content { block-size: auto; }
  /* the topic that closes as another opens shuts at once, so the tapped title can stay put */
  html.motion .pv.pv-snap::details-content { transition: none; }
}
