/* A published site page: the page FRAME, the BANDS, and what a page wants
 * differently from the block vocabulary /article.css carries. Served, never
 * bundled: a baked site page links it with no SPA.
 *
 * Reads the `--sp-*` palette, which /article.css defines — a document linking
 * this sheet links that one too, and loads it FIRST, which is what lets a rule
 * here override one there at equal specificity. The three band grounds that
 * palette does not carry are defined here, since a band ground belongs to this
 * sheet. */

:root,
:root.light {
  --sp-warm: #a16207;
  --sp-ink: #1c1c1e;
  --sp-ink-fg: #f5f3ef;
  /* What reads on `--sp-primary`. One value for both themes, but named here so
     the CTA states it rather than spelling a colour. */
  --sp-primary-fg: #ffffff;
}
:root.dark {
  --sp-warm: #f5b14c;
  --sp-ink: #101012;
  --sp-ink-fg: #ece9e4;
}
@media (prefers-color-scheme: dark) {
  :root:not(.light):not(.dark) {
    --sp-warm: #f5b14c;
    --sp-ink: #101012;
    --sp-ink-fg: #ece9e4;
  }
}

/* This sheet is delivered to hosts with a CSS reset and hosts without one, so
 * its own boxes declare how they are measured rather than inheriting an answer.
 * Border-box is what the SPA's own front page measures its bands in, so all
 * three surfaces put the band measure in the same place. */
[class^="sp-site"],
[class*=" sp-site"] {
  box-sizing: border-box;
}

/* A baked page loads no framework reset, so the keyboard-focus ring is this
 * sheet's own — drawn on :focus-visible alone, so a pointer press never shows
 * one. `--sp-site-ring` is what a ground retunes when the site accent has
 * nothing to sit against. */
[class^="sp-site"]:focus-visible,
[class*=" sp-site"]:focus-visible,
.sp-site-prose a:focus-visible {
  outline: 1.5px solid var(--sp-site-ring, color-mix(in oklab, var(--sp-primary) 65%, transparent));
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  [class^="sp-site"],
  [class*=" sp-site"] {
    transition: none !important;
  }
}

/* A site page's own ground. `body.sp-site` is the BAKED document — the SPA
 * styles its own shell — and the bare class is the same ground on a canvas
 * inside another app, which is how the editor renders a page as published. */
body.sp-site {
  margin: 0;
}
body.sp-site,
.sp-site {
  background: var(--sp-bg);
  color: var(--sp-fg);
  font-family: var(--sp-sans, var(--font-sans, ui-sans-serif, system-ui, sans-serif));
}

/* ── The page frame ────────────────────────────────────────────────────────
 * A baked page draws the site's authored chrome where the SPA shell draws it:
 * the sidebar as the column beside the document, and the document's own column
 * holding the flow and the footer — so the footer stands BESIDE the sidebar,
 * never under it. The SPA gives its own sidebar column `.sp-site-aside` too, so
 * the measure inside it is one rule rather than a second vocabulary. */

.sp-site-frame {
  display: flex;
  align-items: stretch;
}
.sp-site-column {
  display: flex;
  flex-direction: column;
  flex: 1 1 0;
  min-width: 0;
}
.sp-site-main {
  min-width: 0;
}
.sp-site-aside {
  min-width: 0;
}
.sp-site-frame > .sp-site-aside {
  flex: 0 0 14rem;
  padding-inline: 0.875rem;
  border-inline-end: 1px solid var(--sp-border);
}
/* The SPA stacks its own chrome below `md`, so the baked page does too rather
   than squeezing a column the reader would not draw. */
@media (max-width: 48rem) {
  .sp-site-frame {
    display: block;
  }
  .sp-site-frame > .sp-site-aside {
    border-inline-end: none;
    border-block-end: 1px solid var(--sp-border);
  }
}
/* A column, not a page: its content takes the column's width, and the column
   itself supplies the horizontal inset. */
.sp-site-aside .sp-site-band-inner {
  --sp-site-inset: 0px;
  max-width: none;
  padding: 0.75rem 0;
}

