:root{
  --bg: #0b0d10;
  --panel: #10141a;
  --panel2: #0f1318;
  --text: #e6e6e6;
  --muted: #a8b3c2;
  --border: #222b36;
  --accent: #66a3ff;
  --row-hover: rgba(255,255,255,0.04);
  --row-selected: rgba(102,163,255,0.18);
  --row-shared-highlight: rgba(255,196,102,0.18);
  --row-shared-highlight-strong: rgba(255,196,102,0.4);
  --series-1: #3987e5;
  --series-2: #d95926;
  --series-3: #199e70;
  --series-4: #c98500;
  --surface-uncovered: #222b36;
  /* Only used where the topbar is pinned over scrolling content (phone widths):
     a stronger edge than --border plus a shadow, so the bar reads as a layer
     above the page. Both are theme-specific -- a shadow tuned for dark mode
     turns into a grey smear on white. */
  --border-strong: #3a4757;
  --topbar-shadow: 0 4px 16px rgb(0 0 0 / 0.75), 0 1px 3px rgb(0 0 0 / 0.6);
}

:root[data-theme="light"]{
  --bg: #ffffff;
  --panel: #f4f6f8;
  --panel2: #eef1f4;
  --text: #16191d;
  --muted: #55606e;
  --border: #d7dde3;
  --accent: #2464c7;
  --row-hover: rgba(0,0,0,0.04);
  --row-selected: rgba(36,100,199,0.14);
  --row-shared-highlight: rgba(199,130,36,0.16);
  --row-shared-highlight-strong: rgba(199,130,36,0.4);
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;
  --surface-uncovered: #d7dde3;
  --border-strong: #b3bdc7;
  --topbar-shadow: 0 4px 16px rgb(0 0 0 / 0.22), 0 1px 3px rgb(0 0 0 / 0.16);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body{
  margin:0;
  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Arial, "Apple Color Emoji", "Segoe UI Emoji";
  font-size: 18px;
  background: var(--bg);
  color: var(--text);
}

.topbar{
  height: 48px;
  display:flex;
  align-items:center;
  justify-content:space-between;
  padding: 0 14px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}

.brand{ font-weight: 600; display: flex; align-items: center; }
.brand-title{ white-space: nowrap; }
.brand-meta{ display: contents; }

/* Subtle: same color/weight as plain text, just a pointer cursor -- unlike
   .topbar-link (About), which is meant to visually announce itself as a link. */
.brand-title-link{ cursor: pointer; color: inherit; text-decoration: none; }

.sidebar-toggle{
  display: none;
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  width: 34px;
  height: 34px;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  margin-right: 12px;
  flex: 0 0 auto;
}
.sidebar-toggle:hover{ border-color: var(--accent); }
.topbar-link {
  font-weight: normal;
  font-size: 16px;
  margin-left: 25px;
  color: var(--accent);
  text-decoration: none;
  vertical-align: middle;
}
a.topbar-link:hover { text-decoration: underline; }

/* about.html's topbar has 3 direct children (.brand, .topnav, .about-back-link)
   instead of index.html's 2 -- order restores the original visual arrangement
   (title, back-link, then dropdown pushed right) on desktop; see the narrow
   breakpoint below for the mobile swap. */
.about-topbar{ flex-wrap: nowrap; }
.about-topbar .brand{ order: 1; }
.about-topbar .about-back-link{ order: 2; margin-right: auto; }
.about-topbar /* Search + transliteration + theme, kept together so they wrap as one unit.
   Transparent to layout on wide screens: it lays its children out with the
   same gap the nav uses, so the bar looks exactly as it did when they were
   loose children of .topnav. */
.navTools{
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex: 1 1 auto;
}

.topnav{ order: 3; }

.topnav{ display:flex; align-items:center; gap:10px; color: var(--muted); }
.navlink{ color: var(--accent); text-decoration:none; }
.navlink:hover{ text-decoration:underline; }
.sep{ opacity:.6; }
.navlabel{ font-size: 15px; }
#schemeSelect{
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 4px 8px;
  border-radius: 6px;
}

.search-wrap{
  position: relative;
  display: inline-flex;
  align-items: center;
}
.search-wrap-icon{
  position: absolute;
  left: 8px;
  width: 13px;
  height: 13px;
  opacity: 0.5;
  pointer-events: none;
}
.search-input {
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 4px 8px 4px 84px;
  border-radius: 6px;
  width: 320px;
  font-family: inherit;
}
.search-input:focus {
  outline: 1px solid var(--accent);
  border-color: var(--accent);
}
.search-wrap:focus-within .search-wrap-icon{
  opacity: 0.8;
}
.exactToggle{
  position: absolute;
  left: 27px;
  top: 50%;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  padding: 2px 7px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: 11px;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  background: var(--panel2);
}
.exactToggle:hover{
  border-color: var(--accent);
}
.exactToggle:has(input:checked){
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}
/* Visually hidden but still focusable/keyboard-toggleable -- the pill itself
   (the <label>) is the whole visible control, not a checkbox-plus-text. */
