/* ─────────────────────────────────────────────────────────────
   Haotong Liang — image-first portfolio
   ───────────────────────────────────────────────────────────── */

:root {
  --bg: #0b0b0c;
  --bg-2: #121214;
  --ink: #f2f0ed;
  --ink-2: #a8a5a0;
  --ink-3: #6f6c68;
  --line: rgba(255, 255, 255, 0.11);
  --accent: #ff7a35;
  --serif: 'Source Serif 4', Georgia, 'Times New Roman', serif;
  --sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  --pad: clamp(1.25rem, 4vw, 4.5rem);
  --maxw: 1500px;
  /* The gap above a chapter head. It is also the head's scroll-margin, which
     is what makes a snap land on exactly the same pixel as a rail click —
     any disagreement between the two shows up as a yank after the jump. */
  --chapter-pad: clamp(4.5rem, 11vh, 8.5rem);
}

* { box-sizing: border-box; }

/* `proximity`, not `mandatory`: drifting to a stop near a chapter settles onto
   it, but a deliberate scroll runs straight past without the page grabbing. */
html {
  scroll-behavior: smooth;
  scroll-snap-type: y proximity;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, video { display: block; max-width: 100%; }

a { color: inherit; }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--ink);
  color: var(--bg);
  padding: 0.75rem 1.25rem;
  border-radius: 0 0 6px 0;
  text-decoration: none;
}
.skip:focus { left: 0; }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* ── Top bar ───────────────────────────────────────────────── */

.topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.9rem var(--pad);
  background: linear-gradient(to bottom, rgba(11, 11, 12, 0.82), rgba(11, 11, 12, 0));
  transition: background 0.35s ease, backdrop-filter 0.35s ease, border-color 0.35s ease;
  border-bottom: 1px solid transparent;
}
.topbar.solid {
  background: rgba(11, 11, 12, 0.9);
  backdrop-filter: saturate(1.4) blur(14px);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  border-bottom-color: var(--line);
}

/* Read-through progress, sitting on the bar's own bottom border. It stands in
   for the rail on the screens too narrow or too short to show one, so it is
   displayed under exactly the media conditions that hide the rail. */
.progress {
  display: none;
  position: absolute;
  left: 0;
  bottom: -1px;
  width: 100%;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  opacity: 0;
  transition: opacity 0.35s ease;
}
.topbar.solid .progress { opacity: 1; }

.brand {
  font-family: var(--serif);
  font-size: 1.06rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  white-space: nowrap;
}

.topnav {
  display: flex;
  align-items: center;
  gap: clamp(0.9rem, 2.2vw, 1.9rem);
}
.topnav a {
  font-size: 0.83rem;
  letter-spacing: 0.04em;
  color: var(--ink-2);
  text-decoration: none;
  transition: color 0.2s ease;
}
.topnav a:hover { color: var(--ink); }

.topnav .cta {
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.42rem 1.05rem;
  transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.topnav .cta:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: #140800;
}

/* On narrow screens keep only the brand and the résumé call to action —
   email and GitHub are reachable from the hero and the footer. */
@media (max-width: 640px) {
  .topnav a:not(.cta) { display: none; }
  .brand { font-size: 0.98rem; }
}

/* ── Chapter rail ──────────────────────────────────────────────
   A fixed index down the right edge: seven ticks, the current one long and
   accented. It appears only once the hero is behind you, so the opener stays
   a photograph rather than a photograph with furniture on it. */

.rail {
  position: fixed;
  z-index: 80;
  top: 50%;
  right: clamp(0.9rem, 2vw, 1.9rem);
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  opacity: 0;
  visibility: hidden;   /* also takes the links out of the tab order */
  transition: opacity 0.45s ease, visibility 0.45s;
}
.rail.on { opacity: 1; visibility: visible; }

.rail-item {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.55rem;
  padding: 0.26rem 0;
  text-decoration: none;
}

/* The label is the only part that moves, and it moves under the pointer —
   the ticks and numbers hold their column so the rail never reflows. */
.rail-label {
  font-size: 0.72rem;
  letter-spacing: 0.015em;
  white-space: nowrap;
  color: var(--ink-2);
  opacity: 0;
  transform: translateX(7px);
  transition: opacity 0.28s ease, transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), color 0.2s ease;
  text-shadow: 0 1px 10px rgba(9, 9, 11, 0.85);
}