.sp-site-prose > :first-child {
  margin-top: 0;
}
.sp-site-prose > :last-child {
  margin-bottom: 0;
}
.sp-site-prose h2 {
  font-family: var(--sp-serif, var(--font-serif, Georgia, serif));
  font-size: 1.25rem;
  font-weight: 600;
  margin: 1.25rem 0 0.5rem;
}
.sp-site-prose p {
  margin: 0.75rem 0;
  line-height: 1.6;
}
.sp-site-prose a {
  text-decoration: underline;
}
.sp-site-prose ul,
.sp-site-prose ol {
  margin: 0.75rem 0;
  padding-inline-start: 1.5rem;
  list-style: revert;
}
/* ── What a PAGE wants differently ─────────────────────────────────────────
 * The block markup and its rules are /article.css's, one per kind. Everything
 * below states a page's own answer to a rule that sheet already carries; a
 * block absent from here reads on a page exactly as it reads in an article. */

/* A page sets its prose sans and at the page's own size, so the article's
 * serif measure does not follow the markup onto a band. */
.sp-site-prose blockquote,
.sp-site-prose .emphasis,
.sp-site-prose aside.factbox {
  font-size: 1rem;
  line-height: 1.6;
}
/* No lightbox on a page: the picture is not clickable, so it must not offer to
 * be. */
.sp-site-prose figure img {
  max-height: none;
  cursor: auto;
}
.sp-site-prose figure img:hover {
  transform: none;
}
.sp-site-prose figcaption {
  text-align: start;
  font-size: 0.8125rem;
  opacity: 0.7;
}

/* ── Bands ────────────────────────────────────────────────────────────────
 * One section per band, each on its own ground, sharing one measure. Live
 * bands mount an Alpine island inside this same markup (/site.js), so a band's
 * shape does not depend on whether its content arrived yet. */

/* A STRIP is only a ground; a BAND is a ground plus its own measure. Both paint
 * the same way, so a widget reads the same standing on a strip and standing
 * alone. */
.sp-site-band,
.sp-site-strip {
  background: var(--sp-band-ground, var(--sp-bg));
  color: var(--sp-band-ink, inherit);
  font-family: var(--sp-sans, var(--font-sans, ui-sans-serif, system-ui, sans-serif));
}

/* THE BUILT-IN GROUNDS — the eight entries a site's palette seeds with, one rule
 * each. The generated `data/site.css` loads after this sheet, so a retuned entry
 * wins and one carrying no colours of its own falls through to here.
 *
 * Every entry is a LIGHT/DARK PAIR: the first
 * four ride the theme's own tokens, the tinted three state the pair with
 * `light-dark()` (the theme blocks above declare `color-scheme`, which is what
 * resolves it).
 *
 * A pair stays on the SAME SIDE as the page, so the page's own text and muted
 * colours keep reading on it and an entry needs nothing but its ground. `ink`
 * is the one that INVERTS, and it retunes what inverting costs — the ink, the
 * dim every muted part takes, the row rule and the focus ring — right here, so
 * the markup carries no marker and nothing above this sheet knows a ground is
 * dark. */
