/* 眾妙之門 — 古地圖羊皮紙 × Minecraft
 *
 * Aimed at the Minecraft filled-map item and the written-book GUI, not at generic "aged paper":
 * saturated tan rather than cream, wood-frame brown, cinnabar red for anything that marks a place,
 * and hard block edges everywhere. The rule that keeps it from drifting into generic parchment is
 * that surfaces separate by paper *tone*, never by shadow, and every border is a thick fill with a
 * darker keyline rather than a hairline.
 */

:root {
  /* Paper */
  --parch:      #E4D5AE;  /* the map field itself */
  --parch-lit:  #F0E4C4;  /* raised surfaces: cards, the header banner */
  --parch-aged: #C9B488;  /* recessed surfaces, gutters, disabled */
  --parch-deep: #B29B70;  /* deepest tone, used sparingly as a seam */

  /* Frame — the wooden border of the map item */
  --frame:      #7A5C3A;
  --frame-dark: #4A3826;  /* keyline drawn around every frame */

  /* Ink */
  --ink:        #33261A;
  --ink-soft:   #6B5742;
  --ink-faint:  #9A866B;

  /* Cinnabar — landmarks, current state, anything that says "here" */
  --seal:       #A33B2A;
  --seal-deep:  #7C2A1D;
  --seal-wash:  rgba(163, 59, 42, .12);

  /* Verdigris — the secondary accent, carried over from the old site's green */
  --verdigris:  #4A7A5C;
  --verdigris-wash: rgba(74, 122, 92, .14);

  /* Cloud — warm white, never pure #fff, so it reads as paper-lit rather than screen-lit */
  --cloud:      #F7F3E8;

  /* Map ground behind the tiles, before the fog covers it */
  --bg-map:     #44413c;

  --rule:       rgba(51, 38, 26, .22);  /* ink-coloured divider */
  --rule-soft:  rgba(51, 38, 26, .12);

  --font-serif: 'Newsreader', 'Noto Serif TC', serif;
  --font-sans:  'Archivo', 'Noto Sans TC', system-ui, sans-serif;
  --font-mono:  'IBM Plex Mono', 'Noto Sans TC', monospace;

  /* Type scale. Two floors hold across every page: an uppercase Latin label is never under
     --fs-xs, and a line with Han characters in it is never under --fs-sm — a CJK glyph has several
     times the strokes of a Latin one in the same box, and below 12px they close up into grey. */
  --fs-xs:   11px;   /* uppercase mono labels: SEASON 01, DAY 46, coordinates */
  --fs-sm:   12px;   /* the smallest line that carries Chinese */
  --fs-ui:   13px;   /* hints, meta lines, controls */
  --fs-base: 14px;   /* running text */
  --fs-md:   16px;
  --fs-lg:   20px;
  --fs-xl:   28px;
  --fs-2xl:  40px;

  /* Spacing, on a 4px grid. */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;

  /* Secondary text on the dark ground. One value rather than a different alpha per label, and
     bright enough to read over obsidian — the .35 some labels had was below legibility. */
  --on-dark:       #F6F1E6;
  --on-dark-soft:  rgba(246, 241, 230, .72);
  --on-dark-faint: rgba(246, 241, 230, .56);
  --on-dark-gold:  #D9C39A;

  --header-h: 62px;
}

/* Han glyphs come from Noto Serif TC / Noto Sans TC (public/fonts-cjk.css) rather than the system
   stack. The system stack was the old map's Ming voice on a Mac, but on Windows `serif` lands on
   PMingLiU — hairline strokes that break up at heading sizes and read as unstyled. The Latin faces
   stay first in each stack, so English and numerals are unchanged. */

* , *::before, *::after { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--parch);
  color: var(--ink);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
}

/* Paper grain. Two offset dot grids at very low alpha — cheap, resolution-independent, and it
   survives on top of the map tiles where a texture image would have to be composited. */
body.next::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: .055;
  background-image:
    radial-gradient(var(--ink) .5px, transparent .5px),
    radial-gradient(var(--ink) .5px, transparent .5px);
  background-size: 7px 7px, 11px 11px;
  background-position: 0 0, 3px 5px;
}

h1, h2, h3 {
  font-family: var(--font-serif);
  font-weight: 600;
  letter-spacing: -.01em;
  margin: 0;
}

a { color: var(--seal); text-decoration: none; }
a:hover { color: var(--seal-deep); }

:focus-visible {
  outline: 2px solid var(--seal);
  outline-offset: 2px;
}

/* ── Block edge ──────────────────────────────────────────────────────────────
   The signature of the whole system: a thick border in frame brown with a 1px darker keyline
   outside it, which is how Minecraft's GUI panels and the map item read at a glance. Applied via
   a utility rather than repeated, because the keyline is an outline (it doesn't affect layout). */
.edge {
  border: 3px solid var(--frame);
  outline: 1px solid var(--frame-dark);
  outline-offset: 0;
}
.edge-thin {
  border: 2px solid var(--frame);
  outline: 1px solid var(--frame-dark);
}

/* ── Header ─────────────────────────────────────────────────────────────────
   Floats over the full-bleed map, so it is fixed and does not reserve height. */
.next .site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 60;
  height: var(--header-h);
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 28px;
  padding: 0 22px;
  background: var(--parch-lit);
  border-bottom: 3px solid var(--frame);
  box-shadow: 0 1px 0 var(--frame-dark);
}

.next .header-brand {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  color: var(--ink);
  padding: 0 18px;
  /* Paper seams either side of the wordmark, standing in for a map cartouche. */
  border-left: 1px solid var(--rule-soft);
  border-right: 1px solid var(--rule-soft);
}
.next .brand-name {
  font-family: var(--font-serif);
  font-size: 21px;
  font-weight: 600;
  letter-spacing: .06em;
  line-height: 1.1;
}
.next .brand-season {
  font-family: var(--font-mono);
  font-size: 10px;   /* the one label under the floor: it sits under a 21px wordmark and must not compete */
  letter-spacing: .2em;
  color: var(--seal);
}

/* The header is fixed, so it reserves no height and every page has to clear it itself. Pages that
   run their own full-height stage clear it inside that stage instead, and must opt out here — a
   page that does both is exactly one header taller than the window, which raises a scrollbar,
   which narrows the viewport, which shifts the fixed header's centred wordmark sideways relative
   to pages that don't scroll. */
.next main { padding-top: var(--header-h); }
.next.map-full main,
.next.dark-stage main { padding-top: 0; }

.next .nav-side {
  display: flex;
  align-items: center;
  gap: 4px;
}
.next .nav-left  { justify-content: flex-end; }
.next .nav-right { justify-content: flex-start; }

.next .nav-link {
  position: relative;
  padding: 8px 14px;
  font-family: var(--font-serif);
  font-size: var(--fs-md);
  font-weight: 500;
  letter-spacing: .04em;
  color: var(--ink-soft);
  white-space: nowrap;
  transition: color .12s ease;
}
.next .nav-link:hover { color: var(--ink); }
.next .nav-link.active { color: var(--seal); }
/* Underline drawn as a stack of 2px blocks rather than a line, echoing the pixel grid. */
.next .nav-link.active::after {
  content: '';
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 2px;
  height: 2px;
  background: var(--seal);
}

/* ── Floating header ────────────────────────────────────────────────────────
   The header floats on whatever is behind it rather than sitting on a parchment band, so the map
   (and every other dark-ground page) runs to the very top of the window. Any page that wants it
   has to supply a dark ground of its own — light type on parchment is unreadable, which is why
   this is opt-in per page rather than global. */
.next.map-full .site-header,
.next.dark-stage .site-header {
  background: none;
  border-bottom: 0;
  box-shadow: none;
}

/* A scrim, not a panel. The map underneath runs from near-white cloud to dark terrain, so light
   text alone is legible over neither reliably — this darkens just the top band enough to carry it
   and fades out before it reads as a bar. */
.next.map-full .site-header::before,
.next.dark-stage .site-header::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(0, 0, 10, 0.89)0%,
    rgba(0, 0, 0, 0.505) 55%,
    rgba(0, 0, 0, 0) 100%);
}

.next.map-full .header-brand,
.next.dark-stage .header-brand {
  color: #F6F1E6;
  border-left-color: rgba(246, 241, 230, .26);
  border-right-color: rgba(246, 241, 230, .26);
}
.next.map-full .brand-name,
.next.dark-stage .brand-name {
  /* The shadow is doing real work here: over a white cloud the text would otherwise disappear
     into it, scrim or no scrim. */
  text-shadow: 0 1px 3px rgba(12, 8, 4, .75), 0 0 1px rgba(12, 8, 4, .6);
}
.next.map-full .brand-season,
.next.dark-stage .brand-season {
  color: #E8C79A;
  text-shadow: 0 1px 2px rgba(12, 8, 4, .8);
}

