/*
 * The table of contents, and the rotating cylinder it becomes.
 *
 * Loaded only by the home page. Consumes the §2 semantic tokens from tokens.css like site.css does;
 * the handful of raw rgba() values here are white/black scrims and glows, which are properties of
 * light rather than of the palette and would not change under a re-theme.
 *
 * The file is in two halves, and the order matters:
 *
 *   1. The list as it ships -- a plain ordered list of every story. This is what a crawler sees,
 *      what a reader without JavaScript gets, and what is on screen before the script runs.
 *   2. Everything under .toc--enhanced -- the 3D cylinder. cylinder.js adds that class only once it
 *      is certain it can drive the thing, so the fallback is never a broken half-state.
 */

/* ================================================================
 * 1. The list as it ships
 * ================================================================ */

.toc__list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-inline-size: var(--measure-reading);
  margin-inline: auto;
  border-block-start: 1px solid var(--color-border-subtle);
}

.toc__item { border-block-end: 1px solid var(--color-border-subtle); }

.toc__link {
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-2);
  color: var(--color-text-secondary);
  text-decoration: none;
  line-height: var(--leading-tight);
}

.toc__link:hover { color: var(--color-text); background: var(--color-surface); }

.toc__index {
  flex: 0 0 auto;
  font-size: var(--text-xs);
  color: var(--color-text-faint);
  font-variant-numeric: tabular-nums;
}

.toc__footer-cta {
  text-align: center;
  color: var(--color-text-muted);
  font-size: var(--text-sm);
  margin-block-start: var(--space-6);
}

/* ================================================================
 * 2. The cylinder
 * ================================================================ */

/*
 * The book's titles wrapped around a horizontal axis running through the window they turn in. The
 * radial washes are the only place turquoise and green appear together as atmosphere rather than
 * as meaning -- they read as depth, and nothing in them is clickable or informative.
 *
 * Nothing here has a fixed size. The window is whatever the viewport leaves under the site header,
 * and cylinder.js measures it and sizes the wheel from it -- radius, the angle between titles, and
 * how many are on screen at once all follow from that one measurement. The two custom properties it
 * writes onto the stage, --toc-radius and --toc-perspective, are how the parts of the geometry that
 * belong in CSS stay in step with the parts that belong in JavaScript.
 */

/* ---------- the shell ----------
 *
 * The cylinder is the page, not a widget on it. There is nothing under it to scroll to, so the
 * document stops scrolling and the wheel takes the whole area below the header -- the same opt-in
 * layout mode the conversation and the search page make (`shell-app`, `shell-search` in site.css).
 *
 * Applied by cylinder.js rather than by the page model, because unlike those two it is only true
 * once the cylinder exists. Without JavaScript this page is a 314-item list, and a 314-item list
 * with the page scroll taken away from it is a table of contents you can read eight entries of.
 */
