/* ============================================================
   Home — the portfolio list as a map sheet.
   A cartouche masthead, a survey of numbered benchmarks, a
   colophon with the elevation tint bar. The contour field
   behind everything is drawn by js/lib/contours.js.
   ============================================================ */
/* ============================================================
   nate.fun — Base stylesheet
   Foundation plus the shared project shell.

   Every page imports this: `src/less/home.less` for the home page,
   and each project's own `style.less`. Pages are never loaded
   together, so the small duplication buys full isolation — one
   project's CSS can never leak into another.
   ============================================================ */
/* ============================================================
   nate.fun — Design Tokens
   Single source of truth for every visual value on the site.
   Components reference tokens only; never raw hex, px, or ms.

   The identity is "Night Atlas": the site as a dark topographic
   sheet. Titles are set like range labels on a USGS quad, blurbs
   in the blue-leaning italic cartographers reserve for water,
   dates and coordinates in mono like survey benchmarks, and the
   accent is contour amber rather than a product green.

   Dark is the default — a night sheet. Light is the same quad
   in daylight: bone paper, sepia contours, printed inks.
   ============================================================ */
:root {
  /* ── Palette (dark / night sheet) ──────────────────────── */
  /* Contrast against --color-bg: ink 14.1:1, muted 6.8:1,
     faint 5.0:1, accent 7.9:1, hydro 8.0:1 — everything that
     sets text clears WCAG AA for normal sizes. */
  --color-bg: #0e1712;
  /* Page background — spruce ink */
  --color-surface: #15211a;
  /* Cards, panels, code blocks */
  --color-ink: #e8e2d0;
  /* Primary text — map paper, never pure #fff */
  --color-muted: #94a292;
  /* Blurbs, secondary text — sage */
  --color-faint: #6f8d7a;
  /* Benchmarks, coordinates, tags */
  --color-border: #24402f;
  /* Rules and dividers — index contour */
  --color-accent: #d9a05b;
  /* Links, hovers, dates — contour amber */
  --color-accent-soft: rgba(217, 160, 91, 0.12);
  /* Accent wash */
  --color-hydro: #7fb3c4;
  /* Taglines, water-feature italics */
  /* Contour field drawn behind the home page (js/lib/contours.js
     reads these at draw time so the canvas follows the scheme). */
  --contour-line: rgba(110, 155, 125, 0.14);
  --contour-index: rgba(110, 155, 125, 0.3);
  /* Hypsometric elevation ramp — the colophon's tint strip. */
  --hypso-ramp: linear-gradient(90deg, #2e5339, #6d7a45, #a8834f, #b4553a, #e8e2d0);
  /* ── Typography ────────────────────────────────────────── */
  /* Two self-hosted faces carry the map identity (declared in
     typography.less, files in src/assets/fonts/, both OFL):
     Archivo Narrow for the condensed caps of quad labels, and
     Spectral italic for hydrography lettering. Body and mono
     stay system stacks — project UIs are tools, not marginalia. */
  --font-display: "Archivo Narrow", "Arial Narrow", "Helvetica Neue", sans-serif;
  --font-serif: "Spectral", Georgia, "Times New Roman", serif;
  --font-body: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --text-xs: 0.6875rem;
  /* 11px — tags, benchmarks */
  --text-sm: 0.8125rem;
  /* 13px — metadata */
  --text-base: 1rem;
  /* 16px — body */
  --text-md: 1.125rem;
  /* 18px — list titles */
  --text-lg: 1.375rem;
  /* 22px */
  --text-xl: 2rem;
  /* 32px — page titles */
  --leading-tight: 1.15;
  --leading-normal: 1.5;
  --leading-relaxed: 1.65;
  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --tracking-wide: 0.08em;
  /* Uppercase mono labels */
  --tracking-map: 0.22em;
  /* Range-label caps — titles, section labels */
  --tracking-cartouche: 0.38em;
  /* The wordmark alone */
  /* ── Spacing ───────────────────────────────────────────── */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-8: 32px;
  --space-12: 48px;
  --space-16: 64px;
  --space-24: 96px;
  /* ── Radii ─────────────────────────────────────────────── */
  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-full: 9999px;
  /* ── Motion ────────────────────────────────────────────── */
  --duration-fast: 120ms;
  --duration-normal: 220ms;
  --ease-default: cubic-bezier(0.4, 0, 0.2, 1);
  /* ── Layout ────────────────────────────────────────────── */
  --max-width: 720px;
  /* Reading column — home and project chrome */
  --shell-height: 52px;
  /* ── Focus ─────────────────────────────────────────────── */
  --focus-ring: 2px solid var(--color-accent);
  --focus-ring-offset: 3px;
}
/* ── Palette (light / daylight quad) ─────────────────────── */
/* The same sheet in sunlight: bone paper, sepia contour rules,
   printed sienna and hydro-blue inks. The amber deepens to stay
   readable — 5.1:1 on this paper vs 7.9:1 on the night sheet —
   and muted/faint hold 5.1:1 and 5.2:1. */
@media (prefers-color-scheme: light) {
  :root {
    --color-bg: #f1ead8;
    --color-surface: #f9f4e6;
    --color-ink: #2a2718;
    --color-muted: #5d6552;
    --color-faint: #666149;
    --color-border: #d5c9a8;
    --color-accent: #8f5410;
    --color-accent-soft: rgba(143, 84, 16, 0.1);
    --color-hydro: #276e88;
    --contour-line: rgba(146, 116, 60, 0.2);
    --contour-index: rgba(146, 116, 60, 0.38);
    --hypso-ramp: linear-gradient(90deg, #4b7a52, #8b8a4b, #b98d4f, #b4553a, #8a7f5c);
  }
}
/* ============================================================
   Reset — minimal clean slate. Nothing opinionated lives here.
   ============================================================ */
*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
body {
  min-height: 100vh;
  min-height: 100dvh;
  background-color: var(--color-bg);
  color: var(--color-ink);
  line-height: var(--leading-normal);
}
img,
picture,
video,
canvas,
svg {
  display: block;
  max-width: 100%;
}
input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}
button {
  cursor: pointer;
  background: none;
  border: none;
}
a {
  color: inherit;
  text-decoration: none;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}
ul,
ol {
  list-style: none;
}
table {
  border-collapse: collapse;
}
h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: inherit;
  font-weight: inherit;
}
:focus {
  outline: none;
}
:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-sm);
}
::selection {
  background-color: var(--color-accent);
  color: var(--color-bg);
}
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
/* ============================================================
   Typography — the map's three voices.

   Archivo Narrow (condensed caps, tracked) is the lettering of
   range and region labels on a topographic quad — headings and
   section labels. Spectral italic is hydrography lettering —
   taglines and blurbs. System mono stays for benchmarks, dates,
   coordinates and code; system sans for plain UI text.

   Both webfonts are self-hosted from /assets/fonts/ (latin
   subsets, ~27 KB total, SIL Open Font License) — no third-party
   request. The files carry the Google Fonts version in their
   name because /assets/ is served unhashed with a long cache:
   changing a font means changing its filename.
   ============================================================ */
