/* ============================================================
   Ephemeris — a constellation of watches

   Copyright © 2026 Ignacio Izquierdo. All rights reserved.
   Not licensed for reuse or redistribution — see LICENSE.

   Every colour is a custom property declared twice: once for night on
   :root, once for day on [data-theme="day"]. Nothing below the theme
   block hard-codes a colour, so adding a third theme is a variable set.
   ============================================================ */

/* ---------- Latin Modern (Computer Modern) ----------
   Knuth's Computer Modern in its Unicode OpenType form, by GUST. Self-hosted
   because the TeX distribution's copies are not installed as system fonts, so a
   family name alone would never resolve in a browser. Subset from the 10 pt
   optical size and converted to WOFF2 (85 KB for all three); licence in
   assets/fonts/LICENSE.txt. font-display:swap so text is never invisible.
   ---------------------------------------------------- */
@font-face{
  font-family:"Latin Modern Roman";
  src:url("assets/fonts/LatinModernRoman-Regular.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap;
}
@font-face{
  font-family:"Latin Modern Roman";
  src:url("assets/fonts/LatinModernRoman-Bold.woff2") format("woff2");
  font-weight:700;font-style:normal;font-display:swap;
}
@font-face{
  font-family:"Latin Modern Sans";
  src:url("assets/fonts/LatinModernSans-Regular.woff2") format("woff2");
  font-weight:400;font-style:normal;font-display:swap;
}

:root{
  --ink:#e9eef8;
  --ink-dim:#8b97ad;
  --ink-faint:#5b657a;
  --gold:#d8b271;
  --gold-soft:#f0d9a8;
  --line:rgba(216,178,113,.22);
  --line-lit:rgba(240,217,168,.55);
  --line-hot:rgba(240,217,168,.8);
  --line-maker:rgba(240,217,168,.5);
  --panel:rgba(10,14,24,.86);
  --rule:rgba(233,238,248,.1);
  --edge:rgba(216,178,113,.28);

  --dial-face:#0b111d;
  --dial-tick:rgba(233,238,248,.5);
  --dial-sec:rgba(233,238,248,.8);
  --dial-bezel:rgba(216,178,113,.45);
  --dial-gmt:#c98f4a;

  --glow-in:rgba(240,217,168,.55);
  --glow-mid:rgba(216,178,113,.22);
  --glow-out:rgba(216,178,113,0);
  --halo-op:.5;

  --page:#05070d;
  --wash-a:#101a2e;
  --wash-b:#0d1524;
  --star-a:#e9eef8;
  --star-b:#f0d9a8;
  --star-op:1;

  /* Both roles are Computer Modern Roman, because that serif *is* the face people mean
     by "the LaTeX font" — a LaTeX document is roman throughout. An earlier version put
     Latin Modern Sans on the body and UI, which is a perfectly good grotesque and reads
     nothing like LaTeX, so the page looked as though the font had failed to load.
     "CMU Serif" is named first for anyone with Computer Modern Unicode installed. */
  --serif:"CMU Serif","Latin Modern Roman","Computer Modern Roman",
          "Iowan Old Style",Palatino,Georgia,serif;
  --sans:"CMU Serif","Latin Modern Roman","Computer Modern Roman",
         Palatino,Georgia,serif;
  /* Kept for anything that genuinely wants the sans cut; nothing uses it by default. */
  --sans-cm:"CMU Sans Serif","Latin Modern Sans",-apple-system,Helvetica,sans-serif;
}

:root[data-theme="day"]{
  --ink:#1b212e;
  --ink-dim:#535c6e;
  --ink-faint:#8b91a0;
  --gold:#9a6f2a;
  --gold-soft:#7a5312;
  --line:rgba(120,88,36,.3);
  --line-lit:rgba(122,83,18,.6);
  --line-hot:rgba(122,83,18,.85);
  --line-maker:rgba(122,83,18,.5);
  --panel:rgba(252,250,246,.9);
  --rule:rgba(27,33,46,.12);
  --edge:rgba(154,111,42,.32);

  --dial-face:#fbf9f4;
  --dial-tick:rgba(28,34,48,.55);
  --dial-sec:rgba(28,34,48,.7);
  --dial-bezel:rgba(154,111,42,.5);
  --dial-gmt:#b1651f;

  --glow-in:rgba(154,111,42,.2);
  --glow-mid:rgba(154,111,42,.08);
  --glow-out:rgba(154,111,42,0);
  --halo-op:.35;

  --page:#f2eee5;
  --wash-a:#fffdf7;
  --wash-b:#e7e0d2;
  --star-op:0;                 /* the starfield has no business in daylight */
}

*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0;
  background:
    radial-gradient(1200px 700px at 70% -10%, var(--wash-a) 0%, transparent 60%),
    radial-gradient(900px 600px at 10% 110%, var(--wash-b) 0%, transparent 60%),
    var(--page);
  color:var(--ink);
  font-family:var(--sans);
  overflow:hidden;
  transition:background-color .5s;
}