.shell-home {
  /* dvh, not vh: on mobile browsers vh is the height the viewport has with the address bar gone, so
     the bottom of the wheel would sit off-screen with no scroll left to reach it by. The vh line
     above it is the fallback for a browser that does not know the unit -- there the shell is a
     little too tall, which is survivable; a shell with no height at all is not.

     min-block-size, because body carries a 100vh floor from site.css for the benefit of short
     documents, and on a phone that floor is taller than 100dvh and would win. */
  block-size: 100vh;
  block-size: 100dvh;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.shell-home .site-main {
  flex: 1 1 auto;
  /* Without this a flex item refuses to shrink below its content, and the stage would push itself
     off the bottom of the viewport instead of filling it. */
  min-block-size: 0;
  display: flex;
  padding: 0;
  /* The measure and the gutters are what make .site-main a column of text. The stage is not one --
     it is the window onto the wheel, and it goes edge to edge. */
  max-inline-size: none;
  margin-inline: 0;
}

/* A document artefact, and there is no page scroll left to reach it by. */
.shell-home .site-footer { display: none; }

.shell-home .toc {
  position: relative;
  flex: 1 1 auto;
  min-block-size: 0;
  display: flex;
  flex-direction: column;
  inline-size: 100%;
}

/*
 * The one thing on the page that is not the wheel. It lies over the bottom of the stage, where the
 * titles have already dissolved into the fade, rather than taking a strip of height off a stage that
 * is meant to be the whole area -- and it takes the pointer only on the two links, so a drag started
 * anywhere across that line still turns the cylinder.
 */
.shell-home .toc__footer-cta {
  position: absolute;
  inset-block-end: var(--space-4);
  inset-inline: var(--space-5);
  z-index: 3;
  margin: 0;
  pointer-events: none;
}

.shell-home .toc__footer-cta a { pointer-events: auto; }

/* Two sizes the rest of the cylinder derives from. They are local rather than global tokens
   because nothing outside this component has a resting and a focused state to size. */
.toc--enhanced {
  --toc-title: 1.056rem;          /* 19px at the 18px root */
  --toc-title-focused: var(--text-lg);  /* 22px */
}

/* On a phone the row is only ~250px wide once the perspective is accounted for, and Persian story
   titles are long. Smaller type is the difference between reading most of a title and reading three
   words and an ellipsis. */
@media (max-width: 30rem) {
  .toc--enhanced {
    --toc-title: var(--text-base);
    --toc-title-focused: 1.056rem;
  }

  /* The pill's own padding was eating a quarter of the row. Narrowing it buys back about six
     characters of title, which at this width is the difference between two words and four. */
  .toc--enhanced .toc__link,
  .toc--enhanced .toc__item[data-focused] .toc__link {
    padding-inline: var(--space-4);
  }
}

/*
 * The window the wheel turns in. It has no height of its own: the shell hands it everything between
 * the header and the bottom of the viewport, and cylinder.js measures what that came to. There is no
 * fallback height here to go stale, because the two classes are added in the same breath -- there is
 * no state in which the stage is a cylinder but not the page.
 */
.toc--enhanced .toc__stage {
  position: relative;
  flex: 1 1 auto;
  min-block-size: 0;
  overflow: hidden;
  /* Written by cylinder.js as a fixed multiple of the radius, so however large the wheel is drawn
     the title at the axis is always projected the same 1.31x larger than it is laid out -- which is
     the number the row width below divides by. The fallback is what both were when both were
     constants. */
  perspective: var(--toc-perspective, 1100px);
  perspective-origin: 50% 50%;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
  /* The page has no scroll left to give, so a finger dragging up the stage has nowhere to send the
     gesture -- and pan-y would still hand it to the browser, which cancels the drag partway through
     and leaves the wheel stuck. `pinch-zoom` rather than `none`: the vertical drag is ours, but a
     reader who needs to magnify a title must still be able to. */
  touch-action: pinch-zoom;
  background:
    radial-gradient(1100px 620px at 30% -10%, color-mix(in oklab, var(--color-citation) 10%, transparent), transparent 60%),
    radial-gradient(900px 560px at 90% 110%, color-mix(in oklab, var(--color-accent) 14%, transparent), transparent 55%);
}

/* The stage is the single tab stop for the whole cylinder, so it is what the focus ring is drawn
   around -- and it is now exactly the viewport, whose edge the shell's `overflow: hidden` clips.
   Drawn inside, or a keyboard visitor turning the wheel has no ring at all. */
.toc--enhanced .toc__stage:focus-visible {
  outline-offset: calc(-1 * (var(--focus-ring-width) + var(--focus-ring-offset)));
}

.toc--enhanced .toc__stage[data-dragging] { cursor: grabbing; }

/*
 * Titles do not stop at the edge of the window, they dissolve into it. A hard clip would read as a
 * list that has been cut off; a fade reads as a surface continuing out of view, which is what a
 * cylinder actually does.
 */
.toc--enhanced .toc__stage::before,
.toc--enhanced .toc__stage::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  block-size: 30%;
  pointer-events: none;
  z-index: 2;
}

