/* ============================================================
   christiansafka.com
   Industrial retro × Scandinavian calm. No frameworks, no build.

   Sections:  1 fonts · 2 tokens · 3 reset · 4 shell · 5 rail
              6 prose · 7 index list · 8 globe · 9 footer · 10 responsive
   ============================================================ */


/* --- 1. fonts ------------------------------------------------
   Self-hosted, no third-party requests. Newsreader is subset to
   latin with the optical-size axis pinned (halves the file); the
   weight axis stays variable. latin-ext only downloads if a page
   actually uses a glyph from that range.
   ------------------------------------------------------------ */

@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/ibm-plex-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/ibm-plex-mono-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/ibm-plex-mono-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/ibm-plex-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('../fonts/ibm-plex-mono-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('../fonts/ibm-plex-mono-600-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 300 600;
  font-display: swap;
  src: url('../fonts/newsreader-var-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Newsreader';
  font-style: italic;
  font-weight: 300 600;
  font-display: swap;
  src: url('../fonts/newsreader-var-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}


/* --- 2. tokens ---------------------------------------------- */

:root {
  --serif: 'Newsreader', Georgia, 'Times New Roman', serif;
  --mono: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Warm bone paper, graphite ink, moss and clay. Nothing pure black or white. */
  --paper: #efece4;
  --paper-2: #f6f4ee;
  --ink: #1e1d1a;
  --ink-2: #55524a;
  --ink-3: #8c8879;
  /* Rules read as pencil rather than hairline: a shade deeper than a hairline. */
  --rule: #cdc6b5;
  --rule-2: #e2ddd0;
  --moss: #445e48;
  --clay: #a64b2a;

  --grain: url('../img/bg/bayerdark.png');
  --weave: url('../img/bg/28.png');
  --hatch: url('../img/bg/xbw.png');
  --grid: url('../img/bg/check.png');
  --grain-opacity: 0.07;

  /* Everything else the dark page changes, as values rather than as rules.
     These used to be four @media islands scattered down the file, one of which
     had rotted into an empty selector nobody could see was doing nothing. As
     values they collect in one block with the palette — and that is also what
     makes a reader's toggle possible at all, since a click cannot re-run a media
     query but it can re-declare a custom property. */
  --texture-filter: none;
  --moto-filter: none;
  --moto-opacity: 0.2;
  --land-opacity: 0.55;
  --frame-shadow: 0 12px 30px rgba(24, 20, 14, 0.16);
  --gear-filter: drop-shadow(0 10px 18px rgba(24, 20, 14, 0.22));
  --snap-shadow: 0 5px 14px rgba(24, 20, 14, 0.2);
  /* Which face of the toggle to show — named for what it offers, not for what
     it draws. The button needs no script to say what it will do next, so it says
     the right thing on a page where JS never arrives. */
  --face-dark: inline-block;
  --face-light: none;
  /* Styles nothing. It is how theme.js asks the stylesheet which theme actually
     resolved, rather than keeping its own copy of the rules above. */
  --theme: light;

  --rail: 11.5rem;
  --gap: 3rem;
  --measure: 34rem;

  --step: 1.5rem; /* vertical rhythm */
}

/* The dark page, switched on twice from one list of values.

   Twice because the two ways in cannot be expressed as one selector: the system
   preference is a media query, which a click cannot re-run, and the reader's
   choice is an attribute, which the system cannot set. `:not([data-theme='light'])`
   is the half that matters — without it a reader on a dark machine could never
   turn the lights on. There is no CSS build step here to fold the duplicate
   away, and inventing one to save sixteen lines would cost more than it saves. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --paper: #191813;
    --paper-2: #21201a;
    --ink: #e9e5da;
    --ink-2: #a7a294;
    --ink-3: #75705f;
    --rule: #34322a;
    --rule-2: #292820;
    --moss: #9bb694;
    --clay: #d98a68;
    --grain-opacity: 0.05;
    --texture-filter: invert(1);
    --moto-filter: invert(1);
    --moto-opacity: 0.16;
    --land-opacity: 0.7;
    --frame-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
    --gear-filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.5)) brightness(0.92);
    --snap-shadow: 0 5px 16px rgba(0, 0, 0, 0.5);
    --face-dark: none;
    --face-light: inline-block;
    --theme: dark;
  }
}

:root[data-theme='dark'] {
  --paper: #191813;
  --paper-2: #21201a;
  --ink: #e9e5da;
  --ink-2: #a7a294;
  --ink-3: #75705f;
  --rule: #34322a;
  --rule-2: #292820;
  --moss: #9bb694;
  --clay: #d98a68;
  --grain-opacity: 0.05;
  --texture-filter: invert(1);
  --moto-filter: invert(1);
  --moto-opacity: 0.16;
  --land-opacity: 0.7;
  --frame-shadow: 0 12px 30px rgba(0, 0, 0, 0.45);
  --gear-filter: drop-shadow(0 10px 20px rgba(0, 0, 0, 0.5)) brightness(0.92);
  --snap-shadow: 0 5px 16px rgba(0, 0, 0, 0.5);
  --face-dark: none;
  --face-light: inline-block;
  --theme: dark;
}


/* --- 3. reset ----------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 1.1875rem;
  line-height: 1.62;
  font-weight: 380;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Paper grain. Fixed so it reads as the sheet, not the content. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  background-image: var(--grain);
  opacity: var(--grain-opacity);
  /* The texture is 1-bit dark-on-transparent, so on dark paper it has to be
     flipped to read at all. It lives on its own ::before layer purely so this
     can invert the pattern without touching content. Alpha survives. */
  filter: var(--texture-filter);
  pointer-events: none;
  z-index: 0;
}

/* A classic parked in the corner, bleeding off the edge of the page. Drawn
   separately from the room so it gets its own ink budget — cut out of the
   room-wide rendering it came out starved of lines and read as scribble. */
.moto-corner {
  position: fixed;
  right: -2rem;
  bottom: -2rem;
  width: clamp(150px, 20vw, 300px);
  height: auto;
  z-index: 0;
  pointer-events: none;
  opacity: var(--moto-opacity);
  filter: var(--moto-filter);
}

@media print { .moto-corner { display: none; } }

img { max-width: 100%; height: auto; }

a {
  color: var(--moss);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
  text-decoration-color: color-mix(in srgb, var(--moss) 38%, transparent);
  transition: color 0.12s ease, text-decoration-color 0.12s ease;
}
a:hover { color: var(--clay); text-decoration-color: currentColor; }

:focus-visible {
  outline: 2px solid var(--clay);
  outline-offset: 3px;
  border-radius: 1px;
}

::selection { background: color-mix(in srgb, var(--moss) 22%, transparent); }

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


/* --- 4. shell ----------------------------------------------- */

/* Ruler strip: a hairline with tick marks, tall every fifth tick.
   Pure gradients — the one bit of instrument-panel signage up top. */
.ruler {
  position: relative;
  z-index: 1;
  height: 11px;
  border-top: 1px solid var(--rule);
  background-image:
    repeating-linear-gradient(to right, var(--rule) 0 1px, transparent 1px 60px),
    repeating-linear-gradient(to right, var(--rule-2) 0 1px, transparent 1px 12px);
  background-size: 100% 9px, 100% 4px;
  background-position: top left, top left;
  background-repeat: repeat-x, repeat-x;
}

/* The rail used to sit inside a centred block, which pushed it into the middle
   third of a wide screen and read as a fat sidebar. Pinning it near the edge as
   a ruled margin lets the text column centre in the viewport, which is both
   better use of the space and closer to what a page actually looks like. */
.shell {
  position: relative;
  z-index: 1;
  padding: 0 1.5rem 6rem;
}

.content {
  min-width: 0;
  max-width: var(--measure);
  margin: 0 auto;
  padding-top: 4.5rem;
}


/* --- 5. rail ------------------------------------------------ */

.rail {
  position: fixed;
  top: 0;
  left: clamp(1.25rem, 4.5vw, 4.5rem);
  width: 8.75rem;
  height: 100vh;
  padding: 4.5rem 1.4rem 2rem 0;
  border-right: 1px solid var(--rule-2);
  text-align: right;
  font-family: var(--mono);
  display: flex;
  flex-direction: column;
  z-index: 2;
}

.rail__name {
  display: block;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.17em;
  text-transform: uppercase;
  line-height: 1.5;
  color: var(--ink);
  text-decoration: none;
}
.rail__name:hover { color: var(--clay); }
.rail__name span { display: block; }

.rail__nav {
  margin-top: 1.75rem;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.3rem;
}

.rail__nav a {
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
  color: var(--ink-3);
  text-decoration: none;
  position: relative;
  padding: 0.05rem 0;
}
.rail__nav a:hover { color: var(--clay); }

/* The switch, dressed as one more item in the nav — same mono, same size, same
   right edge — with a drawn glyph in front of it so it does not read as a fourth
   page. Both faces are in the markup and the stylesheet shows one, so this is
   correct on arrival and stays correct if the script never loads; all the script
   does is remember the choice. */
.rail__theme {
  margin-top: 1.1rem;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 0.4rem;
  padding: 0.05rem 0;
  border: 0;
  background: none;
  font-family: inherit;
  font-size: 0.8125rem;
  letter-spacing: 0.02em;
  color: var(--ink-3);
  cursor: pointer;
}
.rail__theme:hover { color: var(--clay); }

.rail__theme svg {
  width: 12px;
  height: 12px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.3;
  stroke-linecap: round;
}
/* Moon offers the dark page, sun offers the light one. */
.rail__theme-moon, .rail__theme-dark { display: var(--face-dark); }
.rail__theme-sun, .rail__theme-light { display: var(--face-light); }

@media print { .rail__theme { display: none; } }

/* Active page gets a small register mark in the margin. */
.rail__nav a[aria-current='page'] { color: var(--ink); }
.rail__nav a[aria-current='page']::after {
  content: '';
  position: absolute;
  right: -0.85rem;
  top: 50%;
  width: 4px;
  height: 4px;
  margin-top: -2px;
  background: var(--clay);
}



/* --- 6. prose ----------------------------------------------- */

.lede {
  font-size: 1.3125rem;
  line-height: 1.5;
  margin: 0 0 var(--step);
  text-wrap: pretty;
}

.prose p,
.prose ul,
.prose ol,
.prose blockquote,
.prose pre,
.prose figure { margin: 0 0 var(--step); }

/* Block, or the picture sits on a text baseline and carries a few pixels of
   descender space under it that no caption margin can reach — the gap you
   cannot find in the CSS because it is a line box, not a margin. Auto margins
   so a picture narrower than the column centres over its caption. */
.prose figure img { display: block; margin-inline: auto; }

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

.prose h1 {
  font-size: 2.125rem;
  font-weight: 420;
  line-height: 1.18;
  letter-spacing: -0.012em;
  margin: 0 0 0.6rem;
  text-wrap: balance;
}

.prose h2 {
  font-size: 1.375rem;
  font-weight: 500;
  line-height: 1.3;
  margin: calc(var(--step) * 2) 0 0.5rem;
  text-wrap: balance;
}

.prose h3 {
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
  margin: calc(var(--step) * 1.6) 0 0.5rem;
}

.prose blockquote {
  padding-left: 1.25rem;
  border-left: 2px solid var(--rule);
  font-style: italic;
  color: var(--ink-2);
}

.prose ul, .prose ol { padding-left: 1.3rem; }
.prose li { margin-bottom: 0.35rem; }
.prose li::marker { color: var(--ink-3); }

/* A section break, and meant to be seen as one: wider than the column it
   separates, in stages, because the page has a rail down the left. Past the
   right edge first, since that side is only margin; the left follows once the
   viewport is wide enough that the bleed still clears the nav. */
.prose hr {
  border: 0;
  height: 1px;
  background: var(--rule);
  margin: calc(var(--step) * 2) 0;
}
@media (min-width: 62rem) {
  .prose hr { margin-right: -6rem; }
}
@media (min-width: 78rem) {
  .prose hr { margin-left: -6rem; }
}

/* Centred under the picture and tucked up close to it, so it reads as attached
   to the thing above rather than as the start of what comes next. Held to
   three-quarters of the width for the same reason: a caption that runs the full
   measure looks like a paragraph. Mid ink, not the lightest — it is meant to be
   read, not just noticed. */
/* Serif, because a caption is a sentence.

   These were mono, which is the type this site labels things in — section
   breaks, dates, sources, the era column in a timeline. Setting a sentence in
   the label face makes the caption read as a specification of the picture rather
   than a remark about it, and it left the music page, which is nearly all
   captions, looking like it had been set by someone else. So the rule now runs
   the other way: mono says *what kind of thing this is*, serif says everything
   anybody is meant to read. Small and mid-ink is enough to keep a caption from
   being mistaken for the next paragraph. */
.prose figcaption,
.bay__note,
.bay__gear figcaption {
  font-family: var(--serif);
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--ink-2);
  letter-spacing: 0;
  text-transform: none;
  text-wrap: pretty;
}

.prose figcaption {
  max-width: 75%;
  margin: 0.45rem auto 0;
  text-align: center;
}

code, kbd, samp {
  font-family: var(--mono);
  font-size: 0.85em;
}
:not(pre) > code {
  background: color-mix(in srgb, var(--ink) 6%, transparent);
  padding: 0.1em 0.35em;
  border-radius: 2px;
}
pre {
  font-family: var(--mono);
  font-size: 0.8125rem;
  line-height: 1.6;
  background: var(--paper-2);
  border: 1px solid var(--rule);
  padding: 1rem 1.1rem;
  overflow-x: auto;
}
pre code { background: none; padding: 0; }

/* Article dateline */
.dateline {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 1.75rem;
}

.back {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
  text-decoration: none;
  color: var(--ink-3);
  margin-bottom: 2.5rem;
}
.back:hover { color: var(--clay); }


/* --- 7. index list ------------------------------------------
   A technical-manual table of contents: mono dates, serif titles,
   a hairline that fills whatever space is left in the header row.
   ------------------------------------------------------------ */

/* Doubled class so this wins over `.prose h2` regardless of source order. */
.section-label,
.prose .section-label {
  display: flex;
  align-items: center;
  gap: 0.9rem;
  font-family: var(--mono);
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: calc(var(--step) * 1.8) 0 0.6rem;
}
.section-label::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--rule);
}

