/* The handscroll on /: the colophon list laid out as one long sheet of
   paper, mounted on silk between two wooden rollers. Newest at the left,
   reading back in time to the right (docs: README, decisions D1, D10).

   The scroller is a plain LTR overflow-x block and the list is newest
   first in the DOM, laid out as a flex row, so scrollLeft 0 is the newest
   end with or without script, and Tab order runs newest to oldest.
   public/scroll.js only enhances: eased wheel, keys, jumps, and keeping a
   reader's place when a line arrives.

   Band height: the room between the header and the dock (--band-cap:
   --band-avail measured by scroll.js, a CSS estimate otherwise), between
   18rem and 30rem. Sheets are as wide as their line needs (short, mid,
   long: render.ts buckets them), each a fixed width per breakpoint. Every
   sheet is a fixed, contained box, so a sheet coming into view never
   changes any size and the browser never re-measures the whole row; this
   is what keeps a scroll of 10,000 lines smooth. Where a body is taller
   than its sheet, it scrolls inside the sheet, never clipped. */

.handscroll {
  /* Sheet widths are fixed lengths, not clamp() on vw: every sheet's box is
     known before it renders, which keeps a 10,000-line scroll cheap. */
  --sheet-short: 10rem;
  --sheet-mid: 14rem;
  --sheet-long: 18rem;
  --band-cap: clamp(18rem, var(--band-avail, calc(100dvh - 10rem - var(--dock-h, 7rem))), 30rem);

  position: relative;
  display: flex;
  align-items: stretch;
  min-width: 0;
  margin: 0.4rem 1rem;
  padding: 0.9rem 0;

  /* Silk mount: a band of brocade above and below the paper. */
  background-color: var(--silk);
  background-image:
    linear-gradient(rgb(0 0 0 / 18%), transparent 0.9rem, transparent calc(100% - 0.9rem), rgb(0 0 0 / 18%)),
    var(--slub),
    repeating-linear-gradient(45deg, rgb(255 255 255 / 6%) 0 1px, transparent 1px 1.1rem),
    repeating-linear-gradient(-45deg, rgb(255 255 255 / 6%) 0 1px, transparent 1px 1.1rem);
  background-size: auto, 6rem 6rem, auto, auto;
  background-blend-mode: normal, multiply, normal, normal;
  box-shadow: 0 0.3rem 1rem rgb(0 0 0 / 28%);
}

/* The rollers: a wooden stave at each end, its turned knobs standing
   proud of the silk above and below. */
.handscroll-roller {
  position: relative;
  z-index: 1;
  flex: none;
  width: 1.15rem;
  margin: -1.3rem 0;
  border-radius: 0.3rem;
  background: linear-gradient(90deg, var(--wood-dark), var(--wood) 35%, var(--wood-mid) 70%, var(--wood-dark));
  box-shadow: 0.15rem 0 0.4rem rgb(0 0 0 / 30%);
}

.handscroll-roller::before,
.handscroll-roller::after {
  content: "";
  position: absolute;
  left: -0.25rem;
  width: 1.65rem;
  height: 0.85rem;
  border-radius: 0.35rem;
  background: linear-gradient(var(--wood-dark), var(--wood) 50%, var(--wood-dark));
}

.handscroll-roller::before {
  top: -0.4rem;
}

.handscroll-roller::after {
  bottom: -0.4rem;
}

.handscroll-roller--start {
  margin-left: -0.6rem;
}

.handscroll-roller--end {
  margin-right: -0.6rem;
  box-shadow: -0.15rem 0 0.4rem rgb(0 0 0 / 30%);
}

/* The paper ground. Its weave is painted per sheet (below), so it travels
   with the sheets and only visible sheets paint it. */
.scroll {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  overflow: auto hidden;
  overscroll-behavior-x: contain;
  scroll-behavior: auto;
  background-color: var(--paper);
  box-shadow:
    0 0 0 1px var(--gilt),
    0 0 0 0.3rem rgb(185 160 106 / 35%);
  scrollbar-color: var(--granite) transparent;
  scrollbar-width: thin;
}

/* The focus ring sits inside the paper: on the dark silk it would vanish. */
.scroll:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: -0.3rem;
}

.scroll .colophon-list {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  justify-content: flex-start;
  width: max-content;
  min-width: 100%;
  height: var(--band-cap);
  margin: 0;
  padding: 0 0.6rem;
  list-style: none;
}

/* One colophon, one sheet: a column of paper with a faint join to the
   next, not a card. */
.scroll .colophon {
  position: relative;
  display: flex;
  flex: none;
  flex-direction: column;
  gap: 0.6rem;
  width: var(--sheet-long);
  height: 100%;
  padding: 1.3rem 1.2rem 1rem;
  border: 0;
  border-left: 1px solid rgb(185 160 106 / 28%);
  box-shadow: inset 1px 0 0 rgb(255 255 255 / 45%);

  /* Weave lines are translucent --paper-shade, so nothing behind text is
     darker than that. */
  background-image:
    repeating-linear-gradient(0deg, rgb(235 227 211 / 60%) 0 1px, transparent 1px 0.35rem),
    repeating-linear-gradient(90deg, rgb(235 227 211 / 45%) 0 1px, transparent 1px 0.3rem);
  contain: strict;
  content-visibility: auto;
  contain-intrinsic-size: var(--sheet-long) var(--band-cap);
}

/* Width by length, so a one-line note isn't a column of empty paper and a
   phone shows two short ones at once. Placeholder sizes match. */