.sp-site-band-plain {
  --sp-band-ground: var(--sp-bg);
}
.sp-site-band-surface {
  --sp-band-ground: var(--sp-surface);
}
.sp-site-band-cool {
  --sp-band-ground: color-mix(in srgb, var(--sp-primary) 7%, var(--sp-bg));
}
.sp-site-band-warm {
  --sp-band-ground: color-mix(in srgb, var(--sp-warm) 9%, var(--sp-bg));
}
.sp-site-band-sage {
  --sp-band-ground: light-dark(#e8efe6, #1b241d);
}
.sp-site-band-rose {
  --sp-band-ground: light-dark(#f6e9e9, #271b1d);
}
.sp-site-band-plum {
  --sp-band-ground: light-dark(#eee9f4, #221d2b);
}
.sp-site-band-ink {
  --sp-band-ground: var(--sp-ink);
  --sp-band-ink: var(--sp-ink-fg);
  --sp-band-ink-dim: 0.7;
  --sp-band-rule: color-mix(in srgb, currentcolor 20%, transparent);
  --sp-site-ring: color-mix(in oklab, currentcolor 60%, transparent);
}

/* The measure and its inset are named so a rail can bleed past them and put
   its cards back on them. */
.sp-site-band-inner {
  --sp-site-measure: 64rem;
  --sp-site-inset: 1.5rem;
  max-width: var(--sp-site-measure);
  margin: 0 auto;
  padding: 1.75rem var(--sp-site-inset);
  timeline-scope: --sp-rail;
}
/* A STRIP carries the measure so its ground stays full width: what stands on
   one is already at that measure and inside its inset, so its own inner adds
   neither, and stacked contents keep their rhythm between them instead. The
   INSET itself survives, and must: it is no longer this element's own padding,
   but it is still the distance a rail inside has to pull back out through to
   reach the strip's edge. Zeroing it pins such a rail to the strip's padding. */
.sp-site-band-inner .sp-site-band-inner {
  max-width: none;
  padding: 0;
}
.sp-site-band-inner
  > :is(.sp-site-band, .sp-site-band-inner)
  + :is(.sp-site-band, .sp-site-band-inner) {
  margin-block-start: 1.75rem;
}
.sp-site-band-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
/* The head row's own LINE takes this whether or not it carries the class: an
 * EDITING surface stands an authored text block in the row, and that element
 * follows the block's own setting, not this sheet's. */
.sp-site-band-title,
.sp-site-band-head > :is(h1, h2, h3, p) {
  margin: 0;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--sp-band-ink, var(--sp-muted));
  opacity: var(--sp-band-ink-dim, 1);
}
.sp-site-more {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  flex-shrink: 0;
  border-radius: 0.25rem;
  font-size: 0.875rem;
  color: var(--sp-band-ink, var(--sp-muted));
  opacity: var(--sp-band-ink-dim, 1);
  text-decoration: none;
  transition:
    color 150ms ease,
    opacity 150ms ease;
}
@media (hover: hover) {
  .sp-site-more:hover {
    color: var(--sp-band-ink, var(--sp-fg));
    --sp-band-ink-dim: 1;
  }
}
.sp-site-center {
  text-align: center;
}

/* ── Shared card parts ───────────────────────────────────────────────────── */

/* The cover is its own frame: the hairline and the drop shadow are one
   box-shadow, so a hover retunes either without a wrapper element. */
.sp-site-cover {
  display: block;
  width: 100%;
  aspect-ratio: 210 / 276;
  object-fit: cover;
  border-radius: 0.25rem;
  background: var(--sp-muted-bg);
  box-shadow:
    0 0 0 1px var(--sp-cover-ring, var(--sp-border)),
    var(--sp-cover-shadow, 0 6px 14px -6px rgb(0 0 0 / 0.28));
  transition:
    box-shadow 200ms ease,
    translate 200ms ease,
    scale 260ms ease;
}
/* A special issue carries a warm hairline wherever this band lists it. */
.sp-site-wide-card .sp-site-cover {
  --sp-cover-ring: color-mix(in srgb, var(--sp-warm) 55%, transparent);
}
@media (hover: hover) {
  :is(.sp-site-issue-card, .sp-site-cover-link, .sp-site-wide-card):hover .sp-site-cover {
    --sp-cover-shadow: 0 16px 30px -10px rgb(0 0 0 / 0.45);
    translate: 0 -2px;
    scale: 1.02;
  }
}
.sp-site-cover-blank {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  text-align: center;
  font-family: var(--sp-serif, var(--font-serif, Georgia, serif));
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--sp-muted);
}
.sp-site-card-title {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.875rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sp-site-card-title-lg {
  font-family: var(--sp-serif, var(--font-serif, Georgia, serif));
  font-size: 1.125rem;
  line-height: 1.35;
  white-space: normal;
  text-wrap: balance;
}
.sp-site-card-meta {
  display: block;
  font-size: 0.75rem;
  color: var(--sp-band-ink, var(--sp-muted));
  opacity: var(--sp-band-ink-dim, 1);
}
/* The audio mark sits on the byline's own line, so the meta span becomes a row
   only where a card carries one. */
.sp-site-card-meta-audio {
  display: flex;
  align-items: center;
  gap: 0.35em;
}
.sp-site-kicker {
  display: block;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--sp-primary);
}
.sp-site-badge {
  align-self: start;
  border: 1px solid color-mix(in srgb, var(--sp-warm) 45%, transparent);
  background: color-mix(in srgb, var(--sp-warm) 12%, transparent);
  border-radius: 9999px;
  padding: 0.125rem 0.5rem;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--sp-warm);
}
.sp-site-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border: 1px solid var(--sp-primary);
  border-radius: 9999px;
  background: var(--sp-primary);
  padding: 0.375rem 0.75rem;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--sp-primary-fg);
  text-decoration: none;
  transition: filter 150ms ease;
}
@media (hover: hover) {
  .sp-site-cta:hover {
    filter: brightness(1.1);
  }
}
/* An inline icon takes the line's own size and colour. */
.sp-site-icon {
  flex-shrink: 0;
  width: 1em;
  height: 1em;
}

