:root {
  --paper: #f3f1ec;
  --ink: #23211d;
  --muted: #6d675d;
  color-scheme: light;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 17px/1.5 ui-sans-serif, -apple-system, 'Helvetica Neue', Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

#gl {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  display: block;
}

#story { position: relative; z-index: 1; pointer-events: none; }
#story a { pointer-events: auto; color: inherit; }

/* One step per screen; story progress p counts step tops (src/scroll/timeline.ts), so every step
   but the last must be exactly one viewport tall. The last only needs to reach the bottom. */
.step {
  height: 100vh;
  height: 100lvh;
  display: flex;
  align-items: center;
  padding: 0 6vw;
}
#story .chapter:last-child .step:last-child { height: auto; min-height: 100vh; min-height: 100lvh; }

/* The step text (index.html .text): set directly on the scene, no box. A paper scrim behind it keeps it
   legible: a fixed layer between the canvas and the story, fading in from the text's side (left on
   desktop and short landscape screens, bottom on phones). main.ts sets its opacity from the story
   (scrimOpacity): full while a step is read, lighter mid-move. Without WebGL nothing drives it and it is
   paper on paper. */
#scrim {
  position: fixed;
  inset: 0;
  pointer-events: none;
  will-change: opacity; /* its own layer: the per-step opacity changes composite, never repaint */
}
/* Two gradients that crossfade: ::before is the story's; ::after is the time series' (main.ts sets .ts in
   chapter "crossed"), which covers only the text column, up to --ts-edge (main.ts measureShift), and fades
   out over --ts-fade, so the model beside it sits clear of it. */
#scrim::before, #scrim::after { content: ''; position: absolute; inset: 0; transition: opacity 0.5s ease; }
#scrim::after {
  opacity: 0;
  background: linear-gradient(90deg, var(--paper) 0 var(--ts-edge, 40%), color-mix(in srgb, var(--paper) 84%, transparent) calc(var(--ts-edge, 40%) + var(--ts-fade, 48px) * 0.25), color-mix(in srgb, var(--paper) 50%, transparent) calc(var(--ts-edge, 40%) + var(--ts-fade, 48px) * 0.5), color-mix(in srgb, var(--paper) 16%, transparent) calc(var(--ts-edge, 40%) + var(--ts-fade, 48px) * 0.75), transparent calc(var(--ts-edge, 40%) + var(--ts-fade, 48px)));
}
#scrim.ts::before { opacity: 0; }
#scrim.ts::after { opacity: 1; }
#scrim::before {
  /* paper to 28%, clear from 62%; the stops between follow a smoothstep, so the ramp has no visible edge */
  background: linear-gradient(90deg, var(--paper) 0 28%, color-mix(in srgb, var(--paper) 84%, transparent) 36.5%, color-mix(in srgb, var(--paper) 50%, transparent) 45%, color-mix(in srgb, var(--paper) 16%, transparent) 53.5%, transparent 62%);
}

.text { max-width: 30rem; margin: 0; }
.text p { margin: 0; max-width: 34ch; color: var(--muted); text-wrap: pretty; }
.text p + p { margin-top: 0.6em; }
.text h2 {
  margin: 0 0 0.3em;
  font: 400 clamp(30px, 4.2vw, 56px)/1.05 ui-serif, 'Iowan Old Style', Georgia, serif;
  letter-spacing: -0.01em;
  text-wrap: balance;
}
/* The stops: the stop's number from stops.json (main.ts fillStats) and its one-line caption.
   nowrap: a two-class stop reads "50% · 49%" on one line. */
.text .stat { margin: 0 0 1.1rem; color: var(--ink); }
.stat .num {
  display: block;
  white-space: nowrap;
  font: 300 clamp(44px, 6vw, 88px)/0.95 ui-serif, 'Iowan Old Style', Georgia, serif;
  letter-spacing: -0.02em;
  font-variant-numeric: lining-nums tabular-nums;
}
.stat .cap { display: block; margin-top: 0.7em; font-size: 14px; line-height: 1.35; letter-spacing: 0.02em; color: var(--muted); }
/* Each class in the caption carries its palette swatch (main.ts fillStats, colour inline from config), so
   the caption is the stop's legend on every layout. A swatch never wraps away from its label. */
.stat .cap .cls { white-space: nowrap; }
.stat .cap .sw { display: inline-block; width: 0.8em; height: 0.8em; margin-right: 0.4em; border-radius: 2px; vertical-align: -0.05em; }
/* Without WebGL (or before stops.json is in) the number is empty: no blank gap above the paragraph. */
.text .stat:has(.num:empty) { display: none; }
/* Chapter "crossed": the time-series steps' load failure note (main.ts); empty, it takes no room. */
.text .ts-status { font-size: 14px; }
.text .ts-status:empty { display: none; }
/* Every time-series step's text is as tall as the tallest (main.ts measures it: --ts-text-min), so the
   year and its number sit in the same place from year to year, and the phone panels above it never move. */
.step-ts .text { min-height: var(--ts-text-min, 0px); }

.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* The opening: the big title and its lede, bottom left. */
.step-green { align-items: flex-end; padding-bottom: 12vh; }
.step-green h1 {
  margin: 0;
  font: 300 clamp(56px, 11vw, 168px)/0.9 ui-serif, 'Iowan Old Style', Georgia, serif;
  letter-spacing: -0.03em;
}
.text .lede { margin-top: 1rem; max-width: 32ch; }

/* The colour spread: a deck at the bottom centre, over the open Southern Ocean between the continents. */
.step-spread { justify-content: center; align-items: flex-end; padding-bottom: 6vh; }
.step-spread .text { text-align: center; }
.step-spread .text p { max-width: 34ch; margin-inline: auto; text-wrap: balance; }

/* Steps with a single paragraph and no headline (spread, line) set it as a serif deck. */
.step-line .text p { max-width: 28ch; }
.step-spread .text p,
.step-line .text p {
  font: 400 clamp(21px, 1.9vw, 27px)/1.32 ui-serif, 'Iowan Old Style', Georgia, serif;
  letter-spacing: -0.005em;
  color: var(--ink);
}

/* Each step's scrim weight (main.ts reads --scrim at load and on resize; scrimOpacity): 1 behind text
   set over the scene. The credits carry their own panel. On desktop the colour spread sets its deck over
   the open Southern Ocean, so the scene shows whole (the scrim would hide the Americas the colours spread
   from); the opening's title and lede sit left of the Andes on the table, a shade darker than paper, so a
   light scrim lifts the muted lede to full contrast and leaves the Andes clear. */
.step { --scrim: 1; }
.step-credits { --scrim: 0; }
@media (min-width: 821px) {
  .step-green { --scrim: 0.6; }
  .step-spread { --scrim: 0; }
  /* the time series: the text sits on the paper-faded globe, and the model's west edge clears the scrim */
  .step-ts { --scrim: 0.8; }
}

/* The scene centres in the part of the screen the text leaves free (main.ts measures it at load and on
   resize and shifts the projection, camera.setViewOffset): right of the text column where --text-side is
   left, above the text where it is bottom, under the key where the key is a row at the top. --shift
   scales it per step: 0 where the text does not take a side (the credits' panel; the spread's deck at the
   bottom centre on desktop). */
:root { --text-side: left; }
.step { --shift: 1; }
.step-credits { --shift: 0; }
@media (min-width: 821px) {
  .step-spread { --shift: 0; }
}

/* The credits keep a light panel: the attribution is small text and must read in full. */
.step-credits .text {
  max-width: 46ch;
  padding: 1.1rem 1.3rem;
  font-size: 14px;
  background: color-mix(in srgb, var(--paper) 96%, transparent);
  border-radius: 2px;
}
.step-credits .text p { max-width: none; }

/* Phones and narrow windows: the text along the bottom over a bottom-up scrim, clear of the globe in the middle.
   Steps are 100lvh (the screen with Safari's toolbars hidden); while the bottom toolbar shows it covers
   100lvh − 100svh of the step's foot, so the text's bottom padding adds that (0 where the two agree). */
@media (max-width: 820px) {
  :root { --text-side: bottom; }
  #scrim::after { background: linear-gradient(0deg, var(--paper) 0 var(--ts-edge, 40%), color-mix(in srgb, var(--paper) 84%, transparent) calc(var(--ts-edge, 40%) + var(--ts-fade, 48px) * 0.25), color-mix(in srgb, var(--paper) 50%, transparent) calc(var(--ts-edge, 40%) + var(--ts-fade, 48px) * 0.5), color-mix(in srgb, var(--paper) 16%, transparent) calc(var(--ts-edge, 40%) + var(--ts-fade, 48px) * 0.75), transparent calc(var(--ts-edge, 40%) + var(--ts-fade, 48px))); }
  #scrim::before { background: linear-gradient(0deg, var(--paper) 0 24%, color-mix(in srgb, var(--paper) 84%, transparent) 32%, color-mix(in srgb, var(--paper) 50%, transparent) 40%, color-mix(in srgb, var(--paper) 16%, transparent) 48%, transparent 56%); }
  .step { align-items: flex-end; padding: 0 20px 28px; padding-bottom: calc(28px + 100lvh - 100svh); }
  .text { max-width: none; width: 100%; }
  .text p { font-size: 15px; }
  .step-green { padding-bottom: 10vh; padding-bottom: calc(10svh + 100lvh - 100svh); }
  .step-credits .text { max-width: none; font-size: 15px; }
  .stat .cap { font-size: 13px; }
  .step-spread .text { text-align: left; }
  .step-spread .text p { margin-inline: 0; }
  .step-spread .text p, .step-line .text p { max-width: none; font-size: 20px; }
}

/* Narrow desktop windows: the opening's lede reaches the Andes, so it takes the full scrim; the spread's
   deck narrows to stay over open sea, clear of South America and of the map key at the bottom right. */
@media (min-width: 821px) and (max-width: 1000px) {
  .step-green { --scrim: 1; }
  .step-spread .text p { max-width: 30ch; }
  /* the time series: a narrower text column and panel column, so the model keeps its size between them */
  .step-ts .text p { max-width: 26ch; }
}

/* No scrollbar while loading: if one left when loading ended, the centred bar and text would jump sideways. */
body:has(#loader:not(.done):not(.failed)) { overflow: hidden; }

#loader {
  position: fixed;
  inset: 0;
  z-index: 6; /* over everything, the menus at 5 included */
  display: grid;
  place-content: center;
  gap: 14px;
  text-align: center;
  background: var(--paper);
  color: var(--muted);
  font-size: 13px;
  letter-spacing: 0.04em;
  transition: opacity 1.2s ease 0.3s, visibility 0s linear 1.5s;
}
#loader.done { opacity: 0; visibility: hidden; }
#loader .bar { width: 180px; height: 1px; background: #dcd7cd; margin: 0 auto; }
#loader .bar i { display: block; height: 100%; background: var(--ink); transform: scaleX(0.05); transform-origin: left; transition: transform 0.3s; }

