/*
  landing.css
  Stylesheet for index.html. One dark ground from top to bottom:
  the arc hero, the About section, the projects shelf, the footer. No wave
  dividers -- sections are separated by spacing and a hairline.

  How the arc is built -- all of it in this file, none of it in JS:

    .arcStage      full-width box from the top of the hero down to the
                   "horizon" (--pivot-y), overflow hidden. Anything below
                   the horizon is simply clipped, which is how a whole
                   ring becomes an arc.
    .arcWheel      a 0x0 point sitting on the horizon at centre: the ring's
                   pivot. It carries the ring's rotation:
                     --rot = --drag - --turn * --step
                   where --step = 360deg / --n, --turn is the integer the
                   script increments, and --drag is only ever non-zero
                   mid-swipe.
    .arcTile       rotate(i * --step) translateY(-r): lift the tile up by
                   the radius, then swing it round to its slot, tilting
                   with the ring the way the old wheel's items did.
                   Its angle from the top is --a = i*--step + --rot, so
                   cos(--a) is 1 at the apex, 0 at the horizon and
                   negative below it. That one number drives the scale
                   (biggest at the top) and the fade near the horizon.

  Three tunings of the same geometry, chosen by width:
    >= 1024px   arc:  pivot at 92% of the hero, r ~36vw, 7 tiles across,
                      the copy sits inside the arc.
    641-1023px  fan:  pivot at 50%, bigger r relative to width, 5-7 tiles
                      overlapping like a hand of cards, copy below.
    <= 640px    fan:  pivot at 52%, 5 tiles with the middle one dominant.

  How the projects shelf is built (see the Projects section further down):
  each project is a box on a shelf, spine towards you. The box is a real
  3D cuboid of two faces -- the spine on the shelf plane, the front face
  folded back behind it -- rotating about the spine's centre line. Select
  one and it turns 90deg to face you, its glass cover swings open on a left
  hinge, and the photos inside eject to the right. Because the turned-out
  front sits half a spine's depth in front of the shelf plane, its spilled
  photos always paint over the neighbouring spines.
*/

:root {
  /* The site's brand gradients, carried over unchanged from the original
     style.css. */
  --base-grad: linear-gradient(
    25deg,
    #ee7752,
    #ff5e00,
    #e73c7e,
    #d90ead,
    #23a6d5,
    #70e0ec,
    #23d5ab,
    #00ff44
  );
  --istanbul-grad: linear-gradient(
    25deg,
    #4aa3b3a6,
    #04bcff80,
    #008cff7e,
    #ffa200,
    #ffd900,
    #ffd5008d
  );
  --edinburgh-grad: linear-gradient(
    25deg,
    #005effe1,
    #005effe1,
    #00c8ff,
    #6dff50d1,
    #ffe600,
    #ff7300,
    #ff1302f1
  );

  /* Palette: a near-black with the site's violet in it, so the brand
     gradient glows against it rather than sitting on plain black. */
  --hero-bg: #0d0b16;
  --hero-fg: #f6f3fb;
  --hero-fg-2: rgba(246, 243, 251, 0.72);
  --hero-fg-3: rgba(246, 243, 251, 0.5);
  --hero-line: rgba(246, 243, 251, 0.16);

  /* One turn of the wheel. */
  --turn-dur: 0.9s;
  --turn-ease: cubic-bezier(0.22, 0.85, 0.25, 1);

  /* Hero geometry (desktop values). Declared here rather than on .arcHero
     because the About section's bloom on desktop is positioned off the
     hero's horizon and radius. --hero-h mirrors the hero's height rule so
     the radius cap can be written in terms of the hero's real height -- a
     percentage wouldn't resolve inside translateY(). The tablet/phone
     media blocks override both on .arcHero. */
  --hero-h: max(100vh, 640px);
  --hero-h: max(100svh, 640px);
  --r: min(36vw, 620px, calc(var(--hero-h) * 0.92 - 230px));
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  overflow-x: hidden;
}

body {
  position: relative;
  margin: 0;
  padding: 0;
  background: var(--hero-bg);
  color: var(--hero-fg);
  overflow-x: hidden;
}

body.lb-open {
  overflow: hidden;
}

@keyframes Gradient {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}

/* ---------- Scroll reveal (replaces AOS) ---------- */

[data-reveal] {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.9s ease, transform 0.9s ease;
  transition-delay: var(--reveal-delay, 0s);
}

[data-reveal="right"] {
  transform: translateX(-48px);
}

[data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal] {
    transition: none;
    transform: none;
    opacity: 1;
  }
}

/* ======================================================================
   Hero
   ====================================================================== */

.arcHero {
  /* Geometry (desktop values; --hero-h and --r come from :root, the two
     media blocks further down retune everything here and nothing else). */
  --bar-h: 76px;
  --pivot-y: 92%;
  /* Tile width is the spacing knob: the arc allots each tile r * 24deg of
     arc (~0.42r), so whatever a tile doesn't use is gap. 10.4vw of a
     1440px screen is 150px on a 217px allotment -- a 67px gap. */
  --tile-w: clamp(96px, 10.4vw, 180px);
  /* Scale = --s-min + (1 - --s-min) * cos(angle)^--s-pow: the apex tile is
     always 1; a higher power makes the falloff sharper. */
  --s-min: 0.82;
  --s-pow: 2;
  /* Where the copy block is anchored. On desktop its centre sits inside
     the arc, halfway between the apex tile's bottom edge (pivot - r +
     tile height / 2, with tile height = 4/3 tile width) and the horizon;
     on the fan layouts its top sits just below the horizon. */
  --copy-y: calc(var(--pivot-y) - 0.5 * var(--r) + var(--tile-w) / 3);
  --copy-shift: -50%;

  position: relative;
  height: var(--hero-h);
  overflow: hidden;
  background: var(--hero-bg);
  color: var(--hero-fg);
  font-family: "Poppins", system-ui, sans-serif;
  /* Horizontal swipes are ours (they turn the wheel); vertical ones stay
     with the browser so the page still scrolls normally. */
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}

/* Top bar */

.arcBar {
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--bar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 clamp(16px, 4vw, 48px);
  z-index: 6;
}