/* The rail floats over whatever photograph is passing, and a chapter opener
   can be nearly white. Translucent white plus a shadow reads on both grounds,
   where a flat grey disappears into the light ones. */
.rail-tick {
  flex: none;
  width: 16px;
  height: 1px;
  background: rgba(255, 255, 255, 0.45);
  box-shadow: 0 0 5px rgba(9, 9, 11, 0.8);
  transition: width 0.32s cubic-bezier(0.22, 1, 0.36, 1), background 0.25s ease, height 0.2s ease;
}

.rail-n {
  flex: none;
  width: 1.3rem;      /* fixed, so the ticks line up whatever the numeral */
  text-align: right;
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  color: rgba(255, 255, 255, 0.52);
  transition: color 0.25s ease;
  text-shadow: 0 1px 10px rgba(9, 9, 11, 0.85);
}

.rail-item:hover .rail-label,
.rail-item:focus-visible .rail-label,
.rail-item[aria-current] .rail-label { opacity: 1; transform: none; }

.rail-item:hover .rail-label,
.rail-item:focus-visible .rail-label,
.rail-item:hover .rail-n,
.rail-item:focus-visible .rail-n { color: var(--ink); }

.rail-item:hover .rail-tick,
.rail-item:focus-visible .rail-tick { width: 26px; background: rgba(255, 255, 255, 0.85); }

.rail-item[aria-current] .rail-tick { width: 30px; height: 2px; background: var(--accent); }
.rail-item[aria-current] .rail-n { color: var(--accent); }
.rail-item[aria-current] .rail-label { color: var(--ink); }

/* Below this the rail has nowhere to sit: too narrow and the labels cross the
   image, too short and seven ticks do not fit between the edges. */
@media (max-width: 1000px), (max-height: 560px) {
  .rail { display: none; }
  .progress { display: block; }
}

/* ── Buttons ───────────────────────────────────────────────── */

.btn {
  display: inline-block;
  font-size: 0.9rem;
  letter-spacing: 0.02em;
  text-decoration: none;
  padding: 0.85rem 1.6rem;
  border-radius: 999px;
  border: 1px solid transparent;
  transition: transform 0.2s ease, background 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}
.btn:hover { transform: translateY(-2px); }

.btn-primary { background: var(--accent); color: #170800; font-weight: 500; }
.btn-primary:hover { background: #ff8f57; }

.btn-ghost { border-color: rgba(255, 255, 255, 0.32); color: var(--ink); }
.btn-ghost:hover { border-color: var(--ink); background: rgba(255, 255, 255, 0.07); }

.btn-lg { padding: 1.05rem 2.4rem; font-size: 1rem; }

/* ── Hero ──────────────────────────────────────────────────── */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}

.hero-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center center;
  filter: contrast(1.16) saturate(1.1) brightness(1.08);
  z-index: 0;
}

/* Two scrims: one anchors the text to the bottom-left, one keeps the
   plume on the right readable rather than washing it out. */
.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  background:
    linear-gradient(to top, var(--bg) 0%, rgba(11, 11, 12, 0.78) 14%, rgba(11, 11, 12, 0.22) 44%, rgba(11, 11, 12, 0) 72%, rgba(11, 11, 12, 0.45) 100%),
    linear-gradient(to right, rgba(11, 11, 12, 0.88) 0%, rgba(11, 11, 12, 0.42) 26%, rgba(11, 11, 12, 0) 50%);
}

/* The copy is bottom-anchored, so on a short viewport it grows upward and
   slides under the fixed bar. The top padding is its clearance: invisible on
   tall screens, and on short ones it pushes the hero past 100svh instead of
   letting the nav land on the first line. */
.hero-copy {
  position: relative;
  z-index: 2;
  padding: clamp(5.5rem, 14vh, 8rem) var(--pad) clamp(5rem, 12vh, 9rem);
  max-width: 52rem;   /* enough for the eyebrow to hold one line on desktop */
  /* The line ends over the plume, which is the brightest part of the frame —
     a soft shadow holds contrast there without dimming the photograph. */
  text-shadow: 0 1px 18px rgba(9, 9, 11, 0.6);
}