.next.map-full .nav-link,
.next.dark-stage .nav-link {
  color: #EDE6D8;
  text-shadow: 0 1px 3px rgba(12, 8, 4, .75), 0 0 1px rgba(12, 8, 4, .6);
}
.next.map-full .nav-link:hover,
.next.dark-stage .nav-link:hover { color: #fff; }
.next.map-full .nav-link.active,
.next.dark-stage .nav-link.active { color: #F3C98E; }
.next.map-full .nav-link.active::after,
.next.dark-stage .nav-link.active::after { background: #F3C98E; }

/* The phone header is handled in the media block at the foot of this file, together with the
   --header-h it has to agree with. An earlier attempt lived here with `height: auto`, which let the
   grid wrap to 135px while every page went on clearing 62px. */

/* ── Map stage ──────────────────────────────────────────────────────────────
   Full bleed, with the header floating over it. 100dvh rather than 100vh so the mobile URL bar
   showing and hiding doesn't leave a gap at the bottom. */
.next .map-stage {
  position: relative;
  width: 100%;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
  background: var(--bg-map);
  cursor: grab;
  touch-action: none;
}
.next .map-stage:active { cursor: grabbing; }

.next #mapCanvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}

.next .map-markers {
  position: absolute;
  inset: 0;
  z-index: 10;
  pointer-events: none;
}
/* Anchored on the dot's centre — the dot is the landmark, the label is a caption beside it. This
   used to be translate(-50%, -100%), the bottom-centre of dot and label together, which left the
   dot ~35px left of and ~11px above the true point. A constant screen offset is a few blocks at full
   zoom and hundreds zoomed out, which is why landmarks drifted off their builds everywhere but at
   maximum zoom. Repeated as MARKER_ANCHOR in next-map.js, which writes the transform every frame. */
.next .map-markers .map-marker {
  position: absolute;
  transform: translate(-4px, -50%);
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  cursor: pointer;
  pointer-events: auto;
}

/* ── Minecraft tooltip ──────────────────────────────────────────────────────
   Anything that names a thing on the map — the marker labels and the landmark card — is dressed
   as an item tooltip. The values are the game's own: a near-black violet fill, and a 2px border
   that runs from bright violet at the top to a darker one at the bottom.

   Two details carry the whole look and are easy to leave out: the border is a *gradient*, not a
   flat purple, and every line of text has a hard 2px offset shadow with no blur. Minecraft's font
   renderer draws that shadow on every glyph, and without it the text sits flat on the panel and
   the illusion goes. */
:root {
  --mc-bg:        rgba(16, 0, 16, .94);
  --mc-border-a:  rgba(80, 0, 255, .50);
  --mc-border-b:  rgba(40, 0, 127, .50);
  --mc-shadow:    rgba(0, 0, 0, .78);

  /* The game's text palette, by role rather than by colour code. */
  --mc-white:  #FFFFFF;   /* item name          §f */
  --mc-grey:   #AAAAAA;   /* descriptive lines  §7 */
  --mc-dgrey:  #6A6A6A;   /* de-emphasised      §8 */
  --mc-green:  #55FF55;   /* people, positives  §a */
  --mc-aqua:   #55FFFF;   /* links              §b */
  --mc-gold:   #FFAA00;   /* the season/day     §6 */
  --mc-yellow: #FFFF55;   /* "Advancement Made!" §e */
}

.next .mc-panel,
.next .map-marker-label,
.next .landmark-card {
  background: var(--mc-bg);
  border: 2px solid;
  border-image: linear-gradient(to bottom, var(--mc-border-a), var(--mc-border-b)) 1;
  /* The thin dark ring the game draws outside the violet border. */
  outline: 1px solid rgba(16, 0, 16, .94);
}

.next .map-marker-dot {
  width: 8px;
  height: 8px;
  flex: 0 0 auto;
  background: var(--seal);
  /* Blocky two-step outline rather than a soft ring — the same silhouette a marker has on a
     Minecraft map. */
  outline: 2px solid #100010;
  box-shadow: 0 0 0 3px rgba(80, 0, 255, .45);
}
.next .map-marker-label {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  letter-spacing: .02em;
  color: var(--mc-white);
  text-shadow: 2px 2px 0 var(--mc-shadow);
  padding: 2px 7px 3px;
}
.next .map-marker:hover .map-marker-label,
.next .map-marker:focus-visible .map-marker-label { color: var(--mc-aqua); }

/* The fog canvas sits at z-index 20, above the markers, so the opening cover really does hide
   them. Everything meant to stay legible therefore has to be lifted above it explicitly —
   these were all `position:absolute` with auto z-index and would otherwise be buried in cloud. */
.next .map-compass,
.next .map-empty { z-index: 25; }
.next .landmark-card { z-index: 30; }

/* The mirror of the header's scrim, running up from the bottom edge. It sits above the fog but
   below the compass, so the dial reads against a settled ground instead of against whatever cloud
   happens to be drifting past underneath it. */
.next .map-stage::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 90px;
  z-index: 22;
  pointer-events: none;
  background: linear-gradient(to top,
    rgba(0, 0, 10, .80) 0%,
    rgba(0, 0, 0, .42) 45%,
    rgba(0, 0, 0, 0) 100%);
}

.next .label {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: .14em;
  color: var(--ink-soft);
}

/* ── Day compass ────────────────────────────────────────────────────────────
   The needle only ever sweeps the upper half of the dial, which leaves the lower half free for
   the readout — the two never collide, so neither has to give ground to the other. */
.next .map-compass {
  position: absolute;
  left: 50%;
  bottom: -22px;               /* sunk into the bottom edge, like an instrument set into the frame */
  transform: translateX(-50%);
  width: 176px;
  height: 176px;
  cursor: grab;
  /* Stops the browser scrolling or zooming the page when a finger drags the dial. Belt and braces
     with the stopPropagation in next-map.js, which is what keeps the map from paning underneath. */
  touch-action: none;
  filter: drop-shadow(0 3px 0 rgba(58, 46, 30, .28));
}
.next .map-compass:active { cursor: grabbing; }

.next .map-compass canvas {
  display: block;
  width: 176px;
  height: 176px;
  /* The dial is drawn at 88x88 and upscaled by exactly 2 with nearest-neighbour, which is what
     makes every edge a hard pixel step rather than a smooth vector curve. */
  image-rendering: pixelated;
  pointer-events: none;
}

/* Text stays in the DOM rather than being drawn into the low-res canvas, so it renders crisply
   instead of being upscaled into mush along with the dial. */
.next .compass-readout {
  position: absolute;
  left: 0;
  right: 0;
  top: 108px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  pointer-events: none;
}
.next .compass-day {
  font-family: var(--font-serif);
  font-size: 19px;
  font-weight: 600;
  line-height: 1;
  color: var(--ink);
}
.next .compass-season {
  font-family: var(--font-mono);
  font-size: 10px;   /* under the floor on purpose, like .brand-season: it has to fit inside the dial */
  font-weight: 500;
  letter-spacing: .16em;
  /* Was --ink-faint, which is a tone meant for parchment and vanished against the dial. */
  color: var(--frame-dark);
}

/* The real control: visually hidden, still focusable, still a slider to assistive tech. The
   canvas is only a picture of its value. */
.next .compass-input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  margin: 0;
  cursor: inherit;
  /* Pointer events go to the wrapper, which runs the angle-based drag; leaving them here would
     give the dial a linear left-to-right drag that ignores the needle entirely. */
  pointer-events: none;
}
.next .map-compass:focus-within canvas {
  outline: 2px solid var(--seal);
  outline-offset: 3px;
  border-radius: 50%;
}

.next .map-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  text-align: center;
  color: var(--ink-soft);
}
.next .map-empty code {
  font-family: var(--font-mono);
  background: var(--parch-aged);
  padding: 1px 5px;
}

/* ── Landmark card ──────────────────────────────────────────────────────────
   Expands from its landmark and lingers after the pointer leaves, so its video links can be
   reached. It therefore takes pointer events, unlike a normal tooltip. */
.next .landmark-card {
  position: absolute;
  top: 0;
  left: 0;
  width: 290px;   /* 268 at the old type size; widened with it so lines wrap about where they did */
  padding: 10px 12px 12px;
  /* Fill, border and outline come from the .landmark-card rule in the tooltip block above. */
  color: var(--mc-white);
  font-family: var(--font-mono);
  pointer-events: auto;
  opacity: 0;
  scale: .94;
  transition: opacity .16s ease, scale .18s cubic-bezier(.2, .9, .3, 1.1);
}
.next .landmark-card.open { opacity: 1; scale: 1; }

/* Every line gets the game's glyph shadow: a hard 2px offset, no blur. */
.next .landmark-card > * { text-shadow: 2px 2px 0 var(--mc-shadow); }

/* No close button. On touch the card is dismissed by panning the map or by tapping the same
   landmark again, which is what a map already teaches you to do; a close affordance was one more
   target to hit and, sitting inside a viewport that calls preventDefault on touchstart, it could
   not reliably be hit at all. */

/* Colour carries the meaning here, exactly as it does in a real tooltip: white names the thing,
   grey describes it, green is people, aqua is something you can click. */
.next .lc-name {
  font-size: var(--fs-md);
  font-weight: 500;
  line-height: 1.3;
  color: var(--mc-white);
}
.next .lc-meta {
  font-size: var(--fs-xs);
  line-height: 1.45;
  color: var(--mc-gold);
  margin-top: 3px;
}
.next .lc-meta-part { white-space: nowrap; }
.next .lc-desc {
  font-size: var(--fs-ui);
  line-height: 1.65;
  color: var(--mc-grey);
  margin-top: 9px;
}
.next .lc-creators {
  font-size: var(--fs-ui);
  line-height: 1.5;
  color: var(--mc-green);
  margin-top: 6px;
}
.next .lc-vids {
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px solid rgba(80, 0, 255, .35);
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.next .lc-vid {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--mc-aqua);
  font-size: var(--fs-sm);
  line-height: 1.4;
  text-shadow: 2px 2px 0 var(--mc-shadow);
}
.next .lc-vid:hover { color: var(--mc-white); }
.next .lc-vid img {
  flex: 0 0 auto;
  width: 76px;
  height: 43px;
  object-fit: cover;
  /* No sepia here: that existed to settle the thumbnails into parchment, and on a dark panel it
     only makes them muddy. A violet keyline ties them to the border instead. */
  border: 1px solid rgba(80, 0, 255, .55);
}
.next .lc-more {
  font-size: var(--fs-sm);
  color: var(--mc-dgrey);
}

/* ── Return toast ───────────────────────────────────────────────────────────
   Minecraft's advancement toast, used for "here is what you missed". See public/toast.js.

   Deliberately NOT the violet item tooltip the rest of this file builds. In the game those are two
   different objects — the tooltip is something you asked to see by pointing at it, the toast is the
   game speaking first — and the toast is a flat dark panel with a pale keyline, no violet anywhere.
   Borrowing the tooltip's frame here would make it read as a tooltip that had come loose.

   What is kept is the rest of the game's text grammar: the mono face, the hard 2px glyph shadow,
   and yellow for the announcement line, which is the colour "Advancement Made!" is drawn in. */
.next .toast-rail {
  position: fixed;
  /* Clear of the floating header, which is centred and can be tall on the map page. */
  top: 74px;
  right: 16px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: 8px;
  /* The rail is a container, not a target: clicks pass through the gaps to the map underneath.
     Each toast turns them back on for itself. */
  pointer-events: none;
}