.arcBrand {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  color: var(--hero-fg);
  text-decoration: none;
}

/* The old wheel's gradient core, kept as the mark. */
.arcMark {
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: #fff;
  background: var(--base-grad);
  background-size: 400% 400%;
  animation: Gradient 15s ease infinite;
  box-shadow: 0 8px 20px -8px rgba(217, 14, 173, 0.7);
}

/* Wordmark in the brand gradient. Its two words are inline-blocks so that
   on phones (below) it breaks cleanly onto two lines instead of colliding
   with the nav. */
.arcWordmark {
  display: inline-block;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  background: var(--base-grad);
  background-size: 400% 400%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: Gradient 15s ease infinite;
}

.arcWordmark > span {
  display: inline-block;
  white-space: nowrap;
}

.arcNav {
  display: flex;
  gap: clamp(18px, 2.4vw, 36px);
}

.arcNav a {
  position: relative;
  padding: 6px 0;
  color: var(--hero-fg-2);
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  transition: color 0.25s ease;
}

.arcNav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  border-radius: 2px;
  background: var(--base-grad);
  background-size: 400% 400%;
  animation: Gradient 15s ease infinite;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.35s var(--turn-ease);
}

.arcNav a:hover,
.arcNav a:focus-visible {
  color: var(--hero-fg);
}

.arcNav a:hover::after,
.arcNav a:focus-visible::after {
  transform: scaleX(1);
}

/* Glow under the horizon -- the sun the arc rises over. Sits outside
   .arcStage so it isn't clipped at the horizon and can bleed up under
   the copy. */
.arcGlow {
  position: absolute;
  left: 50%;
  top: var(--pivot-y);
  width: calc(var(--r) * 3.2);
  height: calc(var(--r) * 1.7);
  translate: -50% -50%;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(217, 14, 173, 0.5),
    rgba(231, 60, 126, 0.26) 32%,
    rgba(35, 166, 213, 0.12) 58%,
    transparent 100%
  );
  pointer-events: none;
  z-index: 1;
  animation: arcGlow 12s ease-in-out infinite alternate;
}

@keyframes arcGlow {
  from {
    opacity: 0.75;
    scale: 1;
  }
  to {
    opacity: 1;
    scale: 1.06;
  }
}

/* The wheel */

.arcStage {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: var(--pivot-y);
  overflow: hidden;
  z-index: 2;
}

.arcHorizon {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent,
    var(--hero-line) 18%,
    var(--hero-line) 82%,
    transparent
  );
}

.arcWheel {
  position: absolute;
  left: 50%;
  top: 100%;
  width: 0;
  height: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  --step: calc(360deg / var(--n, 15));
  --drag: 0deg;
  --rot: calc(var(--drag) - var(--turn, 0) * var(--step));
  transform: rotate(var(--rot));
  transition: transform var(--turn-dur) var(--turn-ease);
  /* Entrance: the whole ring swings in from ~55deg back. fill-mode
     backwards only, so once it lands the plain transform rule takes over
     and turning the wheel transitions normally. */
  animation: arcSpinIn 1.6s cubic-bezier(0.16, 0.84, 0.3, 1) backwards;
}

@keyframes arcSpinIn {
  from {
    transform: rotate(calc(var(--rot) - 55deg));
  }
}

.arcTile {
  position: absolute;
  left: 0;
  top: 0;
  width: var(--tile-w);
  aspect-ratio: 3 / 4;
  /* Centre the tile's box on the pivot before the transform swings it out. */
  translate: -50% -50%;
  --a: calc(var(--i, 0) * var(--step) + var(--rot));
  --k: max(0, cos(var(--a)));
  transform: rotate(calc(var(--i, 0) * var(--step))) translateY(calc(-1 * var(--r)));
  transition: transform var(--turn-dur) var(--turn-ease), opacity var(--turn-dur) linear;
  will-change: transform;
  /* Staggered fade-in from the apex outwards (min() of the two ways round
     the ring is the tile's distance from the top at load). */
  animation: arcFadeIn 0.8s ease backwards;
  animation-delay: calc(0.12s + 0.05s * min(var(--i, 0), var(--n, 15) - var(--i, 0)));
}

@keyframes arcFadeIn {
  from {
    opacity: 0;
  }
}

/* Scale-by-angle and the fade near the horizon need CSS trig. Every
   current browser has it (Baseline 2023/24); anything older gets the
   plain version above -- uniform tiles, still clipped at the horizon.
   Kept behind @supports rather than as a second declaration because a
   var()-bearing value that fails only at computed-value time would wipe
   the property instead of falling back. */
@supports (transform: scale(pow(cos(0deg), 2))) {
  .arcTile {
    transform: rotate(calc(var(--i, 0) * var(--step))) translateY(calc(-1 * var(--r)))
      scale(calc(var(--s-min) + (1 - var(--s-min)) * pow(var(--k), var(--s-pow))));
    opacity: clamp(0, calc(var(--k) * 4), 1);
  }
}

.arcHero.is-dragging .arcWheel,
.arcHero.is-dragging .arcTile {
  transition: none;
}

.arcTileBtn {
  display: block;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: #1a1526;
  border-radius: calc(var(--tile-w) * 0.13);
  overflow: hidden;
  cursor: pointer;
  box-shadow: 0 24px 48px -18px rgba(0, 0, 0, 0.7);
  transition: translate 0.35s ease, box-shadow 0.35s ease;
  -webkit-tap-highlight-color: transparent;
}

/* Lift is along the tile's own up-axis, so it moves outward from the
   ring's centre -- reads as the card coming towards you. */
.arcTileBtn:hover {
  translate: 0 -6px;
  box-shadow: 0 32px 56px -16px rgba(0, 0, 0, 0.75), 0 0 0 2px rgba(255, 255, 255, 0.4);
}

.arcTileBtn:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 4px;
}

.arcTileBtn img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
  -webkit-user-drag: none;
  user-select: none;
}

/* Copy */

.arcCopy {
  position: absolute;
  left: 50%;
  top: var(--copy-y);
  translate: -50% var(--copy-shift);
  width: min(560px, calc(100% - 48px));
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
  z-index: 3;
  /* Let drags that start over the text reach the hero; only the links
     take the pointer. */
  pointer-events: none;
}