/* The phone key row's left fade width (legend.ts, the scrolling row below): registered so it can ease in. */
@property --fade-l { syntax: '<length>'; inherits: false; initial-value: 0px; }

/* The map key (ui/legend.ts): bottom-right on desktop, clear of the hero title and the text column; a sideways-scrolling
   row pinned to the top on phones. Colours are set inline from config. */
.legend {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: max-content;
  max-width: calc(100vw - 48px);
  padding: 10px 12px;
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  backdrop-filter: blur(6px);
  border-radius: 2px;
  font-size: 12px;
  line-height: 1.35;
  color: var(--ink);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.4s ease, visibility 0s linear 0.4s;
}
.legend.on { opacity: 1; visibility: visible; transition: opacity 0.4s ease; }
.legend .layers { display: grid; }
/* The two layers never show at once (legendLayers). The hidden one (.off, legend.ts) keeps its width,
   so the key does not change width, but not its height: the cell is as tall as the visible layer. */
.legend .layer { grid-area: 1 / 1; display: flex; flex-direction: column; gap: 6px; }
.legend .layer.off { height: 0; overflow: hidden; visibility: hidden; }
.legend .title { color: var(--muted); }
.legend .ramp { display: block; height: 8px; border-radius: 2px; }
.legend .ends { display: flex; justify-content: space-between; color: var(--muted); font-variant-numeric: tabular-nums; }
.legend .chip { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
/* At a stop the corner panel keeps only the featured class rows (legend.ts .hide, rowHidden), "Not forest"
   and the note: the other rows fold away (height and padding to 0) so the key covers less featured ground.
   The row gap is padding, not the flex gap, so a folded row leaves none; the negative margin keeps the
   layer's outer spacing as it was. The phone row overrides this (it keeps every chip, faded). */
.legend .layer.types { gap: 0; margin-block: -3px; }
.legend .layer.types .chip { max-height: 23px; padding-block: 3px; overflow: hidden; transition: max-height 0.3s ease, padding 0.3s ease; }
.legend .layer.types .chip.hide { max-height: 0; padding-block: 0; }
.legend .swatch { display: block; flex: none; width: 12px; height: 12px; border-radius: 2px; }
.legend .tail { display: flex; flex-direction: column; gap: 6px; }
.legend .note { color: var(--muted); width: 0; min-width: 100%; } /* wraps to the width of the swatch list */

/* Chapter "crossed": its own key (legend.ts mountTsKey), top left over the text column's scrim on desktop,
   headed by the beta label; the seven classes in two columns. */
.legend.tskey { top: 24px; left: 6vw; right: auto; bottom: auto; gap: 9px; }
.legend .beta { margin: 0; color: var(--muted); white-space: nowrap; }
.legend .beta b { display: inline-block; margin-right: 1px; padding: 0 5px; font-weight: 600; color: var(--ink); border: 1px solid color-mix(in srgb, var(--ink) 55%, transparent); border-radius: 2px; }
.legend .layer.v2 { display: grid; grid-template-columns: repeat(2, max-content); gap: 5px 18px; }

/* Also on short landscape screens (phones turned sideways), where the corner key would cover the text. */
@media (max-width: 820px), (max-height: 500px) and (min-aspect-ratio: 3/2) {
  .legend {
    left: 0;
    right: 0;
    top: 0;
    bottom: auto;
    width: auto;
    max-width: none;
    flex-direction: column;
    gap: 4px;
    padding: 8px 16px;
    border-radius: 0;
  }
  /* line 1: the swatches scroll sideways; line 2: "Not forest" and the earth-tone note, always in view */
  .legend .layers { overflow-x: auto; scrollbar-width: none; }
  .legend .layers::-webkit-scrollbar { display: none; }
  /* the hint that the row scrolls: it fades out at the right edge; the end padding lets the last chip
     scroll clear of the fade */
  .legend .layers {
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--fade-l), #000 calc(100% - 36px), transparent);
    mask-image: linear-gradient(to right, transparent 0, #000 var(--fade-l), #000 calc(100% - 36px), transparent);
    transition: --fade-l 0.25s ease;
  }
  /* once scrolled (legend.ts .scrolled) it fades out at the left edge too, so a chip cut there reads as
     "more this way", not as a stray word; the chip scrolled to sits clear of it (ROW_PAD) */
  .legend .layers.scrolled { --fade-l: 36px; }
  .legend .layer.types { margin-block: 0; gap: 14px; }
  .legend .layer.types .chip, .legend .layer.types .chip.hide { max-height: none; padding-block: 0; overflow: visible; }
  .legend .layer { width: max-content; padding-right: 36px; }
  .legend .layer.off { display: none; } /* in the scrolling row its width would be empty space to swipe into */
  .legend .layer { flex-direction: row; align-items: center; gap: 14px; }
  .legend .ramp { width: 80px; }
  .legend .ends { display: contents; }
  .legend .ends span:first-child { order: 1; }
  .legend .ramp { order: 2; }
  .legend .ends span:last-child { order: 3; }
  .legend .tail { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 2px 14px; }
  .legend .note { width: auto; min-width: 0; }
  /* the chapter's key: the same row at the top, the beta label on its first line */
  .legend.tskey { top: 0; left: 0; right: 0; gap: 4px; }
  .legend .layer.v2 { display: flex; gap: 14px; }
}

/* Short landscape screens (phones turned sideways, short windows): the key is the row at the top (above)
   and the text sits on the left under it, over the left-hand scrim. Last, so it wins over the narrow layout. */
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  #scrim::after { background: linear-gradient(90deg, var(--paper) 0 var(--ts-edge, 40%), color-mix(in srgb, var(--paper) 84%, transparent) calc(var(--ts-edge, 40%) + var(--ts-fade, 48px) * 0.25), color-mix(in srgb, var(--paper) 50%, transparent) calc(var(--ts-edge, 40%) + var(--ts-fade, 48px) * 0.5), color-mix(in srgb, var(--paper) 16%, transparent) calc(var(--ts-edge, 40%) + var(--ts-fade, 48px) * 0.75), transparent calc(var(--ts-edge, 40%) + var(--ts-fade, 48px))); }
  #scrim::before { background: linear-gradient(90deg, var(--paper) 0 28%, color-mix(in srgb, var(--paper) 84%, transparent) 36.5%, color-mix(in srgb, var(--paper) 50%, transparent) 45%, color-mix(in srgb, var(--paper) 16%, transparent) 53.5%, transparent 62%); }
  .step { align-items: center; justify-content: flex-start; padding: 60px 24px 12px; }
  .text { width: auto; max-width: min(30rem, 42vw); }
  .text p { font-size: 14px; }
  .step-green { align-items: flex-end; padding-bottom: 8vh; }
  .step-green .text { max-width: none; }
  .step-green h1 { font-size: clamp(48px, 20vh, 96px); }
  /* the spread's deck sits left over the Americas here (no open sea at the bottom): it needs the scrim */
  :root { --text-side: left; }
  .step-green { --scrim: 0.6; }
  .step-spread { --scrim: 1; --shift: 1; }
  .step-spread .text { text-align: left; }
  .step-spread .text p { margin-inline: 0; }
  .step-spread .text p, .step-line .text p { font-size: 18px; }
  .step-credits .text { max-width: min(60ch, 80vw); padding: 0.8rem 1rem; font-size: 13px; }
}

/* No WebGL 2: the loader becomes a note at the top and the page scrolls on under it. */
#loader.failed { inset: 0 0 auto 0; place-content: start center; padding: 14px 6vw; pointer-events: none; transition: none; }
#loader.failed .bar { display: none; }

/* The graphics context was lost (main.ts): a centred card with a reload button. */
#gl-lost { position: fixed; inset: 0; z-index: 7; display: none; place-items: center; pointer-events: none; }
#gl-lost.show { display: grid; }
#gl-lost .card {
  max-width: 34ch; padding: 1.1rem 1.3rem; pointer-events: auto; text-align: center;
  background: color-mix(in srgb, var(--paper) 88%, transparent); backdrop-filter: blur(6px); border-radius: 2px;
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--ink) 14%, transparent);
}
#gl-lost .card p { margin: 0; }
#gl-lost button {
  margin-top: 0.9rem; padding: 0.45rem 1.1rem; font: inherit; color: var(--paper); background: var(--ink);
  border: 0; border-radius: 2px; cursor: pointer;
}
#gl-lost button:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* ---- Explore (main.ts): no scroll; one step's text at a time, destination buttons, the year control ---- */
body.explore { overflow: hidden; height: 100%; }
body.explore #story { position: fixed; inset: 0; }
body.explore .step { position: absolute; inset: 0; height: auto; opacity: 0; visibility: hidden; transition: opacity 0.45s ease, visibility 0s linear 0.45s; }
body.explore .step.on { opacity: 1; visibility: visible; transition: opacity 0.7s ease 0.1s; }
body.explore .step a, body.explore .step button { pointer-events: auto; }
#enter {
  margin-top: 1.6rem; padding: 0.8rem 1.4rem; border: 1px solid var(--ink); border-radius: 999px;
  background: var(--ink); color: var(--paper); font: 500 15px/1 ui-sans-serif, -apple-system, 'Helvetica Neue', Arial, sans-serif;
  letter-spacing: 0.01em; cursor: pointer; pointer-events: auto;
}
#enter:hover { background: transparent; color: var(--ink); }
#dests {
  position: fixed; z-index: 5; top: 16px; left: 50%; transform: translateX(-50%);
  display: flex; gap: 4px; padding: 4px; border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 88%, transparent); backdrop-filter: blur(8px);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.04), 0 4px 18px rgba(40, 30, 10, 0.08);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
#dests button {
  border: 0; background: transparent; color: var(--ink); padding: 0.55rem 0.9rem; border-radius: 999px; cursor: pointer;
  font: 500 13px/1 ui-sans-serif, -apple-system, 'Helvetica Neue', Arial, sans-serif; white-space: nowrap;
}
#dests button:hover { background: color-mix(in srgb, var(--ink) 7%, transparent); }
#dests button[aria-pressed='true'] { background: var(--ink); color: var(--paper); }
body.in-hero:not(.seen) #dests, body:not(.explore) #dests { opacity: 0; pointer-events: none; transform: translate(-50%, -12px); }
#ts-ctl {
  position: fixed; z-index: 5; top: 68px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 12px; padding: 6px 14px 6px 6px; border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 88%, transparent); backdrop-filter: blur(8px);
  box-shadow: 0 4px 18px rgba(40, 30, 10, 0.08);
  opacity: 0; pointer-events: none; transition: opacity 0.5s ease;
}
body.in-ts #ts-ctl { opacity: 1; pointer-events: auto; }
#ts-play { width: 36px; height: 36px; border-radius: 50%; border: 0; background: var(--ink); color: var(--paper); cursor: pointer; font-size: 13px; }
#ts-year { width: min(320px, 50vw); accent-color: var(--ink); }
#ts-ctl output { font: 400 20px/1 ui-serif, 'Iowan Old Style', Georgia, serif; min-width: 3ch; }
@media (max-width: 820px) {
  #dests { top: auto; bottom: calc(10px + env(safe-area-inset-bottom)); left: 10px; right: 10px; transform: none; overflow-x: auto; scrollbar-width: none; }
  body.in-hero:not(.seen) #dests { transform: translateY(12px); }
  #dests button { padding: 0.75rem 0.9rem; font-size: 14px; }
  body.explore .step { padding-bottom: calc(84px + 100lvh - 100svh); }
  #ts-ctl { top: auto; bottom: calc(70px + env(safe-area-inset-bottom)); }
}