#stars{
  position:fixed;inset:0;width:100%;height:100%;display:block;
  opacity:var(--star-op);transition:opacity .5s;
}

/* ---------- header ---------- */
header{
  position:fixed;top:0;left:0;right:0;padding:26px 30px 0;z-index:5;
  pointer-events:none;max-width:100%;
}
/* Zoomed-in content passes behind the controls, which are mostly transparent. A scrim
   keeps them legible without boxing the header in. */
header::before,footer::before{
  content:"";position:absolute;left:0;right:0;z-index:-1;pointer-events:none;
}
header::before{
  top:0;bottom:-40px;
  background:linear-gradient(to bottom,var(--page) 45%,transparent);
  opacity:.82;
}
footer::before{
  bottom:0;top:-30px;
  background:linear-gradient(to top,var(--page) 55%,transparent);
  opacity:.82;
}
h1{
  margin:0;font-family:var(--serif);font-weight:400;color:var(--ink);
  font-size:clamp(20px,2.4vw,30px);letter-spacing:.16em;
}
h1 em{font-style:normal;color:inherit}
.tagline{
  margin:8px 0 0;font-size:12px;letter-spacing:.13em;text-transform:uppercase;
  color:var(--ink-faint);
}

#theme{
  pointer-events:auto;cursor:pointer;
  background:none;border:1px solid var(--edge);border-radius:2px;
  width:30px;height:28px;display:grid;place-items:center;color:var(--gold);
  transition:border-color .2s,color .2s;
}
#theme:hover{border-color:var(--gold);color:var(--gold-soft)}
#theme span{
  width:13px;height:13px;border-radius:50%;
  border:1.5px solid currentColor;
  /* a waxing-moon mask at night, a filled sun in daylight */
  box-shadow:inset -4px 0 0 0 currentColor;
}
:root[data-theme="day"] #theme span{box-shadow:inset 0 0 0 7px currentColor}

/* pill buttons: nav, filters, chips */
header nav{display:flex;gap:10px;margin-top:16px;pointer-events:auto}
.pill,header nav button,#filters button{
  background:none;border:1px solid var(--edge);border-radius:2px;
  padding:6px 12px;cursor:pointer;color:var(--ink-dim);font-family:var(--sans);
  font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;
  transition:border-color .2s,color .2s,background .2s;
}
header nav button:hover,header nav button.on,
#filters button:hover{border-color:var(--gold);color:var(--gold-soft)}
#filters button.on{
  border-color:var(--gold);color:var(--gold-soft);background:color-mix(in srgb,var(--gold) 16%,transparent);
}

#lang{display:inline-flex;gap:0;margin-left:4px}
#lang button{
  background:none;border:1px solid var(--edge);border-radius:0;
  padding:6px 10px;cursor:pointer;color:var(--ink-faint);font-family:var(--sans);
  font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;
  transition:border-color .2s,color .2s,background .2s;
}
#lang button:first-child{border-radius:2px 0 0 2px}
#lang button:last-child{border-radius:0 2px 2px 0;border-left-width:0}
#lang button:hover{color:var(--gold-soft)}
#lang button.on{
  color:var(--gold-soft);border-color:var(--gold);
  background:color-mix(in srgb,var(--gold) 16%,transparent);
}

/* ---------- filter bar ---------- */
/* Collapsed by default: the chips cost more header than a phone can spare, and the
   header insets the map. The labels are the buttons that reveal them. */
