:root {
  color-scheme: light;
  --wave-gutter: clamp(8.75rem, 16vw, 13.5rem);
  --sky: #5aa8dc;
  --ice: #d7fbff;
  --cyan: #73f4ff;
  --blue: #365fc6;
  --violet: #6d2c95;
  --lavender: #b9a8ff;
  --pink: #ffd6f5;
  --magenta: #ff00e6; 
  --ink: #271452;
  --banana: #fff9b1;
  /* Text-bearing panels are fully opaque on purpose: the striped
     index-window behind them otherwise bleeds through and makes prose hard
     to read. Keep any translucency to decorative surfaces only. */
  --panel: #d8f7ff;
  --panel-strong: #e7fcff;
  --line: rgba(231, 252, 255, 0.9);
  --shadow: rgba(65, 24, 127, 0.28);
  /* Link states are tokens rather than literals because a legible accent
     depends on what the link sits on, and links here inherit their colour
     from whatever panel holds them. These two values clear 4.5:1 on every
     panel surface (--panel, --panel-strong, --ice, --pink, white); the two
     surfaces they don't clear — the mid-blue page background under the
     footer, and the violet link-rail — re-point them further down. */
  --link-hover: #a3006d;
  --link-active: #6d2c95;
  /* One near-black focus ring for the whole site. Every ring here is drawn
     outside its element (outline-offset), so what it needs contrast against
     is the page behind the control, not the control's own fill — and that is
     light or mid-tone everywhere, including behind the dark link-rail.
     Measured 5.2:1 (rail) to 16.8:1 (panels). */
  --focus-ring: #1a0d33;
  font-family:  Unique, "Trebuchet MS", "Century Gothic", Arial, sans-serif;
}

@font-face {
  font-family: Unique;
  src: url("fonts/Unique.ttf") format("truetype");
  font-display: swap;
}