.close-text {
  display: block; margin: 0 0 1rem; padding: 0.5rem 0.8rem 0.5rem 0.7rem; border: 1px solid color-mix(in srgb, var(--ink) 22%, transparent); border-radius: 999px;
  background: color-mix(in srgb, var(--paper) 80%, transparent); color: var(--ink); cursor: pointer;
  font: 500 13px/1 ui-sans-serif, -apple-system, 'Helvetica Neue', Arial, sans-serif;
}
.step-spread .close-text { margin-inline: auto; }
.close-text:hover { background: var(--ink); color: var(--paper); }

/* ---- The look of the text (no panel, no fade): type set straight on the scene ---- */
#scrim { display: none; }
.text p, .lede { color: var(--ink); }
.text button, #enter { text-shadow: none; }
.close-text {
  margin: 0 0 0.9rem; padding: 0; border: 0; border-radius: 0; background: none;
  font-size: 13px; text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px;
}
.close-text:hover { background: none; color: var(--ink); text-decoration-thickness: 2px; }

/* The key: no box either, a quiet list in the corner. */
.legend { background: none; backdrop-filter: none; box-shadow: none; }

/* A stop: a lower third. The place's name, large like the title; under it the number, then the words. */
@media (min-width: 821px) {
  .step-stop { --text-side: bottom; }
  body.explore .step-stop { align-items: flex-end; padding: 0 6vw 5vh; }
  .step-stop .text { max-width: none; width: min(62vw, 900px); display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: clamp(24px, 3vw, 48px); align-items: end; }
  .step-stop .close-text, .step-stop h2 { grid-column: 1 / -1; }
  .step-stop .read { display: contents; }
  .step-stop .read > p:not(.stat) { padding-left: clamp(24px, 3vw, 48px); border-left: 1px solid color-mix(in srgb, var(--ink) 30%, transparent); max-width: 38ch; margin: 0 0 1rem; }
}
.step-stop h2 { margin: 0 0 0.16em; font: 300 clamp(56px, 8.6vw, 136px)/0.92 ui-serif, 'Iowan Old Style', Georgia, serif; letter-spacing: -0.03em; }
.step-stop .stat .num { font-size: clamp(38px, 4.6vw, 68px); }
.step-stop .stat .cap { margin-top: 0.5em; font-size: 14px; font-weight: 500; color: var(--ink); }

/* West Kalimantan: on bare paper. The satellite photo and the map side by side, same size, same scale;
   the year control and the chart below them; the year and its numbers on the left. */
:root { --ts-s: min(29vw, calc(100vh - 372px)); }
.tspanels {
  position: fixed; z-index: 1; top: 76px; bottom: 24px; right: 6vw; width: calc(2 * var(--ts-s) + 14px);
  display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: auto auto minmax(0, 1fr) auto; gap: 12px 14px;
  font-size: 12px; line-height: 1.35; color: var(--muted); pointer-events: none;
  opacity: 0; visibility: hidden; transition: opacity 0.5s ease, visibility 0s linear 0.5s;
}
.tspanels.on { opacity: 1; visibility: visible; transition: opacity 0.6s ease 0.2s; }
.tspanels .fig { margin: 0; min-height: 0; }
.tspanels .frame { position: relative; aspect-ratio: 1; background: #e2ded4; overflow: hidden; }
.tspanels .frame img { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; transition: opacity 0.35s ease; }
.tspanels .frame img.on { opacity: 1; }
.tspanels .map img { image-rendering: pixelated; }
.tspanels figcaption { margin-top: 6px; text-wrap: pretty; }
.tspanels .yr { color: var(--ink); font-variant-numeric: lining-nums tabular-nums; }
.tspanels .fig.chart { grid-column: 1 / -1; display: flex; flex-direction: column; }
.tspanels .plot { flex: 1; min-height: 0; display: flex; flex-direction: column; padding-left: 48px; }
.tspanels .bars { display: block; flex: 1; min-height: 0; width: 100%; shape-rendering: crispEdges; }
.tspanels .bars g { opacity: 0.4; transition: opacity 0.3s ease; }
.tspanels .bars g.cur { opacity: 1; }
.tspanels .years { display: grid; grid-template-columns: repeat(9, 1fr); margin-top: 4px; font-size: 11px; line-height: 1.2; text-align: center; font-variant-numeric: lining-nums tabular-nums; }
.tspanels .years .cur { color: var(--ink); font-weight: 600; }
.tspanels .fig.chart figcaption { padding-left: 48px; }
.tspanels #ts-ctl { position: static; transform: none; grid-column: 1 / -1; width: auto; gap: 12px; padding: 0; background: none; backdrop-filter: none; box-shadow: none; border-radius: 0; }
.tspanels #ts-year { flex: 1; width: auto; min-width: 0; margin-inline: calc((100% - 48px) / 18); }
.tspanels #ts-ctl output { display: none; }
body.explore .step-ts { align-items: flex-start; padding-top: 76px; }
.step-ts .text { max-width: calc(88vw - 2 * var(--ts-s) - 14px - 32px); }
.step-ts h2 { margin: 0 0 0.12em; font: 300 clamp(56px, 7vw, 112px)/0.92 ui-serif, 'Iowan Old Style', Georgia, serif; letter-spacing: -0.03em; font-variant-numeric: lining-nums; }
.step-ts .stat .cap { color: var(--ink); }
.legend.tskey { top: auto; bottom: 24px; left: 6vw; right: auto; }

@media (max-width: 820px) {
  body.explore .step-stop { padding-inline: 20px; }
  .step-stop .read > p:not(.stat) { margin: 0; max-width: none; font-size: 15px; line-height: 1.4; }
  /* West Kalimantan on a phone: the key row, the two images side by side, the control and chart, the words. */
  :root { --ts-s: 50vw; }
  .tspanels { top: 104px; left: 12px; right: 12px; bottom: auto; width: auto; grid-template-rows: auto auto auto auto; gap: 8px 8px; font-size: 11px; }
  .tspanels .bars { flex: none; height: 76px; }
  .tspanels .plot, .tspanels .fig.chart figcaption { padding-left: 0; }
  .tspanels .fig.chart figcaption { display: none; }
  .tspanels #ts-year { margin-inline: 0; }
  body.explore .step-ts { align-items: flex-end; padding-top: 0; }
  .step-ts .text { max-width: none; min-height: 0; }
  .step-ts h2 { font-size: 48px; }
  .step-ts .stat .num { font-size: 40px; }
}
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  :root { --ts-s: min(24vw, 44vh); }
  .tspanels { top: 8px; bottom: 8px; right: 12px; left: auto; width: calc(2 * var(--ts-s) + 8px); }
  .step-stop h2, .step-ts h2 { font-size: 40px; }
  .step-stop .stat .num, .step-ts .stat .num { font-size: 30px; }
  .legend.tskey { display: none; }
}

/* The link hugs its text. */
.close-text { justify-self: start; width: fit-content; }
/* The key: the same floating chip as the destination bar. */
.legend { padding: 12px 14px; border-radius: 14px; background: color-mix(in srgb, var(--paper) 88%, transparent); backdrop-filter: blur(8px); box-shadow: 0 4px 18px rgba(40, 30, 10, 0.08); text-shadow: none; }
@media (max-width: 820px) {
  .legend { border-radius: 0; box-shadow: none; }
  .legend.tskey { top: 0; left: 0; right: 0; bottom: auto; }
  /* West Kalimantan on a phone: the year and number on one line, no paragraph, so it fits with the toolbar showing */
  .step-ts .text { display: grid; grid-template-columns: max-content 1fr; column-gap: 14px; align-items: baseline; }
  .step-ts .close-text, .step-ts .stat .cap, .step-ts .ts-status { grid-column: 1 / -1; }
  .step-ts .stat { display: contents; }
  .step-ts .text > p:not(.stat):not(.ts-status) { display: none; }
  .step-ts h2 { margin: 0; font-size: 44px; }
  .step-ts .stat .num { font-size: 44px; }
  .tspanels .bars { height: 56px; }
}

/* The site's name is longer than the old one: the same size as a stop's name, so it fits the hero on one line. */
.step-green .text { max-width: none; }
.step-green h1 { font-size: clamp(48px, 8.6vw, 136px); line-height: 0.92; }
.step-spread a { text-underline-offset: 3px; text-decoration-thickness: 1px; }
.step-spread a:hover { text-decoration-thickness: 2px; }

/* The helper for the controls: the same chip as the destination bar, just under it. */
#hint {
  position: fixed; top: 64px; left: 50%; transform: translateX(-50%); z-index: 3; margin: 0;
  padding: 5px 12px; border-radius: 999px; white-space: nowrap; pointer-events: none;
  font-size: 12px; letter-spacing: 0.01em; color: var(--ink);
  background: color-mix(in srgb, var(--paper) 86%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
  opacity: 0; transition: opacity 0.6s;
}
body.can-move #hint { opacity: 1; }
@media (max-width: 820px) { #hint { top: 102px; font-size: 11.5px; } }

/* The loader stops catching input as soon as it starts to fade. */
#loader.done { pointer-events: none; }

/* Readability without a glow or a box: the scene itself softens behind the text, at the stops and the world
   view. Its own layer, not a child of the fading step: a blur inside an element that is fading sees nothing
   behind it, and would pop in at the end of the fade. The text elsewhere sits on bare paper and needs none. */
#soft {
  position: fixed; z-index: 1; bottom: 0; pointer-events: none;
  /* A veil of the paper's own colour, not a brightness filter, which would blow the paper out to white: paper
     stays paper, and only the land under the words lifts towards it. */
  background: color-mix(in srgb, var(--paper) 42%, transparent);
  backdrop-filter: blur(16px) saturate(0.7); -webkit-backdrop-filter: blur(16px) saturate(0.7);
  opacity: 0; visibility: hidden; transition: opacity 0.45s ease, visibility 0s linear 0.45s;
}
#soft.on { opacity: 1; visibility: visible; transition: opacity 0.7s ease 0.1s; }
#soft[data-shape='stop'] {
  left: 0; width: 78vw; height: 56vh;
  -webkit-mask-image: radial-gradient(ellipse 100% 100% at 0% 100%, #000 66%, transparent 100%); mask-image: radial-gradient(ellipse 100% 100% at 0% 100%, #000 66%, transparent 100%);
}
#soft[data-shape='hero'] {
  left: 0; width: 60vw; height: 78vh;
  -webkit-mask-image: radial-gradient(ellipse 100% 100% at 0% 100%, #000 62%, transparent 100%); mask-image: radial-gradient(ellipse 100% 100% at 0% 100%, #000 62%, transparent 100%);
}
#soft[data-shape='world'] {
  left: 15vw; width: 70vw; height: 52vh;
  -webkit-mask-image: radial-gradient(ellipse 50% 100% at 50% 100%, #000 62%, transparent 100%); mask-image: radial-gradient(ellipse 50% 100% at 50% 100%, #000 62%, transparent 100%);
}
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  #soft[data-shape] { width: 90vw; height: 100vh; left: 0; -webkit-mask-image: linear-gradient(to right, #000 74%, transparent); mask-image: linear-gradient(to right, #000 74%, transparent); }
}