#filters{
  display:flex;flex-direction:column;gap:8px;
  margin-top:16px;pointer-events:auto;max-width:min(880px,80vw);
}
.f-bar{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.f-toggle::after{
  content:"+";margin-left:8px;opacity:.7;font-size:11px;line-height:0;vertical-align:-1px;
}
.f-toggle.open::after{content:"\2212"}          /* minus */
/* An open panel and an active filter are different states and must look different. */
.f-toggle.open{border-color:var(--gold);color:var(--gold-soft)}
.f-toggle.active{
  border-color:var(--gold);color:var(--gold-soft);
  background:color-mix(in srgb,var(--gold) 16%,transparent);
}
.f-toggle.active::before{
  content:attr(data-n);margin-right:7px;
  color:var(--gold);font-variant-numeric:tabular-nums;
}
.f-chips{display:flex;flex-wrap:wrap;align-items:center;gap:7px;min-width:0}
.f-chips[hidden]{display:none}
#filters button{padding:5px 10px}
#filters .f-count{
  margin-left:auto;flex:none;
  font-size:9px;letter-spacing:.16em;text-transform:uppercase;color:var(--gold);
}
#filters [data-reset]{flex:none}
#filters [data-reset][hidden]{display:none}

/* ---------- force controls ---------- */
#forces{
  margin-top:14px;pointer-events:auto;max-width:min(560px,72vw);
  border:1px solid var(--edge);border-radius:2px;padding:14px 16px 12px;
  background:var(--panel);backdrop-filter:blur(14px);
}
#forces[hidden]{display:none}
.fx-grid{display:grid;gap:9px}
.fx-row{display:grid;grid-template-columns:104px 1fr 42px;align-items:center;gap:12px;cursor:pointer}
.fx-label{
  font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-dim);
}
.fx-row output{
  font-family:var(--serif);font-size:12px;color:var(--gold);text-align:right;
  font-variant-numeric:tabular-nums;
}

/* Range styling has to be per-engine; unstyled thumbs read as a different design. */
.fx-row input[type=range]{
  -webkit-appearance:none;appearance:none;width:100%;height:16px;background:none;cursor:pointer;
}
.fx-row input[type=range]::-webkit-slider-runnable-track{
  height:1px;background:var(--line);
}
.fx-row input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;width:11px;height:11px;border-radius:50%;
  background:var(--gold);margin-top:-5px;transition:background .2s,transform .2s;
}
.fx-row input[type=range]:hover::-webkit-slider-thumb{background:var(--gold-soft);transform:scale(1.15)}
.fx-row input[type=range]::-moz-range-track{height:1px;background:var(--line)}
.fx-row input[type=range]::-moz-range-thumb{
  width:11px;height:11px;border:none;border-radius:50%;background:var(--gold);
}

.fx-foot{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  margin-top:12px;padding-top:10px;border-top:1px solid var(--rule);
}
.fx-foot button{
  background:none;border:1px solid var(--edge);border-radius:2px;padding:5px 10px;cursor:pointer;
  color:var(--ink-dim);font-family:var(--sans);
  font-size:9px;letter-spacing:.16em;text-transform:uppercase;
  transition:border-color .2s,color .2s;
}
.fx-foot button:hover{border-color:var(--gold);color:var(--gold-soft)}
.fx-note{font-size:9px;letter-spacing:.1em;text-transform:uppercase;color:var(--ink-faint)}

/* ---------- footer ---------- */
/* 10.5px, not 9.5: Latin Modern is a 10 pt print face with hairline strokes and a small
   x-height, so it needs a little more size and contrast than a screen-optimised system
   sans at the same nominal size. The faintest ink was unreadable here. */
footer{
  position:fixed;bottom:0;left:0;right:0;padding:0 30px 18px;z-index:4;
  font-size:12.5px;letter-spacing:.11em;color:var(--ink-dim);
  display:flex;justify-content:space-between;align-items:baseline;gap:20px;
  pointer-events:none;                 /* the map beneath stays draggable */
}

/* The notice is the least important line in the footer, so it takes the faintest ink the
   face can carry at this size — see the note above about Latin Modern's hairlines. */
.f-copy{color:var(--ink-faint)}

/* An address is not a label: no uppercasing, no tracking, and selectable — the
   footer's pointer-events:none is re-enabled just for this. */
.f-contact{
  text-transform:none;letter-spacing:.01em;font-size:11.5px;
  pointer-events:auto;
}
.f-contact a{
  color:var(--ink);text-decoration:none;
  border-bottom:1px solid var(--edge);
  transition:color .2s,border-color .2s;
}
.f-contact a:hover{color:var(--gold);border-bottom-color:var(--gold)}

/* ---------- star map ---------- */
/* The map is inset below the header and above the footer so the constellation can
   never sit under the controls. --map-top is measured from the real header height
   in app.js, because the header wraps unpredictably at narrow widths.
   width/height must be explicit: a positioned SVG with width:auto falls back to
   its intrinsic viewBox size and pins itself top-left at 1:1. */
