/* CAROUSEL — one-item-at-a-time stage, progressively enhanced.
   The markup ships with every item present and visible (the default stack).
   JavaScript adds [data-carousel-active] to the .arc section element itself,
   which activates the stage layout here. All selectors use
   .arc[data-carousel-active] (attribute ON the .arc element) not
   [data-carousel-active] .arc (attribute on a PARENT of .arc — wrong).
   Without JS the reader gets the existing scroll sequence intact.

   SIZING RATIONALE (1512×900 design target)
   ------------------------------------------
   Viewport height 900px. Sitenav measured 6.35rem (≈101px). Arc rail ~3.6rem (58px).
   Arc head (overline + h2 + lede): measured ~10rem (160px) at ≥75rem viewport width,
   ~14rem (223px) at narrower widths where the lede wraps more.
   Controls bar + caption below image: ~8.75rem (140px).

   Formula: height = 100vh − sitenav − head − rail − ctrl+cap
   At 1512×900: 900 − 101 − 160 − 58 − 140 = 441px  ← floor is fine
   At 901×901 (17rem head): 901 − 101 − 272 − 58 − 140 = 330px  ← compact but legible

   The image gets object-fit:cover so it crops rather than letterboxes.

   RAIL CLEARANCE (JOB 1 — 2026-08-21)
   -------------------------------------
   MEASURED at 1512×900: .rail occupies x 24–202. The bleed plate was x 0–1512.
   The rail sat on top of roughly 12% of every photograph.

   Fix: at ≥601px (37.5625rem), add padding-left: max(var(--gutter), 13rem) to
   .arc__carousel-stage. 13rem ≈ 208px clears the rail right edge (x 202) with 6px
   to spare. The bleed escape (width:100vw + negative margin) is removed in carousel
   mode at desktop so the image fills the remaining width instead of the full viewport.

   Below 601px the rail is display:none so NO inset is applied — the phone keeps its
   full-bleed plate unchanged.

   Controls and caption already use the same max(var(--gutter), 13rem) offset, so
   all three elements (image, caption, controls) align to the same left edge.

   The same 13rem constant is already used by .arc__controls and figcaption padding-left,
   so there is ONE number in the codebase, not two that can drift. */

/* ─────────────────────────────────────────────
   CSS custom property: head-height allowance.
   Set on .arc itself so it inherits to all
   child elements in the formula.
   ───────────────────────────────────────────── */
.arc[data-carousel-active] {
  --arc-head-allowance: 10rem;  /* ≥75rem viewport: head ~160px */
}

@media (max-width: 75rem) {
  /* Narrower: head wraps more, measured ~14rem at 901px wide. */
  .arc[data-carousel-active] {
    --arc-head-allowance: 14rem;
  }
}

/* ─────────────────────────────────────────────
   Stage outer
   ───────────────────────────────────────────── */

/* In carousel mode, collapse the section's top padding to a fixed 1rem and
   tighten the head's bottom margin. This makes the geometry predictable:
   the only variable above the image is the arc head height, which is
   accounted for by --arc-head-allowance. Without this, --sp-xl (a clamp
   that reaches 94px at 901px wide) is invisible to the img height formula
   and the image overflows the viewport. */
.arc[data-carousel-active] {
  padding-top: 1rem;
}
.arc[data-carousel-active] .arc__head {
  margin-bottom: var(--sp-sm);
}

.arc[data-carousel-active] .arc__seq {
  display: block;
  margin-top: 0;
}