.index { list-style: none; margin: 0; padding: 0; }

.index li { border-bottom: 1px solid var(--rule-2); }
.index li:first-child { border-top: 1px solid var(--rule-2); }

.index a {
  display: grid;
  grid-template-columns: 5.25rem 1fr;
  gap: 0 1rem;
  align-items: baseline;
  padding: 0.7rem 0.5rem 0.7rem 0;
  text-decoration: none;
  color: var(--ink);
  transition: background-color 0.12s ease, padding-left 0.12s ease;
}
.index a:hover {
  background: color-mix(in srgb, var(--ink) 4%, transparent);
  padding-left: 0.5rem;
  color: var(--ink);
}

.index .date {
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}
.index a:hover .date { color: var(--clay); }

.index .title {
  font-size: 1.0625rem;
  line-height: 1.35;
  text-wrap: pretty;
}
.index a:hover .title { text-decoration: underline; text-underline-offset: 0.18em; }

.index .note {
  grid-column: 2;
  font-size: 0.9375rem;
  line-height: 1.45;
  color: var(--ink-2);
  margin-top: 0.15rem;
}


/* --- 8. the atlas -------------------------------------------
   Was a three.js globe, which cost 672 KB of vendor code to show half the
   places at a time with the rest around the back of the sphere. This is a flat
   map, drawn once at build time by scripts/places.mjs and shipped as markup:
   every place visible at a glance, and no JavaScript at all.

   Land is a dot matrix, kept from the globe, because that reading — a chart
   rather than a photograph — is the one the rest of the site is written in.
   ------------------------------------------------------------ */