@font-face {
  font-family: Atkinson-Regular;
  src: url('fonts/AtkinsonHyperlegible-Regular.ttf') format('truetype');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: Atkinson-Italic;
  src: url('fonts/AtkinsonHyperlegible-Italic.ttf') format('truetype');
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: Atkinson-Bold;
  src: url('fonts/AtkinsonHyperlegible-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: Atkinson-BoldItalic;
  src: url('fonts/AtkinsonHyperlegible-BoldItalic.ttf') format('truetype');
  font-weight: 700;
  font-style: italic;
  font-display: swap;
}

* {
  box-sizing: border-box;
}

html {
  background: linear-gradient(155deg, #65b8e6 0%, #519ed5 48%, #8abbe9 100%);
  /* Firefox's elastic overscroll bounce (trackpad scroll past the top/bottom)
     reveals a strip of raw browser canvas beyond the rendered document that
     CSS backgrounds can't paint into, showing white. Disabling the bounce
     keeps scrolling clamped at the document edge instead. */
  overscroll-behavior-y: none;
}

body {
  min-height: 100vh;
  margin: 0;
  color: var(--ink);
  background: linear-gradient(155deg, #65b8e6 0%, #519ed5 48%, #8abbe9 100%);
  overflow-x: hidden;
  overscroll-behavior-y: none;
}

/*This does the hyperlink styling*/ 
a {
  color: inherit;
  text-decoration: underline;
}

a:hover {
  color: var(--link-hover);
}

a:active {
  color: var(--link-active);
}

/* :focus-visible, not :focus — :focus also fires on mouse-down, which made
   every click flash the hover colour. The rest of the file already uses
   :focus-visible for its components. */
a:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}
/*end*/

.page-shell {
  position: relative;
  min-height: 100vh;
  padding: clamp(1rem, 2.6vw, 2.4rem);
  padding-left: var(--wave-gutter);
  isolation: isolate;
  /* The wave-field, clouds, and the feature bunny are all meant to bleed to
     (or off) the edges. Clip horizontally so those bleeds never become a
     horizontal scrollbar. overflow-x on <body> alone doesn't hold because
     <html> stays visible; clip (not hidden) keeps the sticky link-rail working
     and doesn't force a vertical scroll container. */
  overflow-x: clip;
}

.page-shell::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -4;
  pointer-events: none;
  /* Forces its own compositor layer so Firefox doesn't clip/drop it during
     the elastic overscroll (trackpad rubber-band) bounce animation. */
  transform: translateZ(0);
  will-change: transform;
  background-image:
    url("images/wirenook/volumetric-clouds.png"),
    url("images/wirenook/volumetric-clouds.png");
  background-position:
    center 8vh,
    58% 78vh;
  background-repeat:
    repeat-y,
    repeat-y;
  background-size:
    min(118rem, 115vw) auto,
    min(92rem, 96vw) auto;
  opacity: .5;
}

/* 
This creates the left hand wave=field element as seen in the design, this has been revised on June 8th, 2026 
in order to accommodate the new wave-gutter variable which allows for more dynamic spacing of the wave-field on smaller screens. 
The wave-field is created using two layered SVG backgrounds that repeat to form a pattern, with a mask applied to create a fading effect on the right edge. 
background-image uses data URIs to embed the SVG directly in the CSS, which allows for easy customization of the wave pattern's colors and opacity.
The drop-shadow filter adds depth to the waves, and pointer-events are disabled to allow interactions with elements behind it.
*/

.wave-field {
  position: fixed;
  inset: 0 auto 0 0;
  width: clamp(7rem, 12vw, 27.5rem);
  height: 100vh;
  transform: translateZ(0);
  will-change: transform;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14'%3E%3Cpolygon points='7,3 8.1,5.9 11,7 8.1,8.1 7,11 5.9,8.1 3,7 5.9,5.9' fill='rgba(139,19,214,0.75)'/%3E%3C/svg%3E"),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14'%3E%3Cpolygon points='7,3 8.1,5.9 11,7 8.1,8.1 7,11 5.9,8.1 3,7 5.9,5.9' fill='rgba(221,200,235,0.34)'/%3E%3C/svg%3E");
  background-size: 14px 14px, 14px 14px;
  background-position: 0 0, 7px 7px;
  background-repeat: repeat;
  z-index: -1;
  filter: drop-shadow(0 0 1px var(--cyan)); 
  opacity: 0.98;
  pointer-events: none;
  mask-image: linear-gradient(
    to right,
    black 40%,
    black 2%,
    transparent 100%
  );
}

/* Each cloud stacks three independent motions without fighting over one
   property: scroll parallax rides the `translate` property (driven by the
   --scrolly counter script.js keeps on <html>), the ambient drift keyframes
   own `transform`, and the static per-layer flips/turns live on the
   individual `scale`/`rotate` properties. Negative animation-delays and
   co-prime durations keep the five layers from ever breathing in sync. */
.cloud-field {
  position: fixed;
  z-index: -2;
  width: min(34rem, 55vw);
  height: auto;
  pointer-events: none;
  --cloud-o: 0.2;
  --drift-x: 2.6rem;
  --drift-y: 0.9rem;
  --parallax: 0.05;
  opacity: var(--cloud-o);
  translate: 0 calc(var(--scrolly, 0) * var(--parallax) * -1px);
  transform: translateZ(0);
  will-change: transform, translate, opacity;
  animation: cloudDrift 90s ease-in-out infinite;
}

.cloud-field-one {
  left: 6vw;
  bottom: -2rem;
  width: min(46rem, 58vw);
  --cloud-o: 0.18;
  --parallax: 0.11;
  --drift-x: 3.4rem;
  animation-duration: 110s;
}

.cloud-field-two {
  right: -12rem;
  bottom: 1rem;
  width: min(46rem, 58vw);
  scale: -1 1;
  --cloud-o: 0.22;
  --parallax: 0.085;
  --drift-x: 3.1rem;
  animation-duration: 133s;
  animation-delay: -47s;
}

.cloud-field-three {
  left: 28vw;
  top: 12vh;
  width: min(30rem, 42vw);
  rotate: -8deg;
  --cloud-o: 0.18;
  --parallax: 0.06;
  animation-duration: 97s;
  animation-delay: -71s;
}

.cloud-field-four {
  left: 18vw;
  bottom: 18vh;
  width: min(42rem, 50vw);
  --cloud-o: 0.13;
  --parallax: 0.075;
  animation-duration: 149s;
  animation-delay: -12s;
}

.cloud-field-five {
  right: 4vw;
  top: 18vh;
  width: min(42rem, 50vw);
  scale: -1 1;
  --cloud-o: 0.13;
  --parallax: 0.045;
  animation-duration: 121s;
  animation-delay: -88s;
}

/* A slow wandering loop rather than a metronome bob: the waypoints trace a
   lopsided ellipse while opacity swells and thins, so clouds read as forming
   and dissipating as they float through. Opacity bottoms out well above zero
   to avoid a popping in/out feel. */
@keyframes cloudDrift {

  0%,
  100% {
    transform: translateZ(0) translate3d(calc(var(--drift-x) * -1), calc(var(--drift-y) * 0.4), 0);
    opacity: calc(var(--cloud-o) * 0.45);
  }

  30% {
    transform: translateZ(0) translate3d(calc(var(--drift-x) * 0.2), calc(var(--drift-y) * -1), 0);
    opacity: var(--cloud-o);
  }

  55% {
    transform: translateZ(0) translate3d(var(--drift-x), calc(var(--drift-y) * 0.3), 0);
    opacity: calc(var(--cloud-o) * 0.85);
  }

  80% {
    transform: translateZ(0) translate3d(calc(var(--drift-x) * -0.25), var(--drift-y), 0);
    opacity: calc(var(--cloud-o) * 0.95);
  }
}

.volume-cloud {
  mix-blend-mode: soft-light;
}

.sigil-lines {
  position: fixed;
  left: 6vw;
  bottom: -4rem;
  width: 28rem;
  max-width: 62vw;
  aspect-ratio: 1;
  z-index: -1;
  border: 3px solid rgba(232, 246, 255, 0.42);
  clip-path: polygon(50% 0, 82% 86%, 20% 72%);
  transform: translateZ(0) rotate(12deg);
  will-change: transform;
}

/* Technical-drawing rulers along the top and right viewport edges, drawn by
   script.js. Purely decorative: fixed so it never takes layout space, and it
   sits with the other background layers behind all content. Same z-index as
   the wave-field but later in the DOM, so it paints above the clouds and
   waves without ever reaching the foreground. */
.tick-rulers {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
  transform: translateZ(0);
  will-change: transform;
}

.site-header {
  text-align: center;
  margin-bottom: clamp(0.85rem, 1.5vw, 1.45rem);
}

.banner-image {
  display: block;
  width: clamp(24rem, 45vw, 60rem);
  max-width: 100%;
  height: auto;
  margin: 0 auto;
  object-fit: contain;
}

.site-header {
  font-size: clamp(2rem, 5vw, 3.5rem);
}

.layout-grid {
  display: grid;
  grid-template-columns: minmax(8.5rem, 13rem) minmax(20rem, 1fr) minmax(15rem, 27rem);
  gap: clamp(1rem, 2vw, 2rem);
  align-items: stretch;
  max-width: 92rem;
  margin: 0 auto;
}

.link-rail {
  /* How far each charm hangs past its button's outer edge. Shared with the
     rail's padding below the stacking breakpoint, where the rail spans the
     page and the overhang would otherwise land in the shell's gutter (or off
     the screen entirely, getting clipped by .page-shell). */
  --charm-overhang: 1.45rem;
  position: sticky;
  top: clamp(0.75rem, 2vw, 1.5rem);
  align-self: start;
  display: grid;
  align-content: start;
  gap: clamp(0.75rem, 1.5vw, 1.1rem);
  padding-top: clamp(0.2rem, 1vw, 0.65rem);
}

.rail-link {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 4.2rem;
  padding: 0.8rem 0.75rem 0.8rem 3.1rem;
  border: 2px double var(--line);
  /* The gradient used to fade out to near-transparent (alpha 0.08) on the
     right, which left the label sitting on raw page background at 1.8:1 —
     worst for the even-numbered rails, which right-align their text straight
     onto that end. It still travels violet -> blue, just dark enough the
     whole way that --ice stays above 4.5:1. */
  background: linear-gradient(100deg, rgba(62, 69, 196, 0.86), rgba(34, 88, 180, 0.85));
  color: var(--ice);
  /* Light-on-dark surface: the page-wide accents are dark and would vanish
     here, so the rail is where the yellow hover still belongs — it reads at
     6.7:1 against the violet. */
  --link-hover: var(--banana);
  --link-active: var(--cyan);
  text-decoration: none;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.36), 0 0.9rem 2rem rgba(74, 37, 132, 0.15);
  transition: transform 180ms ease, background 180ms ease;
}

