/* Hyde Park v2: the motion core's styles. Load before site.css. API and contract: notes/motion.md. */

:root {
  --hp-ease-cine: cubic-bezier(.7, 0, .18, 1);
  --hp-ease-settle: cubic-bezier(.16, 1, .3, 1);
  /* The rhythm, in film frames: notes/motion.md, "Rhythm". */
  --hp-t-touch: .35s; --hp-t-quick: .6s; --hp-t-move: 1.2s; --hp-t-scene: 1.6s; --hp-t-breath: 2.4s;
  --hp-grain-opacity: .14;
  --hp-grain-tone: .55;
  --hp-grain-size: 220px;
  --hp-blur-h: clamp(44px, 7vh, 90px);
  --hp-rule: rgb(239 237 232 / .9);
}

/* ——— Lenis ———
   The page keeps its scrollbar while a change of page holds the scroll: hiding it (Lenis's own overflow: clip)
   widened the page by the scrollbar and moved the nav 13px on every change. The gutter stays for short pages too. */
html { scrollbar-gutter: stable; }
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-smooth iframe { pointer-events: none; }

/* ——— Split text that never clips: each mask carries room for ascenders, descenders and tight tracking ——— */
.wd-mask {
  display: inline-block;
  padding: .16em .08em .2em;
  margin: -.16em -.08em -.2em;
}
/* Line masks clip with clip-path, not padding: stacked blocks collapse their negative margins, so each split line
   stood .16em taller than set and pushed the page down until the split reverted (the /company quote). */
.ln-mask { overflow: visible !important; clip-path: inset(-.16em -.08em -.2em -.6em); }
/* A hanging quote (negative text-indent) belongs to the first line only. Split lines are blocks, so each would
   inherit the indent; later lines drop it, and every mask has room to the left. */
.ln-mask ~ .ln-mask, .ln-line ~ .ln-line { text-indent: 0; }
/* Words never carry it. SplitText finds lines with each word as an inline-block, which would inherit the indent and
   come out narrower than set, so more words fit a split line than fit the page (the /company quote re-wrapped). */
.hp-split :not(.ln-mask, .ln, .ln-line) { text-indent: 0; }

/* ——— Reveals: hidden only with JavaScript running and motion allowed; the final state is the default ——— */
@media (prefers-reduced-motion: no-preference) {
  html.js [data-reveal="fade"]:not([data-revealed]) { opacity: 0; }
  html.js [data-reveal="lines"]:not([data-revealed]),
  html.js [data-reveal="words"]:not([data-revealed]) { visibility: hidden; }
  html.js [data-reveal="media"]:not([data-revealed]) { clip-path: inset(100% 0 0 0); }
  html.js [data-reveal="letterbox"]:not([data-revealed]) { clip-path: inset(50% 0 50% 0); }
  html.js [data-reveal="rule"]:not([data-revealed]) { transform: scaleX(0); }
  html.js [data-reveal="bridge"]:not([data-revealed]) { clip-path: inset(0 50% 0 50%); }
  /* If the core never boots, nothing stays hidden. */
  html.js:not(.hp-booted) [data-reveal] { animation: hp-failsafe 0s 3.5s forwards; }
}
[data-reveal="rule"] { transform-origin: 0 50%; }
[data-reveal="rule"][data-reveal-origin="center"] { transform-origin: 50% 50%; }
[data-reveal="rule"][data-reveal-origin="right"] { transform-origin: 100% 50%; }
@keyframes hp-failsafe { to { opacity: 1; visibility: visible; clip-path: none; transform: none; } }

/* Media frames clip their contents so parallax and reveal scale stay inside. */
[data-reveal="media"], [data-reveal="letterbox"], [data-parallax] { overflow: hidden; }
[data-parallax] > img, [data-parallax] > video, [data-parallax] > picture { will-change: transform; }

/* ——— Film grain: two tiles of different sizes, moved to new random places 24 times a second (composited only).
   The wrapper adds no box of its own, so the tone layer blends with the page itself. ——— */
.hp-grain { display: contents; }
/* Grain sized per screen: one grain is one pixel on a standard screen (smaller turns to mush, so it is softened
   instead), about 1.7 pixels on a 2x screen and 2.1 on a 3x phone, so it reads the same at each one's distance. */
@media (max-resolution: 1.4dppx) { :root { --hp-grain-size: 256px; --hp-grain-tone: .4; --hp-grain-opacity: .11; } }
@media (min-resolution: 2.5dppx) { :root { --hp-grain-size: 190px; } }
/* Lighter on phones, which sit closer to the eye. */
@media (pointer: coarse) { :root { --hp-grain-tone: .42; --hp-grain-opacity: .105; } }
/* Felt, not seen: it recedes while a paragraph sits in the middle of the screen, and further over stills and film
   that carry their own grain (--hp-grain-place, set by the core). A reviewer can step through levels with
   ?grain=0..3 (--hp-grain-k). */
