/* Samples From A Changing Ocean — editorial field-report styling
   Accent: series-blue from the dataviz reference palette (#2a78d6 / #3987e5 dark).
   Everything else stays grayscale so the accent carries all the meaning. */

/* Roboto served from the local ../roboto/ folder, so the site renders
   identically offline and on GitHub Pages with no third-party request.

   WOFF2 first, the TTF behind it. The .woff2 files are built from the
   TTFs by tools/ttf-to-woff2.py and are transport only — the TTFs stay
   the source of truth, and any browser that cannot read WOFF2 falls
   through to them.

   Every weight is its own download, and a face that has not arrived is
   not just late: the browser drops the whole family for that weight and
   takes Segoe UI instead. So Roboto-Bold arriving a moment after
   Roboto-Regular put the page's 700-weight text — the footer buttons
   above all — in Segoe UI Bold while everything at 400 rendered in
   Roboto and looked right. Measured, the buttons came out 161.2px wide
   against Roboto Bold's 154.4, on a capital H one pixel shorter. Halving
   the wire size is what keeps that from happening.

   font-display is block rather than swap for the same reason. Under
   swap, a face still in flight paints in the fallback and snaps when it
   lands; under block it stays invisible and paints once, correctly. The
   page is already held back until the fonts are ready (see the reveal in
   assets/stage.js), so in the ordinary case this is invisible — what it
   buys is that a slow face can never show the wrong one. */
@font-face {
  font-family: "Roboto";
  src: url("../roboto/Roboto-Light.woff2") format("woff2"),
       url("../roboto/Roboto-Light.ttf") format("truetype");
  font-weight: 300;
  font-style: normal;
  font-display: block;
}
/* Roboto Mono — used by the landing credits block. The file is not in the
   repo yet; until it is dropped into roboto/, the stack below falls back
   to the system monospace face. */
@font-face {
  font-family: "Roboto Mono";
  src: url("../roboto/RobotoMono-Regular.woff2") format("woff2"),
       url("../roboto/RobotoMono-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}
@font-face {
  font-family: "Roboto";
  src: url("../roboto/Roboto-Regular.woff2") format("woff2"),
       url("../roboto/Roboto-Regular.ttf") format("truetype");
  font-weight: 400;
  font-style: normal;
  font-display: block;
}
@font-face {
  font-family: "Roboto";
  src: url("../roboto/Roboto-Bold.woff2") format("woff2"),
       url("../roboto/Roboto-Bold.ttf") format("truetype");
  font-weight: 700;
  font-style: normal;
  font-display: block;
}
/* The entry descriptions are italic. Without a real italic face the
   browser fakes one by slanting the upright, which is visibly cruder
   than Roboto's drawn italic. */
@font-face {
  font-family: "Roboto";
  src: url("../roboto/Roboto-Italic.woff2") format("woff2"),
       url("../roboto/Roboto-Italic.ttf") format("truetype");
  font-weight: 400;
  font-style: italic;
  font-display: block;
}
/* Used by the bold 2 in CO2, which sits inside an italic run. Without a
   real bold-italic the browser smears the upright italic instead. */
@font-face {
  font-family: "Roboto";
  src: url("../roboto/Roboto-BoldItalic.woff2") format("woff2"),
       url("../roboto/Roboto-BoldItalic.ttf") format("truetype");
  font-weight: 700;
  font-style: italic;
  font-display: block;
}

:root {
  color-scheme: light;
  --page:            #f5f5f4;
  --surface:         #fcfcfb;
  --text-primary:    #333333;
  --text-secondary:  #333333;
  --text-muted:      #333333;
  --text-faint:      #333333;
  --grid:            #e4e2d9;
  --border:          rgba(23,23,21,0.12);
  --accent:          #2a78d6;
  --accent-soft:     rgba(42,120,214,0.16);
  /* One inset for every edge-anchored element, so the margin off the
     window is identical on all four sides. Subtracting the letterbox
     band measures it from the window rather than the canvas. */
  --edge:            30px;
  /* Every page's copy column is a separate export, each one cropped to
     its own longest line — 593.96 on the landing, 594.82 on Entry 05,
     584.83 on Entry 04 — so their right edges are ragged-right ends, not
     a shared margin. Anchoring them all 30 off the window's right put
     those ragged ends on one x and let the LEFT edge slide, which is the
     edge the eye actually reads a left-aligned column by: Entry 04's
     prose started 10px right of the other two. So the widest column is
     the one that keeps the 30 inset, and the narrower ones are pushed
     further in by their own shortfall, which lands all three left edges
     on 1295.18. */
  --copy-w-max:      594.82px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--page);
  color: var(--text-primary);
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  /* Stops click-and-drag anywhere on the page from producing a text/
     selection highlight — only the RAD chart itself should respond to
     clicks. Re-enabled per-element (e.g. body copy) if/when real text
     content is added later. */
  user-select: none;
  -webkit-user-select: none;
}

a { color: inherit; }

/* ---- Fixed 1920x1080 design canvas, scaled to fit the window as one unit ----
   Every element inside .stage is positioned in true design pixels; nothing is
   individually re-centered or resized — the whole stage scales together so
   positions match the exported mockups at any window size. */
.viewport-frame {
  position: fixed;
  inset: 0;
  overflow: hidden;
  background: var(--page);
}

/* Grid tiles infinitely across the whole window, at the exact cell size
   measured from BACKGROUND.svg (1920/101 x 1080/57 design px), so it never
   stops short at the stage edges on non-16:9 windows. stage.js keeps its
   background-size in lockstep with the stage's scale factor so the pattern
   lines up seamlessly with anything drawn inside the stage. Lines are
   #c1c1c1 at 0.12 so the grid reads as paper the work is set on rather
   than as ruling of its own — quiet enough that the blue and the hairlines
   in the charts are never competing with it. */
.grid-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  background-color: #f5f5f4;
  background-image:
    linear-gradient(to right, rgba(193, 193, 193, 0.12) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(193, 193, 193, 0.12) 1px, transparent 1px);
  background-size: 19.0099px 18.9474px;
}

.stage {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1920px;
  height: 1080px;
  transform: translate(-50%, -50%);
}

/* The scale that fits the canvas to the window can only be worked out in
   JS, so CSS alone would paint the canvas at full 1920x1080 first and
   stage.js would then shrink it — the type visibly starting correct and
   jumping smaller a moment later. Stay hidden until stage.js says the
   page is assembled, so the only frame anyone sees is the finished one.

   The animation is a safety net, not the mechanism: it reveals the page
   after 2.5s no matter what, so a stale cached stage.js (or a JS error)
   can never leave the site permanently blank. It sits well past the 1.6s
   stage.js gives itself, so under any working build the class wins and
   this never fires. */
.viewport-frame {
  visibility: hidden;
  opacity: 0;
  animation: viewport-reveal 0s linear 2.5s forwards;
}
.viewport-frame.is-scaled {
  visibility: visible;
  opacity: 1;
  animation: none;
  transition: opacity 420ms ease;
}

/* ---- Page to page ----
   Both halves of a navigation fade against the same #f5f5f4 that html and
   body are painted, and .grid-bg sits inside the frame — so what crosses
   between two entries is the grid and the artwork, over a ground that
   never changes colour. Fading the body instead, or giving the frame its
   own backdrop, puts a lighter or darker flash in the middle of the
   crossing; this way there is nothing to flash.

   stage.js holds the outgoing page here for as long as the fade lasts
   before it follows the link. Keep the two numbers in step — a longer
   transition than the wait leaves the old page cut off mid-fade, which
   is the jump this replaced. */
.viewport-frame.is-leaving {
  opacity: 0;
  transition: opacity 260ms ease;
}
@keyframes viewport-reveal { to { visibility: visible; opacity: 1; } }

/* A visitor who has asked for less motion still gets the page, just
   without the crossing — and stage.js follows the link at once rather
   than sitting on a fade that is not running. */
@media (prefers-reduced-motion: reduce) {
  .viewport-frame.is-scaled { transition: none; }
  .viewport-frame.is-leaving { opacity: 1; }
}

/* ---- Page slide ------------------------------------------------------
   Where the browser can hold both pages on screen at once, they do not
   cross by fading at all: one slides over the other, the way a card goes
   onto a pile. The navigation is the ordinary one — see
   assets/page-slide.js, which is the only thing that has to be told
   which way a link is going.

   The arriving page is the one that moves, both ways: forwards it comes
   in over the left page from the right edge, back it comes in over it
   from the left. The page being left never moves — it is covered, and
   which edge it is covered from is the whole of what says forwards or
   back.

   Back was an uncover at first, the page being left pulled off to the
   right to show the one under it, which is the truer picture of a pile.
   It asked for the leaving page to be painted over the arriving one,
   which is the reverse of how the two are stacked, and z-index on the
   old snapshot is the only thing that would do it. That worked or it did
   not, depending on the browser, and when it did not the change happened
   in one frame with nothing moving at all. The arriving page is on top
   already, and a move that is always on the page the reader is being
   given cannot be stacked wrongly.

   550ms on a curve that leaves quickly and arrives slowly: the move
   should be over before it is thought about, but it must not stop dead
   against the edge it lands on.

   The opt-in sits inside the motion query because it is the browser that
   acts on it: a visitor who has asked for less motion never starts a
   transition, and gets the fade in assets/stage.js instead. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
}

::view-transition-old(root),
::view-transition-new(root) {
  /* The default crossing is a fade, blended additively so two fading
     pages do not darken where they overlap. Both are wrong for paper
     sliding over paper: it would show one page through the other. */
  animation: none;
  mix-blend-mode: normal;
}

