/* Molecular scene layer: canvas above the poster tint, a CSS veil above the canvas, and a
   glass-card label on top. Nothing here owns layout or input. */
body[data-design] .ambient-scene .scene-canvas {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  opacity: 0;
  pointer-events: none;
  transition: opacity 280ms ease-out;
}

/* The veil reproduces the design tint at a lower alpha so foreground text keeps contrast;
   the runtime sets its colour per look. The ::after tint underneath stays for the poster. */
body[data-design] .ambient-scene .scene-veil {
  position: absolute;
  inset: 0;
  z-index: 2;
  background: var(--scene-veil, #07131b6b);
  opacity: 0;
  pointer-events: none;
  transition: opacity 280ms ease-out;
}
body[data-design="gallery"] .ambient-scene .scene-veil { background: var(--scene-veil, #e8e7f166); }

body[data-design] .ambient-scene[data-scene-ready="true"] .scene-canvas,
body[data-design] .ambient-scene[data-scene-ready="true"] .scene-veil { opacity: 1; }

/* Glass card: the desktop slot is fixed in the right gutter and never repositions. label.js sizes
   it to the gutter actually free beside the content column (248 px when it fits, down to 150 px;
   .scene-label-compact scales the type) and falls back to the collision placer on narrower
   desktops (.scene-label-placed), so archive text never runs through the card. */
body[data-design] .ambient-scene .scene-label {
  position: absolute;
  z-index: 3;
  right: clamp(16px, 2vw, 28px);
  top: clamp(150px, 24vh, 260px);
  display: grid;
  gap: 6px;
  width: 248px;
  box-sizing: border-box;
  padding: 14px 16px;
  border: 1px solid rgba(164, 235, 242, .28);
  border-radius: 10px;
  color: #e9f4f6;
  background: rgba(10, 24, 32, .55);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .06);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
  text-align: left;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  will-change: opacity, transform, filter;
}
body[data-design] .ambient-scene .scene-label-kicker {
  font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace !important;
  font-size: 10px;
  line-height: 1.3;
  font-weight: 500;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: #a4ebf2;
}
body[data-design] .ambient-scene .scene-label-title {
  font-family: 'Space Grotesk', sans-serif !important;
  /* 18 px: the longest title ("Phe · L-Phenylalanine") fits the 216 px card on one line (round 4) */
  font-size: 18px;
  line-height: 1.1;
  font-weight: 600;
  letter-spacing: -.02em;
}
body[data-design] .ambient-scene .scene-label-line {
  font-family: Manrope, 'Avenir Next', sans-serif !important;
  font-size: 12px;
  line-height: 1.45;
  font-weight: 400;
  text-wrap: balance;
}
body[data-design] .ambient-scene .scene-label-note,
body[data-design] .ambient-scene .scene-label-note-short {
  font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace !important;
  font-size: 9.5px;
  line-height: 1.4;
  font-weight: 400;
  opacity: .6;
  /* balanced lines: no orphaned last word on a line of its own (review round 4) */
  text-wrap: balance;
}
/* The short line / short note exist for the compact mobile card only. */
body[data-design] .ambient-scene :is(.scene-label-line-short, .scene-label-note-short) { display: none; }

/* Compact type scale for a card narrower than 220 px (14-inch laptops: the gutter is ~160 px). */
body[data-design] .ambient-scene .scene-label.scene-label-compact {
  gap: 5px;
  padding: 12px 14px;
}
body[data-design] .ambient-scene .scene-label.scene-label-compact .scene-label-kicker { font-size: 9px; letter-spacing: .1em; }
body[data-design] .ambient-scene .scene-label.scene-label-compact .scene-label-title { font-size: 16px; line-height: 1.1; }
body[data-design] .ambient-scene .scene-label.scene-label-compact .scene-label-line { font-size: 11px; line-height: 1.4; }
body[data-design] .ambient-scene .scene-label.scene-label-compact .scene-label-note { font-size: 9px; }
/* Placer fallback on narrow desktops (701–1200 px): the mini card below at the right edge, top
   set by label.js (the rules under .scene-label-mini apply). */
body[data-design] .ambient-scene .scene-label.scene-label-placed {
  right: 16px;
  transition: none;
}
body[data-design="gallery"] .ambient-scene .scene-label {
  color: #1c2240;
  background: rgba(247, 246, 251, .62);
  border-color: rgba(42, 47, 181, .22);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .5);
}
body[data-design="gallery"] .ambient-scene .scene-label-kicker { color: #2e4ec7; }

/* Persistent frame (label.js persistent-frame mode). Two cards use it and each keeps its own rules:
   the Gallery redesign track walks and the Atlas screw descent (.scene-label-travelling). The
   scoping is :where() so every selector keeps the specificity it had on its own line (the static
   tiers' `display: none` below must still win over the flex card).
   Track walks: the card stays up while the walk glides from residue to residue; only the content
   block (title, line, the residue's own note and the kicker's count "02 OF 07") crossfades, its
   opacity and a small blur set inline by label.js from the scene state (the notes fade through
   `filter: opacity()`, so their own .6 / .85 opacity stays). The kicker's "SEMAX /" stays with the
   frame (review round 2: the count digits switched while the frame was at full opacity). */
body[data-design] .ambient-scene .scene-label.scene-label-persistent:where(:not(.scene-label-travelling)) :is(.scene-label-title,
  .scene-label-line, .scene-label-line-short, .scene-label-kicker-count, .scene-label-note, .scene-label-note-short) {
  will-change: opacity, filter;
  transition: none;
}
/* Atlas screw descent: the content block (title, line, the base's own note and the kicker's rolling
   base number) crossfades and travels with the strand; label.js sets opacity, a small blur and
   translateY inline from the scene state. */
body[data-design] .ambient-scene .scene-label.scene-label-persistent:where(.scene-label-travelling) :is(.scene-label-kicker,
  .scene-label-title, .scene-label-line, .scene-label-line-short, .scene-label-index, .scene-label-note, .scene-label-note-short) {
  will-change: opacity, filter, transform;
  transition: none;
}
/* The track-walk card is a flex column with the note pinned to the bottom of the min-height frame
   label.js holds (the tallest residue's content, its note included): a residue with a shorter
   description does not pull its note up by a line (review round 2: the footnote jumped 4 px at
   Met → Glu while the frame and note were fully visible). The gap matches the grid card's (compact
   5 px, mini 1 px through their own rules below). The descent card stays a grid: it holds its line's
   and its note's heights instead (label.reserve, label.reserveNotes). */
body[data-design] .ambient-scene .scene-label.scene-label-persistent:where(:not(.scene-label-travelling)) {
  display: flex;
  flex-direction: column;
}
body[data-design] .ambient-scene .scene-label.scene-label-persistent:where(:not(.scene-label-travelling)) :is(.scene-label-note,
  .scene-label-note-short) {
  margin-top: auto;
}
/* The kicker's count ("02 OF 07") crossfades with the content; "SEMAX /" stays with the frame. */
body[data-design] .ambient-scene .scene-label.scene-label-persistent .scene-label-kicker-count { display: inline-block; }
/* Anchored card (persistent mode off the gutter: phones, 701–1200 px): it rides with the archive
   text and may sit across one of its row dividers, so the glass is nearly opaque there and the line
   does not read through the card's text (review round 1: a divider struck through the card). */
body[data-design="gallery"] .ambient-scene .scene-label.scene-label-anchored {
  background: rgba(247, 246, 251, .92);
}
/* Atlas screw descent (label.js): the content travels with the strand (translateY set inline from
   the scene state, out upward after a base, in from below before the next) and the kicker stays in
   the frame while only its base number rolls; inline-block so the number can take a transform. */
body[data-design] .ambient-scene .scene-label .scene-label-index { display: inline-block; }
/* Every kicker span keeps the kicker's IBM Plex Mono face: common.css forces Manrope on every
   element with `body[data-design] *`, which beats inheritance on spans. Review round 2 of the
   descent fixed the rolling kicker's spans ("STRAND A · 15 OF 32" switched to Manrope on the first
   frame of the walk); the track-walk card's prefix/count spans ("SEMAX / 02 OF 07", Gallery G1/G2)
   follow since 2026-10-08, so both cards set their kicker in the same mono face. :where() keeps the
   selector's specificity unchanged. */
body[data-design] .ambient-scene .scene-label-kicker > span:where(.scene-label-kicker-text, .scene-label-index,
  .scene-label-kicker-fixed, .scene-label-kicker-count, .scene-label-kicker-part) {
  font-family: inherit !important;
  letter-spacing: inherit;
}

/* Mini card (label.js adds .scene-label-mini whenever the sticky collision placer is in use:
   phones and desktops narrower than the gutter card). On a 390 px phone every list description
   spans the full width, so the only free slots are the ≈ 70 px tall windows beside kickers and
   titles on the right 180 px strip (measured on the Atlas/Gallery homepages); on a 1024 px
   desktop the right strip is covered by the two-column stacks grid the same way. A 300 px card
   could never be placed without covering text; this one (≈ 58 px plus the 8 px fade rise the
   placer reserves) fits those windows. The description is
   clamped to two lines here; the full line shows in the desktop gutter card. */
body[data-design] .ambient-scene .scene-label.scene-label-mini {
  right: 12px;
  width: 176px;
  gap: 1px;
  padding: 3px 9px 4px;
  -webkit-backdrop-filter: blur(10px) saturate(1.2);
  backdrop-filter: blur(10px) saturate(1.2);
  /* The sticky placer moves the slot only on collision and the move is instant: an eased
     `top` would glide the card across the text it is avoiding (and the QA scan caught it). */
  transition: none;
}
body[data-design] .ambient-scene .scene-label.scene-label-mini .scene-label-kicker { display: none; }
body[data-design] .ambient-scene .scene-label.scene-label-mini .scene-label-title { font-size: 13px; line-height: 1.05; }
/* The full description and note give way to their short forms (copy.js `short`, `noteShort`):
   the short line fits two 11 px lines and the short note one 9.5 px line with no tracking (each
   card's own fact in at most 26 characters, copy.js). Tighter padding and gap keep the whole card
   at ≈ 58 px (it was 59 with 10/8 px type), so the sticky placer still finds the ≈ 70 px windows
   beside kickers and titles on a 390 px phone; a taller card fell back to the tiny title-only card
   at stop A. */
body[data-design] .ambient-scene .scene-label.scene-label-mini :is(.scene-label-line, .scene-label-note) { display: none; }
body[data-design] .ambient-scene .scene-label.scene-label-mini .scene-label-line-short {
  display: block;
  font-family: Manrope, 'Avenir Next', sans-serif !important;
  font-size: 11px;
  line-height: 1.1;
  font-weight: 400;
}
body[data-design] .ambient-scene .scene-label.scene-label-mini .scene-label-note-short {
  display: block;
  /* review round 3: 8.5 px was at the edge of legibility on the blurred card; 9.5 px with the short
     notes (copy.js noteShort, ≤ 26 characters) still fits one line (nowrap guards the card height for
     the placer) */
  font-size: 9.5px;
  line-height: 1.1;
  letter-spacing: 0;
  white-space: nowrap;
  overflow: hidden;
  opacity: .85;
}
/* The amino-acid / base name never breaks after its stereo-descriptor hyphen ("L-" alone on a
   line read as a typo); a break can only land before the dot. In the compact Gallery gutter card
   (≈ 124 px of text width) "Met · L-Methionine" cannot fit one line, so the code sits on its own
   line above the name and the dot is dropped instead of orphaned (review round 4). */
body[data-design] .ambient-scene .scene-label-title .scene-label-name { white-space: nowrap; }
body[data-design] .ambient-scene .scene-label-title .scene-label-dot { white-space: nowrap; }
body[data-design="gallery"] .ambient-scene .scene-label.scene-label-compact .scene-label-title .scene-label-dot { display: none; }
body[data-design="gallery"] .ambient-scene .scene-label.scene-label-compact .scene-label-title .scene-label-name { display: block; }
/* Tiny card (label.js, second placer attempt): title and short note only, ≈ 35 px, for the
   scroll positions where even the mini card finds no window beside the text. */
body[data-design] .ambient-scene .scene-label.scene-label-mini.scene-label-tiny .scene-label-line-short { display: none; }

/* Static-tier poster of a track walk (build_premium.py poster_position): positioned at the opening's
   camera anchor instead of centred, so the cover-scaled p = 0 frame puts the hero where the live
   opening draws it on wide and short windows (review round 2). Variants without the variables keep
   the design's centred poster. */
body[data-design] .ambient-scene[data-scene-variant] {
  background-position: var(--scene-poster-position, var(--ambient-position, center));
}
@media (max-width: 700px) {
  body[data-design] .ambient-scene[data-scene-variant] {
    background-position: var(--scene-poster-position-mobile, var(--scene-poster-position, var(--ambient-position, center)));
  }
}

/* Inner-page scenes (review round 2): the live tier opens on the look's studio poster (the p = 0
   frame with its object hidden), so the canvas fades the object in over its own studio instead of
   crossfading it from a full poster whose cover-scaled framing differs from the live band frame (a
   double image that then shrank, on every load of the Database page). Reduced motion, static mode, a
   scene failure (data-scene-tier="poster") and no JavaScript (the build's <noscript> rule) keep the
   full poster. */
@media (prefers-reduced-motion: no-preference) {
  body[data-design][data-scene-page]:not(.motion-static) .ambient-scene[data-scene-variant]:not([data-scene-tier="poster"]) {
    background-image: var(--scene-studio-poster, var(--ambient-poster));
  }
}
@media (prefers-reduced-motion: no-preference) and (max-width: 700px) {
  body[data-design][data-scene-page]:not(.motion-static) .ambient-scene[data-scene-variant]:not([data-scene-tier="poster"]) {
    background-image: var(--scene-studio-poster-mobile, var(--scene-studio-poster, var(--ambient-mobile-poster, var(--ambient-poster))));
  }
}

/* Identity card of an inner-page scene (scene/identity.js, owner 2026-10-09: "a small pointer or small
   label with 'Semax' sign" on the Atlas Database page). It is the DNA card's family: the .scene-label
   glass, border, radii and type above (the compact type scale where it runs compact); identity.js sets
   its slot inline from the page layout (left/top; never the residue card's gutter rules) and drives its
   opacity, drift and blur like label.js does, so it has no transition of its own. */
body[data-design] .ambient-scene .scene-label.scene-label-identity {
  right: auto;
  transition: none;
}
/* the sequence before " · " stays on one mono line: Met-Glu-His-Phe-Pro-Gly-Pro (common.css forces
   Manrope on every element; the span keeps the note's IBM Plex Mono) */
body[data-design] .ambient-scene .scene-label-identity .scene-label-sequence {
  font-family: inherit !important;
  letter-spacing: inherit;
  white-space: nowrap;
}
/* Mini identity card (phones, and desktops too narrow for the full card): the kicker, the title and the
   short note (copy.js noteShort), sized to its content; the description line and the long note give way.
   9 px mono with tight tracking (review 2026-10-09: 8.5 px drifted from the family, whose mini note has
   a 9.5 px floor) keeps the card at ≈ 150 × 62 px, the slot beside the "Database" heading on a 390 px
   phone (152 × 65 px between the heading, the lede and the scene layer's edge fade). */
body[data-design] .ambient-scene .scene-label.scene-label-identity.scene-label-identity-mini {
  gap: 3px;
  padding: 7px 9px 8px;
}
body[data-design] .ambient-scene .scene-label.scene-label-identity-mini .scene-label-kicker {
  font-size: 9px;
  letter-spacing: .02em;
}
body[data-design] .ambient-scene .scene-label.scene-label-identity-mini .scene-label-title {
  font-size: 15px;
  line-height: 1.1;
}
body[data-design] .ambient-scene .scene-label.scene-label-identity-mini :is(.scene-label-line, .scene-label-note) { display: none; }
body[data-design] .ambient-scene .scene-label.scene-label-identity-mini .scene-label-note-short {
  display: block;
  font-size: 9px;
  line-height: 1.25;
  letter-spacing: -.02em;
  white-space: nowrap;
  opacity: .85;
}
/* Brief identity card (phones where it fits beside the heading, 390 px and wider; review 2026-10-09
   round 2: the phone card had dropped the description): the mini card with the short line "Russian
   nootropic peptide" under the title, on one 10.5 px line (it keeps the card at the kicker's width and
   ≈ 78 px tall, 16 px clear of the lede on a 390 × 844 phone). */
body[data-design] .ambient-scene .scene-label.scene-label-identity-brief .scene-label-line-short {
  display: block;
  font-family: Manrope, 'Avenir Next', sans-serif !important;
  font-size: 10.5px;
  line-height: 1.15;
  font-weight: 400;
  white-space: nowrap;
}
/* Micro identity card (360–389 px phones, where the mini card does not fit beside the heading): the
   kicker on two lines ("PEPTIDE" / "7 AMINO ACIDS", the separator dropped) above the title, ≈ 100 × 61 px;
   the short note gives way. */
body[data-design] .ambient-scene .scene-label.scene-label-identity-micro .scene-label-kicker {
  font-size: 9.5px;
  letter-spacing: .04em;
}
body[data-design] .ambient-scene .scene-label.scene-label-identity-micro .scene-label-kicker-part {
  display: block;
  white-space: nowrap;
}
body[data-design] .ambient-scene .scene-label.scene-label-identity-micro .scene-label-note-short { display: none; }
/* The leader: a hairline in the card border's cyan from the card to a small ring on a pearl of the
   sculpture (identity.js draws it in, then fades it out as a whole before the card fades). */
body[data-design] .ambient-scene .scene-label-leader {
  position: absolute;
  inset: 0;
  z-index: 3;
  width: 100%;
  height: 100%;
  overflow: visible;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
body[data-design] .ambient-scene .scene-label-leader-line {
  fill: none;
  stroke: rgba(164, 235, 242, .62);
  stroke-width: 1;
}
body[data-design] .ambient-scene .scene-label-leader-start { fill: rgba(164, 235, 242, .85); }
body[data-design] .ambient-scene .scene-label-leader-halo { fill: rgba(7, 19, 27, .4); }
body[data-design] .ambient-scene .scene-label-leader-circle {
  fill: none;
  stroke: rgba(164, 235, 242, .92);
  stroke-width: 1;
}
body[data-design] .ambient-scene .scene-label-leader-dot { fill: #dff8fb; }

/* Static tiers show the poster only: no canvas, no veil, no label (and no identity leader). */
body[data-design].motion-static .ambient-scene :is(.scene-canvas, .scene-veil, .scene-label, .scene-label-leader) {
  display: none;
}
@media (prefers-reduced-motion: reduce) {
  body[data-design]:not(.motion-ready) .ambient-scene :is(.scene-canvas, .scene-veil, .scene-label, .scene-label-leader) {
    display: none;
  }
}
/* ... except the still identity card (identity.js `still`, review 2026-10-09 round 2: reduced-motion
   visitors saw the unlabelled molecule): the runtime creates it for the static tiers only (reduced motion,
   the Reduce motion toggle, data saver, a WebGL failure), beside the poster's sculpture, without a leader.
   The extra class outranks the two rules above on purpose. */
body[data-design] .ambient-scene .scene-label.scene-label-identity.scene-label-still { display: grid; }

/* Lenis (vendor/lenis.mjs) rules, inlined. The `.lenis-stopped { overflow }` rule is omitted on
   purpose: the runtime never calls lenis.stop(), and body overflow must stay untouched. */
html.lenis,
html.lenis body { height: auto; }
.lenis [data-lenis-prevent],
.lenis [data-lenis-prevent-wheel],
.lenis [data-lenis-prevent-touch],
.lenis [data-lenis-prevent-vertical],
.lenis [data-lenis-prevent-horizontal] { overscroll-behavior: contain; }
.lenis.lenis-smooth iframe { pointer-events: none; }
.lenis.lenis-autoToggle {
  transition-property: overflow;
  transition-duration: 1ms;
  transition-behavior: allow-discrete;
}

/* Inner-page scenes (the Atlas Database page) on narrow windows and phones: the content column
   leaves 16–36 px gutters, where the scene showed sharp slivers of atoms at both window edges beside
   the glass-blurred mass behind the table (review round 1). The scene layer fades out toward the
   window edges instead; the static tiers' poster fades with it. */
@media (max-width: 768px) {
  body[data-design][data-scene-page] .ambient-scene {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 28px, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(90deg, transparent, #000 28px, #000 calc(100% - 28px), transparent);
  }
}