/* The two pills that are not places, apart from the places: Intro and About in the top corners. Same chip
   as the destination bar; on the first view of the intro, hidden with it. */
#site { position: fixed; z-index: 5; top: 16px; left: 24px; right: 24px; display: flex; justify-content: space-between; pointer-events: none; }
#site button {
  pointer-events: auto; height: 40px; padding: 0 1.1rem; border: 0; border-radius: 999px; cursor: pointer; color: var(--ink);
  font: 500 13px/1 ui-sans-serif, -apple-system, 'Helvetica Neue', Arial, sans-serif; white-space: nowrap;
  background: color-mix(in srgb, var(--paper) 88%, transparent); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.04), 0 4px 18px rgba(40, 30, 10, 0.08);
}
#site button:hover { background: color-mix(in srgb, var(--paper) 96%, var(--ink)); }
#site button[aria-pressed='true'] { background: var(--ink); color: var(--paper); }
body.in-hero:not(.seen) #site button, body:not(.explore) #site button { opacity: 0; transform: translateY(-12px); pointer-events: none; }
@media (max-width: 820px) {
  /* A phone: under the key row, with the bottom bar for the places alone. About has no key above it. */
  #site { top: 66px; left: 12px; right: 12px; }
  #site button { height: 30px; padding: 0 0.8rem; font-size: 12px; }
}
/* A phone on its side: the key takes the top, so both menus share the bottom row, the corners either side of the places. */
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  #dests { top: auto; bottom: 8px; left: 50%; right: auto; transform: translateX(-50%); max-width: calc(100vw - 170px); overflow-x: auto; scrollbar-width: none; }
  body.in-hero:not(.seen) #dests { transform: translate(-50%, 12px); }
  #dests button { padding: 0.5rem 0.65rem; font-size: 12px; }
  #site { top: auto; bottom: 8px; left: 8px; right: 8px; align-items: flex-end; }
  #site button { height: 36px; padding: 0 0.8rem; font-size: 12px; }
  body.in-hero:not(.seen) #site button { transform: translateY(12px); }
  #hint { top: 66px; }
  body.explore .step-stop { padding: 66px 24px 52px; align-items: flex-start; }
  body.explore .step-spread { padding: 66px 24px 52px; align-items: flex-start; }
  body.explore .step-credits { padding: 66px 24px 52px; align-items: flex-start; }
  body.seen .step-green { padding-bottom: 52px; }
  .step-green h1 { font-size: clamp(32px, 12vh, 56px); }
  .step-green .lede { margin-top: 0.5rem; font-size: 13px; }
  #enter { margin-top: 0.7rem; padding: 0.6rem 1.1rem; font-size: 13px; }
  :root { --ts-s: min(22vw, 34vh); }
  .tspanels { bottom: 52px; }
  .tspanels .fig.chart figcaption { display: none; }
  .step-stop h2 { font-size: 36px; }
}

/* West Kalimantan on the globe: the place in the middle of the free area, its pictures together in a card on one
   side (the same chip as the key), the year and words in the lower left like a stop. */
:root { --ts-s: max(120px, min(17vw, calc(100vh - 560px))); }
.tspanels {
  top: 68px; bottom: auto; right: 24px; width: calc(2 * var(--ts-s) + 10px + 28px); padding: 14px; gap: 10px;
  grid-template-rows: auto auto auto auto auto; border-radius: 14px; pointer-events: auto;
  background: color-mix(in srgb, var(--paper) 92%, transparent); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 4px 18px rgba(40, 30, 10, 0.08);
}
.tspanels .bars { flex: none; height: clamp(56px, 13vh, 120px); }
.tspanels .years { grid-template-columns: repeat(6, 1fr); }
.tspanels #ts-year { margin-inline: calc((100% - 48px) / 12); }
.tspanels .fig.chart figcaption { display: none; }
#soft[data-shape='ts'] {
  left: 0; width: 66vw; height: 54vh;
  -webkit-mask-image: radial-gradient(ellipse 100% 100% at 0% 100%, #000 72%, transparent 100%); mask-image: radial-gradient(ellipse 100% 100% at 0% 100%, #000 72%, transparent 100%);
}
@media (min-width: 821px) {
  body.explore .step-ts { align-items: flex-end; padding: 0 6vw 5vh; }
  .step-ts { --text-side: bottom; }
  .step-ts .text { max-width: none; width: calc(94vw - 2 * var(--ts-s) - 10px - 28px - 24px - 40px); display: grid; grid-template-columns: minmax(0, 16rem) minmax(0, 1fr); column-gap: clamp(20px, 2.4vw, 40px); align-items: end; }
  .step-ts .close-text, .step-ts h2, .step-ts .ts-status { grid-column: 1 / -1; }
  .step-ts .read { display: contents; }
  .step-ts .read > p:not(.stat) { padding-left: clamp(20px, 2.4vw, 40px); border-left: 1px solid color-mix(in srgb, var(--ink) 30%, transparent); max-width: 36ch; margin: 0 0 1rem; }
  .step-ts .stat .num { font-size: clamp(38px, 4.6vw, 68px); }
  .step-ts .stat .cap { margin-top: 0.5em; font-size: 14px; font-weight: 500; }
  .legend.tskey { left: auto; right: 24px; bottom: 24px; top: auto; width: calc(2 * var(--ts-s) + 10px + 28px); box-sizing: border-box; }
  .legend.tskey .layer.v2 { grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); font-size: 12px; }
}
@media (max-width: 820px) {
  :root { --ts-s: 0px; }
  .tspanels { top: auto; bottom: calc(190px + 100lvh - 100svh + env(safe-area-inset-bottom)); left: 12px; right: 12px; width: auto; padding: 10px; gap: 8px; }
  .step-ts .read { display: contents; }
  .step-ts .read > p:not(.stat) { display: none; }
  .tspanels .bars { height: 46px; }
  .tspanels figcaption { font-size: 10.5px; margin-top: 4px; }
  #soft[data-shape='ts'] { height: 24vh; }
}
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  :root { --ts-s: min(16vw, 32vh); }
  .tspanels { top: 8px; right: 12px; left: auto; bottom: auto; width: calc(2 * var(--ts-s) + 8px + 20px); padding: 10px; gap: 6px; font-size: 10.5px; }
  .tspanels .bars { height: 34px; }
  body.explore .step-ts { align-items: flex-start; padding: 12px 24px 52px; }
  .step-ts .text { display: block; width: 25vw; }
  .step-ts .read { display: block; }
  .step-ts .read > p:not(.stat) { display: none; }
  .step-ts h2 { font-size: 36px; }
  #soft[data-shape='ts'] { width: 34vw; height: 100vh; }
}

/* The words leave slowly. Not the time series' years, which swap as the slider moves: two big years must not linger over each other. */
body.explore .step { transition: opacity 0.95s ease-in-out, visibility 0s linear 0.95s; }
body.explore .step.on { transition: opacity 0.7s ease 0.1s; }
body.explore .step-ts:not(.on) { transition: opacity 0.45s ease, visibility 0s linear 0.45s; }
#soft { transition: opacity 0.95s ease-in-out, visibility 0s linear 0.95s; }
#soft.on { transition: opacity 0.7s ease 0.1s; }

/* The World text: wide and short, on a low, wide soft area. */
@media (min-width: 821px) and (min-height: 501px) {
  .step-spread .text { max-width: none; width: min(880px, 72vw); }
  .step-spread .text p { max-width: none; }
  #soft[data-shape='world'] { left: 8vw; width: 84vw; height: var(--soft-h, 42vh); }
}

/* About: laid out as a stop (step-stop), so none of the old panel. */
.step-credits .text { padding: 0; background: none; border-radius: 0; font-size: inherit; }
.step-credits .byline { margin-top: 0; font: 400 clamp(20px, 1.7vw, 25px)/1.28 ui-serif, 'Iowan Old Style', Georgia, serif; }
.step-credits .sources { font-size: 13.5px; line-height: 1.5; }
.step-credits a { text-underline-offset: 3px; text-decoration-thickness: 1px; }
.step-credits a:hover { text-decoration-thickness: 2px; }
@media (min-width: 821px) {
  .step-credits .text { max-width: none; width: min(62vw, 900px); grid-template-columns: minmax(0, 18rem) minmax(0, 1fr); align-items: start; }
  .step-credits .text p { max-width: none; }
  .step-credits .read > p:not(.stat).sources { max-width: 58ch; }
}
@media (max-width: 820px) {
  .step-credits { --shift: 1; }
  .step-credits .text { max-width: none; }
  .step-credits .read > p:not(.stat).sources { font-size: 13px; margin-top: 0.7rem; }
}
/* The stops' divider belongs before the words, not before the byline. */
.step-credits .read > p.byline:not(.stat) { padding-left: 0; border-left: 0; margin-bottom: 0.6rem; }
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  .step-credits .text { width: 80vw; grid-template-columns: 11rem minmax(0, 1fr); }
  .step-credits .byline { font-size: 17px; }
  .step-credits .read > p:not(.stat).sources { max-width: none; font-size: 12px; line-height: 1.4; }
  #soft[data-shape='stop'] { width: 92vw; }
}

/* Glass: the menus and cards are the blurred map, barely tinted, not white. The cards hold small text, so a
   little more tint. On the pills themselves, not on #site: a blur inside a fading parent sees nothing behind it. */
:root {
  --glass: color-mix(in srgb, var(--paper) 42%, transparent);
  --glass-card: color-mix(in srgb, var(--paper) 58%, transparent);
  --glass-filter: blur(20px) saturate(1.3);
}
#dests, #site button, #hint { background: var(--glass); backdrop-filter: var(--glass-filter); -webkit-backdrop-filter: var(--glass-filter); }
.legend, .tspanels { background: var(--glass-card); backdrop-filter: var(--glass-filter); -webkit-backdrop-filter: var(--glass-filter); }
#hint { backdrop-filter: var(--glass-filter); }