.next .mc-toast {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 336px;
  padding: 8px;
  text-decoration: none;
  font-family: var(--font-mono);
  background: linear-gradient(to bottom, #313131, #1E1E1E);
  /* Three rings, outside in: the black keyline the game draws around every GUI element, the pale
     inner border of the toast texture, and a lit top edge so the panel reads as raised rather than
     printed. The drop shadow is what lifts it off the map behind it. */
  box-shadow:
    inset 0 0 0 2px #6E6E6E,
    inset 0 3px 0 rgba(255, 255, 255, .13),
    inset 0 -3px 0 rgba(0, 0, 0, .35),
    0 0 0 2px #0B0906,
    0 4px 12px rgba(0, 0, 0, .5);
  /* Off to the right of the viewport, and the rail's own padding is not enough to hide the
     shadow — hence the extra 30px. */
  transform: translateX(calc(100% + 30px));
  opacity: 0;
  transition: transform .34s cubic-bezier(.2, .9, .3, 1.05), opacity .2s ease;
}
.next .mc-toast.in {
  transform: translateX(0);
  opacity: 1;
}
/* Leaving is the arrival run backwards, which needs its own curve. The arrival's is front-loaded —
   it covers most of the distance in the first few frames and settles — and reused on the way out
   it did the same thing in reverse: gone within ~90ms, which read as the toast blinking out.
   Ease-in instead, so it starts slow and accelerates off the edge, and the fade keeps pace with
   the slide instead of finishing first.

   Then the space it held closes up (.gone), so the toasts under it slide up into place rather
   than jumping 44px in one frame. The height is written inline by toast.js — a transition needs a
   number to start from, not auto — and the negative margin takes the rail's 8px gap with it. */
.next .mc-toast.out {
  transform: translateX(calc(100% + 30px));
  opacity: 0;
  transition: transform .3s cubic-bezier(.55, 0, .8, .35), opacity .3s cubic-bezier(.6, 0, .9, .4);
}
.next .mc-toast.gone {
  overflow: hidden;
  height: 0 !important;
  padding-top: 0;
  padding-bottom: 0;
  margin-bottom: -8px;
  transition: height .2s ease, padding .2s ease, margin .2s ease;
}
.next .mc-toast:hover {
  background: linear-gradient(to bottom, #3B3B3B, #262626);
}
.next .mc-toast:focus-visible {
  outline: 2px solid var(--mc-white);
  outline-offset: 2px;
}

.next .toast-icon {
  flex: 0 0 auto;
  width: 72px;
  height: 41px;
  object-fit: cover;
  border: 1px solid rgba(255, 255, 255, .24);
}

.next .toast-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;   /* or the title's ellipsis never triggers inside the flex row */
}
.next .toast-text > * {
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .75);
}

.next .toast-title {
  font-size: var(--fs-ui);
  font-weight: 500;
  line-height: 1.2;
  color: var(--mc-yellow);
}
/* Two lines of title, then clipped. A third line pushes the toast out of the game's proportions,
   and the creator's name underneath is doing the work of "is this for me" anyway. */
.next .toast-body {
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--mc-white);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.next .toast-who {
  font-size: var(--fs-sm);
  line-height: 1.3;
  color: var(--mc-grey);
}

@media (max-width: 720px) {
  .next .map-compass { width: 148px; height: 148px; bottom: -26px; }
  .next .map-compass canvas { width: 148px; height: 148px; }
  .next .compass-readout { top: 82px; }
  .next .landmark-card { width: min(272px, calc(100vw - 40px)); }
  /* Full width less a gutter either side: a fixed 316px panel pushed off the edge of a small
     phone, taking its own shadow with it. The header wraps to two lines at this width — wordmark
     over nav — so the rail starts below both rather than against the nav's baseline. */
  .next .toast-rail { top: 92px; right: 10px; left: 10px; }
  .next .mc-toast { width: auto; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; }
}

/* ── The dark ground ────────────────────────────────────────────────────────
   Every page that is not the map stands on this: a dark field with one lit surface on it, which is
   how Minecraft's own menus are built. It used to be two crossed gradients standing in for a block
   texture; it is real obsidian now, tiled at block scale with nearest-neighbour scaling.

   The darkening layer over it is not decoration. A 16px texture blown up and tiled across a whole
   screen has an obvious repeat, and the game covers its own menu background for exactly the same
   reason. Knocked back like this it reads as a surface rather than as wallpaper. */
.next.dark-stage,
body.next.dark-stage { background: #150E1C; }

.next .chapter-stage {
  position: relative;
  min-height: 100dvh;
  padding: calc(var(--header-h) + 26px) 22px 56px;
  background:
    linear-gradient(rgba(10, 6, 14, .55), rgba(10, 6, 14, .55)),
    url(/obsidian.webp) 0 0 / 44px 44px repeat,
    #150E1C;
  image-rendering: pixelated;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-6);
}

/* The same three lines open every dark page — title, a hairline, then one line of counts — so
   moving between them the heading never jumps. Every page supplies .chapter-sub, even when it has
   little to say, for the same reason. */
.next .chapter-head {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
}
.next .chapter-title {
  font-family: var(--font-serif);
  font-size: var(--fs-2xl);
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: .08em;
  color: var(--on-dark);
  text-shadow: 2px 2px 0 rgba(0,0,0,.6);
}
/* A short rule in the header's gold, with a pixel diamond at its centre — the one ornament the
   pages share, standing in for the cartouche seams either side of the wordmark. */
.next .chapter-head::after {
  content: '';
  order: 1;
  width: 72px;
  height: 6px;
  background:
    linear-gradient(var(--on-dark-gold), var(--on-dark-gold)) center / 100% 2px no-repeat,
    linear-gradient(var(--on-dark-gold), var(--on-dark-gold)) center / 6px 6px no-repeat;
  opacity: .7;
}
.next .chapter-sub {
  order: 2;
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  letter-spacing: .04em;
  color: var(--on-dark-soft);
}

/* ── Season HUD ─────────────────────────────────────────────────────────────
   A locator bar carrying the chapters, over an experience bar. The locator is the game's own
   idea — a horizontal strip where a marker's position means *where something is* — which is
   exactly what a chapter has: a place in the season. The experience bar underneath fills to where
   the selected chapter ends, so picking an early one visibly winds the season back. */
.next .season-hud {
  width: min(760px, 100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

.next .hud-name {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--fs-lg);
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--on-dark);
  text-shadow: 2px 2px 0 rgba(0,0,0,.7);
  min-height: 22px;
}

.next .locator {
  position: relative;
  width: 100%;
  height: 26px;
}
.next .loc-track {
  position: absolute;
  left: 0;
  right: 0;
  top: 9px;
  height: 8px;
  background: #0E0B08;
  /* The bevel that makes every Minecraft panel look pressed into the screen: dark along the top
     and left, light along the bottom and right. */
  box-shadow:
    inset 2px 2px 0 rgba(0,0,0,.65),
    inset -2px -2px 0 rgba(246,241,230,.14);
}

.next .loc-mark {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  width: 28px;
  height: 26px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
/* The marker itself is a blocky pip on a stem, drawn from two boxes rather than a rounded pin. */
.next .loc-mark i {
  display: block;
  width: 8px;
  height: 8px;
  margin: 0 auto;
  background: var(--parch-aged);
  box-shadow:
    inset 1px 1px 0 rgba(246,241,230,.5),
    inset -1px -1px 0 rgba(0,0,0,.5),
    0 0 0 2px #0E0B08;
  transition: transform .12s ease, background .12s ease;
}
.next .loc-mark::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 9px;
  width: 2px;
  height: 8px;
  margin-left: -1px;
  background: rgba(246,241,230,.3);
}
.next .loc-mark:hover i { transform: scale(1.25); background: var(--parch-lit); }
/* Names the chapter under the pointer before it is chosen — otherwise the only way to find out what
   a pip is was to click it. Drawn as a small tooltip in the HUD's own black and bevel. The current
   chapter's name is already in .hud-name above, so it gets none. */