#map{
  position:fixed;left:0;top:var(--map-top,150px);
  width:100%;height:calc(100% - var(--map-top,150px) - 46px);
  z-index:2;overflow:visible;
  transition:transform .5s cubic-bezier(.22,.8,.25,1);
  /* Required: without it the browser claims pinch and drag for page zoom/scroll
     and the gesture handlers never see them. */
  touch-action:none;
  cursor:grab;
}
#map.grabbing{cursor:grabbing}
#map .node{cursor:grab}
#map .node:active,#map.dragging,#map .node.dragging{cursor:grabbing}
/* The held node keeps its glow at full strength so it stays legible while everything
   else is still moving. */
#map .node.dragging .halo{opacity:1}
#map .node.dragging .case,#map .node.dragging .crown{stroke:var(--gold-soft)}
/* Closely spaced nodes — a same-manufacturer pair sits ~134 units apart — mean the
   open lineage arc can pass straight over a neighbour, so unselected nodes drop
   further back than mere de-emphasis would need. */
#map.has-selection .node:not(.selected){opacity:.2}

.glow-in{stop-color:var(--glow-in)}
.glow-mid{stop-color:var(--glow-mid)}
.glow-out{stop-color:var(--glow-out)}

/* Two relationship types, deliberately unlike each other: a manufacturer edge is a
   structural claim and carries the gravity, a curiosity is a footnote. */
.link{stroke:var(--line);fill:none;transition:stroke .3s,stroke-width .3s,opacity .3s}
.link.manufacturer{stroke-width:2;stroke:var(--line-maker)}
.link.curiosity{stroke-width:.5;opacity:.55}
.link.lit{stroke:var(--line-lit)}
.link.manufacturer.lit{stroke-width:2.4}
.link.curiosity.lit{stroke-width:.9;opacity:1}
/* A 1px line is too thin to hover; an invisible fat line over it carries the pointer. */
.link-hit{stroke:transparent;stroke-width:14;fill:none;cursor:help}
/* A curiosity line is half a unit wide. On a finger-driven device the invisible hit
   line has to be far wider than the ink, or the relationship is unreachable.
   `.coarse` is set by app.js, so this follows the same decision the tap handlers did. */
:root.coarse .link-hit{stroke-width:30}
:root.coarse #tip{pointer-events:auto}   /* tap the card itself to dismiss it */
.linkg:hover .link{stroke:var(--line-hot);stroke-width:1.6}
.linkg.faded{opacity:.12;pointer-events:none}

.node{cursor:pointer;transition:opacity .4s}
.node:focus{outline:none}
.node.faded{opacity:.1;pointer-events:none}
/* No `fill` here. Each node sets its own gradient as a presentation attribute
   (`fill="url(#glow-diver)"`), and a CSS declaration would override every one of them —
   in SVG, CSS beats presentation attributes. Setting the default here silently repainted
   all eight areoles with the neutral glow while the attributes still read correctly,
   which is invisible to any check that reads getAttribute rather than getComputedStyle. */
.node .halo{opacity:var(--halo-op);transition:opacity .3s}
.node:hover .halo,.node:focus-visible .halo,.node.selected .halo{opacity:1}
/* ---------- maker hubs ---------- */
.node.hub{cursor:grab}
.hub-ring{fill:none;stroke:var(--gold);stroke-width:1;opacity:.75}
.hub-core{fill:var(--gold)}
.node.hub:hover .hub-ring,.node.hub.dragging .hub-ring{stroke:var(--gold-soft);opacity:1}
/* Smaller and tighter than a model name: "IWC Schaffhausen" at the model size made a
   footprint wider than any dial, and the separation pass then dictated the layout. */
.node.hub .hub-name{
  font-size:9px;letter-spacing:.14em;fill:var(--gold);
}
.node.hub:hover .hub-name{fill:var(--gold-soft)}

.node .name{
  font-family:var(--serif);font-size:14px;letter-spacing:.1em;text-transform:uppercase;
  fill:var(--ink);text-anchor:middle;
}
.node .maker{
  font-size:9.5px;letter-spacing:.18em;text-transform:uppercase;
  fill:var(--ink-faint);text-anchor:middle;transition:fill .3s;
}
.node:hover .maker,.node.selected .maker{fill:var(--gold)}