@font-face {
  font-family: "Archivo Narrow";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("/assets/fonts/archivo-narrow-v35-latin-600.woff2") format("woff2");
}
@font-face {
  font-family: "Spectral";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("/assets/fonts/spectral-v15-latin-italic.woff2") format("woff2");
}
body {
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: var(--weight-regular);
}
h1 {
  font-family: var(--font-display);
  font-size: var(--text-xl);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-map);
  text-transform: uppercase;
}
h2 {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-map);
  text-transform: uppercase;
}
h3 {
  font-family: var(--font-body);
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
}
p {
  line-height: var(--leading-relaxed);
}
code,
pre,
kbd {
  font-family: var(--font-mono);
  font-size: 0.9em;
}
/* Uppercase mono eyebrow — small chrome labels, "back" links */
.label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--color-muted);
}
/* ── Project shell ───────────────────────────────────────── */
/* The thin header every project page gets from mountProjectShell().
   Keeps a way home on each app without each app rebuilding chrome. */
.shell {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100dvh;
}
.shell-bar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  height: var(--shell-height);
  padding: 0 var(--space-5);
  background-color: color-mix(in srgb, var(--color-bg) 88%, transparent);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--color-border);
}
.shell-back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--color-muted);
  transition: color var(--duration-fast) var(--ease-default);
}
.shell-back:hover {
  color: var(--color-accent);
}
.shell-title {
  /* Lettered like a quad label, same as titles on the home sheet. */
  font-family: var(--font-display);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-map);
  color: var(--color-ink);
}
.shell-sep {
  color: var(--color-border);
  user-select: none;
}
/* Project content mounts here. Projects that want the full viewport
   (canvas toys, games) can just ignore the padding and go edge to edge. */