.instrument {
  border: 1px solid var(--rule);
  background: var(--paper-2);
  margin: calc(var(--step) * 1.5) 0;
}

/* The map is 5:1. Inside a 34rem column that leaves it a hundred pixels tall
   and unreadable, so where there is room it runs past the right edge — the same
   move the music page's carousel makes, and for the same reason. */
.atlas {
  display: block;
  width: 100%;
  height: auto;
  margin: calc(var(--step) * 1.4) 0 var(--step);
  overflow: visible;
}
@media (min-width: 62rem) {
  .atlas { width: calc(100% + 9rem); }
}

.atlas__land {
  fill: none;
  stroke: var(--ink-3);
  stroke-width: 2.6;
  stroke-linecap: round;
  opacity: var(--land-opacity);
}

.atlas__place circle { fill: var(--clay); }

.atlas__place text {
  font-family: var(--mono);
  font-size: 11px;
  fill: var(--ink);
  /* A paper-coloured outline under the glyphs, so a name reads whether it lands
     on ocean, on the land matrix, or on another name brought up by a hover. */
  paint-order: stroke fill;
  stroke: var(--paper);
  stroke-width: 3px;
  stroke-linejoin: round;
}

/* The ones with no room. The dot is drawn like every other; only the name waits
   to be asked for. Every one of them is in the list underneath regardless. */