/* dial internals — class names come from dial.js, shared with the .svg export */
.case{fill:var(--dial-face);stroke:var(--gold);stroke-width:1.3;transition:stroke .3s}
.node:hover .case,.node.selected .case,
.node:hover .crown,.node.selected .crown{stroke:var(--gold-soft)}
/* No stroke-width here: the band's width is geometry and dial.js emits it per element.
   A rule here would win over the presentation attribute and pin every bezel to one width. */
.bezel{fill:none;stroke:var(--dial-bezel)}
.opening{fill:none;stroke:var(--gold);stroke-width:.8;opacity:.75}
.crown{fill:var(--dial-bezel);stroke:var(--gold);stroke-width:1}
.crown-flute{stroke:var(--gold);stroke-width:.6;opacity:.7}
.crown-jewel{fill:var(--dial-gmt)}
.pip{fill:var(--gold-soft)}
.tick{stroke:var(--dial-tick);stroke-width:1}
.subdial{fill:none;stroke:var(--dial-tick);stroke-width:.7;opacity:.8}
.hand-hour{stroke:var(--gold-soft);stroke-width:1.4;stroke-linecap:round}
.hand-hour-shape{fill:var(--gold-soft);stroke:var(--gold-soft);stroke-width:.5}
.hand-min{stroke:var(--gold-soft);stroke-width:1;stroke-linecap:round}
.hand-sec{stroke:var(--dial-sec);stroke-width:.7;stroke-linecap:round}
.hand-gmt{stroke:var(--dial-gmt);stroke-width:1;stroke-linecap:round}
.hand-gmt-tip{fill:var(--dial-gmt)}
.pivot{fill:var(--gold-soft)}

/* ---------- version satellites ---------- */
.trail{opacity:0;pointer-events:none;transition:opacity .5s}
.trail.on{opacity:1;pointer-events:auto}
.trail-arc{fill:none;stroke:var(--line);stroke-width:1;stroke-dasharray:2 5}
.spoke{stroke:var(--line);stroke-width:.8;opacity:.6}

.sat{cursor:pointer}
.sat circle.dot{fill:var(--gold-soft);r:3.4;transition:r .2s,fill .2s}
.sat circle.hit{fill:transparent}
.sat:hover circle.dot,.sat.hot circle.dot{r:6;fill:var(--gold)}
.sat text{
  font-size:9px;letter-spacing:.1em;fill:var(--ink-faint);text-anchor:middle;
  transition:fill .2s;
}
.sat:hover text,.sat.hot text{fill:var(--gold-soft)}

/* ---------- tooltip ---------- */
#tip{
  position:fixed;z-index:8;pointer-events:none;opacity:0;
  transform:translateY(6px);transition:opacity .18s,transform .18s;
  width:262px;padding:14px 16px 13px;
  background:var(--panel);backdrop-filter:blur(14px);
  border:1px solid var(--edge);border-radius:2px;
  box-shadow:0 18px 50px rgba(0,0,0,.45);
}
#tip.on{opacity:1;transform:translateY(0)}
#tip .t-model{font-family:var(--serif);font-size:16px;letter-spacing:.06em;margin-bottom:2px}
#tip .t-maker{
  font-size:9.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--gold);
  margin-bottom:11px;
}
#tip dl{display:grid;grid-template-columns:auto 1fr;gap:5px 12px;margin:0;font-size:11.5px}
#tip dt{
  color:var(--ink-faint);letter-spacing:.08em;text-transform:uppercase;
  font-size:9.5px;padding-top:1px;
}
#tip dd{margin:0;color:var(--ink)}
#tip .t-hint{
  margin-top:11px;padding-top:9px;border-top:1px solid var(--rule);
  font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-faint);
}
#tip .t-note{margin:0;font-size:11.5px;line-height:1.5;color:var(--ink-dim)}

/* ---------- zoom readout ---------- */
#viewstate{
  position:fixed;left:30px;bottom:44px;z-index:6;
  display:flex;align-items:center;gap:10px;
  padding:6px 10px;border:1px solid var(--edge);border-radius:2px;
  background:var(--panel);backdrop-filter:blur(14px);
}
#viewstate[hidden]{display:none}
.vs-level{
  font-family:var(--serif);font-size:12px;color:var(--gold);
  font-variant-numeric:tabular-nums;letter-spacing:.06em;
}
#viewstate button{
  background:none;border:1px solid var(--edge);border-radius:2px;padding:4px 8px;cursor:pointer;
  color:var(--ink-dim);font-family:var(--sans);
  font-size:9px;letter-spacing:.14em;text-transform:uppercase;
  transition:border-color .2s,color .2s;
}
#viewstate button:hover{border-color:var(--gold);color:var(--gold-soft)}