.next .loc-mark::before {
  content: attr(data-name);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  padding: 3px 8px 4px;
  white-space: nowrap;
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--on-dark);
  background: #0E0B08;
  box-shadow: inset 1px 1px 0 rgba(246,241,230,.14), 0 0 0 1px #000;
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease;
}
.next .loc-mark:hover:not(.is-current)::before,
.next .loc-mark:focus-visible:not(.is-current)::before { opacity: 1; }
.next .loc-mark:focus-visible { outline: 2px solid var(--on-dark); outline-offset: 2px; }
.next .loc-mark.is-active i { background: var(--seal); }
.next .loc-mark.is-planned i { background: #5A5142; }
.next .loc-mark.is-current i {
  background: #F6F1E6;
  transform: scale(1.5);
  box-shadow:
    inset 1px 1px 0 #fff,
    inset -1px -1px 0 rgba(0,0,0,.45),
    0 0 0 2px #0E0B08,
    0 0 10px rgba(246,241,230,.55);
}
.next .loc-mark.is-current::after { background: #F6F1E6; }

/* ── Experience bar ─────────────────────────────────────────────────────────
   The HUD bar, at the game's proportions: a long thin track with a bright green fill and the
   level number sitting above it in green with a hard black outline. */
.next .xp {
  position: relative;
  width: 100%;
  padding-top: 16px;
}
.next .xp-level {
  position: absolute;
  top: -2px;
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: 500;
  color: #80FF20;
  /* Outlined on all four sides, the way the game draws it — a single drop shadow reads as a
     web label, not as a HUD number. */
  text-shadow:
    -2px 0 0 #000, 2px 0 0 #000, 0 -2px 0 #000, 0 2px 0 #000,
    -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000, 2px 2px 0 #000;
}
.next .xp-track {
  position: relative;
  display: block;
  width: 100%;
  height: 10px;
  background: #0E0B08;
  box-shadow:
    inset 2px 2px 0 rgba(0,0,0,.7),
    inset -2px -2px 0 rgba(246,241,230,.12);
}
.next .xp-fill {
  display: block;
  height: 100%;
  background: #7EFC20;
  /* A darker band along the bottom, which is what gives the fill its thickness in game. */
  box-shadow: inset 0 -3px 0 #3E9E10, inset 0 2px 0 rgba(255,255,255,.25);
}

/* Where the selected chapter sits on the same scale as the fill. Drawn as an outline rather than
   a solid, so it reads over both the filled and the empty part of the track without hiding
   either. */
.next .xp-span {
  position: absolute;
  top: -3px;
  bottom: -3px;
  background: rgba(246, 241, 230, .14);
  border-left: 2px solid #F6F1E6;
  border-right: 2px solid #F6F1E6;
  transition: left .25s ease, width .25s ease;
  pointer-events: none;
}

.next .xp-scale {
  display: flex;
  justify-content: space-between;
  margin-top: var(--sp-2);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: .12em;
  color: var(--on-dark-faint);
}

/* ── The book ───────────────────────────────────────────────────────────────
   Minecraft's written book: a tan page with a thick frame, arrows at the foot, and a page count
   between them. */
/* 680 rather than 560: the book never scrolls now, so height is the scarce thing and width is
   what there is plenty of beside it. Each leaf gains 60px, which puts a thumbnail and a two-line
   title side by side and takes a line or two off every description. */
.next .book {
  width: min(680px, 100%);
  animation: none;
}

/* The chapter page is one screen, like the creators and gallery pages: the book takes whatever
   height the heading and HUD leave, up to a cap, and holds that size from page to page. It used to
   grow with its content — a landmark with three videos was 885px of book under 344px of heading,
   so the page scrolled and the page-turn arrows went below the fold. */
.next .book-stage {
  height: 100dvh;
  overflow: hidden;
  padding-bottom: var(--sp-6);
}
.next .book-stage .book {
  flex: 1 1 auto;
  min-height: 0;
  max-height: 620px;
  display: flex;
  flex-direction: column;
}
.next .book-stage .book-page {
  flex: 1 1 auto;
  min-height: 0;
}
/* The last line of defence: a landmark with more on it than any leaf holds loses its tail rather
   than pushing the arrows out of the book. The line clamps below are what should make it rare. */
.next .book-stage .book-body {
  min-height: 0;
  overflow: hidden;
}
.next .book.swap { animation: book-swap .26s ease; }
@keyframes book-swap {
  0%   { opacity: 0; transform: translateY(10px) rotate(-.6deg); }
  100% { opacity: 1; transform: none; }
}

/* Two things separate a Minecraft panel from a rectangle with a border: the corners are cut
   rather than square, and the frame is bevelled — lit along the top and left, shaded along the
   bottom and right, so the panel reads as a raised object catching light from above.

   The chamfer is done with clip-path on a wrapper, because clip-path cuts everything inside it:
   putting it on .book-page would take the corners off the page's own content too. */
.next .book {
  --chamfer: 8px;
  filter: drop-shadow(4px 6px 0 rgba(0, 0, 0, .45));
}
.next .book-page {
  position: relative;
  background: var(--frame);
  padding: 6px;
  clip-path: polygon(
    var(--chamfer) 0, calc(100% - var(--chamfer)) 0,
    100% var(--chamfer), 100% calc(100% - var(--chamfer)),
    calc(100% - var(--chamfer)) 100%, var(--chamfer) 100%,
    0 calc(100% - var(--chamfer)), 0 var(--chamfer)
  );
  display: flex;
  flex-direction: column;
  min-height: 420px;
}
/* The paper, inset inside the frame and chamfered a step tighter so the frame stays even at the
   corners instead of pinching to a point. */
.next .book-page::before {
  content: '';
  position: absolute;
  inset: 5px;
  background: var(--parch);
  clip-path: polygon(
    calc(var(--chamfer) - 3px) 0, calc(100% - var(--chamfer) + 3px) 0,
    100% calc(var(--chamfer) - 3px), 100% calc(100% - var(--chamfer) + 3px),
    calc(100% - var(--chamfer) + 3px) 100%, calc(var(--chamfer) - 3px) 100%,
    0 calc(100% - var(--chamfer) + 3px), 0 calc(var(--chamfer) - 3px)
  );
  /* Bevel: light from above-left, shade below-right. */
  box-shadow:
    inset 3px 3px 0 rgba(255, 250, 235, .5),
    inset -3px -3px 0 rgba(74, 56, 38, .3);
}

/* The fold. An open book is two leaves meeting in the middle, and the gutter is where the paper
   curves away from the light on both sides — so it is symmetric, and its darkest line is the exact
   centre. This replaced a darker band down the left edge, which is what a single page has; keeping
   both would have claimed the book was open and closed at once.

   A gradient, so it has to be a background rather than the box-shadow the bevel uses. It spans the
   whole page including the frame (inset: 0, not the paper's inset: 5px), which is what puts the
   small dark notch where the fold meets the top and bottom of the binding. */
.next .book-page::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent              calc(50% - 58px),
    rgba(255, 250, 235, .20) calc(50% - 34px),
    rgba(74, 56, 38, .09)    calc(50% - 11px),
    rgba(74, 56, 38, .26)    calc(50% - 2px),
    rgba(74, 56, 38, .30)    50%,
    rgba(74, 56, 38, .26)    calc(50% + 2px),
    rgba(74, 56, 38, .09)    calc(50% + 11px),
    rgba(255, 250, 235, .20) calc(50% + 34px),
    transparent              calc(50% + 58px));
}
/* Content sits above both the ::before paper and the ::after fold. */
.next .book-page > * { position: relative; z-index: 1; }

.next .book-body,
.next .book-foot { padding: 0 26px; }
.next .book-body { padding-top: 24px; }

/* Two leaves, one each side of the fold. The gutter is centred on it: the body's content box is
   symmetric about the page centre — 32px of padding either side of a 560px page — so equal columns
   put the gap exactly over the fold, and each leaf clears the fold's inner shadow by a few px.
   min-width: 0 because a grid track's default min-content floor would let a long word push the
   column wider than its share and drag the whole layout off-centre. */
.next .book-body {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 36px;
  align-content: start;
}
.next .bk-leaf { min-width: 0; }
.next .book-body.turn-f { animation: page-f .16s ease; }
.next .book-body.turn-b { animation: page-b .16s ease; }
@keyframes page-f { from { opacity: 0; transform: translateX(14px); } }
@keyframes page-b { from { opacity: 0; transform: translateX(-14px); } }

.next .bk-kicker {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  letter-spacing: .12em;
  color: var(--ink-soft);
}
.next .bk-title {
  font-family: var(--font-serif);
  font-size: 34px;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: .02em;
  margin: 8px 0 0;
  color: var(--ink);
}
.next .bk-range {
  margin: 8px 0 0;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .08em;
  color: var(--seal);
}
.next .bk-desc {
  margin: var(--sp-4) 0 0;
  font-size: var(--fs-base);
  line-height: 1.85;
  color: var(--ink-soft);
  max-width: 40ch;   /* the leaf is the real constraint; this is just a readable measure */
}
/* On a landmark's page the description shares the leaf with its videos, so it is held to five lines.
   The payload already cuts it at 260 characters; this is the same limit in the unit the page
   actually runs out of. The chapter's own description on the title page is left whole. */
.next .bk-mark .bk-desc {
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 5;
  line-clamp: 5;
  -webkit-box-orient: vertical;
}
.next .bk-count-note {
  margin: var(--sp-5) 0 0;
  padding-top: var(--sp-3);
  border-top: 1px solid var(--rule-soft);
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  color: var(--ink-soft);
}

/* The contents. Name left, day right, a dotted leader between them — how a printed book's contents
   page reads, which is what the title page of a chapter is. Each row is a button: it turns the book
   to that landmark. */
.next .bk-toc {
  list-style: none;
  margin: var(--sp-2) 0 0;
  padding: 0;
}
.next .bk-toc-item {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  width: 100%;
  padding: 5px 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--fs-ui);
  color: var(--ink);
  text-align: left;
  cursor: pointer;
  transition: color .12s ease;
}
.next .bk-toc-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.next .bk-toc-item::after {
  content: '';
  order: 1;
  flex: 1 1 auto;
  min-width: var(--sp-4);
  border-bottom: 2px dotted var(--rule);
  transform: translateY(-3px);
}
.next .bk-toc-day {
  order: 2;
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink-soft);
}
.next .bk-toc-item:hover,
.next .bk-toc-item:hover .bk-toc-day { color: var(--seal); }
.next .bk-toc-item:focus-visible { outline: 2px solid var(--seal); outline-offset: 2px; }
.next .bk-toc-more {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}

.next .bk-mark-name {
  font-family: var(--font-serif);
  font-size: var(--fs-xl);
  font-weight: 600;
  line-height: 1.25;
  margin: 8px 0 0;
  color: var(--ink);
}
.next .bk-coords {
  margin: var(--sp-2) 0 0;
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  letter-spacing: .06em;
  color: var(--ink-soft);
}
.next .bk-builders {
  margin: var(--sp-2) 0 0;
  font-size: var(--fs-ui);
  font-weight: 500;
  color: var(--verdigris);
}
.next .bk-vids {
  margin: var(--sp-4) 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
/* The first video: thumbnail beside a title of up to two lines. Stacked full width it was ~175px
   an entry, which is what made the book taller than the screen. Side by side this used to leave
   the title ~120px and three characters a line; on the 680px book it has ~180px, about thirteen. */
.next .bk-vid {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-ui);
  line-height: 1.45;
  transition: color .12s ease;
  color: var(--ink);
}
.next .bk-vid span {
  min-width: 0;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
}
.next .bk-vid:hover { color: var(--seal); }
/* Every video after the first: one line, with a stepped play mark in the frame's brown. */
.next .bk-vid-line {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-width: 0;
  padding: 2px 0;
  font-size: var(--fs-ui);
  line-height: 1.5;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: color .12s ease;
}
.next .bk-vid-line::before {
  content: '';
  flex: 0 0 auto;
  width: 7px;
  height: 8px;
  background: var(--frame);
  clip-path: polygon(0 0, 50% 0, 50% 25%, 100% 50%, 50% 75%, 50% 100%, 0 100%);
}
.next .bk-vid-line:hover { color: var(--seal); }
.next .bk-vid-line:hover::before { background: var(--seal); }
.next .bk-vid img {
  flex: 0 0 auto;
  width: 112px;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border: 3px solid var(--frame);
  /* Settles the thumbnails into the page, the way the parchment card used to do. */
  filter: sepia(.3) saturate(.85);
}
.next .bk-more,
.next .bk-empty {
  margin: var(--sp-2) 0 0;
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  color: var(--ink-soft);
}
.next .bk-goto {
  display: inline-block;
  margin: var(--sp-5) 0 0;
  padding: var(--sp-1) 0;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  font-weight: 500;
  letter-spacing: .04em;
  color: var(--seal);
  border-bottom: 2px solid transparent;
  transition: border-color .12s ease, color .12s ease;
}
.next .bk-goto:hover { border-bottom-color: currentColor; }

/* ── Page controls ──────────────────────────────────────────────────────────
   The arrows are the book GUI's: a stepped triangle, not a smooth one. */