.arcCopy a {
  pointer-events: auto;
}

/* Small gradient label, used as the eyebrow of every section. */
.arcEyebrow {
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  background: var(--base-grad);
  background-size: 400% 400%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: Gradient 15s ease infinite;
}

.arcTitle {
  font-size: clamp(38px, 5.4vw, 72px);
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.arcLede {
  max-width: 36ch;
  font-size: clamp(15px, 1.15vw, 18px);
  line-height: 1.55;
  color: var(--hero-fg-2);
  text-wrap: pretty;
}

.arcActions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 10px 22px;
  margin-top: 8px;
}

/* White pill; on hover a brand-gradient ring appears behind it. The ring
   and the fill are both pseudo-elements at negative z-index inside the
   button's own stacking context, so the text stays on top of both. */
.arcBtn {
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  padding: 0.9em 1.7em;
  border-radius: 999px;
  color: var(--hero-bg);
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.02em;
  text-decoration: none;
  box-shadow: 0 12px 30px -12px rgba(0, 0, 0, 0.6);
  transition: transform 0.3s ease;
}

.arcBtn::before,
.arcBtn::after {
  content: "";
  position: absolute;
  border-radius: inherit;
}

.arcBtn::before {
  inset: -3px;
  z-index: -2;
  background: var(--base-grad);
  background-size: 400% 400%;
  animation: Gradient 15s ease infinite;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.arcBtn::after {
  inset: 0;
  z-index: -1;
  background: #fff;
}

.arcBtn:hover,
.arcBtn:focus-visible {
  transform: translateY(-2px);
}

.arcBtn:hover::before,
.arcBtn:focus-visible::before {
  opacity: 1;
}

.arcBtn:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 5px;
}

.arcLink {
  color: var(--hero-fg-2);
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
  transition: color 0.25s ease;
}

.arcLink:hover,
.arcLink:focus-visible {
  color: var(--hero-fg);
}

.arcHint {
  margin-top: 6px;
  font-size: 12px;
  color: var(--hero-fg-3);
}

/* Round arrow buttons -- positioned in the hero, in-flow on the shelf. */

.arcArrow {
  position: absolute;
  top: var(--copy-y);
  translate: 0 -50%;
  width: 52px;
  height: 52px;
  border-radius: 50%;
  border: 1px solid var(--hero-line);
  background: rgba(255, 255, 255, 0.06);
  color: var(--hero-fg);
  display: grid;
  place-items: center;
  cursor: pointer;
  z-index: 4;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: background 0.25s ease, border-color 0.25s ease, transform 0.25s ease;
  -webkit-tap-highlight-color: transparent;
}

.arcArrow--prev {
  left: clamp(16px, 4vw, 56px);
}

.arcArrow--next {
  right: clamp(16px, 4vw, 56px);
}

.arcArrow--inline {
  position: static;
  translate: none;
}

.arcArrow:hover {
  background: rgba(255, 255, 255, 0.14);
  border-color: rgba(255, 255, 255, 0.4);
}

.arcArrow:active {
  transform: scale(0.94);
}

.arcArrow:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

/* Scroll cue */

.arcScrollCue {
  position: absolute;
  left: 50%;
  bottom: 18px;
  translate: -50% 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: var(--hero-fg-3);
  text-decoration: none;
  font-size: 11px;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  z-index: 4;
}

.arcScrollCueLine {
  width: 1px;
  height: 34px;
  background: linear-gradient(180deg, transparent, var(--hero-fg-2));
  transform-origin: top;
  animation: arcCue 2.2s ease-in-out infinite;
}

@keyframes arcCue {
  0%,
  100% {
    transform: scaleY(0.4);
    opacity: 0.4;
  }
  50% {
    transform: scaleY(1);
    opacity: 1;
  }
}

.arcScrollCue:hover {
  color: var(--hero-fg);
}

/* Short desktop windows (a 720p laptop, an iPad on its side): the space
   between the apex tile and the horizon is only ~270px, so the copy
   tightens up and drops its hint line, and the cue shrinks to fit under
   the horizon. */
@media (min-width: 1024px) and (max-height: 800px) {
  .arcCopy {
    gap: 11px;
  }

  .arcHint {
    display: none;
  }

  .arcScrollCue {
    bottom: 12px;
    gap: 6px;
  }

  .arcScrollCueLine {
    height: 22px;
  }
}

@media (max-height: 560px) {
  .arcScrollCue {
    display: none;
  }
}

/* ---------- Tablet & phone: the fan ---------- */

@media (max-width: 1023px) {
  .arcHero {
    --bar-h: 64px;
    --pivot-y: 50%;
    --r: max(120px, min(clamp(190px, 44vw, 340px), calc(var(--hero-h) * 0.5 - 210px)));
    --tile-w: clamp(112px, 19.5vw, 160px);
    --s-min: 0.7;
    --s-pow: 4;
    --copy-y: calc(var(--pivot-y) + 22px);
    --copy-shift: 0%;
  }

  .arcHero .arcArrow {
    top: calc(var(--pivot-y) + 24px);
    translate: 0 0;
    width: 44px;
    height: 44px;
  }
}

@media (max-width: 640px) {
  .arcHero {
    /* Capped below the full screen height on tall phones so the next
       section (and the fact that there's a page under this) shows without
       scrolling. */
    --hero-h: clamp(600px, 100vh, 780px);
    --hero-h: clamp(600px, 100svh, 780px);
    --pivot-y: 50%;
    /* Five cards across: each 24deg step must move a tile ~0.8 of its own
       width sideways, so at ~210px radius (85px per step) the tiles are
       ~105px wide -- the middle one the biggest, the outer two cut by the
       screen edge, like the reference. */
    --r: max(120px, min(clamp(170px, 54vw, 230px), calc(var(--hero-h) * 0.5 - 180px)));
    --tile-w: clamp(88px, 25vw, 112px);
    --copy-y: calc(var(--pivot-y) - 56px);
  }

  /* "Ben Jenkins" over "Photography": a grid, not a width cap, so the box
     always covers both words (text outside a background-clip: text box
     has no gradient behind it and disappears). */
  .arcWordmark {
    display: inline-grid;
    gap: 1px;
    font-size: 11px;
    letter-spacing: 0.1em;
  }

  .arcNav {
    gap: 14px;
  }

  .arcNav a {
    font-size: 12px;
    letter-spacing: 0.1em;
  }

  .arcCopy {
    gap: 10px;
  }

  .arcTitle {
    font-size: clamp(34px, 10vw, 44px);
  }

  /* Bottom corners, either side of the scroll cue. */
  .arcHero .arcArrow {
    top: auto;
    bottom: 14px;
    width: 42px;
    height: 42px;
  }

  .arcHero .arcArrow--prev {
    left: 16px;
  }

  .arcHero .arcArrow--next {
    right: 16px;
  }
}