/* A card anchor carries no link colour of its own. `:where` so this costs no
   specificity: a named part — the more link, the CTA, the rail end — states its
   own colour and must win over the reset. */
:where(.sp-site-band, .sp-site-strip) a {
  color: inherit;
  text-decoration: none;
}
:where(.sp-site-band, .sp-site-strip) .sp-site-prose a {
  color: inherit;
  text-decoration: underline;
}

/* ── latest_issue ────────────────────────────────────────────────────────── */

.sp-site-hero {
  display: grid;
  gap: 1.5rem;
}
/* The cover is this band's subject, so it takes the wider half of the split and
   the lead image beside it is trimmed to suit — the cover then stands taller
   than the photo, which is what makes it read as the artefact. */
@media (min-width: 40rem) {
  .sp-site-hero {
    grid-template-columns: 5fr 6fr;
    gap: 2rem;
  }
}
/* The cover column is a stack, so the CTA and the issue's meta line take their
   spacing from it rather than each carrying a margin of its own. */
.sp-site-hero-cover {
  display: grid;
  align-content: start;
  gap: 0.75rem;
}
.sp-site-cover-link {
  display: block;
  border-radius: 0.25rem;
}
.sp-site-hero-body {
  display: grid;
  align-items: start;
  gap: 1.5rem;
  min-width: 0;
}
@media (min-width: 75rem) {
  /* The body splits into two, so the cover keeps its share of the whole rather
     than of the half the lead now stands in. */
  .sp-site-hero {
    grid-template-columns: calc(31% - 1rem) 1fr;
  }
  .sp-site-hero-body {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    gap: 2rem;
  }
}
/* The list bleeds its first row's padding the way a row bleeds its inline one,
   so the row's thumb starts level with the lead image beside it. */
.sp-site-hero-body > .sp-site-rows {
  margin-block-start: -0.75rem;
}

.sp-site-lead {
  display: block;
  min-width: 0;
  border-radius: 0.5rem;
}
.sp-site-lead-image {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 0.5rem;
  background: var(--sp-muted-bg);
  margin-bottom: 0.75rem;
}
.sp-site-lead-title {
  display: block;
  margin-top: 0.25rem;
  font-family: var(--sp-serif, var(--font-serif, Georgia, serif));
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.2;
  text-wrap: balance;
}
/* Clamped: a blurb is a teaser on every surface that lists one, and an
   unclamped one pushes the card's own meta line out of the fold. */
.sp-site-lead-blurb {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
  margin-top: 0.5rem;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--sp-band-ink, var(--sp-muted));
  opacity: var(--sp-band-ink-dim, 1);
}
.sp-site-lead > .sp-site-card-meta {
  margin-top: 0.5rem;
}

/* ── The per-entry save toggle ────────────────────────────────────────────
   The button stands BESIDE the card's own `<a>` (a button inside an anchor is
   invalid), so the card is wrapped and the wrapper is what positions it. */

.sp-site-savewrap {
  position: relative;
  min-width: 0;
}
.sp-site-save {
  position: absolute;
  inset-block-start: 0.375rem;
  inset-inline-end: 0.375rem;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 0.375rem;
  background: none;
  color: var(--sp-band-ink, var(--sp-muted));
  cursor: pointer;
  transition:
    color 0.15s,
    background-color 0.15s,
    border-color 0.15s;
}
.sp-site-save:hover {
  border-color: var(--sp-border);
  background: var(--sp-surface);
  color: var(--sp-band-ink, var(--sp-fg));
}
.sp-site-save[aria-pressed="true"] {
  color: var(--sp-primary);
}
.sp-site-save[aria-pressed="true"] svg {
  fill: currentcolor;
}
.sp-site-save .sp-site-icon {
  width: 1rem;
  height: 1rem;
}