/* Every hover and press eases its colour, including a pill turning on and off when the destination changes. */
#dests button, #site button, #enter, #ts-play {
  -webkit-tap-highlight-color: transparent;
  transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease, transform 0.18s ease, opacity 0.6s ease;
}
#dests button:hover { background: transparent; }
#site button:hover { background: var(--glass); }
#dests button[aria-pressed='true']:hover, #site button[aria-pressed='true']:hover { background: var(--ink); color: var(--paper); }
@media (hover: hover) {
  #dests button:not([aria-pressed='true']):hover { background: color-mix(in srgb, var(--ink) 12%, transparent); }
  #site button:not([aria-pressed='true']):hover { background: color-mix(in srgb, var(--ink) 14%, var(--glass)); }
  #enter:hover { background: color-mix(in srgb, var(--ink) 78%, var(--paper)); border-color: color-mix(in srgb, var(--ink) 78%, var(--paper)); color: var(--paper); }
  #ts-play:hover { background: color-mix(in srgb, var(--ink) 78%, var(--paper)); }
}
#dests button:not([aria-pressed='true']):active { background: color-mix(in srgb, var(--ink) 24%, transparent); }
#site button:not([aria-pressed='true']):active { background: color-mix(in srgb, var(--ink) 26%, var(--glass)); }
#enter:active, #ts-play:active { background: color-mix(in srgb, var(--ink) 62%, var(--paper)); color: var(--paper); }
@media (hover: none) { #enter:hover { background: var(--ink); color: var(--paper); } }
#dests button:active, #site button:active, #enter:active, #ts-play:active { transform: scale(0.97); }
.close-text, #story a { transition: text-decoration-thickness 0.2s ease, opacity 0.25s ease; }
.close-text:hover, #story a:hover { opacity: 0.68; }
/* On glass, grey small text goes weak, and it includes a required credit: ink. The chart's colours are data and
   its past years are semi-transparent, so they sit on more of the paper's tint, not on whatever is behind the card. */
.tspanels, .tspanels figcaption, .tspanels .years, .legend, .legend .beta, .legend .note { color: var(--ink); }
.tspanels .plot { padding: 8px 8px 4px; margin-left: 40px; border-radius: 8px; background: color-mix(in srgb, var(--paper) 78%, transparent); }
@media (max-width: 820px) { .tspanels .plot { margin-left: 0; } }
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  .tspanels .photo figcaption { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .tspanels .bars { height: 24px; }
  .tspanels .plot { padding: 5px 6px 3px; }
}
.tspanels .fig { min-width: 0; }

/* Galicia: each class on its own line. */
.stat .cap.lines .cls { display: block; margin-top: 0.2em; }

/* West Kalimantan's words: the place's name is the headline and stays put; the year and number sit in a row
   under it. The block is as tall as the tallest year's (--ts-text-min) and fills from the top, so nothing shifts
   between years. Once arrived, the steps swap with no crossfade of the whole block (the name would flicker):
   only what changes eases in. */
.step-ts h2 { font-size: clamp(44px, 5.8vw, 92px); letter-spacing: -0.03em; margin-bottom: 0.2em; white-space: nowrap; }
.step-ts .stat .yr, .step-ts .stat .num { display: inline-block; font: 300 clamp(34px, 3.8vw, 56px)/1 ui-serif, 'Iowan Old Style', Georgia, serif; letter-spacing: -0.02em; font-variant-numeric: lining-nums tabular-nums; white-space: nowrap; }
.step-ts .stat .yr { margin-right: 0.3em; color: color-mix(in srgb, var(--ink) 55%, transparent); }
@media (min-width: 821px) {
  .step-ts .text { grid-template-columns: minmax(0, 19rem) minmax(0, 1fr); align-items: start; align-content: start; }
  .step-ts .read > p:not(.stat) { min-height: 6.6em; }
}
@media (max-width: 820px) {
  .step-ts .text { display: block; }
  .step-ts h2 { font-size: 36px; margin: 0 0 0.15em; }
  .step-ts .stat { display: block; margin: 0; }
  .step-ts .stat .yr, .step-ts .stat .num { font-size: 30px; }
  .step-ts .stat .cap { margin-top: 0.35em; }
  .tspanels { bottom: calc(216px + 100lvh - 100svh + env(safe-area-inset-bottom)); }
}

/* The card: one surface, one grid, one left edge. */
:root { --ts-s: max(120px, min(17vw, calc(100vh - 556px))); }
.tspanels {
  top: 68px; right: 24px; width: calc(2 * var(--ts-s) + 12px + 36px); padding: 18px; gap: 14px 12px; border-radius: 16px;
  grid-template-rows: auto auto auto auto auto; font-size: 11.5px;
  background: color-mix(in srgb, var(--paper) 76%, transparent);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.35) inset, 0 10px 34px rgba(40, 30, 10, 0.14);
  transition: opacity 0.3s ease, visibility 0s linear 0.3s;
}
.tspanels.on { transition: opacity 0.3s ease; }
.tspanels.on > * { animation: ts-card-in 0.45s ease backwards; }
.tspanels.on > .fig.map { animation-delay: 0.05s; }
.tspanels.on > .timeline { animation-delay: 0.1s; }
.tspanels.on > .legend { animation-delay: 0.15s; }
@keyframes ts-card-in { from { opacity: 0; transform: translateY(5px); } }
.tspanels .fig.photo, .tspanels .fig.map { display: flex; flex-direction: column-reverse; gap: 7px; }
.tspanels .frame { border-radius: 7px; }
.tspanels figcaption { margin: 0; line-height: 1.25; }
.tspanels .yr { font-weight: 600; }
.tspanels .timeline { grid-column: 1 / -1; display: grid; grid-template-columns: 30px minmax(0, 1fr); column-gap: 12px; row-gap: 8px; align-items: center; }
.tspanels #ts-ctl { display: contents; opacity: 1; }
.tspanels #ts-play { grid-column: 1; width: 30px; height: 30px; font-size: 11px; display: grid; place-items: center; padding: 0; }
/* The slider's thumb sits over the middle of each year's bar: the track starts half a bar in. */
.tspanels #ts-year { grid-column: 2; margin: 0 calc(100% / 12 - 7px); width: auto; height: 18px; background: none; -webkit-appearance: none; appearance: none; cursor: pointer; }
#ts-year::-webkit-slider-runnable-track { height: 2px; border-radius: 2px; background: color-mix(in srgb, var(--ink) 28%, transparent); }
#ts-year::-moz-range-track { height: 2px; border-radius: 2px; background: color-mix(in srgb, var(--ink) 28%, transparent); }
#ts-year::-webkit-slider-thumb { -webkit-appearance: none; width: 14px; height: 14px; margin-top: -6px; border-radius: 50%; background: var(--ink); box-shadow: 0 0 0 3px var(--paper); transition: transform 0.2s ease; }
#ts-year::-moz-range-thumb { width: 14px; height: 14px; border: 0; border-radius: 50%; background: var(--ink); box-shadow: 0 0 0 3px var(--paper); }
#ts-year:hover::-webkit-slider-thumb { transform: scale(1.15); }
.tspanels .fig.chart { grid-column: 2; }
.tspanels .plot { padding: 0; margin: 0; background: none; border-radius: 0; }
.tspanels .bars { height: clamp(52px, 11vh, 104px); }
.tspanels .bars g { opacity: 0.36; }
.tspanels .years { margin-top: 6px; font-size: 11px; }
/* The key, inside the card (main.ts measureShift): no chip of its own. */
.tspanels .legend.tskey { position: static; grid-column: 1 / -1; width: auto; padding: 11px 0 0; gap: 8px; border-top: 1px solid color-mix(in srgb, var(--ink) 16%, transparent); border-radius: 0; background: none; backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none; font-size: 11.5px; opacity: 1; transform: none; }
.tspanels .legend.tskey .layer.v2 { grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr); gap: 4px 12px; font-size: 11.5px; }
@media (max-width: 820px) {
  :root { --ts-s: 0px; }
  .tspanels { top: auto; left: 12px; right: 12px; width: auto; padding: 12px; gap: 10px 8px; }
  .tspanels .bars { height: 44px; }
}
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  :root { --ts-s: min(15vw, 30vh); }
  .tspanels { top: 8px; right: 12px; left: auto; bottom: auto; width: calc(2 * var(--ts-s) + 8px + 24px); padding: 12px; gap: 7px 8px; }
  .tspanels .bars { height: 24px; }
  .tspanels figcaption { font-size: 10px; }
  .tspanels .photo figcaption { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .step-ts h2 { font-size: 26px; white-space: normal; }
  .step-ts .stat .yr, .step-ts .stat .num { font-size: 26px; }
  .step-ts .text { width: 26vw; }
  .step-ts .stat .cap .cls { display: block; }
}
@media (max-width: 820px) {
  .tspanels { bottom: calc(244px + 100lvh - 100svh + env(safe-area-inset-bottom)); }
  .tspanels .bars { height: 38px; }
}
@media (min-width: 821px) { #soft[data-shape='ts'] { width: 72vw; } }

/* West Kalimantan's caption: the swatches separate the classes, so no dots to dangle at a line's end. */
.step-ts .stat .cap { display: flex; flex-wrap: wrap; gap: 0.2em 0.9em; }
@media (max-height: 500px) and (min-aspect-ratio: 3/2) { .step-ts .stat .cap { flex-direction: column; } .step-ts .stat .cap .cls { display: inline; } }

/* ---- West Kalimantan: the card, final ---- */

/* The pane of glass that grows from the cubes into the card: the card's own surface, no outline. */
#ts-seed { display: none; position: fixed; z-index: 1; pointer-events: none; background: var(--card-glass); backdrop-filter: var(--card-blur); -webkit-backdrop-filter: var(--card-blur); box-shadow: 0 10px 40px rgba(40, 30, 10, 0.12); }

/* A calmer surface than the chips: the map behind it dissolves into a wash, so nothing competes with the data. */
:root { --card-glass: color-mix(in srgb, var(--paper) 80%, transparent); --card-blur: blur(36px) saturate(0.85); --card-rule: color-mix(in srgb, var(--ink) 13%, transparent); --ts-s: max(120px, min(18vw, calc(100vh - 404px))); }
.tspanels {
  top: 68px; right: 24px; width: calc(2 * var(--ts-s) + 8px + 32px); padding: 16px; gap: 8px; border-radius: 18px;
  grid-template-rows: none; grid-auto-rows: auto; font-size: 12px; line-height: 1.3;
  background: var(--card-glass); backdrop-filter: var(--card-blur); -webkit-backdrop-filter: var(--card-blur);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.28), 0 10px 40px rgba(40, 30, 10, 0.12);
}
/* The beta label heads the card. */
.tspanels .legend.tskey { display: block; position: static; grid-column: 1 / -1; padding: 0 0 4px; border: 0; border-radius: 0; font-size: 12px; background: none; backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none; opacity: 1; transform: none; }
.tspanels .legend.tskey .layers { display: none; }
.tspanels .legend .beta { white-space: normal; color: color-mix(in srgb, var(--ink) 70%, transparent); }
.tspanels .legend .beta b { padding: 1px 6px; margin-right: 2px; border: 0; border-radius: 4px; color: var(--paper); background: var(--ink); font-size: 10.5px; letter-spacing: 0.02em; }