/* ======================================================================
   Enlarged view
   ====================================================================== */

.arcLightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  grid-template-columns: 56px 1fr 56px;
  align-items: center;
  justify-items: center;
  gap: 8px;
  padding: 0 clamp(8px, 2vw, 28px);
  background: rgba(9, 7, 16, 0.8);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  color: var(--hero-fg);
  font-family: "Poppins", system-ui, sans-serif;
  animation: lbFade 0.35s ease backwards;
}

/* Author styles beat the UA's [hidden]{display:none}, so restate it. */
.arcLightbox[hidden] {
  display: none;
}

@keyframes lbFade {
  from {
    opacity: 0;
  }
}

.lbFigure {
  grid-column: 2;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  max-width: 100%;
}

/* The gradient backdrop that framed the enlarged photo in the old wheel
   (.photoBg), now a padded frame around the image. It fades in a beat
   after the photo lands, as before. */
.lbFrame {
  position: relative;
  padding: clamp(5px, 0.8vh, 9px);
  border-radius: 14px;
}

.lbFrame::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--base-grad);
  background-size: 400% 400%;
  animation: Gradient 15s ease infinite, lbFrameIn 0.7s ease 0.25s backwards;
}

@keyframes lbFrameIn {
  from {
    opacity: 0;
  }
}

/* Sized by height so the box doesn't change when the thumbnail is
   swapped for the 1600px file; --lb-ar (width / height) is set inline
   by the script from the thumbnail's natural size. */
.lbImg {
  --lb-ar: 0.667;
  position: relative;
  display: block;
  height: min(80svh, calc((100vw - 190px) / var(--lb-ar)));
  width: auto;
  aspect-ratio: var(--lb-ar);
  object-fit: cover;
  border-radius: 9px;
  background: #1a1526;
  cursor: zoom-out;
}

.lbCaption {
  display: flex;
  align-items: baseline;
  gap: 18px;
  font-size: 13px;
  color: var(--hero-fg-2);
}

.lbCount {
  color: var(--hero-fg-3);
  font-variant-numeric: tabular-nums;
}

.lbClose,
.lbNav {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid var(--hero-line);
  background: rgba(255, 255, 255, 0.07);
  color: var(--hero-fg);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: background 0.25s ease, border-color 0.25s ease;
  -webkit-tap-highlight-color: transparent;
}

.lbClose {
  position: fixed;
  top: 16px;
  right: 16px;
}

.lbNav--prev {
  grid-column: 1;
}

.lbNav--next {
  grid-column: 3;
}

.lbClose:hover,
.lbNav:hover {
  background: rgba(255, 255, 255, 0.16);
  border-color: rgba(255, 255, 255, 0.4);
}

.lbClose:focus-visible,
.lbNav:focus-visible {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

@media (max-width: 640px) {
  .arcLightbox {
    grid-template-columns: 1fr;
    padding: 0 16px;
  }

  .lbFigure {
    grid-column: 1;
  }

  .lbImg {
    height: min(72svh, calc((100vw - 46px) / var(--lb-ar)));
  }

  .lbNav {
    position: fixed;
    bottom: 18px;
  }

  .lbNav--prev {
    left: 18px;
  }

  .lbNav--next {
    right: 18px;
  }
}

/* The open/close morph. Both the tile's <img> and .lbImg are given
   view-transition-name: arc-photo by the script across the DOM change;
   these rules only set its pace and stop the two snapshots (3:4 tile,
   2:3 photo) from stretching while they cross-fade. */
::view-transition-group(arc-photo) {
  animation-duration: 0.6s;
  animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
}

::view-transition-old(arc-photo),
::view-transition-new(arc-photo) {
  animation-duration: 0.6s;
  height: 100%;
  width: 100%;
  object-fit: cover;
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.5s;
}

/* ======================================================================
   About
   ====================================================================== */

.aboutArc,
.shelfSection,
.footer {
  background: var(--hero-bg);
  color: var(--hero-fg);
  font-family: "Poppins", system-ui, sans-serif;
  border-top: 1px solid var(--hero-line);
}

.aboutArc {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: clamp(72px, 10vh, 140px) clamp(20px, 5vw, 64px);
}

/* Tablet & phone: a cooler cousin of the hero's glow, behind the portrait.
   (Desktop replaces this -- see below.) */
.aboutArc::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 24%;
  top: 50%;
  width: min(640px, 80vw);
  aspect-ratio: 1;
  translate: -50% -50%;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(35, 166, 213, 0.26),
    rgba(217, 14, 173, 0.14) 45%,
    transparent 100%
  );
  pointer-events: none;
}

/* Desktop: the hero's bloom no longer stops at the section edge. This one
   element replaces .arcGlow: its first layer is the hero glow exactly as
   before (same size, same colours, centred on the horizon), and a second,
   cooler lobe carries the colour on down behind the About content. It
   hangs off the top of this section, reaching up to where the hero glow
   used to start; the hero's own stage, copy and controls all have a
   z-index and stay above it. */