.scroll .colophon--short {
  width: var(--sheet-short);
  contain-intrinsic-size: var(--sheet-short) var(--band-cap);
}

.scroll .colophon--mid {
  width: var(--sheet-mid);
  contain-intrinsic-size: var(--sheet-mid) var(--band-cap);
}

/* Once scroll.js has summed the sheets' widths onto the list, the list is
   a fixed-size box too, so a sheet scrolling into view never makes the
   browser re-measure the whole row. */
.scroll .colophon-list[data-sized] {
  contain: strict;
}

.scroll .colophon--short .colophon-body {
  font-size: 1.25em;
  line-height: 1.4;
}

.scroll .colophon:last-child {
  border-left-color: transparent;
}

.scroll .colophon-body {
  flex: 1 1 auto;
  min-height: 0;
  margin: 0;
  overflow-y: auto;
  overscroll-behavior-y: contain;
}

/* A body taller than its sheet (320 full-width characters, say) scrolls
   inside the sheet. scroll.js marks it: the last lines fade into the paper
   while more is hidden below, and the body takes focus so the arrow keys
   can scroll it. */
.scroll .colophon-body[data-more] {
  mask-image: linear-gradient(#000 calc(100% - 2.2rem), transparent);
}

.scroll .colophon-body[data-overflow]:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* The foot: date, then the seal pressed beside it, closing the passage. */
.scroll .colophon-foot {
  position: relative;
  display: flex;
  flex: none;
  flex-direction: row-reverse;
  align-items: flex-end;
  justify-content: flex-end;
  gap: 0.6rem;
}

.scroll .colophon-foot .colophon-seal {
  position: static;
  flex: none;
}

/* The meaning opens upward from the foot, inside the sheet, since the
   band clips anything that escapes it vertically. */
.scroll .colophon-foot .seal-meaning {
  top: auto;
  bottom: calc(100% + 0.4rem);
  left: 0;
  max-width: 100%;
}

.scroll .empty-note {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  margin: 0;
  padding: 1rem;
  text-align: center;
  color: var(--ink-soft);
}

/* The seal stamp keeps its drawn size, but its tap target reaches 44px:
   an invisible pad around it, inside the sheet's own padding. */
.scroll .colophon-seal summary {
  position: relative;
}

.scroll .colophon-seal summary::after {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  width: max(100%, 2.75rem);
  height: max(100%, 2.75rem);
  transform: translate(-50%, -50%);
}

/* The overview strip under the band (contract O): the whole scroll in
   miniature, newest at the left. Pointer only; the cue says it in words. */
.scroll-overview {
  position: relative;
  height: 1.1rem;
  margin: 0.55rem 1.6rem 0;
  cursor: pointer;
  touch-action: none;
  background: var(--paper);
  box-shadow: inset 0 0 0 1px var(--gilt);
  border-radius: 0.2rem;
}

.scroll-overview-window {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  min-width: 0.9rem;
  background: rgb(97 120 90 / 55%);
  box-shadow: inset 0 0 0 1px var(--silk-dark);
  border-radius: 0.2rem;
  will-change: transform;
}

/* The strip draws 1.1rem tall but takes taps over 44px: an invisible pad
   above and below. Window and ticks stay inside the drawn strip (scroll.js
   clamps the window), so nothing else needs the strip to clip. */
.scroll-overview::before {
  content: "";
  position: absolute;
  inset: 50% 0 auto;
  height: max(100%, 2.75rem);
  transform: translateY(-50%);
}

/* The visitor's own lines (runs merged by scroll.js), drawn above the
   window: red still means only "yours". */
.scroll-overview-tick {
  position: absolute;
  z-index: 1;
  top: 0.15rem;
  bottom: 0.15rem;
  min-width: 2px;
  pointer-events: none;
  background: var(--seal);
}

/* On a paper slip: --ink-soft on the plaster wall would fall under 4.5:1. */
.scroll-cue {
  width: fit-content;
  margin: 0.35rem 1.6rem 0;
  padding: 0.1rem 0.5rem;
  background: var(--paper);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
}

/* Narrow screens: a long sheet leaves a peek of the next one. */
@media (width <= 26rem) {
  .handscroll {
    --sheet-long: 17rem;
  }
}

/* One line on a phone: the cue stretches to the strip's width and tightens. */
@media (width <= 30rem) {
  .scroll-cue {
    margin-inline: 1.1rem;
    padding-inline: 0.35rem;
    font-size: 0.78rem;
    letter-spacing: -0.01em;
  }
}

/* A permalinked line: a brief gilt ring, fading. Opacity only. */
.scroll .colophon--target::after {
  content: "";
  position: absolute;
  inset: 0.3rem;
  pointer-events: none;
  border-radius: 0.2rem;
  box-shadow: 0 0 0 2px var(--gilt);
  opacity: 0;
  animation: scroll-target-glow 2.4s ease-out;
}

@keyframes scroll-target-glow {
  15%,
  45% {
    opacity: 1;
  }
}

@media (prefers-reduced-motion: reduce) {
  .scroll .colophon--target::after {
    opacity: 1;
    animation: none;
  }
}

@media print {
  .scroll {
    height: auto;
    overflow: visible;
  }

  .scroll .colophon-list {
    flex-flow: column;
    width: auto;
  }

  .scroll .colophon {
    width: auto;
    content-visibility: visible;
  }

  .handscroll-roller {
    display: none;
  }
}