/* The pictures: the same size, the caption under each. */
.tspanels .fig.photo, .tspanels .fig.map { display: block; }
.tspanels .frame { border-radius: 10px; }
.tspanels .frame img { transition: none; opacity: 0; }
.tspanels .frame img.on { opacity: 1; transition: opacity 0.55s ease; }
.tspanels figcaption { margin: 6px 0 0; font-size: 11.5px; line-height: 1.25; color: color-mix(in srgb, var(--ink) 72%, transparent); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.tspanels .yr { color: var(--ink); font-weight: 600; }

/* Time: play, the slider over the bars, and the year's shares by name beside them. */
.tspanels .timeline { grid-column: 1 / -1; margin-top: 6px; padding-top: 12px; border-top: 1px solid var(--card-rule); display: grid; grid-template-columns: 30px minmax(0, 1fr) minmax(0, 0.92fr); grid-template-rows: 30px auto; column-gap: 10px; row-gap: 8px; align-items: center; }
.tspanels #ts-ctl { display: contents; opacity: 1; }
.tspanels #ts-ctl output { display: none; }
#ts-play { display: grid; place-items: center; padding: 0; color: var(--paper); }
.tspanels #ts-play { grid-column: 1; grid-row: 1; width: 30px; height: 30px; }
#ts-play svg { grid-area: 1 / 1; width: 14px; height: 14px; fill: currentColor; stroke: currentColor; stroke-width: 0.7; stroke-linejoin: round; transition: opacity 0.2s ease, transform 0.2s ease; }
#ts-play .i-pause { opacity: 0; transform: scale(0.7); }
#ts-play.is-playing .i-play { opacity: 0; transform: scale(0.7); }
#ts-play.is-playing .i-pause { opacity: 1; transform: none; }
.tspanels .rail { position: relative; grid-column: 2; grid-row: 1; height: 30px; display: grid; grid-template-columns: repeat(var(--n, 6), 1fr); align-items: center; justify-items: center; }
.tspanels .rail::before { content: ''; position: absolute; top: 50%; left: calc(50% / var(--n, 6)); right: calc(50% / var(--n, 6)); height: 1px; background: color-mix(in srgb, var(--ink) 30%, transparent); }
.tspanels .rail .stop { position: relative; width: 5px; height: 5px; border-radius: 50%; background: color-mix(in srgb, var(--ink) 42%, var(--paper)); }
.tspanels .rail .handle { position: absolute; top: 50%; left: calc(50% / var(--n, 6)); width: 14px; height: 14px; margin: -7px 0 0 -7px; border-radius: 50%; background: var(--ink); box-shadow: 0 0 0 3px var(--paper); transition: left 0.45s cubic-bezier(0.3, 0, 0.2, 1), transform 0.2s ease; }
/* The real input, invisible, stretched so its stops land on the rail's. */
.tspanels #ts-year { grid-column: 2; grid-row: 1; position: relative; z-index: 1; width: calc(100% - 100% / 6 + 14px); height: 30px; margin: 0 0 0 calc(100% / 12 - 7px); opacity: 0; cursor: pointer; justify-self: start; }
.tspanels .timeline:has(#ts-year:hover) .handle { transform: scale(1.15); }
.tspanels .timeline:has(#ts-year:focus-visible) .handle { outline: 2px solid var(--ink); outline-offset: 3px; }
.tspanels .fig.chart { grid-column: 2; grid-row: 2; }
.tspanels .plot { padding: 0; margin: 0; background: none; border-radius: 0; cursor: pointer; }
.tspanels .bars { height: clamp(64px, 11vh, 110px); shape-rendering: geometricPrecision; }
.tspanels .bars g { opacity: 0.38; transition: opacity 0.45s ease; }
.tspanels .bars g:hover { opacity: 0.62; }
.tspanels .bars g.cur { opacity: 1; }
.tspanels .years { margin-top: 5px; font-size: 11px; color: color-mix(in srgb, var(--ink) 66%, transparent); transition: none; }
.tspanels .years span { transition: color 0.3s ease; }
.tspanels .readout { grid-column: 3; grid-row: 1 / span 2; align-self: stretch; margin: 0 0 0 8px; padding-left: 14px; border-left: 1px solid var(--card-rule); display: flex; flex-direction: column; justify-content: center; gap: 6px; }
.tspanels .readout .row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.tspanels .readout dt { display: flex; align-items: baseline; gap: 7px; min-width: 0; }
.tspanels .readout .sw { flex: none; width: 9px; height: 9px; border-radius: 2px; transform: translateY(0.5px); }
.tspanels .readout dd { margin: 0; font-weight: 600; font-variant-numeric: lining-nums tabular-nums; }
.tspanels.on > .timeline { animation-delay: 0.08s; }

/* The words between years: the title stays, what changes crosses over. */
body.explore .step-ts.swap-in, body.explore .step-ts.swap-out { opacity: 1; visibility: visible; transition: none; }
.step-ts.swap-out h2, .step-ts.swap-out .close-text { visibility: hidden; }
.step-ts.swap-in .read > * { animation: ts-in 0.45s ease both; }
.step-ts.swap-out .read > * { animation: ts-out 0.45s ease both; }
@keyframes ts-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ts-out { from { opacity: 1; } to { opacity: 0; } }

@media (max-width: 820px) {
  .tspanels { top: auto; left: 12px; right: 12px; width: auto; padding: 12px; }
  .tspanels .timeline { grid-template-columns: 30px minmax(0, 1fr); margin-top: 2px; padding-top: 8px; }
  .tspanels .readout { display: none; }
  .tspanels .bars { height: 38px; }
  .tspanels .legend.tskey { display: none; }
  body > .legend.tskey { display: flex; }
}
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  :root { --ts-s: min(14vw, 26vh); }
  .tspanels { top: 8px; right: 12px; left: auto; width: calc(2 * var(--ts-s) + 8px + 24px); padding: 10px; gap: 5px 8px; }
  .tspanels .legend.tskey { display: block; padding: 0 0 2px; font-size: 10.5px; }
  .tspanels .legend .beta { white-space: normal; line-height: 1.25; }
  .tspanels .legend .beta b { font-size: 9.5px; }
  .tspanels .timeline { grid-template-columns: 26px minmax(0, 1fr); grid-template-rows: 26px auto; margin-top: 2px; padding-top: 6px; row-gap: 2px; }
  .tspanels #ts-play { width: 26px; height: 26px; }
  .tspanels .rail, .tspanels #ts-year { height: 26px; }
  .tspanels .readout { display: none; }
  .tspanels .bars { height: 20px; }
  .tspanels figcaption { font-size: 10px; margin-top: 3px; white-space: normal; }
  .tspanels .years { font-size: 10px; margin-top: 2px; }
}

/* Menu text: from the numbers, caps are centred, but most letters are lowercase and sit below that middle, so the
   word looks low. Half a pixel up, between the middle of the caps and of the lowercase. */
#dests button { height: 32px; padding-block: 0 1px; }
#site button { padding-block: 0 2px; }
@media (max-width: 820px) {
  #dests button { height: 40px; padding-block: 0 2px; }
  #site button { padding-block: 0 1px; }
}
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  #dests button { height: 28px; padding-block: 0 1px; }
}

/* The play button: the disc and the icons are one drawing. As separate layers the browser snaps the icon to whole
   pixels and not the disc, so on the card's fractional position they part by up to half a pixel. */
#ts-play, .tspanels #ts-play, #ts-play:hover, #ts-play:active { background: none; }
#ts-play svg { display: block; grid-area: auto; width: 100%; height: 100%; fill: none; stroke: none; overflow: visible; }
#ts-play .i-disc { fill: var(--ink); transition: fill 0.3s ease; }
#ts-play .i-play, #ts-play .i-pause { fill: var(--paper); stroke: var(--paper); stroke-width: 0.8; stroke-linejoin: round; transform-origin: 15px 15px; transition: opacity 0.2s ease, transform 0.2s ease; }
#ts-play .i-pause { opacity: 0; transform: scale(0.7); }
#ts-play.is-playing .i-play { opacity: 0; transform: scale(0.7); }
#ts-play.is-playing .i-pause { opacity: 1; transform: none; }
@media (hover: hover) { #ts-play:hover .i-disc { fill: color-mix(in srgb, var(--ink) 78%, var(--paper)); } }
#ts-play:active .i-disc { fill: color-mix(in srgb, var(--ink) 62%, var(--paper)); }

/* The card: mostly blurred map, with just enough of the paper's tint to hold its small text, now all in ink. */
:root { --card-glass: color-mix(in srgb, var(--paper) 46%, transparent); --card-blur: blur(48px) saturate(1.2); --card-rule: color-mix(in srgb, var(--ink) 20%, transparent); }
.tspanels figcaption, .tspanels .years, .tspanels .legend .beta { color: var(--ink); }
.tspanels .bars g { opacity: 0.5; }
.tspanels .bars g:hover { opacity: 0.72; }
.tspanels .bars g.cur { opacity: 1; }
.tspanels .rail::before { background: color-mix(in srgb, var(--ink) 45%, transparent); }
.tspanels .rail .stop { background: var(--ink); }

/* The light around the place's cubes. A box-shadow paints only outside its box, so the cubes keep their true colour
   in the middle and the light falls off softly from their edges. No outline. */
#ts-glow { position: fixed; z-index: 1; left: 0; top: 0; width: var(--g, 40px); height: var(--g, 40px); pointer-events: none; opacity: 0; transition: opacity 0.5s ease; --light: 255, 252, 238; }
#ts-glow.on { opacity: 1; transition: opacity 0.9s ease; }
#ts-glow i { position: absolute; inset: 0; border-radius: calc(var(--g) * 0.08); }
#ts-glow .steady { box-shadow: 0 0 calc(var(--g) * 0.22) calc(var(--g) * 0.05) rgba(var(--light), 0.95), 0 0 calc(var(--g) * 0.9) calc(var(--g) * 0.22) rgba(var(--light), 0.6), 0 0 calc(var(--g) * 2.2) calc(var(--g) * 0.5) rgba(var(--light), 0.28); animation: ts-breathe 3.2s ease-in-out infinite; }
#ts-glow .ring { opacity: 0; box-shadow: 0 0 calc(var(--g) * 0.4) calc(var(--g) * 0.1) rgba(var(--light), 0.95); }
#ts-glow.arrive .ring { animation: ts-ripple 1.5s cubic-bezier(0.2, 0.6, 0.3, 1) 2 both; }
#ts-glow.arrive .ring + .ring { animation-delay: 0.6s; animation-iteration-count: 1; }
@keyframes ts-breathe { 0%, 100% { opacity: 0.72; } 50% { opacity: 1; } }
@keyframes ts-ripple { 0% { opacity: 0; transform: scale(1); } 15% { opacity: 0.95; } 100% { opacity: 0; transform: scale(3.4); } }
@media (prefers-reduced-motion: reduce) { #ts-glow .steady { animation: none; } }
body.booting .step, body.booting #soft { transition: none !important; }

/* ---- One margin ---- */
/* Everything at the edge of the screen shares it: the pills, the key, the card and the words. It grows on big screens. */
:root { --edge: clamp(24px, calc(3.33vw - 24px), 40px); --bar: 40px; }
#dests, #site { top: var(--edge); }
#site { left: var(--edge); right: var(--edge); }
#hint { top: calc(var(--edge) + var(--bar) + 8px); }
.tspanels { top: calc(var(--edge) + var(--bar) + 12px); right: var(--edge); }

@media (min-width: 821px) and (min-height: 501px) {
  .legend:not(.tskey) { right: var(--edge); bottom: var(--edge); }
  body.explore .step-green, body.explore .step-stop, body.explore .step-ts, body.explore .step-spread, body.explore .step-credits { padding: 0 var(--edge) var(--edge); align-items: flex-end; }
  /* The last line of words sits on the margin: no space under its baseline, nor under the blocks that hold it. */
  .step .text > :last-child, .step .read > p, .step .read > p:not(.stat), .text .stat, .step-spread .text p, .step-credits .read > p.byline:not(.stat) { margin-bottom: 0; }
  .step .read > p:not(.stat), .stat .cap:not(.lines), .stat .cap.lines .cls:last-child, .step-spread .text p, .text .lede { text-box: trim-end cap alphabetic; }
  .step-ts .text { width: calc(100vw - 2 * var(--edge) - (2 * var(--ts-s) + 40px) - 40px); align-items: end; }
  .step-ts .stat { align-self: end; }
  .step-ts .stat .cap .cls { text-box: trim-end cap alphabetic; }
  .step-ts .read > p:not(.stat) { align-self: start; }
  .step-credits .text { align-items: end; }
  .step-credits .read > p.byline:not(.stat) { align-self: end; }
  .step-credits .byline { text-box: trim-end cap alphabetic; }
}
/* A phone: one margin here too, the bar's top setting where the words end. */
@media (max-width: 820px) {
  :root { --edge: 14px; }
  #dests { top: auto; left: var(--edge); right: var(--edge); bottom: calc(var(--edge) + env(safe-area-inset-bottom)); }
  #site { top: 66px; }
  #hint { top: 102px; }
  .legend:not(.tskey), .legend.tskey { right: 0; bottom: auto; padding-inline: var(--edge); }
  body > .legend.tskey { padding-inline: var(--edge); }
  body.explore .step, body.explore .step-green, body.explore .step-stop, body.explore .step-ts, body.explore .step-spread, body.explore .step-credits { padding-inline: var(--edge); padding-bottom: calc(2 * var(--edge) + 48px + env(safe-area-inset-bottom)); }
  .step .text { width: 100%; }
  .tspanels { top: auto; left: var(--edge); right: var(--edge); }
}
/* A phone on its side: the two menus along the bottom, the words above them. */
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  :root { --edge: 12px; --bar: 36px; }
  #dests { top: auto; left: 50%; right: auto; bottom: var(--edge); }
  #site { top: auto; bottom: var(--edge); }
  .tspanels { top: var(--edge); bottom: auto; left: auto; right: var(--edge); }
  .legend:not(.tskey) { padding-inline: var(--edge); }
  body.explore .step-stop, body.explore .step-spread { padding: 66px var(--edge) calc(2 * var(--edge) + var(--bar)); }
  body.explore .step-ts { padding: var(--edge) var(--edge) calc(2 * var(--edge) + var(--bar)); }
  body.explore .step-credits { padding: 66px var(--edge) calc(2 * var(--edge) + var(--bar)); }
  body.explore .step-green { padding: 0 var(--edge) calc(2 * var(--edge) + var(--bar)); }
}

/* Focus: the site's ink instead of the browser's blue, and only for the keyboard. */
button:focus, a:focus, #ts-year:focus { outline: none; }
button:focus-visible, a:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* The phone bars scroll, which would clip a ring outside the pill. */
@media (max-width: 820px), (max-height: 500px) and (min-aspect-ratio: 3/2) { #dests button:focus-visible { outline-offset: -2px; } }
/* An empty status line takes no room, so the words end on the margin. */
.ts-status:empty { display: none; }
/* The figures' own side space, which a canvas cannot measure for lining digits (alignInk covers the titles). */
.step-stop .stat .num, .step-ts .stat .yr { margin-left: -0.03em; }
/* West Kalimantan: the block is as tall as the tallest year's. The last row takes any spare height, so the caption
   ends on the margin in every year and the title never moves. */
@media (min-width: 821px) and (min-height: 501px) {
  .step-ts .text { grid-template-rows: auto auto minmax(0, 1fr); align-content: stretch; }
  .step-ts .read > p:not(.stat) { align-self: stretch; }
}

/* West Kalimantan's words: the name, the year and a sentence. The block stays as tall as the tallest year's on every
   layout (--ts-text-min), filling from the top, so the name and year never move. */
.step-ts .stat .yr { display: block; margin-right: 0; color: var(--ink); text-box: trim-end cap alphabetic; }
@media (max-width: 820px) {
  /* The name and year share a line; the sentence runs under them. */
  .step-ts .text { display: grid; grid-template-columns: max-content minmax(0, 1fr); column-gap: 12px; align-items: baseline; align-content: start; min-height: var(--ts-text-min, 0px); }
  .step-ts .read { display: contents; }
  .step-ts .close-text, .step-ts .ts-status { grid-column: 1 / -1; }
  .step-ts .text > .read > p:not(.stat) { display: block; grid-column: 1 / -1; margin: 6px 0 0; max-width: none; padding: 0; border: 0; font-size: 14.5px; line-height: 1.38; }
  .step-ts h2 { margin: 0; font-size: 34px; }
  .step-ts .stat { margin: 0; }
  .step-ts .stat .yr { font-size: 34px; color: color-mix(in srgb, var(--ink) 55%, transparent); text-box: normal; }
  .tspanels { bottom: calc(248px + 100lvh - 100svh + env(safe-area-inset-bottom)); }
  #soft[data-shape='ts'] { height: 32vh; }
}
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  .step-ts .text { display: block; width: 30vw; min-height: var(--ts-text-min, 0px); }
  .step-ts .read { display: block; }
  .step-ts .text > .read > p:not(.stat) { display: block; margin: 8px 0 0; font-size: 12.5px; line-height: 1.35; }
  .step-ts h2 { font-size: 26px; white-space: normal; }
  .step-ts .stat .yr { font-size: 26px; margin-top: 2px; color: color-mix(in srgb, var(--ink) 55%, transparent); }
  #soft[data-shape='ts'] { width: 44vw; height: 100vh; }
}

/* Desktop: the year level with the first line of the sentence, the divider running down to the margin. */
@media (min-width: 821px) and (min-height: 501px) {
  .step-ts .text { grid-template-columns: max-content minmax(0, 1fr); column-gap: clamp(20px, 2.4vw, 40px); }
  .step-ts .stat { align-self: start; }
  .step-ts .stat .yr { text-box: trim-both cap alphabetic; }
  .step-ts .read > p:not(.stat) { max-width: 46ch; min-height: 0; text-box: trim-both cap alphabetic; padding-block: 0; }
}
/* A phone on its side, of any width: the card keeps its own height and holds the key, and the words run from the margin. */
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  .tspanels { bottom: auto; }
  .tspanels .legend.tskey { display: block; }
  .step-ts .text > .read > p:not(.stat) { padding: 0; border: 0; }
  #soft[data-shape='ts'] { height: 100vh; }
}
/* A small phone on its side: the stops take the same two columns as larger ones, so the words clear the menu. */
@media (max-height: 500px) and (min-aspect-ratio: 3/2) and (max-width: 820px) {
  .step-stop .text { display: grid; width: min(86vw, 640px); max-width: none; grid-template-columns: minmax(0, 11rem) minmax(0, 1fr); column-gap: 16px; align-items: start; }
  .step-stop .close-text, .step-stop h2 { grid-column: 1 / -1; }
  .step-stop h2 { font-size: 30px; }
  .step-stop .read { display: contents; }
  .step-stop .stat { margin: 0; }
  .step-stop .stat .num { font-size: 26px; }
  .step-stop .stat .cap { font-size: 12px; }
  .step-stop .text > .read > p:not(.stat) { margin: 0; font-size: 12.5px; line-height: 1.35; }
  #soft[data-shape='stop'] { width: 96vw; height: 100vh; }
}

/* West Kalimantan's camera is free too, but the helper would sit over its card until the first move. */
body.in-ts #hint { opacity: 0; }

/* Phones. In free explore a step fills the visible screen, so its padding needs no room for Safari's toolbars. */
@media (max-width: 820px) and (min-height: 501px), (max-width: 820px) and (orientation: portrait) {
  /* The intro's first view has no menu, so its words sit at the foot. On a return the menu is back, and they make way
     for it. Leaving waits for the words to fade, so they never jump while they can be seen. */
  body.explore .step-green { padding-bottom: calc(2 * var(--edge) + env(safe-area-inset-bottom)); transition: opacity 0.95s ease-in-out, visibility 0s linear 0.95s, padding-bottom 0s linear 0.95s; }
  body.explore.seen .step-green.on { padding-bottom: calc(2 * var(--edge) + 48px + env(safe-area-inset-bottom)); transition: opacity 0.7s ease 0.1s; }

  /* West Kalimantan: the name and year, one sentence, the two images, the years and the required small print, with
     the card resting on the words whatever their height. No bars, no share list, no Intro and About over the card. */
  .step-ts .close-text { margin-bottom: 4px; }
  body.in-ts #site button { opacity: 0; pointer-events: none; }
  body > .legend.tskey { display: none; }
  .tspanels { bottom: calc(2 * var(--edge) + 48px + env(safe-area-inset-bottom) + var(--ts-text-min, 150px) + 10px); padding: 10px; gap: 7px 8px; border-radius: 16px; }
  .tspanels .legend.tskey { display: flex; order: -1; flex-direction: column; left: auto; right: auto; top: auto; padding: 0; gap: 5px; border-top: 0; min-width: 0; }
  .tspanels .legend.tskey .beta { font-size: 11px; }
  .tspanels .legend.tskey .layers { display: block; min-width: 0; } /* the map's colours: the share list that carries them on desktop is gone */
  .tspanels .legend.tskey .layer.v2 { display: flex; gap: 12px; font-size: 11px; }
  .tspanels .legend.tskey .swatch { width: 10px; height: 10px; }
  .tspanels .fig.photo, .tspanels .fig.map { gap: 4px; }
  .tspanels figcaption { font-size: 10.5px; }
  .tspanels .timeline { padding-top: 2px; margin-top: 0; border-top: 0; row-gap: 2px; }
  .tspanels .bars { display: none; }
  .tspanels .years { margin-top: 0; }
}
/* A short phone: smaller images, so the card still fits with the toolbars showing. */
@media (max-width: 820px) and (max-height: 640px) and (orientation: portrait) {
  .tspanels { left: 16vw; right: 16vw; }
  .tspanels .photo figcaption .long { display: none; }
}

/* Phones: the blur grows from nothing rather than fading. Safari shows a fading blur at full strength at once.
   44% is the thinnest veil that keeps the words at 4.5:1 or better on the darkest ground (Amazon, About). */
@media (max-width: 820px), (max-height: 500px) and (min-aspect-ratio: 3/2) {
  #soft, #soft[data-shape] {
    opacity: 1;
    background: color-mix(in srgb, var(--paper) 0%, transparent);
    backdrop-filter: blur(0px) saturate(1); -webkit-backdrop-filter: blur(0px) saturate(1);
    transition: background-color 0.95s ease-in-out, backdrop-filter 0.95s ease-in-out, -webkit-backdrop-filter 0.95s ease-in-out, visibility 0s linear 0.95s;
  }
  #soft.on, #soft.on[data-shape] {
    background: color-mix(in srgb, var(--paper) var(--soft-veil, 44%), transparent);
    backdrop-filter: blur(22px) saturate(0.55); -webkit-backdrop-filter: blur(22px) saturate(0.55);
    transition: background-color 0.7s ease 0.1s, backdrop-filter 0.7s ease 0.1s, -webkit-backdrop-filter 0.7s ease 0.1s;
  }
}