@media (min-width: 1024px) {
  .arcGlow {
    display: none;
  }

  .aboutArc {
    /* Open vertically so the bloom can reach up into the hero, but clipped
       horizontally: at 3.2r it's wider than the page and would otherwise
       widen the document on mobile browsers. */
    overflow-x: clip;
    overflow-y: visible;
    border-top-color: transparent;
  }

  .aboutArc::before {
    /* The hero's horizon (92% of its height) in this section's coordinates,
       and half the hero glow's height. */
    --horizon: calc(-0.08 * var(--hero-h));
    --up: calc(0.85 * var(--r));
    left: 50%;
    top: calc(var(--horizon) - var(--up));
    width: calc(var(--r) * 3.2);
    height: calc(var(--up) + 940px);
    aspect-ratio: auto;
    translate: -50% 0;
    border-radius: 0;
    background:
      radial-gradient(
          closest-side,
          rgba(217, 14, 173, 0.5),
          rgba(231, 60, 126, 0.26) 32%,
          rgba(35, 166, 213, 0.12) 58%,
          transparent 100%
        )
        50% 0 / 100% calc(var(--r) * 1.7) no-repeat,
      radial-gradient(
          closest-side,
          rgba(35, 166, 213, 0.22),
          rgba(217, 14, 173, 0.1) 48%,
          transparent 100%
        )
        50% calc(var(--up) - 260px) / 80% 1200px no-repeat;
    animation: arcGlow 12s ease-in-out infinite alternate;
  }
}

.aboutInner {
  position: relative;
  max-width: 1100px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(240px, 400px) 1fr;
  gap: clamp(32px, 6vw, 96px);
  align-items: center;
}

/* Same proportions and corner as an arc tile, tilted a touch. `rotate` is
   the individual property, so it composes with the data-reveal
   transform instead of fighting it. */
.aboutPortrait {
  position: relative;
  margin: 0;
  width: 100%;
  aspect-ratio: 4 / 5;
  border-radius: 28px;
  overflow: hidden;
  rotate: -3deg;
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 40px 80px -30px rgba(0, 0, 0, 0.85);
  background: #1a1526;
}

.aboutPortrait img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.aboutBadge {
  position: absolute;
  left: 14px;
  bottom: 14px;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 6px 14px 6px 6px;
  border-radius: 999px;
  background: rgba(13, 11, 22, 0.6);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(255, 255, 255, 0.22);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.04em;
}

.aboutBadge .arcMark {
  width: 28px;
  height: 28px;
  font-size: 10px;
  box-shadow: none;
}

.aboutText {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 18px;
}

.aboutTitle {
  font-size: clamp(32px, 4.2vw, 56px);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.aboutLead {
  max-width: 34ch;
  font-size: clamp(17px, 1.4vw, 21px);
  line-height: 1.5;
}

.aboutBody {
  max-width: 44ch;
  font-size: clamp(15px, 1.1vw, 17px);
  line-height: 1.6;
  color: var(--hero-fg-2);
}

.aboutContact {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.aboutContact a {
  display: inline-flex;
  flex-direction: column;
  gap: 3px;
  padding: 12px 18px;
  border-radius: 14px;
  border: 1px solid var(--hero-line);
  background: rgba(255, 255, 255, 0.04);
  color: var(--hero-fg);
  text-decoration: none;
  transition: background 0.25s ease, border-color 0.25s ease;
}

.aboutContact a:hover,
.aboutContact a:focus-visible {
  background: rgba(255, 255, 255, 0.09);
  border-color: rgba(255, 255, 255, 0.35);
}

.aboutContact small {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hero-fg-3);
}

.aboutContact span {
  font-size: 15px;
  font-weight: 500;
}

.aboutText .arcActions {
  justify-content: flex-start;
  margin-top: 4px;
}

@media (max-width: 860px) {
  .aboutInner {
    grid-template-columns: 1fr;
  }

  .aboutArc::before {
    left: 50%;
    top: 22%;
  }

  .aboutPortrait {
    max-width: 300px;
    margin: 0 auto;
  }

  .aboutText {
    align-items: center;
    text-align: center;
  }

  .aboutContact,
  .aboutText .arcActions {
    justify-content: center;
  }
}

/* ======================================================================
   Projects: the shelf
   ====================================================================== */

.shelfSection {
  /* Box dimensions, plain px on purpose: the script reads them back to
     centre the active box. Retuned at the phone breakpoint below. */
  --spine-w: 64px;
  --front-w: 260px;
  --box-h: 360px;
  --shelf-gap: 20px;
  /* Clear space either side of the open box, so the cover swinging left
     and the photos ejecting right land in air, not on the neighbours.
     Asymmetric: the cover only travels ~40px, the photos ~150px. */
  --slot-pad-l: 44px;
  --slot-pad-r: 132px;
  --shelf-dur: 0.85s;

  position: relative;
  isolation: isolate;
  overflow: hidden;
  padding: clamp(64px, 9vh, 120px) clamp(16px, 4vw, 48px) clamp(56px, 8vh, 100px);
}

/* Ambient light behind the shelf: broad, dim and cool, so the warm pool
   under the open box (.shelfLedge::after) reads against it. Deliberately
   a different temperature from the hero's magenta. */
.shelfSection::before {
  content: "";
  position: absolute;
  z-index: -1;
  left: 50%;
  top: 42%;
  width: min(1500px, 130vw);
  height: 780px;
  translate: -50% -50%;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    rgba(35, 166, 213, 0.2),
    rgba(112, 224, 236, 0.08) 42%,
    rgba(217, 14, 173, 0.05) 70%,
    transparent 100%
  );
  pointer-events: none;
}

.shelfHead {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
  margin-bottom: clamp(24px, 4vh, 48px);
}

.shelfTitle {
  font-size: clamp(28px, 3.6vw, 48px);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.shelfFilter {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 4px;
  padding: 5px;
  margin-top: 10px;
  border: 1px solid var(--hero-line);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.04);
}

.shelfChip {
  padding: 9px 16px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--hero-fg-2);
  font-family: "Poppins", system-ui, sans-serif;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.25s ease, color 0.25s ease;
  -webkit-tap-highlight-color: transparent;
}

.shelfChip:hover {
  color: var(--hero-fg);
}

.shelfChip.is-active {
  background: #fff;
  color: var(--hero-bg);
}

.shelfChip:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/* The stage supplies the perspective; everything from here down to the
   faces keeps transform-style: preserve-3d so the boxes share one 3D
   space and sort by depth against each other. */
