/* =============================================================
   ALTYNAI ZHORABAEVA — visual system
   Palette: white / ivory / ice-blue / cold blue / silver + gold glow
   ============================================================= */

@import url('https://fonts.googleapis.com/css2?family=Inter+Tight:wght@300;400;500;600;700&display=swap');

:root {
  /* surfaces — warm ivory rather than cool white */
  --white: #FFFFFF;
  --ivory: #FFF9F6;
  --paper: #FFF4EF;
  --cream: #FFEFE8;
  --mist:  #F8E2DA;
  --silver:#E9D3CB;

  /* ink — a deep warm black, never blue-black */
  --ink:    #181313;
  --ink-70: rgba(24, 19, 19, .74);
  --ink-45: rgba(24, 19, 19, .58);
  --ink-25: rgba(24, 19, 19, .30);
  --ink-10: rgba(24, 19, 19, .10);

  /* red — the accent, used sparingly; the page stays light */
  --crimson:      #C91F37;
  --crimson-deep: #A81830;
  --poppy:        #E33A3A;
  --scarlet:      #EF4B4B;
  --wine:         #7D1F32;
  --wine-deep:    #681B2B;
  --blush:        #FFF1EB;

  /* a warm metal that belongs with crimson, kept for the honours */
  --gold: #C79A55;
  --gold-soft: rgba(199, 154, 85, .35);

  /* glass — warm translucency with the faintest red refraction */
  --glass:        rgba(255, 252, 250, .58);
  --glass-strong: rgba(255, 252, 250, .80);
  --glass-line:   rgba(255, 255, 255, .72);
  --glass-edge:   rgba(24, 19, 19, .07);
  --glass-tint:   rgba(201, 31, 55, .05);

  /* shadows — warmed, so nothing casts a blue-grey */
  --sh-s: 0 2px 10px rgba(72, 26, 34, .06);
  --sh-m: 0 18px 50px -20px rgba(72, 26, 34, .20), 0 2px 8px rgba(72, 26, 34, .05);
  --sh-l: 0 60px 120px -40px rgba(72, 26, 34, .32), 0 8px 30px rgba(72, 26, 34, .07);
  --sh-glow: 0 0 90px rgba(201, 31, 55, .28);

  /* rhythm */
  --pad: clamp(20px, 5vw, 84px);
  --gap: clamp(56px, 12vh, 180px);
  --r-s: 14px;
  --r-m: 24px;
  --r-l: 34px;
  --r-xl: 46px;

  --ease: cubic-bezier(.22, 1, .36, 1);
  --ease-soft: cubic-bezier(.33, 1, .68, 1);
}


* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* clip (not hidden) — `hidden` would turn the root into a scroll
   container and kill every position:sticky in the stack */
html { -webkit-text-size-adjust: 100%; overflow-x: clip; }

body {
  font-family: 'Inter Tight', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-weight: 400;
  color: var(--ink);
  background: var(--paper);
  overflow-x: clip;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  letter-spacing: -0.02em;
}


img, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
::selection { background: var(--poppy); color: #fff; }

/* soft ambient light for the whole document */
body::before {
  content: '';
  position: fixed; inset: 0;
  pointer-events: none; z-index: 0;
  background:
    radial-gradient(90vw 70vh at 12% -10%, rgba(255, 255, 255, .95), transparent 60%),
    radial-gradient(70vw 60vh at 100% 8%, rgba(201, 31, 55, .26), transparent 62%),
    radial-gradient(58vw 48vh at 6% 72%, rgba(201, 31, 55, .16), transparent 62%),
    radial-gradient(60vw 50vh at 50% 105%, rgba(199, 154, 85, .13), transparent 60%);
}

/* =============================================================
   PLACEHOLDER FRAME — stands in until real photography is uploaded
   ============================================================= */
.frame {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  background:
    linear-gradient(152deg, #FFF6F2 0%, #FCE3DB 52%, #FFF1EA 100%);
  overflow: hidden;
}
.frame::before {
  content: '';
  position: absolute; inset: clamp(7px, 4%, 16px);
  border: 1px solid rgba(201, 31, 55, .38);
  border-radius: inherit;
}
.frame::after {
  content: '';
  position: absolute; inset: 0;
  background: repeating-linear-gradient(58deg,
    rgba(201, 31, 55, .075) 0 1px, transparent 1px 13px);
}
.frame span {
  position: relative; z-index: 1;
  font-size: 10px; font-weight: 500; letter-spacing: .3em; text-transform: uppercase;
  color: rgba(125, 31, 50, .58);
  padding: 6px 12px; border-radius: 999px;
  background: rgba(255, 255, 255, .62);
  border: 1px solid rgba(201, 31, 55, .22);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
}
/* on the deep-blue archive panel the frame flips to a night version */
.frame.dark { background: linear-gradient(152deg, #8E2033 0%, #5C1626 55%, #94233A 100%); }
.frame.dark::before { border-color: rgba(255, 255, 255, .30); }
.frame.dark::after { background: repeating-linear-gradient(58deg, rgba(255,255,255,.07) 0 1px, transparent 1px 13px); }
.frame.dark span { color: rgba(255, 255, 255, .78); background: rgba(255, 255, 255, .12); border-color: rgba(255, 255, 255, .26); }

/* ---------- typography scale ---------- */
.eyebrow {
  font-size: clamp(10px, .78vw, 12px);
  font-weight: 500;
  letter-spacing: .28em;
  text-transform: uppercase;
  color: var(--ink-45);
}
.display {
  font-size: clamp(44px, 8.4vw, 148px);
  font-weight: 500;
  line-height: .92;
  letter-spacing: -0.045em;
}
.h2 {
  font-size: clamp(30px, 4.6vw, 76px);
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.04em;
}
.h3 {
  font-size: clamp(19px, 1.7vw, 26px);
  font-weight: 500;
  letter-spacing: -0.03em;
}
.body {
  font-size: clamp(15px, 1.15vw, 18px);
  line-height: 1.62;
  color: var(--ink-70);
  font-weight: 400;
}
.mono {
  font-size: 11px; letter-spacing: .22em; text-transform: uppercase; font-weight: 500;
}

/* reveal-on-scroll primitive */
[data-reveal] {
  opacity: 0;
  transform: translate3d(0, 26px, 0);
  transition: opacity 1.1s var(--ease), transform 1.25s var(--ease);
  transition-delay: var(--d, 0s);
}
[data-reveal].in { opacity: 1; transform: none; }

/* The mask has to clip vertically for the slide-up reveal, but at
   line-height .92 it was also shaving ~15px off the ink top and bottom.
   Latin and Cyrillic without descenders got away with it; Kazakh did not —
   the tail of "ң" was cut, leaving what looked like "н". The padding widens
   the clip box and the negative margin puts the layout back where it was. */
.split-line {
  display: block;
  overflow: hidden;
  padding-block: .26em;
  margin-block: -.26em;
}
.split-line > span {
  display: block;
  transform: translateY(135%);
  transition: transform 1.25s var(--ease);
  transition-delay: var(--d, 0s);
}
.in .split-line > span, .split-line.in > span { transform: none; }

/* =============================================================
   NAV — glass pill
   ============================================================= */
.nav {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  display: flex; align-items: center; justify-content: space-between;
  padding: 18px var(--pad);
  transition: transform .8s var(--ease), opacity .6s var(--ease);
  mix-blend-mode: normal;
}
.nav.hidden { transform: translateY(-140%); opacity: 0; pointer-events: none; }

.brand {
  font-size: clamp(12px, 1vw, 14px); font-weight: 600;
  letter-spacing: .16em; text-transform: uppercase;
  display: flex; align-items: center; gap: 10px;
}
.brand .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--crimson); box-shadow: 0 0 14px var(--crimson);
}

.nav-links {
  position: absolute; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 4px;
  padding: 6px;
  border-radius: 999px;
  background: var(--glass);
  border: 1px solid var(--glass-line);
  box-shadow: var(--sh-s), inset 0 1px 0 rgba(255,255,255,.9);
  backdrop-filter: blur(14px) saturate(180%);
  -webkit-backdrop-filter: blur(14px) saturate(180%);
}
.nav-links a {
  padding: 8px 16px; border-radius: 999px;
  font-size: 12.5px; font-weight: 500; letter-spacing: -0.01em;
  color: var(--ink-70);
  transition: color .4s var(--ease), background .4s var(--ease);
}
.nav-links a:hover { color: var(--ink); background: rgba(255,255,255,.8); }

.nav-right { display: flex; align-items: center; gap: 10px; }

/* language switcher */
.lang {
  display: flex; align-items: center; gap: 2px; padding: 5px;
  border-radius: 999px; position: relative;
  background: var(--glass);
  border: 1px solid var(--glass-line);
  box-shadow: var(--sh-s), inset 0 1px 0 rgba(255,255,255,.9);
  backdrop-filter: blur(14px) saturate(180%);
  -webkit-backdrop-filter: blur(14px) saturate(180%);
}
.lang .thumb {
  position: absolute; top: 5px; left: 5px; height: calc(100% - 10px);
  border-radius: 999px; background: var(--ink);
  transition: transform .65s var(--ease), width .65s var(--ease);
  z-index: 0;
}
.lang .lang-seg {
  position: relative; z-index: 1;
  padding: 7px 13px; border-radius: 999px;
  font-size: 11px; font-weight: 600; letter-spacing: .1em;
  color: var(--ink-45); transition: color .45s var(--ease);
}
.lang .lang-seg.active { color: #fff; }

/* the single cycling button — phones only */
.lang-cycle { display: none; }

@media (max-width: 900px) {
  /* three segments eat the whole bar on a phone; collapse to one control
     that steps ҚАЗ → РУС → ENG on each tap */
  .lang { padding: 0; background: none; border: 0; box-shadow: none; }
  .lang .lang-seg, .lang .thumb { display: none; }

  .lang-cycle {
    display: flex; align-items: center; gap: 7px;
    padding: 9px 15px 9px 12px; border-radius: 999px;
    background: var(--ink); color: #fff;
    box-shadow: 0 8px 22px -10px rgba(24, 19, 19, .65);
    transition: transform .45s var(--ease), background .45s var(--ease);
  }
  .lang-cycle:active { transform: scale(.94); background: var(--crimson-deep); }
  .lang-cycle svg { width: 15px; height: 15px; opacity: .8; flex: none; }
  .lang-cycle .lang-now {
    font-size: 11.5px; font-weight: 600; letter-spacing: .1em;
    min-width: 3.1em; text-align: left;
    font-variant-numeric: tabular-nums;
  }
  /* the label rolls over as the language steps */
  .lang-cycle.flip .lang-now { animation: lang-roll .5s var(--ease); }
  .lang-cycle.flip svg { animation: lang-spin .6s var(--ease); }
}
@keyframes lang-roll {
  0%   { opacity: 0; transform: translateY(9px); }
  100% { opacity: 1; transform: none; }
}
@keyframes lang-spin {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(180deg); }
}

.nav-cta {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 10px 20px; border-radius: 999px;
  font-size: 12px; font-weight: 500; white-space: nowrap;
  background: var(--ink); color: #fff;
  transition: transform .5s var(--ease), box-shadow .5s var(--ease), padding .4s var(--ease);
  box-shadow: 0 8px 24px -10px rgba(24, 19, 19,.6);
}
.nav-cta svg { width: 15px; height: 15px; fill: currentColor; display: none; }
.nav-cta:hover { transform: translateY(-2px); background: var(--crimson-deep); box-shadow: 0 14px 34px -12px rgba(168, 24, 48,.55); }

/* =============================================================
   LOADING SCREEN
   Covers the finished page while the portrait decodes, then lifts. The
   site underneath is already laid out, so nothing assembles on reveal.
   ============================================================= */
body.boot-lock { overflow: hidden; }

#boot {
  position: fixed; inset: 0; z-index: 120;
  background: linear-gradient(168deg, #FFFCFA 0%, #FFF3EE 55%, #FFEEE6 100%);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 26px;
  transition: opacity .55s var(--ease);
}
#boot.gone { opacity: 0; pointer-events: none; }

.boot-mark {
  display: flex; align-items: center; gap: 11px;
  font-size: clamp(11px, 1vw, 13px); font-weight: 600;
  letter-spacing: .2em; text-transform: uppercase; color: var(--ink);
  opacity: 0; animation: boot-in .9s var(--ease) .1s forwards;
}
.boot-mark .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--crimson); box-shadow: 0 0 16px var(--crimson);
}
@keyframes boot-in { from { opacity: 0; transform: translateY(9px); } to { opacity: 1; transform: none; } }

.boot-meter {
  width: min(220px, 46vw); height: 1px;
  background: rgba(24, 19, 19, .10);
  overflow: hidden;
  opacity: 0; animation: boot-in .9s var(--ease) .25s forwards;
}
.boot-meter i {
  display: block; width: 100%; height: 100%;
  background: linear-gradient(90deg, var(--crimson), var(--crimson-deep));
  transform: scaleX(0); transform-origin: 0 50%;
}
.boot-pct {
  font-size: 10px; letter-spacing: .3em; color: var(--ink-25);
  font-variant-numeric: tabular-nums;
  opacity: 0; animation: boot-in .9s var(--ease) .35s forwards;
}

/* The nav waits for the reveal so it never sits on top of the loader.
   Driven by a body class the loader sets rather than a bare `.nav` rule, so
   the rule cannot strand the nav on a page that never runs a loader. */
body.nav-hold .nav { opacity: 0; pointer-events: none; }