/* The site's name on one line, everywhere. */
.step-green h1 { white-space: nowrap; }

/* About was the story's last step, which had to reach the foot of the page. In explore it just fills the screen. */
body.explore #story .chapter:last-child .step:last-child { min-height: 0; }

/* Phones upright. */
@media (max-width: 820px) and (min-height: 501px), (max-width: 820px) and (orientation: portrait) {
  .step-green h1 { font-size: min(15vw, 96px); }
  /* The map key has no place on the intro, which shows one green. */
  body.in-hero .legend:not(.tskey) { opacity: 0; visibility: hidden; }
  /* The blur reaches just above the words at each stop (main.ts fitSoft), with a soft top edge of a fixed height. */
  #soft[data-shape] {
    left: 0; width: 100vw; height: var(--soft-h, 60vh);
    -webkit-mask-image: linear-gradient(to top, #000 calc(100% - 64px), transparent); mask-image: linear-gradient(to top, #000 calc(100% - 64px), transparent);
  }
}

/* The places, where they don't all fit: a sideways row that dissolves at whichever edge has more to come (main.ts
   destEdges), inside a bar that keeps its shape. */
#dests .row { display: flex; gap: 4px; min-width: 0; }
@property --fade-r { syntax: '<length>'; inherits: false; initial-value: 0px; }
@media (max-width: 820px), (max-height: 500px) and (min-aspect-ratio: 3/2) { .step-spread .close-text { margin-inline: 0; } }
@media (max-width: 1000px), (max-height: 500px) and (min-aspect-ratio: 3/2) {
  #dests { overflow: hidden; }
  #dests .row {
    flex: 1 1 auto; overflow-x: auto; scrollbar-width: none; border-radius: 999px;
    -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--fade-l, 0px), #000 calc(100% - var(--fade-r, 0px)), transparent);
    mask-image: linear-gradient(to right, transparent 0, #000 var(--fade-l, 0px), #000 calc(100% - var(--fade-r, 0px)), transparent);
    transition: --fade-l 0.25s ease, --fade-r 0.25s ease;
  }
  #dests .row::-webkit-scrollbar { display: none; }
  #dests .row.more-l { --fade-l: 44px; }
  #dests .row.more-r { --fade-r: 44px; }
}