@media (max-width:760px){
  #viewstate{left:20px;bottom:64px}
}

/* ---------- lineage drawer ---------- */
#drawer{
  position:fixed;z-index:9;top:0;right:0;height:100%;width:min(400px,92vw);
  background:var(--panel);backdrop-filter:blur(18px);
  border-left:1px solid var(--edge);
  transform:translateX(101%);transition:transform .45s cubic-bezier(.22,.8,.25,1);
  display:flex;flex-direction:column;
}
#drawer.on{transform:translateX(0)}

.d-head{padding:26px 26px 18px;border-bottom:1px solid var(--rule);flex:none}
.d-maker{font-size:9.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--gold)}
.d-model{
  font-family:var(--serif);font-size:26px;font-weight:400;margin:6px 0 14px;letter-spacing:.03em;
}
.d-specs{display:grid;grid-template-columns:auto 1fr;gap:6px 14px;margin:0;font-size:12px}
.d-specs dt{
  color:var(--ink-faint);font-size:9.5px;letter-spacing:.1em;text-transform:uppercase;padding-top:1px;
}
.d-specs dd{margin:0}
.d-blurb{margin:16px 0 0;font-size:12.5px;line-height:1.6;color:var(--ink-dim)}

/* ---------- portrait ---------- */
.d-portrait{
  margin:0 0 18px;padding:0;display:flex;flex-direction:column;align-items:center;gap:10px;
}
/* The frame owns the shape; the contents are absolutely positioned inside it.

   Two failed approaches, for the record: a grid frame lets a tall photo override
   aspect-ratio through its content-based automatic minimum size (the frame goes
   square), and `max-height:100%` on a grid item is ignored because a percentage
   against an auto-sized row is indefinite. Absolute positioning against a frame whose
   height is definite makes both percentages resolve, so a photo of any shape is
   letterboxed and centred rather than stretching or cropping its container. */
.d-portrait .p-frame{
  position:relative;width:100%;aspect-ratio:4/3;min-height:0;
  border:1px solid var(--rule);border-radius:2px;overflow:hidden;
  background:color-mix(in srgb,var(--ink) 4%,transparent);
}
/* Both are stretched to the frame and letterbox themselves — the photo via
   object-fit, the SVG via preserveAspectRatio. The dial needs no CSS inset for
   breathing room: dial.js already leaves a margin inside the viewBox. (A percentage
   `inset` on an SVG loses to its intrinsic 1:1 ratio and overflows the frame.) */
.d-portrait img,.d-portrait svg{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:contain;display:block;
}
.d-portrait figcaption{
  font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-faint);
  text-align:center;line-height:1.5;
}
.d-portrait .p-ref{color:var(--gold);display:block}
.d-portrait .p-placeholder{font-style:normal;text-transform:none;letter-spacing:.04em;font-size:10.5px}
.d-portrait .p-meta{
  display:block;margin-top:3px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-dim);
}

/* ---------- lineage timeline ---------- */
.d-body{overflow-y:auto;padding:20px 26px 40px;flex:1}
.d-body::-webkit-scrollbar,.g-body::-webkit-scrollbar{width:6px}
.d-body::-webkit-scrollbar-thumb,.g-body::-webkit-scrollbar-thumb{
  background:color-mix(in srgb,var(--gold) 30%,transparent);border-radius:3px;
}
.d-lineage{
  font-size:9.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-faint);margin:0 0 16px;
}