/* =============================================================
   BIOGRAPHY — a scroll-read, not an "about" block

   Layering is fixed and deliberate, because the first build let the
   hero's poppies sit on top of the copy:
     0  section ground
     1  the watermark year
     2  background poppies
     5  the timeline
     10 headings and text
   Nothing decorative is ever allowed above 2.
   ============================================================= */
#story { z-index: 5; }

/* Deliberately NOT a .stack section. The stack pins its members at 100vh and
   clips them, which flattened the 600svh scroll track inside to a single
   screen — that is what put two chapters on the page at once. This one runs
   at its natural height and does its pinning internally. */
.story {
  position: relative;
  z-index: 5;
  background: linear-gradient(180deg, var(--ivory) 0%, var(--paper) 46%, var(--ivory) 100%);
  padding: 0 var(--pad);
}
.story-bg {
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background:
    radial-gradient(58vw 46vh at 82% 10%, rgba(201, 31, 55, .055), transparent 62%),
    radial-gradient(46vw 40vh at 4% 78%, rgba(125, 31, 50, .04), transparent 64%);
}

/* the year, set enormous and almost invisible — a watermark, not a heading */
.story-ghost {
  position: absolute; z-index: 1; right: -.04em; top: 26vh;
  font-size: clamp(150px, 26vw, 460px);
  font-weight: 600; letter-spacing: -.06em; line-height: .8;
  color: rgba(201, 31, 55, .05);
  pointer-events: none; user-select: none;
  transform: scale(.97);
  transition: opacity 1.1s var(--ease), transform 1.4s var(--ease);
}

/* ---------- background poppies ---------- */
.story-poppies { position: absolute; inset: 0; z-index: 2; pointer-events: none; overflow: clip; }
.sp {
  position: absolute;
  opacity: 0;                       /* raised per chapter, never above .24 */
  transition: opacity 1.2s var(--ease);
  will-change: transform;
}
.sp svg { display: block; width: 100%; height: 100%; overflow: visible; }

/* ---------- the opening ---------- */
.story-open {
  position: relative; z-index: 10;
  padding: clamp(96px, 17vh, 210px) 0 clamp(60px, 10vh, 130px);
}
.story-name {
  font-size: clamp(3rem, 6vw, 7rem);
  font-weight: 500; line-height: .92; letter-spacing: -.05em;
  color: var(--ink);
}
.story-name .split-line + .split-line { color: var(--crimson); }
.story-label { color: var(--ink-45); margin-top: clamp(16px, 3vh, 30px); }

/* ---------- the pinned stage ---------- */
.story-track { position: relative; z-index: 10; }
.story-stage {
  position: sticky; top: 0;
  height: 100svh;
  display: grid; grid-template-columns: 104px minmax(0, 1fr);
  gap: clamp(18px, 3vw, 56px);
  align-items: center;
  /* the nav is fixed over the page; nothing may sit underneath it */
  padding: clamp(96px, 13vh, 150px) 0 clamp(40px, 8vh, 90px);
}

/* ---------- timeline ---------- */
.story-rail { position: relative; z-index: 5; align-self: center; height: min(58vh, 460px); }
.rail-line {
  position: absolute; left: 11px; top: 2px; bottom: 2px; width: 1px;
  background: rgba(24, 19, 19, .13);
}
.rail-line i {
  position: absolute; left: 0; top: 0; width: 100%; height: 0;
  background: linear-gradient(180deg, var(--crimson), var(--wine));
  transition: height .2s linear;
}
.rail-years {
  list-style: none; height: 100%;
  display: flex; flex-direction: column; justify-content: space-between;
}
.rail-years li {
  position: relative; padding-left: 26px;
  font-size: 11px; letter-spacing: .14em; font-variant-numeric: tabular-nums;
  color: var(--ink); opacity: .25;
  transition: opacity .5s var(--ease), color .5s var(--ease), transform .5s var(--ease);
}
.rail-years li::before {
  content: ''; position: absolute; left: 8px; top: 50%; width: 7px; height: 7px;
  margin-top: -3.5px; border-radius: 50%; background: var(--silver);
  transition: background .5s var(--ease), transform .5s var(--ease);
}
.rail-years li.on { opacity: 1; color: var(--crimson); transform: translateX(3px); }
.rail-years li.on::before { background: var(--crimson); transform: scale(1.35); }

/* ---------- chapters, stacked and cross-faded ---------- */
.story-chapters { position: relative; z-index: 10; height: 100%; }
.chapter {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; justify-content: center;
  /* the safe reading zone: copy stays left of centre, poppies keep to the
     edges, so nothing decorative ever lands under a line of text */
  max-width: min(62ch, 46vw);
  opacity: 0;
  transform: translateY(40px);
  filter: blur(8px);
  pointer-events: none;
  transition: opacity .5s var(--ease), transform .6s var(--ease), filter .5s var(--ease);
}
.chapter.on { opacity: 1; transform: none; filter: none; pointer-events: auto; }
.chapter.out { opacity: 0; transform: translateY(-30px); filter: blur(6px); }

.ch-head { display: flex; align-items: baseline; gap: 18px; flex-wrap: wrap; margin-bottom: clamp(14px, 2.4vh, 28px); }
.ch-no { font-size: 12px; letter-spacing: .28em; color: var(--crimson); font-variant-numeric: tabular-nums; }
.ch-period { font-size: 12px; letter-spacing: .2em; color: var(--ink-45); text-transform: uppercase; }
.ch-title {
  flex: 1 1 100%;
  font-size: clamp(2.1rem, 4.4vw, 4.4rem);
  font-weight: 500; line-height: 1.03; letter-spacing: -.04em;
  color: var(--ink);
}
.ch-title em { font-style: normal; color: var(--crimson); }
.ch-text {
  font-size: clamp(1.05rem, 1.45vw, 1.65rem);
  line-height: 1.5;
  color: rgba(24, 19, 19, .62);
  max-width: 60ch;
}
.ch-text b { font-weight: 500; color: var(--ink); }

/* ---------- archival prints in a chapter ----------
   The photographs enter and leave with the chapter that owns them, but on
   their own curve: the outgoing frame settles back and tips one way, the
   incoming one arrives from the other. Because both states are written
   against `.chapter.on` / `.chapter.out`, they are driven by the same scroll
   position as the text and cannot drift out of step with it. */
.ch-shots {
  display: flex; gap: clamp(12px, 1.8vw, 26px);
  margin-top: clamp(20px, 3.4vh, 40px);
}
.ch-shot {
  overflow: hidden;
  opacity: 0;
  transform: scale(.94) rotate(2deg);
  transition: opacity .62s var(--ease), transform .78s var(--ease);
  transition-delay: calc(var(--i, 0) * .07s);
}
.chapter.on .ch-shot { opacity: 1; transform: scale(1) rotate(var(--tilt, 0deg)); }
.chapter.out .ch-shot { opacity: 0; transform: scale(.94) rotate(-2deg); }
.ch-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ch-shot.a { width: clamp(120px, 13vw, 190px); aspect-ratio: 3/4; --i: 0; }
.ch-shot.b { width: clamp(100px, 11vw, 150px); aspect-ratio: 3/4; align-self: flex-end; --i: 1; }
.ch-shot.c { width: clamp(88px, 9.5vw, 128px); aspect-ratio: 3/4; align-self: center; --i: 2; }
@media (hover: hover) and (pointer: fine) {
  .chapter.on .ch-shot:hover {
    transform: scale(1.03) rotate(calc(var(--tilt, 0deg) * .4));
    box-shadow: 0 30px 60px -22px rgba(72, 26, 34, .42);
  }
}

@media (max-width: 900px) {
  .story-stage {
    grid-template-columns: 46px minmax(0, 1fr);
    gap: 12px;
    padding: clamp(84px, 12vh, 110px) 0 clamp(30px, 6vh, 60px);
  }
  .story-rail { height: min(52vh, 380px); }
  .rail-years li { font-size: 9.5px; letter-spacing: .06em; padding-left: 18px; }
  .rail-line { left: 6px; }
  .rail-years li::before { left: 3px; width: 6px; height: 6px; margin-top: -3px; }
  .chapter { max-width: none; }
  .story-ghost { font-size: clamp(120px, 40vw, 240px); top: 30vh; }
  .ch-shot.a { width: 38vw; }
  .ch-shot.b { width: 30vw; }
  /* the blur costs more than it gives at this size */
  .chapter { filter: none; transform: translateY(24px); }
  .chapter.out { filter: none; }
}

/* -------------------------------------------------------------
   BIOGRAPHY ON A PHONE — the same chapters, a different gesture

   On desktop this section pins itself and spends six screens of scroll
   playing a timeline. On a phone that is a trap: a reader who does not
   want the biography still has to swipe six times to get out of it.

   So below 768px the pin is simply never built — `.story-track` keeps its
   natural height (the JS does not set the 600svh) and the stage stops being
   sticky. The chapters become a horizontal scroll-snap strip: native scroll,
   no touch handlers, no preventDefault anywhere. The browser resolves which
   axis the finger meant, so an upward swipe always belongs to the page and
   a sideways one always belongs to the story.

   `.is-mobile` is set by the JS when it mounts this mode, so these rules
   need both it and the media query to apply — desktop cannot be reached
   by either alone.
   ------------------------------------------------------------- */

/* phone-only furniture, absent from the desktop read */
.story-kicker, .story-cta, .story-hint, .story-prog { display: none; }

@media (max-width: 767px) {

  /* ---------- the way in ---------- */
  .story-open {
    display: flex; flex-direction: column; align-items: flex-start;
    padding: clamp(80px, 14vh, 130px) 0 clamp(26px, 5vh, 44px);
  }
  /* the date sits above the name here, as a dateline does */
  .story-label { order: -1; margin-top: 0; margin-bottom: 14px; }
  .story-name { font-size: clamp(2.9rem, 13vw, 4.4rem); }

  .story-kicker {
    display: block; margin-top: 18px;
    font-size: clamp(1.15rem, 5vw, 1.5rem);
    font-weight: 500; letter-spacing: -.02em;
    color: var(--crimson);
  }
  .story-cta {
    display: inline-flex; align-items: center; gap: 9px;
    margin-top: 20px; padding: 11px 20px 11px 18px;
    font: inherit; font-size: 13.5px; letter-spacing: .02em;
    color: var(--ink); cursor: pointer;
    background: rgba(255, 255, 255, .66);
    border: 1px solid rgba(201, 31, 55, .22);
    border-radius: 999px;
    -webkit-tap-highlight-color: transparent;
  }
  .story-cta::after {
    content: '→'; color: var(--crimson);
    transition: transform .35s var(--ease);
  }
  .story-cta:active::after { transform: translateX(3px); }
  .story-hint {
    display: block; margin-top: 16px;
    font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
    color: var(--ink-45);
  }
  .story-hint::before { content: '← '; }
  .story-hint::after  { content: ' →'; }

  .story-ghost { font-size: clamp(110px, 36vw, 200px); top: 16vh; }

  /* ---------- the pin, removed ---------- */
  /* height is cleared by the JS on unmount too; this is the guarantee that
     no stale inline value can leave six empty screens behind on a phone */
  .is-mobile .story-track { height: auto !important; }
  .is-mobile .story-stage {
    position: static;
    display: block;
    height: auto;
    padding: 0 0 clamp(26px, 6vh, 48px);
  }
  /* the full-section poppy layer belongs to the pinned read; on a phone the
     flowers live on the cards instead */
  .is-mobile .story-poppies { display: none; }

  /* ---------- timeline, turned on its side ---------- */
  .is-mobile .story-rail {
    height: auto; padding: 0; margin: 0 0 18px;
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
  }
  .is-mobile .story-rail::-webkit-scrollbar { display: none; }
  .is-mobile .rail-years {
    flex-direction: row; justify-content: flex-start;
    align-items: flex-end; gap: 22px; height: auto;
    width: max-content; min-width: 100%;
  }
  /* the hairline that joins the dots, level with their centres */
  .is-mobile .rail-line {
    left: 10px; right: 10px; top: auto; bottom: 3.5px;
    width: auto; height: 1px;
  }
  .is-mobile .rail-line i { display: none; }   /* progress lives in .sp-line */
  .is-mobile .rail-years li {
    padding: 0; opacity: 1; transform: none;
  }
  .is-mobile .rail-years li::before { display: none; }
  .is-mobile .rail-years li button {
    display: grid; justify-items: center; gap: 7px;
    padding: 0; border: 0; background: none; cursor: pointer;
    font: inherit; font-size: 11px; letter-spacing: .1em;
    font-variant-numeric: tabular-nums;
    color: var(--ink); opacity: .3;
    -webkit-tap-highlight-color: transparent;
    transition: opacity .4s var(--ease), color .4s var(--ease);
  }
  .is-mobile .rail-years li button::after {
    content: ''; width: 7px; height: 7px; border-radius: 50%;
    background: var(--silver);
    transition: background .4s var(--ease), transform .4s var(--ease);
  }
  .is-mobile .rail-years li.on button { opacity: 1; color: var(--crimson); }
  .is-mobile .rail-years li.on button::after {
    background: var(--crimson); transform: scale(1.35);
  }

  /* ---------- the chapters, swiped ---------- */
  .is-mobile .story-chapters {
    display: flex; gap: 14px; height: auto;
    overflow-x: auto; overflow-y: hidden;
    scroll-snap-type: x mandatory;
    /* a swipe that runs off the end must not chain into the page or trigger
       the browser's back gesture */
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* the side padding is what lets the first and last card reach the centre */
    padding: 4px 7vw 12px;
  }
  .is-mobile .story-chapters::-webkit-scrollbar { display: none; }

  .is-mobile .chapter {
    position: relative; inset: auto;
    flex: 0 0 86vw; max-width: none;
    scroll-snap-align: center;
    display: flex; flex-direction: column; justify-content: flex-start;
    min-height: 60svh;
    padding: 26px 22px 30px;
    border-radius: var(--r-l);
    /* paper, not a product card: a warm wash and a hairline, no elevation */
    background: linear-gradient(168deg, rgba(255,255,255,.74), rgba(255,241,235,.52));
    border: 1px solid rgba(201, 31, 55, .11);
    overflow: hidden;                 /* the card's poppies crop to its edge */
    pointer-events: auto;
    /* opacity and scale are written inline from the scroll position, so a
       transition here would only lag the finger */
    opacity: 1; transform: none; filter: none; transition: none;
    will-change: transform, opacity;
  }
  /* the desktop cross-fade classes must not reach this mode */
  .is-mobile .chapter.on, .is-mobile .chapter.out {
    opacity: 1; transform: none; filter: none;
  }

  .is-mobile .ch-head {
    display: flex; flex-direction: column; align-items: flex-start;
    gap: 10px; margin-bottom: 16px;
  }
  .is-mobile .ch-title { flex: 0 1 auto; font-size: clamp(1.9rem, 8.4vw, 2.7rem); }
  .is-mobile .ch-text { font-size: 15.5px; line-height: 1.58; }
  .is-mobile .ch-head, .is-mobile .ch-text, .is-mobile .ch-shots {
    position: relative; z-index: 1;          /* above the card's poppies */
  }
  .is-mobile .ch-shots { margin-top: auto; padding-top: 22px; }
  .is-mobile .ch-shot.a { width: 34vw; }
  .is-mobile .ch-shot.b { width: 27vw; }
  /* a card is not a gallery: the second print tucks behind the first rather
     than standing beside it, and there is never a third */
  .is-mobile .ch-shots.stacked { position: relative; }
  .is-mobile .ch-shots.stacked .ch-shot.b {
    position: absolute; left: 21vw; bottom: 8px; z-index: -1;
  }
  .is-mobile .ch-shot.c { display: none; }
  /* the chapters do not cross-fade here, so the prints must simply be on */
  .is-mobile .ch-shot { opacity: 1; transform: rotate(var(--tilt, 0deg)); }

  /* a poppy at the card's edge, never under the paragraph */
  .ch-poppy { position: absolute; z-index: 0; pointer-events: none; }
  .ch-poppy svg { display: block; width: 100%; height: 100%; overflow: visible; }

  /* ---------- progress ---------- */
  .is-mobile .story-prog {
    display: flex; align-items: center; gap: 14px;
    margin-top: 18px; padding: 0 2px;
  }
  .sp-count {
    font-size: 11px; letter-spacing: .18em;
    font-variant-numeric: tabular-nums; color: var(--ink-45);
  }
  .sp-count b { color: var(--crimson); font-weight: 500; }
  .sp-count i { font-style: normal; }
  .sp-line {
    position: relative; flex: 1; height: 1px;
    background: rgba(24, 19, 19, .13);
  }
  .sp-line i {
    position: absolute; left: 0; top: 0; height: 100%; width: 0;
    background: linear-gradient(90deg, var(--crimson), var(--wine));
    transition: width .18s linear;
  }
}