/* The name on one line runs further across: the intro's blur reaches further to meet it. */
@media (min-width: 821px) and (min-height: 501px) {
  #soft[data-shape='hero'] { width: 82vw; height: var(--soft-h, 78vh); }
  /* The title is one long line now: its paragraph is a wider column, not a narrow one beneath it. In a narrow
     window it stops short of the land, which the picture cannot clear there. */
  .text .lede { max-width: min(52ch, 46vw); }
}

/* The map key on an upright phone: a pill between Intro and About that opens the list over the map, so the map keeps
   the top of the screen. The pills sit at the top with it. West Kalimantan has its key in the card, so no pill there. */
#key-btn { display: none; }
@media (max-width: 820px) and (min-height: 501px), (max-width: 820px) and (orientation: portrait) {
  #site { top: calc(var(--edge) + env(safe-area-inset-top)); }
  #hint { top: calc(var(--edge) + env(safe-area-inset-top) + 38px); }
  #key-btn { display: block; }
  body.in-hero #key-btn, body:not(:has(> .legend:not(.tskey).on)) #key-btn { opacity: 0; pointer-events: none; }
  body.key-open #key-btn { background: var(--ink); color: var(--paper); }
  body.key-open #hint { opacity: 0; }
  .legend:not(.tskey) {
    top: calc(var(--edge) + env(safe-area-inset-top) + 38px); left: 50%; right: auto; bottom: auto; z-index: 4;
    width: max-content; max-width: calc(100vw - 2 * var(--edge)); padding: 12px 14px; border-radius: 14px; gap: 6px;
    transform: translateX(-50%);
  }
  .legend:not(.tskey).on { opacity: 0; visibility: hidden; transition: opacity 0.2s ease, visibility 0s linear 0.2s; }
  body.key-open .legend:not(.tskey).on { opacity: 1; visibility: visible; transition: opacity 0.2s ease; }
  /* A plain list: everything shows, so nothing scrolls or fades at an edge. */
  .legend:not(.tskey) .layers { overflow: visible; -webkit-mask-image: none; mask-image: none; }
  .legend:not(.tskey) .layer { width: auto; padding-right: 0; }
  .legend:not(.tskey) .layer.types { flex-direction: column; align-items: flex-start; gap: 7px; }
  .legend:not(.tskey) .tail { margin-top: 1px; }
}

/* About sits in a little group at the right of the bar. */
.site-end { display: flex; gap: 4px; align-items: stretch; }
/* Where About leaves no room for the places in the middle: they follow Intro instead, and scroll if they must. */
@media (min-width: 821px) and (max-width: 1000px) and (min-height: 501px), (max-height: 500px) and (min-aspect-ratio: 3/2) {
  #dests { left: calc(var(--edge) + 76px); right: auto; transform: none; max-width: calc(100vw - 2 * var(--edge) - 76px - 100px); }
}
@media (min-width: 821px) and (max-width: 1000px) and (min-height: 501px) {
  body.in-hero:not(.seen) #dests, body:not(.explore) #dests { transform: translateY(-12px); }
}
@media (max-height: 500px) and (min-aspect-ratio: 3/2) {
  body.in-hero:not(.seen) #dests, body:not(.explore) #dests { transform: translateY(12px); }
}

/* World's words are centred, and the colour key sits at the bottom right on their line: the column keeps clear
   of the key on both sides (--key-room: the key's width and a gap). Below 1300 px that would
   leave too narrow a column, so it centres in the room left of the key instead. */
@media (min-width: 821px) and (min-height: 501px) {
  :root { --key-room: 270px; }
  .step-spread .text { width: min(880px, 72vw, 100vw - 2 * (var(--edge) + var(--key-room))); }
}
@media (min-width: 821px) and (max-width: 1299px) and (min-height: 501px) {
  body.explore .step-spread { padding-right: calc(var(--edge) + var(--key-room)); }
  .step-spread .text { width: min(880px, 100%); }
}

/* The hover readout (ui/hover.ts): desktop, what the mouse is over, beside the cursor. The cards' glass; as wide
   as what it says. */
.hover {
  position: fixed; z-index: 4; left: 0; top: 0; width: max-content; max-width: 280px; padding: 9px 12px 8px;
  border-radius: 12px; background: var(--glass-card); backdrop-filter: var(--glass-filter); -webkit-backdrop-filter: var(--glass-filter);
  box-shadow: 0 4px 18px rgba(40, 30, 10, 0.08); color: var(--ink); font-size: 12px; line-height: 1.35;
  pointer-events: none; opacity: 0; transition: opacity 0.15s ease;
}
.hover.on { opacity: 1; }
.hover p { margin: 0; }
.hover .where { font-weight: 600; font-size: 12.5px; }
.hover .at { color: color-mix(in srgb, var(--ink) 74%, transparent); font-variant-numeric: tabular-nums; }
.hover .cover { margin-top: 7px; padding-top: 7px; border-top: 1px solid color-mix(in srgb, var(--ink) 14%, transparent); font-weight: 600; }
.hover ul { list-style: none; margin: 4px 0 0; padding: 0; display: grid; gap: 2px; }
.hover li { display: flex; align-items: center; gap: 7px; }
.hover li i { flex: none; width: 9px; height: 9px; border-radius: 2px; }
.hover li b { margin-left: auto; padding-left: 12px; font-weight: 600; font-variant-numeric: tabular-nums; }
.hover .cell { margin-top: 6px; font-size: 10.5px; color: color-mix(in srgb, var(--ink) 66%, transparent); }

/* The key's colour switch (legend.ts): two halves, "One green" and "Forest types"; an ink highlight slides under
   the one chosen, like a pressed menu pill. Not on the hero, where the reveal button does it. */
.legend .mode { position: relative; display: grid; grid-template-columns: 1fr 1fr; padding: 3px; margin-bottom: 2px; border-radius: 999px; background: color-mix(in srgb, var(--ink) 8%, transparent); }
body.in-hero .legend .mode { display: none; }
.legend .mode .thumb { position: absolute; top: 3px; bottom: 3px; left: 3px; width: calc(50% - 3px); border-radius: 999px; background: var(--ink); transition: transform 0.35s cubic-bezier(0.3, 0.7, 0.2, 1); }
.legend .mode.types .thumb { transform: translateX(100%); }
.legend .mode .end { position: relative; padding: 6px 12px; border: 0; border-radius: 999px; background: none; cursor: pointer; white-space: nowrap; font: 500 12px/1 ui-sans-serif, -apple-system, 'Helvetica Neue', Arial, sans-serif; color: color-mix(in srgb, var(--ink) 72%, transparent); transition: color 0.3s ease; -webkit-tap-highlight-color: transparent; }
.legend .mode .end:hover { color: var(--ink); }
.legend .mode .end[aria-pressed='true'] { color: var(--paper); cursor: default; }
.legend .mode .end:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* A phone turned sideways keeps its key a thin row at the top: no room for the switch there (yet). */
@media (max-height: 500px) and (min-aspect-ratio: 3/2) { .legend .mode { display: none; } }