.arc[data-carousel-active] .arc__carousel-stage {
  position: relative;
  /* Drag: the stage translates horizontally during a pointer drag.
     CSS transition here lets it snap back smoothly on release.
     Under prefers-reduced-motion the global brake collapses this to 0.001ms. */
  transition: transform 220ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* ─────────────────────────────────────────────
   RAIL CLEARANCE — desktop only (≥ 601px)
   Inset the stage so the plate starts clear of
   the fixed .rail chapter list (right edge x≈202
   measured at 1512×900). 13rem ≈ 208px.
   ───────────────────────────────────────────── */
@media (min-width: 37.5625rem) {
  .arc[data-carousel-active] .arc__carousel-stage {
    padding-left: max(var(--gutter), 13rem);
  }
}

/* ─────────────────────────────────────────────
   Individual frames
   ───────────────────────────────────────────── */

/* All frames hidden by default in carousel mode — collapsed, NOT removed.
   The CSS rule display:none applies only when the JS attribute is present.
   Without JS: this rule never fires; every frame is visible in the stack. */
.arc[data-carousel-active] .arc__frame {
  display: none;
  margin: 0;
}

.arc[data-carousel-active] .arc__frame.arc__frame--current {
  display: block;
}

/* ─────────────────────────────────────────────
   Image sizing
   ───────────────────────────────────────────── */
.arc[data-carousel-active] .arc__frame img {
  width: 100%;
  /* Explicit height + object-fit:cover — needed because height:auto on a
     16:9 image ignores max-height when width:100vw sets a wide natural size.

     Controls are placed ABOVE the image (between rail and stage), so the
     formula only needs to subtract above-image elements:
       - sitenav (--sitenav-h, measured 6.35rem)
       - arc head (overline + h2 + lede, --arc-head-allowance)
       - arc rail (sticky stamp, measured 3.6rem)
       - controls bar (measured ~4.5rem including progress bar)
     Caption lives below the image and may need a short scroll on compact
     viewports; that is acceptable — the controls are always in view.

     At 1512×900 (10rem head): 900 − 101 − 160 − 58 − 72 = 509px. Good.
     At 901×901 (14rem head): 901 − 101 − 224 − 58 − 72 = 446px. Good.
     At 1024×768 (14rem head): 768 − 101 − 224 − 58 − 72 = 313px. Compact OK. */
  height: calc(100vh - var(--sitenav-h, 3rem) - 1rem - var(--arc-head-allowance) - 3.6rem - 4.5rem);
  min-height: clamp(180px, 28vw, 380px);
  object-fit: cover;
  background: var(--paper-3);
}

/* BLEED images at desktop (≥ 601px): fill the stage from rail-clear edge to
   viewport right. The stage already has padding-left: 13rem at desktop, so
   width:100% fills from x≈208 to x=1512 — the plate IS large (1304px) and
   no longer sits under the rail.

   At phone (< 601px): no stage padding, so width:100vw + negative margin
   still gives a true full-bleed plate unchanged from the stack. */
@media (min-width: 37.5625rem) {
  /* In carousel mode at desktop: remove the bleed escape so the image
     fills only the inset stage (rail-clear to viewport right). */
  .arc[data-carousel-active] .arc__frame--bleed img {
    width: 100%;
    margin-left: 0;
  }
}

@media (max-width: 37.49rem) {
  /* Phone: keep full-bleed escape exactly as in stack mode. */
  .arc[data-carousel-active] .arc__frame--bleed img {
    width: 100vw;
    margin-left: calc(50% - 50vw);
  }
}

/* Held frame: treat as bleed in carousel mode (inset looks odd on stage). */
.arc[data-carousel-active] .arc__frame--held {
  max-width: none;
  margin-inline: 0;
  padding-inline: 0;
}
.arc[data-carousel-active] .arc__frame--held img {
  width: 100%;
}
@media (max-width: 37.49rem) {
  .arc[data-carousel-active] .arc__frame--held img {
    width: 100vw;
    margin-left: calc(50% - 50vw);
  }
}
.arc[data-carousel-active] .arc__frame--held figcaption {
  padding-left: var(--gutter);
  max-width: 58ch;
  text-align: left;
  margin-left: 0;
}

/* Caption typography unchanged from stack; just tighten top padding.
   At ≥601px: push left edge past the fixed .rail, same offset as controls.
   This aligns caption left edge with the plate left edge (both at 13rem). */
.arc[data-carousel-active] .arc__frame figcaption {
  padding: var(--sp-sm) var(--gutter) 0;
  max-width: 58ch;
}
@media (min-width: 37.5625rem) {
  .arc[data-carousel-active] .arc__frame figcaption {
    padding-left: max(var(--gutter), 13rem);
  }
}

/* ─────────────────────────────────────────────
   Controls bar: prev · counter · progress · next
   Voice: DM Mono chrome, copper ink, paper
   palette — monograph register, not an app.
   ───────────────────────────────────────────── */

/* Hidden without JS (data-carousel-active absent = .arc__controls unstyled
   but also no JS rendered its contents, so it stays an empty invisible div). */
.arc__controls {
  display: none;
}

.arc[data-carousel-active] .arc__controls,
.descent[data-carousel-active] .arc__controls,
.place[data-carousel-active] .arc__controls,
.explains[data-carousel-active] .arc__controls,
.eng[data-carousel-active] .arc__controls {
  display: flex;
  align-items: center;
  gap: var(--sp-sm);
  padding: var(--sp-sm) var(--gutter);
  max-width: 58ch;
}

/* At ≥601px the fixed .rail chapter list occupies the left ~182–210px.
   Push the controls bar past it so buttons and counter are never hidden behind it.
   13rem ≈ 208px clears the rail at all desktop widths without hard-coding pixels.
   Below 601px the rail is display:none so no offset is needed. */
@media (min-width: 37.5625rem) {
  .arc[data-carousel-active] .arc__controls,
  .descent[data-carousel-active] .arc__controls,
  .place[data-carousel-active] .arc__controls,
  .explains[data-carousel-active] .arc__controls,
  .eng[data-carousel-active] .arc__controls {
    padding-left: max(var(--gutter), 13rem);
  }
}

.arc__btn {
  /* Real <button>, always visible at rest — never hover-revealed. ≥44×44px. */
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--rule);
  /* Vertical padding: (44px − 2px border − ~18px text line) / 2 ≈ 12px */
  padding: .65rem 1.1rem;
  min-width: 44px;
  min-height: 44px;
  line-height: 1;
  cursor: pointer;
  border-radius: 0;  /* monograph, not a rounded widget */
  transition: color var(--dur-fast), border-color var(--dur-fast), background-color var(--dur-fast);
}