.next .book-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 22px;
  padding: var(--sp-2) 0 var(--sp-3);
  margin-top: auto;
  border-top: 1px solid var(--rule-soft);
}
/* The button is the hit area, the arrow is drawn inside it. clip-path cuts hit-testing along with
   paint, so with the shape on the button itself only the 26×20 triangle was clickable — most
   clicks near an arrow landed on paper. */
.next .book-arrow {
  position: relative;
  width: 48px;
  height: 36px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}
.next .book-arrow::before {
  content: '';
  position: absolute;
  left: 11px;
  top: 8px;
  width: 26px;
  height: 20px;
  background: var(--ink-soft);
  transition: background .12s ease, transform .12s ease;
  clip-path: polygon(100% 0, 100% 25%, 45% 25%, 45% 0, 20% 50%, 45% 100%, 45% 75%, 100% 75%, 100% 100%);
}
.next .book-arrow.next::before { transform: scaleX(-1); }
.next .book-arrow:hover:not(:disabled)::before { background: var(--seal); transform: translateX(-2px); }
.next .book-arrow.next:hover:not(:disabled)::before { transform: scaleX(-1) translateX(-2px); }
.next .book-arrow:disabled { cursor: default; }
.next .book-arrow:disabled::before { background: var(--parch-deep); }
.next .book-arrow:focus-visible { outline: 2px solid var(--seal); outline-offset: -2px; }
.next .book-count {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  letter-spacing: .1em;
  color: var(--ink-soft);
  min-width: 64px;
  text-align: center;
}

/* ── Advancement tooltip ────────────────────────────────────────────────────
   Not the item tooltip. The advancement screen has its own, and it is built the other way round:
   a bar in the frame's own colour carrying the title in dark ink, and a black panel under it
   carrying the description in green. It also touches the node it belongs to rather than floating
   near it, which is what makes it read as part of the tree rather than as a hint layer over it.

   The map's landmark card stays as it is — that one had to become interactive, because its video
   links have to be clickable. This one is a tooltip proper: no pointer events, gone the moment the
   pointer leaves. */
/* Lives inside the tree, not in the body. As a fixed element it sat in its own stacking context
   above everything, and the node it belongs to — trapped under the tree's transform — could never
   be raised over it. Here the order is wires, nodes, tooltip, then the open node on top, so the
   tooltip hides the frames it crosses while the one it describes stays whole. */
.next .adv-tip {
  position: absolute;
  z-index: 2;
  width: 330px;
  display: flex;
  flex-direction: column;
  font-family: var(--font-mono);
  pointer-events: none;
  opacity: 0;
  transition: opacity .08s ease;
  /* Set by next-adv.js to the node's own width. */
  --tip-inset: 56px;
}
.next .adv-tip.open { opacity: 1; }
/* Out of room below, so the detail grows upward instead — the title bar has to keep its line with
   the node, which is the one thing that must not move. */
.next .adv-tip.up { flex-direction: column-reverse; }

/* The title bar is the node's own band continued sideways: same gold, same height, same top edge,
   so the frame and the bar read as one object. Getting this wrong — floating the tooltip near the
   node instead of on its line — is what made it look like a web tooltip pasted over a game. */
.next .adv-tip .tip-title {
  margin: 0 0 0 var(--tip-inset);
  height: 56px;
  display: flex;
  align-items: center;
  padding: 0 12px;
  background: #BC8F2F;
  color: #FFFFFF;
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: 1.25;
  text-shadow: 2px 2px 0 rgba(59, 41, 6, .85);
  /* Lit along the top, shaded along the bottom, and a keyline on the three outer sides — the
     fourth is where the node's own keyline already sits. */
  box-shadow:
    inset 0 3px 0 rgba(255, 240, 186, .5),
    inset 0 -3px 0 rgba(70, 47, 6, .5),
    0 -2px 0 #0B0906, 0 2px 0 #0B0906, 2px 0 0 #0B0906;
}
/* Flipped to the node's left: the inset moves to the other side. */
.next .adv-tip.flip .tip-title {
  margin: 0 var(--tip-inset) 0 0;
  box-shadow:
    inset 0 3px 0 rgba(255, 240, 186, .5),
    inset 0 -3px 0 rgba(70, 47, 6, .5),
    0 -2px 0 #0B0906, 0 2px 0 #0B0906, -2px 0 0 #0B0906;
}
/* An unobtained advancement's bar is grey, matching its frame. */
.next .adv-tip.dim .tip-title {
  background: #8B8B8B;
  text-shadow: 2px 2px 0 rgba(30, 30, 30, .85);
  box-shadow:
    inset 0 3px 0 rgba(255, 255, 255, .42),
    inset 0 -3px 0 rgba(30, 30, 30, .45),
    0 -2px 0 #0B0906, 0 2px 0 #0B0906, 2px 0 0 #0B0906;
}
.next .adv-tip.dim.flip .tip-title { box-shadow:
  inset 0 3px 0 rgba(255, 255, 255, .42),
  inset 0 -3px 0 rgba(30, 30, 30, .45),
  0 -2px 0 #0B0906, 0 2px 0 #0B0906, -2px 0 0 #0B0906;
}

/* The detail runs the full width, under the node as well as under the bar — that overhang is what
   stops the two halves reading as two separate boxes. */
.next .adv-tip .tip-body {
  padding: 10px 12px 12px;
  /* Opaque. At 88% the frames behind it showed through as ghosts, which is most of what made the
     tooltip look like a rendering fault. */
  background: #070707;
  outline: 2px solid #0B0906;
}
/* Sans rather than the mono of the frame: the lines here are mostly Chinese — a bio, a landmark
   description — and in the mono stack they were set at 11px. */
.next .adv-tip .tip-body > * {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  line-height: 1.6;
  text-shadow: 2px 2px 0 var(--mc-shadow);
}
/* Green carries the line that answers "what was earned here" — in the game that is the task
   ("Upgrade your Pickaxe"), and here it is the counts and coordinates. The free-text bio or
   description is a note underneath in grey, not the headline. Everything else falls back to the
   item tooltip's roles, so the colours mean the same things site-wide. */