/* the depth effect is written inline, so switching it off has to outrank it */
@media (prefers-reduced-motion: reduce) {
  .is-mobile .chapter { transform: none !important; opacity: 1 !important; }
}

/* =============================================================
   HERO DISSOLVE
   Full-viewport canvas so the blooms can leave the picture's box. It sits
   just above the hero and below every following chapter, which is what
   lets the next section rise through the thinning flowers.
   ============================================================= */
#dissolveCv {
  position: fixed; inset: 0; z-index: 5;
  pointer-events: none;
  /* a full-screen canvas that is idle except during the one-off dissolve;
     a canvas gets its own layer when it draws, so the hint was only holding
     a screen-sized texture open for the rest of the session */
}
/* the real <img> is only hidden while the effect is mid-flight; at rest the
   page is back to a plain photograph */
.hero-portrait.dissolving img { transition: opacity .18s linear; }

/* =============================================================
   HERO
   ============================================================= */
.hero {
  position: relative; z-index: 2;
  min-height: 100svh;
  display: flex; align-items: center; justify-content: center;
  padding: 120px var(--pad) 90px;
  overflow: hidden;
}
.hero-portrait {
  position: relative;
  /* Bounded on BOTH axes so the 3:4 always survives — the intro card lands
     on this exact rectangle, and a squashed portrait would break the match. */
  width: min(46vw, 520px, 49.5vh);
  height: auto;
  aspect-ratio: 3 / 4;
  flex: none;
  border-radius: var(--r-xl);
  overflow: hidden;
  box-shadow: var(--sh-l), inset 0 0 0 1px rgba(255,255,255,.6);
  will-change: transform;
}
.hero-portrait img { width: 100%; height: 100%; object-fit: cover; }

/* Background-free portrait: no card, no crop — the figure itself is the shape,
   so the frame, the glass sheen and the box-shadow all have to go, and the
   depth comes from a drop-shadow that follows the alpha edge. */
.hero-portrait.cutout {
  background: none;
  border-radius: 0;
  overflow: visible;
  box-shadow: none;
  width: min(56vw, 620px, 62vh);
  aspect-ratio: auto;
  height: min(72vh, 720px);
}
.hero-portrait.cutout::after { display: none; }
.hero-portrait.cutout img {
  object-fit: contain;
  filter: drop-shadow(0 40px 60px rgba(72, 26, 34,.28))
          drop-shadow(0 4px 14px rgba(72, 26, 34,.10));
}
.hero-portrait::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(140deg, rgba(255,255,255,.34), transparent 40%, rgba(248, 210, 200,.20));
  mix-blend-mode: screen;
}
.hero-glow {
  position: absolute; left: 50%; top: 50%;
  width: min(92vw, 1000px); aspect-ratio: 1; transform: translate(-50%, -50%);
  /* Measured at 0.77 of a viewport being blurred every frame this layer
     moved. A radial gradient is already a soft edge — the 20px blur was
     re-filtering a shape that had no hard edge to soften. The stops are
     widened instead, which is free. */
  background:
    radial-gradient(52% 52% at 50% 50%, rgba(199, 154, 85,.17), transparent 70%),
    radial-gradient(64% 64% at 50% 50%, rgba(201, 31, 55,.17), transparent 74%);
  pointer-events: none;
}

/* Typography sits above every poppy layer. The front layer is z-index 12, so
   at z-index 4 the copy was literally underneath it — measured at 93%% of the
   headline covered by one bloom. Composition keeps the flowers clear of the
   words; this makes sure they can never be on top of them either. */
.hero-copy {
  position: absolute; left: var(--pad); bottom: clamp(60px, 10vh, 120px);
  max-width: min(38vw, 460px); z-index: 13;
}
.hero-copy .display { color: #171111; line-height: .86; }
.hero-copy .eyebrow {
  font-size: clamp(11px, .85vw, 15px);
  letter-spacing: .22em;
  font-weight: 500;
  color: rgba(23, 17, 17, .74);
}
.hero-copy .display { margin: 14px 0 18px; }
.hero-meta {
  position: absolute; right: var(--pad); bottom: clamp(60px, 10vh, 120px);
  text-align: right; z-index: 13; max-width: 300px;
}
.hero-meta .body {
  font-size: clamp(14px, 1vw, 18px) !important;
  line-height: 1.45;
  color: rgba(23, 17, 17, .78);
}
.scroll-cue .mono { color: rgba(23, 17, 17, .72); }
.scroll-cue .rail { background: rgba(23, 17, 17, .22); }
.scroll-cue {
  position: absolute; left: 50%; bottom: 30px; transform: translateX(-50%);
  z-index: 13;
  display: flex; flex-direction: column; align-items: center; gap: 10px; z-index: 4;
}
.scroll-cue .rail {
  width: 1px; height: 46px; background: linear-gradient(var(--ink-25), transparent);
  position: relative; overflow: hidden;
}
.scroll-cue .rail::after {
  content: ''; position: absolute; inset: 0; background: var(--ink);
  animation: railRun 2.4s var(--ease) infinite;
}
@keyframes railRun { 0% { transform: translateY(-100%); } 60%, 100% { transform: translateY(100%); } }

/* =============================================================
   FLOWERS — persistent living layer
   ============================================================= */
#flowers {
  position: fixed; inset: 0; z-index: 2;   /* behind the transparent hero */
  pointer-events: none;
  overflow: hidden;
}
#flowers .fl {
  position: absolute;
  will-change: transform, opacity;
  transform-origin: 50% 50%;
}
#flowers svg { display: block; overflow: visible; }

/* Text safe zones, carved out of the flower layers themselves.

   Per-bloom masks were tried first and did not hold: each one is anchored to
   its own flower, so a bloom only has to drift for the hole to stop lining up
   with the words — measured 0.54 average ink over the headline and peaks of
   1.58 over the description where two blooms stacked.

   These are anchored to the copy instead. Two soft ellipses sit exactly where
   the headline and the description live; `intersect` keeps the layer only
   where both are opaque, so the flowers thin out over the words and stay
   fully saturated everywhere else.

   The stop positions matter as much as the radii. The headline box runs from
   45% to 95% of the viewport height, so a gradient that starts ramping at 26%
   of its radius is already fully opaque by the time it reaches the first line
   -- measured alpha 1.0 across the whole top band. Holding it clear to 62% and
   ramping only over the outer third covers the box without spreading the
   faded area any wider. */
/* The desktop mask is gone. It was two radial gradients composited with
   `intersect` across two full-viewport layers whose children move every
   frame, which made it the single most expensive thing on the page -- worth
   27fps on hero -> story by itself.

   It existed to keep one bloom off the headline. That bloom has been moved
   instead: the composition now does in one layout constant what the mask was
   doing per frame. The phone keeps its mask (below) because its blooms are
   larger and its copy is centred over them.

   If flowers ever return to the upper half, the protection comes back as
   per-bloom opacity in flowers.js, not as a mask on the container. */

/* On a phone the copy is centred and static, so the protected area moves
   with it: one wide, low ellipse instead of two corners.

   Sized to the measured box rather than guessed. The headline occupies
   18%-82% across and 72%-87% down, so the ellipse is wide and shallow — an
   earlier version was 36% tall, nearly five times the height of the text it
   was protecting, and it erased 61% of the one large bloom the composition
   is built around. The vertical radius carries enough margin for a headline
   that runs to three lines in another language. */
