/* Layout for the tokenizer-map network page, keyed by the mode class
   shared/Modes.js puts on <body> (mode-presentation | mode-desktop |
   mode-mobile). Only layout lives here; colours and fonts stay in src/.
   Three distinct treatments: presentation is the base rule below —
   #network fills the full viewport, edge to edge; mode-desktop insets it
   with side margins so the graph doesn't slam the viewport edge the way
   presentation deliberately does; mode-mobile stacks #detail_card
   full-width below #network instead of floating it as an overlay. One
   exception carried here rather than in src/: #detail_card's shown/hidden
   state is the generic `.hidden { display: none; }` class below —
   src/initUI.js starts the card with that class, and Task 5's
   src/network.js toggles it by adding/removing the class, never by
   writing an inline `display` style. */

body {
  margin: 0;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  position: relative; /* anchors #detail_card's desktop/presentation overlay */
}

.hidden {
  display: none;
}

/* ---- The header block (design pass 2, map-legend): title, intro copy,
   the meta line, and the legend — src/initUI.js builds these, in this
   order, as document.body's first children, so DOM order alone (body is
   `flex-direction: column` above) puts the header above #network in every
   mode; the mode-specific rules at the bottom of this file only adjust
   what's visible/wraps within it, not its position. Colours/fonts for the
   text and the legend's swatches are inline (src/initUI.js, src/Main.js);
   only this row/gap/wrap shape lives here. ---- */
#map_header {
  padding: 10px 16px 6px;
  box-sizing: border-box;
}
#map_title {
  margin: 0 0 4px;
  font-size: 20px;
}
#map_intro {
  margin: 0 0 6px;
  max-width: 760px;
  font-size: 13px;
}
#map_meta {
  display: block;
  margin: 0 0 8px;
}
#map_legend {
  display: flex;
  gap: 6px 16px;
  /* Presentation/desktop have room for one row; body.mode-mobile below
     switches this to wrap, since 5 items plus #detail_card's full-width
     stack (mobile's own rule further down) leaves little spare width. */
  flex-wrap: nowrap;
}
.legend_item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
}
.legend_swatch {
  display: inline-flex;
  align-items: center;
}

#status_line {
  padding: 6px 10px;
  box-sizing: border-box;
  min-height: 1em;
}

#network {
  flex: 1 1 auto;
  width: 100%;
  box-sizing: border-box;
  /* Overrides the flex item default of `min-height: auto`, which — without
     a `height` set here — otherwise refuses to shrink below its OWN
     content's intrinsic size. Since that content is the svg src/Main.js's
     renderNetwork just injected (sized from a getBoundingClientRect() of
     this very element), the two would otherwise reinforce each other into
     growing past the viewport (fix round 1 finding: 862px page at
     1280x800). `min-height: 0` lets #network actually take only its real
     flex-allotted share of body's height. */
  min-height: 0;
}

/* ---- The attribution footer (fix round 1, user request), below #network
   in every mode — src/initUI.js appends it right after #network, and DOM
   order plus body's flex-column stacking (no p5-inserted canvas to work
   around, per src/initUI.js's own comment) puts it there without any
   mode-specific rule needed. Colours/font-size are inline (src/initUI.js);
   only this spacing lives here. ---- */
#map_footer {
  margin: 0;
  padding: 6px 16px 10px;
  box-sizing: border-box;
}

#detail_card {
  position: absolute;
  top: 40px;
  right: 12px;
  width: 280px;
  max-width: calc(100% - 24px);
  box-sizing: border-box;
  padding: 12px;
  z-index: 10;
}

/* ---- The detail card's injected html (src/Main.js's showVocabularyCard /
   showModelCard) — plain typography for the raw <h3>/<p>/<a> it writes. ---- */
#detail_card h3 {
  margin: 0 0 6px;
  font-size: 15px;
}
#detail_card p {
  margin: 4px 0;
}
#detail_card a {
  color: rgb(40, 90, 190);
}

/* ---- The script-distribution bar (src/Main.js's scriptsBarHtml, a
   tier-file vocabulary card only) — each .script-segment's width and
   background colour are inline (per-vocabulary data), so only the bar's
   static shape lives here. ---- */
/* The scripts-vs-languages explanation (src/Main.js's SCRIPT_NOTE) opens as
   a modal from the `?` beside this heading — prose that long reads badly in
   a hover bubble and never appears at all on a touch screen. The heading
   itself carries no affordance; the `?` is the whole invitation. */