.next .adv-tip .tip-meta   { color: var(--mc-green); font-family: var(--font-mono); }
.next .adv-tip .tip-desc   { color: #C8C8C8; margin-top: var(--sp-2); }
.next .adv-tip .tip-people { color: var(--mc-aqua); margin-top: var(--sp-2); font-size: var(--fs-sm); }
/* The compact form next-adv.js falls back to when the full tooltip fits on neither side of its
   node — see place(). */
.next .adv-tip.tight .tip-desc {
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
}
.next .adv-tip.tight .tip-hint { display: none; }
.next .adv-tip.tight .tip-vid img { width: 48px; height: 27px; }
.next .adv-tip.tight .tip-body { padding-top: 7px; padding-bottom: 8px; }
.next .adv-tip.tight .tip-desc,
.next .adv-tip.tight .tip-people { margin-top: 4px; }
.next .adv-tip.tight .tip-vids { margin-top: 6px; padding-top: 6px; }
.next .adv-tip .tip-hint   { color: #8A8A8A; margin-top: var(--sp-2); font-size: var(--fs-sm); }

/* The videos attached to the node, the same way the map's landmark card lists them — aqua, because
   in the game's palette that is the colour of something you can go to. They are not links here: the
   tooltip takes no pointer events, and the node itself already goes somewhere. Following one means
   clicking the node and finding them on the map, which is where they are clickable. */
.next .adv-tip .tip-vids {
  margin-top: 9px;
  padding-top: 8px;
  border-top: 1px solid rgba(255, 255, 255, .14);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.next .adv-tip .tip-vid {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  line-height: 1.4;
  color: var(--mc-aqua);
  text-shadow: 2px 2px 0 var(--mc-shadow);
}
.next .adv-tip .tip-vid img {
  flex: 0 0 auto;
  width: 64px;
  height: 36px;
  object-fit: cover;
  image-rendering: auto;
  border: 1px solid rgba(255, 255, 255, .22);
}

/* ── Advancement screen ─────────────────────────────────────────────────────
   The whole GUI, not just the tree: a tab strip, a grey panel with its title in the corner, and a
   stone field inside it that the tree is drawn on and dragged around.

   The panel is Minecraft's own grey rather than this site's parchment and wood. That is not a
   break in the system — the book on the chapter page is wood because a book is wood, and the
   advancement screen is grey because that is what it is made of. Using parchment here would make
   it a parchment panel with advancements drawn on it, which is a different and weaker idea.

   Everything positional is already in the HTML — the page module computes the layout — so this
   file only dresses it. */
.next .adv-stage {
  gap: 14px;
  height: 100dvh;
  overflow: hidden;
}

.next .adv-gui {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  width: min(940px, 100%);
}

/* Tabs. One season, so one tab — which is exactly what the screen looks like early on, and the
   strip is most of what makes it recognisable at a glance. */
/* Pulled 2px left so the tab's black edge lands on the same column as the panel's, which sits
   outside the panel's box. Flush with the panel's *box* still leaves the two black lines a step
   apart, which is what made the corner look like three separate pieces of trim. */
.next .adv-tabs {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  gap: 6px;
  margin-left: -2px;
}
/* Every surface here is trimmed the same: 2px of black outside, 3px of bevel inside. The tab was
   built at 2px and the panel at 3px, and side by side at the join the difference is the first
   thing you see. */
.next .adv-tab {
  width: 66px;
  height: 46px;
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  padding: 0;
  font: inherit;
  cursor: pointer;
  border: 2px solid #000;
  background: #8B8B8B;
  /* Two steps to a side, not one. A single band of pure white against a single band of #555 is
     what the raw GUI texture does at 1x, but blown up to this size the lit and shaded edges meet
     with nothing between them — the node frames read better precisely because their bevel passes
     through a middle tone on the way. */
  box-shadow:
    inset 2px 2px 0 #B4B4B4,
    inset 4px 4px 0 #9E9E9E,
    inset -2px -2px 0 #4E4E4E,
    inset -4px -4px 0 #6C6C6C;
}
.next .adv-tab:not(.is-active):hover { background: #9C9C9C; }
.next .adv-tab:focus { outline: none; }
.next .adv-tab:focus-visible { box-shadow: inset 0 0 0 3px #FFFFFF; }
/* An unselected tab's icon is dimmed the way an unearned frame is, so the strip reads as "you are
   here" rather than as six equally live buttons. */
.next .adv-tab:not(.is-active) img { filter: grayscale(1) brightness(.72) drop-shadow(2px 2px 0 rgba(0, 0, 0, .4)); }
/* The active tab has no floor: its grey is the panel's grey, running straight down into it with no
   line between. Two things have to be covered for that to be true, and missing the second left a
   pale seam across the join — the panel's black keyline, which sits 2px above its box, and the
   panel's lit top bevel, which sits 3px inside it. Hence 5px of overlap, and a matching bottom
   padding so the icon still centres on the 46px that are actually visible. */
.next .adv-tab.is-active {
  position: relative;
  z-index: 3;
  height: 53px;
  margin-bottom: -5px;
  /* Centres the icon on the 46px that are actually visible — above the panel's own top line —
     rather than on the whole box, most of the rest of which is buried in the panel. */
  padding-bottom: 9px;
  border-bottom: 0;
  background: #C6C6C6;
  box-shadow:
    inset 2px 2px 0 #F2F2F2,
    inset 4px 4px 0 #DCDCDC,
    inset -2px 0 0 #6E6E6E,
    inset -4px 0 0 #A4A4A4;
}
/* Below the panel's top line the tab is inside the panel, so its outline has to stop there and its
   lit top edge has to be covered — this patch is exactly the 5px of overlap, no more. Taking two
   pixels more cut the tab's side borders off *above* the panel's black line instead of letting
   them meet it, and the two were left not making a corner at all, just stopping near each other
   with grey in between. The first tab is the exception: its left edge is the panel's own column,
   so there the border and bevel carry straight on down. */
.next .adv-tab.is-active::after {
  content: '';
  position: absolute;
  left: -2px;
  right: -2px;
  bottom: 0;
  height: 5px;
  background: #C6C6C6;
}
.next .adv-tab.is-active:first-child::after { left: 4px; }

.next .adv-tab img {
  display: block;
  width: 34px;
  height: 34px;
  object-fit: cover;
  /* The heads are mostly grey themselves and sit on a grey tab. A hard offset shadow — no blur,
     following the head's own silhouette — is what lifts them off it. */
  filter: drop-shadow(2px 2px 0 rgba(0, 0, 0, .45));
  image-rendering: auto;
}
/* Block textures keep their pixels; a channel avatar is a photograph and must not. */
.next .adv-tab img.pixel { image-rendering: pixelated; }

.next .adv-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  /* The top padding is exactly the active tab's overhang, so the title's band starts where the tab
     stops and can centre itself in what is left. */
  padding: 5px 8px 9px;
  background: #C6C6C6;
  /* Two steps to a side. See the note on .adv-tab: at this scale a single white band meeting a
     single #555 band has nothing between them, and the corners read as a seam rather than a bevel. */
  box-shadow:
    inset 2px 2px 0 #F2F2F2,
    inset 4px 4px 0 #DCDCDC,
    inset -2px -2px 0 #6E6E6E,
    inset -4px -4px 0 #A4A4A4,
    0 0 0 2px #000;
}
/* The GUI title sits in the panel's corner in plain dark grey — Minecraft draws this one without
   the glyph shadow, which is why it looks printed on rather than floating above. */
/* Left-aligned like the game's own GUI titles, but centred in its band — sized as a row rather than
   left to fall wherever the line box lands between two different paddings. */
.next .adv-panel-title {
  margin: 0 0 0 4px;
  height: 32px;
  display: flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: var(--fs-ui);
  font-weight: 500;
  letter-spacing: .06em;
  color: #3A3A3A;
}

.next .adv-window {
  position: relative;
  flex: 1 1 auto;
  min-height: 200px;
  overflow: hidden;
  cursor: grab;
  touch-action: none;
  /* Stone, tiled at block scale, darkened the way the game dims the field behind the tree so the
     gold frames carry. The nearest-neighbour scaling is the whole point: a 16px texture smoothed
     up to 48 would read as grey noise. */
  background:
    linear-gradient(rgba(0, 0, 0, .44), rgba(0, 0, 0, .44)),
    url(/stone.webp) 0 0 / 48px 48px repeat;
  image-rendering: pixelated;
  box-shadow: inset 0 0 0 2px #000;
}
.next .adv-window.dragging { cursor: grabbing; }
/* next-adv.js sets this when the whole tree already fits — nothing to grab. */
.next .adv-window.is-static { cursor: default; }

/* The vignette the game draws over the field's edges: a narrow fade that says "this continues past
   the frame", not a curtain. Wide or heavy, it eats the nodes nearest the edge — which are exactly
   the ones you are about to drag into view. */
.next .adv-window::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  background:
    linear-gradient(to right,  rgba(8, 8, 8, .8) 0, transparent 4.5%, transparent 95.5%, rgba(8, 8, 8, .8) 100%),
    linear-gradient(to bottom, rgba(8, 8, 8, .8) 0, transparent 5.5%, transparent 94.5%, rgba(8, 8, 8, .8) 100%);
}

.next .adv-tree {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
}

/* Wires. Two passes: the black casings (inflated a pixel) then the white cores, so the corners
   join cleanly instead of showing a nick where two outlined segments overlap. */
.next .adv-wires i {
  position: absolute;
  display: block;
  background: #0B0906;
}
.next .adv-wires.ink i { background: #FFFFFF; }

/* ── Advancement node ───────────────────────────────────────────────────────
   A tan tile with a bevel and a hard black keyline, the icon sitting straight on it. Obtained is
   gold, unobtained grey — the game's own signal, driven here by whether the creator has built
   anything yet.

   Three frame shapes, cut with clip-path rather than border-radius: task is a plain square, goal
   has its corners taken off, challenge is spiked. */
.next .adv-node {
  position: absolute;
  z-index: 1;
  width: 56px;
  height: 56px;
  color: inherit;
  text-decoration: none;
}
/* The node the tooltip belongs to sits above it, so its frame is never half-covered. */
.next .adv-node.is-open { z-index: 3; }
/* The frame is two layers, not one bordered box: black underneath, the tile inset two pixels on
   top. A keyline drawn as a box-shadow — inset or outset — is always a rectangle, so clip-path cut
   it away exactly along the chamfers and the corners came out bare. Two layers sharing one clip
   means the black follows whatever shape the clip makes, on the diagonals as well as the sides. */
.next .adv-frame {
  position: relative;
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  background: #0B0906;
  transition: transform .09s ease;
}
.next .adv-frame::before {
  content: '';
  position: absolute;
  inset: 2px;
  background: #8B8B8B;
  box-shadow:
    inset 3px 3px 0 rgba(255, 255, 255, .38),
    inset -3px -3px 0 rgba(30, 30, 30, .45);
}
.next .adv-frame > * { position: relative; z-index: 1; }

.next .adv-node.is-done .adv-frame::before {
  background: #BC8F2F;
  box-shadow:
    inset 3px 3px 0 rgba(255, 240, 186, .5),
    inset -3px -3px 0 rgba(70, 47, 6, .5);
}

/* Every frame is a plain square. The chamfer was meant to distinguish creators from landmarks the
   way the game's goal frame does, but at 56px against a square tooltip bar it just looked like the
   corners had failed to render. The icon already says which is which. */
.next a.adv-node:hover .adv-frame,
.next a.adv-node:focus-visible .adv-frame { transform: scale(1.09); }
.next a.adv-node:focus { outline: none; }
.next a.adv-node:focus-visible .adv-frame::before {
  box-shadow: inset 0 0 0 3px #FFFFFF;
}

.next .adv-frame img {
  width: 32px;
  height: 32px;
  object-fit: cover;
  /* Explicit, not inherited. image-rendering is an inherited property and the field this sits in
     sets it to pixelated for its own tiled stone — without this every channel avatar would be
     resampled nearest-neighbour on the way down from 240px. */
  image-rendering: auto;
}
/* The stand-in head is a block texture, so it keeps its pixels; a channel avatar is a photograph
   and must not. */
.next .adv-frame img.pixel { image-rendering: pixelated; }
/* Fallback glyphs. Nothing in the database supplies a landmark image and a creator may have no
   avatar, so these are what many frames actually contain — they have to differ by kind, or the
   frame shapes carry the entire distinction. A landmark gets the map's own marker; a creator gets
   a head-shaped block. */
.next .adv-blank {
  width: 26px;
  height: 26px;
  background: var(--seal);
  box-shadow: inset 0 0 0 3px var(--seal-deep), 2px 2px 0 rgba(0, 0, 0, .5);
}
.next .is-mark .adv-blank {
  width: 20px;
  height: 20px;
  background: var(--seal);
  outline: 2px solid #100010;
  box-shadow: 0 0 0 3px rgba(80, 0, 255, .45), 2px 2px 0 rgba(0, 0, 0, .5);
}
/* Unobtained advancements are drawn dark in the game — the icon is there, but unlit. */
.next .adv-node:not(.is-done) .adv-frame img { filter: grayscale(1) brightness(.5); }
.next .adv-node:not(.is-done) .adv-blank {
  background: #6A6A6A;
  outline-color: #2B2B2B;
  box-shadow: inset 0 0 0 3px #3C3C3C;
}

/* The name is not drawn under the node — the game puts it in the tooltip, and a caption under
   every frame turns the tree into a labelled diagram. It is still in the DOM for assistive tech
   and for anyone reading with styles off. */
.next .adv-label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.next .adv-empty {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  color: #C8C8C8;
}

/* ── Stone button ───────────────────────────────────────────────────────────
   The game's own button: a grey slab with the same trim as every other surface here — 2px of black
   outside, two steps of bevel inside — and white text with the hard glyph shadow. It lifts a shade
   on hover, which is the only state Minecraft gives it. */
/* ── Grab surfaces ──────────────────────────────────────────────────────────
   Pressing on an image and moving hands the gesture to the browser's own drag-and-drop: a native
   drag starts, pointer events stop arriving, and no pointerup is ever fired. Any drag state the
   page was holding is then stuck for good — which is what made these pages stop responding until
   a reload, and what made a tab click miss whenever the mouse twitched during it.

   Selecting text does the same thing to a drag. Both are switched off wherever a press means
   "move this", which is everywhere that grabs. */
.next .adv-window,
.next .adv-tabs,
.next .gal-hall {
  user-select: none;
  -webkit-user-select: none;
}
.next .adv-window img,
.next .adv-tabs img,
.next .gal-hall img {
  -webkit-user-drag: none;
  pointer-events: none;   /* the frame around it is the target, never the image itself */
}

.next .mc-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 200px;
  padding: 13px 26px;
  border: 2px solid #000;
  background: #8B8B8B;
  box-shadow:
    inset 2px 2px 0 #B4B4B4,
    inset 4px 4px 0 #9E9E9E,
    inset -2px -2px 0 #4E4E4E,
    inset -4px -4px 0 #6C6C6C;
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: .06em;
  color: #F4F4F4;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .7);
  cursor: pointer;
}
.next .mc-btn:hover {
  background: #A2A2A2;
  color: #FFFFFF;
}
.next .mc-btn:focus { outline: none; }
.next .mc-btn:focus-visible { box-shadow: inset 0 0 0 3px #FFFFFF; }

/* ── Disconnected ───────────────────────────────────────────────────────────
   The 404. Minecraft already has a screen for "you asked for something that is not there" — the
   disconnect screen: the dirt field, a line saying what happened, and one button back. Using it
   keeps the rule that every page on this site is a screen from the game. */
.next .lost-stage {
  height: 100dvh;
  overflow: hidden;
  justify-content: center;
  gap: 0;
}
.next .lost-kicker {
  margin: 0 0 14px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .24em;
  color: #B9563F;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .7);
}
.next .lost-title {
  font-family: var(--font-serif);
  font-size: 34px;
  color: #F6F1E6;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .6);
}
.next .lost-desc {
  margin: 14px 0 30px;
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: 1.8;
  color: #C4B08A;
  text-align: center;
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .6);
}

/* ── Gallery ────────────────────────────────────────────────────────────────
   A room, not a grid: an oak wall, a smooth stone floor, and paintings hung along it at the sizes
   the game actually makes them. The hall scrolls sideways — newest at the left — and a pool of
   light fixed to the middle of the screen picks out whatever is passing through.

   Every position and size is in blocks against one --unit, so the room scales as a whole and a
   four-block frame can never grow past the wall onto the floor. */
/* Edge to edge: a hall with a margin either side is a picture of a hall. The heading and the
   signs keep their own gutter, since they are text rather than room. */
.next .gal-stage {
  gap: 12px;
  padding-left: 0;
  padding-right: 0;
  height: 100dvh;
  overflow: hidden;
  position: relative;
  --unit: clamp(38px, 8vh, 84px);
  /* The floor's height, shared by the floor itself and the wall's bottom padding. It has to be in
     the same unit as everything else: a percentage padding resolves against the container's
     *width*, not its height, so `padding-bottom: 22%` on a 4000px-wide room was 880px and pushed
     every frame off the top of the hall. */
  --floor: calc(var(--unit) * 1.7);
  --oak-dark: #64502C;
}

/* Filter signs: small boards nailed up beside the door. Real links, so they work with no script. */
.next .gal-signs {
  display: flex;
  padding-inline: var(--sp-4);
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-2);
  max-width: min(980px, 100%);
}
.next .gal-sign {
  display: inline-flex;
  align-items: center;
  min-height: 32px;
  padding: 0 var(--sp-3);
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  font-weight: 500;
  letter-spacing: .02em;
  transition: background .12s ease, color .12s ease;
  color: #E7D9BC;
  background: var(--oak-dark);
  border: 2px solid #2B2015;
  box-shadow: inset 2px 2px 0 rgba(255, 236, 196, .22), inset -2px -2px 0 rgba(0, 0, 0, .3);
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .55);
}
.next .gal-sign:hover { color: #FFF6E4; background: #7A6135; }
.next .gal-sign.is-on {
  color: #2B2015;
  background: #D9BE82;
  text-shadow: none;
}

/* The light belongs to the view, not to the wall, so the scroller is wrapped rather than holding
   it: anything inside .gal-hall would slide away with the paintings. */
.next .gal-view {
  position: relative;
  flex: 1 1 auto;
  min-height: 220px;
  width: 100%;
}

.next .gal-hall {
  position: absolute;
  inset: 0;
  /* Native scrolling, deliberately. A transform-based scroller would have to reimplement touch
     momentum, trackpad gestures, the keyboard and the scrollbar, and would get all four slightly
     wrong. next-gallery.js only maps a mouse wheel onto this axis. */
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.next .gal-hall::-webkit-scrollbar { display: none; }
.next .gal-hall:focus { outline: none; }
.next .gal-hall:focus-visible { box-shadow: inset 0 0 0 3px rgba(243, 201, 142, .5); }

/* A row, so the frames place themselves and the hall is exactly as long as what is hanging in it.
   The side padding is the lead-in: half a screen of empty wall at each end, which is what puts the
   newest painting under the light the moment the page opens instead of jammed against the frame's
   left edge, in the dark. The bottom padding is the floor, so the frames centre on the wall rather
   than on the room. */
.next .gal-room {
  position: relative;
  height: 100%;
  width: max-content;
  min-width: 100%;
  display: flex;
  align-items: center;
  gap: calc(var(--unit) * .55);
  padding-inline: calc(50vw - var(--unit) * 2);
  padding-bottom: var(--floor);
  /* Oak planks, at block scale and nearest-neighbour — the grain is four pixels tall and vanishes
     into mush the moment it is smoothed. */
  background: url(/oak_planks.webp) 0 0 / calc(var(--unit)) calc(var(--unit)) repeat;
  image-rendering: pixelated;
}
/* The wall is lit from above, the way a room is, and gathers shadow where it meets the floor —
   which is the ambient occlusion the game bakes into every inside corner. The dark band is measured
   up from the floor rather than as a share of the height, so it stays at the skirting whatever the
   viewport does. Also stops the oak reading as a flat swatch. */
.next .gal-room::before {
  content: '';
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to top,
      rgba(0, 0, 0, 0) 0,
      rgba(0, 0, 0, 0) var(--floor),
      rgba(0, 0, 0, .52) var(--floor),
      rgba(0, 0, 0, .22) calc(var(--floor) + var(--unit) * .7),
      rgba(0, 0, 0, 0) calc(var(--floor) + var(--unit) * 2.1)),
    linear-gradient(to bottom, rgba(0, 0, 0, .34) 0%, rgba(0, 0, 0, .06) 34%, rgba(0, 0, 0, .18) 74%);
  pointer-events: none;
}

/* ── The floor ──────────────────────────────────────────────────────────────
   An actual plane lying away from the viewer, not a strip of texture: the stone is tilted back
   about its far edge under a perspective, so the courses converge and the blocks nearest the
   viewer are the biggest. That is the whole depth cue and it needs no drawn grid.

   It lives outside the scroller because a vanishing point belongs to the screen. Inside a
   4000px-wide room it would converge on the middle of the wall, which is usually somewhere off in
   the dark, and the floor would appear to pivot as you walked. */
.next .gal-floor {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  top: calc(100% - var(--floor));
  z-index: 3;
  pointer-events: none;
  overflow: hidden;
  /* The seam where the floor meets the wall. */
  box-shadow: inset 0 3px 0 rgba(0, 0, 0, .6);
}
/* The three numbers here are one decision, not three, so they are worth stating together.
   Tilting about the top edge under a perspective d puts the horizon at d / sin(angle) — everything
   further than that is behind the viewer. With d one block, the horizon landed barely one course
   in, so the whole floor was the first row of stone stretched down the screen. d is five blocks
   now, which puts the horizon out past five courses, and the stone is laid at 0.6 of a block so
   three of them fit in the band with room to spare. Each course then lands about half again as
   deep on screen as the one behind it, which is the effect: three rows, each nearer one bigger. */
.next .gal-floor span {
  position: absolute;
  /* Wider than the slot because the perspective magnifies the near edge out past both sides. */
  left: -60%;
  right: -60%;
  top: 0;
  height: calc(var(--unit) * 5);
  display: block;
  overflow: hidden;
  transform: perspective(calc(var(--unit) * 5)) rotateX(62deg);
  transform-origin: center top;
}
/* The stone itself, as its own layer inside the tilted plane, walked along with a transform.
   It used to be a background-position on the plane, which meant the browser had to re-rasterise a
   large perspective-transformed surface on every scroll frame — cheap enough on a desktop, and on a
   phone the reason the floor flickered and dropped out entirely. A transform is composited: the
   texture is rasterised once and only moved after that.

   A tile of slack on each side, and the offset taken modulo one tile, so it can be walked for ever
   without the layer needing to be any wider than this. */
.next .gal-floor i {
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(var(--unit) * -.6);
  right: calc(var(--unit) * -.6);
  display: block;
  background: url(/smooth_stone.webp) 0 0 / calc(var(--unit) * .6) calc(var(--unit) * .6) repeat;
  image-rendering: pixelated;
  will-change: transform;
  backface-visibility: hidden;
}
/* Light falls off towards the back of the room, which is also what stops the tiled stone reading
   as a flat swatch once it is tilted. */
.next .gal-floor::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, rgba(0, 0, 0, .62), rgba(0, 0, 0, .04) 46%, rgba(0, 0, 0, .22));
}

