/* layout.css: the 2D grid. Vertical scroll snap between panels, horizontal scroll snap
   within a panel between its left pane, spine pane, and right pane. Two rails of dots. */

html, body { height: 100%; }
body { overflow: hidden; }

/* ---- spine: vertical scroll snap, one row per panel ---- */

#spine {
  position: relative;
  height: 100dvh;
  overflow-y: auto;
  overflow-x: hidden;
  scroll-snap-type: y mandatory;
  overscroll-behavior: contain;
  scroll-behavior: smooth;
  scrollbar-width: none;
}
#spine::-webkit-scrollbar { display: none; }
/* a side pane is open: the spine does not scroll until you come back to the center */
#spine.side-open { overflow-y: hidden; }

/* ---- row: horizontal scroll snap between panes ---- */

.panel {
  position: relative;
  height: 100dvh;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;   /* no browser back/forward swipe */
  scroll-behavior: smooth;
  scrollbar-width: none;
}
.panel::-webkit-scrollbar { display: none; }

/* ---- pane: one viewport-sized cell ---- */

.pane {
  flex: 0 0 100%;
  width: 100%;
  height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr);
  gap: 1rem;
  padding: 2rem 4.5rem 3rem 4rem;
}

.pane-meta {
  display: flex;
  gap: 1rem;
  font-size: 0.8rem;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.pane-meta .kind { color: var(--accent); }

/* the title row's one cell (#/0, mounted by main.js from title/): full bleed, no meta */
.pane-title { display: block; padding: 0; }
.pane-title > .pane-body { height: 100%; }

/* ---- pane body: prose column + interactive column ---- */

.pane-body { min-height: 0; overflow: hidden; }

.pane-body > article {
  display: grid;
  grid-template-columns: minmax(18rem, var(--measure)) minmax(0, 1fr);
  /* the heading row is its own height; the content row takes the rest. Two auto rows
     would split the leftover height between them and open a gap under a short pane's h2. */
  grid-template-rows: auto minmax(0, 1fr);
  gap: 1rem 3rem;   /* the row gap sits under the h2, which has its own margin */
  height: 100%;
  align-items: start;
}
.pane-body > article > h2 { grid-column: 1 / -1; }
/* The interactive column (CLAUDE.md, "Pane layout standard"): it fills the content row, and
   its children stack top to bottom: stages, then one controls block, then live equations.
   Everything but a stage takes its natural height; the `fill` stages and square slots in
   controls.css flex into the height that is left. It is a size container, so a stage can
   read the column's width (`100cqw`, its cap) and height (`cqh`, which bounds its floor on a
   viewport wide for its height). Size containment is safe here: the column's height is the
   stretched grid row (`minmax(0, 1fr)` of a definite article), never its content. */
.pane-body .viz {
  align-self: stretch;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  container-type: size;
}
.pane-body .viz > * { flex: none; }

.pane-body.loading { color: var(--muted); }
.pane-missing {
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  padding: 1rem 1.25rem;
  color: var(--muted);
  max-width: var(--measure);
}
.pane-missing code { background: none; padding: 0; }

/* ---- rails: identical dots on the right edge (panels) and bottom edge (panes) ---- */

.rail {
  position: fixed;
  display: flex;
  gap: 0.55rem;
  z-index: 5;
}
#rail  { right: 1.1rem; top: 50%; translate: 0 -50%; flex-direction: column; }
/* raised above the plain 1.1rem the vertical rail uses, to leave room below the dots for the
   down-caret that marks which one drops to the next panel, without it getting clipped */
#hrail { bottom: 1.8rem; left: 50%; translate: -50% 0; flex-direction: row; }

.rail a {
  display: block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 50%;
  background: var(--border-strong);
  transition: background 0.15s, scale 0.15s;
}
.rail a:hover { background: var(--muted); }
.rail a[aria-current="true"] { background: var(--accent); scale: 1.35; }
.rail a[hidden] { display: block; visibility: hidden; }  /* keeps the center dot centered */

/* the hrail dot for the spine pane is the one you can scroll down from; a caret under it
   says so regardless of which pane is current */
#hrail a[data-pane="spine"] { position: relative; }
#hrail a[data-pane="spine"]::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  translate: -50% 0.3rem;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid var(--muted);
}

/* ---- depth vignette: the frame darkens the further right (deeper) the row is scrolled ---- */

/* Under the rails (z-index 5) so the dots stay crisp, over the panes, and never a hit
   target. The opacity is written per scroll frame by main.js, so there is no transition: the
   frame tracks the swipe itself. Weight sits in the last quarter of the radius, and
   `farthest-corner` puts the darkest stop at the four corners alone, so the mid-edges — where
   the prose column and the stage actually reach — stay well short of it. */
#vignette {
  position: fixed;
  inset: 0;
  z-index: 4;
  pointer-events: none;
  opacity: 0;
  will-change: opacity;
  background: radial-gradient(ellipse farthest-corner at 50% 50%,
    rgba(29, 29, 27, 0) 32%,
    rgba(29, 29, 27, 0.10) 58%,
    rgba(29, 29, 27, 0.30) 80%,
    rgba(29, 29, 27, 0.70) 100%);
}