.eyebrow {
  margin: 0 0 1.1rem;
  font-size: 0.76rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Sized against viewport height as well as width, so a short laptop window
   or a phone in landscape does not get a headline taller than the screen. */
.hero h1 {
  margin: 0 0 1.4rem;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(2.3rem, min(7.2vw, 9.5vh), 5.4rem);
  line-height: 1.03;
  letter-spacing: -0.022em;
}

.hero-lede {
  margin: 0 0 2.2rem;
  max-width: 36rem;
  font-size: clamp(1rem, 1.35vw, 1.13rem);
  font-weight: 300;
  color: #ddd9d4;
}

.hero-actions { display: flex; flex-wrap: wrap; gap: 0.85rem; }

.scroll-cue {
  position: absolute;
  z-index: 2;
  bottom: 1.6rem;
  left: 50%;
  transform: translateX(-50%);
  width: 22px;
  height: 36px;
  border: 1px solid rgba(255, 255, 255, 0.4);
  border-radius: 999px;
}
.scroll-cue span {
  position: absolute;
  top: 7px;
  left: 50%;
  width: 3px;
  height: 7px;
  margin-left: -1.5px;
  border-radius: 2px;
  background: var(--ink);
  animation: cue 1.9s ease-in-out infinite;
}
@keyframes cue {
  0%, 100% { transform: translateY(0); opacity: 1; }
  55% { transform: translateY(12px); opacity: 0.15; }
}

/* ── Facts strip ───────────────────────────────────────────── */

.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 1px;
  background: var(--line);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.fact {
  background: var(--bg);
  padding: 1.9rem var(--pad);
}
.fact b {
  display: block;
  font-family: var(--serif);
  font-size: clamp(1.35rem, 3.4vw, 1.75rem);
  font-weight: 400;
  line-height: 1.15;
  overflow-wrap: break-word;      /* a long word must never push the page wide */
}
.fact span {
  display: block;
  margin-top: 0.3rem;
  font-size: 0.8rem;
  letter-spacing: 0.05em;
  color: var(--ink-3);
}

/* ── Chapters ──────────────────────────────────────────────── */

.chapter { padding: var(--chapter-pad) 0 0; }

.chapter-head {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--pad) clamp(2.2rem, 5vh, 3.4rem);
  /* The snap target. Matching --chapter-pad puts the settled position exactly
     where an #anchor jump lands, so the two never fight each other. */
  scroll-snap-align: start;
  scroll-margin-top: var(--chapter-pad);
}

/* Arriving at a chapter reads as a beat: number, then title, then the lead,
   and the opening image a moment behind all three. */