.atlas__place.is-quiet text {
  opacity: 0;
  transition: opacity 0.12s ease;
}
.atlas__place.is-quiet:hover text,
.atlas__place.is-quiet:focus-visible text { opacity: 1; }
.atlas__place:hover circle { r: 5.5; }

/* Every name, always, grouped by country — the half of this that a map cannot
   do. Most-visited countries first. */
.atlas__index { list-style: none; margin: 0 0 var(--step); padding: 0; }

.atlas__index li {
  display: grid;
  grid-template-columns: 9rem 1fr;
  gap: 0 1.25rem;
  padding: 0.55rem 0;
  border-top: 1px solid var(--rule-2);
}
.atlas__index li:last-child { border-bottom: 1px solid var(--rule-2); }

.atlas__index b {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
  padding-top: 0.28rem;
}
.atlas__index span {
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--ink-2);
  text-wrap: pretty;
}

.atlas__note {
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 calc(var(--step) * 1.5);
}

@media (max-width: 40rem) {
  .atlas__index li { grid-template-columns: 1fr; gap: 0.2rem; }
  .atlas__index b { padding-top: 0; }
}


/* --- 9. footer ---------------------------------------------- */

.foot {
  margin-top: calc(var(--step) * 3);
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: 0.6875rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.25rem;
}
.foot a { color: var(--ink-3); text-decoration: none; }
.foot a:hover { color: var(--clay); }
.foot .spacer { flex: 1; }


/* --- 9a. instagram clips ------------------------------------
   A facade: the page ships a poster frame you exported yourself, framed in
   the same .instrument chrome as the globe. Instagram's embed script is
   ~150KB of third-party code that sets cookies, so nothing of theirs loads
   until someone actually clicks a clip — and then only that one.
   ------------------------------------------------------------ */

.clips {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.25rem;
  margin: calc(var(--step) * 1.2) 0 0;
  padding: 0;
  list-style: none;
}

.clip { margin: 0; }

.clip__frame {
  position: relative;
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  background: var(--paper-2);
  text-decoration: none;
}
.clip__frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 400ms ease, filter 300ms ease;
}
.clip__frame:hover img { transform: scale(1.03); filter: brightness(1.04); }

/* No poster exported yet — show the paper grid rather than a broken frame. */
.clip__frame--empty {
  background-image: var(--grid, none);
  display: grid;
  place-items: center;
}
.clip__frame--empty::after {
  content: 'Poster not set';
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.clip__play {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 3.25rem;
  height: 3.25rem;
  margin: -1.625rem 0 0 -1.625rem;
  border-radius: 50%;
  background: rgba(16, 15, 12, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.55);
  backdrop-filter: blur(3px);
  display: grid;
  place-items: center;
  transition: background-color 180ms ease, transform 180ms ease;
}
.clip__play::after {
  content: '';
  border-left: 12px solid #fff;
  border-top: 8px solid transparent;
  border-bottom: 8px solid transparent;
  margin-left: 4px;
}
.clip__frame:hover .clip__play { background: var(--clay); transform: scale(1.06); }

.clip iframe {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  border: 0;
  background: var(--paper-2);
}

.clip__caption {
  border-top: 1px solid var(--rule);
  padding: 0.6rem 0.7rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  line-height: 1.6;
}
.clip__caption b { display: block; font-weight: 500; color: var(--ink-2); }

@media (max-width: 40rem) {
  .clips { grid-template-columns: 1fr; }
}


/* Cross-document view transitions: the rail is identical on every reading page,
   so naming it lets the browser carry it across the navigation instead of
   tearing it down and rebuilding it. Browsers without support just navigate,
   and transition.js falls back to its own fade for them. */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: no-preference) {
  .rail { view-transition-name: rail; }
  .ruler { view-transition-name: ruler; }
}