#detail_card .script-heading {
  display: flex;
  align-items: center;
  gap: 6px;
}
#detail_card .script-help {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  padding: 0;
  line-height: 1;
  border: 1px solid rgb(170, 170, 170);
  border-radius: 50%;
  background: rgb(255, 255, 255);
  color: rgb(90, 90, 90);
  font-size: 11px;
  cursor: pointer;
}
#detail_card .script-caption {
  margin: 6px 0 2px;
  font-size: 11px;
  color: rgb(120, 120, 120);
}
#detail_card .not-vendored {
  font-size: 12px;
  font-style: italic;
  color: rgb(120, 120, 120);
}
#detail_card .script-bar {
  display: flex;
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
  background: rgb(230, 230, 230);
  margin: 0 0 6px;
}
#detail_card .script-segment {
  height: 100%;
}
#detail_card .script-note {
  font-size: 12px;
  color: rgb(120, 120, 120);
}

/* The writing-systems modal (built once in src/initUI.js, hidden until the
   card's `?` opens it). Colours are inline there, per this page's rule;
   only the overlay geometry lives here. */
#script_modal {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
#script_modal.hidden {
  display: none;
}
#script_modal_panel {
  max-width: 420px;
  padding: 18px 20px;
  line-height: 1.45;
}
#script_modal_title {
  margin: 0 0 8px;
}
#script_modal_text {
  margin: 0 0 14px;
}

/* ---- The D3-rendered network svg (src/Main.js's renderNetwork). Every
   node is clickable; evidence-tier classes style vocabulary hubs (solid
   fill = file, dashed stroke = documented, light-grey fill = unpublished) —
   see evidenceClass() in src/Main.js. Model dots and the unmapped cluster
   share one muted fill so hubs read as the graph's real content. ---- */
#network svg {
  display: block;
}
.hub circle,
.model,
.unmapped-dot,
.hub-label,
.hub-size,
.hub-leader {
  cursor: pointer;
}
.hub.evidence-file circle {
  fill: rgb(58, 111, 216);
  stroke: none;
}
.hub.evidence-documented circle {
  fill: rgba(58, 111, 216, 0.15);
  stroke: rgb(58, 111, 216);
  stroke-width: 1.5px;
  stroke-dasharray: 4, 3;
}
/* .hub-vendored-ring: the dark ring src/Main.js draws on top of a
   vendored hub's own fill (its colour, HUB_VENDORED_RING_COLOR, is set
   inline per this file's colour-in-JS convention — this rule is only the
   one non-colour behaviour a stylesheet can't express inline: it must
   never intercept a click meant for the hub circle it sits on top of. */
.hub-vendored-ring {
  pointer-events: none;
}
/* fill-needed round: a hollow (fill:none) unpublished tier read as faint and
   unfinished on a white ground, not a deliberate category, next to the
   solid-blue verified tier and the dashed-but-tinted documented tier (both
   of which already carry SOME fill). rgb(238,238,238) gives it a light grey
   fill of its own — still clearly demoted below verified (which stays
   fully saturated) and below documented's blue tint, but no longer reading
   as empty. The stroke, documented and browsable/verified tiers are
   untouched. */
.hub.evidence-unpublished circle {
  fill: rgb(238, 238, 238);
  stroke: rgb(150, 150, 150);
  stroke-width: 1.5px;
}
/* text-anchor is NOT set here for .hub-label/.hub-size: layoutLabelColumns
   (src/network.js) computes 'start' or 'end' per hub, depending which
   label column it landed in (right/left of the layout's centre), and
   src/Main.js's renderNetwork sets it as a presentation attribute — a CSS
   rule here would always win over that (SVG presentation attributes lose
   to any matching stylesheet rule) and flatten every label back to one
   fixed anchor. .unmapped-label has no such per-node variation, so it
   keeps a plain `middle` below. */
.hub-label,
.hub-size,
.unmapped-label {
  font-family: sans-serif;
}
/* hover-preview round: .hub-label/.hub-size (a labeled hub's own name/count
   text — src/network.js's layoutLabelColumns/positionMinimalLabel) need real
   pointer events so hovering or clicking the label previews/pins exactly
   like hovering the hub itself (src/Main.js's renderNetwork wires the
   identical handlers to both, in both directions). .unmapped-label is just
   the "N models not yet mapped" caption, not a link to anything — it alone
   keeps the `pointer-events: none` every text label had before this round. */
