/* ============================================================
   Woman Metamorphosis — v2
   Unlit house, tungsten light, full-bleed photo essay.
   Five type sizes. Nothing else.
   ============================================================ */

/* --- Self-hosted type (generated; see fonts.css in the working folder) ---
   latin + latin-ext only. latin-ext is what carries the Romanian ș ț ă î â,
   so both are required; `unicode-range` still lets the browser fetch only what
   a page uses. These replaced a fonts.googleapis.com <link> that Lighthouse
   measured at 915 ms of render-blocking time on throttled mobile, and that
   sent every EU visitor's IP to Google with no consent story. Both families
   are VARIABLE, so one file covers a whole weight range — 6 files, not 14. */
@font-face { font-family: 'Cormorant Garamond'; font-style: italic; font-weight: 300 400;
  font-display: swap; src: url(assets/fonts/cormorant-garamond-italic-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: 'Cormorant Garamond'; font-style: italic; font-weight: 300 400;
  font-display: swap; src: url(assets/fonts/cormorant-garamond-italic-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Cormorant Garamond'; font-style: normal; font-weight: 300 400;
  font-display: swap; src: url(assets/fonts/cormorant-garamond-normal-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: 'Cormorant Garamond'; font-style: normal; font-weight: 300 400;
  font-display: swap; src: url(assets/fonts/cormorant-garamond-normal-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }
@font-face { font-family: 'Manrope'; font-style: normal; font-weight: 300 500;
  font-display: swap; src: url(assets/fonts/manrope-normal-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; }
@font-face { font-family: 'Manrope'; font-style: normal; font-weight: 300 500;
  font-display: swap; src: url(assets/fonts/manrope-normal-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; }

:root {
  /* --- Colour: an unlit room, and one tungsten source ---
     There is no coloured ground. The house is neutral warm charcoal so that
     the ONLY colour on the site is the light itself. A wine/burgundy ground
     under a gold accent reads as red velvet — cabaret, not gallery. */
  --noir:    #0B0A0C;   /* the ground — neutral, a hair cool so gold reads warm */
  --ember:   #17140F;   /* where the light falls off */
  --edge:    #2A241E;   /* rules, edges — warm charcoal, never red */
  --fresnel: #E8B672;   /* tungsten. the only bright accent */
  --vellum:  #F2EAE0;   /* warm ink */
  --ash:     #9A9086;   /* utility ink — neutral warm grey (6.3:1 on --noir) */

  --scrim:   11,10,12;  /* --noir as raw channels, for rgba() veils */

  --rule: rgba(242,234,224,0.13);

  /* --- Type: five sizes, no more --- */
  --t-xl: clamp(3rem, 7.4vw, 6.4rem);      /* section + page titles */
  --t-l:  clamp(1.9rem, 3.4vw, 3.1rem);    /* names, pull-quotes */
  --t-m:  clamp(1.2rem, 1.55vw, 1.45rem);  /* leads, subtitles */
  --t-s:  1.125rem;                         /* body */
  --t-xs: 0.6875rem;                        /* labels */

  --display: "Cormorant Garamond", Georgia, serif;
  --body: "Manrope", system-ui, sans-serif;

  /* `ch` is the width of "0", which in Manrope 300 is far wider than the
     average lowercase letter — 72ch rendered ~95 real characters per line.
     These are tuned against MEASURED chars-per-line, not against the unit. */
  --measure: 52ch;      /* the reading column: ~66 real chars */
  --measure-wide: 74ch; /* container for text + breakout quotes */
  --page: 1440px;
  --gut: clamp(20px, 5vw, 64px);
  --section: clamp(90px, 12vh, 160px);
  --ease: cubic-bezier(0.19, 1, 0.22, 1);
}

* { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
  background: var(--noir); color: var(--vellum);
  font-family: var(--body); font-weight: 300;
  font-size: var(--t-s); line-height: 1.75;
  -webkit-font-smoothing: antialiased; overflow-x: hidden;
}
img { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--fresnel); outline-offset: 5px; }

/* --- The house: an unlit room with one warm source overhead ---
   Warm charcoal, not wine. The falloff is what suggests a light source above
   the stage; it must never read as a colour in its own right. */
.house { position: fixed; inset: 0; z-index: -2; pointer-events: none; overflow: hidden;
  background: radial-gradient(125% 85% at 50% -10%, var(--ember) 0%, #100E0D 38%, var(--noir) 72%); }
.house__grain { position: absolute; inset: 0; opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }

/* --- Ambient: three slow sources drifting behind everything ---
   Ported from the v1 hero, which Vlad preferred: the flat ground had no depth.
   Kept in TUNGSTEN rather than v1's rose/orchid, because a pink haze is the
   same family as the burgundy that read as cabaret. Light is the only colour. */
.orb { position: absolute; border-radius: 50%; filter: blur(110px);
  opacity: .40; will-change: transform; pointer-events: none; }
.o1 { width: 46vw; height: 46vw; left: -9vw;  top: -12vh;
      background: rgba(232,182,114,.20); animation: drift1 26s ease-in-out infinite; }
.o2 { width: 38vw; height: 38vw; right: -7vw; top: 20vh;
      background: rgba(214,150,90,.15);  animation: drift2 32s ease-in-out infinite; }
.o3 { width: 42vw; height: 42vw; left: 24vw; bottom: -16vh;
      background: rgba(255,214,160,.10); animation: drift3 38s ease-in-out infinite; }
@keyframes drift1 { 0%,100% { transform: translate(0,0) scale(1);   } 50% { transform: translate(6vw,5vh) scale(1.15); } }
@keyframes drift2 { 0%,100% { transform: translate(0,0) scale(1.1); } 50% { transform: translate(-5vw,3vh) scale(1);   } }
@keyframes drift3 { 0%,100% { transform: translate(0,0) scale(1);   } 50% { transform: translate(-4vw,-6vh) scale(1.18);} }

/* --- Scroll progress --- */
.progress { position: fixed; top: 0; left: 0; height: 2px; width: 0; z-index: 200;
  background: linear-gradient(90deg, rgba(232,182,114,.30), var(--fresnel));
  box-shadow: 0 0 12px rgba(232,182,114,.55); }

/* --- Section rail: where am I, and what else is there --- */
.rail { position: fixed; right: 26px; top: 50%; translate: 0 -50%; z-index: 140;
  display: flex; flex-direction: column; gap: 16px; }
.rail a { display: flex; align-items: center; justify-content: flex-end; gap: 10px;
  color: var(--ash); transition: color .3s var(--ease); }
.rail .roman { font-family: var(--display); font-size: .85rem; }
.rail .label { font-family: var(--body); font-weight: 500; font-size: .62rem;
  letter-spacing: .2em; text-transform: uppercase; white-space: nowrap;
  opacity: 0; translate: 6px 0; transition: opacity .3s var(--ease), translate .3s var(--ease); }
.rail .bead { width: 7px; height: 7px; border-radius: 50%; border: 1px solid var(--ash);
  transition: background .3s var(--ease), border-color .3s var(--ease),
              box-shadow .3s var(--ease), scale .3s var(--ease); }
.rail a:hover .label, .rail a.is-on .label { opacity: 1; translate: 0 0; }
.rail a:hover, .rail a.is-on { color: var(--fresnel); }
.rail a.is-on .bead { background: var(--fresnel); border-color: var(--fresnel);
  box-shadow: 0 0 12px var(--fresnel); scale: 1.3; }

/* --- Scroll cue --- */
.scroll-cue { position: absolute; bottom: 34px; left: 50%; translate: -50% 0; z-index: 3;
  width: 22px; height: 38px; border: 1px solid rgba(232,182,114,.34); border-radius: 14px; }
.scroll-cue span { position: absolute; left: 50%; top: 8px; width: 3px; height: 8px;
  margin-left: -1.5px; border-radius: 2px; background: var(--fresnel);
  animation: cue 1.8s ease-in-out infinite; }
@keyframes cue { 0% { opacity: 0; translate: 0 0; } 30% { opacity: 1; } 100% { opacity: 0; translate: 0 13px; } }

/* --- Mobile loading screen ---
   Phones pull the full-size photographs, so the page assembles visibly as
   images land. Hold a curtain until `window.load` (every image decoded), then
   lift it. Desktop keeps the instant lights-up instead. */
.loader { position: fixed; inset: 0; z-index: 600; display: none;
  place-items: center; background: var(--noir); }
.loader__mark { width: 96px; height: auto; opacity: .9; animation: loaderPulse 2s ease-in-out infinite; }
.loader__bar { position: absolute; left: 50%; bottom: 34%; translate: -50% 0;
  width: 132px; height: 1px; background: rgba(242,234,224,.16); overflow: hidden; }
.loader__bar i { position: absolute; inset: 0; display: block; background: var(--fresnel);
  transform-origin: left; scale: 0 1; transition: scale .35s var(--ease); }
.loader.is-done { animation: loaderOut .6s var(--ease) forwards; }
@keyframes loaderPulse { 0%,100% { opacity:.45; } 50% { opacity:1; } }
@keyframes loaderOut { to { opacity: 0; visibility: hidden; } }

/* The curtain is INVISIBLE by default and only JS raises it, never the other
   way round. It used to be opaque black by default and removed by wm.js — so
   if wm.js did not run (blocked, failed to load, or an error thrown before it
   got here) the whole site was a black screen with every word still in the DOM
   and none of it visible. The no-JS state must be the safe one. */
.lights { position: fixed; inset: 0; z-index: 400; background: #000; pointer-events: none; opacity: 0; }
html.js .lights { opacity: 1; }
.lights.is-up { animation: lightsUp 1400ms var(--ease) forwards; }
@keyframes lightsUp { to { opacity: 0; visibility: hidden; } }

/* --- Type roles: each maps to exactly one size --- */
.t-xl { font-family: var(--display); font-weight: 300; font-size: var(--t-xl); line-height: .94; letter-spacing: -.022em; }
.t-l  { font-family: var(--display); font-weight: 300; font-size: var(--t-l);  line-height: 1.06; letter-spacing: -.01em; }
.t-m  { font-size: var(--t-m); line-height: 1.6; }
.t-xs { font-family: var(--body); font-weight: 500; font-size: var(--t-xs);
        letter-spacing: .26em; text-transform: uppercase; color: var(--ash); }
.t-xs--lit { color: var(--fresnel); }

/* Her sentences are the strongest copy on the site, so the quote BREAKS OUT
   past the reading column rather than being squeezed inside it. At 22ch this
   set 22 chars over 8 lines — a sliver, not a quote. Target is 3–4 lines. */
.pull { font-family: var(--display); font-weight: 300; font-style: italic;
        font-size: var(--t-l); line-height: 1.26; max-width: 40ch;
        text-wrap: balance; }
.serif-m { font-family: var(--display); font-style: italic; font-size: var(--t-m); color: var(--ash); }

.prose { max-width: var(--measure); color: rgba(242,234,224,.74); }
.prose + .prose { margin-top: 1.1em; }
/* Copy has exactly TWO sizes: title and body. A lead paragraph is body size —
   emphasis comes from LIGHT (full-strength ink plus a faint warm glow), not
   from a third step in the scale. --t-m survives only for titling elements
   (album subtitles, nav labels), never for running copy. */
.lead { font-size: var(--t-s); line-height: 1.75; color: var(--vellum);
        max-width: var(--measure); }

/* --- Shell + full bleed --- */
.wrap { max-width: var(--page); margin-inline: auto; padding-inline: var(--gut); }
.bleed { width: 100vw; margin-left: calc(50% - 50vw); }
.bleed img { width: 100%; height: auto; }
/* A single portrait at full bleed is 2.540px tall on a 1905px screen — over
   two screens. Portraits go full bleed in PAIRS, which halves the height and
   fills the width without cropping anything. */
.bleed--pair { display: grid; grid-template-columns: 1fr 1fr; gap: 2px; }

.topbar { position: fixed; inset: 0 0 auto 0; z-index: 150; display: flex;
  align-items: center; justify-content: space-between; padding: 20px var(--gut);
  transition: background .5s var(--ease), padding .5s var(--ease); }
.topbar.is-stuck { padding-block: 12px; background: rgba(var(--scrim),.78); backdrop-filter: blur(16px); }
.topbar__mark { font-family: var(--display); font-size: 1.05rem; letter-spacing: .05em; display: flex; gap: 7px; }
.topbar__mark i { font-style: italic; color: var(--ash); }
.topbar a:hover { color: var(--fresnel); }

/* --- Hero: the wordmark alone, out of an unlit house ---
   The background photograph, its darkening veil and the photo credit were all
   removed together — the veil existed only to keep the logo readable over the
   picture, and the credit named the picture. What's left is the house's own
   warm falloff, which is the whole thesis: light arriving in a dark room. */
.hero { position: relative; min-height: 100svh; display: grid; place-items: center; }
.hero__inner { position: relative; z-index: 2; text-align: center; padding: 110px var(--gut) 90px; }
/* `height: auto` is REQUIRED now that the tag carries width/height attributes.
   Those attributes are presentational hints, so a bare `width` in CSS leaves the
   attribute's height as the used value — this rendered the wordmark 230×500
   instead of square. The attributes stay (they reserve the right box and stop
   layout shift); `height: auto` is what lets the ratio win. */
.hero__logo { width: clamp(230px, 36vw, 440px); height: auto; margin: 20px auto 0;
  filter: drop-shadow(0 0 44px rgba(232,182,114,.22));
  animation: logoIn 1.9s var(--ease) both,
             logoFloat 7s ease-in-out 1.9s infinite; }
@keyframes logoFloat { 0%,100% { translate: 0 0; } 50% { translate: 0 -12px; } }
.hero__cite { margin-top: 14px; }
@keyframes logoIn { from { opacity: 0; scale: .95; translate: 0 14px; } to { opacity: 1; scale: 1; translate: 0 0; } }
.hero__quote { margin: 26px auto 0; max-width: 32ch; font-family: var(--display); font-style: italic;
  font-size: var(--t-m); color: rgba(242,234,224,.72); line-height: 1.5; }

/* --- Prologue: text block → full-bleed photograph → text block --- */
.prologue { padding-block: var(--section) 0; }
/* The movement is the CONTAINER; .prose inside it holds the narrow reading
   column. That split is what lets a pull-quote extend past the text's right
   edge while every block still hangs off one common left edge. */
.movement { max-width: var(--measure-wide); margin-inline: auto; }
.movement + .bleed, .bleed + .movement { margin-top: clamp(56px, 8vw, 120px); }
.prologue__title { margin: 12px 0 clamp(34px, 5vw, 60px); }

/* --- Spreads: text and photograph share the section ---
   The prologue used to be four slabs of text with full-bleed photographs
   BETWEEN them, so a reader never had text and an image on screen together —
   which is what made 616 words feel like a wall. Each movement now sits
   beside a photograph, and the sides alternate down the page.
   The figure is sticky, so the photograph holds while its text scrolls past
   and there is never a screen of pure text. */
.spread { display: grid; grid-template-columns: 1fr 1fr;
  gap: clamp(30px, 4.6vw, 84px); align-items: start;
  margin-block: clamp(84px, 10vw, 150px); }
/* Short text beside a tall portrait would otherwise hang from the top and
   leave a void down the rest of the column. Centring the TEXT (not the row)
   balances it, and is a no-op when the text is the taller of the two — so
   the figure still starts at the top and is free to stick. */
.spread__text { max-width: var(--measure); align-self: center; }
.spread--flip .spread__text { order: 2; }
.spread--flip .spread__fig  { order: 1; }
/* text sits against the gutter on the flipped side so both columns still
   hang off one consistent inner edge */
.spread--flip .spread__text { justify-self: end; }

.spread__fig { position: sticky; top: 13vh; }
.spread__fig > img { width: 100%; height: auto;
  box-shadow: 0 0 0 1px rgba(242,234,224,.12), 0 40px 80px -42px rgba(0,0,0,.92); }
.spread__cap { margin-top: 12px; }

/* A quote is not a paragraph — it gets the full width and its own silence. */
.quote-moment { margin-block: clamp(96px, 12vw, 180px);
  display: grid; place-items: center; text-align: center; }

.plate { position: relative; }
.plate figcaption { position: absolute; left: var(--gut); bottom: 20px; z-index: 2; }
.plate::after { content: ""; position: absolute; inset: auto 0 0 0; height: 40%;
  background: linear-gradient(transparent, rgba(var(--scrim),.75)); pointer-events: none; }

.pull-block { margin-block: clamp(40px, 6vw, 84px); padding-left: clamp(16px, 2.5vw, 34px);
  border-left: 1px solid var(--edge); }

/* The sequence — a real sequence in her words, so it earns the device */
.seq { margin-block: clamp(40px, 6vw, 84px); }
.seq__items { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 2px; margin-top: 16px; }
.seq__items span { font-family: var(--display); font-size: var(--t-m); white-space: nowrap; }
.seq__items .a { color: var(--fresnel); opacity: .6; padding-inline: 7px; }
.seq__items span:last-of-type { color: var(--fresnel); font-style: italic; }

.emergence { display: grid; place-items: center; padding-block: clamp(60px, 9vw, 120px); }
.emergence svg { width: 156px; overflow: visible; }
.emergence path { fill: none; stroke: url(#wingGrad); stroke-width: 1.1;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 620; stroke-dashoffset: calc(620 - (620 * var(--draw, 0)));
  filter: drop-shadow(0 0 8px rgba(232,182,114,.4)); }

/* --- Origin: My Other Self, in full --- */
/* `clip` so the full-bleed inside can't add horizontal scroll, while the
   spreads' figures keep their sticky behaviour. */
.origin { padding-block: var(--section); overflow-x: clip; }
.origin .spread { margin-block: clamp(70px, 8vw, 120px); }
.origin .spread__fig .frame { width: 100%; }
.origin__head { max-width: var(--measure); margin: 0 auto clamp(36px, 5vw, 60px); }
.origin__title { margin: 10px 0 22px; }
.origin__hero { margin-top: clamp(36px,5vw,64px); }
.origin__hero .frame { max-width: min(100%, calc(84vh * var(--ar))); margin-inline: auto; }
.origin__text { max-width: var(--measure); margin: clamp(46px,6vw,90px) auto 0; }
.origin__pair { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(12px,1.4vw,20px); margin-top: clamp(46px,6vw,90px); }

.linkline { display: inline-flex; align-items: baseline; gap: 12px; font-family: var(--display);
  font-style: italic; font-size: var(--t-m); position: relative; padding-bottom: 5px; }
.linkline::after { content: ""; position: absolute; inset: auto 0 0 0; height: 1px;
  background: var(--fresnel); scale: 0 1; transform-origin: left; transition: scale .5s var(--ease); }
.linkline:hover::after { scale: 1 1; }

/* --- Frames (never cropped) --- */
.frame { display: block; position: relative; border: 0; padding: 0; background: none;
  width: 100%; text-align: left; appearance: none; font: inherit; color: inherit; cursor: pointer;
  box-shadow: 0 0 0 1px rgba(242,234,224,.12), 0 40px 80px -42px rgba(0,0,0,.92);
  transition-property: translate, box-shadow; transition-duration: .55s; transition-timing-function: var(--ease); }
.frame img { width: 100%; height: auto; aspect-ratio: var(--ar); }
.frame:hover { translate: 0 -5px; box-shadow: 0 0 0 1px var(--fresnel), 0 54px 96px -42px rgba(0,0,0,.95); }

/* --- Distribuția --- */
/* `clip`, not `hidden`: the spotlight is deliberately wider than the stage and
   would otherwise push the page 115px sideways, but `overflow:hidden` creates
   a scroll container and would kill `.cast__stage`'s `position: sticky`.
   `clip` contains the glow without becoming a scrollport. The pool is already
   under ~2% alpha by the time it reaches the viewport edge, so nothing shows. */
.cast { padding-block: var(--section); border-top: 1px solid var(--rule);
  overflow-x: clip; }
.cast__head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: clamp(30px,4vw,56px); }
.cast__grid { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,.8fr); gap: clamp(30px,5vw,80px); align-items: start; }
.cast__list { list-style: none; }
.cast__row { display: grid; grid-template-columns: 1fr auto; align-items: baseline; gap: 18px;
  padding-block: clamp(14px,1.6vw,22px); border-bottom: 1px solid var(--rule);
  transition: padding-left .55s var(--ease); }
.cast__row:hover, .cast__row.is-active { padding-left: 16px; }
.cast__name { color: rgba(242,234,224,.48); transition: color .5s var(--ease); }
.cast__row:hover .cast__name, .cast__row.is-active .cast__name { color: var(--vellum); }
.cast__title { display: block; margin-top: 5px; color: var(--ash); transition: color .5s var(--ease); }
.cast__row:hover .cast__title, .cast__row.is-active .cast__title { color: var(--fresnel); }

.cast__stage { position: sticky; top: 11vh; height: 78vh; display: grid; place-items: center; }
.cast__plate { grid-area: 1/1; position: relative; max-height: 78vh; max-width: 100%;
  opacity: 0; scale: .975; transition: opacity .6s var(--ease), scale .8s var(--ease); }
.cast__plate.is-lit { opacity: 1; scale: 1; }
.cast__plate img { max-height: 78vh; width: auto; max-width: 100%; height: auto;
  box-shadow: 0 0 0 1px rgba(242,234,224,.12), 0 50px 100px -42px rgba(0,0,0,.95);
  filter: brightness(.4) saturate(.5); transition: filter 1s var(--ease); }
.cast__plate.is-lit img { filter: brightness(1) saturate(1); }
/* The spotlight.
   The old rule used `radial-gradient(ellipse …)`, which defaults to
   `farthest-corner`. On a tall portrait that radius is driven by the vertical
   axis, so at the `transparent 66%` stop the glow was still ~60% opaque where
   it met the box's left and right edges — a hard vertical slice down both
   sides. `closest-side` pins the final stop to the NEAREST edge, so the pool
   always reaches full transparency before it can be clipped, at any aspect
   ratio. Centred slightly high to match the house light coming from above. */
.cast__plate::before { content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: 50%; top: 38%; translate: -50% -50%;
  width: 260%; height: 205%;
  background: radial-gradient(closest-side ellipse at 50% 50%,
      rgba(232,182,114,.30) 0%,
      rgba(232,182,114,.13) 38%,
      rgba(232,182,114,.04) 62%,
      transparent 100%);
  opacity: 0; transition: opacity 1s var(--ease); }
.cast__plate.is-lit::before { opacity: 1; }
.cast__mobile { display: none; }

/* --- Album page --- */
/* `clip` (never `hidden`) so a full-bleed figure can't add horizontal scroll,
   while .spread__fig keeps its sticky behaviour. */
.album { overflow-x: clip; }
.album .spread { margin-block: clamp(70px, 8vw, 120px); }
.album .spread__fig .frame { width: 100%; }
/* One wide photograph edge-to-edge — the page's big breath. */
.album__bleed { position: relative; width: 100vw; margin-left: calc(50% - 50vw);
  margin-block: clamp(70px, 9vw, 130px); }
.album__bleed .frame { width: 100vw; box-shadow: none; }
.album__bleed .frame:hover { translate: 0 0; box-shadow: none; }
.album__bleed img { width: 100vw; height: auto; }
.album__bleed figcaption { position: absolute; left: var(--gut); bottom: 20px; z-index: 2; }
.album { padding-top: clamp(110px, 15vh, 175px); }
.album__head { text-align: center; max-width: 820px; margin: 0 auto clamp(36px,5vw,62px); }
.album__title { margin: 12px 0 8px; }
.album__lead .frame { max-width: min(100%, calc(80vh * var(--ar))); margin-inline: auto; }
.album__story { max-width: var(--measure); margin: clamp(48px,7vw,100px) auto; }
.album__rows { display: flex; flex-direction: column; gap: clamp(44px,6vw,90px); margin-top: clamp(50px,7vw,100px); }
.row { display: flex; gap: clamp(12px,1.4vw,20px); align-items: flex-start; margin-inline: auto; width: 100%; }
.row--solo { max-width: min(1180px, calc(84vh * var(--rowar))); }
.row--pair { max-width: min(100%, calc(80vh * var(--rowar))); }
.row .frame { flex-grow: calc(var(--ar) * 100); flex-shrink: 1; flex-basis: 0; min-width: 0; }
/* The pager lives OUTSIDE .wrap (see build-albums.py), so it spans the real
   visible width instead of the 1440px reading column — on a 1920 screen the two
   destinations were sitting 633px in from each edge, huddled in the middle
   third. `space-between` plus one gutter now puts each at its own edge, and the
   hairline runs the full width like the footer's does. `align-items: start`
   keeps the two labels on a shared top baseline when one title wraps. */
.album__nav { display: flex; justify-content: space-between; align-items: start;
  gap: clamp(28px, 6vw, 96px);
  flex-wrap: wrap; padding-inline: var(--gut);
  margin-top: clamp(70px,9vw,130px); padding-top: 32px; border-top: 1px solid var(--rule); }
.album__nav a { display: grid; gap: 4px; }
.album__nav .is-next { text-align: right; }
.album__nav strong { font-family: var(--display); font-weight: 400; font-size: var(--t-m);
  color: rgba(242,234,224,.75); transition: color .4s; }
.album__nav a:hover strong { color: var(--fresnel); }
.album__nav-arrow { display: none; }

/* --- Lightbox --- */
.lb { position: fixed; inset: 0; z-index: 500; display: grid; place-items: center;
  padding: clamp(14px,4vw,54px); background: rgba(var(--scrim),.96); backdrop-filter: blur(16px);
  opacity: 0; transition: opacity .4s var(--ease); }
.lb[hidden] { display: none; }
.lb.is-open { opacity: 1; }
body.is-locked { overflow: hidden; }
.lb__img { max-width: 100%; max-height: calc(100svh - clamp(70px,11vw,140px)); width: auto; height: auto;
  box-shadow: 0 40px 120px -30px rgba(0,0,0,.95); }
/* the photograph itself changes with direction, so paging feels like moving
   through a sequence rather than the picture being swapped underneath you */
.lb__img.is-out-next { animation: lbOutNext .22s var(--ease) forwards; }
.lb__img.is-out-prev { animation: lbOutPrev .22s var(--ease) forwards; }
.lb__img.is-in-next  { animation: lbInNext  .34s var(--ease) both; }
.lb__img.is-in-prev  { animation: lbInPrev  .34s var(--ease) both; }
@keyframes lbOutNext { to { opacity: 0; translate: -34px 0; } }
@keyframes lbOutPrev { to { opacity: 0; translate:  34px 0; } }
@keyframes lbInNext  { from { opacity: 0; translate:  44px 0; } to { opacity: 1; translate: 0 0; } }
@keyframes lbInPrev  { from { opacity: 0; translate: -44px 0; } to { opacity: 1; translate: 0 0; } }
.lb__figure { touch-action: pan-y; }
.lb__cap { margin-top: 14px; text-align: center; }
.lb__btn { position: fixed; appearance: none; cursor: pointer; background: rgba(242,234,224,.05);
  border: 1px solid rgba(242,234,224,.18); color: var(--vellum); border-radius: 50%;
  display: grid; place-items: center; transition: background .35s, border-color .35s; }
.lb__btn:hover { background: rgba(232,182,114,.18); border-color: var(--fresnel); }
.lb__close { top: 22px; right: 24px; width: 44px; height: 44px; font-size: 1.4rem; }
.lb__prev, .lb__next { top: 50%; translate: 0 -50%; width: 50px; height: 50px; font-size: 1.8rem; padding-bottom: 4px; }
.lb__prev { left: 20px; } .lb__next { right: 20px; }

/* --- Footer --- */
.foot { padding: var(--section) var(--gut); text-align: center;
  border-top: 1px solid var(--rule); }
.foot__links { display: flex; flex-wrap: wrap; justify-content: center; gap: 26px; margin: 32px 0 36px; }
.foot__links a:hover { color: var(--fresnel); }
/* These three links ARE the contact route — there is deliberately no email —
   and they measured 19px tall on a phone, well under the 24px WCAG 2.2 minimum
   and less than half Apple's 44px. Negative margin cancels the padding, so the
   hit area grows to 45px without moving anything on screen. */
.foot__links a { padding-block: 13px; margin-block: -13px; }
.topbar .t-xs { padding-block: 13px; margin-block: -13px; }

/* ============================================================
   Motion — one idea: nothing slides in, things are LIT.
   Every entrance is a light arriving on something already there,
   which is the same gesture the cast stage makes on hover.
   ============================================================ */

/* Same rule as the curtain: content is VISIBLE by default, and JS opts into
   hiding it so it can be revealed. `.rv { opacity: 0 }` unscoped meant that
   without wm.js all 74 revealable elements on the landing stayed invisible
   forever. `html.js` is set by an inline script in <head>, before first paint,
   so there is no flash of un-hidden content. */
.rv { opacity: 1; }
html.js .rv { opacity: 0; }
html.js .rv.in { opacity: 1; }
.rv.in { opacity: 1; animation: rvIn .95s var(--ease) both; }
@keyframes rvIn { from { opacity: 0; translate: 0 24px; } to { opacity: 1; translate: 0 0; } }

/* A .frame owns `translate` for its hover lift. An animation with fill-mode
   `both` outranks a normal declaration in the cascade, so rvIn's final
   `translate: 0 0` was silently killing `.frame:hover`'s lift on all 15
   frames. Frames therefore reveal on `scale`, which nothing else claims. */
.frame.rv.in { animation-name: rvInFrame; animation-duration: 1.1s; }
@keyframes rvInFrame { from { opacity: 0; scale: .986; } to { opacity: 1; scale: 1; } }

/* Photographs arrive dim and warm up, exactly like a portrait on the stage.
   Safe on `filter`: .frame builds its depth from box-shadow, never filter. */
/* Scoped to html.js for the same reason as .rv above — without JS the `.in`
   class never arrives, so unscoped these left every photograph permanently at
   42% brightness. */
html.js .plate.rv img, html.js .frame.rv img, html.js .spread__fig.rv img {
  filter: brightness(.42) saturate(.5);
  transition: filter 1.4s var(--ease); }
html.js .plate.rv.in img, html.js .frame.rv.in img, html.js .spread__fig.rv.in img { filter: brightness(1) saturate(1); }

/* Her sentences are the loudest thing on the page, so the quote takes longer
   to arrive and travels further than the prose around it. */
.pull-block.rv.in, .quote-moment.rv.in { animation-duration: 1.5s; animation-name: rvInQuote; }
@keyframes rvInQuote { from { opacity: 0; translate: 0 40px; } to { opacity: 1; translate: 0 0; } }

/* The cast reads like a cast list — one name lit after another. --i is set
   in wm.js; the cap keeps the last of 12 under a second of delay. */
.cast__list li.rv.in { animation-delay: calc(var(--i, 0) * 62ms); }

/* The name a visitor is holding the lamp on gets a faint warm edge, so the
   link between the list and the stage is visible without moving anything. */
.cast__row { position: relative; }
.cast__row::before { content: ""; position: absolute; left: 0; top: 8%; bottom: 8%; width: 1px;
  background: var(--fresnel); opacity: 0; transition: opacity .55s var(--ease); }
.cast__row:hover::before, .cast__row.is-active::before { opacity: .85; }

/* --- Responsive --- */
@media (max-width: 980px) {
  .rail { display: none; }
  /* html.js only — the curtain is lifted by wm.js, so without JS it would sit
     over the page forever. No JS, no curtain. */
  html.js .loader { display: grid; }

  /* Distribuția: the name, its series and the photo count were stacking into
     three lines, leaving a bare "3" orphaned on its own row. Name and count
     share a baseline; the photograph spans the full width beneath. */
  .cast__row { grid-template-columns: 1fr auto; align-items: baseline;
               padding-left: 0 !important; }
  .cast__mobile { grid-column: 1 / -1; }
  .cast__row::before { display: none; }   /* the lit edge means nothing without a stage */

  /* prev / next on mobile.
     NOT bordered circles — nothing else on this site has a pill or a button
     outline; the language is hairline rules, Cormorant and one gold light. So
     the control is the glyph itself, set in the display face at title weight,
     with the tap area carried by invisible padding (56px, above the 44px
     minimum). Gold on press is the same feedback the cast list uses. */
  /* padding-inline was 0 while the nav sat inside .wrap and inherited its
     gutter. It is now a child of .album, so it carries that gutter itself —
     this keeps the mobile geometry byte-identical to before the desktop change
     (measured: arrows at 20→76 and 314→370 at 390px, unchanged). */
  .album__nav { justify-content: space-between; gap: 0;
    border-top: 0; margin-top: clamp(40px, 8vw, 64px);
    padding-block: clamp(10px, 2.6vw, 16px); padding-inline: var(--gut); }
  .album__nav a { display: grid; place-items: center;
    width: 56px; height: 56px; border: 0; background: none; border-radius: 0; }
  .album__nav .album__nav-arrow { display: block;
    font-family: var(--display); font-weight: 300;
    font-size: 3rem; line-height: 1; color: rgba(242,234,224,.72);
    transition: color .3s var(--ease), translate .3s var(--ease); }
  .album__nav a:active .album__nav-arrow { color: var(--fresnel); }
  .album__nav .is-prev:active .album__nav-arrow { translate: -4px 0; }
  .album__nav .is-next:active .album__nav-arrow { translate:  4px 0; }
  /* A hairline ABOVE the arrows matching the one below — which is the
     footer's own top border, and that runs the full viewport width. A plain
     `border-top` would stop at the content column and read as a different
     line, so this bleeds to 100vw. (.album has overflow-x: clip, so it cannot
     add horizontal scroll.) */
  .album__nav { position: relative; }
  .album__nav::before { content: ""; position: absolute; top: 0; left: 50%;
    translate: -50% 0; width: 100vw; height: 1px; background: var(--rule); }
  /* the vertical tick between the two arrows — ::before is spoken for by the
     bleeding rule above, so this is ::after (an element only has two) */
  .album__nav::after { content: ""; position: absolute; left: 50%; top: 50%;
    translate: -50% -50%; width: 1px; height: 26px; background: var(--rule); }
  .album__nav a .t-xs, .album__nav a strong { display: none; }
  /* (a duplicate .album__nav-arrow rule lived here from the first attempt and,
     being later in the same media query, silently beat the styling above —
     third instance of the same file-order trap this session.) */
  .spread { grid-template-columns: 1fr; gap: clamp(26px,5vw,44px); }
  .spread--flip .spread__text, .spread--flip .spread__fig { order: 0; }
  .spread--flip .spread__text { justify-self: stretch; }
  .spread__fig { position: static; }
  .cast__grid { grid-template-columns: 1fr; }
  .cast__stage { display: none; }
  .cast__mobile { display: block; margin-top: 14px; }
  .cast__mobile img { width: 100%; height: auto; box-shadow: 0 0 0 1px rgba(242,234,224,.12); }
  /* (the old `.cast__row { grid-template-columns: 1fr }` lived here and, being
     later in the same media query at equal specificity, silently beat the
     name+count rule above — which is what orphaned the bare number.) */
  .row--pair { flex-direction: column; max-width: 100%; }
  .origin__pair { grid-template-columns: 1fr; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
  html { scroll-behavior: auto; }
  .rv { opacity: 1; }
  .orb, .scroll-cue span, .hero__logo, .loader__mark { animation: none !important; }
  .lb__img { animation: none !important; }
  .cast__list li.rv.in { animation-delay: 0ms !important; }
  .emergence path { stroke-dashoffset: 0; }
  .cast__plate { opacity: 1; } .cast__plate img { filter: none; }
  /* the new light-up passes must land at full brightness, not stay dim */
  .plate.rv img, .frame.rv img, .spread__fig.rv img { filter: none; }
}