.shelfStage {
  position: relative;
  height: calc(var(--box-h) + 72px);
  perspective: 1400px;
  perspective-origin: 50% 40%;
  touch-action: pan-y;
  -webkit-user-select: none;
  user-select: none;
}

/* The shelf the boxes stand on, with a pool of the brand glow under the
   active one. */
.shelfLedge {
  position: absolute;
  left: 8%;
  right: 8%;
  bottom: 30px;
  height: 1px;
  background: linear-gradient(
    90deg,
    transparent,
    var(--hero-line) 20%,
    var(--hero-line) 80%,
    transparent
  );
  pointer-events: none;
}

.shelfLedge::after {
  content: "";
  position: absolute;
  left: 22%;
  right: 22%;
  top: 1px;
  height: 90px;
  background: radial-gradient(ellipse at 50% 0%, rgba(217, 14, 173, 0.32), transparent 70%);
}

/* The row. Its left edge sits on the stage's centre line; the script
   shifts it left by --shelf-x, the distance from the row's left edge to
   the active box's centre. */
.shelf {
  position: absolute;
  left: 50%;
  top: 36px;
  height: var(--box-h);
  display: flex;
  align-items: stretch;
  gap: var(--shelf-gap);
  margin: 0;
  padding: 0;
  list-style: none;
  transform: translateX(calc(-1 * var(--shelf-x, 0px)));
  transition: transform var(--shelf-dur) var(--turn-ease);
  transform-style: preserve-3d;
}

.shelfItem {
  position: relative;
  flex: 0 0 var(--spine-w);
  height: 100%;
  transform-style: preserve-3d;
  transition: flex-basis var(--shelf-dur) var(--turn-ease);
}

.shelfItem.is-active {
  flex-basis: calc(var(--front-w) + var(--slot-pad-l) + var(--slot-pad-r));
}

.shelfItem[hidden] {
  display: none;
}

/* The box's rotation axis: a zero-width line through the centre of the
   spine, half a spine in from the slot's right edge -- which is the slot's
   middle while the slot is a spine wide. When the slot grows to the front
   face's width plus padding, the axis slides to the right-hand padding's
   inner edge, so the turned-out front (which hangs off the axis to the
   left) lands with its padding either side.

   Rest:   rotateY(0)    -> spine faces you, front folded back behind it
   Active: rotateY(90)   -> front faces you, half a spine in front of the
                            shelf plane; spine edge-on */
.shelfBox {
  position: absolute;
  top: 0;
  left: calc(100% - var(--spine-w) / 2);
  width: 0;
  height: 100%;
  display: block;
  color: var(--hero-fg);
  text-decoration: none;
  transform-style: preserve-3d;
  transform: rotateY(0deg);
  transition: transform var(--shelf-dur) var(--turn-ease), left var(--shelf-dur) var(--turn-ease);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.shelfItem.is-active .shelfBox {
  left: calc(100% - var(--slot-pad-r));
  transform: rotateY(90deg);
}

.shelfBox:focus-visible {
  outline: none;
}

.shelfBox:focus-visible .boxSpine,
.shelfBox:focus-visible .boxCover {
  outline: 3px solid #fff;
  outline-offset: 3px;
}

/* Spine: on the shelf plane, centred on the axis. */
.boxSpine {
  position: absolute;
  top: 0;
  left: calc(var(--spine-w) / -2);
  width: var(--spine-w);
  height: 100%;
  backface-visibility: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  padding: 16px 0;
  border-radius: 7px;
  border: 1px solid rgba(255, 255, 255, 0.3);
  background-size: 400% 400%;
  animation: Gradient 15s ease infinite;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12), 0 24px 40px -20px rgba(0, 0, 0, 0.85);
  transition: border-radius var(--shelf-dur) var(--turn-ease);
}

.boxSpine.istan,
.boxCase.istan {
  background-image: var(--istanbul-grad);
}

.boxSpine.edin,
.boxCase.edin {
  background-image: var(--edinburgh-grad);
  background-size: 600% 600%;
}

.boxSpine.soon,
.boxCase.soon {
  background-image: var(--base-grad);
  filter: saturate(0.55) brightness(0.9);
}

.spineYear {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.85);
}

.spineTitle {
  writing-mode: vertical-rl;
  font-family: "Baskerville", "Georgia", serif;
  font-style: italic;
  font-weight: 300;
  font-size: clamp(15px, 1.3vw, 19px);
  letter-spacing: 3px;
  text-transform: uppercase;
  white-space: nowrap;
  color: #fff;
}

.spineMark {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 8px;
  font-weight: 600;
  color: #fff;
  background: rgba(13, 11, 22, 0.45);
  border: 1px solid rgba(255, 255, 255, 0.35);
}

/* Front face: folded 90deg back from the spine's left edge, so the closed
   box extends away from you into the shelf. Transform functions apply
   right-to-left: the face is first turned edge-on about its own centre,
   then moved so it hangs off the spine's left edge and reaches back a
   full front-width. */
.boxFront {
  position: absolute;
  top: 0;
  left: calc(var(--front-w) / -2);
  width: var(--front-w);
  height: 100%;
  transform: translate3d(calc(var(--spine-w) / -2), 0, calc(var(--front-w) / -2)) rotateY(-90deg);
  backface-visibility: hidden;
  transform-style: preserve-3d;
}

/* Inside the front: the gradient case (with the blurb), the photos, and
   the glass cover on top -- the same three layers as the old cards. */
.boxCase {
  position: absolute;
  inset: 0;
  padding: 18px;
  /* The blurb is texture behind the cover; on a short box it would run
     out of the bottom otherwise. */
  overflow: hidden;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  background-size: 400% 400%;
  animation: Gradient 15s ease infinite;
  box-shadow: 0 34px 60px -26px rgba(0, 0, 0, 0.85);
  transition: border-radius var(--shelf-dur) var(--turn-ease);
}

.boxCase p {
  font-size: 9px;
  font-weight: 300;
  letter-spacing: 2px;
  text-transform: uppercase;
  line-height: 1.8;
  color: rgba(255, 255, 255, 0.85);
}

.boxPhotos {
  position: absolute;
  left: 13%;
  right: 13%;
  top: 10%;
  bottom: 10%;
}