::view-transition-group(rail),
::view-transition-group(ruler) { animation-duration: 260ms; }

::view-transition-old(root) { animation: vt-out 220ms ease both; }
::view-transition-new(root) { animation: vt-in 320ms ease both; }

@keyframes vt-out { to { opacity: 0; } }
@keyframes vt-in { from { opacity: 0; } }


/* --- 9a1b. clip lightbox -----------------------------------
   Their embed is fixed-width and can't be styled from out here, so the only
   lever on the "View more on Instagram" bar is to make the frame around it
   bigger. JS sizes it to the viewport on open and on resize.
   ------------------------------------------------------------ */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: grid;
  place-items: center;
  padding: 1rem;
  background: rgba(10, 9, 7, 0.86);
  backdrop-filter: blur(6px);
}
.lightbox[hidden] { display: none; }

.lightbox__frame {
  background: #14130f;
  border: 1px solid rgba(255, 255, 255, 0.14);
  overflow: hidden;
}
.lightbox__frame iframe {
  display: block;
  width: 100%;
  border: 0;   /* height is set in JS; the overflow above clips the dead space */
}

.lightbox__close {
  position: absolute;
  top: 1rem;
  right: 1.25rem;
  width: 2.25rem;
  height: 2.25rem;
  font-size: 1.5rem;
  line-height: 1;
  font-family: var(--mono);
  color: #efece4;
  background: rgba(20, 19, 15, 0.7);
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 50%;
  cursor: pointer;
}
.lightbox__close:hover { background: var(--clay); border-color: var(--clay); }

html.has-lightbox { overflow: hidden; }


/* --- 9a2. a self-hosted clip -------------------------------
   Native controls, so it can be scrubbed and made fullscreen — which is
   exactly what an Instagram iframe will not let you do.
   ------------------------------------------------------------ */

.videoframe {
  margin: 0;
  border: 1px solid var(--rule);
  background: var(--paper-2);
}
.videoframe video {
  display: block;
  width: 100%;
  /* Without height:auto the HTML height attribute applies as literal pixels,
     and preload="none" means there is no intrinsic ratio to override it. */
  height: auto;
  aspect-ratio: 4 / 5;
  background: #14130f;
}

/* --- the instrument bay ------------------------------------
   The music page is allowed to be looser than the reading pages: a clip sits at
   a modest size with the instrument leaning in beside it, slightly off-square,
   rather than everything squaring up to the same grid.

   Three of those pairs, side by side in a scroller. The arrow is the visible
   control, but the mechanism underneath is a plain scroll-snap track — which
   means a trackpad swipe, a touch drag and a keyboard all work without any of
   them being implemented, and the whole thing still reads left to right with
   scripting off.

   The clips are not the same shape: one is a phone held upright, two are a
   camera on a table. Rather than crop a room full of people into a portrait
   frame, every slide carries its own `--ar` and the bay sizes from a shared
   height, so the frames vary in width but sit on one baseline.
   ------------------------------------------------------------ */

.bay {
  --bay-h: clamp(190px, 44vw, 300px);
  --bay-gap: clamp(0.5rem, 3vw, 2rem);
  position: relative;
  /* Symmetric. The bottom used to be 2.4 steps to leave room for the arrow and
     the counter, which drop below the track on a narrow screen — but they bring
     their own margin down there, so the extra was only ever a gap. */
  margin: calc(var(--step) * 1.6) 0;
}

/* Two of the three clips are landscape, and 34rem of measure leaves them
   postage-stamp sized. Where there is room, the bay runs past the right edge of
   the column — right only, so the heading and captions stay on the same left
   margin as the prose and the bleed reads as deliberate rather than as a
   block that failed to line up. */
@media (min-width: 64rem) {
  .bay { margin-right: -6rem; }
}

/* Doubled class so the list reset beats `.prose ul`, whose indent would make
   every slide narrower than the scrollport and leave a sliver of the next one
   showing at rest. */
.bay__track,
.prose .bay__track {
  display: flex;
  /* Slides are the full width of the scrollport, so this gap is never seen —
     it is there to keep each slide's rotated frame and its drop shadow from
     poking into the edge of the one before it. */
  gap: 3rem;
  margin: 0;
  padding: 0.75rem 0 0.25rem;
  list-style: none;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}
.bay__track::-webkit-scrollbar { display: none; }

/* The caption travels with its own slide rather than being swapped into a fixed
   line underneath — it is a caption for that clip, it should move with it, and
   it goes on saying the right thing with scripting off. Only the heading, which
   spans the column and carries the rule, is left for JS to keep in step. */