html[data-slide="forward"]::view-transition-old(root) { z-index: 0; }
html[data-slide="forward"]::view-transition-new(root) {
  z-index: 1;
  animation: page-slide-in 550ms cubic-bezier(0.4, 0.24, 0.4, 1);
}

html[data-slide="back"]::view-transition-old(root) { z-index: 0; }
html[data-slide="back"]::view-transition-new(root) {
  z-index: 1;
  animation: page-slide-back 550ms cubic-bezier(0.4, 0.24, 0.4, 1);
}

@keyframes page-slide-in { from { transform: translateX(100%); } }
@keyframes page-slide-back { from { transform: translateX(-100%); } }

/* A page arriving on a slide is already being moved across the window,
   so it cannot spend its first 420ms fading up as well — it shows itself
   at once. stage.js puts this on; assets/page-slide.js is what tells it
   the page came in on a slide. */
.viewport-frame.is-instant.is-scaled { transition: none; }

.page {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.wrap {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 40px;
  width: 100%;
}

/* ---- Meta row (top-left credits, top-right nav) ---- */
.meta-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  padding: 28px 0 0;
  font-size: 12px;
  color: var(--text-muted);
  line-height: 1.6;
}
.meta-top .home-link { color: var(--text-secondary); text-decoration: none; font-weight: 600; }
.meta-top .home-link:hover { color: var(--accent); }

/* ---- Hero / headline ---- */
.hero {
  display: grid;
  grid-template-columns: 1.3fr 1fr;
  gap: 64px;
  padding: 8px 0 40px;
  align-items: start;
}

.hero h1 {
  font-size: clamp(34px, 4.4vw, 54px);
  line-height: 1.05;
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: 0;
}

.hero .lede {
  font-size: 15px;
  line-height: 1.65;
  color: var(--text-secondary);
  margin: 0 0 18px;
}
.hero .lede:last-child { margin-bottom: 0; }
.hero .lede strong { color: var(--text-primary); font-weight: 600; }

/* ---- Sample workspace (landing page) ---- */
.workspace {
  position: relative;
  min-height: 560px;
  margin: 8px 0 20px;
}

.sample-card {
  position: absolute;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  text-decoration: none;
  color: inherit;
  width: 180px;
}

.sample-blob {
  width: 148px;
  height: 148px;
  border-radius: 50%;
  border: 1px solid var(--text-faint);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: border-color 0.2s ease, transform 0.2s ease;
}

.sample-blob::before {
  content: "";
  position: absolute;
  inset: 18%;
  border-radius: 50%;
  background: radial-gradient(circle at 50% 50%, var(--accent) 0%, rgba(42,120,214,0.55) 40%, rgba(42,120,214,0) 72%);
  filter: blur(14px);
  opacity: 0.85;
}

.sample-card:hover .sample-blob { border-color: var(--accent); transform: scale(1.03); }

.sample-index {
  margin-top: 14px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary);
}
.sample-name {
  font-size: 12px;
  color: var(--text-secondary);
  font-style: italic;
  margin-top: 2px;
}

/* ---- Footer meta bar ---- */
.meta-bottom {
  margin-top: auto;
  border-top: 1px solid var(--border);
  padding: 18px 0;
  display: flex;
  gap: 40px;
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.meta-bottom span b { color: var(--text-muted); font-weight: 600; }

/* ---- Detail page ---- */
.detail-hero {
  display: grid;
  grid-template-columns: 1.3fr 1fr;
  gap: 64px;
  padding: 8px 0 32px;
  align-items: start;
}
.detail-hero h1 { font-size: clamp(26px, 3.2vw, 38px); font-weight: 500; margin: 0; letter-spacing: -0.01em; }
.eyebrow { font-size: 12px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 10px; }

.how-to-read {
  font-size: 12px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 8px;
}
.how-to-read-body {
  font-size: 13px;
  color: var(--text-secondary);
  line-height: 1.6;
  max-width: 260px;
}

/* ---- Chart card ---- */
.chart-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 24px 24px 8px;
  margin: 8px 0 28px;
  position: relative;
}

.chart-controls {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 12px;
}

.chart-controls label {
  font-size: 12px;
  color: var(--text-muted);
  margin-right: 8px;
}

select.country-select {
  font-family: inherit;
  font-size: 13px;
  color: var(--text-primary);
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 10px;
}

svg.chart-svg { display: block; width: 100%; height: auto; overflow: visible; }
.gridline { stroke: var(--grid); stroke-width: 1; }
.baseline { stroke: var(--text-faint); stroke-width: 1; }
.axis-label { fill: var(--text-muted); font-size: 11px; }
.threshold-line { stroke: var(--accent); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.6; }
.threshold-label { fill: var(--accent); font-size: 10px; }

.country-line { fill: none; stroke: var(--text-faint); stroke-width: 1.3; transition: stroke 0.15s ease, opacity 0.15s ease; }
.country-line.active { stroke: var(--accent); stroke-width: 2.4; }
.country-line.dim { opacity: 0.35; }

.chart-overlay { position: absolute; inset: 0; cursor: crosshair; }
.chart-overlay:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.crosshair-line { stroke: var(--text-muted); stroke-width: 1; pointer-events: none; }
.hover-dot { pointer-events: none; }

.tooltip {
  position: absolute;
  pointer-events: none;
  background: var(--text-primary);
  color: var(--surface);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 12px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.18);
  opacity: 0;
  transform: translate(-50%, -112%);
  transition: opacity 0.08s ease;
  white-space: nowrap;
  z-index: 5;
}
.tooltip.visible { opacity: 1; }
.tooltip .tt-x { color: rgba(255,255,255,0.6); margin-bottom: 3px; }
.tooltip .tt-val { font-weight: 700; }

.chart-foot { display: flex; justify-content: flex-end; padding: 8px 4px 4px; }
.table-toggle {
  border: none; background: none; color: var(--text-muted);
  font-size: 12px; cursor: pointer; text-decoration: underline;
  font-family: inherit; padding: 4px;
}
.table-toggle:hover { color: var(--text-primary); }

.data-table-wrap { margin-top: 4px; max-height: 260px; overflow: auto; border-top: 1px solid var(--border); display: none; }
.data-table-wrap.open { display: block; }
table.data-table { width: 100%; border-collapse: collapse; font-size: 12px; font-variant-numeric: tabular-nums; }
table.data-table th, table.data-table td { text-align: left; padding: 7px 6px; border-bottom: 1px solid var(--grid); }
table.data-table th { color: var(--text-muted); font-weight: 500; position: sticky; top: 0; background: var(--surface); }

.source-table { font-size: 12px; color: var(--text-secondary); margin: 20px 0; }
.source-table div { display: flex; gap: 14px; padding: 5px 0; border-bottom: 1px solid var(--border); }
.source-table div span:first-child { width: 90px; color: var(--text-muted); flex-shrink: 0; }

.detail-nav {
  border-top: 1px solid var(--border);
  padding: 20px 0 32px;
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-top: auto;
}
.detail-nav a { text-decoration: none; color: var(--text-secondary); }
.detail-nav a:hover { color: var(--accent); }

@media (max-width: 860px) {
  .hero, .detail-hero { grid-template-columns: 1fr; gap: 20px; }
  .workspace { min-height: auto; display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; }
  .sample-card { position: static; width: auto; }
  .meta-bottom { flex-wrap: wrap; gap: 16px 32px; }
}

/* ---- Slide 1: minimal intro ---- */
.intro-stage {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
  text-align: center;
}

.intro-stage h1 {
  font-size: 46px;
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: 0;
  max-width: 640px;
}

.enter-btn {
  display: inline-block;
  text-decoration: underline;
  color: var(--text-primary);
  font-size: 15px;
}

/* ---- Entry title ---- */
.entry-title {
  position: absolute;
  top: 28px;
  left: 0;
  z-index: 3;
  margin: 0;
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 50px;
  font-weight: 400;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  white-space: nowrap;
}

.entry-title-name { margin-left: 0.7em; }

/* Right-hand notes column, rebuilt as real HTML text so it can be
   selected, copied, searched and read by screen readers — an <img> of an
   SVG bakes its text into a picture. Every value below is measured from
   RightText.svg (viewBox 602.2x948.75) so it matches the export:
   block starts at x 16.7 / y 17.1, body 20px Roboto Light with 25px
   baselines, one blank line between paragraphs. */
.entry-copy {
  position: absolute;
  top: 28px;
  right: 0;
  z-index: 3;
  width: 602.2px;
  padding-left: 16.7px;
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 20px;
  font-weight: 300;
  line-height: 25px;
  color: var(--text-primary);
  user-select: text;
}

/* 50px between paragraph baselines = one blank 25px line. */
.entry-copy p { margin: 0 0 25px; }

/* Metadata table: 16px regular, 30px row pitch, value column at x 144. */
.entry-meta {
  display: grid;
  grid-template-columns: 144px 1fr;
  margin: 44px 0 0;
  font-size: 16px;
  font-weight: 400;
  line-height: 20px;
}
.entry-meta dt { font-weight: 400; }
.entry-meta dd { margin: 0; }

/* Entry 05 sets its column out differently from Entry 01: the entry's
   name heads the column rather than the page, and the metadata sits at the
   foot, with the space between left for the chart's legend.

   Its own export, Textright.svg, is 594.82x971.23 with the text starting
   at x 0.15 — no inset, unlike Entry 01's RightText.svg, which carries a
   16.67 one. Taking the width and dropping the padding puts the column in
   the export's coordinates exactly, so the prose, the legend and the
   metadata are all placed off the same numbers instead of two of them
   sitting a padding-width away from the third. */