.rail-link:nth-child(even) {
  justify-content: flex-end;
  padding: 0.8rem 3.1rem 0.8rem 0.75rem;
  text-align: right;
}

.rail-link:hover,
.rail-link:focus-visible {
  transform: translateX(0.35rem);
  background: linear-gradient(100deg, rgba(89, 49, 184, 0.92), rgba(40, 86, 192, 0.9));
}

/* Focus-only. The old rule handed the same --pink outline to :hover, where
   it read as a mouse affordance while only managing 1.7-2.3:1 against the
   surfaces behind it. */
.rail-link:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
}

.link-charm {
  position: absolute;
  left: calc(var(--charm-overhang) * -1);
  width: 3rem;
  height: 3.6rem;
  object-fit: contain;
  pointer-events: none;
}

.rail-link:nth-child(even) .link-charm {
  right: calc(var(--charm-overhang) * -1);
  left: auto;
  transform: scaleX(-1);
}

.index-window {
  min-height: 39rem;
  border: 5px double var(--line);
  background:
    repeating-linear-gradient(172deg, rgba(84, 80, 188, 0.2) 0 7px, rgba(111, 244, 255, 0.24) 8px 13px),
    linear-gradient(160deg, rgba(78, 171, 216, 0.72), rgba(191, 171, 255, 0.74));
  box-shadow: inset 0 0 4rem rgba(215, 251, 255, 0.18), 0 1.2rem 3.4rem var(--shadow);
  padding: clamp(1rem, 2vw, 1.5rem);
}

.window-topline {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: center;
  margin-bottom: 1rem;
}

h2,
h3,
p {
  margin-top: 0;
}

h2 {
  font-size: clamp(1.4rem, 3vw, 2.2rem);
}

/* Window headings sit directly on the index-window's stripes. At 4.9:1 the
   ratio passes for text this size, but that number is measured against an
   average the eye never sees: the stripes put ~1400 distinct colours behind
   the glyphs, in a condensed display face. The same flat chip the topline
   links and the post date use takes it to a single background colour at
   15.1:1. Scoped to .window-topline so ## headings in prose stay plain. */
.window-topline h2 {
  margin-bottom: 0;
  padding: 0.15em 0.45em;
  border: 1px solid var(--line);
  background: var(--panel-strong);
}

.status-light {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--line);
  background: var(--panel-strong);
}

.topline-links {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
}

.blink-dot {
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--cyan);
  box-shadow: 0 0 0.75rem var(--cyan);
  animation: blinkDot 1.1s steps(2, end) infinite;
}

.content-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.8rem;
}

.intro-panel,
.link-card,
.socials-panel {
  border: 1px solid rgba(255, 255, 255, 0.72);
  background: var(--panel);
}

.socials-panel {
  padding: clamp(1rem, 2vw, 1.4rem);
}

.socials-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
}

.social-button {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.45rem;
  border: 1px solid var(--line);
  background: var(--panel-strong);
  color: var(--ink);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  box-shadow: inset 0 0 0.6rem rgba(115, 244, 255, 0.18), 0 0.35rem 0.85rem rgba(65, 24, 127, 0.12);
  transition: transform 160ms ease, background 160ms ease, box-shadow 160ms ease;
}

.social-button:hover,
.social-button:focus-visible {
  transform: translateY(-1px);
  background: var(--ice);
}

.social-button:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.social-button::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 0.45rem);
  left: 50%;
  transform: translateX(-50%);
  background: var(--ink);
  color: #e8f9ff;
  padding: 0.25rem 0.55rem;
  border-radius: 4px;
  font-size: 0.8rem;
  /* max-content keeps a short handle on one line the way nowrap did, while
     the cap stops "@cassidyco.bsky.social" from growing wider than a phone
     screen and being clipped away at the shell's edge. */
  width: max-content;
  max-width: min(16rem, 70vw);
  pointer-events: none;
  opacity: 0;
  transition: opacity 160ms ease;
  z-index: 10;
}

.social-button:hover::after,
.social-button:focus-visible::after {
  opacity: 1;
}

.social-icon {
  flex: 0 0 auto;
  width: 1.6rem;
  height: 1.6rem;
  color: var(--violet);
}

.social-button[data-copy].is-copied {
  background: var(--pink);
}

.intro-panel {
  grid-column: 1 / -1;
  /* Hug the poem's longest line rather than spanning the whole window;
     fit-content still allows wrapping when the column gets narrow. */
  width: fit-content;
  justify-self: center;
  padding: clamp(1rem, 2vw, 1.4rem);
}

.intro-panel p {
  max-width: 54ch;
  margin-bottom: 0;
  line-height: 1.55;
  font-family: Atkinson-Regular, arial, sans-serif;
}

.link-card {
  min-height: 7.5rem;
  padding: 1rem;
  display: grid;
  align-content: space-between;
}

.link-card span {
  color: rgba(39, 20, 82, 0.72);
}