/* ── Rows (article teasers, post list) ───────────────────────────────────── */

.sp-site-rows {
  margin: 0;
  padding: 0;
  list-style: none;
  min-width: 0;
}
.sp-site-rows > li + li {
  border-top: 1px solid var(--sp-band-rule, var(--sp-border));
}
/* A list may END in the way out of it, and that link is the row after the last
   teaser: it takes the rows' own vertical rhythm rather than the head row's. */
.sp-site-rows > li > .sp-site-more {
  padding-block: 0.75rem;
}
/* The row bleeds its own inline padding past the list, so the hover ground is
   wider than the text while the rules between rows stay flush with it. */
.sp-site-row {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  margin-inline: -0.75rem;
  padding: 0.75rem;
  border-radius: 0.375rem;
  transition: background-color 150ms ease;
}
@media (hover: hover) {
  .sp-site-row:hover {
    background: color-mix(in srgb, currentcolor 7%, transparent);
  }
}
.sp-site-thumb {
  flex-shrink: 0;
  width: 3.5rem;
  height: 3.5rem;
  object-fit: cover;
  border-radius: 0.25rem;
  background: var(--sp-muted-bg);
}
.sp-site-row-body {
  min-width: 0;
}
.sp-site-row-title {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.35;
}
.sp-site-row-body > .sp-site-card-meta {
  margin-top: 0.125rem;
}

/* ── podcast ─────────────────────────────────────────────────────────────── */

/* Flex, not a grid: the show column hides itself when the feed named neither
   artwork nor a blurb, and the episode list then takes the whole measure. */
.sp-site-podcast {
  display: flex;
  flex-wrap: wrap;
  gap: 1.5rem;
}
.sp-site-podcast-show {
  flex: 0 0 auto;
  width: 8rem;
}
.sp-site-podcast-show:not(:has(img, p)) {
  display: none;
}
.sp-site-podcast > .sp-site-rows {
  flex: 1 1 18rem;
}
@media (min-width: 40rem) {
  .sp-site-podcast-show {
    width: 11rem;
  }
}
.sp-site-podcast-art {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 0.5rem;
  background: var(--sp-muted-bg);
}
.sp-site-podcast-about {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
  margin: 0.75rem 0 0;
  font-size: 0.75rem;
  line-height: 1.6;
  color: var(--sp-band-ink, var(--sp-muted));
  opacity: var(--sp-band-ink-dim, 1);
}

/* ── Rails (issue_rail, post_list, the reader's own) ────────────────────── */

/* THE RAIL BLEEDS. It runs its ground's full width and its own inline padding
   puts the cards back on the measure, so a card the edge cuts runs on into the
   page margin, or below the measure into the inset. Two things say "more"
   there, and neither is measured: the mask goes opaque where a resting card
   stands — the inset — and fades outward over `--sp-rail-fade`, never past the
   ground's edge, so a short margin gets a short fade and no margin at all still
   gets the inset rather than a cut — and every card dims as a whole by
   `--sp-rail-t`, where it stands against the measure — -1 wholly past the
   trailing edge, 0 inside, 1 wholly past the leading edge. A browser with view
   timelines writes `--sp-rail-t` itself; site.js writes the same value for one
   that has none. At either end the fade covers only padding at rest and at
   every snap stop, so the rail at rest shows nothing that needs undoing. */