.shell-content {
  flex: 1;
  min-height: 0;
}
/* ── Bottom sheet (mobile map chrome) ────────────────────── */
/* On a phone a panel floating over a map buries it, so each map app
   docks its panel to the bottom edge instead: the map gets the whole
   viewport, a compact peek row stays visible, and the full controls
   expand on tap. js/lib/sheet.js adds .is-sheet when its media query
   matches and toggles .is-open from the grab button — there is
   deliberately no media query here, so each app's breakpoint lives
   in its one attachSheet() call (plus a matching media block in that
   app's style.less for its map chrome).

   .shell-content wins the specificity tie against each app's own
   single-class panel rules (.vs-panel, .rp-panel, .vt-panel), which
   sit later in the compiled file. */
.sheet-grab {
  display: none;
}
.shell-content .is-sheet {
  position: absolute;
  /* Clears Leaflet's controls (800) and MapLibre's (2) alike. */
  z-index: 1000;
  inset: auto 0 0 0;
  width: auto;
  max-height: 65dvh;
  display: flex;
  flex-direction: column;
  padding: 0;
  padding-bottom: env(safe-area-inset-bottom, 0);
  overflow: hidden;
  border: 1px solid var(--color-border);
  border-bottom: none;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  background-color: var(--color-bg);
  box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.35);
}
.is-sheet .sheet-grab {
  display: block;
  flex: none;
  width: 100%;
  padding: var(--space-3) 0;
  cursor: pointer;
  /* The pill is drawn rather than content, so the whole strip stays
     one big tap target for a thumb. */
}
.is-sheet .sheet-grab::before {
  content: "";
  display: block;
  width: 44px;
  height: 4px;
  margin: 0 auto;
  border-radius: var(--radius-full);
  background-color: var(--color-border);
}
.is-sheet .sheet-peek {
  flex: none;
  padding: 0 var(--space-4) var(--space-3);
}
.is-sheet .sheet-body {
  min-height: 0;
  overflow-y: auto;
  padding: var(--space-2) var(--space-4) var(--space-4);
}
/* The last body takes the leftover height; a body above the peek
   (vantage keeps its settings there) shrinks and scrolls first. */
.is-sheet .sheet-body:last-child {
  flex: 1;
}
.is-sheet:not(.is-open) .sheet-body {
  display: none;
}
/* The contour canvas sits behind the page, fixed so the terrain
   holds still while the list scrolls over it — content over ground,
   like labels over a basemap. */
.contour-field {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
  pointer-events: none;
}
.page {
  position: relative;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: var(--space-24) var(--space-5) var(--space-16);
}
/* The corner sheet — a bare coordinate, changing once per visit.
   home.js picks it from a list of places worth standing. */