.hp-grain > i {
  position: fixed; z-index: 100; pointer-events: none;
  inset: calc(var(--s) * -1);
  background-size: var(--s) var(--s);
  will-change: transform;
  transition: opacity var(--hp-t-move) linear;
}
.hp-grain__tone {
  --s: var(--hp-grain-size);
  background-image: var(--hp-grain-tone-tile, none);
  mix-blend-mode: soft-light;
  opacity: calc(var(--hp-grain-tone) * var(--hp-grain-k, 1) * var(--hp-grain-place, 1));
}
.hp-grain__fleck {
  --s: calc(var(--hp-grain-size) * 1.551);
  background-image: var(--hp-grain-fleck-tile, none);
  mix-blend-mode: soft-light;
  opacity: calc(var(--hp-grain-opacity) * var(--hp-grain-k, 1) * var(--hp-grain-place, 1));
}
.hp-grain.is-plain > .hp-grain__tone { display: none; }
.hp-grain.is-plain > .hp-grain__fleck { opacity: calc(var(--hp-grain-opacity) * 1.25 * var(--hp-grain-k, 1) * var(--hp-grain-place, 1)); }
.hp-grain-level {
  position: fixed; left: 16px; bottom: 16px; z-index: 101; margin: 0; padding: 8px 10px 7px;
  background: #efede8; color: #070707; font: 600 11px/1 var(--font, system-ui, sans-serif); letter-spacing: .08em; text-transform: uppercase;
  pointer-events: none; opacity: 0; transition: opacity var(--hp-t-touch) linear;
}
.hp-grain-level.is-on { opacity: 1; }
.hp-grain[hidden] { display: none; }