.arc__btn:hover:not([disabled]) {
  color: var(--copper-ink);
  border-color: var(--copper-ink);
  /* Subtle warm paper tint on hover — monograph register, not a UI widget highlight */
  background-color: color-mix(in oklch, var(--copper-ink) 6%, transparent);
}

.arc__btn:focus-visible {
  outline: 2px solid var(--copper-ink);
  outline-offset: 3px;
  /* Match hover color on focus so keyboard + pointer are the same register */
  color: var(--copper-ink);
  border-color: var(--copper-ink);
}

.arc__btn[disabled] {
  opacity: .32;
  cursor: default;
}

/* Counter: "3 / 7" — DM Mono, tabular nums so width is stable. */
.arc__counter {
  font-family: var(--font-mono);
  font-size: var(--text-label);
  font-weight: 500;
  letter-spacing: .12em;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  user-select: none;
  white-space: nowrap;
}

/* ─────────────────────────────────────────────
   Progress bar (JOB 2, priority 4)
   A slim track between counter and Next button.
   Fills left-to-right as the reader pages.
   Copper-ink fill on paper-3 track — monograph.
   ───────────────────────────────────────────── */
.arc__progress-track {
  flex: 1;              /* takes the space between counter and Next button */
  height: 2px;
  background: var(--paper-3, #e8e5df);
  position: relative;
  overflow: hidden;
  border-radius: 1px;
  /* Minimum visible width even on narrow viewports */
  min-width: 2rem;
  max-width: 12rem;   /* cap so it does not become a landscape runway */
}

.arc__progress-fill {
  position: absolute;
  inset-block: 0;
  left: 0;
  width: 0%;           /* updated by JS each slide change */
  background: var(--copper-ink);
  border-radius: 1px;
  transition: width 220ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* ─────────────────────────────────────────────
   Screen-reader live region (visually hidden,
   SR-announced on each frame change).
   ───────────────────────────────────────────── */
.arc__caption-live {
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
}

/* ─────────────────────────────────────────────
   Keyboard focus on the stage container.
   ───────────────────────────────────────────── */
.arc__carousel-stage:focus-visible {
  outline: 2px solid var(--copper-ink);
  outline-offset: 4px;
}

/* ─────────────────────────────────────────────
   Phone — no regression
   ───────────────────────────────────────────── */
@media (max-width: 37.49rem) {
  .arc[data-carousel-active] .arc__controls {
    max-width: 100%;
  }
  .arc[data-carousel-active] .arc__frame img {
    min-height: 200px;
  }
  /* On phone, progress track may be very narrow — allow it to shrink. */
  .arc__progress-track {
    min-width: 1rem;
    max-width: none;
  }
}

/* ─────────────────────────────────────────────
   Reduced motion: the global brake in motion.css
   already sets transition-duration: 0.001ms.
   No JS branch needed; transitions finish instantly.
   Stage translate snap and progress fill also
   collapse to instant under this brake.
   ───────────────────────────────────────────── */


/* ═══════════════════════════════════════════════════════════
   #FINISHED — "Finished" descent photographs
   Same structure as The Arc: 4 full-width photographs,
   no date/phase rail (descent has no .arc__rail).
   Controls element (.descent__controls) is a sibling of
   .descent__seq in the HTML so it sits between the head and
   the image sequence at natural flow order.

   SIZING RATIONALE (1512×900 design target)
   ------------------------------------------
   Descent head (overline + h2 + lede + margin-bottom):
     measured ~11rem (176px) at ≥75rem viewport.
   At narrower widths (≤75rem) head wraps: ~14rem (224px).
   No sticky rail in this section (+0px vs The Arc).
   Controls bar: same 4.5rem as The Arc.

   Formula: height = 100vh − sitenav − head − ctrl
   At 1512×900: 900 − 101 − 176 − 72 = 551px. Good.
   ═══════════════════════════════════════════════════════════ */

.descent[data-carousel-active] {
  --descent-head-allowance: 11rem;
  padding-top: 1rem;
}
@media (max-width: 75rem) {
  .descent[data-carousel-active] {
    --descent-head-allowance: 14rem;
  }
}
.descent[data-carousel-active] .descent__head {
  margin-bottom: var(--sp-sm);
}
.descent[data-carousel-active] .descent__seq {
  display: block;
  margin-top: 0;
}
.descent[data-carousel-active] .arc__carousel-stage {
  position: relative;
  transition: transform 220ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* Rail clearance — same 13rem offset as The Arc at desktop.
   Below 601px no rail is shown so full-bleed is kept. */
@media (min-width: 37.5625rem) {
  .descent[data-carousel-active] .arc__carousel-stage {
    padding-left: max(var(--gutter), 13rem);
  }
}

/* Hide all frames in carousel mode; show only the current one. */
.descent[data-carousel-active] .descent__frame {
  display: none;
  margin: 0;
}
.descent[data-carousel-active] .descent__frame.descent__frame--current {
  display: block;
}

/* Image sizing — same formula as The Arc minus the arc-rail term (3.6rem). */
.descent[data-carousel-active] .descent__frame img {
  width: 100%;
  height: calc(100vh - var(--sitenav-h, 3rem) - 1rem
               - var(--descent-head-allowance) - 4.5rem);
  min-height: clamp(180px, 28vw, 380px);
  object-fit: cover;
  background: var(--paper-3);
}

/* Desktop: remove bleed escape so image fills only the inset stage. */
@media (min-width: 37.5625rem) {
  .descent[data-carousel-active] .descent__frame img {
    width: 100%;
    margin-left: 0;
  }
}

/* Phone: keep full-bleed escape. */
@media (max-width: 37.49rem) {
  .descent[data-carousel-active] .descent__frame img {
    width: 100vw;
    margin-left: calc(50% - 50vw);
    min-height: 200px;
  }
}

/* Controls positioning: controls live as a sibling above .descent__seq.
   Padding-left at desktop is handled by the shared rule in the base controls block. */


/* ═══════════════════════════════════════════════════════════
   #EXPLAINS and #PLACE — text card carousels
   These sections use .place__grid--carousel as the seq and
   .place__item as the frame. carousel.js wraps items in an
   .arc__carousel-stage and inserts controls before it inside
   the grid container.

   STRUCTURAL CHANGE: in carousel mode the grid switches from
   a horizontal scroll strip / responsive grid to a single-item
   stage. The existing .place__grid--carousel rules (sections.css)
   define the JS-off scroll strip. Here we OVERRIDE them when
   [data-carousel-active] is on the section root.

   JS-OFF CONTRACT: without [data-carousel-active], all cards
   are visible as the existing scroll strip on mobile and the
   responsive grid on desktop — unchanged from before.

   SIZING: text cards do not need a viewport-height image formula.
   They flow at natural text height. The stage collapses the flex
   strip to show one item at a time; each item fills the available
   column width and grows to its text height.

   NO rail clearance needed: the text column already starts at
   var(--gutter) which the section padding provides; on desktop
   the section padding-left is already inside the rail zone.
   Check: .place sections have padding:var(--sp-xl) var(--gutter).
   At 1512px: --gutter ≈ 2rem = 32px. Rail right edge ≈ 202px.
   The section heading already clears the rail at desktop; the
   grid is max-width:78rem centred, which at 1512px viewport
   leaves (1512-1248)/2 = 132px left — INSIDE the rail (202px).
   FIX: at ≥37.5625rem, add the same 13rem left offset to the
   stage so cards start clear of the rail, matching the head.
   ═══════════════════════════════════════════════════════════ */

/* Override the scroll-strip layout when JS activates carousel mode. */
.place[data-carousel-active] .place__grid--carousel,
.explains[data-carousel-active] .place__grid--carousel {
  /* Cancel scroll-strip: become a block container for the stage */
  display: block;
  overflow: visible;
  overscroll-behavior-x: auto;
  scroll-snap-type: none;
  margin-inline: 0;
  padding-inline: 0;
  padding-block: 0;
  border-top: 1px solid var(--ink);
  gap: 0;
  /* Cancel the desktop grid override in sections.css */
  grid-template-columns: none;
}

/* The stage wraps all items; only the current one is shown. */
.place[data-carousel-active] .arc__carousel-stage,
.explains[data-carousel-active] .arc__carousel-stage {
  position: relative;
  transition: transform 220ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* Rail clearance at desktop: offset stage left edge past the fixed .rail.
   The section body already starts inside the max-width container, but
   at some viewport widths the left edge falls behind x=202. Applying
   the same 13rem offset as photo carousels is consistent and safe. */
@media (min-width: 37.5625rem) {
  .place[data-carousel-active] .arc__carousel-stage,
  .explains[data-carousel-active] .arc__carousel-stage {
    padding-left: max(var(--gutter), 13rem);
  }
}

/* Hide all items in carousel mode; show only the current one. */
.place[data-carousel-active] .place__item,
.explains[data-carousel-active] .place__item {
  display: none;
  /* Cancel scroll-strip item sizing */
  flex: none;
  scroll-snap-align: none;
  border-left: none;
  border-bottom: 1px solid var(--paper-3);
  padding: var(--sp-lg) 0;
}

.place[data-carousel-active] .place__item.place__item--current,
.explains[data-carousel-active] .place__item.place__item--current {
  display: block;
}

/* Text sizing: each card now owns a full stage.
   Title link: Fraunces, larger than the grid cell allowed.
   Body: DM Mono for technical items, --ink-2 for supporting text. */
.place[data-carousel-active] .place__k,
.explains[data-carousel-active] .place__k {
  margin-bottom: var(--sp-sm);
}

.place[data-carousel-active] .place__k a,
.explains[data-carousel-active] .place__k a {
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 2.5vw, 2.2rem);
  line-height: 1.15;
  font-weight: 400;
  letter-spacing: -.01em;
  color: var(--ink);
  text-decoration-line: underline;
  text-decoration-color: var(--copper-ink);
  text-underline-offset: .2em;
}

.place[data-carousel-active] .place__k a:hover,
.explains[data-carousel-active] .place__k a:hover {
  color: var(--copper-ink);
}

.place[data-carousel-active] .place__v,
.explains[data-carousel-active] .place__v {
  max-width: 48ch;
  font-size: var(--text-body);
  color: var(--ink-2);
  margin-top: var(--sp-xs, .35rem);
}

/* Controls positioning: controls live as a sibling BEFORE the grid in the HTML.
   Padding-left at desktop is handled by the shared rule in the base controls block. */

/* Phone: text cards full width, natural height. */
@media (max-width: 37.49rem) {
  .place[data-carousel-active] .arc__carousel-stage,
  .explains[data-carousel-active] .arc__carousel-stage {
    padding-left: 0;
  }
  .place[data-carousel-active] .place__item,
  .explains[data-carousel-active] .place__item {
    padding: var(--sp-md) 0;
  }
}


/* ═══════════════════════════════════════════════════════════
   #ENGINEERING — 4 spec blocks (text-only, no images).
   Same progressive-enhancement contract as #explains/#place:
   without JS the four .eng__block articles are visible in
   their natural stacked layout (the existing sections.css
   grid rules apply). With JS, [data-carousel-active] is set
   on the section root and one block is shown at a time.

   .eng__seq wraps the four articles; the controls element
   (.eng__controls) is a sibling before it.

   JS-OFF CONTRACT: without [data-carousel-active], all four
   blocks are visible as the existing stacked grid — unchanged.

   SIZING: text blocks flow at natural height (no viewport-
   height image formula needed). The .eng__seq becomes a block
   container for the stage; each block owns the full width.

   RAIL CLEARANCE: same 13rem left offset at ≥601px, matching
   the section head (which uses max-width centering that already
   clears the rail via the same padding-left approach used for
   .place and .explains).

   NUMERAL vs COUNTER: the .eng__num (01–04) is a decorative
   watermark inside each card — aria-hidden, opacity .42,
   display-scale type. The arc__counter ("1 / 4") lives in the
   controls bar above the card at label scale. They occupy
   different vertical positions and scales; no collision.
   ═══════════════════════════════════════════════════════════ */

/* The seq is a plain block container; no scroll-strip to cancel.
   sections.css already styles .eng__block as a grid row — in
   carousel mode we just wrap them and show one at a time. */
.eng[data-carousel-active] .eng__seq {
  display: block;
  border-top: 1px solid var(--ink);
}

/* Stage wrapper (injected by carousel.js). */
.eng[data-carousel-active] .arc__carousel-stage {
  position: relative;
  transition: transform 220ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
}

/* Rail clearance at desktop. */
@media (min-width: 37.5625rem) {
  .eng[data-carousel-active] .arc__carousel-stage {
    padding-left: max(var(--gutter), 13rem);
  }
}

/* Hide all blocks in carousel mode; show only the current one.
   CSS rule gated on [data-carousel-active] — never fires without JS. */
.eng[data-carousel-active] .eng__block {
  display: none;
  margin: 0;
}

.eng[data-carousel-active] .eng__block.eng__block--current {
  display: grid;   /* restore the grid layout from sections.css */
}

/* Controls: handled by the shared selector list above. */

/* Phone: full width, natural padding. */
@media (max-width: 37.49rem) {
  .eng[data-carousel-active] .arc__carousel-stage {
    padding-left: 0;
  }
  .eng[data-carousel-active] .eng__block {
    padding: var(--sp-md) 0;
  }
  .eng[data-carousel-active] .arc__controls {
    max-width: 100%;
  }
}