.bay__slide {
  flex: 0 0 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
  display: flex;
  flex-direction: column;
  /* Room on the right for the arrow to sit over rather than on the artwork. */
  padding-right: 2.75rem;
}

.bay__pair {
  display: flex;
  align-items: flex-end;
  gap: var(--bay-gap);
  min-height: var(--bay-h);
}

/* Width from the shared height and the slide's own ratio, capped so a landscape
   frame cannot crowd the instrument out of its own slide. */
.bay__slide .videoframe {
  flex: 0 0 auto;
  width: min(calc(var(--bay-h) * var(--ar)), 56%);
  transform: rotate(-1.2deg);
  box-shadow: var(--frame-shadow);
}
.bay__slide .videoframe video { aspect-ratio: var(--ar); }

.bay__gear {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.55rem;
}
.bay__gear img {
  max-width: 100%;
  max-height: var(--bay-h);
  width: auto;
  height: auto;
  transform: rotate(2.4deg);
  filter: var(--gear-filter);
}
.bay__gear figcaption {
  text-align: center;
  text-wrap: balance;
}

/* Type comes from the shared caption rule up in §6; this is only its place on
   the slide. Sentence case matters here more than anywhere: the note is usually
   a piece of music, and a title in capitals stops looking like a title. */
.bay__note {
  margin: 0.9rem 0 0;
}
/* The arrow rides at the middle of the frames rather than the middle of the
   slide, so it stays on the eye line whichever shape is showing. */
.bay__next {
  position: absolute;
  right: -0.5rem;
  top: calc(0.75rem + var(--bay-h) / 2);
  z-index: 2;
  width: 2.5rem;
  height: 2.5rem;
  margin-top: -1.25rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: var(--paper-2);
  color: var(--ink-2);
  cursor: pointer;
  transition: background-color 180ms ease, color 180ms ease,
              border-color 180ms ease, transform 180ms ease;
}
.bay__next svg { width: 0.9rem; height: 0.9rem; }
.bay__next:hover {
  background: var(--clay);
  border-color: var(--clay);
  color: var(--paper-2);
  transform: translateX(2px);
}
.bay__next:focus-visible { outline: 2px solid var(--clay); outline-offset: 3px; }

.bay__count {
  position: absolute;
  right: -0.5rem;
  top: calc(0.75rem + var(--bay-h) / 2 + 1.9rem);
  width: 2.5rem;
  text-align: center;
  font-family: var(--mono);
  font-size: 0.5625rem;
  letter-spacing: 0.1em;
  color: var(--ink-3);
}