.link-card a {
  font-weight: 700;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.22em;
  /* Long bare URLs have no natural break points; wrap them rather than
     letting them clip out of the card when the column runs narrow. */
  overflow-wrap: anywhere;
}

.badge-card {
  align-content: start;
  gap: 0.5rem;
  padding: 0.6rem;
  min-height: 0;
  /* The badges wrap on this card's width, not the window's. The card sits
     four nested layout decisions below the viewport — wave gutter, the
     layout-grid's middle track, the index window's padding, the content
     grid's two columns — so no @media breakpoint can honestly name the
     viewport width at which two 88px badges stop fitting here. The 86rem
     breakpoint guessed, and guessed ~40px low, which is why the badges
     spilled out of the card between 1378px and 1416px.

     inline-size containment also stops the badge block from setting this
     card's own width, so an oversized badge grid can no longer inflate the
     card and push its siblings around. */
  container-type: inline-size;
}

.badge-grid {
  /* Badges are pixel art at their native 88x31 and look mushy at any other
     size, so the grid drops to a single column rather than scaling them. */
  --badge-width: 88px;
  display: grid;
  grid-template-columns: repeat(2, var(--badge-width));
  gap: 2px;
  justify-content: center;
  justify-items: center;
  align-items: center;
}

/* 178px is two badges plus the gap between them. It has to be spelled out —
   container query conditions cannot read custom properties — but it is a
   fact about this card's own contents rather than a guess about the window. */
@container (width < 178px) {
  .badge-grid {
    grid-template-columns: var(--badge-width);
  }
}

.badge-grid a {
  display: block;
  line-height: 0;
}

.badge-grid img {
  display: block;
  /* The AGPL badge is wider than one column; let it scale in the single
     column layout instead of hanging off the card. */
  max-width: 100%;
  height: auto;
  image-rendering: pixelated;
}

.badge-grid .badge-wide {
  grid-column: 1 / -1;
}

.badge-note {
  margin: 0;
  font-size: 0.78rem;
  text-align: center;
  color: rgba(39, 20, 82, 0.72);
}

.right-stack {
  display: grid;
  align-content: center;
  gap: 1rem;
}

.webring,
.neolink-panel,
.death-clock,
.feature-object {
  position: relative;
  border: 2px solid rgba(232, 246, 255, 0.75);
  background: rgba(216, 247, 255, 0.24);
  padding: 1rem;
}

.neolink-panel {
  display: grid;
  justify-items: center;
}

.neolink-panel iframe {
  max-width: 100%;
}

.death-clock {
  display: grid;
  gap: 0.65rem;
  align-content: start;
}

.death-clock-topline {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
}

.death-clock-title {
  margin: 0;
  font-size: 1.05rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.death-clock-status {
  font-size: 0.82rem;
}

/* The index window's dot announces "Online"; this one is a heartbeat. */
.death-clock .blink-dot {
  background: var(--magenta);
  box-shadow: 0 0 0.75rem var(--magenta);
  animation: heartbeatDot 1.1s ease-in-out infinite;
}

@keyframes heartbeatDot {

  0%,
  48%,
  100% {
    transform: scale(1);
    opacity: 0.85;
  }

  12% {
    transform: scale(1.45);
    opacity: 1;
  }

  30% {
    transform: scale(1.25);
    opacity: 1;
  }
}

.death-clock-readout {
  display: flex;
  gap: 0.3rem;
  margin: 0;
}

.death-cell {
  flex: 1 1 0;
  display: grid;
  place-items: center;
  padding: 0.5rem 0.15rem;
  border: 1px solid var(--line);
  background: var(--panel-strong);
  box-shadow: inset 0 0 0.6rem rgba(115, 244, 255, 0.18);
}

.death-num {
  font-family: Atkinson-Bold, arial, sans-serif;
  font-weight: 700;
  font-size: 1.1rem;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.death-clock-bar {
  height: 0.6rem;
  border: 1px solid var(--line);
  background: var(--panel-strong);
  overflow: hidden;
}

.death-clock-fill {
  display: block;
  width: 0;
  height: 100%;
  background: linear-gradient(90deg, var(--cyan), var(--blue), var(--violet));
}

.death-clock-note {
  margin: 0;
  font-size: 0.82rem;
  font-family: Atkinson-Italic, arial, sans-serif;
  font-style: italic;
  color: rgba(39, 20, 82, 0.8);
}

/* The bonus round: past the estimate the heartbeat settles into a steady
   gold glow and the bar overflows into candy stripes. Mostly for people who
   mess with the clock (?dies=2020-01-01) — or for Cassidy in 2083. */
.death-clock.is-overtime {
  border-color: var(--banana);
  box-shadow: 0 0 1.1rem rgba(255, 0, 230, 0.35);
}

.death-clock.is-overtime .blink-dot {
  animation: none;
  background: var(--banana);
  box-shadow: 0 0 0.75rem var(--banana);
}

.death-clock.is-overtime .death-clock-fill {
  background: repeating-linear-gradient(135deg, var(--magenta) 0 8px, var(--banana) 8px 16px);
}

.portal-image {
  display: block;
  width: min(20rem, 100%);
  aspect-ratio: 900 / 620;
  height: auto;
  margin: 0 auto 1rem;
  object-fit: contain;
  animation: hoverPortal 6s ease-in-out infinite;
}

.spinner {
  position: relative;
  width: 12rem;
  height: 12rem;
  margin: 1rem auto;
  border: 1px dashed rgba(39, 20, 82, 0.4);
  border-radius: 50%;
}

.spinner a {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 5.2rem;
  min-height: 2.05rem;
  padding: 0.35rem 0.45rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-strong);
  box-shadow: inset 0 0 0.75rem rgba(115, 244, 255, 0.3), 0 0.45rem 1rem rgba(65, 24, 127, 0.12);
  text-align: center;
  text-decoration: none;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 700ms cubic-bezier(0.2, 0.9, 0.25, 1.2);
  transform: translate(-50%, -50%) rotate(calc(var(--i) * (360deg / var(--count, 4)) + var(--spin, 0deg))) translateY(-4.35rem) rotate(calc((var(--i) * (-360deg / var(--count, 4))) - var(--spin, 0deg)));
}

.spinner a img {
  display: block;
  max-width: 100%;
  height: auto;
  border-radius: 999px;
}

.spin-controls {
  display: flex;
  justify-content: center;
  align-items: stretch;
  gap: 0.6rem;
}

.spin-button {
  position: relative;
  display: block;
  min-height: 2.7rem;
  padding: 0 1.4rem;
  border: 2px solid var(--violet);
  border-radius: 999px;
  background: var(--ice);
  color: var(--ink);
  font: inherit;
  font-weight: 700;
  text-decoration: none;
  cursor: pointer;
  box-shadow: inset 0 0 0.85rem rgba(115, 244, 255, 0.35), 0 0.45rem 1.1rem rgba(65, 24, 127, 0.13);
}

.spin-step {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 2.7rem;
  padding: 0;
}

/* Border-drawn chevron so we don't depend on the display font having
   nice arrow glyphs. */
.spin-step-glyph {
  display: block;
  width: 0.5em;
  height: 0.5em;
  border-right: 0.16em solid currentColor;
  border-bottom: 0.16em solid currentColor;
}

.spin-step-glyph-prev {
  transform: translateX(15%) rotate(135deg);
}

.spin-step-glyph-next {
  transform: translateX(-15%) rotate(-45deg);
}

/* The chevrons are <a>, SPIN is a <button>; setting colour here rather than
   letting a:hover reach only the anchors keeps all three in step. */
.spin-button:hover,
.spin-button:focus-visible {
  background: var(--pink);
  color: var(--link-hover);
}

.spin-button:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 3px;
}