/* ── A painting ─────────────────────────────────────────────────────────────
   Hung by its centre on an eye line, with a hash-driven nudge up or down so the row does not read
   as a spreadsheet. */
/* Every frame the same size and the same 16:9 as the thumbnail inside it. Mixed sizes were the more
   faithful answer — the game picks a painting at random when you hang one — but a thumbnail is not
   decoration: crop it square or letterbox it and you lose the face or the title text that makes it
   worth clicking. Uniform frames also give one clean hanging line, so the height jitter that was
   breaking up a ragged row went with them. */
.next .gal-art {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  width: calc(4 * var(--unit));
  color: inherit;
  text-decoration: none;
  transition: transform .18s cubic-bezier(.2, .9, .3, 1.1);
}
.next .gal-frame {
  display: block;
  position: relative;
  /* content-box, deliberately. Everything on this site is border-box, but here the 16:9 has to
     belong to the picture rather than to the picture plus its frame — measured border-box, the
     inside of the frame came out at 1.837 and cropped three percent off the top and bottom of
     every thumbnail. */
  box-sizing: content-box;
  aspect-ratio: 16 / 9;
  /* The item's own wooden edge, and the shadow a thing hanging off a wall casts. */
  border: 6px solid var(--oak-dark);
  outline: 2px solid #201509;
  box-shadow: 5px 7px 0 rgba(0, 0, 0, .45), inset 0 0 0 2px rgba(0, 0, 0, .4);
  background: #14100B;
  overflow: hidden;
}
.next .gal-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* A 320px thumbnail carried up to frame size keeps its pixels, which is the one thing that makes
     a photograph sit in a Minecraft frame without looking pasted in. */
  image-rendering: pixelated;
}
/* The empty hall's hanging spot: the frame with nothing in it. */
.next .gal-frame.is-blank {
  background:
    repeating-linear-gradient(45deg, rgba(0,0,0,.22) 0 8px, rgba(0,0,0,.32) 8px 16px),
    #2A2118;
}