@media (max-width: 40rem) {
  .bay { --bay-h: clamp(170px, 52vw, 240px); }
  .bay__pair {
    flex-direction: column;
    align-items: center;
    gap: 0.75rem;
  }
  .bay__slide .videoframe { width: min(calc(var(--bay-h) * var(--ar)), 82%); }
  .bay__gear img {
    max-width: 76%;
    max-height: calc(var(--bay-h) * 0.8);
    transform: rotate(2deg);
  }

  /* The strip has to look like a strip. A slide that fills the scrollport edge
     to edge is indistinguishable from a picture, so it gives up 15% and the next
     one shows along the right — the one affordance that says "there is more this
     way" without a word or an arrow, and the thing every phone gallery does.
     `scroll-snap-align: center` would centre that narrower slide and split the
     peek down both sides, so on this layout it snaps to the start instead. */
  .bay__track, .prose .bay__track { gap: 0.75rem; }
  .bay__slide {
    flex-basis: 85%;
    scroll-snap-align: start;
    padding-right: 0;
  }

  /* The controls used to sit under the stacked pair, which on a portrait clip put
     them most of a thumb's reach below the artwork with empty room in between.
     Both are inline-level once they stop being absolute, so they fall into a
     single line box under the strip on their own — no flex container, and the
     section label's hairline is left alone. The arrow is the second way to move
     the strip now, not the first. */
  .bay__next, .bay__count {
    position: static;
    display: inline-flex;
    vertical-align: middle;
  }
  .bay__next {
    width: 2rem;
    height: 2rem;
    margin: 0.5rem 0 0;
  }
  .bay__count {
    width: auto;
    margin: 0.5rem 0 0 0.6rem;
    text-align: left;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bay__next:hover { transform: none; }
}
.videoframe figcaption {
  border-top: 1px solid var(--rule);
  padding: 0.6rem 0.75rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}


/* --- 9a3. timeline ------------------------------------------ */

.timeline { list-style: none; margin: 0; padding: 0; }

.timeline li {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: 0 1.5rem;
  padding: 1rem 0;
  border-top: 1px solid var(--rule-2);
}
.timeline li:last-child { border-bottom: 1px solid var(--rule-2); }

.timeline b {
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding-top: 0.35rem;
}
.timeline p { margin: 0; font-size: 1.0625rem; line-height: 1.5; text-wrap: pretty; }
.timeline p + p { margin-top: 0.5rem; }

@media (max-width: 40rem) {
  .timeline li { grid-template-columns: 1fr; gap: 0.3rem; }
  .timeline b { padding-top: 0; }
}


/* --- 9a3b. the collage ---------------------------------------
   Eight frames of playing live, laid on the page rather than arranged on it.

   Multi-column rather than a grid of spans: the photographs are five different
   shapes, two of them portrait, and a grid would want every one of them assigned
   a row and column span by hand — a table of numbers to re-derive every time a
   photograph is added or swapped. Columns just flow, the browser balances the
   heights, and adding a ninth frame is one more <figure> and no arithmetic.

   The tilt is what makes it a collage instead of a contact sheet. Small angles,
   all different, no rotation repeated in a column — and the gap is wide enough to
   swallow the two or three pixels a corner travels, so nothing overlaps and the
   layout is unaffected either way, transforms not being part of it.
   ------------------------------------------------------------ */

.collage {
  columns: 3;
  column-gap: 0.9rem;
  margin: calc(var(--step) * 0.6) 0 var(--step);
}

/* Where there is room, out past the measure — same move the map and the carousel
   make. Eight photographs inside a 34rem column are eight thumbnails. */
@media (min-width: 62rem) {
  .collage { width: calc(100% + 9rem); }
}

/* Doubled throughout: `.prose figure`, `.prose figure img` and `.prose p` all
   carry two classes' worth of specificity and would otherwise put the reading
   page's margins back between these. */
.collage figure,
.prose .collage figure {
  margin: 0 0 0.9rem;
  break-inside: avoid;
  filter: drop-shadow(var(--snap-shadow));
}

.collage img,
.collage video,
.prose .collage figure img {
  display: block;
  width: 100%;
  height: auto;
  margin: 0;
  border-radius: 2px;
}

/* Eight angles, written out rather than generated, because the point is that no
   two neighbours agree and a formula would make them. */
.collage figure:nth-child(1) { transform: rotate(-1.1deg); }
.collage figure:nth-child(2) { transform: rotate(0.9deg); }
.collage figure:nth-child(3) { transform: rotate(1.3deg); }
.collage figure:nth-child(4) { transform: rotate(-0.7deg); }
.collage figure:nth-child(5) { transform: rotate(-1.4deg); }
.collage figure:nth-child(6) { transform: rotate(1deg); }
.collage figure:nth-child(7) { transform: rotate(-0.9deg); }
.collage figure:nth-child(8) { transform: rotate(0.6deg); }

@media (max-width: 46rem) {
  .collage { columns: 2; }
}


/* --- 9a4. the vine -------------------------------------------
   "I like to play" written once, and then a line out of the end of it that
   carries the phrase into four endings. A colon and four bullets say the same
   thing; this says it the way the sentence does, since every branch reads back
   through the stem and each one finishes it.

   One stretched SVG rather than a border and four pseudo-elements, because the
   junctions are curves and the shoots leave the spine at an angle. Stretched,
   not scaled: `preserveAspectRatio="none"` lets the box take whatever height
   the list ends up being, and non-scaling-stroke keeps the line a hairline
   while that happens, so there is no second set of numbers to keep in step
   with the type.

   Rows are `1fr`, which in an auto-height grid makes all four the same height
   whatever is in them — and that is what lands the shoots on the right lines.
   Their ends sit at the eighths of the box; the eighths are the row middles by
   construction, at any font size, in any language, however the items wrap.
   ------------------------------------------------------------ */

.vine {
  display: grid;
  /* The first column is the phrase's own width, so the line always leaves the
     end of the word rather than a guessed indent. */
  grid-template-columns: auto 3rem 1fr;
  margin: 0 0 var(--step);
}

/* Doubled class again: `.prose p` carries a var(--step) bottom margin and two
   classes' worth of specificity, which on the narrow layout opens a 1.5rem hole
   between the phrase and the list it runs into. The padding is a little air, or
   the stroke butts into the descender of the y. */
.vine__stem,
.prose .vine__stem { margin: 0; padding-right: 0.3rem; }

.vine__line {
  width: 100%;
  height: 100%;
  fill: none;
  stroke: var(--ink-3);
  stroke-width: 1.2;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

/* Doubled class so the reset beats `.prose ul`, whose indent and markers would
   otherwise sit between the line and the words it is pointing at. */
.vine__items,
.prose .vine__items {
  display: grid;
  grid-auto-rows: 1fr;
  align-items: center;
  list-style: none;
  margin: 0;
  padding: 0;
}
.vine__items li { margin: 0; }

/* No room for a three-column diagram on a phone, and a 3rem gutter taken out
   of a 20rem column would wrap the long item to three lines and leave the
   drawing pointing at the gaps between them. The sentence still works read
   straight down. */
@media (max-width: 40rem) {
  .vine { grid-template-columns: 1fr; }
  .vine__line { display: none; }
  .vine__stem::after { content: ':'; }
  .vine__items,
  .prose .vine__items {
    display: block;
    padding-left: 1.3rem;
    margin-top: 0.35rem;
    list-style: disc;
  }
  .vine__items li { margin-bottom: 0.35rem; }
  .vine__items li::marker { color: var(--ink-3); }
}


/* --- 9b. page transitions -----------------------------------
   Content fades against each page's own background rather than a shared
   overlay, so moving from the garage's dusk to the blog's paper has no
   flash in between. The arrival half runs on every load, including direct
   links, so nothing is penalised for arriving cold.
   ------------------------------------------------------------ */

@media (prefers-reduced-motion: no-preference) {
  html.is-arriving .ruler,
  html.is-arriving .shell,
  html.is-arriving .stage { animation: page-arrive 380ms ease both; }

  html.is-leaving .ruler,
  html.is-leaving .shell,
  html.is-leaving .stage {
    opacity: 0;
    transition: opacity 240ms ease;
  }
}

@keyframes page-arrive {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Reading pages get a hair of lift as well; the garage stays put. */
@media (prefers-reduced-motion: no-preference) {
  html.is-arriving .shell { animation-name: page-arrive-lift; }
}

@keyframes page-arrive-lift {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}


/* --- 9c. return to the garage -------------------------------- */

.rail__back {
  display: block;
  margin-top: 2rem;
  text-decoration: none;
  color: var(--ink-3);
}
.rail__back img {
  display: block;
  width: 100%;
  border: 1px solid var(--rule);
  filter: saturate(0.75) brightness(0.94);
  transition: filter 160ms ease, border-color 160ms ease;
}
.rail__back span {
  display: block;
  margin-top: 0.5rem;
  font-family: var(--mono);
  font-size: 0.625rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}
.rail__back:hover img { filter: none; border-color: var(--clay); }
.rail__back:hover span { color: var(--clay); }


/* --- 9d. post footer nav ------------------------------------- */

.postnav {
  display: flex;
  gap: 1.5rem;
  justify-content: space-between;
  margin-top: calc(var(--step) * 2.5);
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
}
.postnav a {
  flex: 1;
  text-decoration: none;
  color: var(--ink);
  max-width: 47%;
}
.postnav a:last-child { text-align: right; }
.postnav b {
  display: block;
  font-family: var(--mono);
  font-size: 0.625rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.3rem;
}
.postnav span { font-size: 1rem; line-height: 1.35; }
.postnav a:hover span { color: var(--clay); }


/* --- 10. responsive ----------------------------------------- */

@media (max-width: 62rem) {
  .shell {
    padding: 0 1.5rem 4rem;
  }

  .rail {
    position: static;
    width: auto;
    height: auto;
    z-index: auto;
    padding: 2.25rem 0 1.25rem;
    border-right: 0;
    border-bottom: 1px solid var(--rule);
    text-align: left;
    display: grid;
    /* Third column for the switch: the bar has room for it, and stacked under
       the name it would read as a heading rather than a control. */
    grid-template-columns: auto 1fr auto;
    align-items: center;
    column-gap: 1.5rem;
  }
  .rail__theme { margin-top: 0; }

  .rail__name span { display: inline; }
  .rail__name span + span::before { content: ' '; }

  .rail__nav {
    margin-top: 0;
    flex-direction: row;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
  }
  .rail__nav a[aria-current='page']::after { right: auto; left: 50%; top: auto; bottom: -0.4rem; margin: 0 0 0 -2px; }

  /* The garage thumbnail is desktop furniture; drop it rather than crowd the bar. */
  .rail__back { display: none; }

  .moto-corner { width: clamp(130px, 36vw, 200px); opacity: 0.15; }

  .content { padding-top: 2.5rem; }

  body { font-size: 1.125rem; }
  .lede { font-size: 1.1875rem; }
  .prose h1 { font-size: 1.75rem; }

  .index a { grid-template-columns: 1fr; gap: 0.15rem; }
  .index .note { grid-column: 1; }

  .postnav { flex-direction: column; gap: 1rem; }
  .postnav a, .postnav a:last-child { max-width: none; text-align: left; }

}

/* On a phone the bar runs out of room. Three things across one line — a spaced
   uppercase name, three links and the theme switch — needs about 26rem, and below
   that the grid squeezes the middle column until the nav wraps mid-row and the
   whole header reads as a pile rather than a bar.

   So it becomes two rows: who this is, with the switch opposite it, and then the
   nav on its own line under the pair. Same order, same right edge, one less thing
   competing for the same 20rem. */
@media (max-width: 30rem) {
  .rail {
    grid-template-columns: 1fr auto;
    row-gap: 0.85rem;
    column-gap: 1rem;
    padding-bottom: 1.1rem;
  }
  .rail__name { grid-area: 1 / 1; }
  .rail__theme { grid-area: 1 / 2; justify-self: end; }
  .rail__nav {
    grid-area: 2 / 1 / 2 / -1;
    justify-content: flex-start;
    gap: 0.25rem 1.15rem;
  }
  /* The marker sits under its link, and row 2 is the last thing in the bar, so
     it needs the padding above to clear the border rather than sit on it. */
  .rail__nav a[aria-current='page']::after { bottom: -0.3rem; }
}

@media print {
  .rail, .ruler, .foot { display: none; }
  /* The map prints; it is ink on paper either way. Its bleed does not. */
  .atlas { width: 100%; }
  body { background: #fff; color: #000; font-size: 11pt; }
  body::before { display: none; }
  .shell { display: block; max-width: none; padding: 0; }
}