@property --sp-rail-t {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}
@property --sp-rail-on {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
@property --sp-rail-p {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}
/* A rail measures its bleed against the GROUND it stands on: a band, or the
   strip a band stands directly on — that band is no container, so the rail
   runs to the strip's edge and its fade lands on the strip's own ground. */
.sp-site-band,
.sp-site-strip {
  container-type: inline-size;
}
.sp-site-strip > .sp-site-band-inner > .sp-site-band {
  container-type: normal;
}
.sp-site-rail {
  --sp-rail-margin: calc((100cqw - min(var(--sp-site-measure), 100cqw)) / 2);
  --sp-rail-inset: calc(var(--sp-rail-margin) + var(--sp-site-inset));
  --sp-rail-fade: min(6rem, var(--sp-rail-margin));
  display: flex;
  gap: 1rem;
  /* `overflow-x: auto` clips the block axis too, so the headroom a card's
     hover lift needs is padding here. */
  padding: 0.5rem var(--sp-rail-inset);
  margin-inline: calc(-1 * var(--sp-rail-inset));
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--sp-rail-inset);
  scroll-timeline: --sp-rail inline;
  scrollbar-width: none;
  mask-image: linear-gradient(
    to right,
    transparent calc(var(--sp-rail-margin) - var(--sp-rail-fade)),
    #000 var(--sp-rail-inset),
    #000 calc(100% - var(--sp-rail-inset)),
    transparent calc(100% - var(--sp-rail-margin) + var(--sp-rail-fade))
  );
}
.sp-site-rail::-webkit-scrollbar {
  display: none;
}
.sp-site-rail > * {
  opacity: calc(1 - abs(var(--sp-rail-t)) * 0.7);
}
/* The CARDS are the snap stops, and the trailing tile deliberately is not: it is
   the one child standing there before an island's cards arrive, and a snap
   container re-snaps to its selected target after a layout change — which is
   what would leave a freshly-filled rail standing at its END. */
.sp-site-rail > *:not(.sp-site-rail-end) {
  scroll-snap-align: start;
}
/* The measure, not the scrollport, is a card's viewport: the inset is the same
   length the padding and the snap alignment read. */
@supports (view-timeline-inset: 1px) {
  .sp-site-rail > * {
    view-timeline: --sp-rail-card inline;
    view-timeline-inset: var(--sp-rail-inset);
    animation:
      sp-rail-enter linear backwards,
      sp-rail-leave linear forwards;
    animation-timeline: --sp-rail-card, --sp-rail-card;
    animation-range:
      entry 0% entry 100%,
      exit 0% exit 100%;
  }
  .sp-site-rail-bar {
    animation:
      sp-rail-on linear both,
      sp-rail-p linear both;
    animation-timeline: --sp-rail;
  }
}
@keyframes sp-rail-enter {
  from { --sp-rail-t: -1; }
  to { --sp-rail-t: 0; }
}
@keyframes sp-rail-leave {
  from { --sp-rail-t: 0; }
  to { --sp-rail-t: 1; }
}
/* How far along the rail stands; a rail that fits keeps it at its base of 0. */
@keyframes sp-rail-on {
  from, to { --sp-rail-on: 1; }
}
@keyframes sp-rail-p {
  from { --sp-rail-p: 0; }
  to { --sp-rail-p: 1; }
}
.sp-site-rail-bar {
  position: relative;
  height: 2px;
  margin-top: 0.75rem;
  border-radius: 1px;
  background: var(--sp-band-rule, var(--sp-border));
  opacity: var(--sp-rail-on);
}
.sp-site-rail-bar::after {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: 30%;
  border-radius: 1px;
  background: currentcolor;
  translate: calc(var(--sp-rail-p) * 233.333%) 0;
}
.sp-site-issue-card {
  display: block;
  flex: 0 0 auto;
  width: 10.5rem;
  border-radius: 0.25rem;
}
.sp-site-rail-end {
  display: flex;
  flex: 0 0 auto;
  width: 10.5rem;
  aspect-ratio: 210 / 276;
  align-items: center;
  justify-content: center;
  align-self: start;
  border: 1px dashed var(--sp-border);
  border-radius: 0.25rem;
  padding: 0.75rem;
  text-align: center;
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--sp-muted);
  transition:
    border-color 150ms ease,
    background-color 150ms ease,
    color 150ms ease;
}
@media (hover: hover) {
  .sp-site-rail-end:hover {
    border-color: var(--sp-muted);
    background: var(--sp-muted-bg);
    color: var(--sp-fg);
  }
}
/* The teaser card a rail of entries is made of — the post band's, and the
   reader's own rails. The wrapper is the rail child, so the save toggle beside
   the link stands over the card; the link is the surface. */