.boxPhoto {
  position: absolute;
  inset: 0;
  margin: 0;
  border-radius: 9px;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.35);
  box-shadow: 0 18px 34px -14px rgba(0, 0, 0, 0.75);
  background: #1a1526;
  transform-origin: 50% 85%;
  transition: transform 0.7s var(--turn-ease);
}

.boxPhoto img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.boxPhoto figcaption {
  position: absolute;
  top: 10px;
  right: 10px;
  writing-mode: vertical-rl;
  font-family: "Baskerville", "Georgia", serif;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.7);
}

.boxPhoto--back {
  z-index: 1;
}

.boxPhoto--middle {
  z-index: 2;
}

.boxPhoto--front {
  z-index: 3;
}

/* The glass folder cover, hinged on its left edge. Its colour comes from
   two layers of its own -- the project's gradient at half strength
   (::before) under a white glaze (::after) -- rather than from
   backdrop-filter alone: inside a 3D rendering context browsers can't
   reliably sample what's behind a rotated element, which left the open
   cover clear while the unselected boxes' covers read as tinted glass.
   The blur stays as an extra where it does work. */
.boxCover {
  position: absolute;
  inset: 0;
  z-index: 4;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 24px 20px;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.45);
  background: rgba(255, 255, 255, 0.66);
  -webkit-backdrop-filter: blur(22px) saturate(1.4);
  backdrop-filter: blur(22px) saturate(1.4);
  /* Inset highlight only. A drop shadow here extends the layer's bounds
     past the hinge into the space behind the case, and Chrome's 3D depth
     sort then paints the case over the ~27px of cover nearest the hinge.
     Verified: the shadow alone causes it; radius, border and backface
     don't. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4);
  text-align: center;
  transform-origin: left center;
  backface-visibility: hidden;
  transition: transform 0.8s var(--turn-ease), border-radius var(--shelf-dur) var(--turn-ease);
}

.boxCover::before,
.boxCover::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
}

.boxCover::before {
  z-index: -2;
  background-image: var(--base-grad);
  background-size: 400% 400%;
  animation: Gradient 15s ease infinite;
  opacity: 0.95;
}

.boxCover::after {
  z-index: -1;
  background: linear-gradient(
    155deg,
    rgba(255, 255, 255, 0.36),
    rgba(255, 255, 255, 0.1) 45%,
    rgba(255, 255, 255, 0.24)
  );
}

.boxCase.istan ~ .boxCover::before {
  background-image: var(--istanbul-grad);
}

.boxCase.edin ~ .boxCover::before {
  background-image: var(--edinburgh-grad);
  background-size: 600% 600%;
}

.boxCase.soon ~ .boxCover::before {
  filter: saturate(0.55) brightness(0.9);
}

.coverTitle {
  font-family: "Baskerville", "Georgia", serif;
  font-style: italic;
  font-weight: 300;
  font-size: clamp(24px, 2.2vw, 30px);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: #fff;
}

.coverDate {
  font-size: 15px;
  font-weight: 300;
  letter-spacing: 3px;
  color: rgba(255, 255, 255, 0.9);
}

.coverHint {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 20px;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.8);
  opacity: 0;
  transition: opacity 0.4s ease;
}

/* Open state, once the box faces you: the cover swings 40deg on its left
   hinge -- rotation only, no translate, so its left edge stays on the
   case's left edge like a real cover -- and the photos come out to the
   right. These are the positions touch devices (no hover) always show. */
.shelfItem.is-active .boxCover {
  transform: rotateY(-40deg);
}

.shelfItem.is-active .coverHint {
  opacity: 1;
}

.shelfItem.is-active .boxPhoto--front {
  transform: translate(21px, 0) rotate(8deg);
}

.shelfItem.is-active .boxPhoto--middle {
  transform: translate(47px, -12px) rotate(14deg);
}

.shelfItem.is-active .boxPhoto--back {
  transform: translate(73px, -26px) rotate(20deg);
}

/* The selection sequence -- turn, then cover, then photos one by one --
   is just these delays. They apply only while .is-opening, which the
   script removes ~1.5s after selection, so hovering in and out afterwards
   responds immediately instead of waiting on the stagger. */
.shelfItem.is-opening .boxCover {
  transition-delay: 0.5s;
}

.shelfItem.is-opening .coverHint {
  transition-delay: 0.9s;
}

.shelfItem.is-opening .boxPhoto--front {
  transition-delay: 0.6s;
}

.shelfItem.is-opening .boxPhoto--middle {
  transition-delay: 0.68s;
}

.shelfItem.is-opening .boxPhoto--back {
  transition-delay: 0.76s;
}

/* With a pointer that can hover, the open box rests with its photos
   mostly tucked inside the case (the middle one flush with its inset,
   the back one peeking past the edge), and the hover pushes them 47px
   further out to the positions above while the cover swings wider on the
   same hinge -- the old cards' fan-out, kept. */
@media (hover: hover) {
  .shelfItem.is-active .boxPhoto--front {
    transform: translate(-26px, 0) rotate(8deg);
  }

  .shelfItem.is-active .boxPhoto--middle {
    transform: translate(0, -12px) rotate(14deg);
  }

  .shelfItem.is-active .boxPhoto--back {
    transform: translate(26px, -26px) rotate(20deg);
  }

  .shelfItem.is-active .shelfBox:hover .boxCover {
    transform: rotateY(-60deg);
    transition-delay: 0s;
  }

  .shelfItem.is-active .shelfBox:hover .boxPhoto--front {
    transform: translate(21px, 0) rotate(8deg);
    transition-delay: 0s;
  }

  .shelfItem.is-active .shelfBox:hover .boxPhoto--middle {
    transform: translate(47px, -12px) rotate(14deg);
    transition-delay: 0.06s;
  }

  .shelfItem.is-active .shelfBox:hover .boxPhoto--back {
    transform: translate(73px, -26px) rotate(20deg);
    transition-delay: 0.12s;
  }
}

/* Boxes to the right of the open one show their side in perspective: the
   front face seen edge-on, meeting the spine at the fold. Two rounded
   faces meeting at a fold leave a notch at the top and bottom, so just
   the corners on the fold are squared -- the box's outer corners stay
   round -- and they round again as the box turns to face you. */