ol.tl{list-style:none;margin:0;padding:0 0 0 20px;position:relative}
ol.tl::before{
  content:"";position:absolute;left:3px;top:6px;bottom:6px;width:1px;background:var(--line);
}
ol.tl li{position:relative;padding:0 0 20px;cursor:pointer}
ol.tl li::before{
  content:"";position:absolute;left:-20px;top:5px;width:7px;height:7px;border-radius:50%;
  background:var(--dial-face);border:1px solid var(--gold);transition:background .2s,transform .2s;
}
ol.tl li.hot::before{background:var(--gold-soft);transform:scale(1.35)}
ol.tl li.shown::before{background:var(--gold);transform:scale(1.35)}
ol.tl li .yr{font-family:var(--serif);font-size:13px;color:var(--gold);letter-spacing:.08em}
ol.tl li .ref{font-size:12.5px;margin:1px 0 3px;letter-spacing:.02em}
ol.tl li .note{font-size:11.5px;line-height:1.55;color:var(--ink-dim)}
ol.tl li .tl-price{
  margin-top:5px;font-family:var(--serif);font-size:12px;color:var(--gold);
  letter-spacing:.05em;font-variant-numeric:tabular-nums;
}
ol.tl li .tl-mfr{
  font-family:var(--sans);font-size:9px;letter-spacing:.14em;color:var(--ink-faint);
  margin-left:6px;
}
ol.tl li.current .ref::after{
  content:var(--label-current,"current");margin-left:8px;font-size:8.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--gold);border:1px solid var(--edge);padding:1px 5px;border-radius:2px;
  vertical-align:1px;
}
/* Which references have a photograph, so the rest of the lineage is discoverable
   rather than looking identical to the ones that don't. */
ol.tl li.has-photo .yr::after{
  content:var(--label-photo,"photo");margin-left:8px;font-family:var(--sans);
  font-size:8.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-faint);
  border:1px solid var(--rule);padding:1px 5px;border-radius:2px;vertical-align:1.5px;
}

.panel-close{
  position:absolute;top:20px;right:20px;z-index:1;width:28px;height:28px;
  background:var(--panel);border:1px solid var(--edge);border-radius:50%;
  color:var(--ink-dim);font-size:14px;line-height:1;cursor:pointer;
  display:grid;place-items:center;transition:border-color .2s,color .2s;
}
.panel-close:hover{border-color:var(--gold);color:var(--gold)}

/* ---------- field guide (left panel) ---------- */
#guide{
  position:fixed;z-index:9;top:0;left:0;height:100%;width:min(430px,92vw);
  background:var(--panel);backdrop-filter:blur(18px);
  border-right:1px solid var(--edge);
  transform:translateX(-101%);transition:transform .45s cubic-bezier(.22,.8,.25,1);
}
#guide.on{transform:translateX(0)}
#guide .panel-close{z-index:3}
.g-body{height:100%;overflow-y:auto;padding:26px 28px 48px}

/* The right padding keeps the section switcher clear of the close button. The guide's
   close sits above this bar (z-index below): the bar is sticky and opaque, so at
   z-index 1 the cross was painted underneath it and showed only as a grey ghost. */
.g-nav{
  position:sticky;top:-26px;z-index:2;display:flex;gap:8px;
  margin:-26px -28px 22px;padding:22px 62px 12px 28px;
  background:var(--panel);border-bottom:1px solid var(--rule);
}
.g-nav button{
  background:none;border:1px solid var(--edge);border-radius:2px;
  padding:6px 12px;cursor:pointer;color:var(--ink-dim);font-family:var(--sans);
  font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;
  transition:border-color .2s,color .2s,background .2s;
}
.g-nav button:hover{border-color:var(--gold);color:var(--gold-soft)}
.g-nav button.on{
  border-color:var(--gold);color:var(--gold-soft);
  background:color-mix(in srgb,var(--gold) 16%,transparent);
}

.g-section + .g-section{margin-top:40px;padding-top:32px;border-top:1px solid var(--rule)}
.g-section h2{
  font-family:var(--serif);font-weight:400;font-size:22px;margin:0 0 4px;letter-spacing:.04em;
}
.g-section .g-kicker{
  font-size:9.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--gold);margin:0 0 10px;
}
.g-section .g-intro{margin:0 0 24px;font-size:12.5px;line-height:1.65;color:var(--ink-dim)}

.g-term{margin-bottom:20px}
.g-swatch{
  display:inline-block;width:9px;height:9px;border-radius:50%;
  margin-right:8px;vertical-align:1px;
}
.g-line{
  display:inline-block;width:26px;margin-right:8px;vertical-align:4px;
  border-top:2px solid var(--line-maker);
}
.g-line.curiosity{border-top-width:1px;opacity:.55}
.g-term h3{
  font-family:var(--serif);font-weight:400;font-size:14.5px;margin:0 0 4px;
  letter-spacing:.03em;color:var(--gold-soft);
}
.g-term p{margin:0;font-size:12px;line-height:1.6;color:var(--ink-dim)}
.g-term .g-here{
  display:block;margin-top:6px;font-size:10px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-faint);
}
/* An arrow Latin Modern actually has — it has no ↳, which would fall back to a
   different face for that one character. */
.g-term .g-here::before{content:"→ ";color:var(--gold)}