.toc--enhanced .toc__stage::before {
  inset-block-start: 0;
  background: linear-gradient(180deg, var(--color-canvas), transparent);
}

.toc--enhanced .toc__stage::after {
  inset-block-end: 0;
  background: linear-gradient(0deg, var(--color-canvas), transparent);
}

/* A soft pool of light on the row at the axis, so the eye knows where the selection is before it
   reads anything. */
.toc--enhanced .toc__list::before {
  content: "";
  position: absolute;
  inset-inline-start: 50%;
  inset-block-start: 50%;
  transform: translate(50%, -50%);
  inline-size: min(680px, 82vw);
  /* Tied to the radius so the pool of light keeps its proportion to the wheel: on a tall window a
     fixed 230px band lights a tenth of a much larger cylinder and stops reading as a pool at all. */
  block-size: calc(var(--toc-radius, 260px) * 0.88);
  pointer-events: none;
  filter: blur(4px);
  z-index: 0;
  background: radial-gradient(
    ellipse 46% 52% at 50% 50%,
    color-mix(in oklab, var(--color-accent-bright) 14%, transparent),
    color-mix(in oklab, var(--color-citation) 6%, transparent) 45%,
    transparent 72%
  );
}

.toc--enhanced .toc__list {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(50%, -50%);
  transform-style: preserve-3d;
  /*
   * 66vw, not the 86vw the width would suggest. The title at the axis is pushed one radius toward
   * the reader against a perspective cylinder.js holds at 4.23 radii, so it is projected about
   * 1.31x larger than it is laid out however large the wheel is drawn -- and a row sized to the
   * window renders a third wider than the window and has its ends clipped off. Dividing the
   * available width by that factor is what keeps the ellipsis doing the truncating instead of the
   * overflow.
   */
  inline-size: min(760px, 66vw);
  /* The row box, and so the unit the whole geometry is in: cylinder.js measures this and picks the
     angle between titles that keeps them this far apart on the surface at any radius. */
  block-size: 3.5rem;
  max-inline-size: none;
  border: 0;
  margin: 0;
}

.toc--enhanced .toc__item {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: 100%;
  block-size: 3.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  transform-style: preserve-3d;
  backface-visibility: hidden;
  will-change: transform, opacity;
}

/*
 * Items outside the rendered window are taken out of the layout rather than transformed. Every one
 * of the 314 links stays in the document and stays crawlable -- sitemap.xml and the ItemList
 * JSON-LD carry the same set -- but only the dozen near the axis cost anything per frame.
 */
.toc--enhanced .toc__item[data-parked] { display: none; }

.toc--enhanced .toc__link {
  position: relative;
  flex: 0 0 auto;
  inline-size: max-content;
  max-inline-size: 100%;
  justify-content: center;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-6);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  background: transparent;
  color: var(--color-text);
  font-size: var(--toc-title);
  font-weight: 500;
  line-height: var(--leading-tight);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color 0.25s, background 0.25s, box-shadow 0.3s, border-color 0.25s,
    transform 0.3s cubic-bezier(0.2, 0.85, 0.25, 1);
}

/*
 * The ellipsis has to live on the title span, not on the link. The link is a flex container, and
 * text-overflow has no effect on one -- it truncates inline content inside a block box. Without
 * min-inline-size:0 the span also refuses to shrink below its content, because that is the default
 * minimum size of a flex item.
 */