.shelfItem.is-active ~ .shelfItem .boxSpine {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

.shelfItem.is-active ~ .shelfItem .boxCase,
.shelfItem.is-active ~ .shelfItem .boxCover {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

/* Caption, arrows, counter */

.shelfInfo {
  display: grid;
  grid-template-columns: 52px 1fr 52px;
  align-items: center;
  gap: 16px;
  max-width: 820px;
  margin: 8px auto 0;
  text-align: center;
}

.shelfCaption {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.shelfCaption.is-swapping {
  opacity: 0;
  transform: translateY(6px);
}

.shelfName {
  font-size: clamp(26px, 3vw, 40px);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
}

.shelfMeta {
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hero-fg-3);
}

.shelfSummary {
  max-width: 54ch;
  font-family: "Baskerville", "Georgia", serif;
  font-style: italic;
  font-size: clamp(15px, 1.2vw, 18px);
  line-height: 1.6;
  color: var(--hero-fg-2);
  text-wrap: pretty;
}

.shelfCta {
  margin-top: 10px;
}

/* Tablet: the summary gets ~22% more measure than on phones. */
@media (min-width: 641px) {
  .shelfSummary {
    max-width: 66ch;
  }
}

/* Desktop: the caption row spans nearly the full page so the arrows sit
   out by the edges (like the reference) and the summary gets room to run
   two lines instead of three. */
@media (min-width: 1024px) {
  .shelfInfo {
    max-width: 1180px;
    gap: 40px;
  }

  .shelfSummary {
    max-width: 85ch;
  }
}

.shelfFoot {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  max-width: 1100px;
  margin: clamp(28px, 4vh, 48px) auto 0;
  padding-top: 16px;
  border-top: 1px solid var(--hero-line);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hero-fg-3);
}

.shelfCount {
  font-variant-numeric: tabular-nums;
}

@media (max-width: 640px) {
  /* Row of three = 44 + 12 + (16 + 184 + 56) + 12 + 44 = 368px, so it fits
     a 390px phone with the open box centred; the photos eject a shorter
     way to stay off the right-hand spine. */
  .shelfSection {
    --spine-w: 44px;
    --front-w: 184px;
    --box-h: 270px;
    --shelf-gap: 12px;
    --slot-pad-l: 16px;
    --slot-pad-r: 56px;
  }

  /* Keep all four chips on one row. */
  .shelfChip {
    padding: 8px 11px;
    font-size: 11px;
    letter-spacing: 0.08em;
  }

  .shelfInfo {
    grid-template-columns: 42px 1fr 42px;
    gap: 8px;
  }

  .shelfInfo .arcArrow {
    width: 42px;
    height: 42px;
  }

  .shelfItem.is-active .boxPhoto--front {
    transform: translate(10px, 0) rotate(6deg);
  }

  .shelfItem.is-active .boxPhoto--middle {
    transform: translate(26px, -10px) rotate(11deg);
  }

  .shelfItem.is-active .boxPhoto--back {
    transform: translate(42px, -20px) rotate(16deg);
  }
}

/* Narrow window with a mouse: same tucked-in rest / spilled hover pair,
   scaled to the smaller box (a 33px push instead of 47px). */
@media (hover: hover) and (max-width: 640px) {
  .shelfItem.is-active .boxPhoto--front {
    transform: translate(-23px, 0) rotate(6deg);
  }

  .shelfItem.is-active .boxPhoto--middle {
    transform: translate(-7px, -10px) rotate(11deg);
  }

  .shelfItem.is-active .boxPhoto--back {
    transform: translate(9px, -20px) rotate(16deg);
  }

  .shelfItem.is-active .shelfBox:hover .boxPhoto--front {
    transform: translate(10px, 0) rotate(6deg);
  }

  .shelfItem.is-active .shelfBox:hover .boxPhoto--middle {
    transform: translate(26px, -10px) rotate(11deg);
  }

  .shelfItem.is-active .shelfBox:hover .boxPhoto--back {
    transform: translate(42px, -20px) rotate(16deg);
  }
}

/* ---------- Verse ---------- */

.verseText {
  max-width: 900px;
  margin: clamp(56px, 8vh, 96px) auto 0;
  text-align: center;
  font-size: clamp(16px, 2vw, 24px);
  font-style: italic;
  font-weight: 500;
  line-height: 1.5;
  color: var(--hero-fg);
}

.verseText h1,
.verse h1 {
  font-size: inherit;
  font-weight: inherit;
}

/* Same device as before -- the surrounding words outlined, "seen" and
   "unseen" solid -- inverted for the dark ground. */
.verseSpan {
  -webkit-text-stroke: 1px rgba(246, 243, 251, 0.55);
  color: transparent;
}

.verse {
  margin-top: 14px;
  text-align: center;
  font-size: clamp(13px, 1.4vw, 18px);
  font-style: italic;
  color: var(--hero-fg-2);
}

/* ---------- Footer ---------- */

.footer {
  padding: clamp(28px, 4vh, 48px) clamp(16px, 4vw, 48px);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

.footerText {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px 32px;
  text-align: center;
}

.footerText h3 {
  font-size: clamp(15px, 1.4vw, 20px);
  font-weight: 500;
}

.footerText h3 a {
  color: var(--hero-fg);
  text-decoration: none;
  border-bottom: 1px solid var(--hero-line);
  transition: border-color 0.25s ease;
}

.footerText h3 a:hover,
.footerText h3 a:focus-visible {
  border-color: var(--hero-fg);
}

.footerText h1 {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--hero-fg-3);
}

/* ---------- Reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  .arcHero {
    --turn-dur: 0.01s;
  }

  .shelfSection {
    --shelf-dur: 0.01s;
  }

  .arcWheel,
  .arcTile,
  .arcGlow,
  .arcScrollCueLine,
  .arcLightbox {
    animation: none;
  }

  .lbFrame::before {
    animation: Gradient 15s ease infinite;
  }

  .boxCover,
  .boxPhoto,
  .coverHint,
  .shelfCaption {
    transition-duration: 0.01s !important;
    transition-delay: 0s !important;
  }

  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}