.g-chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
.g-chips button{
  background:none;border:1px solid var(--edge);border-radius:2px;
  padding:3px 9px;cursor:pointer;color:var(--gold);font-family:var(--sans);
  font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;
  transition:background .2s,color .2s;
}
.g-chips button:hover{
  background:color-mix(in srgb,var(--gold) 16%,transparent);color:var(--gold-soft);
}
.g-chips .g-none{
  font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-faint);
  border:1px dashed var(--rule);padding:3px 9px;border-radius:2px;
}

/* ---------- fatal error ---------- */
#fatal{
  position:fixed;inset:0;z-index:20;display:grid;place-items:center;padding:30px;
  background:color-mix(in srgb,var(--page) 90%,transparent);
}
#fatal[hidden]{display:none}
#fatal .box{
  max-width:460px;border:1px solid var(--edge);padding:26px 28px;border-radius:2px;
  background:var(--panel);
}
#fatal h2{
  margin:0 0 12px;font-family:var(--serif);font-weight:400;font-size:19px;letter-spacing:.06em;
}
#fatal p{margin:0 0 12px;font-size:12.5px;line-height:1.6;color:var(--ink-dim)}
#fatal code{
  display:block;margin:0;padding:10px 12px;
  background:color-mix(in srgb,var(--ink) 8%,transparent);
  border:1px solid var(--rule);border-radius:2px;
  font-size:12px;color:var(--gold-soft);white-space:pre-wrap;
}

/* ---------- responsive ---------- */
@media (max-width:760px){
  header{padding:18px 20px 0}
  #filters{max-width:100%}
  #filters .f-count{margin-left:0;width:100%}
  footer{padding:0 20px 12px;flex-direction:column;gap:4px}
  /* The stacked footer costs a line per item, and the map's bottom inset is a fixed 46px —
     so a third line overlays the constellation rather than making room for itself. The
     notice is the one item a reader does not need: it survives in LICENSE (served at
     /LICENSE), in the View Source comment, and in every source file header, and copyright
     does not depend on a visible notice in any case. */
  .f-copy{display:none}
  /* The sheet scrolls as one column. Splitting it into a fixed head and a scrolling
     body works on a wide screen, where the head is a fraction of the height; at phone
     width the portrait alone is ~420px and the head grew to 759px inside a 545px
     sheet, putting the lineage 200px below the bottom of the screen with nothing able
     to scroll to it. */
  #drawer{
    width:100%;height:82vh;top:auto;bottom:0;border-left:none;
    border-top:1px solid var(--edge);transform:translateY(101%);
    overflow-y:auto;overscroll-behavior:contain;
  }
  #drawer.on{transform:translateY(0)}
  .d-body{overflow:visible;flex:none;padding-bottom:36px}
  .d-head{flex:none}
  /* A 4:3 frame is a quarter of a phone screen before anything else is read. */
  .d-portrait .p-frame{aspect-ratio:auto;height:30vh}
  /* In flow and sticky, so it survives the sheet scrolling under it. Scoped to the
     drawer: it is the drawer that becomes one scrolling column here. #guide keeps a
     separately-scrolling .g-body, so a sticky close would have nothing to stick to —
     it landed in the top-left corner instead of the top-right, and read as a stray
     mark rather than as a way out of the panel. */
  #drawer .panel-close{position:sticky;top:10px;align-self:flex-end;margin:0 14px -14px 0}
  #guide{
    width:100%;height:82vh;top:auto;bottom:0;border-right:none;
    border-top:1px solid var(--edge);transform:translateY(101%);
  }
  #guide.on{transform:translateY(0)}

  /* One toggle instead of a pair. The button that shows is the language you are *not*
     reading, so tapping it always switches — and the label is what you will get, which is
     the only thing a single button can honestly say.

     It works by hiding the active one, so there is no second markup path and no new state:
     buildLangSwitch() already marks the current language, and the survivor is the target by
     construction. The two corner rules have to be restated at :first-child/:last-child
     specificity, or the segmented radii from above keep winning and the lone button renders
     with one square side and no left border. */
  #lang button.on{display:none}
  #lang button:first-child,#lang button:last-child{
    border-radius:2px;border-left-width:1px;
  }
  /* Not hidden any more: on touch this card *is* the way to read a link or a house,
     since neither can be selected. Wider and a touch larger, since it is tapped open
     deliberately rather than grazed with a cursor. */
  #tip{width:min(300px,86vw);font-size:12px}
}

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