.feature-object {
  min-height: clamp(13rem, 23vw, 18rem);
  overflow: visible;
  border: 0;
  background: transparent;
  padding: 0.75rem;
  pointer-events: none;
  display: grid;
  place-items: end center;
}

.feature-image {
  display: block;
  position: relative;
  right: auto;
  bottom: auto;
  width: min(118%, 31rem);
  max-width: none;
  height: auto;
  margin: -3.2rem -1.2rem -1.4rem auto;
  opacity: 0.88;
  z-index: 0;
}

@keyframes hoverPortal {

  0%,
  100% {
    transform: translateY(0) rotate(2deg);
  }

  50% {
    transform: translateY(-0.55rem) rotate(-1deg);
  }
}

@keyframes blinkDot {

  0%,
  58% {
    opacity: 1;
  }

  59%,
  100% {
    opacity: 0.22;
  }
}

.site-footer {
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
  padding: 0.75rem 0;
  text-align: center;
  font-size: 0.82rem;
  border-top: 1px solid rgba(231, 252, 255, 0.45);
  /* The only text on the site that sits straight on the page gradient rather
     than on a panel. At 0.7 it managed 3.5:1 over the gradient's dark stop;
     0.9 clears 4.5:1 across the whole sweep and still reads as muted. */
  color: rgba(39, 20, 82, 0.9);
  /* Same reason the accents are re-pointed: against mid-blue, the panel-tuned
     magenta drops to 2.6:1. These are the darkest members of the same family
     that clear 4.5:1 on every stop of the gradient. */
  --link-hover: #50006b;
  --link-active: #2e0a5e;
}