/* ——— Progressive blur along the bottom edge: stacked backdrop blurs, each masked to a band ——— */
.hp-blur {
  position: fixed; inset: auto 0 0; z-index: 40;
  height: var(--hp-blur-h);
  pointer-events: none;
  will-change: transform;
}
.hp-blur > i {
  position: absolute; inset: 0;
  -webkit-backdrop-filter: blur(var(--b));
  backdrop-filter: blur(var(--b));
  -webkit-mask-image: linear-gradient(to bottom, transparent var(--m0), #000 var(--m1), #000 var(--m2), transparent var(--m3));
  mask-image: linear-gradient(to bottom, transparent var(--m0), #000 var(--m1), #000 var(--m2), transparent var(--m3));
}
/* A breath of the page's own colour under the blur, so text at the edge recedes rather than smears. */
.hp-blur::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to bottom, transparent, color-mix(in srgb, var(--tint, #070707) 34%, transparent));
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hp-blur > i { display: none; }
  .hp-blur::after { background: linear-gradient(to bottom, transparent, color-mix(in srgb, var(--tint, #070707) 70%, transparent)); }
}
.hp-blur[hidden] { display: none; }

/* ——— The cut: the page closes to their logo's rule, and the rule opens on the next page ———
   It plays under the header (z-index 90), so the nav stays put through every change of page; only the page moves. */
.hp-cut {
  position: fixed; inset: 0; z-index: 85;
  pointer-events: none; visibility: hidden;
}
.hp-cut.is-on { visibility: visible; pointer-events: auto; cursor: progress; }
.hp-cut__bar {
  position: absolute; left: 0; right: 0; height: 50.5%;
  background: var(--hp-cut-bg, var(--tint, #070707));
  will-change: transform;
}
.hp-cut__bar--top { top: 0; transform-origin: 50% 0; }
.hp-cut__bar--bottom { bottom: 0; transform-origin: 50% 100%; }
.hp-cut__rule {
  position: absolute; left: 0; right: 0; top: 50%;
  height: 1px; margin-top: -.5px;
  background: var(--hp-rule);
  transform: scaleX(0); transform-origin: 0 50%;
  will-change: transform;
}
/* The shared still that carries a film from its card into its page. */
.hp-carry {
  position: fixed; z-index: 86; margin: 0;
  pointer-events: none; overflow: hidden;
  will-change: transform;
}
.hp-carry > img { width: 100%; height: 100%; object-fit: cover; display: block; will-change: transform; }

/* ——— The ground (hp-motion.js, setTint and HP.ground): the page's tint changes like light in a room, on one
   timing for every layer that paints it (a scene, on the camera curve), by background-color only. A cut (a change
   of page behind the bars, or arriving) holds the transition off for that one change. ——— */
:root { --hp-ground: var(--hp-t-scene) var(--hp-ease-cine); }
body { transition: background-color var(--hp-ground); }
html.hp-tint-cut body { transition: none; }

/* ——— The cursor (hp-motion.js): a point of light, a ring over links, a label over films. Fine pointers only. ——— */
html.hp-cursor-on, html.hp-cursor-on * { cursor: none !important; }
html.hp-cursor-on :is(input, textarea, select, [contenteditable]:not([contenteditable="false"])) { cursor: auto !important; }
.hp-cursor {
  position: fixed; left: 0; top: 0; z-index: 97;
  pointer-events: none;
  opacity: 0; transition: opacity var(--hp-t-touch) linear;
}
.hp-cursor.is-on { opacity: 1; }
.hp-cursor[data-mode="type"] { opacity: 0; }
.hp-cursor__dot { position: absolute; left: -4px; top: -4px; width: 8px; height: 8px; will-change: transform; }
.hp-cursor__dot b, .hp-cursor__dot::after {
  content: ""; position: absolute; border-radius: 50%;
  transition: transform var(--hp-t-touch) var(--hp-ease-settle), opacity var(--hp-t-touch) linear;
}
.hp-cursor__dot b { inset: 0; background: #efede8; box-shadow: 0 0 0 .5px rgb(7 7 7 / .35), 0 0 12px rgb(239 237 232 / .2); }
.hp-cursor__dot::after {
  left: -9px; top: -9px; width: 26px; height: 26px;
  box-shadow: inset 0 0 0 1px rgb(239 237 232 / .85), 0 0 0 .5px rgb(7 7 7 / .3);
  transform: scale(.3); opacity: 0;
}
.hp-cursor[data-mode="link"] .hp-cursor__dot b { transform: scale(.45); }
.hp-cursor[data-mode="link"] .hp-cursor__dot::after { transform: scale(1); opacity: 1; }
.hp-cursor[data-mode="label"] .hp-cursor__dot b { transform: scale(.75); }
.hp-cursor.is-down .hp-cursor__dot b { transform: scale(.7); }
.hp-cursor.is-down[data-mode="link"] .hp-cursor__dot::after { transform: scale(.82); }
.hp-cursor__label {
  position: absolute; left: 0; top: 0; padding: 9px 12px 8px;
  background: #efede8; color: #070707;
  font: 600 11px/1 var(--font, system-ui, sans-serif); letter-spacing: .08em; text-transform: uppercase; white-space: nowrap;
  /* Hung just off the point, so it never covers the title being pointed at. */
  transform-origin: 0 0; transform: translate(14px, 12px) scale(.3); opacity: 0;
  transition: transform var(--hp-t-touch) var(--hp-ease-settle), opacity .2s linear;
}
.hp-cursor[data-mode="label"] .hp-cursor__label { transform: translate(14px, 12px) scale(1); opacity: 1; }
/* Reduced motion: no point of light; the label sits beside the system cursor and only fades. */
.hp-cursor--label-only .hp-cursor__dot { display: none; }
.hp-cursor--label-only .hp-cursor__label, .hp-cursor--label-only[data-mode="label"] .hp-cursor__label { transform: translate(14px, 14px); transition: opacity .2s linear; }

/* ——— The peek: the picture that follows the pointer over a list of films (hp-gl.js). Fine pointers only. ——— */
.hp-peek { position: fixed; inset: 0; z-index: 70; pointer-events: none; visibility: hidden; }
.hp-peek.is-on { visibility: visible; }
.hp-peek__card { position: absolute; left: 0; top: 0; will-change: transform; }
.hp-peek--gl .hp-peek__card { transform: none; }
.hp-peek__gl { position: fixed; left: 0; top: 0; display: block; max-width: none; will-change: transform; }
.hp-peek:not(.hp-peek--gl) .hp-peek__card { overflow: hidden; background: #141414; box-shadow: 0 30px 60px -30px rgb(0 0 0 / .6); }
.hp-peek__card img { display: block; width: 100%; height: 100%; object-fit: cover; }
.hp-peek__cap {
  position: absolute; left: 0; top: 0; margin: 0;
  font: 500 13px/1.3 var(--font, system-ui, sans-serif); letter-spacing: -.004em;
  color: var(--fg, #efede8); white-space: nowrap; opacity: 0;
}
.hp-peek__year { color: var(--fg-3, rgb(239 237 232 / .42)); font-variant-numeric: tabular-nums; margin-left: .35em; }
.hp-peek__cap--rich { white-space: normal; }

/* ——— Link lines: the rule draws in under a link and leaves to the right ——— */
.hp-line { position: relative; text-decoration: none; }
.hp-line::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -.18em; height: 1px;
  background: currentColor;
  transform: scaleX(0); transform-origin: 100% 50%;
  transition: transform var(--hp-t-quick) var(--hp-ease-settle);
}
@media (hover: hover) {
  .hp-line:hover::after { transform: scaleX(1); transform-origin: 0 50%; }
}
.hp-line:focus-visible::after, .hp-line[aria-current="page"]::after { transform: scaleX(1); transform-origin: 0 50%; }

/* ——— WebGL media: the DOM image stays for layout, alt text and fallback; the canvas shows it ——— */
.hp-gl-on { position: relative; }
.hp-gl-on > canvas.hp-gl-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; pointer-events: none; }
.hp-gl-on.hp-gl-live > img, .hp-gl-on.hp-gl-live > picture, .hp-gl-on.hp-gl-live > video { opacity: 0; }

main[tabindex="-1"]:focus { outline: none; }

/* Live region the router uses to announce the new page. */
.hp-sr { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }

@media (prefers-reduced-motion: reduce) {
  .hp-grain > i { will-change: auto; }
  .hp-line::after { transition: none; }
}
@media print {
  .hp-grain, .hp-blur, .hp-cut, .hp-cursor, .hp-peek { display: none !important; }
}