@media (max-width: 767px) {
  #flowers, #flowers-front {
    -webkit-mask-image:
      radial-gradient(80% 22% at 50% 79.5%, transparent 0%, transparent 58%, #000 100%);
    mask-image:
      radial-gradient(80% 22% at 50% 79.5%, transparent 0%, transparent 58%, #000 100%);
  }
}

#flowers-front .fl { position: absolute; transform-origin: 50% 50%; }
#flowers-front svg { display: block; overflow: visible; }

/* Compositor-only drift, used instead of the JS loop on phones. Keeping the
   whole animation to transform means it never touches layout or paint.

   Deliberately small. This is the only continuous motion left on a phone, and
   at 18px of travel with four degrees of roll it read as the page wobbling
   rather than as air moving through a field. Half that, over a longer cycle,
   reads as wind. */
.fl.drift {
  will-change: transform;
  animation-name: fl-drift;
  animation-timing-function: ease-in-out;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
@keyframes fl-drift {
  from { transform: translate3d(0, 0, 0) rotate(-1.8deg) scale(.986); }
  to   { transform: translate3d(0, -8px, 0) rotate(1.8deg) scale(1.014); }
}

/* A loose petal keeps the angle it was dropped at, so its sway has to be
   written around that resting rotation rather than replacing it. */
.fl.pt.drift { animation-name: pt-drift; }
@keyframes pt-drift {
  from { transform: translate3d(0, 0, 0) rotate(calc(var(--rot) - 3deg)) scale(.98); }
  to   { transform: translate3d(0, -7px, 0) rotate(calc(var(--rot) + 3deg)) scale(1.02); }
}
/* no resting angle set (desktop never builds petals) — degrade to no tilt */
.fl.pt { --rot: 0deg; }
#flower-video {
  position: fixed; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 3; pointer-events: none;
  mix-blend-mode: multiply; opacity: .38; filter: saturate(.7) brightness(1.25);
}

/* =============================================================
   STACK — every section pins to the top, the next one rides over it
   ============================================================= */
/* Every chapter is exactly one screen tall. That is what makes the stack
   work: a sticky box taller than the viewport would hide its own tail. */
.stack {
  position: sticky; top: 0;
  height: 100svh; min-height: 560px;
  display: flex; flex-direction: column; justify-content: center;
  border-radius: clamp(20px, 2.6vw, 42px) clamp(20px, 2.6vw, 42px) 0 0;
  box-shadow: 0 -34px 90px -34px rgba(72, 26, 34, .28), 0 -1px 0 rgba(255, 255, 255, .8);
  overflow: hidden;
  isolation: isolate;
  /* No `will-change: transform` here. There are eight of these, each a full
     screen, and the property promoted every one of them to its own permanent
     compositor layer -- about nineteen viewports of texture held for the whole
     session. A sticky box is already given a layer when it needs one, so the
     hint bought nothing and cost the entire scroll: removing it alone returned
     21fps on hero -> story. */
}
/* the covered section quietly falls away underneath */
.stack > * { transition: none; }
.stack-inner { width: 100%; }

/* .section / .music / .footer also set `position`, so win the cascade back */
.hero.stack, .section.stack, .music.stack, .footer.stack {
  position: sticky; top: 0;
}

/* explicit paint order: each later section sits on top of the last */
#hero    { z-index: 4; }
#about   { z-index: 5; }
#archive { z-index: 6; }
#music   { z-index: 7; }
#films   { z-index: 8; }
#awards  { z-index: 9; }
#live    { z-index: 10; }
#footer  { z-index: 11; }

/* the first one owns the top of the page — no lip, and it lets the
   flower layer behind it show through */
#hero.stack { border-radius: 0; box-shadow: none; background: transparent; overflow: visible; }

/* opaque tones so a section genuinely hides the one below */
.tone-a { background: linear-gradient(180deg, #FFFFFF 0%, #FFF7F2 100%); }
.tone-b { background: linear-gradient(180deg, #FFFFFF 0%, #FFF2EC 100%); }
.tone-c { background: linear-gradient(170deg, #FFF8F4 0%, #FFEBE3 100%); }

/* the archive chapter carries the flag blue and hands it to the panel */
#archive.stack {
  background: linear-gradient(168deg, #C91F37 0%, #A81830 55%, #7D1F32 100%);
  color: #fff;
}
#archive .eyebrow, #archive .mono { color: rgba(255, 255, 255, .72); }
#archive .h2 { color: #fff; }
.tone-d { background: linear-gradient(180deg, #FFF9F6 0%, #FFF2EB 100%); }
.tone-e { background: linear-gradient(180deg, #FFF7F2 0%, #FFE8DF 100%); }
.tone-f { background: linear-gradient(180deg, #FFF2EC 0%, #FFFFFF 100%); }
/* the awards room: ivory warmed by the gold the honours carry */
.tone-g { background: linear-gradient(172deg, #FFFBF8 0%, #FFF5F0 46%, #FFF0E9 100%); }

/* =============================================================
   AWARDS — a horizontal cabinet of honours
   ============================================================= */
#awards.stack { overflow: hidden; }
#awards .sec-head { align-items: flex-end; }
.aw-count { color: var(--ink-25); white-space: nowrap; }
.aw-count span:first-child { color: var(--gold); font-weight: 600; }

/* the rail itself */
.aw-rail {
  display: flex; gap: clamp(14px, 1.6vw, 26px);
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory;
  padding: 6px calc(var(--pad)) 26px 0;
  margin-right: calc(var(--pad) * -1);
  scrollbar-width: none;
  cursor: grab;
  overscroll-behavior-x: contain;   /* never hand the swipe to the page */
}
.aw-rail::-webkit-scrollbar { display: none; }
.aw-rail.drag { cursor: grabbing; scroll-snap-type: none; }

.aw-card {
  position: relative; flex: 0 0 auto;
  width: clamp(232px, 23vw, 316px);
  scroll-snap-align: start;
  display: flex; flex-direction: column;
  border-radius: var(--r-m);
  background: var(--glass-strong);
  border: 1px solid var(--glass-line);
  box-shadow: var(--sh-m), inset 0 1px 0 rgba(255,255,255,.9);
  backdrop-filter: blur(14px) saturate(180%); -webkit-backdrop-filter: blur(14px) saturate(180%);
  overflow: hidden;
  transition: transform .7s var(--ease), box-shadow .7s var(--ease);
}
.aw-card:hover { transform: translateY(-8px); box-shadow: var(--sh-l), inset 0 1px 0 rgba(255,255,255,.95); }

/* the picture slot — a frame until the photographs arrive */
.aw-shot { position: relative; aspect-ratio: 4 / 3; overflow: hidden; flex: none; }
.aw-shot img { width: 100%; height: 100%; object-fit: cover; }

.aw-year {
  position: absolute; left: 14px; top: 14px; z-index: 2;
  padding: 5px 11px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600; letter-spacing: .06em;
  font-variant-numeric: tabular-nums;
  color: #fff; background: rgba(24, 19, 19, .62);
  border: 1px solid rgba(255,255,255,.34);
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}

/* the accent ribbon reads the kind of honour at a glance */
.aw-kind {
  position: absolute; right: 14px; top: 14px; z-index: 2;
  width: 26px; height: 26px; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(255,255,255,.82); border: 1px solid rgba(255,255,255,.7);
  box-shadow: var(--sh-s);
}
.aw-kind svg { width: 14px; height: 14px; }
.aw-card[data-kind="state"] .aw-kind svg { fill: var(--gold); }
.aw-card[data-kind="grand"] .aw-kind svg { fill: #B07B2C; }
.aw-card[data-kind="prize"] .aw-kind svg { fill: var(--crimson-deep); }

.aw-card::after {
  content: ''; position: absolute; left: 0; right: 0; top: 0; height: 3px; z-index: 3;
  background: linear-gradient(90deg, var(--crimson), var(--scarlet));
}
.aw-card[data-kind="state"]::after { background: linear-gradient(90deg, var(--gold), #EEDCB6); }
.aw-card[data-kind="grand"]::after { background: linear-gradient(90deg, #B07B2C, var(--gold), #F3E4C4); }

.aw-body { padding: 18px 20px 22px; display: flex; flex-direction: column; gap: 9px; flex: 1; }
.aw-title {
  font-size: 15.5px; font-weight: 600; line-height: 1.32; letter-spacing: -.02em;
  text-wrap: balance;                 /* stop a lone word dangling on line 4 */
  overflow-wrap: anywhere;            /* long Kazakh compounds must break */
}
.aw-place {
  font-size: 10.5px; letter-spacing: .13em; text-transform: uppercase;
  line-height: 1.4; color: var(--wine); font-weight: 600;
  padding-bottom: 3px; border-bottom: 1px solid rgba(201, 31, 55, .18);
}
.aw-text { font-size: 13.5px; line-height: 1.62; color: var(--ink-70); }

.aw-hint {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  color: var(--ink-25); margin-top: 4px;
}
.aw-arrows { display: flex; gap: 8px; }
.aw-nav {
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center; color: var(--ink-70);
  background: var(--glass-strong); border: 1px solid var(--glass-line);
  box-shadow: var(--sh-s);
  transition: transform .5s var(--ease), background .4s var(--ease), color .4s var(--ease);
}
.aw-nav svg { width: 19px; height: 19px; }
.aw-nav:hover { background: var(--crimson-deep); color: #fff; transform: scale(1.08); }
.aw-nav:active { transform: scale(.93); }
.aw-nav[disabled] { opacity: .3; pointer-events: none; }

/* =============================================================
   SECTIONS shell
   ============================================================= */
.section { position: relative; z-index: 4; padding: var(--gap) var(--pad); }
.section.stack { padding: clamp(80px, 11vh, 128px) var(--pad) clamp(34px, 6vh, 66px); }
.stack .sec-head { margin-bottom: clamp(22px, 4vh, 54px); }
.sec-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 32px; margin-bottom: clamp(36px, 6vh, 84px); }
.sec-head .left { max-width: 60ch; }
.sec-head .eyebrow { margin-bottom: 18px; }

/* ABOUT */
.about { display: grid; grid-template-columns: 1.1fr .9fr; gap: clamp(30px, 6vw, 96px); align-items: center; }
.about-figure {
  position: relative; border-radius: var(--r-l); overflow: hidden;
  aspect-ratio: 4/5; height: min(62vh, 620px); width: 100%; margin-left: auto;
  box-shadow: var(--sh-m);
}
.about-figure img { width: 100%; height: 100%; object-fit: cover; }
.stats { display: flex; gap: clamp(20px, 4vw, 60px); margin-top: clamp(22px, 4vh, 44px); flex-wrap: wrap; }
.stat .n { font-size: clamp(32px, 4vw, 58px); font-weight: 500; letter-spacing: -0.05em; line-height: 1; }
.stat .l { font-size: 11px; letter-spacing: .18em; text-transform: uppercase; color: var(--ink-45); margin-top: 10px; }

/* =============================================================
   GALLERY — faithful port of the reference scroll engine
   ============================================================= */
/* The whole archive block shares z-index 6 with the #archive chapter and
   sits after it in the DOM, so it covers it — while #music (z 7) is free
   to ride over the top of the archive on the way out. */
#gal-spacer { position: relative; z-index: 6; user-select: none; }

#gal-panel {
  position: fixed; inset: 0; z-index: 1;
  background: var(--wine);
  overflow: hidden;
  transform: translateY(100vh);
  visibility: hidden;
}
/* Promoted only while it is on screen. Hidden, this panel and its wrapper
   still held roughly eleven viewports of compositor texture -- the two
   largest layers on the page, for something nobody was looking at. */
#gal-panel.live { visibility: visible; will-change: transform; }
#gal-panel.live #gal-wrap { will-change: transform; }
#gal-panel::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background:
    radial-gradient(60vw 50vh at 12% 0%, rgba(201, 31, 55,.55), transparent 62%),
    radial-gradient(50vw 45vh at 92% 92%, rgba(199, 154, 85,.15), transparent 60%);
}
/* the lead-in used to be 40vh, which read as a huge empty blue band before
   the first frame ever appeared */
#gal-wrap { width: 100%; padding-top: min(140px, 12vh); position: relative; z-index: 2; }
#gal-grid { display: grid; gap: 0; }
.gal-cell { aspect-ratio: 2 / 3; }
.bp-card {
  transform: scale(0);
  width: 100%; height: 100%;
  overflow: hidden;
  border-radius: 4px;
  position: relative;
  background: rgba(255,255,255,.04);
}
.bp-card img { width: 100%; height: 100%; object-fit: cover; }
.bp-card .frame { border-radius: inherit; }
.bp-card::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(150deg, rgba(255,255,255,.16), transparent 42%);
  mix-blend-mode: screen; pointer-events: none;
}
@media (prefers-reduced-motion: reduce) { .bp-card { will-change: auto; } }

/* fixed gallery chrome — plain white, not `exclusion`: on the flag-blue
   panel an inverted blend would turn the type muddy red */
.gal-fixed { position: fixed; z-index: 6; pointer-events: none; color: #fff; }
#gal-info { right: var(--pad); bottom: 80px; width: 330px; display: flex; flex-direction: column; align-items: center; opacity: 0; }
#gal-info .top { display: flex; flex-direction: column; align-items: flex-start; width: 100%; margin-bottom: 32px; }
#gal-circle {
  position: relative; width: 30px; height: 30px; display: grid; place-items: center; margin-bottom: 14px;
}
#gal-circle svg { position: absolute; inset: 0; width: 100%; height: 100%; }
#circle-symbol { font-size: 15px; font-weight: 500; letter-spacing: -.04em; text-transform: uppercase; }
#gal-collection { font-size: 30px; font-weight: 500; line-height: 1; text-align: center; letter-spacing: -.04em; text-transform: uppercase; width: 100%; }

#gal-view {
  right: var(--pad); bottom: 32px; width: 330px; height: 174px;
  transform-origin: right bottom; transform: scale(0);
  background: #fff; border-radius: 1335px;
  display: flex; align-items: center; justify-content: center;
}
#gal-view span { font-size: 96px; font-weight: 500; letter-spacing: -.04em; color: var(--wine); line-height: 1; }

#gal-overlay { position: fixed; inset: 0; z-index: 6; pointer-events: none; background: #fff; opacity: 0; }

#gal-footer {
  left: var(--pad); bottom: 32px; display: flex; gap: 80px; opacity: 0;
  font-size: 13px; font-weight: 500; letter-spacing: -.02em; text-transform: uppercase;
}

/* =============================================================
   MUSIC — Apple-Music-grade coverflow + glass transport
   ============================================================= */
.music { position: relative; z-index: 4; padding: var(--gap) 0; overflow: hidden; }
.music.stack { padding: clamp(80px, 11vh, 128px) 0 clamp(28px, 4vh, 52px); }
.music.stack .music-head { margin-bottom: 0; }
.music-bg {
  position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(60vw 60vh at 50% 40%, var(--tint, rgba(227, 58, 58,.22)), transparent 65%);
  transition: background 1.2s var(--ease);
  /* blur removed: a full viewport of filter over a gradient */
}
.music-head { padding: 0 var(--pad); position: relative; z-index: 2; }

/* ---- carousel + its flanking arrows ---- */
.cf-wrap {
  position: relative; z-index: 2;
  display: flex; align-items: center; gap: clamp(4px, 1.5vw, 18px);
  padding: 0 clamp(6px, 2vw, 26px);
}
.cf-arrow {
  flex: none;
  width: clamp(44px, 4.2vw, 58px); height: clamp(44px, 4.2vw, 58px);
  border-radius: 50%;
  display: grid; place-items: center;
  color: var(--ink-70);
  background: var(--glass-strong);
  border: 1px solid var(--glass-line);
  box-shadow: var(--sh-m), inset 0 1px 0 rgba(255,255,255,.9);
  backdrop-filter: blur(14px) saturate(180%); -webkit-backdrop-filter: blur(14px) saturate(180%);
  transition: transform .5s var(--ease), background .4s var(--ease), color .4s var(--ease), box-shadow .5s var(--ease);
}
.cf-arrow svg { width: 22px; height: 22px; }
.cf-arrow:hover {
  color: #fff; background: var(--crimson-deep);
  transform: scale(1.08);
  box-shadow: 0 18px 40px -14px rgba(168, 24, 48,.65);
}
.cf-arrow:active { transform: scale(.93); }
.cf-arrow.kick { animation: cf-kick .45s var(--ease); }
.cf-arrow.prev.kick { animation-name: cf-kick-l; }
@keyframes cf-kick-l { 40% { transform: translateX(-6px) scale(1.06); } }
@keyframes cf-kick { 40% { transform: translateX(6px) scale(1.06); } }

.cf {
  position: relative; z-index: 2;
  flex: 1; min-width: 0;
  /* The section is one screen tall and clips. On a laptop — a 1440x720
     window, say — the carousel at 36vh plus its margins left the track list
     37px short of its second row, so half the songs were cut off by the
     edge of the screen with the next chapter sliding over them. The
     carousel gives that height back: it is the one block here that can
     lose 60px without losing its job. */
  height: clamp(190px, 30vh, 420px);
  margin: clamp(14px, 2.6vh, 52px) 0 clamp(12px, 2.2vh, 44px);
  perspective: 1400px;
  perspective-origin: 50% 50%;
  cursor: grab;
  touch-action: pan-y;          /* let the page scroll vertically through it */
}
.cf.drag { cursor: grabbing; }
.cf-track { position: absolute; left: 50%; top: 50%; width: 0; height: 0; transform-style: preserve-3d; }
.cover {
  position: absolute; top: 0; left: 0;
  width: clamp(160px, 17vw, 250px); aspect-ratio: 1;
  margin-left: calc(clamp(160px, 17vw, 250px) / -2);
  margin-top: calc(clamp(160px, 17vw, 250px) / -2);
  border-radius: clamp(14px, 1.5vw, 22px);
  /* `filter` in the hint forces a filter surface for every cover, always.
     The covers do move, so transform and opacity stay; the blur they take
     while off-centre is rare enough to be worth a re-raster when it happens. */
  overflow: hidden;
  /* promoted only while the chapter is on screen -- see #music.near below */
  will-change: auto;
  box-shadow: 0 34px 70px -26px rgba(72, 26, 34,.42), inset 0 0 0 1px rgba(255,255,255,.5);
  background: var(--mist);
  transform-style: preserve-3d;
}
.cover img { width: 100%; height: 100%; object-fit: cover; }
.cover::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(135deg, rgba(255,255,255,.38), transparent 44%);
  mix-blend-mode: screen; pointer-events: none;
}
.cover .cap {
  position: absolute; left: 8px; right: 8px; bottom: 8px;
  padding: 12px 14px; border-radius: 14px;
  background: rgba(255,255,255,.30);
  border: 1px solid rgba(255,255,255,.45);
  backdrop-filter: blur(14px) saturate(180%); -webkit-backdrop-filter: blur(14px) saturate(180%);
  color: #fff; text-shadow: 0 1px 12px rgba(72, 26, 34,.4);
  opacity: 0; transform: translateY(8px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
}
.cover.active .cap { opacity: 1; transform: none; }
.cover .cap .t { font-size: 14px; font-weight: 600; letter-spacing: -.02em; }
.cover .cap .a { font-size: 11.5px; opacity: .85; margin-top: 2px; }
.cover.active { box-shadow: 0 60px 110px -34px rgba(72, 26, 34,.5), 0 0 70px rgba(199, 154, 85,.25), inset 0 0 0 1px rgba(255,255,255,.7); }

/* transport bar */
.transport {
  position: relative; z-index: 3;
  margin: 0 var(--pad);
  display: flex; align-items: center; gap: clamp(10px, 1.4vw, 22px);
  padding: 12px 16px;
  border-radius: 999px;
  background: var(--glass);
  border: 1px solid var(--glass-line);
  box-shadow: var(--sh-m), inset 0 1px 0 rgba(255,255,255,.9);
  backdrop-filter: blur(14px) saturate(190%); -webkit-backdrop-filter: blur(14px) saturate(190%);
}
.tp-btn {
  width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--ink-70);
  transition: background .4s var(--ease), color .4s var(--ease), transform .4s var(--ease);
}
.tp-btn:hover { background: rgba(255,255,255,.75); color: var(--ink); transform: scale(1.06); }
.tp-btn:active { transform: scale(.94); }
.tp-btn svg { width: 20px; height: 20px; fill: currentColor; }
.tp-play {
  width: 48px; height: 48px; position: relative;
  background: var(--ink); color: #fff;
  box-shadow: 0 10px 26px -12px rgba(24, 19, 19,.7);
  transition: background .5s var(--ease), transform .4s var(--ease), box-shadow .5s var(--ease);
}
.tp-play:hover { background: var(--crimson-deep); color: #fff; }
.tp-play.on { background: var(--crimson-deep); box-shadow: 0 14px 34px -12px rgba(168, 24, 48,.75); }

/* the two glyphs cross-fade through each other instead of swapping a path */
.tp-play svg {
  position: absolute; top: 50%; left: 50%;
  width: 20px; height: 20px; fill: currentColor;
  transition: opacity .34s var(--ease), transform .44s var(--ease);
}
.tp-play .ic-play  { transform: translate(-50%,-50%) scale(1) rotate(0deg);      opacity: 1; }
.tp-play .ic-pause { transform: translate(-50%,-50%) scale(.45) rotate(-90deg);  opacity: 0; }
.tp-play.on .ic-play  { transform: translate(-50%,-50%) scale(.45) rotate(90deg); opacity: 0; }
.tp-play.on .ic-pause { transform: translate(-50%,-50%) scale(1) rotate(0deg);    opacity: 1; }

/* a ring breathes out of the button on each press */
.tp-ring {
  position: absolute; inset: 0; border-radius: 50%;
  border: 1.5px solid var(--crimson); opacity: 0; pointer-events: none;
}
.tp-play.kick .tp-ring { animation: tp-ring .62s var(--ease); }
@keyframes tp-ring {
  0%   { transform: scale(1);   opacity: .85; }
  100% { transform: scale(1.9); opacity: 0; }
}
.tp-play.kick { animation: tp-press .42s var(--ease); }
@keyframes tp-press { 35% { transform: scale(.88); } }

/* the live cover lifts and glows while the track runs */
.cover.playing {
  box-shadow: 0 70px 130px -34px rgba(72, 26, 34,.55),
              0 0 90px var(--album, rgba(201, 31, 55,.45)),
              inset 0 0 0 1px rgba(255,255,255,.8);
}
.cover.playing::before {
  content: ''; position: absolute; inset: -3px; border-radius: inherit;
  border: 1.5px solid rgba(255,255,255,.55);
  animation: cover-pulse 2.6s var(--ease-soft) infinite;
  pointer-events: none;
}
@keyframes cover-pulse {
  0%, 100% { opacity: .18; transform: scale(1); }
  50%      { opacity: .58; transform: scale(1.022); }
}
.prow.live .eq i { animation-play-state: running; }
.prow .eq i { animation-play-state: paused; }

.tp-now {
  flex: 1; display: flex; align-items: center; gap: 12px;
  padding: 6px 8px; border-radius: 999px; min-width: 0;
  background: rgba(255,255,255,.45);
  border: 1px solid rgba(255,255,255,.6);
}
.tp-now .mini { width: 38px; height: 38px; border-radius: 11px; overflow: hidden; flex: none; box-shadow: var(--sh-s); position: relative; }
/* no artwork yet: the slot keeps its shape as a small tinted plate */
.tp-now .mini.empty {
  background: linear-gradient(150deg, #FFF6F2, #FCE3DB 60%, #FFF1EA);
  box-shadow: inset 0 0 0 1px rgba(201, 31, 55, .30);
}
.tp-now .mini img { width: 100%; height: 100%; object-fit: cover; }
.tp-txt { min-width: 0; flex: 1; }
.tp-txt .t { font-size: 13.5px; font-weight: 600; letter-spacing: -.02em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tp-txt .a { font-size: 11.5px; color: var(--ink-45); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tp-bar { position: relative; height: 4px; border-radius: 3px; background: var(--ink-10); margin-top: 6px; cursor: pointer; }
.tp-fill { position: absolute; inset: 0 auto 0 0; width: 0%; border-radius: 3px; background: linear-gradient(90deg, var(--crimson), var(--crimson-deep) 60%, var(--gold)); }
.tp-time { font-size: 11px; color: var(--ink-45); font-variant-numeric: tabular-nums; flex: none; }
.tp-icons { display: flex; align-items: center; gap: 4px; }

/* playlist */
.plist {
  margin: clamp(16px, 3vh, 32px) var(--pad) 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 6px; position: relative; z-index: 2;
  /* `flex: none` because this is the last child of a centred flex column:
     without it the column squeezed the list below its own max-height and
     the scrollbar appeared over content that had room to be shown. */
  flex: none;
  max-height: min(28vh, 210px); overflow-y: auto; overscroll-behavior: contain;
  scrollbar-width: thin; scrollbar-color: var(--ink-10) transparent;
}
.plist::-webkit-scrollbar { width: 6px; }
.plist::-webkit-scrollbar-thumb { background: var(--ink-10); border-radius: 6px; }
.prow {
  display: flex; align-items: center; gap: 14px; padding: 10px 14px;
  border-radius: 16px; cursor: pointer;
  border: 1px solid transparent;
  transition: background .45s var(--ease), border-color .45s var(--ease), transform .45s var(--ease);
}
.prow:hover { background: rgba(255,255,255,.6); border-color: var(--glass-line); transform: translateX(3px); }
.prow.on { background: rgba(255,255,255,.82); border-color: var(--glass-line); box-shadow: var(--sh-s); }
.prow .idx { font-size: 11px; color: var(--ink-25); width: 18px; font-variant-numeric: tabular-nums; }
/* `position: relative` is load-bearing: the placeholder .frame inside is
   absolutely positioned, and without a positioned parent it resolved
   against the whole playlist — eight 40px thumbs became eight stacked
   535px panels filling the section. */
.prow .th { position: relative; width: 40px; height: 40px; border-radius: 11px; overflow: hidden; flex: none; }
.prow .th img { width: 100%; height: 100%; object-fit: cover; }
.prow .nm { font-size: 13.5px; font-weight: 500; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.eq { display: flex; gap: 2px; align-items: flex-end; height: 14px; opacity: 0; transition: opacity .4s; }
.prow.on .eq { opacity: 1; }
.eq i { width: 2px; background: var(--crimson-deep); border-radius: 2px; animation: eq .9s ease-in-out infinite; }
.eq i:nth-child(2) { animation-delay: .18s; } .eq i:nth-child(3) { animation-delay: .36s; }
@keyframes eq { 0%,100% { height: 4px; } 50% { height: 14px; } }

/* =============================================================
   FILMS
   ============================================================= */
/* A single unbroken band of clips: no gaps, no radii, no shadows between
   them — the seam between two clips should read as a cut, not a gutter.
   Bleeds past the section padding to touch both edges of the screen. */
/* the chapter is nothing but the band of clips — no head, no padding */
#films.stack { padding: 0; overflow: hidden; }

.films-strip {
  display: flex;
  gap: 0;
  height: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-x: contain;
  width: 100%;
  scrollbar-width: none;
  cursor: grab;
  background: var(--wine-deep);
}
.films-strip::-webkit-scrollbar { display: none; }
.films-strip.drag { cursor: grabbing; scroll-snap-type: none; scroll-behavior: auto; }

.film {
  /* Sized as a fraction of the strip, never a fixed pixel width, so a whole
     number of clips always spans the section with no dead space at the end.
     Percentages resolve against the scroll container, which — unlike 100vw —
     excludes the scrollbar and so cannot cause a horizontal overflow. */
  position: relative;
  flex: 0 0 25%;
  width: 25%;
  height: 100%;
  border-radius: 0; overflow: hidden;
  box-shadow: none; cursor: pointer;
  scroll-snap-align: start;
}
@media (max-width: 1279px) { .film { flex-basis: 33.3333%; width: 33.3333%; } }
@media (max-width: 1023px) { .film { flex-basis: 50%;      width: 50%; } }
/* one clip per screen on a phone — a quarter-width vertical video would be
   a sliver, and the footage is the whole point of this chapter */
@media (max-width: 639px)  { .film { flex-basis: 100%;     width: 100%; } }
.film img, .film video, .film iframe {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; border: 0;
}
/* the clip plays underneath the poster, which lifts once it is running */
.film video, .film iframe { opacity: 0; transition: opacity .9s var(--ease); }
.film.play video, .film.play iframe { opacity: 1; }
/* a YouTube frame is 16:9 — blow it up so the vertical crop stays filled */
.film iframe { pointer-events: none; }
.film.yt iframe {
  width: 300%; height: 100%; left: -100%;
}

/* navigation floats over the footage itself, one arrow per edge */
.film-nav {
  position: absolute; top: 50%; z-index: 5;
  transform: translateY(-50%);
  width: clamp(46px, 4vw, 60px); height: clamp(46px, 4vw, 60px);
  border-radius: 50%;
  display: grid; place-items: center;
  color: #fff;
  background: rgba(72, 26, 34, .34);
  border: 1px solid rgba(255, 255, 255, .38);
  box-shadow: 0 12px 34px -14px rgba(0, 0, 0, .7);
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  transition: background .4s var(--ease), border-color .4s var(--ease), opacity .4s var(--ease);
}
.film-nav.prev { left: clamp(10px, 2vw, 30px); }
.film-nav.next { right: clamp(10px, 2vw, 30px); }
.film-nav svg { width: 22px; height: 22px; }
.film-nav:hover { background: rgba(201, 31, 55, .78); border-color: rgba(255,255,255,.6); }
/* the transform carries the centring, so the press has to keep it */
.film-nav:active { transform: translateY(-50%) scale(.92); }
/* the veil existed to keep a caption legible; with the captions gone it was
   only darkening the footage */
/* a hairline seam so two dark clips do not merge into one another */
.film + .film::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 1px; z-index: 3;
  background: rgba(255, 255, 255, .12);
}
.film .badge {
  position: absolute; top: 16px; left: 16px;
  padding: 7px 13px; border-radius: 999px; font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  background: rgba(255,255,255,.28); border: 1px solid rgba(255,255,255,.42); color: #fff;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  opacity: 0; transform: translateY(-6px); transition: all .6s var(--ease);
}
.film:hover .badge { opacity: 1; transform: none; }

/* =============================================================
   LIVE
   ============================================================= */
/* =============================================================
   CONCERTS — a listing, not event cards

   The date is the loudest thing on the row: two enormous numerals with the
   month set small and wide underneath, the way a poster prints it. Everything
   else is quiet — city, title, venue, and a ticket link only when there is
   somewhere for it to go.
   ============================================================= */
.live-list { display: flex; flex-direction: column; gap: 8px; }

/* the between-tours state: a line and a way to get in touch, never a void */
.live-empty {
  display: flex; flex-direction: column; align-items: flex-start; gap: 18px;
  padding: clamp(28px, 5vw, 54px) 0 0;
  max-width: 46ch;
}
.live-empty p {
  font-size: clamp(15px, 1.4vw, 18px); line-height: 1.6; color: var(--ink-45);
  margin: 0;
}

.gig {
  position: relative;
  display: grid;
  grid-template-columns: clamp(96px, 11vw, 148px) minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(16px, 3vw, 44px);
  padding: clamp(20px, 2.6vh, 34px) clamp(18px, 2.4vw, 34px);
  border-radius: var(--r-m);
  background: var(--glass);
  border: 1px solid var(--glass-line);
  box-shadow: var(--sh-s), inset 0 1px 0 rgba(255, 255, 255, .85);
  backdrop-filter: blur(14px) saturate(170%);
  -webkit-backdrop-filter: blur(14px) saturate(170%);
  overflow: hidden;
  transition: transform .7s var(--ease), box-shadow .7s var(--ease),
              background .7s var(--ease);
}
.gig:hover { transform: translateY(-4px); box-shadow: var(--sh-m); background: var(--glass-strong); }
/* a featured concert is marked by a crimson edge, not by a badge */
.gig.feat { border-left: 2px solid var(--crimson); }
.gig.off { opacity: .62; }
.gig.off .t, .gig.off .c { text-decoration: line-through; text-decoration-thickness: 1px; }

/* ---------- the date ---------- */
.gd { display: flex; flex-direction: column; line-height: 1; }
.gd .day {
  font-size: clamp(40px, 5.4vw, 74px); font-weight: 500;
  letter-spacing: -.05em; color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.gd .mon {
  margin-top: 8px; font-size: 11px; letter-spacing: .2em;
  color: var(--crimson); text-transform: uppercase;
}
.gd .yr {
  margin-top: 5px; font-size: 11px; letter-spacing: .16em;
  color: var(--ink-45); font-variant-numeric: tabular-nums;
}
/* no date yet — the placeholder takes the same slot without shouting */
.gd .tba {
  font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-45); line-height: 1.5;
}

/* ---------- the body ---------- */
.gb { min-width: 0; }
.gb .c {
  font-size: 11px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--ink-45); margin-bottom: 9px;
}
.gb .t {
  font-size: clamp(19px, 2.1vw, 31px); font-weight: 500;
  letter-spacing: -.035em; line-height: 1.1; color: var(--ink);
}
.gb .v { margin-top: 9px; font-size: 12.5px; color: var(--ink-45); }
.gb .gx {
  margin-top: 11px; font-size: 13.5px; line-height: 1.55;
  color: rgba(24, 19, 19, .58); max-width: 54ch;
}

/* ---------- actions ---------- */
.ga { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; justify-content: flex-end; }
.tag {
  font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  padding: 6px 12px; border-radius: 999px;
  border: 1px solid var(--ink-10); color: var(--ink-45);
}
.tag.out { color: var(--wine); border-color: rgba(125, 31, 50, .3); }
.tag.off { color: var(--ink-25); }
/* placeholder content, marked so it can never read as a real date */
.demo {
  font-size: 9.5px; letter-spacing: .2em; text-transform: uppercase;
  padding: 5px 10px; border-radius: 999px;
  color: var(--crimson); background: rgba(201, 31, 55, .08);
  border: 1px solid rgba(201, 31, 55, .18);
}
.buy {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 12px 24px; border-radius: 999px; font-size: 12px; font-weight: 500;
  background: var(--ink); color: #fff; white-space: nowrap;
  transition: transform .5s var(--ease), background .5s var(--ease);
}
.buy i { font-style: normal; transition: transform .4s var(--ease); }
.buy:hover { transform: translateY(-2px); background: var(--crimson-deep); }
.buy:hover i { transform: translate(2px, -2px); }

/* ---------- optional poster ----------
   A concert with a cover reveals it inside its own row on hover. Pointer
   only: there is no hover on a phone, so nothing there depends on it. */
.gp {
  position: absolute; right: 0; top: 0; bottom: 0; width: 34%;
  opacity: 0; pointer-events: none;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 62%);
  mask-image: linear-gradient(90deg, transparent, #000 62%);
  transition: opacity .8s var(--ease);
}
.gp img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (hover: hover) and (pointer: fine) {
  .gig:hover .gp { opacity: .3; }
}

@media (max-width: 767px) {
  /* one column, and the date turns to run across rather than down */
  .gig {
    grid-template-columns: 1fr;
    gap: 16px;
    padding: 22px 20px 24px;
  }
  .gd { flex-direction: row; align-items: baseline; gap: 10px; }
  .gd .day { font-size: 46px; }
  .gd .mon, .gd .yr { margin-top: 0; }
  .gb .t { font-size: 22px; }
  .ga { justify-content: flex-start; }
  /* a thumb needs a real target, and the link has the row to itself here */
  .buy { padding: 14px 26px; font-size: 13px; }
  .gp { display: none; }
}

/* =============================================================
   FOOTER
   ============================================================= */
.footer { position: relative; z-index: 4; padding: var(--gap) var(--pad) 40px; }
.footer.stack { padding: clamp(80px, 11vh, 128px) var(--pad) 34px; justify-content: space-between; }
.footer.stack .f-bottom { margin-top: clamp(24px, 4vh, 60px); }
.f-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: clamp(30px, 5vw, 80px); }
.f-name { font-size: clamp(30px, 5vw, 74px); font-weight: 500; letter-spacing: -.05em; line-height: .95; }
.f-col h4 { font-size: 10.5px; letter-spacing: .24em; text-transform: uppercase; color: var(--ink-45); margin-bottom: 20px; font-weight: 500; }
.f-col a, .f-col p { display: block; font-size: 14px; color: var(--ink-70); margin-bottom: 11px; transition: color .4s var(--ease), transform .4s var(--ease); }
.f-col a:hover { color: var(--ink); transform: translateX(4px); }
.f-bottom {
  display: flex; justify-content: space-between; align-items: center; gap: 20px; flex-wrap: wrap;
  margin-top: clamp(40px, 8vh, 90px); padding-top: 24px; border-top: 1px solid var(--ink-10);
  font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-25);
}

/* =============================================================
   RESPONSIVE
   ============================================================= */
@media (max-width: 1024px) {
  .nav-links { display: none; }
  .about { grid-template-columns: 1fr; }
  .f-grid { grid-template-columns: 1fr 1fr; }
  #gal-info { width: 300px; }
  #gal-view { width: 300px; height: 150px; }
  #gal-view span { font-size: 78px; }
}
/* Below the tablet break a chapter can no longer be trusted to fit one
   screen, and a clipped sticky box would swallow its own tail — so the
   stack relaxes into normal flow while keeping the layered lip. */
@media (max-width: 900px) {
  .hero.stack, .section.stack, .music.stack, .footer.stack {
    position: relative; height: auto; min-height: 100svh;
  }
  .stack { overflow: visible; }

  /* …except the sections that genuinely fit one screen. Dropping every
     chapter into normal flow also threw away the layered ride, so after the
     archive the whole page scrolled flat. `.can-pin` is set from JS after
     measuring, so a chapter whose content overruns the viewport still scrolls
     normally and cannot swallow its own tail. */
  #archive.stack,
  .stack.can-pin { position: sticky; top: 0; height: 100svh; min-height: 0; }
  /* taller chapters stick by their bottom edge — `top` is set from JS */
  .stack.pin-tall { position: sticky; height: auto; min-height: 100svh; }

  .about-figure { height: auto; aspect-ratio: 4/5; }
  .plist { max-height: none; overflow: visible; }
}

@media (max-width: 767px) {
  .hero { padding-top: 96px; }
  .hero-portrait { width: min(78vw, 46vh); }
  .hero-copy { position: static; max-width: none; margin-top: 34px; text-align: center; }
  .hero-meta, .scroll-cue { display: none; }
  .hero { flex-direction: column; justify-content: center; }
  .f-grid { grid-template-columns: 1fr; }
  .transport { flex-wrap: wrap; justify-content: center; }
  .tp-icons { display: none; }
  #gal-info { left: 0; right: 0; width: auto; bottom: 44px; }
  #gal-collection { font-size: 20px; }
  #gal-view { left: 16px; right: 16px; width: auto; height: 100px; bottom: 56px; }
  #gal-view span { font-size: 64px; }
  #gal-footer { left: 16px; right: 16px; justify-content: space-between; gap: 0; font-size: 11px; }
  .sec-head { flex-direction: column; align-items: flex-start; }

  /* ---- nav on a phone ----
     Brand + language + Contact would not fit a 375px bar: the button was
     landing past the right edge entirely. The label drops away and the CTA
     becomes the envelope alone, and the wordmark tightens to make room. */
  .nav { padding: 14px 16px; }
  .nav-right { gap: 8px; }
  .brand { font-size: 11px; letter-spacing: .07em; }
  .brand .dot { width: 6px; height: 6px; }
  .nav-cta {
    width: 40px; height: 40px; padding: 0;
    justify-content: center; gap: 0;
  }
  .nav-cta svg { display: block; width: 17px; height: 17px; }
  .nav-cta span { display: none; }

  /* ---- awards on a phone ---- */
  /* narrower than this and the longer honours run to four cramped lines */
  .aw-card { width: min(80vw, 320px); }
  .aw-title { font-size: 16.5px; }
  .aw-text { font-size: 14px; }
  .aw-body { padding: 18px 20px 24px; }
  .aw-hint { flex-wrap: wrap; gap: 12px; }
  #awards .sec-head { align-items: flex-start; }
  .aw-count { align-self: flex-start; }

  .cf-wrap { padding: 0 2px; gap: 2px; }
  .cf-arrow { width: 42px; height: 42px; }
  .cf-arrow svg { width: 19px; height: 19px; }
}

/* =============================================================
   MOBILE PERFORMANCE
   A phone repaints a backdrop-filter over the whole area behind it on every
   scrolled pixel, and a blurred SVG re-rasterises with it. Stacked, they are
   what made this page crawl. Below the tablet break the glass falls back to
   flat translucency and the soft-focus layers are dropped outright —
   the layout is untouched.
   ============================================================= */
@media (max-width: 900px) {
  /* Named selectors did not hold. Measured on a 390px viewport, 38 elements
     were still compositing a backdrop blur — eight of them `blur(22px)
     saturate(1.8)` — because the list below only covered the ones that
     existed when it was written, and every card added since brought its own.

     So the rule is inverted: nothing blurs its backdrop on a phone unless it
     is on the allowlist, and the allowlist is the navigation bar alone,
     because that is the one surface a reader sees against moving content.
     Everything else keeps its translucency, its border and its highlight —
     which is what the glass actually reads as at this size — and drops the
     one property that costs a full-area GPU repaint per scrolled pixel. */
  *:not(.nav) {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
  .nav {
    backdrop-filter: blur(14px) !important;
    -webkit-backdrop-filter: blur(14px) !important;
  }

  /* the surfaces that lost their blur get a solid enough ground to stay
     legible over whatever passes behind them */
  .nav-links, .transport, .cf-arrow, .tp-now { background: rgba(255,255,255,.92); }
  .frame span { background: rgba(255,255,255,.86); }
  .cap { background: rgba(255,252,250,.92) !important; }
  .badge { background: rgba(255,252,250,.9) !important; }
  .gig, .f-card, .glass { background: rgba(255,252,250,.9); }

  /* soft-focus decoration: invisible at this size, expensive at any size */
  .hero-glow, .music-bg { filter: none; }
  #flowers .fl, #flowers-front .fl { filter: none !important; }
  .cover { filter: none !important; }

  /* Compositor layers are not free — each one is a texture held in GPU
     memory for as long as the hint is set. 52 were live at once, 21 of them
     archive cards that are three screens away and not moving. The hint now
     belongs only to things that are actually animating right now: the hero
     blooms and the pinned stack. */
  .about-figure, .film, .f-card, .gig, .bp-card, .cover,
  #gal-wrap, .chapter, .aw-card {
    will-change: auto;
  }
}

/* The archive card is promoted only while the gallery is the thing on
   screen; gallery.js adds `live` when its panel is in view and removes it
   again afterwards, so 21 permanent layers become 21 temporary ones. */
#gal-panel.live .bp-card { will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}


/* =============================================================
   ARCHIVE VIEWER

   Warm ivory rather than the usual black scrim: these are photographs from
   different years and different cameras, and a black room makes them all
   look like one set. No filter, no tint, no blend mode touches the image —
   a black-and-white print stays black-and-white and a colour frame keeps
   its colour. The palette shows up in the chrome around it instead.
   ============================================================= */
html.vw-lock, html.vw-lock body { overflow: hidden; }

.vw {
  position: fixed; inset: 0; z-index: 300;
  display: flex; align-items: center; justify-content: center;
  padding: clamp(16px, 4vh, 54px) clamp(16px, 6vw, 96px);
  opacity: 0; pointer-events: none;
  transition: opacity .42s var(--ease);
}
.vw.on { opacity: 1; pointer-events: auto; }

.vw-scrim {
  position: absolute; inset: 0;
  background: rgba(255, 249, 246, .93);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
}

.vw-fig {
  position: relative; margin: 0;
  max-width: 100%; max-height: 100%;
  display: flex; flex-direction: column; align-items: center; gap: 18px;
  transform-origin: 50% 50%;
  transition: transform .52s var(--ease), opacity .42s var(--ease);
}
.vw-fig.in { transform: none; opacity: 1; }
.vw-fig img {
  display: block; max-width: 100%;
  max-height: calc(100vh - clamp(150px, 22vh, 230px));
  object-fit: contain;
  border-radius: 4px;
  /* the only decoration: a hairline and a soft drop, like a mounted print */
  box-shadow: 0 40px 90px -30px rgba(72, 26, 34, .38), 0 2px 10px rgba(72, 26, 34, .1);
}

.vw-cap { text-align: center; max-width: 62ch; }
.vw-meta {
  font-size: 11px; letter-spacing: .2em; text-transform: uppercase;
  color: var(--ink-45);
  display: flex; align-items: center; justify-content: center; gap: 10px;
}
.vw-meta b { color: var(--crimson); font-weight: 500; font-variant-numeric: tabular-nums; }
.vw-meta i { font-style: normal; color: var(--ink-25); }
.vw-text { margin-top: 9px; font-size: 14px; line-height: 1.55; color: var(--ink-70); }

.vw-x, .vw-nav {
  position: absolute; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  border-radius: 999px; cursor: pointer; color: var(--ink);
  background: rgba(255, 255, 255, .74);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--sh-s);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  transition: background .3s var(--ease), transform .3s var(--ease), color .3s;
}
.vw-x:hover, .vw-nav:hover { background: #fff; color: var(--crimson); }
.vw-x { top: clamp(14px, 3vh, 30px); right: clamp(14px, 3vw, 34px); width: 44px; height: 44px; }
.vw-nav { top: 50%; margin-top: -26px; width: 52px; height: 52px; }
.vw-nav.prev { left: clamp(10px, 2vw, 30px); }
.vw-nav.next { right: clamp(10px, 2vw, 30px); }
.vw-nav:hover.prev { transform: translateX(-2px); }
.vw-nav:hover.next { transform: translateX(2px); }

.vw-count {
  position: absolute; left: 50%; transform: translateX(-50%);
  bottom: clamp(14px, 3vh, 28px);
  font-size: 11px; letter-spacing: .2em; color: var(--ink-45);
  font-variant-numeric: tabular-nums;
}

/* a single petal crossing the frame as the picture changes — it starts and
   ends off the image, and never sits over a face */
.vw-petal {
  position: absolute; left: 50%; top: 0; width: 30px; height: 30px;
  opacity: 0; pointer-events: none; z-index: 3;
}
.vw.petal .vw-petal { animation: vw-drift 1.15s var(--ease); }
.vw-petal svg { width: 100%; height: 100%; display: block; }
@keyframes vw-drift {
  0%   { opacity: 0; transform: translate(-160%, -18vh) rotate(-30deg); }
  22%  { opacity: .85; }
  100% { opacity: 0; transform: translate(150%, 92vh) rotate(220deg); }
}

@media (max-width: 767px) {
  .vw { padding: 14px; }
  .vw-fig img { max-height: calc(100vh - 200px); }
  /* the arrows are a pointer affordance; the phone swipes instead */
  .vw-nav { display: none !important; }
  .vw-x { width: 40px; height: 40px; top: 12px; right: 12px; }
}
@media (prefers-reduced-motion: reduce) {
  .vw-fig { transition: opacity .3s linear; }
  .vw.petal .vw-petal { animation: none; }
}

/* an archive thumbnail is a control now */
.bp-card { cursor: zoom-in; }

/* =============================================================
   ARCHIVAL PRINTS IN THE BIOGRAPHY
   A mount and a hairline, not a scrapbook: ivory border, one soft shadow,
   a degree or two of rotation. No tape, no torn edges, no sepia.
   ============================================================= */
.ch-shot {
  background: var(--white);
  padding: 8px 8px 8px;
  border: 1px solid rgba(24, 19, 19, .07);
  border-radius: 3px;
  box-shadow: 0 22px 46px -22px rgba(72, 26, 34, .34), 0 1px 4px rgba(72, 26, 34, .07);
  cursor: zoom-in;
}
.ch-shot img { border-radius: 1px; }
.ch-shot figcaption {
  margin-top: 7px; font-size: 9.5px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-45); text-align: center;
  font-variant-numeric: tabular-nums;
}

/* the year on an archive card — only rendered when the date is known */
.bp-year {
  position: absolute; left: 10px; bottom: 9px; z-index: 2;
  font-size: 9.5px; letter-spacing: .18em; color: #fff;
  font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 6px rgba(24,19,19,.6);
  opacity: 0; transition: opacity .5s var(--ease);
}
.bp-card:hover .bp-year { opacity: .92; }
.bp-card { position: relative; }

/* the footer's columns are generated now, so the grid has to hold whatever
   number of groups actually has content — one, or none at all */
.f-cols { display: contents; }

/* The artist's name, opening the H1 above the display lines. Small and
   letterspaced so the hero composition still reads as it did — the big
   type is still the tagline — but the page now states whose site this is
   in its own heading rather than only in the nav. */
.hero-name {
  display: block;
  font-size: clamp(11px, 1.05vw, 13px);
  font-weight: 500;
  letter-spacing: .26em;
  text-transform: uppercase;
  color: var(--crimson);
  margin-bottom: clamp(12px, 1.6vh, 20px);
  line-height: 1.4;
}
@media (max-width: 767px) {
  .hero-name { font-size: 10.5px; letter-spacing: .2em; margin-bottom: 10px; }
}

/* Footer links were 17px tall — fine for a mouse, far under the 44px a thumb
   needs. Padding rather than font-size, so the type keeps its scale and only
   the target grows. */
@media (max-width: 767px) {
  .f-col a {
    display: block;
    min-height: 44px;
    padding: 13px 0;
    line-height: 1.35;
  }
  .f-col h4 { margin-bottom: 4px; }
}


/* =============================================================
   HERO SCENE — depth, glass and a local reveal

   The parallax is driven by two custom properties, --px and --py, written
   once per frame on #hero by hero.js. Every layer multiplies them by its own
   amplitude in its own transform, so the per-frame JS cost is two style
   writes no matter how many layers exist, and the compositor does the rest.
   That is why there is no per-layer loop anywhere in the script.

   Amplitudes follow the depth table: the background barely stirs, the
   portrait is nearly static, and the card moves most. Foreground elements
   run slightly counter to the rest, which is what stops the whole scene
   sliding as one sheet.
   ============================================================= */
#hero { --px: 0; --py: 0; --lift: 0; }

.hero-glow {
  transform: translate(-50%, -50%)
             translate3d(calc(var(--px) * 5px), calc(var(--py) * 3px), 0);
  transition: transform .9s var(--ease-soft);
}

.hero-silk {
  position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 1;
  opacity: 0;
  transform: translate3d(calc(var(--px) * -6px), calc(var(--py) * -4px), 0);
  transition: opacity 1.2s var(--ease);
}
.hero.ready .hero-silk { opacity: 1; }

.hero-portrait {
  /* nearly static: the subject is the anchor the rest moves around */
  transform: translate3d(calc(var(--px) * 3px), calc(var(--py) * 2px), 0)
             scale(calc(1 + var(--lift) * 0.02));
}

.hero-copy {
  transform: translate3d(calc(var(--px) * -4px), calc(var(--py) * -2px), 0);
}

/* ---------- glass card ---------- */
.hero-card {
  position: absolute; z-index: 14;
  /* Far enough left to sit ON the figure, not beside it. Measured at 1280
     the card began at 66.8% while the portrait ended at 63.3% — adjacent,
     which reads as two objects in a row rather than one in front of the
     other. The overlap is what makes the depth. */
  right: clamp(20px, 21vw, 320px);
  /* Overlap belongs on the dress, never on the face. Centred vertically the
     card's top landed at 27.7% while the face occupies roughly 19-50% — it
     sat straight across it. Anchored lower it still passes in front of the
     figure, which is the point, but crosses the skirt instead. */
  top: 68%;
  /* Sized so it fits between the face above and the meta line below: at
     1440 a 330px card was 44% of the viewport tall and could not clear
     both. Narrower is also truer to the reference, where the card is a
     slim panel rather than a poster. */
  width: clamp(210px, 17vw, 260px);
  aspect-ratio: 5 / 6;
  perspective: 900px;
  pointer-events: auto;
  opacity: 0;
  transform: translate3d(calc(var(--px) * 11px), calc(-50% + var(--py) * 7px), 0);
  transition: opacity .9s var(--ease);
}
.hero.ready .hero-card { opacity: 1; }

.hc-inner {
  position: relative; width: 100%; height: 100%;
  border-radius: clamp(24px, 2vw, 34px);
  overflow: hidden;
  transform-style: preserve-3d;
  /* the tilt is small on purpose — past about four degrees the card reads as
     a toy rather than a pane of glass */
  transform: rotateY(calc(var(--px) * 4deg)) rotateX(calc(var(--py) * -3deg));
  transition: transform .5s var(--ease-soft);
  background: linear-gradient(135deg,
    rgba(255, 249, 246, .48),
    rgba(201, 31, 55, .10));
  border: 1px solid rgba(255, 255, 255, .58);
  box-shadow:
    0 28px 80px rgba(117, 13, 27, .14),
    inset 0 1px 0 rgba(255, 255, 255, .65);
  backdrop-filter: blur(14px) saturate(1.08);
  -webkit-backdrop-filter: blur(14px) saturate(1.08);
}

/* Refraction, confined to the rim. A spectral wash over the whole pane is
   the cheap version of this effect and reads as a filter; in the reference
   the colour only ever appears where the glass has an edge. */
.hc-edge {
  position: absolute; inset: 0; border-radius: inherit;
  pointer-events: none;
  padding: 1.5px;
  background: linear-gradient(
    calc(120deg + var(--px) * 40deg),
    rgba(255, 173, 190, .85) 0%,
    rgba(255, 236, 206, .55) 22%,
    rgba(173, 216, 255, .60) 44%,
    rgba(255, 255, 255, .20) 62%,
    rgba(201, 31, 55, .35) 100%);
  -webkit-mask:
    linear-gradient(#000 0 0) content-box,
    linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: .9;
}

/* ---------- the reveal ----------
   The second photograph is always in the DOM and always loaded; what changes
   is how much of it the mask lets through. The mask follows the pointer with
   its own damping, so it trails the cursor slightly instead of being welded
   to it — that lag is most of what makes it read as glass. */
.hc-reveal {
  position: absolute; inset: 0;
  --rx: 50%; --ry: 50%; --r: 0px;
  -webkit-mask-image: radial-gradient(circle var(--r) at var(--rx) var(--ry),
    #000 0%, #000 55%, transparent 100%);
  mask-image: radial-gradient(circle var(--r) at var(--rx) var(--ry),
    #000 0%, #000 55%, transparent 100%);
  transition: none;
}
.hc-reveal img {
  width: 100%; height: 100%; object-fit: cover;
  /* the picture drifts less than the mask does, so the two decouple and the
     edge behaves like something seen through a lens */
  transform: translate3d(calc(var(--px) * -5px), calc(var(--py) * -4px), 0) scale(1.06);
}

.hc-body {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: clamp(16px, 1.5vw, 22px);
  display: flex; flex-direction: column; align-items: flex-start; gap: 9px;
}
.hc-kicker {
  font-size: 10px; letter-spacing: .22em; text-transform: uppercase;
  color: var(--crimson);
}
.hc-title {
  font-size: clamp(15px, 1.25vw, 19px); font-weight: 500;
  letter-spacing: -.02em; line-height: 1.2; color: var(--ink);
  margin: 0;
}
.hc-play {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 4px; padding: 9px 15px 9px 11px;
  border-radius: 999px; font-size: 12px; font-weight: 500;
  background: var(--ink); color: #fff; text-decoration: none;
  transition: background .35s var(--ease), transform .35s var(--ease);
}
.hc-play:hover { background: var(--crimson-deep); transform: translateY(-1px); }
.hc-play:focus-visible {
  outline: 2px solid var(--crimson); outline-offset: 3px;
}
.hc-ico { display: grid; place-items: center; }

/* the whole card is focusable, and focus shows a predictable static reveal */
.hero-card:focus-within .hc-inner { transform: none; }
.hero-card:focus-within .hc-reveal { --r: 150px; --rx: 50%; --ry: 42%; }

/* ---------- first-scroll transition ----------
   Driven by --lift, which hero.js sets from scroll progress. Nothing is
   pinned and the wheel is never intercepted: the page scrolls natively and
   these are only transforms responding to where it already is. */
.hero-copy   { will-change: transform; }
.hero.leaving .hero-copy {
  transform: translate3d(calc(var(--px) * -4px), calc(-24px - var(--lift) * 40px), 0);
  opacity: calc(1 - var(--lift) * 1.1);
}
.hero.leaving .hero-card {
  transform: translate3d(calc(var(--px) * 11px), calc(-50% + var(--lift) * 60px), 0);
  opacity: calc(1 - var(--lift) * 1.4);
}
.hero.leaving .hero-meta { opacity: calc(1 - var(--lift) * 1.3); }

/* ---------- load choreography ----------
   Each layer has its own delay, so the scene assembles back to front rather
   than appearing at once. The background is painted immediately — there is
   never a white flash to fade in from. */
.hero-portrait { opacity: 0; transition: opacity 1s var(--ease), transform .9s var(--ease-soft); }
.hero.ready .hero-portrait { opacity: 1; }
.hero-copy, .hero-meta { opacity: 0; transition: opacity .9s var(--ease), transform .8s var(--ease-soft); }
.hero.ready .hero-copy { opacity: 1; transition-delay: .18s; }
.hero.ready .hero-meta { opacity: 1; transition-delay: .3s; }
.hero.ready .hero-card { transition-delay: .42s; }
.hero.ready .hero-silk { transition-delay: .1s; }

@media (max-width: 900px) {
  /* On a phone the card stops floating over the portrait and becomes a strip
     under it, where it cannot cover the face and is inside thumb reach. */
  .hero-card {
    position: static;
    width: 100%;
    max-width: 420px;
    aspect-ratio: auto;
    margin: clamp(14px, 3vh, 24px) auto 0;
    transform: none;
    perspective: none;
  }
  .hc-inner { transform: none; aspect-ratio: auto; }
  .hc-body { position: static; padding: 14px 16px; }
  .hc-reveal { display: none; }
  .hero-silk { opacity: .35; }
  /* Order on a phone must match the order of importance: the singer, then
     her name and the headline, then the music. In DOM order the card comes
     first, so it is moved after the copy rather than being re-nested. */
  .hero { flex-direction: column; }
  .hero-portrait { order: 1; }
  .hero-copy     { order: 2; position: static; max-width: none;
                   text-align: center; align-items: center; margin-top: 20px; }
  .hero-card     { order: 3; }
  .hero-copy .display { font-size: clamp(30px, 11vw, 46px); }
  /* 44px floor, same rule the rest of the site follows */
  .hc-play { min-height: 44px; padding: 12px 20px 12px 15px; font-size: 13px; }
}

/* Reduced motion keeps the composition and drops the machinery: no inertia,
   no tilt, no scroll-driven transforms. The reveal becomes a fixed, visible
   state rather than something that must be chased with a pointer. */
@media (prefers-reduced-motion: reduce) {
  .hero-glow, .hero-silk, .hero-portrait, .hero-copy, .hero-card,
  .hc-inner, .hc-reveal img {
    transform: none !important;
    transition: opacity .3s linear !important;
  }
  .hero.leaving .hero-copy,
  .hero.leaving .hero-card { opacity: 1; }
  .hc-reveal { --r: 140px; --rx: 50%; --ry: 42%; }
}


/* =============================================================
   PAINT BUDGET

   Measured on desktop 1440x900 before this block:
     filter: blur()      8 elements, 3.48 viewports of area
     backdrop-filter    47 elements, 1.92 viewports
     compositing layers 26

   The two largest blurs were sitting on radial gradients, which have no
   hard edge to soften — they were re-filtering softness that was already
   there. Six more were blur(0px): no visual effect, but a filter with a
   radius of zero still forces its own layer and its own pass.

   The awards rail carried 24 backdrop surfaces for a carousel that shows
   three at a time. Below they keep the translucency, the hairline and the
   inner highlight — which is what reads as glass at that size — and drop
   the backdrop resample, which is what costs.
   ============================================================= */
.aw-card, .aw-year, .cap, .badge {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
}
.aw-card { background: rgba(255, 252, 250, .90); }
.aw-year { background: rgba(255, 252, 250, .86); }
.cap     { background: rgba(255, 252, 250, .88); }
.badge   { background: rgba(255, 252, 250, .86); }

/* The chapters cross-fade with opacity and transform; the blur they also
   carried was the expensive third of that and the least visible. */
.chapter { filter: none; }

/* The cover flow is only worth its compositor layers while it is being
   looked at. Off screen the same seven promoted, preserve-3d, soft-shadowed
   boxes were still being composited on every frame, which is what made the
   music -> film handoff the slowest transition on the page. */
#music.near .cover { will-change: transform, opacity; }

/* =============================================================
   SCROLL COST — the glass controls
   Measured on the music -> film handoff: turning backdrop-filter off was
   worth +31.9fps there, and the shadows another +20. These are all small
   controls sitting on a light page, where a frosted backdrop and a plain
   translucent one are visually almost the same thing -- but the frosted one
   has to re-read a moving backdrop on every single frame of the transition,
   and it can never be cached.

   The big glass surfaces keep their frost. Only the buttons and the
   transport bar, where nobody can tell, give it up.
   ============================================================= */
.transport,
.cf-arrow,
.film-nav,
.tp-btn,
.aw-nav {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  background-color: rgba(255, 252, 250, .9);
}

/* a 70px shadow blur on seven covers is paid for on every frame they move */
.cover {
  box-shadow: 0 18px 34px -22px rgba(72, 26, 34, .40), inset 0 0 0 1px rgba(255,255,255,.5);
}

/* Seven large blooms, each hinted with "transform, opacity", is seven big
   compositor surfaces, and naming opacity forces a separate one whether the
   opacity actually changes or not. Hiding the layer outright measured at
   +15.9fps on hero -> story, so this is where the cost of the restored
   poppies lives. Trimming the hint alone did not move the number; the area
   is the price. */
#flowers .fl, #flowers-front .fl { will-change: transform; }

/* =============================================================
   AWARDS — the card has to fit its own text

   Every chapter is exactly one screen tall, so the rail gets whatever the
   viewport leaves after the heading. On a short screen that is not enough
   for a 4:3 photograph plus four lines of Kazakh, and the card quietly cut
   the description off mid-line -- measured at 1920x809 and 1536x780.

   The photograph is the half with slack in it: 236px of a 454px card. It
   gives up height as the viewport gets shorter, and the type tightens with
   it, so the words always fit instead of being clipped.
   ============================================================= */
@media (max-height: 900px) {
  .aw-shot { aspect-ratio: 16 / 10; }
}
@media (max-height: 820px) {
  .aw-shot { aspect-ratio: 16 / 9; }
  .aw-body { padding: 15px 18px 18px; gap: 7px; }
  .aw-title { font-size: 15px; }
  .aw-text  { font-size: 13px; line-height: 1.55; }
}
@media (max-height: 740px) {
  .aw-shot { aspect-ratio: 2 / 1; }
  .aw-body { padding: 13px 16px 16px; gap: 6px; }
  .aw-title { font-size: 14.5px; line-height: 1.28; }
  .aw-text  { font-size: 12.5px; line-height: 1.5; }
  .aw-place { font-size: 10px; }
}

/* =============================================================
   AWARDS — controls beside the cards
   They used to live in the hint line under the rail, which on a tall screen
   sat below the fold, so the rail looked like it had no controls at all.
   ============================================================= */
.aw-wrap { position: relative; }

.aw-side {
  /* Over the photograph, not the words. At the vertical middle the left
     control sat straight on the first card title; the image band is the one
     place on a card where a floating control hides nothing that has to be
     read. */
  position: absolute; top: 26%;
  transform: translateY(-50%);
  z-index: 3;
  width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  background: rgba(255, 252, 250, .94);
  border: 1px solid var(--glass-line);
  box-shadow: 0 10px 26px -12px rgba(72, 26, 34, .34);
  color: var(--ink-70);
  cursor: pointer;
  transition: background .25s var(--ease), color .25s var(--ease), opacity .25s var(--ease);
}
.aw-side svg { width: 17px; height: 17px; display: block; }
.aw-side.prev { left: 10px; }
.aw-side.next { right: 10px; }
.aw-side:hover { background: var(--crimson-deep); color: #fff; }
/* at the ends they fade rather than vanish, so the rail does not twitch */
.aw-side[disabled] { opacity: .3; pointer-events: none; }

/* the rail keeps its own edge padding so a card never hides under a control */
.aw-wrap .aw-rail { padding-left: 4px; }

@media (max-width: 900px) {
  /* a thumb reaches the cards directly here, so the controls step aside */
  .aw-side { display: none; }
}

/* =============================================================
   TAIL SCROLL COST
   Measured with one continuous wheel gesture rather than separate notches,
   which is what finally showed the cost: awards -> concerts ran at 34fps
   with 150ms frames.
   ============================================================= */

/* A chapter the reader has already passed is behind an opaque one and cannot
   be seen. Hiding it costs nothing visually and takes it out of compositing
   for the rest of the page. Layout is untouched, so the stack is unchanged. */
.stack.covered { visibility: hidden; }

/* Every award card was its own backdrop-filtered surface -- a dozen of them,
   each re-reading what is behind it on every frame, on a chapter that is
   itself sliding. Worth 16fps on the way into the concerts. They are cards on
   a light page, so a solid translucent ground is indistinguishable. */
.aw-card {
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  background-color: rgba(255, 252, 250, .94);
}

/* =============================================================
   STORY — one chapter legible at a time

   The chapters share a single absolute position and used to fade in and out
   over the same half second, so a quick scroll left the previous text at
   half opacity underneath the arriving one -- "Мировые конкурсы" printed
   across "Большая сцена".

   Three phases now, and they never overlap: the outgoing chapter clears
   fast and at once, the incoming one waits until it has gone, and the
   current one simply holds. The delay belongs to the transition itself, so
   scrolling back up plays it correctly in reverse.
   ============================================================= */
.chapter {
  /* arriving: hold back until the previous chapter has cleared */
  transition:
    opacity .34s var(--ease) .2s,
    transform .42s var(--ease) .2s,
    filter .34s var(--ease) .2s;
}
.chapter.out {
  /* leaving: immediately, and faster than the next one arrives */
  transition:
    opacity .2s var(--ease) 0s,
    transform .28s var(--ease) 0s,
    filter .2s var(--ease) 0s;
}
@media (prefers-reduced-motion: reduce) {
  .chapter, .chapter.out { transition: opacity .01s linear; }
}

/* =============================================================
   ARCHIVE — the second photograph under the pointer

   Two images at identical size inside one figure. The upper one is always
   there; the lower is masked to a soft circle that follows the pointer, so
   moving across the card reads as uncovering another version of the picture
   rather than as cutting a hole in it.

   The figure is a child of .bp-card. The gallery timeline scales .bp-card
   and nothing here touches that, so the two never fight over a transform.
   ============================================================= */

/* A custom property has to be registered before it can be animated -- this
   is what lets the radius grow and shrink instead of snapping. Where it is
   unsupported the circle appears at full size and only the fade is animated,
   which still reads correctly. */
@property --reveal-r {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}

.reveal-photo {
  position: absolute; inset: 0;
  margin: 0;
  overflow: hidden;
  border-radius: inherit;
}
.reveal-photo img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

/* Scoped to the gallery card. The hero has its own lens with its own
   layers, and a bare .reveal-alt reached into it and hid the picture. */
.reveal-photo .reveal-alt {
  --reveal-r: 0px;
  opacity: 0;
  /* leaving: quicker than it arrived */
  transition: --reveal-r .24s cubic-bezier(.4, 0, .2, 1),
              opacity .22s ease;
  /* the edge is soft over about a third of the radius, so there is no rim */
  -webkit-mask-image: radial-gradient(circle var(--reveal-r) at var(--rx, 50%) var(--ry, 50%),
    #000 0%, #000 58%, rgba(0, 0, 0, .48) 80%, transparent 100%);
  mask-image: radial-gradient(circle var(--reveal-r) at var(--rx, 50%) var(--ry, 50%),
    #000 0%, #000 58%, rgba(0, 0, 0, .48) 80%, transparent 100%);
  /* the faintest lens: enough to feel like a different plane, not a loupe */
  transform: scale(1.02);
  filter: contrast(1.03);
  pointer-events: none;
}

.reveal-photo.revealing .reveal-alt {
  --reveal-r: 98px;      /* a 196px circle */
  opacity: 1;
  /* arriving: slower, and eased so it opens rather than pops */
  transition: --reveal-r .34s cubic-bezier(.22, 1, .36, 1),
              opacity .3s ease;
}

/* Touch and coarse pointers never get the follower: the alternate simply
   never shows, the card behaves as an ordinary photograph, and nothing
   listens for a gesture that belongs to the page. */
/* `not all and (a) and (b)` negates the whole query and is unreliable across
   engines -- on an emulated phone it left the layer displayed. Two plain
   conditions say the same thing and are honoured everywhere. */
/* Note the selector: .reveal-photo img above is (0,1,1) and out-specifies a
   bare .reveal-alt, which is (0,1,0) -- the layer stayed displayed on a
   phone until this matched it with equal weight. */
@media (hover: none), (pointer: coarse) {
  .reveal-photo .reveal-alt { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .reveal-photo .reveal-alt { display: none; }
}