.site-footer a {
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

/*
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%%%%%%%%%%%%%%@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@#-..............=%@@@@@@@@@@@
@@@@#+=======+#@@+==+#%*===##+==+%@*========#@#===*%@@@@@@@@@@@@@@@@@%+-+%@@@@@@@@@@@@#=-*@@@@@@@@@@
@@@@#- ..#*: .-@@:. -*#-  .**: .:%@-. :*%...*@*.  =%@@@@@@@@@@@@@@@@#--#%*:..........=#%+:+%@@@@@@@@
@@@@#- ..#=:.:=@@:. -*%+...==...=%@-. :*%%#*#@*.  =%@@@@@@@@@@@@@@@=:+%#=:............-*%#-:#%@@@@@@
@@@@#- ..-+*%@@@@:. -*%#+-.--:-+*%@-. .:=#%@@@*.  =%@@@@@@@@@@@@%#--#%*-...............:=#%+:+%@@@@@
@@@@#- ..#@@@@@@@:. -*#-  .**: ..%@-. :*@%*=*@*.  =%%*=+@@@@@@@%*:+%%=:..................-*@%--#@@@@
@@@@#- ..#@@@@@@@:. -*#-  .**: ..%@-. :=*...*@*.  -=:..=@@@@@@@*-+@#-:....................:+%#-=%@@@
@@@@%*===%@@@@@@@===+#%*==+##+==+%@+========#@#========*@@@@@@@#=-@#-.....................:+%*-+@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%*.#%=:....................:%%==#@@@@
@@@@#+======+#@@@%*+=====+#%@%#+=====+*%%+====+#%*=======+%@@@@@#-=%*-...................:=@#:+%@@@@
@@@@*...=*-. :+@@-. .-*-  .*@*: .:*=. .-%##:. :+%-..:++: .:#@@@@%+.%%=:..................-*@+-*@@@@@
@@@@#=+%%-...-%@@-. .=#-  .*@+. .:#+: .-@@@-. :+%+=#%+:...*%@@@@@#.*%*-.................:-%@:+%@@@@@
@@@@@@%#:. .=%@@@-. .=#-  .*@+. .:#+: .-@@@-. :+@@@%+: .:*%@@@@@@%=-#%*=-:............:-+#%*:*%@@@@@
@@@@@%#.. .+##*%@-. .=#-  .*@+. .:#+: .-@@@-. :+@@%-. .-#%##%@@@@%#:-+#%%%+-::....::=*%%%*=:=#@@@@@@
@@@@%*.  .*=..:*@-. .=#-  .*@*. .:#+: .-@@@-. :+@%-. .=*-..-#@@@@@@%#*=--+#%%#+==*#%%*=--+#%%@@@@@@@
@@@@#==========#@%+=======*%@%*=======+#@@@+===*%+=========+%@@@@@@@@@@%#+--=*#%%*+--=*%@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%+:..-*%@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%%@@@@@@@@@@@@@@@@@@@

!!post your asci here to troll cassidy!!
*/

.license-main {
  max-width: 56rem;
  margin: 0 auto;
}

.archive-intro {
  border: 1px solid rgba(255, 255, 255, 0.72);
  background: var(--panel);
  padding: clamp(1rem, 2vw, 1.4rem);
  margin-bottom: 1rem;
  line-height: 1.55;
  font-family: Atkinson-Regular, arial, sans-serif;
}

.archive-intro > :last-child {
  margin-bottom: 0;
}

.license-list {
  display: grid;
  /* minmax(0, 1fr) rather than the implicit `auto`: an auto track is floored
     at its content's min-content width, so a single unbreakable path in a
     .license-files <code> would push the whole column wider than the window
     it sits in. */
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
}

.license-entry {
  border: 1px solid rgba(255, 255, 255, 0.72);
  background: var(--panel);
  padding: clamp(1rem, 2vw, 1.4rem);
}

.license-entry h3 {
  margin-bottom: 0.4rem;
  font-size: 1.15rem;
}

.license-entry p {
  margin-bottom: 0.5rem;
  line-height: 1.55;
  font-family: Atkinson-Regular, arial, sans-serif;
}

.license-entry p:last-child {
  margin-bottom: 0;
}

/* Prose rendered from Markdown — license blurbs, blog posts, the press-kit
   notes — routinely carries a bare URL, which is a single token as wide as
   it is long. `anywhere` rather than `break-word` so it also stops setting
   the block's min-content width, which is what pushes a grid track wider
   than the container holding it. */
.license-entry p,
.license-callout p,
.press-kit-note p,
.archive-intro,
.entry-body,
.post-body {
  overflow-wrap: anywhere;
}

/* Fenced code blocks carry pre-formatted lines that must not be re-wrapped --
   the chat export in the "Prove It" post is a box-drawing table, and wrapping
   it would shred the alignment that makes it a table at all. So the long line
   has to go somewhere, and `white-space: pre` with the UA's default
   `overflow: visible` sends it straight out of the panel: at every width it
   spilled ~2500px past the column, then got swallowed by .page-shell's
   `overflow-x: clip`, which made the far side of the table unreachable rather
   than merely ugly. `overflow-x: auto` turns that spill into a scroll region
   the reader can actually pan. Hugo already emits `tabindex="0"` on <pre> for
   exactly this, so the region is keyboard-scrollable once it exists. */
.post-body pre,
.entry-body pre {
  max-width: 100%;
  overflow-x: auto;
  margin: 1.4rem 0;
  border: 1px solid var(--line);
  background: var(--panel-strong);
  padding: 0.8rem 0.9rem;
  font-size: 0.86rem;
  line-height: 1.45;
}

/* The block already draws the panel; a nested <code> must not draw a second
   one on top of it, and it inherits the smaller font from the <pre>. */
.post-body pre code,
.entry-body pre code {
  background: none;
  padding: 0;
  font-size: inherit;
}

.license-files {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  color: rgba(39, 20, 82, 0.65);
  font-size: 0.88rem;
}

.license-files code {
  background: rgba(255, 255, 255, 0.4);
  padding: 0.1em 0.35em;
  border-radius: 3px;
  /* Paths like fonts/AtkinsonHyperlegible-BoldItalic.ttf are one long token
     with no natural break point. */
  overflow-wrap: anywhere;
}

.gallery-section {
  margin-top: 1.4rem;
  border: 1px solid rgba(255, 255, 255, 0.72);
  background: var(--panel);
  padding: clamp(1rem, 2vw, 1.4rem);
}

.gallery-section h3 {
  margin-bottom: 0.4rem;
  font-size: 1.15rem;
}

.gallery-section > p {
  margin-bottom: 0.5rem;
  line-height: 1.55;
}

.gallery-license {
  font-size: 0.72rem;
  vertical-align: middle;
  border: 1px solid var(--line);
  background: var(--panel-strong);
  padding: 0.15em 0.5em;
  border-radius: 3px;
  white-space: nowrap;
}

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: 0.75rem;
  margin-top: 0.9rem;
}

.gallery-item {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 0.35rem;
  text-align: center;
  padding: 0.6rem;
  border: 1px solid rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.28);
}

.gallery-item img {
  max-width: 100%;
  height: 7rem;
  object-fit: contain;
  /* Checkerboard so transparent art reads against the panel. */
  background:
    repeating-conic-gradient(rgba(255, 255, 255, 0.55) 0% 25%, rgba(215, 251, 255, 0.35) 0% 50%)
    0 0 / 1rem 1rem;
}

.gallery-item figcaption {
  font-size: 0.78rem;
  color: rgba(39, 20, 82, 0.65);
  overflow-wrap: anywhere;
}

.gallery-item code {
  background: rgba(255, 255, 255, 0.4);
  padding: 0.1em 0.35em;
  border-radius: 3px;
}

/* A file name is one unbreakable token, and a centred flex item is sized to
   fit-content — which bottoms out at min-content, not at the cell. Without a
   break opportunity the name sets the card's floor and hangs out of the grid
   cell at every width where the name is longer than the column. */
.gallery-item > * {
  max-width: 100%;
}

.gallery-item a,
.gallery-item code {
  overflow-wrap: anywhere;
}

/* Cards with no preview (an offsite source file) still have to hold a grid
   cell the same size as the ones carrying a 7rem image. */