.toc--enhanced .toc__title {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.toc--enhanced .toc__index { flex: 0 0 auto; opacity: 0.65; transition: color 0.25s, opacity 0.25s; }

/* Hover lifts the title out of the cylinder wall toward the reader. This is the one place in the
   site where hover moves something: here the surface is already in motion, so it reads as depth
   rather than as the layout twitching. */
.toc--enhanced .toc__link:hover {
  color: #fff;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.11), rgba(255, 255, 255, 0.03));
  border-color: var(--color-border-strong);
  transform: translateZ(34px) scale(1.05);
  box-shadow: 0 22px 55px -16px color-mix(in oklab, var(--color-accent-bright) 55%, transparent);
}

.toc--enhanced .toc__link:hover .toc__index { color: var(--color-accent-bright); opacity: 1; }

/* A sheen crossing the pill on hover. Purely physical direction: it is a reflection, and a
   reflection has no reading order to respect. */
.toc--enhanced .toc__link::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: 55%;
  background: linear-gradient(255deg, transparent, rgba(255, 255, 255, 0.22), transparent);
  transform: skewX(18deg) translateX(-250%);
  pointer-events: none;
  opacity: 0;
}

.toc--enhanced .toc__link:hover::after { animation: toc-sheen 0.75s ease; }

@keyframes toc-sheen {
  0% { transform: skewX(18deg) translateX(-250%); opacity: 0; }
  12% { opacity: 1; }
  100% { transform: skewX(18deg) translateX(250%); opacity: 0; }
}

/* The title at the axis: a glass pill with a slow breathing glow, so the selection is legible even
   in peripheral vision while the cylinder is spinning. */
.toc--enhanced .toc__item[data-focused] .toc__link {
  padding: var(--space-4) var(--space-6);
  font-size: var(--toc-title-focused);
  font-weight: 650;
  color: #fff;
  border-color: var(--color-accent-bright);
  background: linear-gradient(
    180deg,
    color-mix(in oklab, var(--color-accent-bright) 16%, transparent),
    rgba(255, 255, 255, 0.03)
  );
  animation: toc-pill-glow 3.4s ease-in-out infinite;
}

.toc--enhanced .toc__item[data-focused] .toc__index { color: var(--color-accent-bright); opacity: 1; }

@keyframes toc-pill-glow {
  0%, 100% {
    box-shadow: 0 14px 44px -16px color-mix(in oklab, var(--color-accent-bright) 35%, transparent),
      inset 0 1px 0 rgba(255, 255, 255, 0.12);
  }
  50% {
    box-shadow: 0 20px 64px -14px color-mix(in oklab, var(--color-citation) 50%, transparent),
      inset 0 1px 0 rgba(255, 255, 255, 0.16);
  }
}

/* ---------- the jump rail ---------- */

/*
 * A scrollbar for a list that has no scrollbar. It sits on the inline-end side (visually left in
 * RTL) and doubles as a position readout: with 314 entries on a wheel, "where am I" is a real
 * question, and rotating to entry 200 by flicking is not a reasonable ask.
 */
.toc__rail {
  position: absolute;
  inset-block: var(--space-5);
  inset-inline-end: var(--space-4);
  inline-size: 8px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.05);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
  z-index: 8;
  cursor: pointer;
  transition: inline-size 0.18s ease, background 0.18s ease;
}

/* Proportional once the stage is the viewport: a fixed inset there leaves a rail almost as tall as
   the screen, and a thumb with that much travel is a coarser control, not a finer one. */
.shell-home .toc__rail { inset-block: 14%; }

.toc__rail:hover,
.toc__rail[data-active] { inline-size: 12px; background: rgba(255, 255, 255, 0.08); }

.toc__rail-thumb {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 0;
  block-size: 52px;
  border-radius: var(--radius-pill);
  background: linear-gradient(180deg, var(--color-accent-bright), var(--color-citation));
  box-shadow: 0 4px 16px -2px color-mix(in oklab, var(--color-accent-bright) 50%, transparent),
    inset 0 1px 0 rgba(255, 255, 255, 0.35);
  will-change: top;
}

/* Grip dots, so the thumb reads as something you can take hold of. */
.toc__rail-thumb::after {
  content: "";
  position: absolute;
  inset-inline-start: 50%;
  inset-block-start: 50%;
  transform: translate(-50%, -50%);
  inline-size: 2px;
  block-size: 14px;
  border-radius: 2px;
  background: rgba(6, 10, 18, 0.35);
  box-shadow: -3px 0 0 rgba(6, 10, 18, 0.35), 3px 0 0 rgba(6, 10, 18, 0.35);
}