.graticule {
  position: absolute;
  top: var(--space-8);
  right: var(--space-5);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  color: var(--color-faint);
}
/* ── Masthead ────────────────────────────────────────────── */
.wordmark {
  letter-spacing: var(--tracking-cartouche);
  /* The cartouche rule — a short amber underscore, the site's
     only ornament. */
}
.wordmark::after {
  content: "";
  display: block;
  width: 120px;
  margin-top: var(--space-4);
  border-top: 1px solid var(--color-accent);
}
.tagline {
  max-width: 44ch;
  margin-top: var(--space-4);
  font-family: var(--font-serif);
  font-style: italic;
  color: var(--color-hydro);
}
/* ── Project list ────────────────────────────────────────── */
.projects {
  margin-top: var(--space-16);
}
.projects-label {
  /* Overrides the mono .label: the section header is lettered like
     the sheet itself, in tracked display caps, amber. */
  font-family: var(--font-display);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-map);
  color: var(--color-accent);
  padding-bottom: var(--space-3);
  border-bottom: 1px solid var(--color-border);
}
.entry-list > li + li {
  border-top: 1px solid var(--color-border);
}
.entry {
  display: grid;
  grid-template-columns: 3.2rem 1fr auto;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-5) 0;
}
/* Benchmark index — "△ 01" for pages here, "● 03" for settlements
   that live on their own domain. The marker is presentational; the
   empty alt keeps it out of the accessible name. */
.entry-index {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--color-faint);
  transition: color var(--duration-fast) var(--ease-default);
}
.entry-index::before {
  content: "\25B3\00a0" / "";
}
.entry-index-external::before {
  content: "\25CF\00a0" / "";
}
.entry-body {
  display: block;
  min-width: 0;
}
.entry-title {
  display: block;
  font-family: var(--font-display);
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-map);
  line-height: var(--leading-tight);
  transition: color var(--duration-fast) var(--ease-default);
}
.entry:hover .entry-index,
.entry:hover .entry-title {
  color: var(--color-accent);
}
.entry-blurb {
  display: block;
  margin-top: var(--space-2);
  max-width: 52ch;
  font-family: var(--font-serif);
  font-style: italic;
  line-height: var(--leading-relaxed);
  color: var(--color-muted);
}
.entry-date {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-accent);
  white-space: nowrap;
  text-align: right;
}
/* ── Outbound rows ───────────────────────────────────────── */
/* Projects that live on their own domain. The settlement dot, the
   arrow and the hostname are the only difference — an outbound row
   should read as a peer of the rest, not as a footnote. */
.entry-arrow {
  margin-left: 0.4em;
  font-size: 0.7em;
  vertical-align: 0.35em;
  letter-spacing: 0;
  color: var(--color-faint);
  transition: color var(--duration-fast) var(--ease-default);
}
.entry:hover .entry-arrow {
  color: var(--color-accent);
}
.entry-host {
  display: block;
  margin-top: var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: 0;
  text-transform: none;
  color: var(--color-faint);
}
.entry-tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
}
.tag {
  padding: 2px var(--space-2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-style: normal;
  color: var(--color-faint);
}
.entry-empty {
  padding: var(--space-8) 0;
  color: var(--color-muted);
}
.entry-empty code {
  padding: 2px var(--space-2);
  border-radius: var(--radius-sm);
  background-color: var(--color-surface);
  color: var(--color-ink);
}
/* ── Colophon ────────────────────────────────────────────── */
.colophon {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-top: var(--space-16);
  padding-top: var(--space-5);
  border-top: 1px solid var(--color-border);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--color-faint);
}
.colophon a:hover {
  color: var(--color-accent);
}
/* The hypsometric tint bar every printed quad carries in its margin. */
.ramp {
  width: 110px;
  height: 6px;
  margin-left: auto;
  border-radius: var(--radius-sm);
  background: var(--hypso-ramp);
}
/* ── Small screens ───────────────────────────────────────── */
/* Drop the benchmark column and tuck the date under the title — at
   this width a three-column grid squeezes the blurb to nothing. */
@media (max-width: 560px) {
  .page {
    padding-top: var(--space-16);
  }
  .graticule {
    top: var(--space-5);
  }
  .entry {
    grid-template-columns: 1fr;
    gap: var(--space-2);
  }
  .entry-index {
    display: none;
  }
  .entry-date {
    order: 3;
    /* Full-width column here, so the desktop flush-right would strand the
       date at the far edge away from everything else. */
    text-align: left;
  }
  .entry-host {
    display: inline;
    margin-left: var(--space-2);
  }
}