.gallery-item-source {
  min-height: 9.6rem;
}

.gallery-note {
  display: block;
  margin-top: 0.3rem;
  font-size: 0.72rem;
  line-height: 1.4;
  /* 0.55 gave 3.7:1 on --panel; 0.7 clears 4.5:1 at this size. */
  color: rgba(39, 20, 82, 0.7);
}

.gallery-alt,
.gallery-offsite {
  display: inline-block;
  border: 1px solid var(--line);
  background: var(--panel-strong);
  padding: 0.05em 0.4em;
  border-radius: 3px;
  font-size: 0.7rem;
}

.gallery-alt {
  margin-left: 0.3rem;
}

/* Placeholders keep the grid's rhythm while reading as not-yet-shipped.
   This used to be `opacity: 0.62` on the whole figure, which multiplied into
   every colour inside it — the caption landed at 3.4:1 and the "coming soon"
   label at 2.3:1, and any future change to those colours would have been
   silently scaled too. The dashed empty box already carries the meaning, so
   the recession is a background tint that leaves text contrast alone. */
.gallery-item-coming {
  background: rgba(255, 255, 255, 0.1);
  border-style: dashed;
}

.gallery-coming {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 7rem;
  border: 1px dashed rgba(39, 20, 82, 0.5);
  font-size: 0.78rem;
  color: rgba(39, 20, 82, 0.7);
}

.press-kit-note,
.license-callout {
  margin-top: 1.4rem;
  border: 1px solid rgba(255, 255, 255, 0.72);
  background: var(--panel);
  padding: clamp(1rem, 2vw, 1.4rem);
  line-height: 1.55;
  font-family: Atkinson-Regular, arial, sans-serif;
}

.press-kit-note p,
.license-callout p {
  margin-bottom: 0.5rem;
}

.press-kit-note > p:last-child,
.license-callout > p:last-child {
  margin-bottom: 0;
}

.guestbook-intro {
  max-width: 60ch;
  margin: 0 auto 1.2rem;
  padding: clamp(0.85rem, 1.7vw, 1.2rem);
  border: 1px solid rgba(255, 255, 255, 0.72);
  background: var(--panel);
  line-height: 1.55;
}

.guestbook-frame-wrap {
  position: relative;
  margin: 0 auto;
  max-width: 44rem;
  padding: clamp(0.75rem, 1.7vw, 1.2rem);
  border: 3px double var(--line);
  background: var(--panel-strong);
  box-shadow:
    inset 0 0 1rem rgba(115, 244, 255, 0.25),
    0 0.9rem 2.2rem var(--shadow);
}

.guestbook-frame {
  display: block;
  width: 100%;
  height: min(80vh, 50rem);
  min-height: 32rem;
  border: 1px solid rgba(232, 246, 255, 0.55);
  background: var(--ice);
  color-scheme: light;
}

.blog-main {
  max-width: 72rem;
  margin: 0 auto;
}

.blog-columns {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(13rem, 17rem);
  gap: 1rem;
  align-items: start;
}

.blog-feed {
  display: grid;
  gap: 1rem;
  min-width: 0;
}

.blog-search input[type="search"] {
  width: 100%;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--line);
  background: var(--panel-strong);
  color: var(--ink);
  font: inherit;
}

.blog-search input[type="search"]:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 2px;
}

.search-status {
  margin: 0;
  font-size: 0.88rem;
  color: rgba(39, 20, 82, 0.72);
}

.blog-entry {
  border: 1px solid rgba(255, 255, 255, 0.72);
  background: var(--panel);
  padding: clamp(1rem, 2vw, 1.4rem);
}

.entry-title {
  margin-bottom: 0.4rem;
  font-size: 1.3rem;
}

.entry-body {
  line-height: 1.55;
  font-family: Atkinson-Regular, arial, sans-serif;
}

.entry-body > :last-child {
  margin-bottom: 0;
}

.entry-more {
  margin: 0.5rem 0 0;
  font-size: 0.92rem;
}

.entry-meta,
.post-meta {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-size: 0.82rem;
  color: rgba(39, 20, 82, 0.72);
}

.entry-meta {
  margin: 0.85rem 0 0;
  padding-top: 0.55rem;
  border-top: 1px dashed rgba(39, 20, 82, 0.25);
}

.entry-permalink {
  text-decoration: none;
  border: 1px solid var(--line);
  background: var(--panel-strong);
  padding: 0 0.45em;
}

.blog-highlights {
  position: sticky;
  top: clamp(0.75rem, 2vw, 1.5rem);
  border: 2px solid rgba(232, 246, 255, 0.75);
  background: var(--panel);
  padding: 1rem;
}

.blog-highlights h3 {
  margin-bottom: 0.6rem;
  font-size: 1.05rem;
}

.blog-highlights ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.6rem;
}

.highlight-date {
  display: block;
  font-size: 0.78rem;
  color: rgba(39, 20, 82, 0.65);
}

.highlight-empty {
  margin: 0;
  font-size: 0.88rem;
  color: rgba(39, 20, 82, 0.65);
}

.post-meta {
  margin: 0 0 1rem;
  /* Shares the .entry-meta rule above, but not its background: .entry-meta
     sits inside a .blog-entry panel, while this one sits directly on the
     index-window's stripes. Solid ink alone technically clears AA there
     (4.9:1), but the ratio is measured against an average the eye never
     sees — the stripes put 182 different colours behind these glyphs, and
     this is the smallest text on the page set in the display face. So it
     gets a flat panel to sit on, the same chip the topline links use. */
  width: fit-content;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--line);
  background: var(--panel-strong);
  color: var(--ink);
}

.post-body {
  border: 1px solid rgba(255, 255, 255, 0.72);
  background: var(--panel);
  padding: clamp(1rem, 2vw, 1.4rem);
  line-height: 1.55;
  font-family: Atkinson-Regular, arial, sans-serif;
}