.chapter-head > * {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
.chapter-head.in > * { opacity: 1; transform: none; }
.chapter-head.in h2 { transition-delay: 0.09s; }
.chapter-head.in p { transition-delay: 0.18s; }

.chapter-n {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  width: max-content;
  margin-bottom: 0.9rem;
  font-family: var(--sans);
  font-size: 0.74rem;
  letter-spacing: 0.22em;
  color: var(--accent);
}
/* The rule draws itself out of the numeral, which is what marks the stop. */
.chapter-n::after {
  content: '';
  flex: none;
  width: 3.4rem;
  height: 1px;
  background: currentColor;
  opacity: 0.55;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform 0.75s cubic-bezier(0.22, 1, 0.36, 1) 0.14s;
}
.chapter-head.in .chapter-n::after { transform: none; }

.chapter-head h2 {
  margin: 0 0 1rem;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(1.85rem, 4.2vw, 3.1rem);
  line-height: 1.08;
  letter-spacing: -0.018em;
  max-width: 20ch;
}
.chapter-head p {
  margin: 0;
  max-width: 52ch;
  font-size: clamp(0.97rem, 1.2vw, 1.06rem);
  font-weight: 300;
  color: var(--ink-2);
}

/* ── Shots ─────────────────────────────────────────────────── */

.shot {
  margin: 0;
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.75s ease, transform 0.75s cubic-bezier(0.22, 1, 0.36, 1);
}
.shot.in { opacity: 1; transform: none; }

.shot-btn {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  font: inherit;
  color: inherit;
}

.shot-frame {
  display: block;
  position: relative;
  overflow: hidden;
  background-color: #17171a;
  background-size: cover;
  background-position: center;
  border-radius: 3px;
}

.shot img,
.shot video {
  width: 100%;
  height: auto;
  opacity: 0;
  transition: opacity 0.7s ease, transform 0.9s cubic-bezier(0.22, 1, 0.36, 1);
}
.shot img.loaded, .shot video { opacity: 1; }
.shot-btn:hover img { transform: scale(1.035); }

.shot figcaption {
  padding: 0.85rem 0.15rem 0;
  font-size: 0.85rem;
  line-height: 1.5;
}
.shot figcaption b {
  display: block;
  font-weight: 500;
  letter-spacing: 0.005em;
}
.shot figcaption span {
  display: block;
  margin-top: 0.15rem;
  color: var(--ink-3);
  font-weight: 300;
}

/* Feature: full-bleed opener for each chapter */
.shot-feature { margin-bottom: clamp(2rem, 5vh, 3.6rem); }
/* Held back until the chapter head has finished landing */
.shot-feature.in { transition-delay: 0.3s; }
.shot-feature .shot-frame { border-radius: 0; }
.shot-feature img {
  width: 100%;
  max-height: 82svh;
  object-fit: cover;
}
.shot-feature figcaption {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0.95rem var(--pad) 0;
}

/* CSS columns are the no-JS fallback; script.js upgrades this to real
   shortest-column packing so the columns end level. */
.grid.masonry {
  display: flex;
  align-items: flex-start;
  /* column-gap above doubles as the flex gap */
}
.masonry-col { flex: 1 1 0; min-width: 0; }

.grid {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 0 var(--pad);
  column-count: 3;
  column-gap: clamp(0.9rem, 1.6vw, 1.6rem);
}
.grid .shot {
  break-inside: avoid;
  margin-bottom: clamp(1.6rem, 3vw, 2.6rem);
}

/* Three columns need at least five tiles to balance; fewer than that leaves
   a hole where the short columns run out. */
.grid[data-count="4"],
.grid[data-count="3"] { column-count: 2; }
.grid[data-count="2"] { column-count: 2; }

@media (max-width: 1100px) { .grid { column-count: 2; } }
@media (max-width: 620px)  { .grid, .grid[data-count] { column-count: 1; } }

.shot-video .shot-frame { cursor: default; }
.shot-video video { border-radius: 3px; }

/* A wide slot: diagrams, dense plots and animations that must not be cropped
   and need to stay readable. Narrower than the grid, centred, never a column. */
.chapter-wide {
  max-width: 1100px;
  margin: clamp(1.8rem, 3.5vw, 3rem) auto 0;
  padding: 0 var(--pad);
}
.chapter-wide figcaption { text-align: center; }
.chapter-wide figcaption span { max-width: 62ch; margin-inline: auto; }

/* Plots and detector readouts arrive on a white background. Giving them a
   light card says "this is data", instead of looking like a bad exposure. */
/* Pure white, so figures exported on a white canvas meet the card edge
   invisibly and ones with transparency sit on the same ground. */
.shot-figure .shot-frame {
  background-color: #fff;
  padding: clamp(0.5rem, 1vw, 0.9rem);
}
.shot-figure img,
.shot-figure video { border-radius: 2px; }
.shot-figure .shot-btn:hover img { transform: scale(1.015); }

/* ── Repository cards ──────────────────────────────────────────
   The open-source chapter. Two cards, side by side on anything wide
   enough, each one a single link the size of the whole card. */

.repos {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: clamp(1rem, 1.6vw, 1.6rem);
  max-width: var(--maxw);
  margin: 0 auto clamp(2.4rem, 5vh, 3.6rem);
  padding: 0 var(--pad);
}

.repo {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.55rem;
  padding: clamp(1.5rem, 2.4vw, 2.2rem);
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  text-decoration: none;
  transition: transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),
              border-color 0.25s ease, background 0.25s ease;
}
.repo:hover {
  transform: translateY(-3px);
  border-color: rgba(255, 122, 53, 0.55);
  background: #17161a;
}

.repo-role {
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
}

.repo-name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.6rem, 3vw, 2.1rem);
  line-height: 1.1;
  letter-spacing: -0.015em;
}

.repo-desc {
  max-width: 46ch;
  font-size: 0.95rem;
  font-weight: 300;
  color: var(--ink-2);
}

.repo-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.35rem;
}
.repo-tags i {
  font-style: normal;
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--ink-3);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.2rem 0.65rem;
}