.entry-copy-footed {
  /* Each entry's export sets its own column width — 594.82 here, 584.83
     on Entry 04 — and everything else about the block is shared, so the
     width is the one thing a page overrides. */
  width: var(--copy-w, 594.82px);
  padding-left: 0;
  min-height: 971.23px;
  /* Anchored the way the landing page's column is: the same 30px from the
     top and the right of the *window*, letterbox included, rather than
     hard against the canvas edge. */
  top: calc(var(--edge) - var(--letterbox-y, 0px));
  right: calc(var(--edge) - var(--letterbox-x, 0px) + var(--copy-w-max) - var(--copy-w, 594.82px));
  /* Roboto Regular, not the Light Entry 01 uses: this export sets its body
     runs in Roboto-Regular, as the landing page's copy does. */
  font-weight: 400;
}

/* "How to read:" is 20px bold here, not the 16px Entry 01 uses: its run
   carries only the bold face and takes the 20px its text block sets, where
   Entry 01's names 16px outright. On the body's 25px line so it keeps the
   column's grid.

   One blank line under the last of the prose, as the export sets it: last
   body baseline 480, this one 530. The margin has to clear 25px to have
   any effect at all — below that the paragraph's own bottom margin
   collapses over it and wins. */
.entry-copy-footed .entry-howto {
  font-size: 20px;
  line-height: 25px;
  margin-top: 25.2px;
}

/* Pinned where the export puts it — 906.9 for the first row's baseline —
   rather than flowed, since everything below "How to read:" is a fixed
   part of the layout rather than something the prose pushes around. */
.entry-copy-footed .entry-meta {
  position: absolute;
  left: 0;
  right: 0;
  /* Set so the last line of this block lands where the landing page's
     credits end, at 1080 less 80 — the two are read as the same block of
     small print across the pages, so they finish on the same line. That is
     6 above the export's own 906.9, which had it a touch below. */
  top: 890px;
  /* Cleared: on an absolutely placed box the flow margin would shift it
     off the coordinate top is putting it on. */
  margin: 0;
  /* The same setting as the landing page's credits, which is what the
     export asks for too: Roboto Mono, and a 10px gap opening the 20px
     lines out to the export's 30px row pitch. */
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  row-gap: 10px;
}

.entry-copy-title {
  /* 22.5, not 25: the title's 30px lines leave more room under the last
     baseline than the 25px body does, and the export wants the same 50px
     between the name and the first line of prose as anywhere else. */
  margin: 0 0 22.5px;
  font-size: 20px;
  font-weight: 400;
  line-height: 30px;
}
/* The entry's number carries the weight; its name stays light beneath. */
.entry-copy-num { display: block; font-weight: 700; }
.entry-copy-name { display: block; font-style: italic; }

/* The legend that goes with the chart. Its pieces are pinned to the
   coordinates Textright.svg gives them rather than flowed one after the
   other, because they are a diagram: two labels sit against a sample dot
   and a leader line runs from each across to its note.

   All four sit above the export's own baselines — the labels by 5px, the
   notes by 9.6px. At the export's figure the two labels hang low against
   the sample, and each leader arrives over the top of the note it points
   at instead of running through it; now the label, the leader and the
   note's first line all read off one level. */
.entry-legend {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* The sample circle, its two leaders and the 96 dots inside it, at the
   export's own weight. Inlined rather than loaded through <img> so it
   inherits the page's ink colour. */
.entry-legend-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  fill: none;
  stroke: var(--text-primary);
  stroke-width: 0.3px;
  stroke-linecap: square;
  stroke-linejoin: round;
}

.entry-legend-country,
.entry-legend-value,
.entry-legend-note {
  position: absolute;
  margin: 0;
  font-size: 10px;
  font-weight: 400;
  line-height: 12px;
  pointer-events: auto;
}

/* The dots are filled, unlike the outline they sit in. */
.entry-legend-dots { fill: var(--text-primary); stroke: none; }

.entry-legend-country { left: 20.94px; top: 586.4px; font-style: italic; }
.entry-legend-value   { left: 34.55px; top: 602.4px; }
/* 152px holds the wrap the export sets — four lines, then three. */
.entry-legend-note    { left: 180.63px; width: 152px; }
/* A further 4.6px, so the leader runs through the middle of the first
   line it points at rather than arriving just over it. */
.entry-legend-note-1  { top: 604.8px; }
.entry-legend-note-2  { top: 665.8px; }

/* ---- Entry 04's column and legend ----
   Same construction as Entry 05's above, off its own export: TextRight.svg
   is 584.83x971.25 with the text block starting at x 0.4. */
.entry-copy-lecz { --copy-w: 584.83px; }

/* Four rows of metadata here against Entry 05's three, and the block is
   read bottom-up: the last line has to land on the same line as the
   landing page's credits and Entry 05's own. One more 20px row and its
   10px gap is 30 higher than Entry 05's 890. */
.entry-copy-lecz .entry-meta { top: 860px; }

/* Positions are the export's own x, and its baselines lifted the way
   Entry 05's are — the labels by about 9.5, the notes by 14.2, which puts
   each leader through the middle of the line it points at. */
.entry-legend-lecz .entry-legend-country { left: 40.55px; top: 586.9px; }
.entry-legend-lecz .entry-legend-value   { left: 28px;    top: 603px; }
.entry-legend-lecz .entry-legend-note    { left: 180.88px; width: 152px; }
.entry-legend-lecz .entry-legend-note-1  { top: 604.6px; }
.entry-legend-lecz .entry-legend-note-2  { top: 677.1px; }

/* The headcount is the one blue thing in the column, and it is blue in
   the chart's labels too — that is what ties the legend's sentence to the
   number under each sphere. */