.post-body > :last-child {
  margin-bottom: 0;
}

/* Markdown figures come out of the exporter at their native pixel size (the
   forest plots are 1760px wide), which is far past the column — clamp them
   here rather than sizing each one at the call site. */
.post-body img,
.entry-body img {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}

/* The "Jarred says" pull-quote is rendered in bun.com's own skin rather than
   this site's: Tailwind Typography `prose` at base size, on their page
   background, in their system-ui stack. Every value below is lifted from
   bun.com/_assets/site-*.css so the block reads as a slab of the source page
   dropped onto this one — that's the joke, so resist tidying it into the
   surrounding Atkinson prose. It tracks the reader's color-scheme preference
   because bun.com's own theme switch ships set to `system`. */
.bun-quote {
  --bun-bg: #f9fafb;        /* gray-50, their <body> */
  --bun-body: #374151;      /* --tw-prose-body */
  --bun-headings: #111827;  /* --tw-prose-headings */
  --bun-line: #e5e7eb;      /* gray-200 */
  margin: 1.8rem 0;
  padding: clamp(1.1rem, 3vw, 2rem) clamp(1.1rem, 3.5vw, 2.5rem);
  border: 1px solid var(--bun-line);
  border-radius: 0.75rem;
  background: var(--bun-bg);
  color: var(--bun-body);
  font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
    "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  font-size: 1rem;
  line-height: 1.75;
}

@media (prefers-color-scheme: dark) {
  .bun-quote {
    --bun-bg: #111827;        /* gray-900 */
    --bun-body: #d1d5db;      /* --tw-prose-invert-body */
    --bun-headings: #fff;
    --bun-line: #374151;
  }
}

/* Their `prose` container is max-w-65ch mx-auto and the children just fill
   it. The measure has to live on the wrapper, not on each child: `ch` is
   font-size-relative, so a per-child max-width gives the 1.25em h3 a wider
   box than the paragraphs and every heading hangs left of the text. */
.bun-prose {
  max-width: 65ch;
  margin-inline: auto;
}

.bun-prose > :first-child {
  margin-top: 0;
}

.bun-prose > :last-child {
  margin-bottom: 0;
}

/* margin-block, not the `margin` shorthand — the shorthand would reset the
   auto inline margins above and knock the column out of center. */
.bun-quote p {
  margin-block: 1.25em;
}

.bun-quote h3,
.bun-quote h4 {
  color: var(--bun-headings);
  font-family: inherit;
  font-weight: 600;
}

.bun-quote h3 {
  font-size: 1.25em;
  line-height: 1.6;
  margin-block: 1.6em 0.6em;
}

.bun-quote h4 {
  font-size: 1em;
  line-height: 1.5;
  margin-block: 1.5em 0.5em;
}

/* Their `h3+*`/`h4+*` rules. Without these the paragraph's 1.25em top margin
   collapses against the heading's smaller bottom margin and wins, so every
   heading sits ~0.5em further from its text than it does on bun.com. */
.bun-quote h3 + *,
.bun-quote h4 + * {
  margin-top: 0;
}

/* Their heading anchors are undecorated and take the heading color, hover
   included. Ours points back at the source section instead of a local id. */
.bun-quote h3 a,
.bun-quote h3 a:hover,
.bun-quote h3 a:focus,
.bun-quote h3 a:active {
  color: inherit;
  text-decoration: none;
}

/* ```goat fences become inline SVG during Hugo's own markdown pass, via the
   embedded render-codeblock-goat.html hook — no client-side renderer. GoAT
   strokes everything with currentColor, so the diagram picks up --ink from
   body without any help. It emits a viewBox and no width/height, though, so
   the sizing below is what actually makes it lay out. */
.goat {
  margin: 1.4rem 0;
}

.goat svg {
  display: block;
  width: 100%;
  max-width: 38rem;
  height: auto;
  margin: 0 auto;
}

/* The three-column layout needs the wave gutter (16vw) plus all three
   columns near their preferred widths; below ~86rem the middle column gets
   squeezed toward its minimum and its contents clip, so stack well before
   that happens. The wave gutter itself stays until 62rem (further down). */
@media (max-width: 86rem) {
  .layout-grid {
    grid-template-columns: 1fr;
  }

  .link-rail {
    position: static;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-content: stretch;
    /* The rail spans the page from here down, so the charms need their room
       inside the grid; the shell's gutter is narrower than the overhang once
       the wave gutter collapses. */
    padding-inline: var(--charm-overhang);
  }

  .rail-link {
    padding-left: 3rem;
  }

  .rail-link:nth-child(even) {
    padding-right: 3rem;
  }

  .index-window {
    min-height: auto;
  }

  .right-stack {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  /* Webring and neolink pair up side by side; the death clock and the bunny
     each get a full row. */
  .right-stack .death-clock,
  .right-stack .feature-object {
    grid-column: 1 / -1;
  }
}

@media (max-width: 62rem) {
  .page-shell {
    --wave-gutter: clamp(1rem, 2.6vw, 2.4rem);
    padding-left: clamp(1rem, 2.6vw, 2.4rem);
  }

  .blog-columns {
    grid-template-columns: 1fr;
  }

  .blog-highlights {
    position: static;
  }
}

@media (max-width: 40rem) {
  .page-shell {
    padding: 0.9rem;
  }

  .wave-field {
    opacity: 0.4;
  }

  /* Panels run nearly edge-to-edge here, so the rulers sit behind content
     more often — fade them like the wave-field rather than dropping them. */
  .tick-rulers {
    opacity: 0.55;
  }

  .link-rail,
  .content-grid,
  .right-stack {
    grid-template-columns: 1fr;
  }

  .rail-link {
    min-height: 3.7rem;
  }

  .feature-image {
    position: relative;
    right: auto;
    bottom: auto;
    width: min(100%, 25rem);
    margin-left: auto;
  }
}

@media (prefers-reduced-motion: reduce) {

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
  }
}