.sp-site-rail-card {
  flex: 0 0 auto;
  width: 16rem;
}
.sp-site-card-link {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-width: 0;
  overflow: hidden;
  border: 1px solid var(--sp-border);
  border-radius: 0.75rem;
  background: var(--sp-surface);
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  transition: box-shadow 200ms ease;
}
@media (hover: hover) {
  .sp-site-card-link:hover {
    box-shadow: 0 10px 24px -8px rgb(0 0 0 / 0.3);
  }
}
.sp-site-card-image {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  background: var(--sp-muted-bg);
}
.sp-site-card-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 0.375rem;
  min-width: 0;
  padding: 0.875rem;
}
.sp-site-card-body > .sp-site-card-title {
  margin-top: 0;
}
.sp-site-card-body > .sp-site-card-meta {
  margin-top: auto;
  padding-top: 0.5rem;
}
/* The toggle stands over the picture, so it brings its own ground. */
.sp-site-rail-card > .sp-site-save {
  background: color-mix(in srgb, var(--sp-surface) 85%, transparent);
}
.sp-site-wide-card {
  display: flex;
  flex: 0 0 auto;
  gap: 1rem;
  width: min(26rem, 80vw);
  border: 1px solid var(--sp-border);
  border-radius: 0.75rem;
  background: var(--sp-surface);
  padding: 0.875rem;
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  transition: box-shadow 200ms ease;
}
@media (hover: hover) {
  .sp-site-wide-card:hover {
    box-shadow: 0 10px 24px -8px rgb(0 0 0 / 0.3);
  }
}
/* Here the cover stands alone beside a blurb, so it keeps the picture's OWN
   shape — the portrait frame belongs to the rail, where a row of covers lines
   up. The ratio is only what reserves the box until the image has loaded. */
.sp-site-wide-card .sp-site-cover {
  width: 8rem;
  flex-shrink: 0;
  align-self: start;
  aspect-ratio: auto 210 / 276;
  object-fit: contain;
}
.sp-site-wide-body {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  min-width: 0;
}

/* ── footer ──────────────────────────────────────────────────────────────── */

.sp-site-footer {
  border-top: 1px solid var(--sp-border);
  font-family: var(--sp-sans, var(--font-sans, ui-sans-serif, system-ui, sans-serif));
  font-size: 0.875rem;
  color: var(--sp-muted);
}
/* A run of prose reads as ONE flow, like every other area: the footer's columns
   are the titled lists an author placed, never an arrangement imposed on
   whatever stands here. */
.sp-site-footer .sp-site-prose h2,
.sp-site-footer .sp-site-prose h3 {
  margin: 0 0 0.5rem;
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}
.sp-site-footer .sp-site-prose ul,
.sp-site-footer .sp-site-prose ol {
  margin: 0 0 1.25rem;
  padding: 0;
  list-style: none;
}
.sp-site-footer .sp-site-prose li {
  margin: 0 0 0.375rem;
}
.sp-site-footer .sp-site-prose a {
  color: inherit;
  text-decoration: none;
}
.sp-site-footer .sp-site-prose a:hover {
  color: var(--sp-fg);
}

/* ── authored navigation ─────────────────────────────────────────────────────
 * The rows the site's chrome authors. One markup for the baked page and for the
 * SPA sidebar, sized to sit under the reader's own fixed destinations. */

.sp-site-nav {
  display: flex;
  flex-direction: column;
  gap: 1px;
  font-family: var(--sp-sans, var(--font-sans, ui-sans-serif, system-ui, sans-serif));
  font-size: 0.875rem;
  font-weight: 500;
}
.sp-site-nav-row {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.625rem;
  border-radius: 0.375rem;
  color: inherit;
  text-decoration: none;
}
.sp-site-nav-row > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sp-site-nav-row .sp-site-icon {
  width: 1rem;
  height: 1rem;
}
a.sp-site-nav-row:hover {
  background: var(--sp-muted-bg);
}
/* WHERE THE READER STANDS. The shell marks the row whose address names the
   route it recognizes; a baked page carries no mark, having no route. */
.sp-site-nav-row[aria-current="page"] {
  background: var(--sp-muted-bg);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--sp-primary);
}
/* A live count the shell hangs on a row it recognizes. */
.sp-site-nav-badge {
  margin-inline-start: auto;
  padding: 0 0.375rem;
  border: 1px solid var(--sp-border);
  border-radius: 9999px;
  font-size: 0.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--sp-muted);
}