/* Points into the page from the rail, never off the edge of the window. */
.toc__rail-bubble {
  position: absolute;
  inset-inline-end: calc(100% + var(--space-3));
  inset-block-start: 0;
  transform: translateY(-50%);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-sm);
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font-size: var(--text-xs);
  font-weight: 600;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.18s ease;
}

.toc__rail-bubble b { color: var(--color-accent-bright); }

.toc__rail:hover .toc__rail-bubble,
.toc__rail[data-active] .toc__rail-bubble { opacity: 1; }

/* ---------- reduced motion ---------- */

/*
 * The cylinder is a rotating, parallaxing surface -- exactly the class of motion that triggers
 * vestibular symptoms. Under prefers-reduced-motion the glow stops breathing and the sheen stops
 * sweeping; cylinder.js separately drops the easing so rotation is instant rather than animated.
 * The control itself stays, because it is the navigation.
 */
@media (prefers-reduced-motion: reduce) {
  .toc--enhanced .toc__item[data-focused] .toc__link { animation: none; }
  .toc--enhanced .toc__link:hover { transform: none; }
  .toc--enhanced .toc__link:hover::after { animation: none; }
}

/* ================================================================
 * The summary panel
 * ================================================================ */

/*
 * Hinged on the inline-start edge and swung open, the way a Persian book opens. The 105° start
 * angle is past the point where the panel is edge-on, so it arrives as a page turning rather than
 * a box scaling up.
 */
.story-panel {
  inline-size: min(40rem, calc(100vw - var(--space-6)));
  max-block-size: 80vh;
  padding: var(--space-6);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: linear-gradient(180deg, var(--color-surface), var(--color-surface-sunken));
  color: var(--color-text);
  box-shadow: 0 40px 120px -30px rgba(0, 0, 0, 0.8);
  overflow: auto;
  transform-origin: right center;
}

.story-panel::backdrop {
  background: rgba(3, 5, 10, 0.72);
  backdrop-filter: blur(8px);
}

.story-panel[open] { animation: story-panel-open 0.62s cubic-bezier(0.2, 0.75, 0.2, 1); }

.story-panel[open]::backdrop { animation: story-panel-fade 0.3s ease; }

@keyframes story-panel-open {
  from { transform: perspective(2200px) rotateY(105deg); opacity: 0.2; }
  to { transform: perspective(2200px) rotateY(0deg); opacity: 1; }
}

@keyframes story-panel-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .story-panel[open],
  .story-panel[open]::backdrop { animation: none; }
}

.story-panel__close {
  position: absolute;
  inset-block-start: var(--space-4);
  inset-inline-end: var(--space-4);
  inline-size: 2rem;
  block-size: 2rem;
  border-radius: 50%;
  background: var(--color-surface-raised);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
  transition: background 0.2s, transform 0.2s;
}

.story-panel__close:hover { background: var(--color-surface); transform: rotate(90deg); }

.story-panel__eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-accent-bright);
  font-size: var(--text-sm);
  font-weight: 700;
  margin-block: 0 var(--space-3);
}

.story-panel__dot {
  inline-size: 7px;
  block-size: 7px;
  border-radius: 50%;
  background: var(--color-accent-bright);
  box-shadow: 0 0 12px var(--color-accent-bright);
}

.story-panel__title {
  font-size: var(--text-xl);
  line-height: var(--leading-tight);
  font-weight: 700;
  margin-block: 0 var(--space-3);
}

.story-panel__chips { margin-block-end: var(--space-4); }

.story-panel__chips:empty { display: none; }

.story-panel__summary {
  color: var(--color-text-secondary);
  font-size: var(--text-base);
  line-height: var(--leading-prose);
  margin-block: 0 var(--space-5);
  min-block-size: var(--space-7);
}

.story-panel__cta { text-decoration: none; }