.exactToggle input{
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

.theme-toggle{
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  padding: 4px 10px;
  border-radius: 6px;
  cursor: pointer;
  font-size: 15px;
  line-height: 1.3;
  white-space: nowrap;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.theme-toggle:hover{ border-color: var(--accent); }

.layout{
  height: calc(100% - 48px);
  display:grid;
  grid-template-columns: var(--sidebar-width, 400px) 6px 1fr;
}

.sidenav{
  border-right: 1px solid var(--border);
  background: var(--panel2);
  padding: 12px;
  overflow:auto;
  /* Lets .allRow respond to the sidebar's own width rather than the viewport's --
     the sidebar is user-draggable (--sidebar-width, see app.js), so it can get
     narrow enough to wrap that row at any viewport size. See the container query
     below .includeOrphansToggle. */
  container-type: inline-size;
  container-name: sidenav;
}

.sidebar-resizer{
  cursor: col-resize;
  background: transparent;
  position: relative;
}
.sidebar-resizer::after{
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 2px;
  width: 2px;
  background: var(--border);
}
.sidebar-resizer:hover::after,
.sidebar-resizer.dragging::after{
  background: var(--accent);
}

.sidebar-backdrop{
  display: none;
}

.panelTitle{ font-weight: 600; margin-bottom: 6px; }
.panelTitleLink{ cursor: pointer; }
/* A sub-domain heading under its domain, on the author axis grouped by
   category. Indented and lighter so the two levels read as a hierarchy
   rather than as two peer headings; its works indent with it. */
.groupSubHeading{
  margin-left: 18px;
  font-weight: 500;
  font-size: 0.85em;
  color: var(--muted);
  letter-spacing: 0.02em;
  border-left: 2px solid var(--border-strong);
  padding-left: 10px;
}
.groupSubHeading + .pageList{ margin-left: 30px; }
.hint{ font-size: 14px; color: var(--muted); margin-bottom: 10px; }
.expandAllButton{
  font-size: 13px;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  cursor: pointer;
}
.expandAllButton:hover{ border-color: var(--accent); color: var(--accent); }

/* The overview's caption and its buttons on one line, wrapping on narrow
   screens rather than pushing the buttons off the edge. */
.overviewControls{
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin-bottom: 10px;
}

/* --- two-axis browsing (see the header comment in app.js) --- */

/* The axis switch. Two peers, styled as a segmented control rather than a
   dropdown, so neither reads as the default with the other as a filter. */
.axisSwitch{ display: inline-flex; border: 1px solid var(--border); border-radius: 6px; overflow: hidden; }
.axisButton{
  font-size: 13px;
  padding: 3px 10px;
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.axisButton + .axisButton{ border-left: 1px solid var(--border); }
.axisButton:hover{ color: var(--accent); }
.axisButton.active{ background: var(--accent); color: #fff; }

/* The secondary-grouping pill. Same look as .exactToggle but laid out inline in
   the nav rather than absolutely positioned inside the search box. */
.groupToggle{
  display: inline-flex;
  align-items: center;
  padding: 2px 9px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: 11px;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  background: var(--panel2);
}
.groupToggle:hover{ border-color: var(--accent); }
.groupToggle:has(input:checked){
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}
.groupToggle input{
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Work lists */
.pageList{ list-style: none; margin: 0 0 6px 0; padding: 0; }
.pageLi{ padding: 2px 0; line-height: 1.5; }
.pageLink{ color: var(--text); text-decoration: none; }
.pageLink:hover{ color: var(--accent); text-decoration: underline; }

/* Format/structure markers on a work: TOC, PDF, text. Small and quiet -- they
   qualify an entry rather than competing with its title. */
.badge{
  font-size: 11px;
  padding: 0 5px;
  margin-right: 4px;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--muted);
  white-space: nowrap;
}
.badgeWarn{ border-color: var(--accent); color: var(--accent); }
/* A format badge that opens that format on the site. Text and PDF are two
   distinct documents, so each is its own link; the title goes to the metadata
   page instead. Kept quiet until hover -- a row can carry two of these and
   they should not compete with the title. */
a.badgeLink{
  cursor: pointer;
  text-decoration: none;
  display: inline-block;
}
a.badgeLink:hover, a.badgeLink:focus-visible{
  color: var(--accent);
  border-color: var(--accent);
  text-decoration: none;
}

/* The authorless bucket is a real member of the author axis, not an error --
   muted to read as "no attribution recorded" rather than flagged as a problem.
   Most of these are anonymous by convention (Vedas, Upanisads, Puranas,
   stotras), not works whose author was lost. */
.absentValue .title, .panelTitle.absentValue{ font-style: italic; color: var(--muted); }
.hint-touch{ display: none; }
/* Coarse-pointer devices (touch, no hover) get the long-press hint instead of
   the Option/Shift-click one -- keyed on pointer capability, not viewport width,
   so it also applies to touch laptops/tablets at wide viewports. */
@media (hover: none) and (pointer: coarse){
  .hint-desktop{ display: none; }
  .hint-touch{ display: inline; }
}
.loadingHint{ font-size: 14px; color: var(--muted); padding: 10px 4px; }
.allRow{
  display:flex;
  flex-wrap: wrap;
  align-items:center;
  justify-content: space-between;
  gap: 6px;
  padding: 3px 2px;
  border-radius: 6px;
  cursor:pointer;
}
.allRow:hover{ background: var(--row-hover); }
.allRow.selected{ background: var(--row-selected); }
/* "All" itself must never shrink/truncate -- unlike nested category rows,
   which legitimately compete with a long title for space, this row's title
   is a fixed 3-character word and the thing actually short on space is
   everything else (toggle pill, stats). DOM order is All / stats / toggle
   (so the toggle wraps to its own line last on mobile, see the narrow
   breakpoint below), but visually the toggle sits right before stats on
   desktop, both pushed together to the row's right edge -- `order` plus
   the toggle's own margin-left:auto (below) decouples visual placement
   from DOM position. */
.allRow .title.topLevel{ flex: 0 0 auto; order: 1; }
.allStats{
  order: 3;
  padding-left: 10px;
  opacity: 0.7;
}
.allDivider{ border: none; border-top: 1px solid var(--border); margin: 6px 0 10px; }
.includeOrphansToggle{
  display: inline-flex;
  align-items: center;
  gap: 5px;
  order: 2;
  margin-left: auto;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--border);
  font-size: 13px;
  font-weight: normal;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  background: var(--panel2);
  flex: 0 0 auto;
}
.includeOrphansToggle input{
  margin: 0;
  width: 13px;
  height: 13px;
}
.includeOrphansToggle:hover{
  border-color: var(--accent);
}
.includeOrphansToggle:has(input:checked){
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}

/* Once the sidebar is too narrow to fit All + stats + toggle on one line, use the
   same arrangement the phone breakpoint uses: All + stats stay together on line 1
   and the toggle drops to its own line below, right-aligned. Keyed to the
   sidebar's inline size, not the viewport's, because the sidebar is
   drag-resizable -- at a wide viewport with a dragged-in sidebar a viewport-only
   rule leaves desktop's order in place, wrapping the *stats* to line 2 and
   stranding them under the pill instead.

   Threshold is 450px of *content*: a container query resolves against the
   container's content box, and .sidenav is border-box with 12px side padding, so
   this corresponds to a 474px sidebar as measured while dragging -- just under the
   475px at which the desktop order fits on one line.

   Between the wrap point and the threshold (~460-470px outer) all three still fit
   on one line, in the swapped order All < stats < toggle. Left as-is: it reads
   fine, and the alternatives (forcing the pill onto its own row with
   flex-basis:100%) stretch the pill across the full row instead of letting it hug
   its text. */
@container sidenav (max-width: 450px){
  .allStats{ order: 2; }
  .includeOrphansToggle{ order: 3; margin-left: auto; }
}

.mainpane{
  overflow:auto;
}

.content{
  padding: 14px;
}

.tree .row{
  display:flex;
  align-items:center;
  gap: 6px;
  padding: 3px 2px;
  border-radius: 6px;
}
.tree .row:hover{ background: var(--row-hover); }
.tree .row.selected{ background: var(--row-selected); }
.tree .row.shared-highlight{ background: var(--row-shared-highlight); }

.toggleArrow{
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
  user-select:none;
  color: var(--muted);
  font-size: 1.6rem;
}
/* Top-level leaf rows (the whole author axis): just enough gutter to keep
   titles off the edge, and no pointer, since there is nothing to click.
   Nested leaves keep their bullet -- see renderSidebarNode. */
.toggleArrowLeaf{
  width: 8px;
  cursor: default;
}
/* A bullet is a position marker, not a control: only nodes with children get a
   click handler, so only they get the pointer. */
.toggleArrowStatic{ cursor: default; }
.title{
  cursor:pointer;
  user-select:none;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
  flex: 1 1 auto;
  min-width: 0;
}
.title.topLevel, .small.topLevel{
  font-weight: bold;
}
.tree .row .small{
  flex-shrink: 0;
  white-space: nowrap;
}
.indent{ margin-left: 18px; }

.content .block{
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 12px;
  margin-bottom: 10px;
}

.content .block.collapsible{ padding: 10px 12px; }
.content .block.collapsible > summary{
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
}
.content .block.collapsible > summary::-webkit-details-marker{ display: none; }

/* Sticky section headers (about.html): while a top-level section is open, pin its
   own summary to the top of the scroll area so the reader always knows which
   section they're in and can close it by clicking the same bar again -- the same
   affordance sticky category headers give on index.html (see positionStickyHeaders
   in app.js).

   Only *open* summaries stick, and only top-level ones (:not(.block.collapsible
   .block.collapsible) > summary): a closed summary is already in normal flow, and
   pinning nested disclosures too -- the audit tree runs several deep -- would
   stack a pile of bars down the viewport.

   Negative margins cancel .block's 10px 12px padding so the bar spans the full
   block width and fully masks the prose scrolling beneath it; the matching
   positive padding puts the text back where it was. z-index keeps it above
   sibling content but below index.html's sticky topbar (1000). */
.content .block.collapsible:not(.block.collapsible .block.collapsible)[open] > summary{
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--panel);
  margin: -10px -12px 0;
  padding: 10px 12px;
  border-radius: 10px 10px 0 0;
}

/* Audit findings (about.html "Data Quality" section, generated by
   pipeline/audit.py): every finding row -- whether it has results (a
   <summary>) or not (a plain <div>) -- shares the same .audit-summary
   element type/box model, so the text always lines up regardless of
   whether a triangle is drawn. A finding with results shows a custom
   disclosure triangle (same construction as .block.collapsible above) in
   place of a bullet; a zero-count finding's ::before is left transparent,
   acting as a same-size invisible spacer instead. */
.audit-item{ list-style: none; }
.audit-summary{
  display: flex;
  align-items: center;
}
.audit-summary:not(.audit-summary-empty){ cursor: pointer; }
.audit-summary::-webkit-details-marker{ display: none; }
.audit-summary::before{
  content: "";
  display: inline-block;
  flex-shrink: 0;
  width: 0.6em;
  height: 0.6em;
  margin-right: 10px;
  border-right: 2px solid transparent;
  border-bottom: 2px solid transparent;
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.audit-summary:not(.audit-summary-empty)::before{
  border-right-color: var(--muted);
  border-bottom-color: var(--muted);
}
details[open] > .audit-summary::before{ transform: rotate(45deg); }
.content .block.collapsible > summary h2,
.content .block.collapsible > summary h3{ margin: 0; }
.content .block.collapsible > summary::before{
  content: "";
  display: inline-block;
  flex-shrink: 0;
  width: 0.6em;
  height: 0.6em;
  margin-right: 10px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(-45deg);
  transition: transform 0.15s ease;
}
.content .block.collapsible[open] > summary::before{ transform: rotate(45deg); }
.content .block.collapsible h3{ margin: 14px 0 4px; }
.content .block.collapsible h3:first-of-type{ margin-top: 14px; }
.content .block.collapsible p{ margin: 4px 0 10px; }

.content .block.collapsible .block.collapsible{
  background: none;
  border: none;
  padding: 10px 0 0;
  margin-bottom: 0;
}

.sticky-header{
  position: sticky;
  background: var(--panel);
  padding: 6px 4px;
  margin: 0 -4px;
}

/* Native fragment scrolling (a pasted #hash, before anchor-details.js runs, or
   any jump it does not intercept) puts the target flush against the top of its
   scroll container -- underneath the pinned topbar and any sticky section
   header. anchor-details.js measures the real offset; this is the floor that
   keeps the heading legible when it doesn't. */
:target,
.collapsible > summary:has(:target),
.content h2, .content h3 { scroll-margin-top: calc(var(--topbar-h, 0px) + 60px); }

.content a{ color: var(--accent); text-decoration:none; }
.content a:hover{ text-decoration:underline; }

/* === External link icon === */
a[target="_blank"]::after{
  content: "";
  display: inline-block;
  width: 0.65em;
  height: 0.65em;
  margin-left: 0.15em;
  vertical-align: middle;
  position: relative;
  top: -0.05em;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 9 L9 1 M5 1 L9 1 L9 5' stroke='black' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 9 L9 1 M5 1 L9 1 L9 5' stroke='black' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.catLinkArrow{
  display: inline-block;
  margin-left: 4px;
  opacity: 0.6;
  vertical-align: middle;
}
.catLinkArrow:hover{ opacity: 1; }

.content ul {
  list-style: none;
  padding: 0;
  margin: 8px 0 0 12px;
}
.content li {
  margin-bottom: 4px;
}

.subpageList {
  margin: 4px 0 4px 18px;
  padding-left: 10px;
  border-left: 1px solid var(--border);
}

.pageRow{
  display: flex;
  align-items: center;
  gap: 4px;
  border-radius: 6px;
}
.pageRow.shared-highlight{ background: var(--row-shared-highlight); }
.pageRowMain{
  min-width: 0;
  /* The link text/meta reads as sitting slightly below the magnifying-glass
     icon's true visual center (font line-box asymmetry) -- nudge it and the
     disclosure triangle (below) up together to line up with the icon. */
  transform: translateY(-1px);
}
.subpageToggleGroup{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 0;
  cursor: pointer;
}
.subpageToggle{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  transform: translateY(1px);
}
/* Filled-triangle SVG mask (rather than the Unicode ▸/▾ glyphs, which
   carry a lot of built-in whitespace inside their own em-box at any
   font-size) -- fills its box tightly, so a bigger visible glyph doesn't
   also mean a bigger (row-height-inflating) box. Points right by default,
   rotates to point down when expanded -- one shape, no separate asset. */
.triangleIcon{
  display: inline-block;
  width: 12px;
  height: 12px;
  background-color: currentColor;
  opacity: 0.7;
  transition: transform 0.1s ease;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.5 1 L8 5 L2.5 9 Z'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.5 1 L8 5 L2.5 9 Z'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}
.triangleIcon.expanded{
  transform: rotate(90deg);
}

.small{ font-size: 14px; color: var(--muted); }

/* Elements with a `title` attribute get a long-press popup instead (see
   initLongPressTooltips in app.js) -- suppress iOS/Safari's native
   long-press text-selection callout and copy/search context menu so it
   doesn't show up alongside our own popup. .selectable-title opts an
   element back into normal text selection (e.g. #dataUpdated, whose text
   is itself useful to copy) while still getting the long-press popup. */
[title]:not(.selectable-title){
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.touch-tooltip{
  position: fixed;
  z-index: 3000;
  max-width: min(80vw, 320px);
  transform: translate(-50%, -100%) translateY(-10px);
  background: var(--panel2);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 13px;
  line-height: 1.4;
  white-space: pre-line;
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
  pointer-events: none;
}

.ocrBadge, .indexBadge{
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: #a87a2b;
  background: rgba(199,130,36,0.16);
  border: 1px solid rgba(199,130,36,0.35);
  border-radius: 4px;
  padding: 1px 6px;
  margin-right: 6px;
  white-space: nowrap;
}
:root[data-theme="light"] .ocrBadge, :root[data-theme="light"] .indexBadge{
  color: #8a5a10;
}

.multiCatBtn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  padding: 0;
  color: inherit;
  opacity: 0.5;
  background: none;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}
.multiCatBtn:hover{
  opacity: 1;
}
.pageRow.shared-highlight .multiCatBtn{
  opacity: 1;
  background: var(--row-shared-highlight-strong);
}
.searchIcon{
  display: inline-block;
  width: 12px;
  height: 12px;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='4.2' cy='4.2' r='3.2' stroke='black' stroke-width='1.2' fill='none'/%3E%3Cpath d='M6.6 6.6 L9.2 9.2' stroke='black' stroke-width='1.2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='4.2' cy='4.2' r='3.2' stroke='black' stroke-width='1.2' fill='none'/%3E%3Cpath d='M6.6 6.6 L9.2 9.2' stroke='black' stroke-width='1.2' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

.sourceIndexLinks{
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 6px;
}
.sourceIndexLink{
  display: inline-flex;
  align-items: center;
  gap: 3px;
  opacity: 0.5;
}
.sourceIndexLink:hover{
  opacity: 1;
}
.scanIcon{
  display: inline-block;
  width: 12px;
  height: 12px;
  background-color: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.5' y='0.5' width='7' height='9' rx='0.6' stroke='black' stroke-width='1' fill='none'/%3E%3Cpath d='M3.2 3.2 H6.8 M3.2 5 H6.8 M3.2 6.8 H5.4' stroke='black' stroke-width='0.8' stroke-linecap='round'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 10 10' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.5' y='0.5' width='7' height='9' rx='0.6' stroke='black' stroke-width='1' fill='none'/%3E%3Cpath d='M3.2 3.2 H6.8 M3.2 5 H6.8 M3.2 6.8 H5.4' stroke='black' stroke-width='0.8' stroke-linecap='round'/%3E%3C/svg%3E");
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
}

/* Mid-size: topbar wraps to 2 rows, but sidebar stays visible in the grid
   (no hamburger/off-canvas -- that's reserved for the narrower tier below). */
@media (max-width: 1100px){
  .topbar{ flex-wrap: wrap; height: auto; row-gap: 6px; padding: 8px 10px; }
  .brand{ flex: 1 1 100%; align-items: baseline; column-gap: 10px; }

  /* about.html only: title + dropdown share row 1 (title shrinks to fit,
     dropdown pushed to the row's far right); "Back to tree" moves to its
     own row 2 below, left-aligned -- see the desktop-order rules above
     .topnav for why this page's topbar has 3 direct children instead of
     index.html's 2. */
  .about-topbar{ flex-wrap: wrap; }
  /* The brand shrinks (flex-shrink:1) while .topnav is pushed right by its
     margin-left:auto, so without a floor between them the ellipsized title runs
     straight into the select with no gap at all. column-gap alone won't do it --
     the auto margin absorbs the free space first -- so the title needs its own
     right margin. */
  .about-topbar .brand{ flex: 0 1 auto; margin-right: 14px; min-width: 0; }
  .about-topbar .topnav{ flex: 0 0 auto; margin-left: auto; order: 2; }
  .about-topbar .about-back-link{ order: 3; flex: 1 1 100%; margin-right: 0; }
  .brand-title{ flex: 0 1 auto; overflow: hidden; text-overflow: ellipsis; font-size: 17px; }
  /* Wrapping starts HERE, not at the phone tier. Forcing one nowrap row down
     to 800px squeezed the toggles and search until they overflowed sideways --
     the group needs to be free to drop to its own line as soon as the row is
     genuinely full, whatever width that happens at. */
  .topnav{ flex: 1 1 100%; display: flex; align-items: center; column-gap: 6px; flex-wrap: wrap; row-gap: 6px; }
  /* `1 1 100%` would force a second row even when everything still fits, so the
     group only takes a full row once the browsing toggles have claimed theirs:
     a large flex-basis wraps it, then it fills whatever line it lands on. */
  .navTools{ flex: 1 1 420px; min-width: 0; }
  .search-wrap{ flex: 1 1 auto; min-width: 0; }
  .search-input{ width: auto; flex: 1 1 auto; min-width: 0; }
  /* Scoped to the topbar: this hides the "अ→a" caption to buy horizontal space in
     a cramped nav, but .navlabel is also used for the changelog's
     "include असम्बद्धवर्गीकृतम्" checkbox down in about.html's Data Quantity
     section, which has a full-width row to itself and no reason to disappear. An
     unscoped rule here hid that toggle -- and with it the only way to include the
     orphan bucket in the trends -- at every width below 1100px. */
  .topnav .navlabel{ display: none; }
  #schemeSelect{ flex: 0 1 auto; min-width: 0; max-width: 160px; }

  /* A <select> sizes itself to its *longest* option, not the selected one, so
     the box stayed wide while showing a 4-character "IAST". Shortening the two
     worst labels ("Harvard-Kyoto" -> "HK", "Devanagari" -> "देवनागरी", which is
     both narrower and self-demonstrating) shrinks it at the source, leaving
     "ISO 15919" as the widest -- no max-width clamp needed. */
  .about-topbar #schemeSelect{
    font-size: 14px;
    padding: 4px 6px;
  }
  .topnav .sep:last-of-type{ order: 5; }
  .theme-toggle{
    order: 6;
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .theme-toggle .toggle-label{ display: none; }

  /* about.html's nav is just the scheme dropdown + this button (no search box),
     so the toggle sits directly beside #schemeSelect rather than after a
     separator. A fixed 34px box next to the dropdown reads as oversized and
     empty on a phone, so match the dropdown's own height instead and let the
     icon center itself. */
  .about-topbar .theme-toggle{
    width: auto;
    height: auto;
    aspect-ratio: 1;
    padding: 4px 8px;
  }

  .layout{ height: calc(100% - 76px); }
}

/* Narrow (phone): hide version/date, switch sidebar to off-canvas hamburger. */
@media (max-width: 800px){
  .sidebar-toggle{ display: inline-flex; align-items: center; justify-content: center; }
  .brand-meta{ display: none; }

  /* Phone widths: three lines total -- brand, browsing toggles, then the
     search/transliteration/theme group on a row of its own. Source order
     already runs toggles -> search -> transliteration, so no `order` juggling.

     The group must not itself wrap, or the theme button drops to a fourth
     line: search absorbs the slack (flex 1, tiny basis) while the select and
     the button keep their natural widths. */
  .topnav .navTools{ flex: 1 1 100%; min-width: 0; flex-wrap: nowrap; }
  /* The input carries a fixed width in the base rule, which floors the wrap at
     ~240px and pushes the row off-screen below ~400px. Both the wrap and the
     input it contains need min-width:0 before flex can shrink them. */
  .topnav .search-wrap{ flex: 1 1 60px; min-width: 0; }
  /* The 84px left padding reserves room for the icon and the "exact" chip that
     sit absolutely inside the field. It is an incompressible floor: no width or
     flex value can shrink the input below it, which is what pushed the row
     off-screen on narrow phones. The chip stays, so the padding only tightens
     to what the two actually occupy. */
  .topnav .search-input{ width: 100%; min-width: 0; flex: 1 1 auto; padding-left: 72px; }
  .topnav #schemeSelect{ flex: 0 1 auto; min-width: 0; max-width: none; }
  .topnav .theme-toggle{ flex: 0 0 auto; }
  /* Every divider goes: at this width the pipes cost horizontal room that the
     search box needs, and the row breaks already separate the groups. */
  .topnav .sep{ display: none; }

  /* Body (not .mainpane) scrolls at this width (see .layout below), so the
     topbar needs its own sticky pin here -- above sticky category headers
     (max z-index ~999, see renderCategoryBlock in app.js) but below the
     off-canvas sidebar/backdrop (1900/2000) so an open sidebar still overlays it. */
  /* Content scrolls *under* this bar at phone widths, so the 1px --border alone
     wasn't enough separation -- --panel and --bg differ by only a few points of
     luminance, leaving text appearing to bleed through the boundary. Give the
     bar its own slightly-lifted surface plus a drop shadow so it reads as a
     layer above the page rather than part of it. */
  .topbar{
    position: sticky;
    top: 0;
    z-index: 1000;
    background: var(--panel);
    border-bottom-color: var(--border-strong);
    box-shadow: var(--topbar-shadow);
  }

  /* The document (not .mainpane) scrolls here and the topbar is pinned over it,
     so sticky section headers must come to rest *below* that bar rather than at
     top:0, where they'd slide underneath it. --topbar-h is measured and set on
     :root by about.html, since this topbar wraps to a variable height. */
  .content .block.collapsible:not(.block.collapsible .block.collapsible)[open] > summary{
    top: var(--topbar-h, 48px);
  }

  /* ...and .mainpane must stop being an overflow container at this width, or the
     sticky headers above have nothing to stick to. A position:sticky element
     anchors to its nearest *scrolling* ancestor, and an overflow:auto box counts
     as one even when it has nothing to scroll -- which is the case here, since
     about.html's inline `height: calc(100% - 48px)` resolves against an auto-height
     body and lets .mainpane grow to full content height. The header would then
     stick relative to .mainpane and scroll out of view along with it, landing
     underneath the topbar. Handing the scroll to the document instead makes the
     viewport the anchor. `height: auto` also overrides that inline height, which a
     media query otherwise loses to. */
  .mainpane{
    overflow: visible;
    height: auto !important;
  }

  /* With the off-canvas sidebar open, prevent the background (mainpane/body) from
     scrolling underneath it -- html, not just body, since body's own box isn't
     what's scrolling here (see .layout below: document/viewport scroll is used). */
  html:has(body.sidebar-open-lock){ overflow: hidden; }
  body.sidebar-open-lock{ overflow: hidden; }

  body{ height: auto; min-height: 100%; }
  .layout{
    height: auto;
    min-height: calc(100vh - 48px);
    display: block;
    position: relative;
  }

  .sidebar-resizer{ display: none; }

  .sidenav{
    position: fixed;
    top: 0;
    left: 0;
    height: 100%;
    width: min(85vw, 340px);
    /* Sticky category headers in the main pane use z-index up to ~999
       (1000 - depth, see renderCategoryBlock in app.js) -- the mobile sidebar
       and its backdrop must stay above that ceiling or a deep-scrolled sticky
       header punches through the open sidebar. */
    z-index: 2000;
    transform: translateX(-100%);
    transition: transform 0.2s ease;
    box-shadow: 2px 0 12px rgba(0,0,0,0.3);
  }
  .sidenav.open{
    transform: translateX(0);
  }

  .sidebar-backdrop{
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.45);
    z-index: 1900;
  }
  .sidebar-backdrop.open{
    display: block;
  }

  .mainpane{
    height: 100%;
  }

  /* The All-row stats/toggle reflow that used to live here is now handled by the
     @container sidenav rule up by .includeOrphansToggle: the off-canvas sidebar is
     min(85vw, 340px) at this width, comfortably under that query's threshold, so
     phones get the same arrangement without a second copy of the rule. */

  /* about.html readability at phone widths. Section <h2>s inherit the UA's 1.5em,
     which at this width wraps a title like "Data Quality: Structural Errors and
     Provenance Hints" to three lines and costs most of the viewport before any
     content shows. Scale the headings down and tighten their leading; the sticky
     bar they sit in shrinks with them. */
  .content .block.collapsible > summary h2{
    font-size: 19px;
    line-height: 1.25;
  }
  .content .block.collapsible > summary h3{
    font-size: 16px;
    line-height: 1.3;
  }
  .content .block.collapsible{ padding: 8px 10px; }
  .content .block.collapsible:not(.block.collapsible .block.collapsible)[open] > summary{
    /* Keep the negative margins matched to the reduced padding above, or the
       pinned bar stops spanning the block's full width. */
    margin: -8px -10px 0;
    padding: 8px 10px;
  }
  /* The disclosure triangle is sized in em, so it shrank with the heading -- give
     it back a little breathing room from the now-tighter title. */
  .content .block.collapsible > summary::before{ margin-right: 8px; }

  /* Granularity + orphan-bucket toggle: on one cramped line the label wraps mid-
     phrase, stranding "asambaddhavargīkṛtam" on its own row away from its
     checkbox. Give each control its own full-width row instead, and keep each
     checkbox next to its own text. */
  #changelogGranularity{ margin-left: 6px; }
  label[for="changelogIncludeOrphans"]{
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: 0 !important;
    margin-top: 8px;
  }
  label[for="changelogIncludeOrphans"] input{ flex: 0 0 auto; }

  .chart-card h4{ font-size: 12px; }
}

/* === Changelog trend charts === */
.chart-card{
  margin: 10px 0 16px;
}
.chart-card h4{
  margin: 0 0 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}
.chart-svg{
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}
.chart-svg .chart-grid{
  stroke: var(--border);
  stroke-width: 1px;
  shape-rendering: crispEdges;
}
.chart-svg .chart-axis-text{
  fill: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

/* Chart text is authored in the SVG's 720px user-space coordinates and the SVG is
   then scaled to fit (.chart-svg is width:100%), so these font sizes are NOT
   on-screen pixels -- they shrink with the chart. On a 390px phone the chart
   renders ~341px wide, a 0.47x scale, which turns the 11px above into ~5px on
   screen against 18px body text: unreadable.
   Counter-scale so the labels land near 12px actual. Must be declared after the
   base rule above -- a media query adds no specificity, so an identical selector
   later in the file would otherwise win. */
@media (max-width: 800px){
  .chart-svg .chart-axis-text{ font-size: 24px; }
}
.chart-svg .chart-line{
  fill: none;
  stroke: var(--accent);
  stroke-width: 2px;
  stroke-linejoin: round;
  stroke-linecap: round;
}
.chart-svg .chart-area{
  fill: var(--accent);
  opacity: 0.1;
}
.chart-svg .chart-dot{
  fill: var(--accent);
  stroke: var(--panel);
  stroke-width: 2px;
}
.chart-svg .chart-crosshair{
  stroke: var(--muted);
  stroke-width: 1px;
  opacity: 0;
  pointer-events: none;
}
.chart-svg .chart-hover-dot{
  fill: var(--accent);
  stroke: var(--panel);
  stroke-width: 2px;
  opacity: 0;
  pointer-events: none;
}
.chart-svg .chart-hit{
  fill: transparent;
  cursor: crosshair;
}
.chart-tooltip{
  position: absolute;
  pointer-events: none;
  background: var(--panel2);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 8px;
  font-size: 12px;
  color: var(--text);
  white-space: nowrap;
  opacity: 0;
  transition: opacity 0.1s ease;
  z-index: 10;
}
.chart-tooltip .chart-tooltip-date{
  color: var(--muted);
  margin-bottom: 2px;
}
.chart-tooltip .chart-tooltip-value{
  font-weight: 600;
}
.chart-wrap{
  position: relative;
}
.sourceTimeline{
  position: relative;
  margin: 14px 0 10px;
  max-width: 100%;
}
.sourceTimelineRow{
  display: flex;
  align-items: stretch;
}
.sourceTimelinePre{
  flex: none;
  width: 28px;
  height: 28px;
  margin-right: 2px; /* surface gap, same as between two real segments */
  border-radius: 4px 0 0 4px;
  background: var(--surface-uncovered);
  cursor: default;
}
.sourceTimelineBar{
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  height: 28px;
  border-radius: 0 4px 4px 0;
  overflow: hidden;
  background: var(--panel2);
}
.sourceTimelineSeg{
  position: relative;
  height: 100%;
  margin-right: 2px; /* surface gap between touching segments */
  cursor: default;
}
.sourceTimelineSeg:last-child{ margin-right: 0; }
.sourceTimelineAxis{
  position: relative;
  height: 16px;
  margin-top: 4px;
  font-size: 12px;
  color: var(--muted);
}
.sourceTimelineAxis span{
  position: absolute;
  transform: translateX(-50%);
  white-space: nowrap;
}
.sourceTimelineAxis span:first-child{ transform: translateX(0); }
.sourceTimelineAxis span:last-child{ transform: translateX(-100%); }
.sourceTimelineLegend{
  display: flex;
  /* Right-aligned so the legend's end lines up with the bar's present-day
     end. Item order (materialized -> legacy-live -> current-live, matching
     the bar's oldest-to-newest layout) comes from the markup, not from
     flex-direction -- reversing via row-reverse looked right on one line but
     flipped back to newest-first as soon as the legend wrapped. */
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 10px;
  font-size: 14px;
  color: var(--text);
}
.legendItem{
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.legendSwatch{
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  flex: none;
}
.sourceTimelineTooltip{
  position: absolute;
  z-index: 20;
  pointer-events: none;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 6px 10px;
  font-size: 13px;
  color: var(--text);
  box-shadow: 0 4px 16px rgba(0,0,0,0.25);
  white-space: normal;
  width: max-content;
  max-width: min(360px, calc(100vw - 48px));
}
.sourceTimelineTooltip .ttLabel{ color: var(--muted); }

/* ---- About page: catalogue Venn figure ---- */

.venn-figure{ margin: 18px 0 16px; }
.venn-figure svg{ width: 100%; height: auto; display: block; }
.venn-figure figcaption{
  font-size: 14px;
  color: var(--muted);
  margin-top: 4px;
  padding-left: 2px;
  line-height: 1.55;
}

/* Fills stay translucent so the intersection reads as a third region without
   being painted as one. */
.venn-circle{ fill-opacity: 0.18; stroke-width: 1.5; }
.venn-uni{ fill: var(--accent); stroke: var(--accent); }
.venn-pdf{ fill: var(--muted); stroke: var(--muted); }

/* Top corners mirror the inner counts: those run number-over-caption, these
   run caption-over-number, so the figure reads inward from both ends. */
.venn-label{ fill: var(--text); font-size: 14px; font-weight: 600; text-anchor: middle; }
.venn-total{ fill: var(--muted); font-size: 20px; font-weight: 400; text-anchor: middle; }
.venn-count{ fill: var(--text); font-size: 19px; font-weight: 700; text-anchor: middle; }
.venn-cap{ fill: var(--muted); font-size: 12px; text-anchor: middle; }
text.venn-count.venn-both{ fill: var(--accent); }
text.venn-cap.venn-both{ fill: var(--accent); }

/* The query stubs: monospace, and clearly clickable/hoverable. */
.venn-query{
  fill: var(--muted);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 9.5px;
}
.venn-query:hover{ fill: var(--accent); }
tspan.venn-page{ fill: var(--accent); font-weight: 700; }

/* One example per sector. Filled so it reads as a marker, not an outline. */
.venn-dot{
  fill: var(--accent);
  stroke: var(--bg);
  stroke-width: 2;
  fill-opacity: 0.55;
}
.venn-dot:hover{ fill-opacity: 1; }
.venn-figure a{ cursor: pointer; }

/* Example markers: a visible label beats a tooltip nobody hovers. */
.venn-dotlabel{
  fill: var(--muted);
  font-size: 11px;
  text-anchor: middle;
}
.venn-dotid{
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10.5px;
}
.venn-figure a:hover .venn-dotlabel{ fill: var(--accent); }

/* The SVG scales to the container, so every font-size above is effectively
   multiplied by (container width / 620). On a phone that multiplier drops
   below 1 and the small labels stop being legible -- bump them back up in
   user units so they hold their rendered size. */
@media (max-width: 640px){
  .venn-count{ font-size: 34px; }
  .venn-cap{ font-size: 22px; }
  .venn-label{ font-size: 24px; }
  .venn-total{ font-size: 28px; }
  .venn-query{ font-size: 12px; }
  .venn-dotlabel{ font-size: 21px; }
  .venn-rowlabel{ font-size: 24px; }
  .venn-dot{ r: 8; }
}
.venn-leader{ stroke: var(--border-strong); stroke-width: 1; stroke-dasharray: 3 3; }
.venn-rowlabel{ fill: var(--muted); font-size: 12px; text-anchor: middle; }
.venn-rowlabel-work{ font-style: italic; }

/* At mobile type sizes the header row would overlap the circles, and the
   outboard columns need to come inward. Nudge them with transforms rather
   than duplicating the markup for a second viewBox. */
@media (max-width: 640px){
  .venn-label{ transform: translateY(-6px); }
  /* Same as the caps below: the enlarged number needs room under its label. */
  .venn-total{ transform: translateY(8px); }
  .venn-query{ transform: translateY(-1px); }
  .venn-rowlabel{ transform: translateY(6px); }
  /* The enlarged counts sit tight against their caps; drop the caps clear. */
  .venn-cap{ transform: translateY(9px); }
}

/* --- growth over time (about.html) ------------------------------------- */
/* Stacked bars, hand-rolled: six periods and three bands need no library,
   and about.html loads no external dependencies. */
/* Two nested gaps, and the difference between them is the whole point: a
   label sits close to the buttons it governs, and the next group starts far
   enough away that the pairing is unambiguous. One flat row with a uniform
   gap read as eleven equidistant controls rather than four groups. */
.gb-controls {
  display: flex; align-items: center; flex-wrap: wrap;
  /* The bottom margin has to clear the 22px between groups: a legend sitting
     closer to the controls than the controls sit to each other reads as part
     of the same row. */
  gap: 6px 22px; margin: 10px 0 14px;
}
.gb-group { display: inline-flex; align-items: center; gap: 7px; }
.gb-seg { display: inline-flex; }
.gb-ctl-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em;
  opacity: 0.55;
  /* A two-word label breaking mid-group ("time / period") makes that group
     taller than its neighbours and pushes the row out of alignment. Wrap
     between groups instead, which the row already does. */
  white-space: nowrap;
}
.gb-controls button {
  font: inherit; font-size: 12px; padding: 3px 10px; cursor: pointer;
  background: transparent; color: inherit;
  border: 1px solid var(--border);
}
/* Scoped to the segment, not the row. As `.gb-controls button:first-of-type`
   these matched once per toolbar, so every button between the two ends kept
   square corners and read as a loose box instead of a joined segment. */
.gb-seg button:first-child { border-radius: 4px 0 0 4px; }
.gb-seg button:last-child { border-radius: 0 4px 4px 0; }
.gb-seg button + button { border-left: none; }
.gb-controls button:hover { background: color-mix(in srgb, currentColor 8%, transparent); }
.gb-controls button.gb-on {
  background: var(--accent); border-color: var(--accent); color: #fff;
}

.gb-chart {
  display: flex; align-items: flex-end; gap: 10px;
  height: 200px; margin: 10px 0 4px;
  /* Anchors the gridlines and the tick column, both absolutely placed. The
     bottom padding is the year-label row, which sits outside the plot so a
     0 tick lands on the baseline the bars actually start from. */
  position: relative; padding-left: 44px; padding-bottom: 19px;
}

/* Tick labels, right-aligned into the padding the chart reserves for them.
   Each is translated up by half its line height so the text centres on its
   gridline rather than hanging below it. */
.gb-axis { position: absolute; left: 0; top: 0; bottom: 19px; width: 40px; }
.gb-tick {
  position: absolute; right: 0; transform: translateY(50%);
  font-size: 10px; opacity: 0.5; text-align: right; line-height: 1;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* Behind the bars: `z-index: 0` on the grid and `1` on the columns, so the
   lines never cross a filled segment. */
.gb-grid { position: absolute; left: 44px; right: 0; top: 0; bottom: 19px; z-index: 0; }
.gb-line {
  position: absolute; left: 0; right: 0; height: 1px;
  background: var(--border); opacity: 0.55;
}
.gb-line:first-child { opacity: 0.9; }

/* At month resolution 67 columns share the width, so a 10px gap is wider than
   the bar it separates. Set by the JS, which knows how many it just drew. */
.gb-chart.gb-dense { gap: 2px; }
.gb-chart.gb-dense .gb-stack > .gb-seg:first-child { border-radius: 0; }
.gb-col {
  flex: 1 1 0; height: 100%;
  display: flex; flex-direction: column; justify-content: flex-end;
  position: relative; z-index: 1;
}
.gb-stack { display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
.gb-seg { width: 100%; min-height: 1px; }
.gb-stack > .gb-seg:first-child { border-radius: 3px 3px 0 0; }
/* Three bands, one hue family, light-to-dark by how much text is available:
   PDF-only is the palest because it is the least useful to a text atlas. */
.gb-pdf  { background: color-mix(in srgb, var(--accent) 28%, transparent); }
.gb-both { background: color-mix(in srgb, var(--accent) 62%, transparent); }
.gb-text { background: var(--accent); }
/* Absolutely placed into the chart's bottom padding so the plot area ends at
   the baseline; a flowed label would push the bars up off the 0 gridline. */
.gb-year {
  font-size: 11px; opacity: 0.65; text-align: center;
  font-variant-numeric: tabular-nums;
  position: absolute; left: 0; right: 0; top: 100%; margin-top: 4px;
  /* A label is wider than its column once the columns are thin, so it has to
     be free to overhang rather than wrap into a second row. It is absolutely
     positioned and so takes no layout space either way; only every twelfth
     column carries one, which is what keeps them from colliding. */
  white-space: nowrap;
}

/* Marks a period holding interpolated addition dates. Deliberately visible
   rather than subtle: it is the difference between a measurement and an
   estimate, and it carries the explanation on hover. */
.gb-est {
  color: var(--accent); font-weight: 700; margin-left: 1px;
  cursor: help;
}

.gb-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; margin: 0 0 8px; }
.gb-key { display: inline-flex; align-items: center; gap: 5px; }
/* `display` on a class beats the `hidden` attribute's UA rule, so the legend
   key for a band the scope filter drops would still show. */
.gb-key[hidden] { display: none; }
.gb-swatch { width: 11px; height: 11px; border-radius: 2px; display: inline-block; }

/* --- category mix over time (about.html) -------------------------------- */
/* An SVG line chart, drawn into a fixed 1000x300 viewBox and scaled by the
   page. Eight series over 67 months is past what the stacked-bar approach
   above can carry -- stacking hides a category's own shape behind the ones
   below it, and the question here is which subjects were the intake focus
   when, which is a comparison of shapes.

   The colours are the eight-slot categorical palette, both modes validated
   against this page's own surfaces (#ffffff and #0b0d10). They are assigned
   to categories in rank order and never cycled: the ninth category and below
   fold into "other", which is why there are only ever eight lines. Colour
   follows the category, not the line's rank on screen, so toggling one off
   never repaints the rest. */
/* The palette lives on the section wrapper, not on the SVG: the legend keys
   sit outside the chart and carry the same colours, and a variable scoped to
   the SVG is invisible to them. */
.cm-scope { --cm-1: #2a78d6; --cm-2: #eb6834; --cm-3: #1baf7a; --cm-4: #eda100;
            --cm-5: #e87ba4; --cm-6: #008300; --cm-7: #4a3aa7; --cm-8: #e34948;
            /* The fold-in line is deliberately not a ninth hue: it is not a
               subject, it is the absence of one, and a gray says so. */
            --cm-other: #8b96a5; }
:root[data-theme="dark"] .cm-scope,
:root:not([data-theme="light"]) .cm-scope {
  --cm-1: #3987e5; --cm-2: #d95926; --cm-3: #199e70; --cm-4: #c98500;
  --cm-5: #d55181; --cm-6: #008300; --cm-7: #9085e9; --cm-8: #e66767;
  --cm-other: #79838f;
}
/* The default theme is dark (`:root` with no stamp), so the light values have
   to be reasserted under an explicit light stamp -- the rule above matches a
   missing attribute too. */
:root[data-theme="light"] .cm-scope {
  --cm-1: #2a78d6; --cm-2: #eb6834; --cm-3: #1baf7a; --cm-4: #eda100;
  --cm-5: #e87ba4; --cm-6: #008300; --cm-7: #4a3aa7; --cm-8: #e34948;
  --cm-other: #8b96a5;
}

.cm-chart { width: 100%; height: auto; display: block; margin: 10px 0 4px;
            overflow: visible; }
.cm-grid { stroke: var(--border); stroke-width: 1; opacity: 0.55; }
.cm-grid.cm-base { opacity: 0.9; }
.cm-tick { fill: var(--muted); font-size: 11px; }
/* 2px, round join and cap -- a monthly series turns sharply and a mitred
   join spikes at every reversal. */
.cm-line { fill: none; stroke-width: 2; stroke-linejoin: round;
           stroke-linecap: round; }
/* The surface ring is what keeps a dot legible where two series cross. */
.cm-dot { stroke: var(--bg); stroke-width: 2; }
/* A series switched off in the legend stays in the DOM so its colour stays
   bound to it; hiding rather than removing is also what keeps the y-scale
   from rescaling under the reader. */
.cm-series[hidden] { display: none; }

.cm-legend { display: flex; gap: 6px 14px; flex-wrap: wrap; font-size: 12px;
             margin: 0 0 8px; }
.cm-key { display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
          background: none; border: none; color: inherit; font: inherit;
          padding: 1px 2px; border-radius: 3px; }
.cm-key:hover { background: color-mix(in srgb, currentColor 8%, transparent); }
/* Off is dimmed rather than hidden: the key is the control that switches it
   back on, so it has to stay readable. */
.cm-key[aria-pressed="false"] { opacity: 0.4; }
/* A short line-key rather than a square, matching the mark it names. */
.cm-swatch { width: 14px; height: 3px; border-radius: 2px; display: inline-block;
             flex: none; }

/* Follows the pointer across the plot, naming every visible series at the
   month under it -- with eight lines a per-point tooltip would mean hunting
   for a 4px dot. */
.cm-cursor { stroke: var(--border-strong); stroke-width: 1; }
.cm-tip {
  position: absolute; z-index: 5; pointer-events: none;
  background: var(--panel); border: 1px solid var(--border-strong);
  border-radius: 5px; padding: 6px 8px; font-size: 12px;
  box-shadow: 0 3px 12px rgba(0,0,0,0.28); white-space: nowrap;
}
.cm-tip-head { font-weight: 600; margin-bottom: 3px;
               font-variant-numeric: tabular-nums; }
.cm-tip-row { display: flex; align-items: center; gap: 6px; }
.cm-tip-row .cm-swatch { width: 10px; }
/* Text wears text tokens; the swatch beside it carries the identity. */
.cm-tip-val { margin-left: auto; padding-left: 10px; color: var(--muted);
              font-variant-numeric: tabular-nums; }
.cm-wrap { position: relative; }

/* Computed figures. Every `data-stat` number is rewritten by pipeline/audit.py,
   so bolding them marks what is measured rather than authored. Scoped to
   `span` deliberately: the Venn's SVG `text` nodes carry the same attribute but
   already encode their own hierarchy (venn-count 700 against venn-total 400),
   which a blanket rule would flatten. */
span[data-stat] { font-weight: 700; }

/* --- "Understanding Serials" table ------------------------------------- *
 * One row per 500 serials. The percentage columns carry a bar behind the
 * number: the point of the table is the SHAPE of each column down the page
 * (text availability decaying, the chaptered block in the middle), which is
 * hard to see as 26 rows of digits.                                        */
/* --- serial bands on a time axis (about.html) --------------------------- */
/* The table above tells you a band of 500 serials closed in, say, Jan 2025.
   It cannot show you that the band before it closed ten months earlier and
   nothing happened in between -- rows are evenly spaced whatever the dates
   say. This chart puts the same 26 bands on a real monthly axis, so the
   stalls are gaps and the bursts are clusters.

   A bar per band, at the month that band closed. Most months carry none,
   which is the finding rather than missing data. */
.sn-wrap { position: relative; }
.sn-chart { width: 100%; height: auto; display: block; margin: 10px 0 4px; }

/* The stack is ORDINAL, not categorical: structured text -> flat text ->
   PDF-only is a ranked sequence by how much of the item is actually readable,
   so it takes one hue in monotone steps rather than four identities. Same
   accent ramp as the growth chart above, which splits the same corpus the
   same way -- a reader moving between the two charts should not have to
   relearn the colours. */
.sn-structured { fill: var(--accent); }
.sn-text { fill: color-mix(in srgb, var(--accent) 62%, transparent); }
.sn-pdf { fill: color-mix(in srgb, var(--accent) 28%, transparent); }
/* A 2px surface gap between stacked segments, so the ramp's adjacent steps
   stay separable where they meet. */
.sn-seg { stroke: var(--bg); stroke-width: 2; stroke-linejoin: miter; }

.sn-grid { stroke: var(--border); stroke-width: 1; opacity: 0.55; }
.sn-grid.sn-base { opacity: 0.9; }
.sn-tick { fill: var(--muted); font-size: 11px; }

.sn-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px;
             margin: 4px 0 8px; align-items: center; }
.sn-key { display: inline-flex; align-items: center; gap: 5px; }
.sn-swatch { width: 11px; height: 11px; border-radius: 2px; display: inline-block;
             flex: none; }
.sn-sw-structured { background: var(--accent); }
.sn-sw-text { background: color-mix(in srgb, var(--accent) 62%, transparent); }
.sn-sw-pdf { background: color-mix(in srgb, var(--accent) 28%, transparent); }

.sb-wrap{ overflow-x: auto; margin: 12px 0 4px; }
.sb-table{ border-collapse: collapse; font-size: 13px; min-width: 640px; }
.sb-table th{
  text-align: left;
  font-weight: 600;
  color: var(--muted);
  padding: 4px 10px 6px 0;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}
.sb-table td{ padding: 3px 10px 3px 0; border-bottom: 1px solid var(--border); }
.sb-table tr:last-child td{ border-bottom: none; }
.sb-range code{ font-size: 12px; color: var(--muted); white-space: nowrap; }

.sb-num{ text-align: right; white-space: nowrap; }
.sb-zero{ color: var(--muted); opacity: 0.45; }
.sb-none{ color: var(--muted); opacity: 0.6; }
.sb-end{ white-space: nowrap; color: var(--muted); }

/* The local-only plain-text badge, shown by `serve_docs.py --fulltext`.
   Deliberately quiet: a dev affordance, not part of the published UI, and it
   never renders on the deployed site. */
.badgeLocal { opacity: 0.65; }
.badgeLocal:hover { opacity: 1; }
