/* Homepage: a punched tape that scrolls under a fixed read head, with
   one entry panel below showing whichever cell the head is over. */

.wrap:has(.home-stage) { max-width: 78rem; padding: 0 2.25rem; }
.wrap:has(.home-stage) header.site { padding-top: 2rem; padding-bottom: .8rem; }
.wrap:has(.home-stage) { --home-secondary: #5f5b54; --home-tertiary: #6f6a62; }
:root[data-theme="dark"] .wrap:has(.home-stage) { --home-secondary: #b0ada5; --home-tertiary: #7e7e7e; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .wrap:has(.home-stage) { --home-secondary: #b0ada5; --home-tertiary: #7e7e7e; }
}

.identity {
  margin: .9rem 0 0;
  font: .8rem/1.5 var(--mono);
  letter-spacing: 0;
  color: var(--home-secondary);
}

.home-stage { margin: 0 0 2rem; }

/* ---- tape --------------------------------------------------------- */

/* Held to a compact centred width so the reader reads as one piece of
   machinery sitting in the page, rather than a rule drawn across it. */
.tape-nav {
  --cell-w: 9.5rem;
  --cell-h: 5.5rem;
  --tape-visible: 5;
  position: relative;
  max-width: 46rem;
  margin: 5.5rem auto 0;
}

/* The head is a fixed gate bolted to the frame, not a marker floating
   over the tape - the tape is threaded through it. Same idea as the
   finger stop on a rotary dial: the stop never moves, the dial turns
   against it and every number arrives at the same place. */
.tape-head {
  position: absolute;
  left: 50%;
  top: -.75rem;
  bottom: -.75rem;
  width: calc(var(--cell-w) + 1.5rem);
  transform: translateX(-50%);
  border-left: 1px solid var(--ink);
  border-right: 1px solid var(--ink);
  z-index: 3;
  pointer-events: none;
}

/* The pointer, attached to the gate rather than hovering above it. */
.tape-head::before {
  content: "";
  position: absolute;
  left: 50%;
  top: -.58rem;
  transform: translateX(-50%);
  width: 0;
  height: 0;
  border-left: .45rem solid transparent;
  border-right: .45rem solid transparent;
  border-top: .6rem solid var(--ink);
}

/* The tape edges - the two rails and their sprocket holes - live on the
   viewport, not the track, so they span the full frame and stay put
   while only the cells travel underneath. Putting them on the track
   made them scroll off with it, so the rails stopped short of the frame
   and the cells appeared to spill out of their own box. A background on
   a scroll container is fixed to the element by default, which is
   exactly the behaviour wanted here. */
/* No scroll-snap here on purpose. Mandatory snap silently reverts any
   intermediate scrollLeft assignment, which killed the per-frame
   animation outright: the panel changed but the tape never moved. The
   snapping is done in site.js instead, by easing to the nearest cell
   once a drag settles - same behaviour, fully under our control. */
.tape-viewport {
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  border-top: 1px solid var(--ink);
  border-bottom: 1px solid var(--ink);
  background-image:
    radial-gradient(circle at center, var(--grey) 0 1.15px, transparent 1.5px),
    radial-gradient(circle at center, var(--grey) 0 1.15px, transparent 1.5px);
  background-size: 13px 13px, 13px 13px;
  background-position: 0 5px, 0 calc(100% - 5px);
  background-repeat: repeat-x, repeat-x;
}

.tape-viewport::-webkit-scrollbar { display: none; }

/* The tape is grabbable, so say so. Cells keep the pointer cursor at
   rest because they are also links; once a drag is under way everything
   becomes the tape. */
.tape-viewport { cursor: grab; }

.tape-nav.is-dragging { user-select: none; }
.tape-nav.is-dragging .tape-viewport,
.tape-nav.is-dragging .tape-cell { cursor: grabbing; }

.tape-track {
  display: flex;
  align-items: center;
  width: max-content;
}

/* The blank run-out either side, and the only thing that lets the first
   and last cells reach the centre of the frame. A run-out that is even
   slightly short is a hard wall: the end cells then sit past the end of
   the scroll range and cannot be reached at all.

   Two empty boxes with a width, and nothing cleverer than that. This has
   now been percentage padding, vw padding and pseudo-element flex items,
   and each one collapsed to nothing on some engine - padding because the
   track is width:max-content under a global box-sizing:border-box, which
   puts padding inside that width instead of extending it. A plain
   in-flow box with a width is the one construction every engine agrees
   contributes to a flex row's max-content size.

   The width here is only the floor for a page with no JS; site.js sets
   it in real pixels off the measured frame. Half the window is always
   enough, because the frame is never wider than the window. */
.tape-runout {
  flex: 0 0 auto;
  width: calc(50vw - (var(--cell-w) / 2));
  align-self: stretch;
}

.tape-cell {
  flex: 0 0 var(--cell-w);
  height: var(--cell-h);
  margin: 1.15rem 0;
  padding: 0;
  display: grid;
  place-items: center;
  background: none;
  border: 1px solid var(--grey);
  color: var(--home-tertiary);
  font-family: var(--display);
  font-size: 1.5rem;
  cursor: pointer;
  transition: color .25s ease, border-color .25s ease;
}

.tape-cell + .tape-cell { margin-left: -1px; }
.tape-cell:hover { color: var(--ink); }
.tape-cell:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }

.tape-cell.is-active {
  color: var(--ink);
  border-color: var(--ink);
  box-shadow: 0 0 0 1px var(--ink);
  z-index: 1;
}

.tape-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 1.9rem;
  height: 1.9rem;
  border: 0;
  background: none;
  color: var(--grey);
  font: .9rem var(--mono);
  cursor: pointer;
  z-index: 2;
}

.tape-arrow:hover { color: var(--ink); }
.tape-arrow:focus-visible { outline: 1px solid var(--ink); outline-offset: 3px; }
.tape-prev { left: -.35rem; }
.tape-next { right: -.35rem; }

/* ---- entry panel -------------------------------------------------- */

.panel-stage { position: relative; margin-top: 4rem; }

.entry-panel {
  display: grid;
  grid-template-columns: minmax(0, .8fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: start;
}

/* Panels stack in the same grid cell so switching one out doesn't
   reflow the page; only the active one is visible. */
.entry-panel + .entry-panel {
  position: absolute;
  inset: 0;
}

.entry-panel:not(.is-active) {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 300ms ease, visibility 0s linear 300ms;
}

.entry-panel.is-active {
  opacity: 1;
  visibility: visible;
  transform: translateX(0);
  transition: opacity 350ms ease, transform 350ms cubic-bezier(.16, .78, .18, 1);
}

.entry-meta {
  margin: 0 0 1.1rem;
  font: .68rem var(--mono);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--home-tertiary);
}

.entry-title { margin: 0 0 1.1rem; font-size: 1.75rem; font-weight: 400; line-height: 1.2; }
.entry-title a { color: var(--ink); text-decoration: none; }
.entry-title a:hover { text-decoration: underline; text-underline-offset: .22em; }

.entry-desc {
  margin: 0 0 1.1rem;
  font: .95rem/1.6 var(--mono);
  letter-spacing: 0;
  color: var(--home-secondary);
}

.entry-tags {
  margin: 0;
  font: .68rem var(--mono);
  letter-spacing: .04em;
  color: var(--home-tertiary);
}

.entry-figure { border: 1px solid var(--hairline); }

.cover-fig-chrome {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: .45rem .7rem;
  border-bottom: 1px solid var(--hairline);
  font: .6rem var(--mono);
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--home-tertiary);
}

/* Capped against the viewport height so the whole homepage - identity,
   tape and panel - lands inside one screen on a normal laptop instead
   of pushing the figure below the fold. The SVGs carry a viewBox and
   default preserveAspectRatio, so they scale down and centre rather
   than distort. */
.cover-svg {
  display: block;
  width: 100%;
  height: auto;
  max-height: min(20rem, 34vh);
  filter: grayscale(1) contrast(1.4) brightness(.94);
}

:root[data-theme="dark"] .cover-svg { filter: grayscale(1) invert(1) contrast(1.35) brightness(.94); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .cover-svg { filter: grayscale(1) invert(1) contrast(1.35) brightness(.94); }
}

/* Diagram draw-in, fired once per panel the first time it becomes
   active (site.js adds .is-drawn). */
@keyframes cover-draw { to { stroke-dashoffset: 0; } }
.entry-panel.is-drawn .draw-path { animation: cover-draw 1.6s ease forwards; }

@media (prefers-reduced-motion: reduce) {
  .draw-path { stroke-dashoffset: 0 !important; animation: none !important; }
  .tape-cell, .entry-panel { transition: none; }
}

/* ---- narrow screens ----------------------------------------------- */

@media (max-width: 900px) {
  .entry-panel { grid-template-columns: 1fr; gap: 1.75rem; }
}

@media (max-width: 600px) {
  .wrap:has(.home-stage) { padding: 0 1rem; }
  .tape-nav { --cell-w: 6.5rem; --cell-h: 4.75rem; --tape-visible: 3; margin-top: 2.5rem; }
  .tape-cell { font-size: 1.2rem; margin: .9rem 0; }
  .panel-stage { margin-top: 2.75rem; }
  .entry-title { font-size: 1.4rem; }
  .entry-desc { font-size: .88rem; }
  .tape-prev { left: -.5rem; }
  .tape-next { right: -.5rem; }

  /* The figure loses its box on mobile and bleeds to the screen edges
     instead - the one moment of real scale on a page that is otherwise
     a single column of body text. Matches the wrap's own padding so it
     lands flush; the side borders go with it since a border on an edge
     that touches nothing doesn't read as a box anymore. */
  .entry-figure {
    margin-left: -1rem;
    margin-right: -1rem;
    border-left: none;
    border-right: none;
  }

  .cover-fig-chrome { padding: .45rem 1rem; }
}