/* The arrow slides on hover, so the card reads as somewhere to go. */
.repo-go {
  margin-top: auto;
  padding-top: 1.1rem;
  font-size: 0.83rem;
  letter-spacing: 0.03em;
  color: var(--ink);
  border-bottom: 1px solid transparent;
  transition: color 0.2s ease;
}
.repo-go::after {
  content: '→';
  display: inline-block;
  margin-left: 0.45em;
  transition: transform 0.25s ease;
}
.repo:hover .repo-go { color: var(--accent); }
.repo:hover .repo-go::after { transform: translateX(4px); }

/* The open-source fact links into the chapter rather than just stating it. */
.fact a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid var(--line);
  transition: color 0.2s ease, border-color 0.2s ease;
}
.fact a:hover { color: var(--ink); border-bottom-color: var(--accent); }

/* ── Résumé band ───────────────────────────────────────────── */

.resume-band {
  margin-top: clamp(5rem, 12vh, 9rem);
  padding: clamp(4.5rem, 12vh, 8rem) var(--pad);
  text-align: center;
  background: var(--bg-2);
  border-top: 1px solid var(--line);
}
.resume-band h2 {
  margin: 0 0 1rem;
  font-family: var(--serif);
  font-weight: 300;
  font-size: clamp(1.8rem, 4vw, 2.9rem);
  line-height: 1.1;
  letter-spacing: -0.018em;
}
.resume-band p {
  margin: 0 auto 2.2rem;
  max-width: 46ch;
  color: var(--ink-2);
  font-weight: 300;
}
.resume-band .eyebrow { margin-bottom: 0.9rem; }

/* ── Footer ────────────────────────────────────────────────── */

.foot {
  padding: 3rem var(--pad) 3.5rem;
  border-top: 1px solid var(--line);
  text-align: center;
}
.foot div {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.6rem;
  margin-bottom: 1.2rem;
}
.foot a {
  font-size: 0.88rem;
  color: var(--ink-2);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-bottom: 2px;
  transition: color 0.2s ease, border-color 0.2s ease;
}
.foot a:hover { color: var(--ink); border-bottom-color: var(--accent); }
.foot p {
  margin: 0;
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--ink-3);
}

/* ── Lightbox ──────────────────────────────────────────────── */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(6, 6, 7, 0.97);
  opacity: 0;
  transition: opacity 0.28s ease;
}
.lightbox[hidden] { display: none; }
.lightbox.open { opacity: 1; }

.lb-stage {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1rem;
  max-width: 100%;
  max-height: 100%;
}
.lb-stage img {
  max-width: min(100%, 1500px);
  max-height: 78svh;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 3px;
}
.lb-stage figcaption {
  text-align: center;
  font-size: 0.9rem;
  max-width: 62ch;
}
.lb-stage figcaption b { display: block; font-weight: 500; }
.lb-stage figcaption span { display: block; margin-top: 0.2rem; color: var(--ink-3); font-weight: 300; }

.lb-close, .lb-nav {
  position: absolute;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--line);
  color: var(--ink);
  cursor: pointer;
  border-radius: 50%;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.lb-close:hover, .lb-nav:hover { background: rgba(255, 255, 255, 0.16); border-color: rgba(255, 255, 255, 0.3); }

.lb-close {
  top: clamp(0.9rem, 2.5vw, 1.6rem);
  right: clamp(0.9rem, 2.5vw, 1.6rem);
  width: 42px;
  height: 42px;
  font-size: 1.6rem;
  line-height: 1;
}
.lb-nav {
  top: 50%;
  transform: translateY(-50%);
  width: 46px;
  height: 46px;
  font-size: 1.9rem;
  line-height: 1;
  padding-bottom: 4px;
}
.lb-prev { left: clamp(0.5rem, 2vw, 1.6rem); }
.lb-next { right: clamp(0.5rem, 2vw, 1.6rem); }

body.lb-lock { overflow: hidden; }

@media (max-width: 620px) {
  .lb-nav { top: auto; bottom: 1rem; transform: none; }
  .lb-stage img { max-height: 66svh; }
}

/* ── Motion / print ────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; scroll-snap-type: none; }
  .shot { opacity: 1; transform: none; transition: none; }
  .chapter-head > * { opacity: 1; transform: none; transition: none; }
  .chapter-n::after { transform: none; transition: none; }
  .scroll-cue span { animation: none; }
  .btn:hover, .shot-btn:hover img, .repo:hover { transform: none; }
  * { transition-duration: 0.01ms !important; }
}