/* A museum label, not floating text. On bare oak the caption was competing with the plank grain
   behind every letter; on its own dark board it reads at a glance and looks like it belongs to the
   painting above it. */
.next .gal-plate {
  display: block;
  margin-top: 10px;
  padding: var(--sp-2) var(--sp-3) 9px;
  background: rgba(14, 10, 6, .86);
  border: 2px solid #241A0F;
  box-shadow: inset 0 2px 0 rgba(255, 236, 196, .1);
  font-family: var(--font-sans);
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .8);
}
.next .gal-title {
  display: block;
  font-size: var(--fs-ui);
  font-weight: 500;
  line-height: 1.45;
  color: #F2E7D0;
  transition: color .12s ease;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
}
.next .gal-by {
  display: block;
  margin-top: var(--sp-1);
  font-size: var(--fs-sm);
  color: #C9B38A;
}
.next a.gal-art:hover .gal-title { color: var(--mc-aqua); }
/* next-gallery.js marks whichever painting is under the light. Only that one, never all of them. */
.next .gal-art.is-lit {
  transform: scale(1.045);
  z-index: 3;
}

/* The door at the end of the hall. */
.next .gal-door {
  position: relative;
  z-index: 2;
  flex: 0 0 auto;
  align-self: center;
  width: calc(var(--unit) * 2.1);
  padding: 16px 12px;
  text-align: center;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  font-weight: 500;
  line-height: 1.5;
  color: #F2E7D0;
  background: var(--oak-dark);
  border: 4px solid #2B2015;
  box-shadow: inset 3px 3px 0 rgba(255, 236, 196, .2), inset -3px -3px 0 rgba(0, 0, 0, .35);
  text-shadow: 2px 2px 0 rgba(0, 0, 0, .6);
}
.next .gal-door:hover { background: #7A6135; color: #FFF6E4; }

/* ── Step buttons ───────────────────────────────────────────────────────────
   Oak boards at either end of the view, carrying the book's stepped arrow. They sit over the dim
   (z-index 4) so they are never in the dark, and centre on the wall rather than the whole view —
   the floor's height is taken off the bottom. Pointer-only: a touch screen already swipes the hall,
   and two boards covering the edges of a 390px wall would hide more than they help. */
.next .gal-step {
  position: absolute;
  z-index: 5;
  top: calc((100% - var(--floor)) / 2);
  transform: translateY(-50%);
  width: 44px;
  height: 56px;
  padding: 0;
  cursor: pointer;
  background: var(--oak-dark);
  border: 3px solid #2B2015;
  box-shadow: inset 2px 2px 0 rgba(255, 236, 196, .22), inset -2px -2px 0 rgba(0, 0, 0, .3), 3px 4px 0 rgba(0, 0, 0, .4);
  transition: background .12s ease;
}
.next .gal-step.prev { left: var(--sp-4); }
.next .gal-step.next { right: var(--sp-4); }
.next .gal-step::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 20px;
  height: 16px;
  margin: -8px 0 0 -10px;
  background: #F2E7D0;
  clip-path: polygon(100% 0, 100% 25%, 45% 25%, 45% 0, 20% 50%, 45% 100%, 45% 75%, 100% 75%, 100% 100%);
}
.next .gal-step.next::before { transform: scaleX(-1); }
.next .gal-step:hover:not(:disabled) { background: #7A6135; }
.next .gal-step:disabled { cursor: default; opacity: .4; }
.next .gal-step:focus-visible { outline: 2px solid #F3C98E; outline-offset: 2px; }
.next .gal-step[hidden] { display: none; }
@media (hover: none), (max-width: 720px) {
  .next .gal-step { display: none; }
}

.next .gal-sign:focus-visible,
.next .gal-door:focus-visible { outline: 2px solid #F3C98E; outline-offset: 2px; }
.next a.gal-art:focus-visible { outline: none; }
.next a.gal-art:focus-visible .gal-frame { outline-color: #F3C98E; outline-width: 3px; }

/* ── The room's shadow ──────────────────────────────────────────────────────
   No lamp. Three attempts at one — a clipped wedge, a blurred cone, a lantern with a pool on the
   wall — and every version cost the same thing: a light layer over the room is white over oak, and
   white over oak is haze. It lifts the wall into a milky wash and takes the frames and the plates
   with it, which is worse than having no lamp at all.

   What is left is only the darkness: the room falls away at the edges, and shadow gathers where the
   wall meets the floor. That is enough to put the middle of the hall forward, and the oak keeps its
   colour. One fixed layer, no blending, nothing per painting. */
.next .gal-dim {
  position: absolute;
  left: 0;
  right: 0;
  pointer-events: none;
}

/* Edges, not a ring — a ring reads as a circle drawn on the wall, which is what it would be. */
.next .gal-dim {
  top: 0;
  bottom: 0;
  z-index: 4;
  background:
    linear-gradient(to right,
      rgba(5, 3, 2, .94) 0%, rgba(5, 3, 2, .38) 15%, rgba(5, 3, 2, 0) 33%,
      rgba(5, 3, 2, 0) 67%, rgba(5, 3, 2, .38) 85%, rgba(5, 3, 2, .94) 100%),
    linear-gradient(to bottom,
      rgba(5, 3, 2, .6) 0%, rgba(5, 3, 2, 0) 26%,
      rgba(5, 3, 2, 0) 72%, rgba(5, 3, 2, .34) 100%);
}

@media (prefers-reduced-motion: reduce) {
  .next .gal-art { transition: none; }
}

/* Panning has to be discoverable without waiting for the visitor to guess, and with no captions
   under the nodes the tooltip has to be advertised too. */
.next .adv-hint {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-ui);
  letter-spacing: .04em;
  color: var(--on-dark-faint);
}

/* Short windows — a 1366×768 laptop leaves about 650px under the browser's own chrome. The heading
   and the chapter HUD tighten up so the book keeps a usable height; wide ones are untouched. */
@media (max-height: 820px) {
  .next .chapter-stage { padding-top: calc(var(--header-h) + var(--sp-3)); gap: var(--sp-4); }
  .next .chapter-head { gap: var(--sp-1); }
  .next .chapter-title { font-size: 30px; }
  .next .season-hud { gap: 6px; }
  .next .xp { padding-top: 14px; }
  .next .book-body { padding-top: var(--sp-4); }
  .next .bk-title { font-size: 28px; }
  .next .bk-mark .bk-desc { -webkit-line-clamp: 4; line-clamp: 4; }
}

/* Very short windows: a laptop with its bookmarks bar showing, or a small phone. What goes is only
   what is said twice — the HUD's chapter name is the title page's heading, the ornament and count
   line repeat the header, and the day scale repeats the XP number. */
@media (max-height: 700px) {
  .next .book-stage .hud-name,
  .next .book-stage .xp-scale,
  .next .book-stage .chapter-head::after,
  .next .book-stage .chapter-sub { display: none; }
  .next .chapter-stage { gap: var(--sp-3); }
  .next .bk-title { font-size: 24px; }
  .next .bk-mark-name { font-size: 22px; }
  .next .bk-mark .bk-desc { -webkit-line-clamp: 3; line-clamp: 3; }
  .next .bk-vid img { width: 96px; }
}

@media (max-width: 720px) {
  /* The strip has to hold one tab per creator on a 350px field, so the tabs shrink rather than
     scroll: a scroll container would clip the active tab's overhang into the panel, which is the
     join the whole thing depends on. */
  /* Shrink to share the row, capped at 48px: with nine creators a fixed 48px ran the strip past
     the panel's right edge. */
  .next .adv-tabs { gap: 2px; }
  .next .adv-tab { flex: 1 1 0; min-width: 0; width: auto; max-width: 48px; height: 38px; }
  .next .adv-tab.is-active { height: 43px; }
  .next .adv-tab img { width: 26px; height: 26px; }

  /* Four nav items either side of the wordmark do not fit on a phone, so the grid was wrapping and
     the header silently grew to 135px — while every page still cleared only --header-h, putting
     each page's own title underneath it. Stacked into two rows instead, with --header-h updated so
     the pages keep clearing the real thing. */
  :root { --header-h: 74px; }
  .next .site-header {
    grid-template-columns: auto auto;
    grid-template-rows: auto auto;
    justify-content: center;
    align-content: center;
    gap: 2px 0;
    padding: 0 12px;
  }
  .next .header-brand {
    grid-row: 1;
    grid-column: 1 / -1;
    border: 0;
    padding: 0;
  }
  .next .nav-left  { grid-row: 2; grid-column: 1; }
  .next .nav-right { grid-row: 2; grid-column: 2; }
  .next .nav-link { font-size: 14px; padding: 4px 9px; }
  .next .nav-link.active::after { left: 8px; right: 8px; bottom: 0; }
  .next .brand-name { font-size: 19px; }

  .next .chapter-title { font-size: 30px; }
  .next .adv-window { min-height: 200px; }
  .next .chapter-stage { padding-left: 12px; padding-right: 12px; }
  .next .hud-name { font-size: 15px; }
  .next .xp-level { font-size: 13px; }
  .next .book-page { min-height: 380px; }
  .next .book-body, .next .book-foot { padding-left: 16px; padding-right: 16px; }
  /* No fold on a phone. A two-page spread needs the width for two pages; at ~350px it would run a
     dark line through the middle of the text and halve every measure to about 150px. One narrow
     page is the honest reading of the same book. */
  .next .book-page::after { display: none; }
  /* One leaf at a time. book.js watches the same breakpoint and puts one slot in a spread below it,
     so there is never a second column to lay out here. */
  .next .book-body { grid-template-columns: 1fr; }
  .next .bk-vids { max-width: 320px; }
  .next .bk-title { font-size: 27px; }
  .next .bk-mark-name { font-size: 22px; }
}