.hub-label,
.hub-size {
  pointer-events: auto;
}
.unmapped-label {
  pointer-events: none;
}
.hub-label {
  font-size: 11px;
  font-weight: 600;
  fill: rgb(40, 40, 40);
}
.hub-size {
  font-size: 10px;
  fill: rgb(110, 110, 110);
}
.unmapped-label {
  font-size: 11px;
  text-anchor: middle;
  fill: rgb(120, 120, 120);
}
/* .label-halo: a white outline behind a label's glyphs (fix round 3,
   controller's mobile screenshot: the 'minimal' label plan's text —
   src/network.js's positionMinimalLabel places it directly above/below its
   own hub — was rendering straight over that hub's own saturated fill, or
   over a stray model dot, and reading as unreadable dark-on-dark; the
   unmapped-cluster caption has the same problem where it lands near
   dots). The standard SVG halo idiom: stroke behind the fill (paint-order)
   in a fixed width, wide enough to clear a single glyph stroke's own
   antialiasing without visibly thickening the letterforms. Added only in
   the 'minimal' plan (src/Main.js's renderNetwork, conditionally on the
   class string it builds per label/the caption) — the 'columns' plan's
   labels sit in clear space beside the network, where this would only be
   visual noise, so they never get this class at all. White (not the
   colour-in-JS convention this file's other rules follow) because it is a
   fixed, purely presentational outline with no per-vocabulary/per-model
   data behind it, same as the evidence-tier rules above; the existing
   .hub-label/.hub-size/.unmapped-label `fill` rules are untouched and
   still supply the glyphs' own colour. */
.label-halo {
  stroke: #fff;
  stroke-width: 3;
  paint-order: stroke fill;
}
.model-link {
  stroke: rgb(210, 210, 210);
  stroke-width: 1px;
}
/* .hub-leader: the four-point elbow from a labeled hub's edge out to its
   label-column anchor (src/network.js's elbowFor — the horizontal-arrival
   round added the p1->p2->p3 bend so the final leg into the text runs flat
   rather than diagonal), drawn as an svg polyline by src/Main.js's
   renderNetwork — the same faint grey as .model-link, since both are "line,
   not content". `fill: none` matters here in a way it didn't for the old
   plain <line>: a <polyline>'s default fill is black, and without this it
   would shade in the polygon its points and implicit closing edge
   describe. */
.hub-leader {
  fill: none;
  stroke: rgb(210, 210, 210);
  stroke-width: 1px;
}
/* .model's fill is NOT set here — src/Main.js's renderNetwork sets it
   inline to MODEL_DOT_COLOR (the amber model-dot color family; colours
   live in JS, per this file's header comment). Opacity is layout/
   presentation-weight, not part of that color choice, so it stays here. */
.model {
  opacity: 0.55;
}
.unmapped-dot {
  fill: rgb(180, 180, 180);
}

/* ---- hover-preview round: a light, generic acknowledgement for whichever
   node is currently hovered — applied by src/Main.js's applyHubHighlight
   (a hub, keyed off its `<g class="hub">`) or directly on a model/unmapped
   dot's own `<circle>`. A hub gets a modest stroke-width bump rather than a
   brightness filter, deliberately: this page's fill-needed round gave the
   unpublished tier a light rgb(238,238,238) fill, which a brightness filter
   would barely move against the white page background; a plain stroke reads
   consistently across all three evidence tiers instead (thinner and greyer
   than .hub-vendored-ring's own stroke below, so it never competes with
   that ring's own, stronger, "you can open this one" cue — the two are
   separate circles layered independently, so nothing here touches the
   ring). Model/unmapped dots have no such near-white-fill case, so a
   brightness bump suits their own solid amber/grey fills fine. ---- */
.hub.node-hover circle {
  stroke: rgb(90, 90, 90);
  stroke-width: 2px;
}
.model.node-hover,
.unmapped-dot.node-hover {
  filter: brightness(1.3);
}
/* .hub-leader-hover: the associated leader line, darkened while its hub,
   label, or the line itself is hovered (src/Main.js's applyHubHighlight) —
   still clearly a leader line, just enough contrast over the base
   rgb(210,210,210) to help the eye trace text back to circle across a busy
   field. */
.hub-leader.hub-leader-hover {
  stroke: rgb(140, 140, 140);
}

/* ---- Presentation: the network fills the viewport, so the header's own
   intro paragraph (a full sentence of copy) is dropped to save vertical
   space for the graph — title, meta and legend stay visible in every
   mode, only #map_intro is presentation-only. `height: 100vh` (not just
   the base rule's `min-height`) gives body a DEFINITE size in this mode,
   which #network's `flex: 1 1 auto` needs to correctly claim "the actual
   remaining space below the header" instead of falling back to sizing
   itself off its own about-to-be-inserted content (this file's #network
   comment has the full mechanism); `overflow-y: hidden` is a pixel-
   rounding safety net on top of that, not the fix itself — real content
   never needs to exceed this box once #network measures correctly. ---- */
body.mode-presentation {
  height: 100vh;
  overflow-y: hidden;
}
body.mode-presentation #map_intro {
  display: none;
}

/* ---- Desktop stand-alone: the graph insets from the viewport edge ---- */
body.mode-desktop #network {
  width: auto;
  margin: 16px 32px;
}

/* ---- Mobile stand-alone: the card stacks full-width below, not floats;
   the legend wraps instead of overflowing the narrow viewport. ---- */
body.mode-mobile #detail_card {
  position: static;
  width: 100%;
}
body.mode-mobile #map_legend {
  flex-wrap: wrap;
}