.entry-legend-headcount { font-weight: 700; color: #547def; }

/* The subregion key: a title, then three rows, each against the mark that
   stands for it — two rings, three dots, one dot. The marks are in the
   artwork at 796.3 / 812.5 / 828.8; the labels take the export's own x
   and its baselines lifted like the ones above. */
.entry-legend-key-title,
.entry-legend-key {
  position: absolute;
  left: 20.34px;
  margin: 0;
  font-size: 10px;
  font-weight: 400;
  line-height: 12px;
  pointer-events: auto;
}
.entry-legend-key-title { left: 0.4px; top: 773.5px; font-weight: 700; }
.entry-legend-key-1 { top: 789.8px; }
.entry-legend-key-2 { top: 806px; }
.entry-legend-key-3 { top: 822.3px; }

/* The legend's own artwork, by role rather than by the export's class
   numbers. Needles and leaders are stroked at the export's 0.3; the
   sample body, the subregion marks and the blue dots are filled. */
.entry-legend-spike { fill: none; stroke: var(--text-primary); stroke-width: 0.3px; stroke-miterlimit: 10; }
/* The sample's spikes, pulled in about its own centre by the same 0.86 the
   chart's needles are drawn at — see NEEDLE_TRIM in assets/lecz-spheres.js,
   and change the two together. Only the spikes move: the body stays r 26.4,
   and the inner ends of the needles disappear under it as they always did.
   The blue dots keep their places, since they scatter through the shell
   rather than sitting on the tips. */
.entry-legend-spikes {
  transform: translate(57.39px, 677.38px) scale(0.86) translate(-57.39px, -677.38px);
}
/* The far half of the sample's needles, drawn shorter and inside the
   near ones — the export's way of showing a sphere from one side. */
.entry-legend-spike-far { opacity: 0.55; }
.entry-legend-leader { fill: none; stroke: var(--text-primary); stroke-width: 0.3px; stroke-linecap: square; stroke-linejoin: round; }
.entry-legend-solid  { fill: var(--text-primary); stroke: none; }
/* 0.92, not the export's 0.6: the chart's dots had to come up to be
   findable at all, and the sample has to be the same blue as the thing it
   is a sample of. */
.entry-legend-people { fill: #547def; opacity: 0.92; stroke: none; }

/* ---- Entry 03's column and legend ----
   Same construction as Entry 04's and Entry 05's above, off its own
   export: TextRight.svg is 584.92x971.19 with the text block starting at
   x 0.15, so the column takes the width and no padding. */
.entry-copy-sla { --copy-w: 584.92px; }

/* Four rows of metadata, as Entry 04 has, and the block is read bottom-up
   so its last line lands on the landing page's credits: the same 860. */
.entry-copy-sla .entry-meta { top: 860px; }

/* Positions are the export's own x, and its baselines lifted the way the
   other two legends are — the labels by about 9.5, the notes by 14.2,
   which puts each leader through the middle of the line it points at. */
.entry-legend-sla .entry-legend-country { left: 39.07px; top: 537.4px; }
.entry-legend-sla .entry-legend-value   { left: 43.62px; top: 557.2px; }
/* Blue and bold, unlike the other two entries' sample values: this one is
   the same mark as the figure beside each chart, so it is set in the same
   ink. */
.entry-legend-sla .entry-legend-value   { font-weight: 700; color: #547def; }
.entry-legend-sla .entry-legend-note    { left: 181.57px; width: 152px; }
.entry-legend-sla .entry-legend-note-1  { top: 559.3px; }
.entry-legend-sla .entry-legend-note-2  { top: 607.8px; }
.entry-legend-sla .entry-legend-note-3  { top: 656.1px; }
.entry-legend-sla .entry-legend-key-title { left: 0.15px; top: 708.5px; }
.entry-legend-sla .entry-legend-key       { left: 20.09px; }
.entry-legend-sla .entry-legend-key-1 { top: 724.8px; }
.entry-legend-sla .entry-legend-key-2 { top: 741.1px; }
.entry-legend-sla .entry-legend-key-3 { top: 757.3px; }

/* The sample chart's own parts, by role rather than by the export's class
   numbers: a bold 0 cm circle, thin contours around it, and the year marks
   at half the size the chart sets them, since the whole sample is drawn at
   about half scale. */
.entry-legend-base    { fill: none; stroke: var(--text-primary); stroke-width: 1px; }
.entry-legend-contour { fill: none; stroke: var(--text-primary); stroke-width: 0.3px; }
.entry-legend-year {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 5px;
  fill: var(--text-primary);
  stroke: none;
}
/* The sample's blue: the same stack of morphed rings the charts carry,
   built on load by assets/sla-contours.js. The fill has to be named here
   rather than left to the elements: .entry-legend-art sets fill: none
   across the whole sample, and a stylesheet rule beats an attribute. */
.entry-legend-rise .e-sla-wash { fill: #547def; stroke: none; }

/* ---- Entry 02's column and legend ----
   Same construction as the other three above, off its own export:
   ImageRight.svg is 584.73x971.29 with the text block starting at x 0.4. */
.entry-copy-precip { --copy-w: 584.73px; }

/* Four rows of metadata, as Entry 03 and Entry 04 have, and the block is
   read bottom-up so its last line lands on the landing page's credits:
   the same 860. */
.entry-copy-precip .entry-meta { top: 860px; }

/* Positions are the export's own x, and its baselines lifted the way the
   other three legends are - the labels by 9.45, the notes by 14.2, which
   puts each leader through the middle of the line it points at.

   The notes sit further right here than on the other entries, at 230
   against their 181: this sample is a whole country's record rather than
   a single bubble, and it carries its own mm ruler on its right-hand
   side, so it is wider and the leaders are shorter. */
.entry-legend-precip .entry-legend-country { left: 0.52px;  top: 628.8px; }
.entry-legend-precip .entry-legend-value   { left: 99.66px; top: 561.9px; }
/* Blue and bold, like Entry 03's: this label is the same mark as the
   figures that sit against each country's wettest and driest year, so it
   is set in the same ink. */
.entry-legend-precip .entry-legend-value   { font-weight: 700; color: #547def; }
.entry-legend-precip .entry-legend-note    { left: 230.01px; width: 152px; }
.entry-legend-precip .entry-legend-note-1  { top: 562.8px; }
.entry-legend-precip .entry-legend-note-2  { top: 623.3px; }
.entry-legend-precip .entry-legend-key-title { left: 0.4px; top: 674.6px; }
.entry-legend-precip .entry-legend-key       { left: 20.33px; }
.entry-legend-precip .entry-legend-key-1 { top: 690.9px; }
.entry-legend-precip .entry-legend-key-2 { top: 707.2px; }
.entry-legend-precip .entry-legend-key-3 { top: 723.4px; }

/* The sample's own parts, by role rather than by the export's class
   numbers: the record's lines at the weight the chart draws them, the
   leaders and the mm ruler at the export's hairline 0.3, and the ruler's
   labels at 5px - half the size the chart sets its years, since the whole
   sample is drawn small. */
.entry-legend-peak { fill: none; stroke: var(--text-primary); stroke-width: 1px; }
.entry-legend-mm {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 5px;
  fill: var(--text-primary);
  stroke: none;
}
/* The wash under the tallest peak. Named here for the same reason Entry
   03's is: .entry-legend-art sets fill: none across the whole sample, and
   a stylesheet rule beats an attribute. */
.entry-legend-wash { fill: url(#e-precip-wash); stroke: none; }
/* CO and its 2 are one unit — never let a line break fall between them. */
.co2 { white-space: nowrap; }

/* "How to read" — 16px bold, 142px below the metadata block's baseline. */
.entry-howto {
  margin: 52px 0 0;
  font-size: 16px;
  font-weight: 700;
  line-height: 19px;
}

/* The diagram stays artwork — 233 vector paths, not text. Its own small
   callout labels ride along inside it and are described in the alt text. */
.entry-howto-img {
  display: block;
  width: 387.73px;
  max-width: 100%;
  height: auto;
  margin-top: 18px;
  user-select: none;
  -webkit-user-drag: none;
}

/* Holds the space inside the bracketed slots in the copy. Both render
   empty for now; .swatch-blue is the parked blue-glow fill — re-enable by
   adding that class back onto the first slot's span in index1.html. */
.entry-copy .swatch {
  display: inline-block;
  width: 1.6em;
  vertical-align: middle;
}
.entry-copy .swatch-blue {
  height: 0.9em;
  margin: 0 0.1em;
  border-radius: 2px;
  background: radial-gradient(circle at 50% 50%, #547def 0%, rgba(84, 125, 239, 0.65) 55%, rgba(84, 125, 239, 0) 100%);
  filter: blur(1.5px);
}

/* ---- Background layers ----
   Decorative #c1c1c1 line art that sits above the grid and below all
   content, on the landing page and on every entry. Background_1 is authored at exactly canvas size; Background_2
   is larger (2289.62x1235.58) and deliberately overflows, so it is
   centred on the canvas at native size and allowed to bleed past the
   edges — .viewport-frame clips it at the window. */
.stage-bg {
  position: absolute;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}

.stage-bg-1 {
  left: 0;
  top: 0;
  width: 1920px;
  height: 1080px;
  z-index: 0;
}

.stage-bg-2 {
  left: 50%;
  top: 50%;
  width: 2289.62px;
  height: 1235.58px;
  transform: translate(-50%, -50%);
  z-index: 1;
}

/* Content sits above both layers. */
.intro-stage { z-index: 3; }

/* ---- Landing entry markers ----
   Five self-contained SVGs (label + artwork), scattered across the
   workspace at the positions from the reference layout. Each is placed by
   its own top-left corner in canvas px, at native size. */
.entry-marker {
  position: absolute;
  /* Above the title and the copy, so a marker that drifts over either can
     still be grabbed — at a lower z-index those elements swallowed the
     pointer and the bubble became stuck. The footer sits higher still, so
     parking a marker over it never blocks the link out. */
  z-index: 5;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
  cursor: grab;
  touch-action: none;
  /* transform/filter are driven per-frame by landing-links.js, so no CSS
     transition here — it would fight the easing done in JS. */
}
.entry-marker.is-dragging { cursor: grabbing; z-index: 7; }

/* The 2 in CO2, set as a proper subscript: smaller and dropped below the
   baseline. Applied to the inlined SVG run, so it survives re-inlining. */
/* The 2 in CO2: clearly smaller than the CO it follows, and sitting on
   the same baseline rather than hanging below it. */
.entry-sub {
  font-size: 0.52em;
  /* The 2 must not stretch the line it sits on. It inherits the block's
     line-height as a length, so at 0.52em its inline box hangs 3.3px below
     the parent's and every line carrying a CO2 came out that much taller —
     which walked the whole column off the export, a paragraph at a time. */
  line-height: 1;
  /* Bolder than the surrounding italic so the small digit still reads at
     this size — a 0.52em glyph in the regular weight goes too faint. */
  font-weight: 700;
}

/* Inlined SVG rather than <img>, so the artwork uses the bundled Roboto
   and its labels stay real text.

   Illustrator only puts font-family on the tspans it styles, so the outer
   <text> would otherwise inherit the page's system-ui stack. Naming the
   family here covers everything; the export's own font-weight/font-style
   still win on the tspans that set them. */
.entry-marker svg text,
.entry-marker svg tspan,
.landing-buttons svg text,
.landing-buttons svg tspan {
  /* Must override the tspans too, not just <text>. Illustrator writes
     `font-family: Roboto-Italic, Roboto` alongside `font-style: italic`.
     On a machine with the Roboto TTFs installed, the browser matches the
     already-slanted "Roboto-Italic" face and then synthesises a second
     slant on top — the glyphs come out ~8% wide, and since every tspan is
     placed at an absolute x, the runs overlap and swallow spaces
     ("coastal zones" rendered as "coastalzones"). Forcing the plain
     family lets font-style pick our real Roboto-Italic.ttf, whose metrics
     match what Illustrator measured. */
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
}

.entry-marker img,
.entry-marker > svg {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
  /* Entry05's artboard is cropped ~4 units tighter than its content, so
     the end of its label would be cut off at the viewBox edge. Letting
     the SVG paint outside its box costs nothing and protects every entry
     against the same tight-crop problem on re-export. */
  overflow: visible;
}
.entry-marker:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 6px;
}

.entry-marker-1 { left: 155px;  top: 317px; width: 340.76px; }
.entry-marker-2 { left: 563px;  top: 497px; width: 311.99px; }
.entry-marker-3 { left: 913px;  top: 242px; width: 276.13px; }
.entry-marker-4 { left: 44px;   top: 670px; width: 365.47px; }
.entry-marker-5 { left: 944px;  top: 722px; width: 200.68px; }

/* ---- Landing title ----
   Mirrors the copy column's inset: 30 canvas px from the top and left of
   the *window*, using the same --letterbox-x/y compensation. The artwork
   is hand-lettered script (outlined paths + a masked bitmap), so it stays
   an image; the real wording lives in the img alt. */
.landing-name {
  position: absolute;
  /* Top matches the copy column exactly (canvas-anchored) so the two
     blocks sit level; the left hugs the window edge, mirroring the way
     the copy hugs the right. */
  top: calc(var(--edge) - var(--letterbox-y, 0px));
  left: calc(var(--edge) - var(--letterbox-x, 0px));
  z-index: 3;
  margin: 0;
  width: 1194.79px;
  line-height: 0;
}
.landing-name img {
  display: block;
  width: 100%;
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
}

/* ---- Landing footer bar ----
   Equal 30 canvas px inset from the left and right of the *window*, and
   the same off the bottom, so it spans the full width between margins.
   The artwork stays a flat image; only "GO TO THE ENTRY 01" is a link,
   the other three labels are greyed status text in the file.
   Hotspot is positioned as a fraction of the 1860-wide viewBox
   (x 1553.2, width 193.3) so it tracks the bar at any window size. */
.landing-buttons {
  position: absolute;
  left: calc(var(--edge) - var(--letterbox-x, 0px));
  right: calc(var(--edge) - var(--letterbox-x, 0px));
  bottom: calc(var(--edge) - var(--letterbox-y, 0px));
  z-index: 6;
  line-height: 0;
  /* Spread evenly across the bar: the first label sits on the left edge,
     the link on the right, and the gaps between all four are equal. The
     export's hand-set x positions were not evenly spaced. */
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
}



/* ---- Landing copy column ----
   Rebuilt as real HTML text rather than an <img> of Text_Landing.svg, so
   it can be selected, copied, searched and read by screen readers. Specs
   measured from that file (viewBox 593.96x970.98): body Roboto Regular
   20px on 25px baselines with a blank line between paragraphs; credits in
   Roboto Mono 16px on 20px baselines, label column 0, values at x 180.04. */
.landing-copy {
  position: absolute;
  /* Anchored to the window edge, not the canvas edge: --letterbox-x/y is
     the empty band beside the canvas on non-16:9 windows (published by
     stage.js in canvas px), so this keeps the same visual margin from the
     browser edge at any window size without ever clipping. */
  top: calc(var(--edge) - var(--letterbox-y, 0px));
  right: calc(var(--edge) - var(--letterbox-x, 0px) + var(--copy-w-max) - var(--copy-w));
  z-index: 3;
  /* Named the same way the entries' columns are, so the left edges of all
     three line up off --copy-w-max — see the note on it. */
  --copy-w: 593.96px;
  width: var(--copy-w);
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 20px;
  font-weight: 400;
  line-height: 25px;
  color: var(--text-primary);
  user-select: text;
}

.landing-copy p { margin: 0 0 25px; }

/* ---- Words that take the weight ----
   The notes column names the two things the pointer can do here, and both
   words stay bold. They are labels for what is possible, not a readout of
   what the pointer is doing, so nothing swaps the weight between them —
   an earlier version did, and the sentence changing under you while you
   read it was more movement than the hint was worth. Entry 04's two cue
   words are bold in the same plain way. */
.cue { font-weight: 700; }

.landing-credits {
  display: grid;
  grid-template-columns: 180.04px 1fr;
  margin: 60px 0 0;
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 16px;
  line-height: 20px;
  /* 10px, not 30: the Authors value runs to three 20px lines, so a 30px
     gap pushed "Data sources" 90px below "Authors" where the export has
     it at 69.87px. */
  row-gap: 10px;
}
.landing-credits dt { font-weight: 400; }
.landing-credits dd { margin: 0; }
/* The Copernicus attribution sits a line clear of the source list. */
.landing-credits dd p { margin: 0 0 10px; }
.landing-credits dd p:last-child { margin-bottom: 0; }

/* ---- Slide 2: radial chart ---- */
.radial-stage {
  position: absolute;
  inset: 0;
  /* Above both background layers (0 and 1), below the title and copy. */
  z-index: 2;
}

.radial-back {
  position: absolute;
  top: 32px;
  left: 32px;
  z-index: 2;
  font-size: 13px;
  color: var(--text-muted);
  text-decoration: none;
}
.radial-back:hover { color: var(--accent); }

.radial-title {
  position: absolute;
  top: 32px;
  right: 32px;
  z-index: 2;
  font-size: 12px;
  color: var(--text-muted);
  text-align: right;
  line-height: 1.5;
}

.radial-chart-box {
  position: absolute;
  top: 50%;
  left: 27%;
  width: 800px;
  height: 800px;
  /* -50% centers the box on top/left; the +38px shifts it 1cm down
     (38px at the 96dpi CSS reference, in design-canvas pixels). */
  transform: translate(-50%, calc(-50% + 38px));
  touch-action: none;
  cursor: grab;
}
.radial-chart-box.dragging { cursor: grabbing; }

img.radial-image {
  width: 100%;
  height: 100%;
  object-fit: contain;
  user-select: none;
  -webkit-user-drag: none;
}

/* ---- Entry 05's chart ----
   Eleven clusters, one dot to roughly 10 Mt. Inlined rather than loaded
   through <img> so its labels render in the bundled Roboto and can be
   selected, and so the dots are reachable for the fill.

   Placed beside the notes column, both starting on the same top line: the
   chart is 965.09 tall against the column's 971.23, so the two blocks read
   as one band.

   Its clusters are spread to a wider pitch on the way in (see
   space_clusters in tools/inline-svg.py): 100 between the circles, which
   leaves 58.2 to the left of them and 58.2 across to the column. Air
   between the circles and air around the figure trade against each other —
   the four circles are a fixed width, so every point given to one comes
   off the other. */
.entry-chart {
  position: absolute;
  /* Levelled with the column rather than boxed with it: the chart's own
     first line sits 8.31 higher than the column's inside their boxes, so
     matching the boxes leaves the two blocks reading as if the chart had
     been nudged up. This puts "China" on the same line as "Entry 05 /". */
  top: calc(var(--edge) + 8.31px - var(--letterbox-y, 0px));
  left: 58.24px;
  width: 1178.7px;
  height: 965.09px;
  z-index: 2;
  overflow: visible;
}

/* The dots arrive rather than being there already — see
   assets/co2-dots.js, which turns them on a few per frame. Hidden only
   once that script has taken charge, so a page whose JavaScript never runs
   simply shows the whole field. */
.entry-chart.is-filling .entry-chart-dot { opacity: 0; }
/* A dot arrives at its own colour, in one step, with no fade.

   It was faded in for a while, on the thought that a soft arrival would
   be gentler than a pop. What it actually did was leave every dot that
   had landed in the last second or so sitting at some part of its
   colour, scattered through the ball, so the field never showed one
   weight of mark — the ones that had just arrived read as half
   transparent beside the ones that had been there a moment longer.
   Lengthening the fade only widened that band. A dot that is simply
   there, at full strength, the instant its ten megatonnes are counted,
   is both the plainer statement and the more even picture: the only
   thing that varies across the field is how many dots there are. */
.entry-chart.is-filling .entry-chart-dot.is-in { opacity: 1; }


/* ---- The CO2 swimming under the pointer ----
   The movement itself is worked out per dot per frame in
   assets/co2-dots.js, not keyframed here: a keyframe is one path, and any
   path shared between dots is the thing the eye picks out. All the
   stylesheet owes it is the hit target below. */

/* The ball is what answers the pointer, across its whole face — a ring
   drawn with no fill would otherwise only be hoverable on its own line.
   The dots stay out of it, or crossing one would count as leaving. */
.entry-chart-ring { pointer-events: all; }
.entry-chart-dot { pointer-events: none; }


/* ---- Entry 04: the nine needle spheres ----
   Neddles.svg is 1107.46x964.95 against Entry 05's 1178.7x965.09, so the
   figure is 71 narrower. It keeps Entry 05's left margin all the same:
   the two charts start on the same line of the page, and the extra width
   goes into the gap before the notes column. */
.entry-chart-needles {
  width: 1107.46px;
  height: 964.95px;
  /* 3.42, not Entry 05's 8.31: this export's first line — the country
     names on the top row — sits that much higher inside its box than
     "Entry 04 /" does inside the column's. Levelling the two boxes would
     leave the figure reading as nudged down. */
  top: calc(var(--edge) + 3.42px - var(--letterbox-y, 0px));
}

/* The export's labels and subregion marks. The spheres themselves are
   canvases stacked over this. */
.entry-chart-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.e-needles-name {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  font-style: italic;
  fill: var(--text-primary);
}
.e-needles-value {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  fill: var(--text-primary);
}
/* The headcount, in the same blue as the dots that count it out. */
.e-needles-people { font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif; font-weight: 700; fill: #547def; }
.e-needles-mark   { fill: var(--text-primary); }
/* Illustrator's per-run kerning, kept so the centred labels stay the
   width the export centred them at. */
.e-needles-cls-1  { letter-spacing: -0.01em; }
.e-needles-cls-2  { letter-spacing: 0em; }
.e-needles-cls-3  { letter-spacing: -0.02em; }
.e-needles-cls-4  { letter-spacing: -0.05em; }
.e-needles-cls-7  { letter-spacing: -0.05em; }
.e-needles-cls-12 { letter-spacing: 0em; }
.e-needles-cls-13 { letter-spacing: 0em; }
.e-needles-cls-15 { letter-spacing: -0.02em; }

/* Each sphere is a canvas the size of the room its needles need, laid
   over the centre the export gives it. Nothing is centred by flow: the
   left/top on each element is that centre less half the canvas, written
   out in the markup, so a moved export moves the spheres with it.

   The cursor over one is the grab hand, set below. */
.lecz-sphere {
  position: absolute;
  display: block;
  touch-action: none;
  user-select: none;
  -webkit-user-drag: none;
}

/* The same pair of hands the landing page's markers and Entry 01's chart
   use — open over a sphere, closed while one is being turned. The
   renderer puts the dragging class on the canvas itself, so the closed
   hand needs nothing else to hold it. */
.lecz-sphere { cursor: grab; }
.lecz-sphere.dragging { cursor: grabbing; }

/* ---- Entry 03: the nine sea level charts ----
   Graphic.svg is 1109.1x965.13, within a couple of px of Entry 04's
   figure, and it keeps the same left margin: the three charts start on the
   same line of the page across the three entries. */
.entry-chart-sla {
  width: 1109.1px;
  height: 965.13px;
  /* 3.42, the same as Entry 04's: this export's first line — the country
     names on the top row — sits at y 13.69 against the column's 17.11, so
     levelling the boxes would leave the figure reading as nudged down. */
  top: calc(var(--edge) + 3.42px - var(--letterbox-y, 0px));
}

.e-sla-name {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  font-style: italic;
  fill: var(--text-primary);
}
/* The 2025 figure, in the same blue as the shape whose edge it sits on. */
.e-sla-value {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  font-weight: 700;
  fill: #547def;
}
.e-sla-year {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 10px;
  fill: var(--text-primary);
}
.e-sla-mark { fill: var(--text-primary); }

/* The 0 cm reference at the export's own weight, and every observation
   contour at 0.3. Three of Papua New Guinea's arrive from Illustrator at
   0.5 while the other fifty-odd are at 0.3; that is a slip in one chart
   out of nine, not a reading, so they are all set here instead. */
.e-sla-base {
  fill: none;
  stroke: var(--text-primary);
  stroke-width: 1px;
  stroke-linecap: square;
  stroke-linejoin: round;
}
.e-sla-contour {
  fill: none;
  stroke: var(--text-primary);
  stroke-width: 0.3px;
  stroke-linecap: square;
  stroke-linejoin: round;
}
/* The wash: thirty-six stages a bubble, each that bubble's own form
   morphed a little further out and laid over the one before, so the ink
   falls off across the band the way the export's blend does. Built on
   load out of the artwork's own lines — see assets/sla-contours.js, which
   sets each stage's opacity and moves the clip the tide is made of. */
.e-sla-wash { fill: #547def; stroke: none; }

/* Illustrator's per-run kerning, kept so the labels stay the width the
   export set them at. */
.e-sla-cls-1  { letter-spacing: -0.01em; }
.e-sla-cls-5  { letter-spacing: -0.03em; }
.e-sla-cls-10 { letter-spacing: 0em; }
.e-sla-cls-11 { letter-spacing: -0.02em; }
.e-sla-cls-12 { letter-spacing: -0.05em; }
.e-sla-cls-13 { letter-spacing: -0.05em; }
.e-sla-cls-14 { letter-spacing: -0.02em; }
.e-sla-cls-15 { letter-spacing: 0em; }
.e-sla-cls-16 { letter-spacing: -0.02em; }


/* ---- Entry 02: the nine precipitation records ----
   ImageLeft.svg is 1192.72x912.35 - wider than the other three figures and
   84 shorter, because it is one wide panel rather than a 3x3 grid. It keeps
   the same 58.24 left margin all the same, so all four figures start on the
   same line of the page; the extra width comes off the gap before the
   column, which closes from 58.2 to 44.2.

   Vertically it cannot follow the others' rule. Theirs is that the figure's
   first text baseline lands on the column's title baseline - which works
   because their first line is a row of country names. This figure's first
   line is a row of years, a ruler rather than a reading, and hanging it off
   the title would push the panel's whole body 40px above the other pages'.
   So the box is centred instead, inside the band the other three occupy:
   3.42 + half the 52.78 of height it does not use. */
.entry-chart-precip {
  width: 1192.72px;
  height: 912.35px;
  top: calc(var(--edge) + 29.81px - var(--letterbox-y, 0px));
}

.e-precip-name {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  font-style: italic;
  fill: var(--text-primary);
}
.e-precip-year {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 10px;
  fill: var(--text-primary);
}
.e-precip-mark { fill: var(--text-primary); }

/* The year columns. The rule is the export's dotted hairline; the ticks
   are the solid nicks it runs between, one at each end, which are what
   tie the column to its label. Both at the export's own 0.3 and its
   square caps — the caps are why a 0.5-long tick reads as a mark at all.

   These stand still. So do the nine baselines below: they are the frame
   the record is read against, not the record, and a reference that has to
   arrive first is no reference at all. Only the wavy lines move — see
   assets/precip-record.js. */
/* A continuous rule, not the export's 1-1 dash. At this stage scale a
   0.3px dash is a 0.9px mark with a 0.9px gap, which reads as a column of
   loose specks rather than as a line down the panel — and each speck is
   also a sub-pixel stroke, so it arrives at a quarter of its ink. Drawn
   solid the same 0.3 reads as one quiet grey rule, which is what a
   gridline is for. */
.e-precip-rule {
  fill: none;
  stroke: var(--text-primary);
  stroke-width: 0.3px;
  stroke-linecap: square;
  stroke-linejoin: round;
}
.e-precip-tick {
  fill: none;
  stroke: var(--text-primary);
  stroke-width: 0.3px;
  stroke-linecap: square;
  stroke-linejoin: round;
}

/* The record. Every wavy line is one 5 mm step of anomaly, and the
   baseline is 0 mm — the same ink and the same weight, because the
   baseline is the first of the contours rather than an axis drawn over
   them. The export's own 1px, which is three times the frame's hairline:
   the frame is meant to be looked past and the record is meant to be
   read. */
.e-precip-line,
.e-precip-baseline {
  fill: none;
  stroke: var(--text-primary);
  stroke-width: 1px;
  stroke-miterlimit: 10;
}

/* The wedge under each country's wettest and driest year. Its fill is an
   attribute rather than a rule — one gradient per wedge, anchored on that
   country's own baseline — so there is nothing to say here but that it is
   not stroked. */
.e-precip-wash { stroke: none; }

/* The figure against each of those years, in the same blue as the wedge
   whose top it sits on, and the same setting as Entry 03's. */
.e-precip-value {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  font-weight: 700;
  fill: #547def;
}

/* The export's own kerning, kept: these are the tucks Illustrator set
   between particular pairs of letters in the names. */
.e-precip-cls-1  { letter-spacing: -0.01em; }
.e-precip-cls-4  { letter-spacing: -0.05em; }
.e-precip-cls-15 { letter-spacing: 0em; }
.e-precip-cls-17 { letter-spacing: -0.02em; }
.e-precip-cls-21 { letter-spacing: 0em; }
.e-precip-cls-22 { letter-spacing: -0.05em; }
.e-precip-cls-29 { letter-spacing: 0em; }
.e-precip-cls-31 { letter-spacing: -0.02em; }




/* ---- Entry 01: the radial chart ----
   ImageLeft.svg is 833.04x918.78 - one circle, where the other four
   entries carry a row or a grid of small charts. So it does not take
   their 58.24 left margin: a round figure held against the left edge
   reads as pushed there, with all its air on one side. It is centred
   instead, in the band the column leaves - between the page's own 30
   inset and the column's left edge at 1295.18 - which puts 216.07 on
   each side of it, and centred the same way inside the 971.29 of height
   the column occupies, which puts 26.26 above and below. */
.entry-chart-sst {
  left: 246.07px;
  top: calc(var(--edge) + 26.26px - var(--letterbox-y, 0px));
  width: 833.04px;
  height: 918.78px;
  cursor: grab;
  /* The whole box answers the drag, not just the lines in it: a chart you
     can only grab by a 1px bar is a chart you cannot grab. */
  touch-action: none;
}
.entry-chart-sst.dragging { cursor: grabbing; }

/* The chart itself, and the only one of the three layers that turns.

   Promoted to its own compositor layer. This is 250 paths and 209
   separate pieces of text, and asking the browser to lay all of that out
   again on every frame of a drag is what makes a turn stutter; rotating a
   layer that has already been rasterised does not. The old page got this
   for free by rotating an <img>, which is a picture and cannot be
   anything else - and paid for it by losing the bundled font, since an
   SVG behind an <img> cannot reach the page's @font-face. */
.entry-rad-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  will-change: transform;
}

/* ---- The blue, and the hole in it ----
   One element, and the hole is in the gradient rather than painted over
   it. The middle of the chart has to stay transparent: the page's grid
   and its background line art run underneath, and the old page only got
   away with covering them because its export shipped a filled disc -
   WhiteFilterRad.svg - to cover them with. This one does not, and should
   not have one invented for it.

   The trick is that CSS lets one gradient mix units. The two stops that
   open the blue are given in px, so the inside edge sits on +0.5 C -
   313.23 from the ring centre - and stays there whatever size the element
   is. Every stop after them is a percentage, so the falloff belongs to
   the box and travels out with it. The script at the foot of
   Entry01.html sizes the box from the drag; nothing here is transformed,
   because a transform would scale the px stop along with everything else
   and take the hole with it.

   At rest the box is exactly the +0.5 C circle, so every percentage stop
   resolves inside 313.23px and CSS clamps them all up to it: the band has
   no width at all and there is nothing to see. It opens out from the ring
   as the box grows, which is the behaviour asked for and falls out of the
   arithmetic rather than being arranged.

   Nothing here has a transition. The rise tracks the drag frame by frame;
   a transition would have it arriving after the chart instead of with it.

   The falloff is not invented either. TextRight.svg draws the same
   highlight in the legend's sample as a blend of a hundred concentric
   strokes, from the +0.5 C ring out to +2.0 C, and compositing that stack
   gives the curve: flat, then away to nothing, gently at first and then
   steeply. The sample in the column is drawn with it exactly as it comes
   - see the radialGradient inside .entry-legend-art.

   Here it is the same curve, spread. The blend gives an edge that begins
   to go at nine tenths of the radius, which is right for a mark 57px
   across and much too abrupt at the size this one reaches: on most of a
   screen a tenth of the radius is a rim, not a blur, and the wash reads
   as a disc with a soft edge instead of light coming off the ring. So the
   falloff starts at 0.62 rather than 0.89, three and a half times the
   room, and every value in it is Illustrator's own - only where the curve
   begins has moved, never its shape.

   One thing follows from spreading it that is worth knowing rather than
   being surprised by. The inside edge is fixed in px and the falloff is
   in percentages, so while the band is narrow the 0.62 stop still
   resolves inside 313.23px and clamps to it: the blue opens faint and
   fills in as it grows, reaching its full 0.6 at the ring once the box
   passes r 505. That is the mark arriving rather than switching on, and
   it is the behaviour wanted.

   Its peak is 0.6, the opacity the artwork names. The stack composites to
   0.81, but that is 0.25-wide strokes overlapping on a 0.139 pitch, not a
   density anyone chose. */
.entry-rad-rise {
  position: absolute;
  /* Placed and sized per frame; these are the resting values, which are
     the +0.5 C circle itself. */
  left: 104.59px;
  top: 149.60px;
  width: 626.46px;
  height: 626.46px;
  opacity: 0;
  pointer-events: none;
  background: radial-gradient(circle closest-side,
    rgba(84, 125, 239, 0.000) 0,
    rgba(84, 125, 239, 0.000) 313.23px,
    rgba(84, 125, 239, 0.600) 313.23px,
    rgba(84, 125, 239, 0.583) 62.0%,
    rgba(84, 125, 239, 0.564) 65.5%,
    rgba(84, 125, 239, 0.550) 68.9%,
    rgba(84, 125, 239, 0.535) 72.4%,
    rgba(84, 125, 239, 0.511) 75.8%,
    rgba(84, 125, 239, 0.475) 79.3%,
    rgba(84, 125, 239, 0.422) 82.7%,
    rgba(84, 125, 239, 0.370) 86.2%,
    rgba(84, 125, 239, 0.286) 89.6%,
    rgba(84, 125, 239, 0.187) 93.1%,
    rgba(84, 125, 239, 0.102) 95.9%,
    rgba(84, 125, 239, 0.044) 97.9%,
    rgba(84, 125, 239, 0.000) 100.0%);
}

/* ---- The chart's ink ----
   Every element is renamed on the way in by what it is rather than by the
   .cls-n Illustrator gave it, because the export uses one class for two
   things: the same 1px stroke draws the 0 C ring and the three subregion
   arrows. Which is which is read off the radius. */
.e-sst-bar,
.e-sst-ring,
.e-sst-base,
.e-sst-arrow {
  fill: none;
  stroke: var(--text-primary);
  stroke-linejoin: round;
}
/* One bar is one year, 2000 to 2025, measured out from the 0 C ring. */
.e-sst-bar   { stroke-width: 1px; }
/* +0.5, +1.0 and +1.5 C, each drawn as three arcs with the label sitting
   in the gaps between them. */
.e-sst-ring  { stroke-width: 0.3px; stroke-linecap: square; }
/* The 0 C reference, at the export's own heavier weight. */
.e-sst-base  { stroke-width: 1px; stroke-linecap: square; }
/* The three arcs outside the chart that give each subregion its reading
   direction. Mitred and butt-ended, so the stroke stops where the
   arrowhead starts instead of poking through it. */
.e-sst-arrow { stroke-width: 1px; stroke-linejoin: miter; stroke-miterlimit: 10; }
.e-sst-solid { fill: var(--text-primary); }
/* Illustrator leaves one path in the file with a fill of none and no
   stroke at all - a duplicate of the +1.5 C ring, drawn as 27 straight
   segments. It is invisible in the export and it stays invisible here;
   saying so is cheaper than deleting it and having the next re-export
   quietly bring it back. */
.e-sst-ghost { fill: none; stroke: none; }

.e-sst-name {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  font-style: italic;
  fill: var(--text-primary);
}
.e-sst-region {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  font-weight: 700;
  fill: var(--text-primary);
}
/* The years and the ring labels are one setting in the export and are
   separated here only by what they say. Seventeen of the eighteen year
   labels arrive at 10px and one at 10.58; that is a slip in one label out
   of eighteen, not a reading, so they are all set here instead - the same
   call Entry 03 makes about its three heavy contours. */
.e-sst-year,
.e-sst-scale {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 10px;
  fill: var(--text-primary);
}

/* ---- Entry 01's column and legend ----
   Same construction as the other four, off its own export: TextRight.svg
   is 584.75x971.29 with the text block starting at x 0.41. */
.entry-copy-sst { --copy-w: 584.75px; }

/* Four rows of metadata, as Entry 02, Entry 03 and Entry 04 have, and the
   block is read bottom-up so its last line lands on the landing page's
   credits: the same 860. Its value column starts at 180 rather than the
   144 the others use - DF_CLIMATE_CHANGE is a longer name than any of
   them carry, and the export moved the column out to clear it. */
.entry-copy-sst .entry-meta {
  top: 860px;
  grid-template-columns: 180px 1fr;
}

/* Three notes, at the export's own x, and its baselines lifted by 14.2
   the way the other legends' are, which puts each leader through the
   middle of the line it points at. No country label and no value beside
   the sample here, and no subregion key underneath: the sample is the
   whole chart in miniature and the subregions are named on it. */
.entry-legend-sst .entry-legend-note   { left: 180.92px; width: 152px; }
/* The first note is set in a narrower box than its neighbours, because
   152 does not hold its wrap: "The blue highlight marks a" plus the
   "+0.5°C" that follows it comes to 151.06, so at 152 the first line
   swallows the reading the note is about and the block loses a line. The
   room between that and the widest line any of the three notes carries
   (149.84, in the last one) is a single pixel, too little to hold a box
   all three could share - so this one takes its own, with four pixels
   either side of the break instead of half of one. */
.entry-legend-sst .entry-legend-note-1 { top: 611.27px; width: 146px; }
.entry-legend-sst .entry-legend-note-2 { top: 671.78px; }
.entry-legend-sst .entry-legend-note-3 { top: 720.17px; }

/* The sample is the chart at 0.1374, and Illustrator does not scale
   strokes with it: the bars come down from 1px to 0.3 and the rings stay
   at 0.3. Each is taken at the weight its own export sets. */
.entry-legend-bar    { stroke-linecap: butt; }
.entry-legend-arrow  { stroke-width: 1px; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 10; }
/* The blue, and the same unstroked duplicate the chart carries. Both need
   saying, because .entry-legend-art strokes everything inside it by
   default and neither of these should take that. */
.entry-legend-rise   { stroke: none; }
.entry-legend-ghost  { fill: none; stroke: none; }



/* ---- The cue line ----
   "Drag and explore ..." — both words bold, and bold from the start.

   They used to take the weight one at a time, Drag while the pointer was
   over a sphere and explore while one was being turned, so the line only
   named a thing once you were already doing it: an instruction that
   waits until it is redundant. Said plainly on arrival it is an
   instruction again.

   With nothing swapping the weight, none of the machinery that used to
   protect the line is needed: a word that never changes weight cannot
   move the sentence, so there is no width to reserve, no hidden twin of
   each word, and no pair of body classes to keep in step with the
   pointer. Two <strong>s in the markup say the whole thing. The landing
   page went the same way — see "Words that take the weight". */
.entry-copy p strong { font-weight: 700; }

/* ---- Entry page footer bar ----
   The same bar the landing page carries, with a link at each end: back on
   the left, onward on the right. Both are .footer-go, so the type, the
   colour, the hover behaviour and the arrow are shared rather than
   restated — and both anchor to the window edge the same way, which is
   what keeps the bar level with the landing page's across window sizes. */
.entry-nav {
  position: absolute;
  left: calc(var(--edge) - var(--letterbox-x, 0px));
  right: calc(var(--edge) - var(--letterbox-x, 0px));
  bottom: calc(var(--edge) - var(--letterbox-y, 0px));
  z-index: 6;
  line-height: 0;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
}

/* The backward link leads with its arrow instead of trailing it. Same
   drawing, mirrored, so the two can never drift apart in weight or
   length the way a second hand-drawn path would. */
.footer-go-back .footer-arrow { transform: scaleX(-1); }

/* The blue glow, drawn in CSS rather than from an SVG so it can expand
   past the artwork and wash the whole screen. JS sizes and centres it on
   RAD.svg's +0.5°C ring, then writes scale/opacity every frame from the
   rotation amount (see applyGlow in index1.html) — deliberately no
   transition here, which would lag behind the drag instead of tracking
   it. The fill is 40% #547def, fading out at the rim so the growing edge
   stays soft. */
.glow-sphere {
  position: absolute;
  border-radius: 50%;
  opacity: 0;
  transform: scale(1);
  pointer-events: none;
  background: radial-gradient(
    circle closest-side,
    rgba(84, 125, 239, 0.4) 0%,
    rgba(84, 125, 239, 0.4) 62%,
    rgba(84, 125, 239, 0.22) 84%,
    rgba(84, 125, 239, 0) 100%
  );
}

img.radial-blue-image {
  position: absolute;
  width: 100%;
  height: 100%;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}

svg.radial-svg { width: 100%; height: 100%; overflow: visible; user-select: none; }
.radial-grid-circle { fill: none; stroke: var(--text-faint); stroke-width: 1; }
.radial-grid-label { fill: var(--text-muted); font-size: 12px; }
.radial-tick { stroke: var(--text-primary); stroke-width: 1.6; stroke-linecap: round; }
.radial-year-label { fill: var(--text-faint); font-size: 8px; }
.radial-country-label { fill: var(--text-secondary); font-size: 13px; font-style: italic; }
.radial-region-label { fill: var(--text-primary); font-size: 15px; font-weight: 600; }
.radial-arrow-path { fill: none; stroke: var(--text-faint); stroke-width: 1; }
.radial-arrow-head { fill: var(--text-faint); }

/* ---- Entry 02 prototype: needle spheres ---- */
.proto-row {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 40px;
}

.proto-slot { margin: 0; }

/* No grab/grabbing cursor: the sphere already follows the pointer on
   hover, so the motion itself is the affordance. */
.proto-canvas {
  display: block;
  touch-action: none;
  user-select: none;
  -webkit-user-drag: none;
}

.proto-caption {
  margin-top: 8px;
  text-align: center;
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  font-weight: 400;
  color: var(--text-primary);
}

/* Credits typewriter. Every character is present from the first frame and
   only its visibility changes, so the block's line breaks are settled
   once and never shift while it types. Words are kept unbreakable so
   wrapping matches ordinary text. */
.type-word { white-space: nowrap; }
.type-char { visibility: hidden; }
.type-char.is-typed { visibility: visible; }

/* ---- Footer bar ----
   Real text rather than the exported artwork, so it renders in the
   bundled Roboto and can be selected. The x positions are the ones from
   Buttons.svg (0 / 538.25 / 981.36 / 1553.2 of its 1860 viewBox),
   expressed as percentages so they hold at any window size. The three
   greyed labels are status; only the last one navigates. */
.footer-meta,
.footer-go {
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  white-space: nowrap;
}

.footer-meta { color: #c1c1c1; }

/* Anchored to the right edge of the bar, mirroring the status labels
   that run from the left. The label and its arrow are one target —
   clicking anywhere across either navigates. */
.footer-go {
  color: var(--text-primary);
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 14px;
  -webkit-tap-highlight-color: transparent;
}
/* Deliberately no colour change on hover, focus or active: the label
   stays in the page's ink rather than flashing link-blue. */
.footer-go:hover,
.footer-go:active,
.footer-go:visited { color: var(--text-primary); }
.footer-go:focus-visible { outline: 1px solid var(--text-primary); outline-offset: 6px; }

/* The arrow that trails the label: a plain rule with an open chevron
   head, matching the reference. Drawn as strokes rather than a filled
   triangle so the head stays open and the weights match the line. */
.footer-arrow {
  display: block;
  flex: none;
  width: 72px;
  height: 14px;
  overflow: visible;
}

/* ---- Number bubbles ----
   Drop targets for ordering the entries. They exist only while something
   is being dragged, so the resting page never shows them. The ball is
   blurred and translucent; the numeral sits on top of it un-blurred, or
   it would be unreadable. */
.drop-slots {
  position: absolute;
  inset: 0;
  z-index: 4;
  pointer-events: none;   /* the drop is resolved by distance, not hit-testing */
  opacity: 0;
  transition: opacity 320ms ease;
}
.drop-slots.is-visible { opacity: 1; }

.drop-slot {
  position: absolute;
  width: 160px;
  height: 160px;
  margin: -80px 0 0 -80px;   /* left/top are the bubble's centre */
  display: grid;
  place-items: center;
}

.drop-slot-ball {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  /* Many stops rather than three: the falloff is smooth enough that the
     ball reads as light rather than a disc with a soft edge. */
  background: radial-gradient(circle at 50% 50%,
    #547def 0%,
    rgba(84, 125, 239, 0.92) 18%,
    rgba(84, 125, 239, 0.74) 33%,
    rgba(84, 125, 239, 0.52) 46%,
    rgba(84, 125, 239, 0.32) 57%,
    rgba(84, 125, 239, 0.16) 68%,
    rgba(84, 125, 239, 0.06) 80%,
    rgba(84, 125, 239, 0) 92%);
  filter: blur(9px);
  opacity: 0.6;
  transition: opacity 260ms ease, transform 260ms ease;
}

.drop-slot-num {
  position: relative;
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  font-weight: 700;
  color: #547def;
  opacity: 1;
  transition: opacity 260ms ease;
}

/* The bubble an entry is about to drop into. */
.drop-slot.is-near .drop-slot-ball { opacity: 0.85; transform: scale(1.18); }

/* Already claimed: still shown, but clearly not a target. */
.drop-slot.is-taken .drop-slot-ball { opacity: 0.16; }
.drop-slot.is-taken .drop-slot-num { opacity: 0.25; }

/* Walking the order, once every entry has a number. The ball's opacity
   and scale are driven per frame from landing-links.js, so the transition
   is dropped here rather than left to smear one bubble's turn into the
   next; the 0 is what each ball reads before the first frame sets it.
   The numerals stay dark throughout — each entry is carrying its own
   number by then, and a second one glowing underneath only asks to be
   read twice. Last, so these win the tie with the two rules above. */
.drop-slots.is-sequence .drop-slot-ball {
  transition: none;
  opacity: 0;
  /* Half again the drop bubble across, and blurred to match: under an
     entry this reads as a wash of light coming through the artwork
     rather than a ball with an edge to it. The gradient is the same
     one, so the falloff stays in proportion as it grows. Scale and
     the 50px offset to the left are set per frame. */
  inset: -45px;
  filter: blur(15px);
}
/* No transition on these either. With one, the numerals faded out over
   260ms while the layer faded in over 320 — which is all five of them
   showing, at about a quarter strength, every time the walk began. */
.drop-slots.is-sequence .drop-slot-num { opacity: 0; transition: none; }

/* ---- Footer link, before every entry has a number ----
   Greyed out. It still responds — clicking it deals the numbers out at
   random rather than navigating. */
.footer-go.is-locked,
.footer-go.is-locked:hover { color: #c1c1c1; }

/* ---- Small screens: the canvas is not offered at all ----
   Every page is one fixed 1920x1080 stage that scales as a unit, so on a
   phone it either shrinks to unreadable or gets scrolled around blind —
   neither is the piece. Rather than reflow it into something it isn't,
   the stage is taken off the page entirely below the cutoff and this
   note stands in its place, on the same paper, the same grid and the
   same two background drawings the pages are built on, set in the same
   Roboto used everywhere else. */
.mobile-gate { display: none; }

/* The landing's two background layers, in the same order and the same
   proportion to each other as on the stage: Background_1 fills the frame,
   Background_2 sits over it overscanned by the ratio its own artboard has
   to the canvas (2289.62/1920 across, 1235.58/1080 down), so the second
   drawing runs past the edges here exactly as far as it does there.
   Cropped rather than fitted — on a portrait phone the whole 16:9
   composition would shrink to a band across the middle. */
.mobile-gate-bg {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  object-fit: cover;
  pointer-events: none;
  user-select: none;
  -webkit-user-drag: none;
}
.mobile-gate-bg-1 { width: 100%;      height: 100%;      z-index: 0; }
.mobile-gate-bg-2 { width: 119.251%;  height: 114.406%;  z-index: 1; }

.mobile-gate-inner {
  /* Above both drawings. */
  position: relative;
  z-index: 2;
  max-width: 34em;
  /* Away from the notch and the home indicator as well as the edges. */
  padding: max(28px, env(safe-area-inset-top)) 28px max(28px, env(safe-area-inset-bottom));
  text-align: left;
}

/* The site's name, at the size and the spacing it has always had here —
   only the face changes. Not the landing's Roboto Mono any more: there is
   no mono italic in roboto/, and the browser would slant the upright to
   fake one. */
.mobile-gate-name {
  margin: 0 0 28px;
  font-family: Roboto, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 12px;
  font-style: italic;
  font-weight: 400;
  line-height: 20px;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

/* What the visitor is being told, upright and at the size it was set in.
   In the credits' Roboto Mono, since this is the line that types itself:
   the typing and the typewriter face are the same idea, and a proportional
   face re-spaces every character as the next one lands. */
.mobile-gate-line {
  margin: 0;
  font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 22px;
  font-weight: 400;
  line-height: 32px;
  color: var(--text-primary);
}

/* The cutoff: narrow windows, and any touch-only device regardless of how
   wide it reports itself (a tablet in landscape is still not a pointer).
   A touchscreen laptop keeps its mouse, so it stays on the desktop side. */
@media (max-width: 900px), (hover: none) and (pointer: coarse) {
  /* Nothing of the stage is built, painted, or animated. */
  .viewport-frame { display: none !important; }

  html, body {
    overflow: hidden;
    height: 100%;
    /* iOS Safari otherwise letterboxes an inch of white under the note. */
    background: var(--page);
  }

  .mobile-gate {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 9999;
    /* The drawings are cropped to the window, not scrolled past it. */
    overflow: hidden;
    align-items: center;
    justify-content: center;
    /* The same paper and the same grid as every page, at a cell size that
       reads on a phone rather than the stage's scaled one. */
    background-color: var(--page);
    background-image:
      linear-gradient(to right, rgba(193, 193, 193, 0.12) 1px, transparent 1px),
      linear-gradient(to bottom, rgba(193, 193, 193, 0.12) 1px, transparent 1px);
    background-size: 19.0099px 18.9474px;
  }
}
