/* =========================================================
   ANALYSER - Swiss / International Typographic Style
   Black on white, hairline rules, 12-col grid, red accent.
   ========================================================= */

/* ---------- VIEW TRANSITIONS ---------- */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.2s;
  animation-timing-function: ease;
}

:root {
  --bg:        #ffffff;
  --fg:        #0a0a0a;
  --muted:     #6b6b6b;
  --hairline:  #0a0a0a;
  --rule:      #e6e6e6;
  /* Composite border shorthand - the single most repeated literal in this file.
     Resolves the nested var(--hairline), so it flips with the theme. */
  --bd-hairline: 1px solid var(--hairline);
  /* Same shorthand over the slightly stronger --rule colour (both flip with the theme). */
  --bd-rule: 1px solid var(--rule);
  --surface:   #f4f4f4;
  --accent:    #e60023;
  --accent-fg: #ffffff;
  --partial:   #c47a0f;   /* yellowish-orange: the in-between "Partial" depth tag */

  --font-sans: "Geist", -apple-system, "Helvetica Neue", Helvetica, "Liberation Sans", system-ui, sans-serif;
  --font-mono: "Geist Mono", ui-monospace, "SF Mono", "Cascadia Mono", Consolas, "DejaVu Sans Mono", monospace;

  --gap:    24px;
  --pad-x:  clamp(20px, 4vw, 56px);
  --pad-y:  clamp(56px, 9vw, 120px);

  --t-mega:  clamp(64px, 13vw, 200px);
  --t-huge:  clamp(48px, 9vw, 112px);
  --t-h1:    clamp(28px, 4.2vw, 56px);
  --t-h2:    clamp(20px, 2.2vw, 28px);
  --t-h3:    16px;
  --t-body:  16px;
  --t-small: 13px;
  --t-tiny:  11px;
  --t-micro: 10px;

  /* On-dark chrome - theme-independent: these live on always-dark media
     surfaces (spectrogram / waveform / fullscreen) so they do NOT get a dark
     override. */
  --media-bg:              #0a0a0a;
  --on-dark:               #ffffff;
  --surface-on-dark:       #1a1a1a;
  --hairline-on-dark:      #333;
  --hairline-strong:       #555;
  --border-on-dark-ctl:    #444;
  --muted-on-dark:         #999;
  --border-on-dark:        rgba(255, 255, 255, 0.15);
  --border-on-dark-strong: rgba(255, 255, 255, 0.5);

  /* Repeated white-on-dark alpha tints. (0.15 and 0.5 already have the semantic
     --border-on-dark / --border-on-dark-strong tokens above, so only the other
     two repeated values get a generic token here.) */
  --white-a40: rgba(255, 255, 255, 0.4);
  --white-a80: rgba(255, 255, 255, 0.8);

  /* UI transition / animation durations. The --t-* namespace is type sizes, so
     timing uses its own --dur-* tokens. */
  --dur-fast:   0.12s;
  --dur-snappy: 0.15s;
  --dur-base:   0.2s;
  --dur-slow:   0.3s;

  /* Token triples for alpha tints - use rgb(var(--accent-rgb) / a). */
  --accent-rgb: 230, 0, 35;
  --bg-rgb:     255, 255, 255;

  /* Elevation */
  --shadow-color:   rgba(0, 0, 0, 0.14);
  --shadow-popover: 0 8px 28px var(--shadow-color);

  /* Layout */
  --nav-offset: 60px;
  /* Standalone section dividers echo the navbar's structure - a hairline, a band
     of page-fill, then a hairline - at half the navbar's height (see --nav-offset). */
  --rule-band-h: calc(var(--nav-offset) / 2);
  --radius:     0;

  /* Rhythm */
  --lh-tight: 1.2;
  --lh-body:  1.5;
  --lh-prose: 1.65;
  --ls-caps:  0.08em;
  --ls-micro: 0.15em;
}

* { box-sizing: border-box; }
html { background: var(--bg); }
html, body {
  margin: 0; padding: 0;
  color: var(--fg);
}
body {
  background: transparent;
  font-family: var(--font-sans);
  font-size: var(--t-body);
  line-height: 1.45;
  font-feature-settings: "ss01", "cv11", "tnum";
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* Faint drifting grid backdrop. NOTE: on high-refresh-rate displays a recent
   Firefox/WebRender regression pixel-snaps this slow axis-aligned drift, so it can
   look choppy in Firefox (Chrome/Safari stay smooth). It is NOT a site bug - 2.0
   and 3.0 snapshots with identical code are choppy too. A rotate(0.02deg) hack
   defeats the snap but softens the lines and causes moire at fractional zoom, so we
   keep the crisp original and wait for Firefox to fix the snapping. Don't re-add the
   rotate workaround. */
body::before {
  content: '';
  position: fixed;
  inset: -72px;
  z-index: -1;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48'%3E%3Cline x1='0' y1='24' x2='15' y2='24' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3Cline x1='33' y1='24' x2='48' y2='24' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3Cline x1='24' y1='0' x2='24' y2='15' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3Cline x1='24' y1='33' x2='24' y2='48' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3Cline x1='19' y1='24' x2='29' y2='24' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3Cline x1='24' y1='19' x2='24' y2='29' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3C/svg%3E");
  background-size: 72px 72px;
  animation: bg-drift 24s linear infinite;
  will-change: transform;
  pointer-events: none;
}
@keyframes bg-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(72px, 72px, 0); }
}
:root[data-theme="dark"] body::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48'%3E%3Cline x1='0' y1='24' x2='15' y2='24' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3Cline x1='33' y1='24' x2='48' y2='24' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3Cline x1='24' y1='0' x2='24' y2='15' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3Cline x1='24' y1='33' x2='24' y2='48' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3Cline x1='19' y1='24' x2='29' y2='24' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3Cline x1='24' y1='19' x2='24' y2='29' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3C/svg%3E");
}

/* ---------- BOOT SPLASH ----------
   Covers the page until the app's JS runs boot() (app.js adds .is-done, then
   removes the node after the fade). Themed through the tokens so it flips with
   data-theme. Every animation here is a compositor-only transform/opacity so it
   keeps moving even while the heavy module graph parses on the main thread - an
   rAF loop would stall. Echoes the site's signature ASCII bracket-bar loader and
   the wordmark, with a forensic scan line sweeping the letters. */
.splash {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg);
  opacity: 1;
  transition: opacity 0.5s ease;
}
.splash.is-done { opacity: 0; pointer-events: none; }
/* Skip the splash on Firefox (grid/scan animation snaps there); flag set in <head>. */
:root[data-ff="1"] .splash { display: none; }
/* Same faint drifting grid as body::before (which the splash covers), so the boot
   screen shares the page's backdrop. Dark variant + reduced-motion handled below. */
.splash::before {
  content: '';
  position: absolute;
  inset: -72px;
  z-index: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48'%3E%3Cline x1='0' y1='24' x2='15' y2='24' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3Cline x1='33' y1='24' x2='48' y2='24' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3Cline x1='24' y1='0' x2='24' y2='15' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3Cline x1='24' y1='33' x2='24' y2='48' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3Cline x1='19' y1='24' x2='29' y2='24' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3Cline x1='24' y1='19' x2='24' y2='29' stroke='%23000' stroke-opacity='0.07' stroke-width='0.75'/%3E%3C/svg%3E");
  background-size: 72px 72px;
  animation: bg-drift 24s linear infinite;
  will-change: transform;
  pointer-events: none;
}
:root[data-theme="dark"] .splash::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48'%3E%3Cline x1='0' y1='24' x2='15' y2='24' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3Cline x1='33' y1='24' x2='48' y2='24' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3Cline x1='24' y1='0' x2='24' y2='15' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3Cline x1='24' y1='33' x2='24' y2='48' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3Cline x1='19' y1='24' x2='29' y2='24' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3Cline x1='24' y1='19' x2='24' y2='29' stroke='%23fff' stroke-opacity='0.12' stroke-width='0.75'/%3E%3C/svg%3E");
}
.splash-inner {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 22px;
  padding: 0 var(--pad-x);
}
.splash-kicker {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Wordmark, clipped so the scan line can only travel within the letters. font-size
   is set here (not just on the child) so the em padding scales with the wordmark -
   the bottom padding has to clear the "y" descender or overflow:hidden crops it. */
.splash-word {
  position: relative;
  overflow: hidden;
  font-size: var(--t-huge);
  padding: 0.08em 0.05em 0.22em;
}
.splash-word-text {
  display: block;
  font-family: var(--font-sans);
  font-size: var(--t-huge);
  font-weight: 600;
  letter-spacing: -0.045em;
  line-height: 0.9;
  color: var(--fg);
  /* Hidden until the Font Loading API confirms Geist is ready (script in
     index.html adds .is-fontready), so the wordmark never flashes the fallback
     font and swaps to Geist mid-splash. */
  opacity: 0;
  transition: opacity 0.25s ease;
}
.splash.is-fontready .splash-word-text { opacity: 1; }
/* Accent "scanner" line sweeping top-to-bottom over the wordmark. font-size ties
   the sweep distance to the wordmark height, so it tracks the responsive type. */
.splash-scan {
  position: absolute;
  left: -4%;
  right: -4%;
  top: 0;
  font-size: var(--t-huge);
  height: 0.1em;
  background: var(--accent);
  box-shadow: 0 0 14px 1px rgb(var(--accent-rgb) / 0.7);
  will-change: transform, opacity;
  animation: splash-scan 2.1s cubic-bezier(0.9, 0, 0.1, 1) infinite;
}
@keyframes splash-scan {
  0%   { transform: translateY(-0.1em); opacity: 0; }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { transform: translateY(1.05em); opacity: 0; }
}
/* The site's signature loader: a solid block of accent slashes bouncing between
   brackets ([////   ]). Reuses the anr-css-bar-bounce keyframes. */
.splash-bar {
  display: flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 18px;
  font-weight: 900;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--muted);
}
.splash-bar-track {
  position: relative;
  width: min(220px, 62vw);
  height: 1.2em;
  overflow: hidden;
  margin: 0 8px;
}
.splash-bar-win {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 25%;
  white-space: pre;
  overflow: hidden;
  color: var(--accent);
  will-change: transform;
  animation: anr-css-bar-bounce 1.9s steps(16, jump-none) infinite alternate;
}
@media (prefers-reduced-motion: reduce) {
  .splash-scan { animation: none; opacity: 0.85; }
  .splash-bar-win { animation: none; }
}

/* No-JavaScript warning. Analyser is a browser-only app driven entirely by
   JavaScript, so with it disabled nothing works - this full-bleed accent banner
   makes that obvious. Only ever rendered inside <noscript>. */
.noscript-warning {
  margin: 0;
  padding: 16px 22px;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: var(--t-base);
  font-weight: 600;
  line-height: 1.5;
  letter-spacing: 0.01em;
  text-align: center;
}
.noscript-warning strong { font-weight: 800; }

h1, h2, h3, h4, p, dl, dd, dt { margin: 0; }

a {
  color: var(--fg);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
a:hover { color: var(--accent); }

button { font-family: inherit; }
/* Every enabled <button> gets the hand cursor. Native buttons default to the
   arrow, and most of ours never set cursor explicitly, so this covers them all
   in one place (including JS-built buttons). Disabled buttons keep the default
   via :not(:disabled); the few buttons with an intentional non-pointer cursor
   (e.g. .offline-btn.is-included) use two
   classes, so they outrank this rule and still win. */
button:not(:disabled) { cursor: pointer; }

::selection { background: var(--accent); color: var(--accent-fg); }

/* Keyboard focus - one accent ring on every interactive control. :focus-visible
   fires for keyboard / assistive-tech focus but not mouse clicks, so pointer
   users never see it. Range sliders opt out here and ring their thumb instead. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
select:focus-visible,
input:not([type="range"]):focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- GRID ---------- */
.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--gap);
  padding: 0 var(--pad-x);
  max-width: 1440px;
  margin: 0 auto;
  width: 100%;
}

/* On displays wider than the 1440px content column there's empty margin on
   either side, which makes the page edges read as "floating". These two fixed
   panels fill that outer margin with the page background - so the drifting grid
   backdrop only shows WITHIN the column, never outside it - and draw the column's
   left/right edge as a hairline on their inner border, running the full height
   from the (sticky) navbar down. Only rendered once the viewport is wider than the
   content, so narrower screens (grid fills the whole page) are unaffected. */
@media (min-width: 1441px) {
  html::before,
  html::after {
    content: '';
    position: fixed;
    top: 0;
    bottom: 0;
    width: calc(50% - 720px);
    background: var(--bg);
    z-index: 25;
    pointer-events: none;
  }
  html::before { left: 0; border-right: 1px solid var(--hairline); }
  html::after { right: 0; border-left: 1px solid var(--hairline); }
}

/* ---------- HEADER ---------- */
.site-header {
  padding: clamp(40px, 6vw, 80px) 0 clamp(28px, 4vw, 48px);
  border-bottom: var(--bd-hairline);
  max-width: 1440px;
  margin: 0 auto;
}

/* Free-floating section dividers (a hairline with empty space on both sides)
   echo the navbar's structure: a hairline, a strip of page-fill at half the
   navbar height, then a hairline - replacing the old single rules. Applies to
   the home page's numbered sections and the dropzone strip; bars that sit flush
   against another element (the header above the navbar) keep a plain hairline,
   and the changelog's entry rules stay single lines by request. The band is
   drawn as an ::after where the old border-bottom sat. */
.section::after,
.quickdrop::after,
body:not(:has(.site-nav)) .site-header::after {
  content: '';
  display: block;
  height: var(--rule-band-h);
  background: var(--bg);
  border-top: var(--bd-hairline);
  border-bottom: var(--bd-hairline);
}
.section::after { margin-top: var(--pad-y); }
/* The changelog, stats and privacy pages have no navbar beneath the header, so
   that header divider is free-floating - give it the band. Any page WITH a navbar
   (index, about, per-format pages, the /formats hub) keeps the plain hairline (the
   navbar sits flush against it), so key the exclusion on the navbar itself. */
body:not(:has(.site-nav)) .site-header { border-bottom: none; padding-bottom: 0; }
body:not(:has(.site-nav)) .site-header::after { margin-top: clamp(28px, 4vw, 48px); }
/* The dropzone strip carries its own horizontal padding, so pull the band back
   out to the full-bleed width the old border-bottom spanned. */
.quickdrop::after {
  margin-top: 24px;
  margin-left: calc(-1 * var(--pad-x));
  margin-right: calc(-1 * var(--pad-x));
}
/* No band where a section sits directly above the footer - the footer keeps its
   own plain top hairline. Restore that section's bottom padding, which the
   band's margin-top would otherwise have supplied. */
.section:last-of-type::after { display: none; }
.section:last-of-type { padding-bottom: var(--pad-y); }

.site-mark {
  grid-column: 1 / span 8;
  -webkit-user-select: none;
  user-select: none;
}
.site-meta {
  grid-column: 10 / span 3;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.site-meta dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 4px 16px;
  margin: 0;
}
.site-meta dt { color: var(--muted); }
.site-meta dd { margin: 0; color: var(--fg); }
.site-meta .dot {
  display: inline-block;
  width: 6px; height: 6px;
  background: var(--accent);
  border-radius: 50%;
  vertical-align: middle;
  margin-right: 4px;
  transition: background-color 0.25s ease;
}
/* Online: the connectivity dot is green (label set by updateNetStatus in app.js). */
.site-meta dd.net-status .dot { background: #22c55e; }
/* Offline: the dot turns red - the conventional "disconnected" signal. */
.site-meta dd.net-status.is-offline .dot {
  background: #ef4444;
}

.site-kicker {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--accent);
  display: inline-block;
  margin-bottom: 16px;
}

.site-title {
  font-size: var(--t-mega);
  line-height: 0.86;
  font-weight: 600;
  letter-spacing: -0.045em;
  margin: 0;
}
/* PATCH NOTES PAGE - and the stats / privacy pages, which borrow the same shape:
   no left meta rail, so the single content column is centred. The title uses the
   normal --t-mega size, like the other pages. */
.patch-page .section .grid,
.stats-page .section .grid,
.privacy-page .section .grid { display: block; }
.patch-page .section-content,
.stats-page .section-content,
.privacy-page .section-content {
  grid-column: auto;
  max-width: 760px;
  margin: 0 auto;
}
/* Privacy folds three formerly-numbered sections into one box; give the stacked
   topic headings room to breathe between them. */
.privacy-page .section-content .section-head:not(:first-child) { margin-top: 40px; }

/* 404 page: no header/footer chrome, just a single centred card that fills the
   viewport. Sharp-cornered and hairline-bordered like every other surface. */
.notfound-page {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 40px 24px;
}
.notfound-card {
  width: 100%;
  max-width: 560px;
  background: var(--bg);
  border: var(--bd-hairline);
  padding: 48px 40px;
  text-align: center;
}
/* The big numeral: a mono accent glyph in the site's own idiom (like the samples
   accent chip) - no decoration, just the code. */
.notfound-code {
  font-family: var(--font-mono);
  font-size: clamp(80px, 20vw, 160px);
  /* Heavy at rest so the per-letter hover effect (setupNotFoundFx) has room to
     thin the digits toward the cursor - Geist Mono spans 400-600. */
  font-weight: 600;
  letter-spacing: -0.06em;
  line-height: 0.9;
  color: var(--accent);
  margin: 0 0 12px;
}
.notfound-card h1 {
  font-size: var(--t-h1);
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.1;
  margin: 0 0 16px;
}
.notfound-card p {
  color: var(--muted);
  line-height: 1.6;
  max-width: 46ch;
  margin: 0 auto 12px;
}
.notfound-card p:last-of-type { margin-bottom: 32px; }
/* Go-home button reuses the .anr-btn box model but sits large and centred. */
.notfound-card .anr-btn { font-size: var(--t-small); padding: 10px 20px; }

/* Byline directly under the title. Its size is a fixed fraction of the title
   size (--t-mega), so the two scale together and the byline's right edge stays
   anchored just left of the "y" at every viewport width. */
.site-byline {
  font-size: calc(var(--t-mega) * 0.29);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
  margin: -8px 0 0;
}
.site-byline-link {
  color: inherit;
  text-decoration: none;
  cursor: pointer;
  position: relative;
  /* Smooth but fast white<->red on hover (the colour itself comes from the
     global a:hover accent rule, which has no transition of its own). */
  transition: color var(--dur-snappy) ease;
}
.site-byline-link::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -2px;
  height: 2px;
  width: 0;
  background: currentColor;
  transition: width var(--dur-slow) ease;
}
.site-title:hover ~ .site-byline .site-byline-link::after,
.site-byline:hover .site-byline-link::after { width: 100%; }
.site-byline-link:hover::after { background: var(--accent); }

/* CHANGELOG header.
   - "Changelog" is one long word: tighten the tracking and pin it to one row
     (white-space:nowrap) so the per-letter inline-block spans the header effect
     builds can't break between letters and drop the final "g". That break happens
     on wide viewports / zoomed out, where --t-mega clamps at 200px and the word is
     widest in the 8-column header.
   - The title's deep "g" descender dips into the byline, so nudge just the
     "valjdakosta" word down to clear it. position:relative shifts it visually
     without reflowing "Analyser by". Its hover-underline rides 2px beneath it and
     would land on the sub-text below, so reserve the same amount under the byline
     with padding-bottom (padding doesn't margin-collapse, so the sub-text keeps a
     normal gap to the nudged word).
   Both offsets are em of the byline - which scales with --t-mega - so the nudge and
   its clearance track the title as it grows with viewport width. */
.site-mark--changelog .site-title { letter-spacing: -0.09em; white-space: nowrap; }
/* The same byline nudge (without the title tweaks) clears the "y" descender of
   "Privacy", so "valjdakosta" sits indented to match the changelog header. */
.site-mark--changelog .site-byline-link,
.site-mark--privacy .site-byline-link,
.site-mark--samples .site-byline-link,
.site-mark--compare .site-byline-link {
  position: relative;
  top: 0.37em;
}
.site-mark--changelog .site-byline,
.site-mark--privacy .site-byline,
.site-mark--samples .site-byline,
.site-mark--compare .site-byline { padding-bottom: 0.37em; }
/* That byline padding-bottom (clearance for the nudged "valjdakosta" underline)
   also pushes the grey description - and the nav chip row below it - down by the
   same 0.37em, so on these headers the nav sat lower than on the plain ones
   (Home/About/Formats/Stats). Pull the description back up by exactly that padding
   (0.37em of the byline, whose font is var(--t-mega) * 0.29) so the nav aligns
   across every page; the nav follows the description, keeping their 24px gap.
   Desktop only: at <=900px the header is a fixed-row grid, so the padding never
   shifted the nav there and .site-sub keeps its own mobile margin. */
@media (min-width: 901px) {
  .site-mark--changelog .site-sub,
  .site-mark--privacy .site-sub,
  .site-mark--samples .site-sub,
  .site-mark--compare .site-sub { margin-top: calc(20px - var(--t-mega) * 0.29 * 0.37); }
}

.site-sub {
  font-size: clamp(15px, 1.6vw, 18px);
  color: var(--muted);
  margin: 20px 0 0;
  max-width: 50ch;
  line-height: 1.45;
}

/* Per-format pages (/formats/<ext>) carry a one-line description (the format
   name, e.g. "MP3 audio") where every other page carries the standard two-line
   tagline. That shorter block pulled the nav chip row up, so it no longer lined
   up with the rest of the site. Reserve the tagline's height (two lines at the
   50ch max-width - wrap count is font-size-independent since text and container
   both scale with the font) so the nav drops to the same baseline everywhere. */
.site-mark--format .site-sub { min-height: calc(1.45em * 2); }

/* Cross-page buttons (About / Patch notes) sitting under the header text.
   Outlined mono pills matching the footer nav button; filled when current. */
.site-mark-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 24px 0 0;
}
/* Same look as the top-right chips (.dark-toggle): small mono pill on the subtle
   surface fill that inverts on hover. */
.header-btn {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 3px 8px;
  background: var(--surface);
  border: var(--bd-hairline);
  color: var(--fg);
  text-decoration: none;
  /* Every chip shares one box model (inline-flex, tight 1em line box) so a
     text <a> and the icon-bearing Share <button> are the same intrinsic
     height. Without this the two heights only match because a single flex row
     stretches them together - once the row wraps, each row stretches on its
     own and the chips visibly change height. */
  display: inline-flex;
  align-items: center;
  line-height: 1;
}
.header-btn:hover { background: var(--fg); color: var(--bg); }
button.header-btn { cursor: pointer; line-height: 1; }

/* The Samples nav chip is accented red (not the default ink/grey) to draw the
   eye to the live demos; hover fills red to match the site's accent language. */
.header-btn[href="/samples"] { color: var(--accent); border-color: var(--accent); }
.header-btn[href="/samples"]:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }

/* Current-page nav chip: a persistent "you are here" fill (the inverted hover
   state, made permanent) so the button for the page you're on always reads as
   active. Keyed off aria-current, so the markup stays semantic and no extra
   class is needed. Every page carries the same full chip row - only the active
   one differs - which is what keeps the nav stable page to page. */
.header-btn[aria-current="page"] { background: var(--fg); color: var(--bg); cursor: default; }
.header-btn[href="/samples"][aria-current="page"],
.header-btn[href="/samples"][aria-current="page"]:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }

/* The "I'm feeling lucky" cell in the per-format .site-nav bar is a <button>;
   strip the native chrome so it sits in the grid exactly like the <a> nav-links
   (font, colour and dividers still come from .nav-link / .format-nav below). */
button.nav-link {
  background: none;
  border-top: 0;
  border-bottom: 0;
  font-family: inherit;
  cursor: pointer;
}
/* The per-format pager mixes <a> and a <button>, which throws off the about
   nav's :first/last-of-type dividers (the lone <button> counts as both). Pin the
   vertical dividers by child position instead, scoped so the other navs are
   untouched. */
.format-nav .nav-link:first-child { border-left: 1px solid var(--nav-line); }
.format-nav .nav-link:not(:first-child) { border-left: 0; }
/* The /formats hub bar carries a single "I'm feeling lucky" cell. Drop it into
   the middle of the three equal columns (from `body:has(.about-page) .site-nav`)
   so it's exactly as wide as if Previous/Next sat beside it, centred, with a
   divider on each side. */
/* `.site-nav` qualifier bumps specificity to (0,3,0) so both dividers beat the
   main-nav `.nav-link:last-of-type { border-right: none }` (0,2,0) - otherwise the
   lone lucky button (which is also :last-of-type) loses its right separator. */
.site-nav.formats-hub-nav .nav-link-lucky {
  grid-column: 2;
  justify-content: center;
  border-left: 1px solid var(--nav-line);
  border-right: 1px solid var(--nav-line);
}

/* Share button carries an icon - lay it out inline next to the label. It's a
   <button>, so reset the native chrome to match the <a> chips exactly. */
.header-btn-share {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  font-family: var(--font-mono);
  line-height: 1;
}
.header-btn-ico {
  width: 1em;
  height: 1em;
  display: block;
  flex: none;
}

/* On small phones the header chip row (Home/About/Patches/Formats/Stats/Samples
   + Share) is tight - shave the gap and chip padding so the Share button stays on
   the same row instead of dropping to a second one. */
@media (max-width: 480px) {
  .site-mark-nav { gap: 6px; }
  .header-btn { padding: 3px 6px; letter-spacing: 0.05em; }
}

/* Principal responsive breakpoints (CSS @media can't read custom properties, so
   these literals recur throughout): 480px = small phone, 700px = phone, 900px =
   tablet / single-column. Header and sections both collapse at 900px. Other
   one-off widths (540/560/600/640/719/760px) appear where a specific component
   needs them. */
@media (max-width: 900px) {
  /* MOBILE / TABLET ONLY (<=900px). Restructure the header into a 5-column grid
     and reorder it: the version moves up onto the kicker (red text) row, top-right;
     then title, byline; then the remaining meta in two clusters (Visitors / Status
     left, Other stuff / Dark mode right); then the description; then the buttons.
     display:contents on BOTH .site-mark and its .site-meta > dl frees every leaf
     (kicker, title, byline, sub, nav AND each meta dt/dd) to be placed directly in
     this header grid, so they can interleave regardless of DOM nesting. Columns:
     [left label][left value][flex gap][right label][right value]. Every page's
     header shares this exact structure, so the fixed placements are safe. */
  .site-header .grid {
    display: grid;
    grid-template-columns: auto auto 1fr auto auto;
    column-gap: 10px;
    row-gap: 0;
    align-items: start;
  }
  .site-mark { display: contents; }
  .site-meta { display: contents; }
  .site-meta dl { display: contents; }

  .site-kicker { grid-column: 1; grid-row: 1; }
  .site-title  { grid-column: 1 / -1; grid-row: 2; }
  .site-byline { grid-column: 1 / -1; grid-row: 3; margin-top: -4px; }
  .site-sub    { grid-column: 1 / -1; grid-row: 7; margin-top: 16px; }
  .site-mark-nav { grid-column: 1 / -1; grid-row: 8; margin-top: 20px; }

  /* Status (dt/dd = children 5-6) sits on the kicker row, top-right. */
  .site-meta dl > :nth-child(5) { grid-column: 4; grid-row: 1; }
  .site-meta dl > :nth-child(6) { grid-column: 5; grid-row: 1; }
  /* Version (1-2) and Visitors (3-4) form the left cluster, rows 4-5. The row-4
     margin-top is the gap between the title/byline block above and the meta. */
  .site-meta dl > :nth-child(1) { grid-column: 1; grid-row: 4; margin-top: 32px; }
  .site-meta dl > :nth-child(2) { grid-column: 2; grid-row: 4; margin-top: 32px; }
  .site-meta dl > :nth-child(3) { grid-column: 1; grid-row: 5; margin-top: 4px; }
  .site-meta dl > :nth-child(4) { grid-column: 2; grid-row: 5; margin-top: 4px; }
  /* Other stuff (7-8) and Dark mode (9-10) form the right cluster, rows 4-5;
     Clear view (11-12) sits directly under Dark mode on row 6. */
  .site-meta dl > :nth-child(7) { grid-column: 4; grid-row: 4; margin-top: 32px; }
  .site-meta dl > :nth-child(8) { grid-column: 5; grid-row: 4; margin-top: 32px; }
  .site-meta dl > :nth-child(9) { grid-column: 4; grid-row: 5; margin-top: 4px; }
  .site-meta dl > :nth-child(10) { grid-column: 5; grid-row: 5; margin-top: 4px; }
  .site-meta dl > :nth-child(11) { grid-column: 4; grid-row: 6; margin-top: 4px; }
  .site-meta dl > :nth-child(12) { grid-column: 5; grid-row: 6; margin-top: 4px; }
}

/* ---------- NAV ---------- */
/* The primary nav owns a small, self-contained colour palette (--nav-*). Every
   surface inside the bar - background, text, dividers, the hover wash, the active
   pill, the search box - draws ONLY from these, never from the page-wide --bg /
   --fg / --rule. That keeps the bar's colours in one place and lets it flip to an
   inverted scheme by re-pointing the palette in a single rule, rather than
   re-skinning a dozen elements.

   Normal state: the palette mirrors the page (paper bar, ink text), so the bar
   looks identical to before. The inverted state (further down) repoints the same
   six variables, and because each variable's VALUE changes, every dependent
   property - including each divider's border colour - re-resolves and transitions
   together. The previous version swapped the global tokens instead, which made
   dividers update unevenly. */
.site-nav {
  --nav-bg:   var(--bg);        /* bar background (paper) */
  --nav-fg:   var(--fg);        /* text (ink) */
  --nav-line: var(--rule);      /* the vertical dividers between cells */
  --nav-seam: var(--hairline);  /* the 1px bottom border under the whole bar */
  --nav-soft: var(--surface);   /* hover / pressed wash */
  --nav-dim:  var(--muted);     /* the 01-03 numbers + search placeholder */

  position: -webkit-sticky;   /* older iOS Safari needs the prefix to pin */
  position: sticky;
  top: 0;
  z-index: 20;
  background-color: var(--nav-bg);
  border-bottom: 1px solid var(--nav-seam);
  display: grid;
  grid-template-columns: 1fr 1fr 1fr auto;
  max-width: 1440px;
  margin: 0 auto;
  padding: 0 var(--pad-x);
  transition: background-color var(--dur-slow) ease, border-color var(--dur-slow) ease;
}
/* The same easing on EVERY descendant (background, text, dividers, search chrome)
   so the whole bar flips between the two palettes in lockstep over 0.3s instead of
   the background sliding while a divider snaps. opacity stays fast (0.15s) so the
   search-button/arrow hover fade isn't slowed. The per-element transitions these
   elements used to carry were removed so this single rule governs them uniformly -
   that uneven mix of durations/properties was part of why dividers lagged. */
.site-nav * {
  transition: background-color var(--dur-slow) ease, color var(--dur-slow) ease,
              border-color var(--dur-slow) ease, opacity var(--dur-snappy) ease;
}
/* Inverted palette - applied ONLY when all three hold:
     body.anr-has-file   a file has been loaded,
     body.anr-nav-stuck  the bar is pinned to the top of the viewport,
     body.anr-nav-live   at least one section link is still live (a non-media file
                         greys every link, and a bar of dimmed text shouldn't flip).
   Re-pointing the six --nav-* variables flips the entire bar at once. The values
   are spelled out per theme (a near-black bar in light mode, near-white in dark)
   - including --nav-seam, so the bottom divider inverts with everything else
   instead of being left behind as a stray line. */
body.anr-has-file.anr-nav-stuck.anr-nav-live .site-nav {
  --nav-bg:   #0a0a0a;
  --nav-fg:   #f4f4f4;
  --nav-line: #383838;
  --nav-seam: #383838;
  --nav-soft: #1f1f1f;
  --nav-dim:  #9a9a9a;
}
:root[data-theme="dark"] body.anr-has-file.anr-nav-stuck.anr-nav-live .site-nav {
  --nav-bg:   #e8e8e8;
  --nav-fg:   #0a0a0a;
  --nav-line: #c2c2c2;
  /* Bottom seam stays the dark-mode hairline grey (#333, as when not stuck) so
     the white pinned bar reads against the dark page instead of blending out. */
  --nav-seam: #333;
  --nav-soft: #d2d2d2;
  --nav-dim:  #5a5a5a;
}
/* Format landing + hub navs (prev / lucky / next) keep 3 equal columns. */
body:has(.about-page) .site-nav.format-nav { grid-template-columns: repeat(3, 1fr); }
/* About page primary nav has 5 section links (Why / Where / What / How / FAQ) and
   no home/search chrome - 5 equal columns in a single row. The grid keeps it to one
   row; the links use the default left-aligned .nav-link styling like the rest of
   the site. */
body:has(.about-page) .site-nav:not(.format-nav) { grid-template-columns: repeat(5, 1fr); }
.nav-link {
  min-width: 0;
  padding: 16px 4px;
  text-decoration: none;
  color: var(--nav-fg);
  font-size: var(--t-small);
  font-weight: 500;
  border-right: 1px solid var(--nav-line);
  display: flex;
  align-items: baseline;
  gap: 10px;
  /* transition handled by the shared `.site-nav *` rule above */
}
.nav-link:last-of-type { border-right: none; }
/* The search chrome carries a divider on the bar's right edge; mirror it on the
   left so Photo gets the matching vertical rule it was missing. (Also on the About
   nav, which has no search chrome, so the outer edges match the main page.) */
.nav-link:first-of-type { border-left: 1px solid var(--nav-line); }
/* The right-edge divider normally comes from the search chrome's left border; when
   the search is hidden (no file yet, or the About page) the last link would lose it,
   so close the edge here. */
body:not(.anr-has-file) .nav-link:last-of-type { border-right: 1px solid var(--nav-line); }
.nav-link:hover { background-color: var(--nav-soft); color: var(--nav-fg); }
.nav-link.is-active { background-color: var(--nav-fg); color: var(--nav-bg); }
.nav-link.is-active .nav-num { color: var(--nav-bg); opacity: 0.5; }
/* Greyed-out + unresponsive when the link's section isn't on the page (e.g. the
   analysed file isn't photo/audio/video). Home and Search are separate controls. */
.nav-link.is-disabled {
  opacity: 0.35;
  pointer-events: none;
  cursor: default;
}
.nav-link.has-data::after {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
  margin-left: auto;
}
.nav-link.is-active.has-data::after {
  background: var(--nav-bg);
}
.nav-search {
  /* Hidden until a file is analysed - revealed by body.anr-has-file below. */
  display: none;
  align-items: stretch;
  border-left: 1px solid var(--nav-line);
  border-right: 1px solid var(--nav-line);
}
body.anr-has-file .nav-search { display: flex; }
.nav-search-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--nav-fg);
  opacity: 0.5;
  height: 46px;
  /* colour + opacity transitions handled by the shared `.site-nav *` rule */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  width: 46px;
}
.nav-search-btn:hover { opacity: 1; }
.nav-search.is-open .nav-search-btn { display: none; }
.nav-search-input {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: transparent;
  color: var(--nav-fg);
  border: 1px solid var(--nav-seam);   /* matches the original --hairline framing */
  border-radius: 0;
  outline: none;
  display: none;
}
.nav-search.is-open .nav-search-input {
  display: block;
  width: 180px;
  padding: 6px 10px;
  opacity: 1;
}
.nav-search-input:focus { border-color: var(--accent); }
.nav-search-input::placeholder {
  color: var(--nav-dim);
  opacity: 0.4;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 11px;
}
.nav-search-arrow { display: none; }
.nav-search.is-open .nav-search-arrow {
  display: block;
  background: none;
  border: none;
  cursor: pointer;
  color: var(--nav-fg);
  font-size: 18px;
  line-height: 1;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.5;
  /* colour + opacity transitions handled by the shared `.site-nav *` rule */
}
.nav-search.is-open .nav-search-arrow:hover { opacity: 1; background-color: var(--nav-soft); }
.nav-search.is-open .nav-search-arrow:active { opacity: 1; background-color: var(--nav-fg); color: var(--nav-bg); }
.nav-search-arrow:hover { opacity: 1; }
.search-overlay { display: none; }
@media (max-width: 700px) {
  .search-overlay {
    display: block;
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 100;
    background: var(--bg);
    border-bottom: var(--bd-hairline);
    padding: 10px 16px;
    opacity: 0;
    transform: translateY(-100%);
    pointer-events: none;
    transition: opacity 0.25s ease, transform 0.25s ease;
  }
  .search-overlay.is-open {
    opacity: 1;
    transform: translateY(0);
    pointer-events: auto;
  }
}
.search-overlay-bar {
  display: flex;
  align-items: center;
  gap: 10px;
}
.search-overlay-input {
  flex: 1;
  font-family: var(--font-mono);
  font-size: 14px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 10px 12px;
  background: transparent;
  color: var(--fg);
  border: var(--bd-hairline);
  border-radius: 0;
  outline: none;
}
.search-overlay-input:focus { border-color: var(--accent); }
.search-overlay-input::placeholder {
  color: var(--muted);
  opacity: 0.4;
  text-transform: uppercase;
}
.search-overlay-close {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--fg);
  font-size: 24px;
  line-height: 1;
  padding: 6px;
  opacity: 0.5;
}
.search-overlay-close:hover { opacity: 1; }
.search-overlay-arrow {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--fg);
  font-size: 28px;
  line-height: 1;
  padding: 0;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0.5;
  transition: opacity var(--dur-snappy) ease, background var(--dur-snappy) ease;
}
.search-overlay-arrow:hover { opacity: 1; background: var(--surface); }
.search-overlay-arrow:active { opacity: 1; background: var(--fg); color: var(--bg); }
.anr-search-highlight {
  background: rgba(var(--accent-rgb), 0.06);
}
.anr-search-current {
  background: rgba(var(--accent-rgb), 0.15);
}
.nav-num {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--nav-dim);
  letter-spacing: 0.08em;
}

/* ---------- SECTIONS ---------- */
.section {
  padding: var(--pad-y) 0 0;
  scroll-margin-top: var(--nav-offset);
  max-width: 1440px;
  margin: 0 auto;
}
.section-meta {
  grid-column: 1 / span 3;
  align-self: start;
  /* Pin the number + kicker to the left while its section scrolls past. The
     containing block is the grid, so it stays put until the section ends, then
     scrolls away with it. Reset to static on mobile (see ≤900px override). */
  position: sticky;
  top: calc(var(--nav-offset) + 16px);
  padding-top: 4px;
  background: var(--bg);
  padding: 24px;
  border: var(--bd-hairline);
}
.section-num {
  font-family: var(--font-mono);
  font-size: var(--t-huge);
  font-weight: 500;
  line-height: 0.9;
  letter-spacing: -0.03em;
  display: block;
}
.section-kicker {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 12px;
  display: block;
}
.section-content {
  grid-column: 5 / -1;
  background: var(--bg);
  padding: 32px;
  border: var(--bd-hairline);
}
.section-head {
  font-size: var(--t-h1);
  font-weight: 500;
  letter-spacing: -0.025em;
  line-height: 1.05;
  margin: 0 0 16px;
  max-width: 22ch;
}
/* Section headings react to hover (the per-letter weight effect), so show the
   normal arrow cursor rather than the text I-beam - on the elements AND on the
   per-letter <span>s the hover effect injects (so the I-beam never shows over the
   glyphs). user-select:none also keeps a stray drag from selecting the text. The
   .is-tappable pointer rule below is more specific, so mobile tap still wins. */
.section-num, .section-kicker, .section-head, .stats-total-num,
.section-num span, .section-kicker span, .section-head span, .stats-total-num span {
  cursor: default;
  -webkit-user-select: none;
  user-select: none;
}
.section-lede {
  font-size: clamp(14px, 1.4vw, 17px);
  color: var(--muted);
  max-width: 60ch;
  margin: 0 0 32px;
}

/* Small grey footnote under the stats lede - when file counting began. Pulled up
   tight beneath the lede (which carries a 32px bottom margin of its own). */
.stats-since {
  font-size: var(--t-small);
  color: var(--muted);
  margin: -24px 0 32px;
}

/* Heading mirrored into the numbered meta card (clone created in app.js). Hidden
   by default; on mobile, once the section has analysed a file (.is-analysed), it
   replaces the original head + lede so the results lead. Desktop keeps the full
   head + lede and never shows this clone. */
.section-meta-head { display: none; }
.section.is-tappable .section-head,
.section.is-tappable .section-lede,
.section.is-tappable .section-meta-head { cursor: pointer; }
@media (max-width: 700px) {
  .section.is-analysed .section-head,
  .section.is-analysed .section-lede { display: none; }
  .section.is-analysed .section-meta {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: baseline;
    gap: 0 10px;
  }
  .section.is-analysed .section-num { grid-column: 1; grid-row: 1; }
  .section.is-analysed .section-meta-head {
    display: block;
    grid-column: 2;
    grid-row: 1;
    margin: 0;
    font-size: var(--t-small);
    font-weight: 500;
    line-height: 1.2;
    letter-spacing: -0.02em;
    color: var(--fg);
  }
  .section.is-analysed .section-kicker {
    grid-column: 1 / -1;
    grid-row: 2;
  }
  .section.is-analysed .section-meta-slot { grid-column: 1 / -1; }
}

@media (max-width: 900px) {
  .section-meta { grid-column: 1 / -1; position: static; margin-bottom: 8px; }
  .section-content { grid-column: 1 / -1; }
  .section-num { font-size: clamp(40px, 14vw, 80px); }
}

/* ---------- DROPZONE ---------- */
.anr-dropzone {
  border: var(--bd-hairline);
  background: var(--bg);
  margin: 24px 0 32px;
  transition: background var(--dur-fast) ease, border-color var(--dur-base) ease;
}
.anr-dropzone:hover {
  border-color: var(--accent);
}
.anr-dropzone-inner {
  transition: opacity var(--dur-snappy) ease;
}
.anr-dropzone:hover .anr-dropzone-inner {
  opacity: 0.6;
}
/* Hovering the dropzone tints its whole outer border accent (above). The
   buttons inside draw their own inner borders (Info's top/left, the audio
   strip's top, the Record/Live divider), so tint those to match - otherwise the
   card edge goes red while the button edges stay dark. */
.anr-dropzone:hover .fmt-help-btn {
  border-top-color: var(--accent);
  border-right-color: var(--accent);
}
.anr-dropzone:hover .anr-audio-modes { border-top-color: var(--accent); }
.anr-dropzone:hover .anr-audio-modes .anr-btn { border-right-color: var(--accent); }
.anr-dropzone.is-dragover {
  background: var(--surface);
  outline: 2px dashed var(--accent);
  outline-offset: -8px;
}
.anr-dropzone-inner {
  display: block;
  padding: clamp(40px, 8vw, 88px) 24px;
  text-align: center;
  cursor: pointer;
}
.anr-drop-icon {
  font-family: var(--font-mono);
  font-size: clamp(28px, 5vw, 44px);
  font-weight: 300;
  display: inline-block;
  margin-bottom: 10px;
}
.anr-dropzone-inner strong {
  display: block;
  font-size: var(--t-h2);
  font-weight: 500;
  letter-spacing: -0.01em;
  margin: 4px 0 6px;
}
.anr-dropzone-inner p {
  margin: 6px 0;
  color: var(--muted);
}
.anr-hint {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  margin-top: 14px !important;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  overflow-wrap: break-word;
  overflow-wrap: anywhere;
}
/* The "analysis may be limited" caveat. Styled like a hint but kept on its own
   class so it survives the mobile rule that hides the format-list hints. */
.anr-drop-warn {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  opacity: 0.5;
  margin-top: 14px !important;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  overflow-wrap: anywhere;
}
.quickdrop-zone .anr-drop-warn { margin-top: 10px !important; }

.anr-audio-modes {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: center;
  padding: 0 24px 28px;
}

/* ---------- BUTTONS ---------- */
.anr-btn {
  font-family: var(--font-sans);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.01em;
  padding: 9px 16px;
  border: var(--bd-hairline);
  background: var(--bg);
  color: var(--fg);
  cursor: pointer;
  border-radius: 0;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.anr-btn:hover {
  background: var(--fg);
  color: var(--bg);
}
.anr-btn:disabled {
  opacity: 0.35;
  cursor: not-allowed;
  background: var(--bg);
  color: var(--fg);
}
.anr-btn.is-active,
.anr-btn.is-recording {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: var(--accent);
}
/* A real CSS circle (like the site's other status dots), not the ● glyph, which
   sat low on the baseline inside the button. currentColor so it inverts with the
   button on hover / while recording. */
.anr-rec-dot {
  display: inline-block;
  width: 0.62em;
  height: 0.62em;
  background: currentColor;
  border-radius: 50%;
  vertical-align: middle;
  position: relative;
  top: -0.04em;
}
.anr-btn.is-recording .anr-rec-dot { animation: anr-blink 1s steps(2, end) infinite; }
@keyframes anr-blink { 50% { opacity: 0; } }

/* Prominent red call-to-action button (the "Analyse audio"/"Analyse photo"
   prompts a video drops into the Sound/Photo sections). Larger than the stock
   .anr-btn and filled with the accent so it reads as the one thing to click. */
.anr-btn--cta {
  font-size: var(--t-base);
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 14px 28px;
  background: var(--accent);
  color: var(--accent-fg);
  border-color: var(--accent);
}
.anr-btn--cta:hover {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: var(--accent);
  filter: brightness(1.08);
}
.anr-btn--cta:disabled {
  background: var(--accent);
  color: var(--accent-fg);
}

.anr-btn-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0; }
/* `display: flex` above outranks the UA `[hidden]` rule, so a `.hidden = true`
   row would otherwise stay as an empty gap - collapse it explicitly. */
.anr-btn-row[hidden] { display: none; }

/* G-code viewer "colour by line type" legend - a row of toggle chips, one per
   feature, each carrying its colour swatch (matching the WebGL palette). Same chip
   look and gap as the CNC tool picker below, so the two rows read as a set. */
.anr-gcode-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 8px 0 0;
}
.anr-gcode-swatch {
  width: 12px;
  height: 12px;
  border: 1px solid var(--hairline);
  border-radius: 0;
  flex: none;
}

/* CNC tool table - a true multi-column grid, so override the 2-column readout's
   wide first-column header rule and tighten the cells. */
.anr-gcode-tooltable th,
.anr-gcode-tooltable td {
  width: auto;
  padding: 8px 14px 8px 0;
  white-space: nowrap;
}
.anr-gcode-tooltable th {
  font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.anr-gcode-tooltable td:first-child { color: var(--accent); font-weight: 600; }
/* The Type column carries a free-text tool description that can run long; let it
   wrap at word boundaries instead of forcing the whole table wide. Override the
   readout's break-all so it never splits a word mid-character. */
.anr-gcode-tooltable th:nth-child(2),
.anr-gcode-tooltable td:nth-child(2) { white-space: normal; word-break: normal; overflow-wrap: anywhere; }

/* On a phone all six columns can't fit, so the wrappable Type column collapsed to
   one character per line. Below 640px, drop the table grid and render each tool as
   a self-labelled card (the header row is hidden; each cell shows its data-label). */
@media (max-width: 640px) {
  .anr-gcode-tooltable,
  .anr-gcode-tooltable tbody { display: block; width: 100%; }
  .anr-gcode-tooltable tr:first-child { display: none; }   /* the <th> header row */
  .anr-gcode-tooltable tr {
    display: block;
    border: var(--bd-hairline);
    padding: 4px 12px 8px;
    margin-bottom: 10px;
  }
  .anr-gcode-tooltable tr:hover td { background: transparent; }
  .anr-gcode-tooltable td {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 18px;
    padding: 5px 0;
    border: 0;
    border-bottom: var(--bd-rule);
    white-space: normal;
    word-break: normal;
    overflow-wrap: anywhere;
    text-align: right;
  }
  .anr-gcode-tooltable td:last-child { border-bottom: 0; }
  .anr-gcode-tooltable td::before {
    content: attr(data-label);
    flex: none;
    font-family: var(--font-mono);
    font-size: var(--t-tiny);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
    text-align: left;
    white-space: nowrap;
  }
  /* The tool id (T2) reads as the card title. */
  .anr-gcode-tooltable td:first-child {
    font-size: var(--t-small);
    padding-top: 8px;
  }
}

/* CNC tool picker - neutral buttons like the 3MF parts chips, each carrying its
   tool's colour swatch. Shown by default; toggled off (.is-off) dims the chip. */
.anr-gcode-toolchip { display: inline-flex; align-items: center; gap: 7px; }
.anr-gcode-toolchip .anr-gcode-swatch { width: 11px; height: 11px; flex: none; }
.anr-gcode-toolchip.is-off { color: var(--muted); opacity: 0.45; }
.anr-gcode-toolchip.is-off .anr-gcode-swatch { opacity: 0.4; }

/* ---- G-code / 3D viewer toolbar -------------------------------------------
   Every control under the canvas lives in one panel, divided from the viewport by
   a hairline and stacked into clearly-labelled rows (display buttons, what is
   shown, then the two sliders) - so the section reads as a single control panel
   rather than a loose pile of rows. */
.anr-gcode-orbithint {
  margin: 8px 0 0;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  text-align: center;
}
/* Bottom-right camera-control stack (the view cube owns bottom-left): the manual
   zoom pad, the scroll-zoom toggle, then the fullscreen button at the corner. One
   positioned column so the three read as a set and stack with a consistent gap.
   Shared by every interactive 3D viewer (the G-code toolpath and the STL/STEP/3MF
   mesh viewer) via the neutral .anr-cam* class set; the .anr-gcode-* names are kept
   as aliases so nothing that still references them breaks. */
.anr-cam, .anr-gcode-cambtns {
  position: absolute;
  right: 12px;
  bottom: 12px;
  z-index: 8;   /* above the toolbar overlay that pins along the bottom in fullscreen */
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
}
.anr-cam .anr-btn, .anr-gcode-cambtns .anr-btn { box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35); }
/* The two wide text buttons match each other's width; the square zoom pad stays
   flush to the right edge above them. The scroll-zoom toggle is a stock .anr-btn
   toggle: white when off, accent-red (.is-active) while on. */
.anr-cam-zoombtn, .anr-cam-fsbtn,
.anr-gcode-zoombtn, .anr-gcode-fsbtn { align-self: stretch; text-align: center; }
/* Manual zoom pad: a + stacked over a -, hold to zoom continuously. */
.anr-cam-pad, .anr-gcode-zoompad { display: flex; flex-direction: column; gap: 8px; }
.anr-cam-zbtn, .anr-gcode-zbtn {
  width: 34px;
  height: 34px;
  padding: 0;
  font-family: var(--font-mono);
  font-size: 20px;
  line-height: 1;
  touch-action: none;   /* hold-to-zoom on touch must not also scroll the page */
  user-select: none;
  -webkit-user-select: none;
}
/* Mobile browsers (notably iOS Safari) don't support element fullscreen, and there
   is no wheel to gate - hide those two on a small canvas, but keep the manual zoom
   pad, which is the only precise zoom aid touch users get besides pinch. */
@media (max-width: 700px) {
  .anr-cam-fsbtn, .anr-cam-zoombtn,
  .anr-gcode-fsbtn, .anr-gcode-zoombtn { display: none; }
}

/* Colour panel, overlaid top-right of the viewer: the colour-mode selector with the
   legend / key for the active mode directly beneath it. The canvas always clears to a
   dark grey, so the legend is dark-translucent with light text in either theme. */
.anr-gcode-colpanel {
  position: absolute;
  top: 12px;
  right: 12px;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  max-width: 60%;
}
/* Colour selector - ~7% wider than the stock select for breathing room. */
.anr-gcode-colsel { cursor: pointer; padding-left: 22px; padding-right: 34px; }

/* Top-left overlay slot (Quality popup). The viewport clips overflow, so the popup
   opens downward here rather than the stock upward direction, and sits above the
   bottom-left view cube (z-index 3) so when they meet the popup overlaps the cube. */
.anr-gcode-topleft { position: absolute; top: 12px; left: 12px; z-index: 6; }
.anr-gcode-topleft .anr-aa-panel { top: calc(100% + 6px); bottom: auto; z-index: 6; }
.anr-gcode-legendbody {
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 8px 10px;
  background: rgba(18, 18, 22, 0.66);
  border: var(--bd-hairline);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  font-family: var(--font-mono);
  color: #f4f4f6;
  pointer-events: none;
}
.anr-gcode-legendbody:empty { display: none; }
.anr-gcode-legendbody.is-hidden { display: none; }
/* Small toggle that shows/hides the colour legend, sitting under the colour select. */
.anr-gcode-legendtoggle { font-size: var(--t-tiny); padding: 5px 11px; }
.anr-gcode-key-row { display: flex; align-items: center; gap: 8px; }
.anr-gcode-key-label { font-size: var(--t-tiny); white-space: nowrap; }
.anr-gcode-ramp { display: flex; flex-direction: column; gap: 6px; }
.anr-gcode-ramp-title {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  opacity: 0.82;
}
.anr-gcode-ramp-body { display: flex; gap: 8px; align-items: stretch; }
.anr-gcode-ramp-bar {
  width: 12px;
  height: 96px;
  background: linear-gradient(to top,
    rgb(46, 82, 235) 0%, rgb(26, 204, 219) 25%, rgb(56, 209, 77) 50%,
    rgb(247, 204, 51) 75%, rgb(245, 66, 51) 100%);
}
.anr-gcode-ramp-scale {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  font-size: var(--t-tiny);
  white-space: nowrap;
}

.anr-gcode-toolbar {
  margin-top: 12px;
  padding-top: 14px;
  border-top: var(--bd-hairline);
  display: flex;
  flex-direction: column;
  gap: 12px;
}
/* Button/toggle rows drop the stock .anr-btn-row / .anr-gcode-legend margins so the
   toolbar's own gap is the only vertical rhythm. */
.anr-gcode-toolbar .anr-gcode-toolrow,
.anr-gcode-toolbar .anr-gcode-legend { margin: 0; align-items: center; }
.anr-gcode-toolbar .anr-spd-warn { margin: 0; }
/* The view-control row (spin / reset / perspective / rapids / tool changes) above the
   viewer: equal-width buttons that together span the full width of the row. */
.anr-gcode-viewrow { flex-wrap: nowrap; }
.anr-gcode-viewrow .anr-btn {
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* On phones the six controls won't fit one row legibly - wrap them three to a
   row (so six become two rows of three). Each button is locked to a third of the
   width minus the two 8px gaps between three items. */
@media (max-width: 700px) {
  .anr-gcode-viewrow { flex-wrap: wrap; }
  .anr-gcode-viewrow .anr-btn { flex: 0 0 calc((100% - 16px) / 3); }
}
/* Fullscreen: the toolbar is reparented into the viewer and pinned along the bottom as a
   scrollable, blurred overlay so every control (view buttons, sliders, playback, quality)
   stays reachable without leaving fullscreen. The camera stack (zoom pad / scroll zoom /
   Exit) is lifted above the bar by gcode.js, so no right padding is needed to clear it. */
.anr-gcode-toolbar--fs {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 6;
  margin: 0;
  padding: 12px 16px;
  border-top: var(--bd-hairline);
  /* Solid (no backdrop-filter): a filter would make this the containing block for the
     position:fixed popups below and re-clip them inside this scroll box. */
  background: rgba(12, 12, 16, 0.95);
  max-height: 48%;
  overflow-y: auto;
}
/* Fullscreen starts collapsed to just the play bar (Play + progress + the More
   button), so the canvas keeps the screen - "More controls" expands the full set.
   The rules hide every toolbar block except the player, and inside the player
   everything after its first row (the speed/follow row and the real-time warning). */
.anr-gcode-toolbar--fs.is-collapsed > *:not(.anr-gcode-player) { display: none; }
.anr-gcode-toolbar--fs.is-collapsed .anr-gcode-player > *:not(:first-child) { display: none; }
/* The More/Fewer controls toggle only exists in fullscreen. Pinned to the far right
   of the play row (margin-left:auto), with extra clearance from the move counter on
   its left so the two never crowd; flex-none so it keeps its full width and padding
   instead of being squeezed by the slider. */
.anr-gcode-fsmore { display: none; }
.anr-gcode-toolbar--fs .anr-gcode-fsmore {
  display: inline-block;
  white-space: nowrap;
  flex: 0 0 auto;
  margin-left: auto;
  padding-left: 20px;
  padding-right: 20px;
}
/* The counter sits between the slider and the button - give it breathing room. */
.anr-gcode-toolbar--fs .anr-gcode-playrow .anr-gcode-slider-val { margin-right: 12px; }
/* The two player rows (Play + progress bar, then Speed / Follow / Tool changes).
   Classes, not inline styles, so the collapsed rule above can hide the second row. */
.anr-gcode-playrow { display: flex; align-items: center; gap: 8px; }
.anr-gcode-playctrl { display: flex; align-items: center; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
/* The bar is always dark, which defeats the sliders' theme-dependent colours (a
   light-theme hairline track disappears on it). Use the same on-dark tokens as the
   fullscreen spectrogram controls: a brighter track, a slightly larger accent thumb
   (white on hover, not the theme fg), and on-dark text for labels and readouts. */
.anr-gcode-toolbar--fs .anr-range::-webkit-slider-runnable-track { height: 4px; background: var(--muted-on-dark); }
.anr-gcode-toolbar--fs .anr-range::-moz-range-track { height: 4px; background: var(--muted-on-dark); }
.anr-gcode-toolbar--fs .anr-range::-webkit-slider-thumb { width: 6px; height: 16px; margin-top: -6px; }
.anr-gcode-toolbar--fs .anr-range::-moz-range-thumb { width: 6px; height: 16px; }
.anr-gcode-toolbar--fs .anr-range:hover::-webkit-slider-thumb { background: var(--on-dark); }
.anr-gcode-toolbar--fs .anr-range:hover::-moz-range-thumb { background: var(--on-dark); }
.anr-gcode-toolbar--fs .anr-gcode-slider-val,
.anr-gcode-toolbar--fs .anr-gcode-rowlabel { color: var(--muted-on-dark); }
/* ---- Export clip (G-code viewer) ---- */
/* Settings rows in the Export clip popup: a mono label column, then the preset /
   toggle buttons. Reuses .anr-btn + is-active for the segmented choices. */
/* Never taller than the viewport (keeps the modal's 24px top/bottom padding); the
   sections stay stacked one under the other, and it is not scrollable - it fits by
   reshaping instead: wider (two-across rows) on a wide-short screen, and tighter
   spacing on a phone (below). */
/* Compound selector so this beats the generic ".anr-modal-card { max-width: 360px }"
   further down the file (equal specificity there would otherwise win on source order,
   which is why the popup width never changed). */
.anr-modal-card.anr-clip-card { position: relative; max-width: 400px; max-height: calc(100vh - 48px); max-height: calc(100dvh - 48px); }
/* Corner close button. Hidden on desktop (Cancel + backdrop + Esc close there);
   only appears in the mobile fullscreen layout below. Sharp-cornered, no border. */
.anr-clip-x {
  display: none;
  position: absolute;
  top: 6px;
  right: 6px;
  width: 40px;
  height: 40px;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--muted);
  font-size: 26px;
  line-height: 1;
  cursor: pointer;
}
.anr-clip-x:hover { color: var(--fg); }
/* When the screen is short (and wide enough), the card gets WIDER and the rows
   inside each section sit two-across, so it is shorter and still fits without
   scrolling. Sections stay stacked - only the rows within them flow sideways. */
@media (max-height: 780px) and (min-width: 720px) {
  .anr-modal-card.anr-clip-card { max-width: 680px; }
  .anr-clip-rows { display: grid; grid-template-columns: 1fr 1fr; gap: 0 24px; align-items: start; }
  .anr-clip-rows > * { min-width: 0; }
}
/* On a phone the panel goes fullscreen: the overlay padding is dropped so the card
   fills the whole viewport (no border, edge to edge), a corner X appears to close it,
   and the vertical spacing is tightened so the whole control set fits. The card can
   scroll only if a very small screen still overflows after tightening - a safety net,
   normally invisible. */
@media (max-width: 719px) {
  /* Two classes so this beats the generic ".anr-modal { padding: 24px }" further down
     the file (a single-class rule there would otherwise win on source order). */
  .anr-modal.anr-modal--clip { padding: 0; }
  .anr-modal-card.anr-clip-card {
    max-width: none;
    height: 100vh;
    height: 100dvh;
    max-height: none;
    border: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: 44px 16px 20px;   /* extra top room clears the corner X */
  }
  .anr-clip-x { display: flex; }
  .anr-clip-card .anr-modal-kicker { margin-bottom: 6px; }
  .anr-clip-card .anr-modal-title { margin-bottom: 12px; }
  .anr-clip-card .anr-clip-sec { margin: 9px 0 6px; padding-bottom: 4px; }
  .anr-clip-card .anr-clip-sec:first-of-type { margin-top: 2px; }
  .anr-clip-card .anr-clip-row { margin-bottom: 6px; }
  .anr-clip-card .anr-clip-opt { padding: 5px 9px; }
  .anr-clip-card .anr-clip-foot { margin-top: 10px; padding-top: 10px; }
}
.anr-clip-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 10px;
}
.anr-clip-lab {
  flex: 0 0 62px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.anr-clip-opts { display: flex; flex-wrap: wrap; gap: 6px; flex: 1; min-width: 0; }
.anr-clip-opt { padding: 6px 10px; }
/* Custom time / lines-per-second fields: a bordered mono number input with a muted
   unit label, sharp-cornered to match the rest of the panel. */
.anr-clip-field {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
}
.anr-clip-num {
  width: 72px;
  padding: 6px 8px;
  border: var(--bd-hairline);
  border-radius: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: var(--t-small);
}
.anr-clip-num:focus { outline: none; border-color: var(--fg); }
/* The two custom fields (seconds + lines/s) sit side by side, never wrapping apart. */
.anr-clip-opts--fields { flex-wrap: nowrap; gap: 10px; }
/* Quiet section divider grouping the rows (Frame / Output / Camera): a short mono
   caps word underlined by a hairline, sitting flush-left with the row labels. */
.anr-clip-sec {
  margin: 16px 0 10px;
  padding-bottom: 6px;
  border-bottom: var(--bd-hairline);
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--fg);
}
.anr-clip-sec:first-of-type { margin-top: 4px; }
.anr-clip-est {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
}
/* Footer bar: the live size estimate on the left, the Cancel / Render actions on
   the right, over a hairline rule. Wraps to stacked rows when the card is narrow. */
.anr-clip-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 14px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: var(--bd-hairline);
}
.anr-clip-foot .anr-clip-est { flex: 2 1 120px; min-width: 0; margin: 0; }
.anr-clip-foot .anr-modal-actions { flex: 1 1 160px; }
/* Render-progress chip pinned top-centre over the canvas while a clip renders:
   the fixed dark-translucent overlay treatment (media-canvas exception), so it
   reads on any toolpath in either theme. */
.anr-gcode-clipstat {
  position: absolute;
  top: 12px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 8;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 12px;
  background: rgba(10, 12, 16, 0.82);
  border: 1px solid rgba(255, 255, 255, 0.22);
  color: #eef2ff;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  white-space: nowrap;
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.4);
}
.anr-gcode-clipstat .anr-btn {
  border-color: rgba(255, 255, 255, 0.35);
  color: #eef2ff;
  background: transparent;
}
.anr-gcode-clipstat .anr-btn:hover { border-color: rgba(255, 255, 255, 0.7); }
/* Speed popup while open in fullscreen: reparented onto the viewer wrap (see gcode.js) and
   pinned as a centred overlay above everything - canvas, view cube, toolbar, Exit button. */
.anr-spd-panel.anr-spd-panel--fs {
  position: absolute;
  left: 50%;
  right: auto;
  bottom: 92px;
  top: auto;
  transform: translateX(-50%);
  max-height: 72vh;
  overflow-y: auto;
  z-index: 90;
}
/* The label that fronts every toggle/slider row. A shared min-width lines the row
   contents up down the left edge so the panel scans cleanly. */
.anr-gcode-rowlabel {
  flex: none;
  min-width: 92px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}
.anr-gcode-slider {
  display: flex;
  align-items: center;
  gap: 10px 12px;
  flex-wrap: wrap;
}
.anr-gcode-slider .anr-range { flex: 1; min-width: 150px; }
.anr-gcode-slider-val {
  flex: none;
  margin-left: auto;
  min-width: 56px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  text-align: right;
  white-space: nowrap;
  font-feature-settings: "tnum";
}
.anr-gcode-slider-val--wide { min-width: 104px; }

/* Live playback HUD under the play bar: elapsed / total time, current layer, and the
   active tool (with a colour swatch). Monospace + tabular figures so the ticking
   numbers don't jitter; items divided by a hairline rule. */
.anr-gcode-hud {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin-top: 8px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  font-feature-settings: "tnum";
}
.anr-gcode-hud-item {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
}
.anr-gcode-hud-item + .anr-gcode-hud-item {
  padding-left: 14px;
  border-left: var(--bd-hairline);
}
.anr-gcode-hud-swatch {
  flex: none;
  width: 10px;
  height: 10px;
  border: var(--bd-hairline);
  background: #ccc;
}
.anr-gcode-toolbar--fs .anr-gcode-hud { color: var(--muted-on-dark); }

/* Sliders in the 3D / G-code viewers - the same hairline track + thin square
   accent thumb the spectrogram controls use, so they match the site aesthetic. */
.anr-range {
  -webkit-appearance: none;
  appearance: none;
  height: 16px;
  background: transparent;
  cursor: pointer;
  vertical-align: middle;
}
.anr-range::-webkit-slider-runnable-track { height: 3px; background: var(--hairline); border-radius: 0; }
.anr-range::-moz-range-track { height: 3px; background: var(--hairline); border-radius: 0; }
.anr-range::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 4px; height: 14px; margin-top: -5.5px;
  background: var(--accent); border: 0; border-radius: 0;
}
.anr-range::-moz-range-thumb { width: 4px; height: 14px; background: var(--accent); border: 0; border-radius: 0; }
.anr-range:focus { outline: none; }
.anr-range:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--accent); }
.anr-range:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--accent); }
.anr-range:hover::-webkit-slider-thumb { background: var(--fg); }
.anr-range:hover::-moz-range-thumb { background: var(--fg); }

/* Selects styled to sit with the .anr-btn buttons: square, same border/padding,
   with a custom chevron (native selects ignore most of it otherwise). */
select.anr-select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 28px;
  background-image:
    linear-gradient(45deg, transparent 50%, var(--fg) 50%),
    linear-gradient(135deg, var(--fg) 50%, transparent 50%);
  background-position: calc(100% - 15px) 52%, calc(100% - 10px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  cursor: pointer;
}
select.anr-select:hover {
  background-color: var(--fg);
  color: var(--bg);
  /* Re-declare every background longhand: .anr-btn:hover's `background` shorthand
     would otherwise reset the caret's position/size/repeat and make it tile. */
  background-image:
    linear-gradient(45deg, transparent 50%, var(--bg) 50%),
    linear-gradient(135deg, var(--bg) 50%, transparent 50%);
  background-position: calc(100% - 15px) 52%, calc(100% - 10px) 52%;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
select.anr-select option { background: var(--bg); color: var(--fg); }

/* Anti-aliasing / quality popup for the 3D viewers. */
.anr-aa-wrap { position: relative; display: inline-flex; }
.anr-aa-panel {
  position: absolute;
  left: 0;
  bottom: calc(100% + 6px);
  z-index: 5;
  width: 252px;
  padding: 12px;
  background: var(--bg);
  border: var(--bd-hairline);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.anr-aa-panel.is-hidden { display: none; }
.anr-aa-title {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Quality settings are site-style buttons stacked in the panel: full width, left-aligned,
   border lights red (.is-on) when the setting is active, grey when off. No descriptions. */
.anr-aa-btn { width: 100%; text-align: left; }
.anr-aa-btn:hover { background: rgba(var(--accent-rgb), 0.06); color: var(--fg); }
.anr-aa-btn.is-on { border-color: var(--accent); color: var(--accent); }
.anr-aa-btn.is-on:hover { background: rgba(var(--accent-rgb), 0.10); color: var(--accent); }

/* Playback-speed popup - two columns of presets (lines/s and total length) plus a
   custom field. Shares the .anr-aa-panel chrome; just wider with its own layout. */
.anr-spd-panel { width: auto; min-width: 300px; }
.anr-spd-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.anr-spd-col { display: flex; flex-direction: column; gap: 6px; }
.anr-spd-title {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 2px;
}
.anr-spd-opt { width: 100%; text-align: left; padding: 7px 12px; font-size: var(--t-small); }
.anr-spd-custom { display: flex; gap: 6px; padding-top: 11px; border-top: var(--bd-hairline); }
.anr-spd-input {
  flex: 1;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  padding: 8px 10px;
  background: var(--bg);
  color: var(--fg);
  border: var(--bd-hairline);
}
.anr-spd-input:focus { outline: none; border-color: var(--accent); }
.anr-spd-unit { white-space: nowrap; }
/* Caveat shown under the speed control when "Real time" is selected. */
.anr-spd-warn { margin: 6px 0 0; font-size: 12px; line-height: 1.35; color: #e5484d; }
.anr-spd-warn[hidden] { display: none; }

/* On mobile these toolbar popups are absolutely positioned off a button that can
   wrap anywhere along the controls row, so a fixed width anchored to the button
   ran off the screen edge. Pin them to the viewport as a bottom sheet instead -
   always fully on-screen, whatever column the button ended up in. */
@media (max-width: 700px) {
  .anr-aa-panel,
  .anr-spd-panel,
  .anr-gcode-topleft .anr-aa-panel {
    position: fixed;
    left: 12px;
    right: 12px;
    bottom: 12px;
    top: auto;
    width: auto;
    min-width: 0;
    max-height: 70vh;
    overflow-y: auto;
    z-index: 60;
  }
}

/* View cube - OrcaSlicer-style orientation gizmo in the lower-left of a 3D
   viewport. A CSS-3D cube that rotates with the camera; faces, edges and corners
   snap the view, and the whole cube is grabbable to orbit. */
.anr-viewcube {
  position: absolute;
  left: 18px;
  bottom: 18px;
  width: 54px;
  height: 54px;
  perspective: 260px;
  z-index: 3;
  cursor: grab;
}
.anr-viewcube:active { cursor: grabbing; }
.anr-viewcube-cube {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  will-change: transform;
}
.anr-viewcube-face {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 54px;
  height: 54px;
  margin: -27px 0 0 -27px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #eaeaec;
  background: rgba(18, 18, 22, 0.74);
  border: 1px solid rgba(255, 255, 255, 0.22);
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  backface-visibility: hidden;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
/* Edge + corner hotspots: children painted on top of their face (left/top set
   inline), faint by default, accent on hover - the standard navcube affordance. */
.anr-viewcube-edge,
.anr-viewcube-corner {
  position: absolute;
  transform: translate(-50%, -50%);
  background: rgba(150, 154, 170, 0.18);
  cursor: pointer;
  backface-visibility: hidden;
  transition: background 0.12s ease;
}
.anr-viewcube-edge { width: 16px; height: 16px; border-radius: 0; }
.anr-viewcube-corner { width: 14px; height: 14px; border-radius: 0; }
.anr-viewcube-face.is-hot { background: var(--accent); color: var(--accent-fg, #fff); border-color: var(--accent); }
/* When the whole face is the target, let its edge/corner patches melt into the
   accent so the face reads as one solid highlight, not a face with dots on it. */
.anr-viewcube-face.is-hot .anr-viewcube-edge,
.anr-viewcube-face.is-hot .anr-viewcube-corner { background: transparent; }
.anr-viewcube-edge.is-hot,
.anr-viewcube-corner.is-hot { background: var(--accent); border: 1px solid var(--accent); }
.anr-frame-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin: 8px 0; }
.anr-frame-grid > .anr-btn { width: 100%; }

/* Folder "which files can't be opened" check (folder.js) */
.anr-folder-scan-intro { margin: 0 0 12px; max-width: 62ch; font-size: var(--t-small); color: var(--muted); }
.anr-folder-scan-progress { margin-top: 12px; }
.anr-folder-scan-progress[hidden] { display: none; }
.anr-folder-scan-status {
  margin-top: 12px; font-family: var(--font-mono); font-size: var(--t-small); color: var(--muted);
  white-space: normal; overflow-wrap: anywhere; word-break: break-word;
  /* Reserve 3 lines so the height stays put as filenames wrap across 1-3 rows. */
  line-height: 1.4; min-height: 4.2em; overflow: hidden;
}
.anr-folder-scan-status[hidden] { display: none; }
.anr-folder-scan-report { margin-top: 14px; }
.anr-scan-ok { margin: 0; font-weight: 500; color: var(--fg); }
.anr-scan-bad { margin: 0 0 10px; font-weight: 600; color: var(--accent); }
.anr-scan-copy { margin: 0 0 12px; }
/* DaVinci Resolve colour-grade node chips */
.anr-drt-nodes { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.anr-drt-node {
  display: inline-block; padding: 2px 10px; border: var(--bd-hairline); border-radius: 0;
  font-size: 11.5px; font-weight: 600; letter-spacing: .02em;
  background: rgba(240, 168, 48, .10);
}
.anr-drt-arrow { opacity: .4; font-size: 11px; margin: 0 1px; }
/* Colour LUT (.cube) memory-colour swatches */
.anr-lut-swatches { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
.anr-lut-sw { border: var(--bd-hairline); border-radius: 0; padding: 8px; }
.anr-lut-sw-pair { display: flex; align-items: center; gap: 7px; }
.anr-lut-chip { flex: 1 1 0; height: 30px; border-radius: 0; box-shadow: inset 0 0 0 1px rgba(128,128,128,.25); }
.anr-lut-arrow { flex: 0 0 auto; opacity: .5; font-size: 12px; }
.anr-lut-sw-name { font-size: 12px; font-weight: 600; margin-top: 6px; }
.anr-lut-sw-hex { font-family: var(--font-mono); font-size: 10.5px; opacity: .6; }
.anr-lut-frames { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 10px; margin-top: 6px; }
.anr-lut-frames--single { grid-template-columns: 1fr; max-width: 720px; }
.anr-lut-frame { border: var(--bd-hairline); border-radius: 0; padding: 6px; }
.anr-lut-pair { display: flex; gap: 6px; }
.anr-lut-half { flex: 1 1 0; min-width: 0; }
/* Fixed-aspect thumbnails so every uploaded frame is the same size (object-fit
   crops the source into the box rather than letting it resize the cell). */
.anr-lut-thumb { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 0; display: block; background: #0a0a0a; cursor: zoom-in; transition: filter .12s; }
.anr-lut-thumb:hover { filter: brightness(1.08); }
.anr-lut-cap { text-align: center; font-size: 10.5px; opacity: .6; margin-top: 2px; }
/* XMP develop sidecar (.xmp): the Basic-panel sliders drawn as bipolar bars,
   the HSL colour-mixer grid, and keyword/module chips. */
.anr-xmp-sliders { display: grid; grid-template-columns: minmax(78px, auto) 1fr minmax(52px, auto); gap: 6px 12px; align-items: center; margin-top: 6px; }
.anr-xmp-sk { font-size: 12px; opacity: .85; }
.anr-xmp-sv { font-family: var(--font-mono); font-size: 11.5px; text-align: right; white-space: nowrap; }
.anr-xmp-bar { position: relative; height: 6px; background: rgba(128,128,128,.16); overflow: hidden; }
.anr-xmp-bar--mini { min-width: 34px; }
.anr-xmp-bar-fill { position: absolute; top: 0; bottom: 0; background: var(--fg); opacity: .78; }
.anr-xmp-bar-zero { position: absolute; top: -2px; bottom: -2px; width: 1px; background: currentColor; opacity: .4; }
.anr-xmp-hsl { display: grid; grid-template-columns: minmax(84px, auto) repeat(3, 1fr); gap: 5px 10px; align-items: center; margin-top: 6px; }
.anr-xmp-hsl-head { font-size: 10.5px; opacity: .55; text-transform: uppercase; letter-spacing: .04em; }
.anr-xmp-hsl-head:first-child { grid-column: 1; }
.anr-xmp-hsl-name { display: flex; align-items: center; gap: 6px; font-size: 12px; }
.anr-xmp-hsl-chip { width: 12px; height: 12px; flex: 0 0 auto; box-shadow: inset 0 0 0 1px rgba(128,128,128,.3); }
.anr-xmp-kws { display: flex; flex-wrap: wrap; gap: 5px; }
.anr-xmp-kw { font-size: 11px; font-family: var(--font-mono); padding: 2px 7px; border: var(--bd-hairline); color: var(--muted); }
.anr-scan-list { list-style: none; margin: 0; padding: 0; }
.anr-scan-list li {
  display: flex; flex-wrap: wrap; gap: 4px 16px; justify-content: space-between;
  padding: 8px 0; border-top: var(--bd-hairline);
}
.anr-scan-list li:last-child { border-bottom: var(--bd-hairline); }
.anr-scan-path { font-family: var(--font-mono); font-size: var(--t-small); word-break: break-all; }
.anr-scan-reason { font-size: var(--t-small); color: var(--muted); white-space: nowrap; }

/* Git object viewer (gitobject.js) - commit/tag message + textual blob preview */
.anr-git-block {
  margin: 0; padding: 12px 14px; max-height: 460px; overflow: auto;
  font-family: var(--font-mono); font-size: var(--t-small); line-height: 1.5;
  color: var(--fg); background: var(--surface); border: var(--bd-hairline);
  white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word;
}

.anr-timecode {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 6px 12px;
  border: var(--bd-hairline);
  background: var(--bg);
  cursor: pointer;
}
.anr-timecode-label {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 1;
  margin-bottom: 2px;
}
.anr-timecode-value {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
.anr-frame-wrap { margin: 8px 0; }
.anr-frame-wrap .anr-frame-grid { margin: 0; }
/* Space between the timecode button and the frame-control grid below it. */
.anr-frame-wrap .anr-timecode { margin-bottom: 8px; }
.anr-timecode-hint {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.12em;
  color: var(--muted);
  margin-bottom: 6px;
  line-height: 1;
  text-align: center;
  display: block;
}
/* The timecode is an editable readout, not a button - keep it subtle: a quiet
   border that just picks up on hover (no inversion / fill). */
.anr-timecode { transition: border-color var(--dur-fast) ease; }
.anr-timecode:hover { border-color: var(--fg); }
:root[data-theme="dark"] .anr-timecode { background: var(--surface); }
/* Per-field editing: HH:MM:SS:FF, each segment focusable and seek-on-commit. */
.anr-timecode-edit {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
  display: inline-flex;
  align-items: center;
}
.anr-tc-seg {
  width: 2ch;
  font: inherit;
  text-align: center;
  border: none;
  background: transparent;
  color: inherit;
  outline: none;
  padding: 0;
}
.anr-tc-seg:focus { background: var(--accent); color: var(--accent-fg); }
.anr-tc-sep { opacity: 0.5; padding: 0 1px; }

/* ---------- EDITING TIMELINE VIEWER ---------- */
.anr-tl-scroll { width: 100%; overflow-x: auto; }
.anr-tl-ruler {
  position: relative;
  height: 16px;
  margin-left: 44px;
  border-bottom: var(--bd-hairline);
}
.anr-tl-tick {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-family: var(--font-mono);
  font-size: 9px;
  color: var(--muted);
  white-space: nowrap;
  border-left: var(--bd-hairline);
  padding-left: 3px;
}
.anr-tl-tick:first-child { transform: none; }
.anr-tl-rows { margin-top: 4px; display: flex; flex-direction: column; gap: 4px; }
.anr-tl-row { display: flex; align-items: stretch; gap: 8px; }
.anr-tl-rowlabel {
  flex: 0 0 36px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  color: var(--muted);
  text-align: right;
  align-self: center;
}
.anr-tl-lane {
  position: relative;
  flex: 1 1 auto;
  height: 26px;
  background: var(--media-bg);
  border: var(--bd-hairline);
}
.anr-tl-clip {
  position: absolute;
  top: 2px;
  bottom: 2px;
  min-width: 2px;
  border: var(--bd-hairline);
  overflow: hidden;
  box-sizing: border-box;
  cursor: default;
}
.anr-tl-clip.is-video { background: var(--accent); color: var(--accent-fg); }
.anr-tl-clip.is-audio { background: var(--surface, #e8e8e8); color: var(--fg); }
.anr-tl-cliplabel {
  display: block;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 22px;
  padding: 0 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
:root[data-theme="dark"] .anr-tl-clip.is-audio { background: #2a2a2a; }

/* ---------- RESULTS / CARDS ---------- */
.anr-results { margin: 0; scroll-margin-top: var(--nav-offset); }

.anr-card {
  margin: 0 0 56px;
  padding: 0;
  background: transparent;
  border: none;
}
.anr-card h3 {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--fg);
  font-weight: 500;
  margin: 0 0 14px;
  padding-bottom: 8px;
  border-bottom: var(--bd-hairline);
}
/* Collapsible analysis cards. Only cards explicitly marked .anr-collapsible
   toggle their body open/closed via their direct-child <h3> - those carry the
   +/- disclosure glyph, pointer cursor and hover accent. Every other .anr-card
   is a static, always-open section: no toggle, no glyph (the heading keeps its
   flex layout so a trailing [?] still sits correctly). JS adds .is-collapsed on
   click. Open = minus, collapsed = plus. */
.anr-card > h3 {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
}
.anr-card.anr-collapsible > h3 {
  cursor: pointer;
  user-select: none;
}
.anr-card.anr-collapsible > h3::after {
  content: "\2212";                  /* minus = open */
  font-family: var(--font-mono);
  font-size: 16px;
  line-height: 1;
  color: var(--muted);
  flex: none;
  transition: color var(--dur-snappy) ease;
}
.anr-card.anr-collapsible > h3:hover { color: var(--accent); }
.anr-card.anr-collapsible > h3:hover::after { color: var(--accent); }
.anr-card.is-collapsed > h3 { margin-bottom: 0; }
.anr-card.anr-collapsible.is-collapsed > h3::after { content: "+"; }   /* plus = closed */
/* !important so it wins over inline display (e.g. the video player carries an
   inline display:block, which would otherwise keep it visible when collapsed -
   hiding the controls around it but not the player itself). */
.anr-card.is-collapsed > :not(h3) { display: none !important; }

/* ---------- PREVIEW ---------- */
.anr-preview {
  background: var(--surface);
  padding: 16px;
  border: var(--bd-rule);
  text-align: center;
}
.anr-preview img {
  max-width: 100%;
  max-height: 480px;
  display: inline-block;
}
/* ---------- READOUT TABLE ---------- */
.anr-readout {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-feature-settings: "tnum";
}
.anr-readout th,
.anr-readout td {
  text-align: left;
  padding: 9px 12px 9px 0;
  vertical-align: top;
  border-bottom: var(--bd-rule);
  word-break: break-all;
  overflow-wrap: anywhere;
}
.anr-readout th {
  font-weight: 400;
  color: var(--muted);
  width: 38%;
  text-transform: none;
}
.anr-readout td { color: var(--fg); font-weight: 500; }
.anr-readout tr:last-child th,
.anr-readout tr:last-child td { border-bottom: none; }
.anr-readout tr:hover th,
.anr-readout tr:hover td { background: var(--surface); }

.anr-readout-section {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 28px 0 6px;
  padding-top: 12px;
  border-top: var(--bd-hairline);
}
.anr-readout-section:first-of-type { border-top: none; margin-top: 0; padding-top: 0; }

/* ---------- BOX (ATOM) TREE ---------- */
/* Indented, hairline-guided tree of MP4/MOV boxes in the video Advanced card.
   Container boxes are <details> (nested), leaves are plain rows. Sharp-cornered
   and monospace, matching the readout tables it sits beside. */
.anr-boxtree {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  line-height: 1.5;
  overflow-x: auto;
  max-height: 420px;
  overflow-y: auto;
  border: var(--bd-hairline);
  padding: 8px 10px;
}
.anr-boxtree-row {
  display: flex;
  gap: 10px;
  align-items: baseline;
  white-space: nowrap;
  padding: 1px 0;
}
.anr-boxtree-node > summary.anr-boxtree-row { cursor: pointer; list-style: none; }
.anr-boxtree-node > summary.anr-boxtree-row::-webkit-details-marker { display: none; }
.anr-boxtree-node > summary.anr-boxtree-row::before {
  content: '\25B8';                       /* right-pointing triangle */
  color: var(--muted);
  margin-right: -4px;
  transition: transform 0.12s ease;
}
.anr-boxtree-node[open] > summary.anr-boxtree-row::before { transform: rotate(90deg); }
.anr-boxtree-leaf { padding-left: 14px; }  /* align leaf 4CCs under expandable ones */
.anr-boxtree-fcc { color: var(--fg); font-weight: 600; }
.anr-boxtree-meta { color: var(--muted); }
.anr-boxtree-kids {
  margin-left: 6px;
  padding-left: 10px;
  border-left: var(--bd-hairline);
}

/* ---------- CONVERT: ADVANCED SETTINGS ---------- */
/* Resolution / frame-rate / speed panel on the "Convert to H.264" card. Hidden
   until the Advanced button toggles it - the [hidden] override is required
   because the display:flex class would otherwise beat the UA [hidden] rule. */
.anr-conv-settings {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  align-items: flex-end;
  margin-top: 12px;
  padding: 12px;
  border: var(--bd-hairline);
}
.anr-conv-settings[hidden] { display: none; }
.anr-conv-setting {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.anr-conv-setting > span {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.anr-btn.is-active { background: var(--fg); color: var(--bg); }

/* ---------- RAW ANALYSE-MODE TOGGLE ---------- */
/* Top-of-column switch between the embedded-preview JPEG and the full demosaic. */
.anr-raw-modebar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: var(--bd-hairline);
}
.anr-raw-modebar-label {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-right: 2px;
}

/* ---------- HISTOGRAM / WAVEFORM ---------- */
.anr-histogram,
.anr-waveform {
  width: 100%;
  display: block;
  background: var(--media-bg);
  border: var(--bd-hairline);
}
/* No fixed aspect ratio: width:100% with height auto lets the canvas scale to
   its own 1024x200 pixels at any window size. */
.anr-histogram { height: auto; }
.anr-hist-block { margin: 0 0 56px; }   /* same body rhythm as .anr-card */
.anr-waveform { height: 96px; }

.anr-playhead {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 1px;
  /* Defaults to the media-canvas white, since most playheads sit on a fixed dark
     canvas (spectrogram, waveform). A themed surface - e.g. the loudness graph,
     whose canvas is var(--bg) - sets --playhead on its wrapper to override it,
     rather than needing a playhead variant of its own. */
  background: var(--playhead, var(--on-dark));
  pointer-events: none;
  z-index: 3;
  will-change: left;
  /* Eases to the position on discrete seeks. JS sets transition:none during live
     playback and scrubbing so the line tracks without lag. */
  transition: left 0.28s cubic-bezier(0.4, 0, 0.2, 1);
}

/* ---------- PALETTE ---------- */
.anr-palette {
  display: grid;
  grid-template-columns: repeat(8, 1fr);
  gap: 0;
  margin: 0;
  border-top: var(--bd-hairline);
  border-left: var(--bd-hairline);
}
.anr-swatch {
  aspect-ratio: 1 / 1;
  position: relative;
  font-family: var(--font-mono);
  font-size: clamp(8px, 1.1vw, 11px);
  color: var(--fg);
  border-right: var(--bd-hairline);
  border-bottom: var(--bd-hairline);
  overflow: hidden;
}
.anr-swatch {
  transition: transform var(--dur-snappy) ease;
}
.anr-swatch:hover {
  transform: translateY(-3px);
  z-index: 2;
  box-shadow: 0 0 0 1px var(--hairline);
}
.anr-swatch span {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  padding: 4px 4px;
  background: var(--bg);
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  border-top: var(--bd-hairline);
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: clip;
}
/* On phones, two rows of four (4x2) are friendlier than eight tiny squares.
   600px so it catches every phone in portrait - the old 420px missed the wider
   modern handsets (Pro Max ~430, large Androids 412-480), leaving them at 8. */
@media (max-width: 600px) {
  .anr-palette { grid-template-columns: repeat(4, 1fr); }
}

/* ---------- CARVED IMAGES (disk-image / blob signature scan) ----------
   Gallery of images recovered by scanning raw bytes for image signatures. Each
   thumbnail is bare - no card around it - with its Analyse / Download actions
   overlaid on hover, the same treatment the PDF page previews use. */
.anr-carve-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 12px;
}
.anr-carve-cell { position: relative; }
.anr-carve-thumb {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 120px;
  overflow: hidden;
  border: var(--bd-hairline);
  background: var(--surface);
  cursor: zoom-in;
}
.anr-carve-thumb canvas,
.anr-carve-thumb img { display: block; max-width: 100%; max-height: 200px; height: auto; width: auto; }
/* A carve that wouldn't decode shows a text placeholder instead, and has no
   lightbox to open - so it must not claim to be clickable. */
.anr-carve-thumb.is-plain { cursor: default; }
/* Overlaid actions: hidden until the cell is hovered or a button inside it takes
   keyboard focus, so the gallery reads as pictures rather than rows of buttons. */
.anr-carve-actions {
  position: absolute;
  top: 6px;
  left: 6px;
  right: 6px;
  display: flex;
  gap: 6px;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) ease;
}
.anr-carve-cell:hover .anr-carve-actions,
.anr-carve-cell:focus-within .anr-carve-actions { opacity: 1; pointer-events: auto; }
/* Touch devices have no hover, so the actions are shown permanently there. */
@media (hover: none), (pointer: coarse) {
  .anr-carve-actions { opacity: 1; pointer-events: auto; }
}
/* Also worn by <a download> links, hence the anchor resets. */
.anr-carve-btn {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  line-height: 1.5;
  padding: 2px 6px;
  border: var(--bd-hairline);
  border-radius: 0;
  background: var(--bg);
  color: var(--fg);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.anr-carve-btn:hover { background: var(--fg); color: var(--bg); }
.anr-carve-cap {
  margin: 6px 0 0;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted);
  text-align: center;
  line-height: 1.4;
  word-break: break-word;
}

/* ---------- MAP ---------- */
.anr-map {
  width: 100%;
  height: 360px;
  border: var(--bd-hairline);
  background: var(--surface);
  position: relative;
  z-index: 1;
}

/* ---------- SPECTROGRAM ---------- */
.anr-spec-card { position: relative; }

.anr-spec-wrap {
  display: flex;
  background: var(--media-bg);
  border: var(--bd-hairline);
  position: relative;
  min-height: 0;
}

.anr-spec-yaxis-wrap { flex: 0 0 48px; display: flex; flex-direction: column; }
.anr-spec-yaxis {
  flex: 1 1 auto;
  position: relative;
  background: var(--media-bg);
  border-right: 1px solid var(--hairline-on-dark);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--muted-on-dark);
  overflow: hidden;
}
.anr-spec-yaxis span { position: absolute; right: 6px; transform: translateY(-50%); letter-spacing: 0.04em; }
.anr-spec-corner {
  height: 20px;
  background: var(--media-bg);
  border-right: 1px solid var(--hairline-on-dark);
  border-top: 1px solid var(--hairline-on-dark);
}

.anr-spec-scroll {
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  overflow-y: hidden;
  display: flex;
  flex-direction: column;
  background: var(--media-bg);
  -webkit-overflow-scrolling: touch;
  scrollbar-color: var(--border-on-dark-ctl) var(--media-bg);
  scrollbar-width: thin;
}
/* The native scrollbar is hidden - a custom one (.anr-spec-sb) is rendered under
   the spectrogram instead. Scrolling itself (wheel / trackpad / playhead-follow)
   still works through this overflow container. */
.anr-spec-scroll { scrollbar-width: none; }
.anr-spec-scroll::-webkit-scrollbar { height: 0; width: 0; display: none; }

/* Custom horizontal scrollbar, shown under the canvas only when zoomed in. The
   leading spacer matches the y-axis column so the track sits under the canvas. */
.anr-spec-sb { display: flex; margin-top: 6px; }
.anr-spec-sb.is-hidden { display: none; }
.anr-spec-sb-spacer { flex: 0 0 48px; }
.anr-spec-sb-track {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  height: 12px;
  background: var(--media-bg);
  border: var(--bd-hairline);
  cursor: pointer;
  touch-action: none;
}
.anr-spec-sb-thumb {
  position: absolute;
  top: 1px;
  left: 0;
  height: calc(100% - 2px);
  width: 40px;
  background: var(--border-on-dark-ctl);
  cursor: grab;
  will-change: transform;
}
.anr-spec-sb-thumb:hover { background: var(--hairline-strong); }
.anr-spec-sb-track.is-dragging .anr-spec-sb-thumb,
.anr-spec-sb-thumb:active { background: var(--accent); cursor: grabbing; }

.anr-spec-canvas-wrap { position: relative; flex: 0 0 auto; }
.anr-spec-canvas { display: block; image-rendering: pixelated; }
.anr-spec-xaxis {
  height: 20px; flex: 0 0 auto;
  background: var(--media-bg);
  border-top: 1px solid var(--hairline-on-dark);
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--muted-on-dark);
  position: relative;
  pointer-events: none;
}
.anr-spec-xaxis span { position: absolute; top: 4px; transform: translateX(-50%); letter-spacing: 0.04em; }

/* fullscreen */
.anr-spec-card:fullscreen,
.anr-spec-card:-webkit-full-screen {
  background: var(--media-bg);
  padding: 20px;
  margin: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.anr-spec-card:fullscreen h3,
.anr-spec-card:-webkit-full-screen h3 {
  color: var(--on-dark);
  border-bottom-color: var(--hairline-on-dark);
  flex: 0 0 auto;
}
.anr-spec-card:fullscreen .anr-controls,
.anr-spec-card:-webkit-full-screen .anr-controls {
  flex: 0 0 auto;
  background: var(--surface-on-dark);
  border-color: var(--hairline-on-dark);
}
.anr-spec-card:fullscreen .anr-controls label,
.anr-spec-card:-webkit-full-screen .anr-controls label { color: var(--muted-on-dark); }
.anr-spec-card:fullscreen .anr-controls select,
.anr-spec-card:-webkit-full-screen .anr-controls select {
  background: var(--surface-on-dark);
  color: var(--on-dark);
  border-color: var(--border-on-dark-ctl);
}
/* Center the spectrogram vertically in the available area when it's shorter than
   the screen. The canvas-wrap fills the remaining height and the canvas fills it
   via height:100%, so the spectrogram fills the screen with no gap. Scoped to the
   file panel's canvas-wrap so the live card (canvas directly in the scroll) keeps
   its own JS sizing. */
.anr-spec-card:fullscreen .anr-spec-canvas-wrap,
.anr-spec-card:-webkit-full-screen .anr-spec-canvas-wrap { flex: 1 1 0; min-height: 0; }
.anr-spec-card:fullscreen .anr-spec-canvas-wrap > .anr-spec-canvas,
.anr-spec-card:-webkit-full-screen .anr-spec-canvas-wrap > .anr-spec-canvas { height: 100%; }
/* Height stays usable in fullscreen on the file spectrogram (it gains a "Fill"
   option there). Elsewhere - e.g. the live spectrogram, whose fullscreen always
   auto-fills - the control is inert in fullscreen, so keep hiding it. */
.anr-spec-card:not(.anr-spec-fillable):fullscreen .anr-ctl-height,
.anr-spec-card:not(.anr-spec-fillable):-webkit-full-screen .anr-ctl-height { display: none; }

/* Floating exit-fullscreen button (top-right), only shown while fullscreen. */
.anr-spec-fs-close { display: none; }
.anr-spec-card:fullscreen .anr-spec-fs-close,
.anr-spec-card:-webkit-full-screen .anr-spec-fs-close {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 10;
  width: 34px;
  height: 34px;
  font-size: 16px;
  line-height: 1;
  background: var(--surface-on-dark);
  color: var(--on-dark);
  border: 1px solid var(--border-on-dark-ctl);
  cursor: pointer;
}
.anr-spec-card:fullscreen .anr-spec-fs-close:hover,
.anr-spec-card:-webkit-full-screen .anr-spec-fs-close:hover { background: var(--on-dark); color: var(--media-bg); }
.anr-spec-card:fullscreen .anr-spec-wrap,
.anr-spec-card:-webkit-full-screen .anr-spec-wrap {
  flex: 1 1 auto;
  min-height: 0;
  border-color: var(--hairline-on-dark);
}
/* Fullscreen Height set to a fixed pixel value (not "Fill"): size the whole
   spectrogram box to it instead of filling the screen. The canvas-wrap still grows
   inside this fixed box, so the canvas and y-axis resize together and stay aligned;
   the empty space lands below. Higher specificity than the rule above, so it wins. */
.anr-spec-card.is-spec-fixed-h:fullscreen .anr-spec-wrap,
.anr-spec-card.is-spec-fixed-h:-webkit-full-screen .anr-spec-wrap {
  flex: 0 0 auto;
  height: var(--spec-fixed-h, 320px);
}
.anr-spec-card:fullscreen .anr-spec-hint,
.anr-spec-card:-webkit-full-screen .anr-spec-hint { color: var(--muted-on-dark); }
.anr-spec-card:fullscreen .anr-toggle,
.anr-spec-card:-webkit-full-screen .anr-toggle { border-color: var(--border-on-dark-ctl); }
.anr-spec-card:fullscreen .anr-toggle button,
.anr-spec-card:-webkit-full-screen .anr-toggle button { color: var(--muted-on-dark); }
.anr-spec-card:fullscreen .anr-toggle button.is-active,
.anr-spec-card:-webkit-full-screen .anr-toggle button.is-active { background: var(--on-dark); color: var(--media-bg); }
/* Dark-theme the rest of the controls so the newer additions (Sensitivity slider,
   Save / Record / Live buttons) and the stats readout match the fullscreen palette. */
.anr-spec-card:fullscreen .anr-btn,
.anr-spec-card:-webkit-full-screen .anr-btn {
  background: var(--surface-on-dark); color: var(--on-dark); border-color: var(--border-on-dark-ctl);
}
.anr-spec-card:fullscreen .anr-btn:hover,
.anr-spec-card:-webkit-full-screen .anr-btn:hover { background: var(--on-dark); color: var(--media-bg); }
.anr-spec-card:fullscreen .anr-btn.is-active,
.anr-spec-card:fullscreen .anr-btn.is-recording,
.anr-spec-card:-webkit-full-screen .anr-btn.is-active,
.anr-spec-card:-webkit-full-screen .anr-btn.is-recording {
  background: var(--accent); color: var(--accent-fg); border-color: var(--accent);
}
.anr-spec-card:fullscreen input[type="range"]::-webkit-slider-runnable-track,
.anr-spec-card:-webkit-full-screen input[type="range"]::-webkit-slider-runnable-track { background: var(--border-on-dark-ctl); }
.anr-spec-card:fullscreen input[type="range"]::-moz-range-track,
.anr-spec-card:-webkit-full-screen input[type="range"]::-moz-range-track { background: var(--border-on-dark-ctl); }
.anr-spec-card:fullscreen .anr-range-readout,
.anr-spec-card:-webkit-full-screen .anr-range-readout { color: var(--on-dark); }
/* Keep the stats + status rows to a single, fixed-height line in fullscreen.
   Otherwise their text re-wraps as values change per setting, and since the
   canvas height is the flex remainder, a taller row would shrink the canvas
   (settings appeared to randomly grow/shrink the spectrogram). */
.anr-spec-card:fullscreen .anr-spec-stats,
.anr-spec-card:-webkit-full-screen .anr-spec-stats { overflow: hidden; }
/* The findings table collapses to a single horizontal line in fullscreen. */
.anr-spec-card:fullscreen .anr-spec-stats .anr-readout,
.anr-spec-card:-webkit-full-screen .anr-spec-stats .anr-readout { display: block; }
.anr-spec-card:fullscreen .anr-spec-stats tbody,
.anr-spec-card:-webkit-full-screen .anr-spec-stats tbody {
  display: flex; flex-wrap: nowrap; gap: 4px 18px; align-items: baseline;
}
.anr-spec-card:fullscreen .anr-spec-stats tr,
.anr-spec-card:-webkit-full-screen .anr-spec-stats tr {
  display: inline-flex; gap: 6px; align-items: baseline;
}
.anr-spec-card:fullscreen .anr-spec-stats th,
.anr-spec-card:-webkit-full-screen .anr-spec-stats th,
.anr-spec-card:fullscreen .anr-spec-stats td,
.anr-spec-card:-webkit-full-screen .anr-spec-stats td {
  border: none; padding: 0; white-space: nowrap;
}
.anr-spec-card:fullscreen .anr-spec-stats th,
.anr-spec-card:-webkit-full-screen .anr-spec-stats th { color: var(--muted-on-dark); }
.anr-spec-card:fullscreen .anr-spec-stats td,
.anr-spec-card:-webkit-full-screen .anr-spec-stats td { color: var(--on-dark); }
.anr-spec-card:fullscreen .anr-spec-hint,
.anr-spec-card:-webkit-full-screen .anr-spec-hint {
  white-space: nowrap; overflow: hidden;
}

/* ---------- CONTROLS ROW ---------- */
.anr-controls {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 22px;
  align-items: center;
  padding: 12px 16px;
  background: var(--surface);
  border: var(--bd-hairline);
  border-bottom: none;
  font-family: var(--font-sans);
  font-size: var(--t-small);
}
.anr-control { display: flex; align-items: center; gap: 8px; }
.anr-control label {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
/* A label that resets its control when clicked. */
.anr-control label.anr-resettable { cursor: pointer; }
.anr-control label.anr-resettable:hover { color: var(--accent); }

/* Actions row pulled out from the settings and placed under the scrubber: give
   it a full border (the settings .anr-controls has no bottom border because it
   butts against the canvas) and a little breathing room above. */
.anr-spec-actions {
  border-bottom: var(--bd-hairline);
  margin-top: 10px;
}

/* ---------- ISOLATE FREQUENCIES (spectrogram band-stop) ---------- */
/* Overlay layer sits over the canvas but below the playhead (z-index 3). Bands
   are purely visual - the drag handler lives on .anr-spec-canvas-wrap. */
.anr-spec-bandlayer {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}
.anr-spec-band,
.anr-spec-bandsel {
  position: absolute;
  left: 0;
  right: 0;
  box-sizing: border-box;
  pointer-events: none;
}
/* A disabled band: dim + desaturate the spectrogram beneath it, hatch it, and
   fence it with accent hairlines so it reads as "cut". Fixed dark treatment - the
   spectrogram surface is always dark, in either theme. */
.anr-spec-band {
  background: repeating-linear-gradient(
    -45deg,
    rgba(230, 0, 35, 0.18) 0, rgba(230, 0, 35, 0.18) 6px,
    rgba(0, 0, 0, 0.30) 6px, rgba(0, 0, 0, 0.30) 12px);
  border-top: 1px solid var(--accent);
  border-bottom: 1px solid var(--accent);
  backdrop-filter: saturate(0.12) brightness(0.5);
  -webkit-backdrop-filter: saturate(0.12) brightness(0.5);
}
/* The live drag rectangle before the band is committed. */
.anr-spec-bandsel {
  background: rgba(230, 0, 35, 0.14);
  border-top: 1px dashed var(--accent);
  border-bottom: 1px dashed var(--accent);
}
/* Karaoke (Remove vocals) is a stereo centre-cancel with no single band, but the
   removed energy that matters is the vocal, so we draw the deadzone over the vocal
   range (top/height set in JS) using the same "cut" treatment as a band. */
.anr-spec-karaoke {
  position: absolute;
  left: 0;
  right: 0;
  box-sizing: border-box;
  pointer-events: none;
  background: repeating-linear-gradient(
    -45deg,
    rgba(230, 0, 35, 0.16) 0, rgba(230, 0, 35, 0.16) 6px,
    rgba(0, 0, 0, 0.28) 6px, rgba(0, 0, 0, 0.28) 12px);
  border-top: 1px solid var(--accent);
  border-bottom: 1px solid var(--accent);
  backdrop-filter: saturate(0.12) brightness(0.55);
  -webkit-backdrop-filter: saturate(0.12) brightness(0.55);
}
.anr-spec-canvas-wrap.anr-spec-isolating { touch-action: none; }
.anr-spec-isolating .anr-spec-canvas { cursor: ns-resize; }

/* Control panel under the actions row: band list + numeric editing. */
.anr-iso-panel {
  /* No border-bottom: the Analysis header right below supplies its own
     border-top, so a bottom border here would double it up. */
  padding: 10px 0 12px;
  display: flex;
  flex-direction: column;
  gap: 0;
}
.anr-iso-panel.is-hidden { display: none; }
/* Two labelled tiers: EQ isolation (presets + manual bands) and the AI
   separator. Each tier stacks its own controls; the AI tier is fenced off with
   a hairline so it doesn't read as more of the same EQ chips. */
.anr-iso-sec { display: flex; flex-direction: column; gap: 9px; }
/* The AI tools are their own panel now (opened by their own button, and never at
   the same time as Isolate), so this is the only section in it - no leading
   divider, which would just draw a line across the top of the panel. */
.anr-iso-sec-ai { margin-top: 0; padding-top: 0; border-top: none; }
.anr-iso-seclabel {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
/* Subtle within-tier divider between the one-click presets and the manual band
   tools - lighter than the section hairline so the bigger break still reads. */
.anr-iso-rule { height: 1px; background: var(--hairline); opacity: 0.45; }
.anr-iso-presets { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.anr-iso-presets:empty { display: none; }
.anr-iso-preset-clear { margin-left: auto; }   /* floats Clear to the row's right */
/* Vertical divider inside the AI model row (tiers | denoise). */
.anr-iso-seg-div { color: var(--muted); opacity: 0.5; user-select: none; }
/* Same divider in the spectrogram Actions row (between Isolate and Save PNG). */
.anr-spec-actdiv { color: var(--muted); opacity: 0.5; user-select: none; align-self: center; }
/* The rough EQ presets, at the head of the Isolate panel above its manual band
   tools. Wraps gracefully when the row runs out of width. */
.anr-iso-preset-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.anr-iso-hint { margin: 0; }
.anr-iso-list { display: flex; flex-direction: column; gap: 6px; }
.anr-iso-list:empty { display: none; }
.anr-iso-band {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: var(--t-small);
}
.anr-iso-num {
  width: 88px;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  padding: 4px 6px;
  background: var(--bg);
  color: var(--fg);
  border: var(--bd-hairline);
}
.anr-iso-sep,
.anr-iso-unit { color: var(--muted); }
.anr-iso-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.anr-iso-draghint { color: var(--muted); font-size: var(--t-small); }
.anr-iso-export { margin-left: auto; }   /* Download WAV floats right, opposite + Custom band */

/* The AI separator is a plain white button (default .anr-btn look). It now sits in
   the Actions row beside Isolate rather than inside the Separate panel, so it needs
   no alignment override of its own - the .anr-control wrapper places it exactly
   like Isolate, Save PNG and Fullscreen. */

/* The Isolate toggle looks like every other Actions-row button (no accent border);
   when toggled on it uses the standard .anr-btn.is-active fill. */

/* AI vocal separation: status + determinate progress bar, the one-off size
   warning, and the resulting stem rows (label + native player + download). */
.anr-iso-aistatus {
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: var(--t-small);
}
.anr-iso-aistatus[hidden] { display: none; }
.anr-iso-aimsg { color: var(--muted); }
/* One-off "download the model" prompt. A calm surface panel with a hairline
   border (not the old loud full-accent box); the accent lives only on the primary
   Download button, where it reads as a call to action rather than an alarm. */
.anr-iso-confirm {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px;
  border: var(--bd-hairline);
  background: var(--surface);
}
.anr-iso-confirm-title {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--fg);
  font-weight: 500;
}
.anr-iso-confirm-size { color: var(--muted); }
.anr-iso-confirm p { margin: 0; color: var(--muted); line-height: 1.5; }
.anr-iso-confirm-btns { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
/* Primary action carries the accent (border + text) and fills accent on hover -
   the one place the accent is used here, so it reads as the call to action. */
.anr-iso-confirm-yes { border-color: var(--accent); color: var(--accent); }
.anr-iso-confirm-yes:hover { background: var(--accent); color: #fff; border-color: var(--accent); }

/* AI model tier picker (Standard / Lite) - a mono label + a small segmented row,
   matching the Channel picker's control strip. */
.anr-iso-modelrow { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.anr-iso-modelrow[hidden] { display: none; }   /* class display:flex would beat the UA [hidden] rule */
.anr-iso-modellabel {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.anr-iso-modelseg { display: flex; gap: 6px; flex-wrap: wrap; }
.anr-iso-stems { display: flex; flex-direction: column; gap: 14px; }
.anr-iso-stems:empty { display: none; }
.anr-iso-stem { display: flex; flex-direction: column; gap: 8px; }
.anr-iso-stem-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.anr-iso-stem-label {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--fg);
  min-width: 84px;
}
/* Let the shared custom player take the free space in the row. */
.anr-iso-stem-head .anr-player { flex: 1 1 260px; min-width: 0; }
.anr-iso-stem-spec[hidden] { display: none; }
.anr-iso-stem-canvas {
  display: block;
  width: 100%;
  height: auto;
  border: var(--bd-hairline);
  image-rendering: pixelated;
}

/* ---- Blend playground: vocals <-> instrumental slider that morphs the MAIN
   spectrogram (no canvas of its own) and crossfades the two stems. ---- */
/* Mount between the scrubber and the actions row; only takes up space once the
   blend slider lands in it (after AI separation). */
.anr-blend-mount:not(:empty) { margin-top: 12px; }
.anr-blend { display: flex; flex-direction: column; gap: 10px; }
.anr-blend-head { display: flex; align-items: baseline; gap: 10px; }
.anr-blend-tag {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.anr-blend-note { margin: 0; }
.anr-blend-transport { display: flex; align-items: center; gap: 12px; }
.anr-blend-time {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.anr-blend-sliderrow { display: flex; align-items: center; gap: 12px; }
.anr-blend-end {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--muted);
  white-space: nowrap;
}
/* The slider wrap holds the centre tick (position:relative anchor) and fills the
   row; unlike the sensitivity slider's wrap it isn't fixed to 120px. */
.anr-blend-wrap {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
}
.anr-blend-slider { flex: 1 1 auto; width: 100%; min-width: 0; }
/* Centre slit lights up when the pointer is over its snap zone (class toggled in
   renderBlend, since the tick is pointer-events:none and can't :hover itself). */
.anr-blend-wrap .anr-range-tick { transition: height .12s ease, background-color .12s ease; }
.anr-blend-wrap.is-mid-hover { cursor: pointer; }
.anr-blend-wrap.is-mid-hover .anr-range-tick { height: 16px; background: var(--accent); }

/* ---- Pro 4-stem mixer: a mixing-desk stack of per-stem fader rows that morph
   the MAIN spectrogram and crossfade the stems - the N-stem generalisation of the
   two-stem blend above. It reuses .anr-blend for the head/transport shell and adds
   the fader-row grid below (each fader is a plain .anr-range, so it matches the
   spectrogram sliders exactly). ---- */
.anr-mixer .anr-blend-head { align-items: center; }
/* Push the transport (Play + clock) to the right edge, mixing-desk style. */
.anr-mixer .anr-blend-head .anr-btn { margin-left: auto; }
.anr-mixer-rows { display: flex; flex-direction: column; gap: 9px; margin-top: 2px; }
.anr-mixer-row { display: flex; align-items: center; gap: 10px; }
/* Fixed label + level columns so every fader shares one left edge and the numbers
   sit in a clean right-aligned column (same idiom as .anr-iso-stem-label and
   .anr-range-readout). */
.anr-mixer-label {
  flex: 0 0 68px;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--fg);
}
.anr-mixer-fader { flex: 1 1 auto; width: 100%; min-width: 40px; }
.anr-mixer-pct {
  flex: 0 0 auto;
  min-width: 38px;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
/* Solo toggle: content-sized (identical "Solo" text on every row, so the buttons
   line up as a column on their own); active state uses the standard
   .anr-btn.is-active accent fill. */
.anr-mixer-btn { flex: 0 0 auto; }

/* Segmented control groups (View / Resolution / Actions) - a captioned cluster
   with a hairline divider on its left edge. align-self:stretch makes the divider
   span the full row height even though the row centres its items. */
.anr-control-group {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 7px;
  align-self: stretch;
  padding: 0 18px;
  border-left: var(--bd-hairline);
}
.anr-control-group:first-child { padding-left: 0; border-left: none; }
.anr-control-group-label {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.7;
}
.anr-control-group-items {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 16px;
}

/* "Advanced" disclosure inside the spectrogram settings row: collapses the
   analysis params (Mode / FFT / Window) behind the site's standard <details>
   +/- toggle. Spans the full row width so it opens onto its own line under the
   View controls, with a hairline divider separating it from them. */
.anr-spec-advanced {
  flex: 1 1 100%;
  border: none;
  border-top: var(--bd-hairline);
  margin: 2px 0 0;
  padding: 11px 0 2px;
}
.anr-spec-advanced > summary {
  color: var(--muted);
  letter-spacing: 0.14em;
  opacity: 0.7;
}
.anr-spec-advanced[open] > summary { opacity: 1; }
.anr-spec-advanced > .anr-control-group-items { margin-top: 12px; }
.anr-control select,
.anr-control input[type="number"],
.anr-control input[type="text"] {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  padding: 5px 8px;
  border: var(--bd-hairline);
  background: var(--bg);
  color: var(--fg);
  border-radius: 0;
}
/* Custom range to match the site: a hairline track with a thin, square accent
   thumb (no rounded corners) instead of the rounded native control. */
.anr-control input[type="range"] {
  -webkit-appearance: none;
  appearance: none;
  width: 120px;
  height: 16px;
  background: transparent;
  cursor: pointer;
}
.anr-control input[type="range"]::-webkit-slider-runnable-track {
  height: 3px; background: var(--hairline); border-radius: 0;
}
.anr-control input[type="range"]::-moz-range-track {
  height: 3px; background: var(--hairline); border-radius: 0;
}
.anr-control input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 4px; height: 14px;
  margin-top: -5.5px;          /* centre the thumb on the 3px track */
  background: var(--accent); border: 0; border-radius: 0;
}
.anr-control input[type="range"]::-moz-range-thumb {
  width: 4px; height: 14px;
  background: var(--accent); border: 0; border-radius: 0;
}
.anr-control input[type="range"]:focus { outline: none; }
.anr-control input[type="range"]:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--accent); }
.anr-control input[type="range"]:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 2px var(--bg), 0 0 0 3px var(--accent); }
.anr-control input[type="range"]:hover::-webkit-slider-thumb { background: var(--fg); }
.anr-control input[type="range"]:hover::-moz-range-thumb { background: var(--fg); }
.anr-range-readout {
  display: inline-block; min-width: 38px; text-align: right;
  color: var(--fg);
  font-family: var(--font-mono);
  font-size: var(--t-small);
}
/* A range slider wrapped with a 100% "slit" marker over its track. The wrap stands
   in for the bare <input> in the control's flex layout (same 120px footprint). */
.anr-control .anr-range-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  width: 120px;
  vertical-align: middle;
}
.anr-control .anr-range-wrap input[type="range"] { width: 100%; }
/* The slit itself: a short vertical tick at the 100% position (left set inline).
   Non-interactive - clicks fall through to the slider, which snaps to exactly 100%
   near it - so it never blocks grabbing the thumb when it rests at the default. */
.anr-range-tick {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 2px;
  height: 10px;
  background: var(--muted);
  pointer-events: none;
}

/* Font preview card (nested in the identification card): give it clear breathing
   room above so it doesn't butt against the metadata table. */
.anr-font-preview { margin-top: 48px; }

/* At-a-glance spectrogram stats strip (under the canvas) */
/* Stats block under the spectrogram: a captioned header with a [?] toggle, then
   a tidy grid of labelled value cells. */
/* The "Analysis" block is a sub-heading inside a readout card, so it takes the
   .anr-readout-section rhythm exactly - 28px above the rule, 12px below it, 6px
   before the table. It used to be 10/10/12, which put the same full-strength
   hairline right up against the File info table above with the label almost
   sitting on the line. */
.anr-spec-statsblock { margin: 28px 0 0; }
.anr-spec-stats-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px 12px;
  border-top: var(--bd-hairline);
  padding-top: 12px;
}
.anr-spec-stats-headleft { display: flex; align-items: center; gap: 12px; }
/* Frame/bin/px/ms readout folded into the header (right, muted) instead of a
   stray line under the grid - one tidy block. Wraps under on a narrow card. */
.anr-spec-status {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
  text-align: right;
}
/* Matches .anr-readout-section exactly: no extra opacity on top of --muted, which
   made this the one sub-label on the page fainter than all the others. */
.anr-spec-stats-title {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
}
/* The [?] in this header is a flex item, so neutralise .anr-info-btn's float. */
.anr-spec-stats-head .anr-info-btn { float: none; }
/* Holds the standard .anr-readout findings table; just a top margin here. */
.anr-spec-stats { margin: 6px 0 0; }
.anr-spec-stats .anr-readout { margin: 0; }

/* Channel picker: a lean control strip above the spectrogram - a mono uppercase
   label + [?] with the peak/RMS readout riding right, then the Mix/L/R buttons. */
/* The Channel card uses the standard .anr-card h3 heading (via h3help) like its
   sibling cards; only the peak/RMS readout beneath the buttons needs styling -
   a muted mono line matching the spectrogram status readout. */
.anr-chan-readout {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  opacity: 0.7;
  font-variant-numeric: tabular-nums;
}

.anr-toggle { display: inline-flex; border: var(--bd-hairline); }
.anr-toggle button {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  padding: 5px 10px;
  background: transparent;
  border: none;
  color: var(--fg);
  cursor: pointer;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
.anr-toggle button:hover:not(.is-active) { color: var(--accent); }
.anr-toggle button.is-active { background: var(--fg); color: var(--bg); }

/* Part picker for a large raw stream split into keyframe-aligned MP4 segments. */
.anr-seg-strip { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 10px; }
.anr-seg-btn {
  min-width: 30px;
  padding: 4px 8px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  border: var(--bd-hairline);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}
.anr-seg-btn:hover { color: var(--fg); border-color: var(--fg); }
.anr-seg-btn.is-active { background: var(--fg); color: var(--bg); border-color: var(--fg); }

.anr-spec-hint {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

/* ---------- INFO BUTTON / PANEL ---------- */
.anr-info-btn {
  /* Sits inline right after its section title/label, never floated to the far
     edge of the header (the per-context rules below add the small left gap). */
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 0;
  line-height: inherit;
}
.anr-info-btn:hover { color: var(--accent); }
/* Card headings are flex + space-between (to park the collapse +/- glyph at the
   right edge), so a bare [?] flex item would strand in the middle. margin-right:
   auto makes it hug the title on the left and pushes the ::after glyph to the
   edge; the h3's own gap supplies the spacing after the title. */
.anr-card > h3 > .anr-info-btn { margin-right: auto; }
/* The isolate seclabel is a plain inline span, so the [?] just needs a small gap. */
.anr-iso-seclabel > .anr-info-btn { margin-left: 8px; }
/* Label + [?] grouped on the left of a flex `summary` (which is space-between, so
   only the open/close marker sits at the right edge). */
.anr-summary-label { display: inline-flex; align-items: center; gap: 8px; }
.anr-summary-label .anr-info-btn { float: none; }

/* ---------- TIP POPUP (inline row help) ---------- */
.anr-readout th { position: relative; }
.anr-tip-btn {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 0;
  line-height: inherit;
  opacity: 0.4;
}
.anr-tip-btn:hover { color: var(--accent); opacity: 1; }
.anr-tip {
  display: none;
  position: absolute;
  bottom: calc(100% + 4px);
  left: 0;
  z-index: 100;
  min-width: 260px;
  max-width: 380px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  line-height: 1.65;
  letter-spacing: 0.02em;
  padding: 10px 12px;
  background: var(--bg);
  border: var(--bd-hairline);
  box-shadow: var(--shadow-popover);
  white-space: normal;
  word-break: normal;
}
.anr-tip.is-active { display: block; }
.anr-info-panel {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  line-height: 1.7;
  letter-spacing: 0.02em;
  padding: 12px 0 16px;
  border-bottom: var(--bd-rule);
  margin-bottom: 14px;
}
.anr-info-panel strong {
  color: var(--fg);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
/* Popup mode: wireInfoToggle floats the panel as an overlay anchored to the
   heading/row that holds its [?] button (unified with the .anr-tip row help),
   instead of the old inline drop-down that pushed content down. The button itself
   is left in place, so its heading layout is unchanged. */
.anr-info-pop-anchor { position: relative; }
.anr-info-panel.anr-info-pop {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 100;
  min-width: 260px;
  max-width: 380px;
  padding: 10px 12px;
  margin: 0;
  border: var(--bd-hairline);
  background: var(--bg);
  box-shadow: var(--shadow-popover);
  line-height: 1.65;
  white-space: normal;
}
.anr-info-panel.anr-info-pop.is-active { display: block; }
/* Escaped popup: pinned to the viewport (JS sets left/top from the button's rect)
   so a clipping-overflow ancestor - the compare view's side-by-side columns - can't
   cut it off. Only added when such an ancestor is detected; ordinary pages keep the
   absolute anchoring above. */
.anr-info-panel.anr-info-pop.anr-info-pop-fixed { position: fixed; }

/* ---------- CUSTOM PLAYER ---------- */
.anr-player {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  border: var(--bd-hairline);
  background: var(--surface);
  margin-bottom: 14px;
}
.anr-player-play,
.anr-player-mute {
  flex: 0 0 auto;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: var(--bd-hairline);
  background: var(--bg);
  color: var(--fg);
  font-size: 12px;
  cursor: pointer;
  padding: 0;
  font-family: var(--font-sans);
  line-height: 1;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.anr-player-play:hover,
.anr-player-mute:hover {
  background: var(--fg);
  color: var(--bg);
}
/* Replay state (playback ended): the ↻ glyph reads thin at the play/pause size. */
.anr-player-play.is-replay { font-size: 16px; }
.anr-player-track {
  flex: 1 1 auto;
  height: 32px;
  cursor: pointer;
  position: relative;
  display: flex;
  align-items: center;
}
.anr-player-track::before {
  content: '';
  position: absolute;
  left: 0; right: 0;
  height: 3px;
  background: var(--rule);
}
/* Scrubber fill. Painted with transform: scaleX(), never width - see
   setPlayerFill() in core/util.js. It is repainted on EVERY animation frame while
   something plays, and a percentage width forces a layout of the whole analysis
   document each time; a scaleX is composited instead. Hence width:100% here with
   the scale doing the work, and origin on the left so it grows rightwards. */
.anr-player-fill {
  height: 3px;
  background: var(--accent);
  width: 100%;
  transform: scaleX(0);
  transform-origin: 0 50%;
  pointer-events: none;
  position: relative;
  z-index: 1;
}
.anr-player-time {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.06em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  min-width: 80px;
  text-align: right;
}
/* Volume: a mute toggle that reveals a popup (hover or click) with a vertical
   draggable bar, 0-225% - default 100% is unity gain, no boost applied. */
.anr-player-vol {
  flex: 0 0 auto;
  position: relative;
  display: flex;
  align-items: center;
}
/* The popup's own box includes a 10px padding-bottom "bridge" down to the
   button, so the gap is still part of its (and the wrap's) hoverable area -
   without it, the mouse crosses uncovered space on the way up and the
   wrap's mouseleave fires before the cursor ever reaches the popup. */
.anr-player-volpop {
  position: absolute;
  left: 50%;
  bottom: 100%;
  padding-bottom: 10px;
  transform: translateX(-50%);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--dur-fast) ease;
  z-index: 30;
}
.anr-player-vol.is-open .anr-player-volpop {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
/* The 10px padding-bottom bridges the card down to the button's TOP; this reaches
   the rest of the way DOWN over the whole 32px mute button (as wide as the card),
   so sliding from the card onto the button never crosses uncovered space and the
   wrap's mouseleave can't fire mid-move. Inherits the popup's pointer-events, so
   it's only live while open. */
.anr-player-volpop::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: 34px;
}
.anr-player-volcard {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 10px 10px 12px;
  background: var(--surface);
  border: var(--bd-hairline);
  box-shadow: var(--shadow-popover);
}
.anr-player-volpct {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.anr-player-voltrack {
  width: 28px;
  height: 96px;
  position: relative;
  cursor: pointer;
  touch-action: none;   /* a vertical drag sets volume, never scrolls the page */
}
.anr-player-voltrack::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: 50%;
  width: 3px;
  transform: translateX(-50%);
  background: var(--rule);
}
/* 100% reference tick - the default/unity level, at 100/225 up the track. */
.anr-player-voltrack::after {
  content: '';
  position: absolute;
  left: 50%;
  bottom: 44.44%;
  width: 12px;
  height: 1px;
  transform: translateX(-50%);
  background: var(--muted);
}
.anr-player-volfill {
  width: 3px;
  height: 0%;
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  background: var(--accent);
  pointer-events: none;
}
/* Past 100%: the fill switches to the same amber used for "Partial" tags
   elsewhere, a quiet cue that this clip is being amplified beyond its source. */
.anr-player-volfill.is-boosted {
  background: var(--partial);
}

/* ---------- TRANSPORT ---------- */
audio { width: 100%; }

/* ---------- PROGRESS ---------- */
.anr-progress {
  margin: 18px 0 20px;
}
.anr-progress-bar {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 900;
  white-space: pre;
  line-height: 1.4;
  overflow: hidden;
  color: var(--fg);
}
.anr-progress-bar .anr-bar-fill {
  color: var(--accent);
}
.anr-progress-label {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1.3;
  min-height: 1.3em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 4px;
}

/* ---------- DROP LOADER (bottom-of-window popup) ---------- */
.anr-drop-loader {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%) translateY(14px);
  z-index: 1200;
  width: min(360px, calc(100vw - 32px));
  background: var(--bg);
  border: var(--bd-rule);
  box-shadow: var(--shadow-popover);
  padding: 12px 14px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) ease, transform var(--dur-base) ease;
}
.anr-drop-loader.is-open { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }
/* Label + Cancel share one line: label fills the left, button hugs the right. */
.anr-drop-loader-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 8px;
}
.anr-drop-loader-label {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1 1 auto;
  min-width: 0;
}
.anr-drop-loader-cancel {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  transition: color var(--dur-snappy) ease;
}
.anr-drop-loader-cancel:hover { color: var(--fg); }

/* "Suggest this format" nudge - a bottom toast shown for unreadable or very thin
   files (see showSuggestPopup in app.js). Same bottom-anchored slide-in as the
   drop loader, but it persists until dismissed or the next file is loaded. */
.anr-suggest-pop {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%) translateY(14px);
  z-index: 1200;
  width: min(380px, calc(100vw - 32px));
  background: var(--bg);
  border: var(--bd-rule);
  box-shadow: var(--shadow-popover);
  padding: 12px 14px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) ease, transform var(--dur-base) ease;
}
.anr-suggest-pop.is-open { opacity: 1; transform: translateX(-50%) translateY(0); pointer-events: auto; }

/* Post-analysis "share this" nudge - same popover language as the suggest popup,
   but anchored bottom-LEFT so the two never collide (and they're mutually exclusive
   anyway; see scheduleShareNudge in app.js). */
.anr-share-nudge {
  position: fixed;
  left: 22px;
  bottom: 22px;
  transform: translateY(14px);
  z-index: 1200;
  width: min(320px, calc(100vw - 32px));
  background: var(--bg);
  border: var(--bd-rule);
  box-shadow: var(--shadow-popover);
  padding: 12px 14px;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-base) ease, transform var(--dur-base) ease;
}
.anr-share-nudge.is-open { opacity: 1; transform: translateY(0); pointer-events: auto; }
.anr-share-nudge-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}
.anr-share-nudge-kicker {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.anr-share-nudge-close {
  flex: 0 0 auto;
  background: none;
  border: none;
  padding: 0;
  line-height: 1;
  font-size: 18px;
  color: var(--muted);
  cursor: pointer;
  transition: color var(--dur-snappy) ease;
}
.anr-share-nudge-close:hover { color: var(--fg); }
.anr-share-nudge-text {
  margin: 0 0 10px;
  font-size: var(--t-small);
  line-height: 1.45;
  color: var(--fg);
}
.anr-share-nudge-cta {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  background: var(--fg);
  color: var(--bg);
  border: none;
  padding: 8px 12px;
  cursor: pointer;
  transition: opacity var(--dur-snappy) ease;
}
.anr-share-nudge-cta:hover { opacity: 0.85; }
@media (max-width: 700px) {
  /* On phones, match the 16px gutter the width already assumes (left was 22px, so
     the right margin came out narrower) and lift clear of the iOS home indicator;
     the CTA spans the card for an easy thumb tap. */
  .anr-share-nudge {
    left: 16px;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  }
  .anr-share-nudge-cta { display: block; width: 100%; text-align: center; }
}
.anr-suggest-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}
.anr-suggest-kicker {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.anr-suggest-close {
  flex: 0 0 auto;
  background: none;
  border: none;
  padding: 0;
  line-height: 1;
  font-size: 18px;
  color: var(--muted);
  cursor: pointer;
  transition: color var(--dur-snappy) ease;
}
.anr-suggest-close:hover { color: var(--fg); }
.anr-suggest-text {
  margin: 0 0 10px;
  font-size: var(--t-small);
  line-height: 1.45;
  color: var(--fg);
}
.anr-suggest-cta {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  background: var(--fg);
  color: var(--bg);
  border: none;
  padding: 8px 12px;
  cursor: pointer;
  transition: opacity var(--dur-snappy) ease;
}
.anr-suggest-cta:hover { opacity: 0.85; }
.anr-suggest-cta:disabled { cursor: default; opacity: 0.6; }
/* Turnstile gate: revealed under the CTA on click; the mailto is only assembled
   once the challenge passes (see showSuggestPopup in app.js). */
.anr-suggest-gate { margin-top: 10px; }
.anr-suggest-gate-status {
  margin: 0 0 8px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  color: var(--muted);
}
.anr-suggest-turnstile { min-height: 65px; }
/* The drop loader's bar: a continuously scrolling strip of accent slashes
   inside brackets ([////////]). The motion is a pure CSS `transform`, NOT a
   requestAnimationFrame loop - rAF runs on the main thread, so it froze
   whenever the file's heavy synchronous work (FFTs, BPM, pixel stats) blocked
   it. A transform animation runs on the compositor and keeps moving regardless. */
.anr-css-bar {
  display: flex;
  align-items: center;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 900;
  line-height: 1.2;
  color: var(--fg);
}
.anr-css-bar-track {
  position: relative;
  flex: 1 1 auto;
  height: 1.2em;
  overflow: hidden;
  margin: 0 1px;
}
.anr-css-bar-win {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  width: 25%;                 /* the bouncing window, matching asciiBar's round(W*0.25) */
  white-space: pre;
  overflow: hidden;           /* clip the slash pile to a solid 25%-wide block */
  color: var(--accent);
  will-change: transform;
  /* steps() makes it jump in discrete chunks - choppy, like the original ASCII
     bar's character-by-character stepping - rather than a smooth glide.
     jump-none (not the default jump-end) holds BOTH the first and last step, so
     the window rests flush against the left bracket AND the right one; jump-end
     only holds the left, leaving a visible gap before "]" on the right side. */
  animation: anr-css-bar-bounce 1.9s steps(16, jump-none) infinite alternate;
}
/* Paused while the popup is hidden so it costs nothing between loads. */
.anr-drop-loader:not(.is-open) .anr-css-bar-win { animation-play-state: paused; }
/* The window is 25% of the track wide, so 300% of its own width = 75% of the
   track: left edge → right edge. `alternate` + linear timing reproduces the
   original asciiBar's triangle bounce (it swept ~1.9 s each way). */
@keyframes anr-css-bar-bounce {
  from { transform: translateX(0); }
  to   { transform: translateX(300%); }
}
@media (prefers-reduced-motion: reduce) {
  .anr-css-bar-win { animation: none; }
}
/* Inline per-card loader (e.g. building a folder treemap) */
.anr-inline-loader {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
}
.anr-inline-loader-label {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
/* Keep the ASCII bar at its full character width - flex shrink would otherwise
   clip its trailing "]" since .anr-progress-bar is overflow:hidden. */
.anr-inline-loader .anr-progress-bar { flex: 0 0 auto; }
/* MIDI instrument / track lists */
.anr-midi-list { margin: 0; padding-left: 20px; font-size: var(--t-small); line-height: 1.7; color: var(--fg); }
.anr-midi-list li { word-break: break-word; }

/* GPX/KML/GeoJSON map */
.anr-geo-map { width: 100%; height: 380px; border: var(--bd-hairline); background: var(--surface); }
.anr-geo-map .anr-hint { padding: 16px; }

/* LRC timed-lyrics list */
.anr-lrc-list { max-height: 460px; overflow: auto; font-size: var(--t-small); line-height: 1.5; }
.anr-lrc-line { display: flex; gap: 12px; padding: 1px 0; }
.anr-lrc-time {
  flex: 0 0 auto; min-width: 56px; text-align: right;
  font-family: var(--font-mono); color: var(--muted);
}
.anr-lrc-text { flex: 1 1 auto; word-break: break-word; }

/* Embedded lyrics block */
.anr-lyrics {
  margin: 0;
  max-height: 360px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  font-family: var(--font-sans);
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--fg);
}

/* ---------- OCR ---------- */
.anr-ocr-text {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  background: var(--surface);
  color: var(--fg);
  padding: 16px;
  border: var(--bd-hairline);
  max-height: 360px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
  margin: 28px 0 0;
  line-height: 1.5;
}
.anr-ocr-text:empty::before { content: "(no text detected)"; color: var(--muted); font-style: italic; }

/* ---------- BANNERS ---------- */
.anr-error {
  background: var(--accent);
  color: var(--accent-fg);
  padding: 14px 18px;
  margin: 16px 0;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  letter-spacing: 0.02em;
}
.anr-cloud-warning, .anr-empty-warning { line-height: 1.55; }
.anr-cloud-warning ul, .anr-empty-warning ul { line-height: 1.55; }
.anr-info {
  background: var(--surface);
  border: var(--bd-rule);
  color: var(--fg);
  padding: 14px 18px;
  margin: 16px 0;
  font-family: var(--font-mono);
  font-size: var(--t-small);
}

/* ---------- DETAILS (raw dump) ---------- */
details {
  border-top: var(--bd-hairline);
  padding: 16px 0 4px;
  margin: 24px 0 0;
}
details summary {
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--fg);
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
details summary::-webkit-details-marker { display: none; }
details summary:hover { color: var(--accent); }
details summary::after {
  content: "+";
  font-family: var(--font-mono);
  font-size: 16px;
  color: var(--muted);
  transition: color var(--dur-snappy) ease;
}
details summary:hover::after { color: var(--accent); }
details[open] summary::after { content: "\2212"; }
/* A <details> that LEADS a card (the HTML "Rendered preview", which is inserted
   above the readout table) sits immediately under the heading's own rule, so its
   border-top would draw a second hairline 24px below the first. Drop the rule and
   the top gap there; the heading's 14px margin is the separator, exactly as it is
   when a readout table follows instead. */
.anr-card > h3 + details { border-top: 0; margin-top: 0; padding-top: 0; }

/* ---------- ADVANCED CARD (one dropdown, uniform parts) ----------
   The photo / video / sound "Advanced" cards are ONE collapsible section, sat at
   the very bottom of the page and closed by default: the card's own <h3> is the
   only disclosure (.anr-collapsible, +/- glyph, handled by the delegated toggle in
   app.js). Its heavier forensic reads are flat parts inside it - a
   .anr-readout-section-style sub-heading with its content below, exactly the
   Metadata-card idiom - NOT a stack of nested accordions. Nothing is boxed or
   tinted. Direct-child selectors only, so the box tree, the DTMF timing list and
   the [?] info panels nested deeper keep their own styling. */
.anr-adv > .anr-adv-part { margin: 0; padding: 0; }
.anr-adv > .anr-adv-part > .anr-adv-parthead {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 6px;
}
/* Divider + gap only BETWEEN parts: the first part sits directly under the card
   heading, which already carries its own hairline, so a second line there just
   doubles it up.
   Written as part ~ part rather than :first-of-type because "the first part" is
   not the first child of any type - h3help's [?] info panel (sound, video) and
   photo's intro hint both sit between the <h3> and the first part, so
   :first-of-type matched the wrong element or nothing at all. */
.anr-adv > .anr-adv-part ~ .anr-adv-part > .anr-adv-parthead {
  margin-top: 28px;
  padding-top: 12px;
  border-top: var(--bd-hairline);
}
/* A genuine disclosure nested inside a part body (the DTMF timing list, the box
   tree): trim the generic details' large top gap so it sits close under its part. */
.anr-adv > .anr-adv-part > div > details { margin-top: 12px; padding-top: 12px; }

/* --flow: the card is ONE continuous readout. No sub-headings, no dividers, no
   gaps - the rows simply run on, exactly as they would in a single table. The
   .anr-adv-part wrappers survive only to carry each group's aria-label; visually
   there is nothing between them. Used by the sound renderer, where every row
   already carries its own label and [?] help. Photo and video keep their headings:
   their parts hold images and trees that a label is doing real work above.
   The rules come from .anr-readout, so the only fix needed is its last-row
   exception - without this the join between two groups would be the one place with
   no rule, which is precisely backwards. */
.anr-adv--flow > .anr-adv-part ~ .anr-adv-part { margin-top: 0; padding-top: 0; border-top: none; }
.anr-adv--flow .anr-readout tr:last-child th,
.anr-adv--flow .anr-readout tr:last-child td { border-bottom: var(--bd-rule); }
.anr-adv--flow > .anr-adv-part:last-child .anr-readout tr:last-child th,
.anr-adv--flow > .anr-adv-part:last-child .anr-readout tr:last-child td { border-bottom: none; }
/* The loudness graph + its transport are the one non-table thing in that flow, so
   they get air on both sides before the rows resume. */
.anr-adv--flow .anr-loudgraph { margin: 4px 0 22px; }
/* The one part allowed to break the flow: --titled keeps a sub-heading and the
   divider above it, because its content is a keypad grid rather than rows and
   would read as noise arriving unannounced. Built last, so nothing flows after it. */
.anr-adv--flow > .anr-adv-part--titled { margin-top: 36px; padding-top: 26px; border-top: var(--bd-hairline); }
.anr-adv--flow > .anr-adv-part--titled > .anr-adv-parthead { margin: 0 0 14px; }
/* The flow ends where a titled part begins, so the last row before one drops its
   rule - the titled part's own hairline is the divider there, and keeping both
   would draw two lines a gap apart. */
.anr-adv--flow > .anr-adv-part:has(+ .anr-adv-part--titled) .anr-readout tr:last-child th,
.anr-adv--flow > .anr-adv-part:has(+ .anr-adv-part--titled) .anr-readout tr:last-child td { border-bottom: none; }

/* ---------- DTMF touch-tones (sound Advanced) ----------
   The decoded number, then where its tones fall across the recording, then one
   cell per tone. Replaces a <details> holding a start/end table, which buried the
   answer and could not show the shape of the dialling at all. */
.anr-dtmf-seq {
  font-family: var(--font-mono);
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: 0.22em;
  word-break: break-all;
  margin: 0 0 16px;
}
/* Hairline the width of the file, one tick per digit: a number dialled in a single
   burst reads as a burst, digits spread through a long call read as spread. */
.anr-dtmf-track {
  position: relative;
  height: 18px;
  border-bottom: var(--bd-hairline);
  margin: 0 0 7px;
}
.anr-dtmf-tick { position: absolute; bottom: 0; width: 1px; height: 11px; background: var(--accent); }
.anr-dtmf-caption {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 16px;
}
.anr-dtmf-keys {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(66px, 1fr));
  gap: 8px;
}
/* display:grid above outranks the UA [hidden] rule, so without this the grid
   stays visible and the "Show N tones" button reveals nothing. */
.anr-dtmf-keys[hidden] { display: none; }
.anr-dtmf-key {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 11px 4px 9px;
  background: transparent;
  border: var(--bd-hairline);
  color: var(--fg);
  font: inherit;
  cursor: pointer;
  transition: border-color var(--dur-snappy) ease, color var(--dur-snappy) ease;
}
.anr-dtmf-key:hover,
.anr-dtmf-key:focus-visible { border-color: var(--accent); color: var(--accent); outline: none; }
.anr-dtmf-digit { font-family: var(--font-mono); font-size: 20px; line-height: 1; font-weight: 500; }
.anr-dtmf-at {
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  letter-spacing: 0.06em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.anr-dtmf-key:hover .anr-dtmf-at,
.anr-dtmf-key:focus-visible .anr-dtmf-at { color: var(--accent); }

/* ---------- ABOUT / FOOTER ---------- */
.site-footer--about {
  background: var(--fg);
  color: var(--bg);
  border-top: none;
  padding: clamp(40px, 6vw, 64px) var(--pad-x);
  max-width: none;
  scroll-margin-top: var(--nav-offset);
}
.site-footer--about a { color: var(--bg); }
.site-footer--about a:hover { color: var(--accent); }
.footer-about-heading {
  margin-bottom: 28px;
}
.footer-about-heading .footer-mark {
  font-size: var(--t-h2);
  font-weight: 500;
  letter-spacing: -0.01em;
  /* Carries the per-letter hover effect (setupFooterFx), like the headings - so
     suppress the text caret and selection on its split letters. */
  cursor: default;
  -webkit-user-select: none;
  user-select: none;
}
.footer-about-heading .footer-meta {
  color: rgba(255,255,255,0.5);
  margin-top: 4px;
}
/* On the home page the footer also carries the "Recently analysed" panel, which
   sits ahead of this heading in source (the heading lives inside the generated
   FOOTER markers and can't be moved there). Flex-reorder so the heading leads
   the footer, above the recent panel. Scoped by :has so only the home footer
   (the one containing .recent-history) becomes a flex column. */
.site-footer--about:has(.recent-history) { display: flex; flex-direction: column; }
.site-footer--about:has(.recent-history) .footer-about-heading { order: -1; }
.about-defs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0 var(--gap);
  margin: 0 0 32px;
  padding: 0;
}
/* Three columns is a lot to fit as the footer narrows: drop to two, then one. */
@media (max-width: 900px) { .about-defs { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 600px) { .about-defs { grid-template-columns: 1fr; } }
.about-defs > div {
  border-top: 1px solid var(--border-on-dark);
  padding: 12px 0;
}
.about-defs dt {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--white-a40);
  margin: 0 0 2px;
}
.about-defs dd {
  margin: 0;
  font-size: var(--t-small);
  color: var(--white-a80);
}
/* Small, secondary "licence" link after each dependency. Bracketed to match the
   site's monospace [?] idiom; muted so it reads as an aside next to the dep name.
   The footer forces plain <a> to --fg in dark mode, so both themes are set below. */
.about-defs .dep-lic {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--white-a40);
  text-decoration: none;
  white-space: nowrap;
}
.about-defs .dep-lic::before { content: "["; }
.about-defs .dep-lic::after { content: "]"; }
.about-defs .dep-lic:hover { color: var(--accent); }
:root[data-theme="dark"] .site-footer--about a.dep-lic { color: var(--white-a40); }
:root[data-theme="dark"] .site-footer--about a.dep-lic:hover { color: var(--accent); }
.about-details {
  border-top: none;
  padding: 0;
  margin: 0 0 24px;
}
/* Outlined button - same border language as the offline tiers + Install/Clear. */
.about-details summary {
  display: inline-flex;
  width: max-content;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  color: var(--white-a80);
  background: transparent;
  border: 1px solid var(--border-on-dark);
  padding: 6px 14px;
  letter-spacing: 0.08em;
}
.about-details summary:hover { color: var(--accent); border-color: rgba(255,255,255,0.5); }
/* Match the label size so the marker doesn't make the button taller than the
   sibling Install/Clear buttons. */
.about-details summary::after { color: var(--white-a40); font-size: var(--t-tiny); line-height: 1; }
.about-details[open] summary { margin-bottom: 16px; }
.about-details .about-defs { margin: 0; }
/* Dependencies is the 3rd button in the Install/Clear row and must never resize
   or move when toggled. display:contents promotes its <summary> and <dl> to
   direct grid items: the summary auto-flows into the 3rd cell (fixed-width, like
   the two buttons beside it) and stays put in both states, while the definitions
   open as a full-width panel on the next row instead of stretching the button. */
.offline-bottom-row .about-details { display: contents; }
/* Modern Chromium wraps a <details>'s non-summary content in a ::details-content
   box, so display:contents on .about-details alone only promotes the <summary> -
   the <dl> stays trapped in that box and lands in a single 1/3 track. Promote the
   content box too so the <dl> is a direct grid item again and its grid-column
   span below takes effect. (Older engines without ::details-content ignore this
   and the <dl> was already a direct grid item.) */
.offline-bottom-row .about-details::details-content { display: contents; }
.offline-bottom-row .about-details summary { justify-content: center; box-sizing: border-box; width: 100%; }
/* Open: the list spans the whole row underneath; the button above is unchanged.
   (Closed, the <dl> drops out of the grid entirely, leaving a clean 3-up row.) */
.offline-bottom-row .about-details .about-defs { grid-column: 1 / -1; }
.offline-bottom-row .about-details:not([open]) .about-defs { display: none; }
/* No summary bottom-margin inside the row - it would unbalance the button height. */
.offline-bottom-row .about-details[open] summary { margin-bottom: 0; }

.offline-section {
  border-top: 1px solid var(--border-on-dark);
  padding-top: 24px;
  margin-bottom: 24px;
}
.offline-heading {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.5);
  margin: 0 0 12px;
}
.offline-heading-row { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; }
.offline-heading-row .offline-heading { margin-bottom: 0; }
/* The heading doubles as the collapse toggle. It carries .offline-heading for
   its mono/uppercase look; reset the button chrome and add a rotating chevron. */
.offline-toggle {
  display: inline-flex; align-items: baseline; gap: 8px;
  background: none; border: 0; padding: 0;
  cursor: pointer; text-align: left;
}
.offline-toggle:hover { color: var(--accent); }
.offline-toggle-ico { font-size: 0.8em; transition: transform 0.15s ease; transform: rotate(-90deg); }
.offline-section.is-open .offline-toggle-ico { transform: rotate(0deg); }
.offline-section:not(.is-open) .offline-body { display: none; }
/* [?] dropdown listing each download tier - only shown on mobile, where the
   per-tier descriptions are hidden. Same plain "[?]" style as the rest of the
   site (.anr-info-btn), tinted for the dark footer. */
.offline-help { display: none; }
.offline-help-btn {
  display: inline-block;   /* not list-item -> no disclosure +/- marker */
  list-style: none; cursor: pointer; user-select: none;
  font-family: var(--font-mono); font-size: var(--t-tiny); letter-spacing: 0.08em;
  color: rgba(255,255,255,0.5); padding: 0;
}
.offline-help-btn::-webkit-details-marker { display: none; }
.offline-help-btn::marker { content: ''; }
/* The generic `details summary::after` adds a +/- disclosure glyph; the offline
   [?] is a plain help toggle, so suppress it in both states. */
.offline-help-btn::after,
.offline-help[open] .offline-help-btn::after { content: none; }
.offline-help-btn:hover,
.offline-help[open] .offline-help-btn { color: var(--accent); }
/* The panel is a normal-flow block (a real dropdown that pushes the download
   buttons down), revealed when the [?] details is open. */
.offline-help-panel { display: none; margin: 0 0 14px; }
.offline-section:has(.offline-help[open]) .offline-help-panel { display: block; }
.offline-help-panel > div {
  padding: 8px 0; border-top: 1px solid rgba(255,255,255,0.12);
  font-family: var(--font-sans); font-size: var(--t-small); line-height: 1.45;
  color: var(--white-a80);
}
.offline-help-panel > div:first-child { border-top: none; }
.offline-help-panel strong {
  color: var(--on-dark); font-family: var(--font-mono); font-size: var(--t-tiny);
  text-transform: uppercase; letter-spacing: 0.08em;
}
.offline-help-panel span { color: rgba(255,255,255,0.45); font-size: 11px; margin-left: 6px; }
.offline-options {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}
/* Let the tier tracks resolve to a strict 1fr (same as .offline-bottom-row),
   so the download buttons and the Install/Clear/Dependencies row are identical
   widths - content can't push a track wider than its third. */
.offline-options > * { min-width: 0; }
/* Failure note under the download buttons (created by app.js on a failed tier). */
.offline-status {
  margin: 14px 0 0;
  padding-left: 10px;
  border-left: 2px solid var(--accent);
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  line-height: 1.6;
  letter-spacing: 0.03em;
  color: var(--accent);
}
.offline-status[hidden] { display: none; }

/* Same 3-column grid as the download tiers above, so Install / Clear /
   Dependencies line up at the same width as the download buttons. This base
   MUST precede the @media block below - equal specificity means source order
   decides, so a later base would otherwise clobber the mobile gap override. */
.offline-bottom-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  margin-top: 10px;
}
.offline-bottom-row > * { min-width: 0; }   /* let labels wrap instead of widening the track */

/* Keep the three tiers on one row even on mobile; drop the long descriptions
   there so the compact name + size cards fit across a narrow screen. */
@media (max-width: 700px) {
  .offline-options { gap: 6px; }
  .offline-btn { padding: 10px 8px; }
  .offline-tier { font-size: var(--t-tiny); }
  .offline-desc { display: none; }
  /* Descriptions are hidden here, so reveal the [?] that lists them instead.
     display:contents (not block) so the <details> generates no box of its own -
     a block flex item here rendered as a stray hairline above the heading; its
     [?] summary still sits inline in the row and the toggle still works. */
  .offline-help { display: contents; }
  .offline-size { font-size: 10px; }
  /* Match the tiers' tighter mobile gap and let labels wrap inside their cell. */
  .offline-bottom-row { gap: 6px; }
  .offline-bottom-row .offline-install,
  .offline-bottom-row .offline-clear,
  .offline-bottom-row .about-details summary {
    padding: 6px 4px;
    text-align: center;
    overflow-wrap: break-word;
  }
  /* "Dependencies" can't wrap (one word); drop the +/- marker so it fits. */
  .offline-bottom-row .about-details summary::after { display: none; }
  .offline-bottom-row .about-details summary { gap: 0; }
  /* Install's instruction message expands full width like an opened Dependencies
     (mobile only); Clear + Dependencies then split the row beneath it. */
  .offline-bottom-row .offline-install.is-expanded { grid-column: 1 / -1; }
  .offline-bottom-row:has(.offline-install.is-expanded) { grid-template-columns: repeat(2, 1fr); }
}
.offline-btn {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 16px;
  border: 1px solid var(--border-on-dark);
  background: transparent;
  color: var(--white-a80);
  cursor: pointer;
  text-align: left;
  font-family: var(--font-sans);
  transition: border-color var(--dur-base) ease, background var(--dur-base) ease;
}
/* "Recommended" crown badge, top-right of the Everything tier. Faint + decorative
   (pointer-events:none so it never intercepts the button click). */
.offline-rec {
  position: absolute;
  top: 8px;
  right: 10px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  pointer-events: none;
  color: rgba(255,255,255,0.5);
}
.offline-rec-crown { width: 14px; height: auto; display: block; }
.offline-rec-text {
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--white-a40);
}
@media (max-width: 700px) {
  /* Keep just the crown when the buttons get narrow. */
  .offline-rec { top: 6px; right: 6px; }
  .offline-rec-text { display: none; }
}
.offline-btn:hover { border-color: rgba(255,255,255,0.5); background: rgba(255,255,255,0.05); }
.offline-btn.is-done { border-color: var(--accent); }
.offline-btn.is-done.is-fading { border-color: var(--border-on-dark); transition: border-color 1s ease; }
.offline-btn.is-active { border-color: rgba(255,255,255,0.6); }
/* A lower tier already covered by a higher cached one: greyed and non-interactive
   (its files are in the cache anyway, so there's nothing to download). */
.offline-btn.is-included {
  opacity: 0.45;
  cursor: default;
  pointer-events: none;
  border-color: var(--hairline);
}
.offline-tier {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.offline-desc {
  font-size: 12px;
  color: var(--white-a40);
  line-height: 1.3;
}
.offline-size {
  font-family: var(--font-mono);
  font-size: 11px;
  color: rgba(255,255,255,0.35);
  margin-top: 2px;
}
.offline-bar {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  font-weight: 900;
  white-space: pre;
  overflow: hidden;
  color: rgba(255,255,255,0.3);
  margin-top: 6px;
  line-height: 1;
}
.offline-bar .offline-bar-fill {
  color: var(--white-a80);
}
.offline-btn.is-done .offline-bar .offline-bar-fill { color: var(--accent); }
.offline-btn.is-done.is-fading .offline-bar .offline-bar-fill { color: var(--white-a80); transition: color 1s ease; }
/* "✓ Cached" badge pinned to the bottom of a tier button once its files are in
   the offline cache. Persisted across reloads via localStorage; the accent tint
   stays even after the border fades, so the cached tier reads at a glance. */
.offline-cached[hidden] { display: none; }
.offline-cached {
  margin-top: 6px;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
  display: flex;
  align-items: center;
  gap: 5px;
}
/* Badge parts: desktop shows "✓ Cached · v2.0"; the dash variant of the
   separator is desktop-hidden. */
.offline-cached-dash { display: none; }
@media (max-width: 700px) {
  .offline-cached { font-size: 9px; }
  /* Mobile trims to just "Cached - v2.0": no checkmark, the · swapped for a -. */
  .offline-cached-check, .offline-cached-dot { display: none; }
  .offline-cached-dash { display: inline; }
}

/* Install + Clear share the same outlined button shape as everything else.
   Clear keeps muted text so the destructive action stays de-emphasised. */
.offline-install,
.offline-clear {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: transparent;
  border: 1px solid var(--border-on-dark);
  cursor: pointer;
  padding: 6px 14px;
  transition: border-color var(--dur-base) ease, color var(--dur-base) ease;
}
.offline-install { color: var(--white-a80); }
.offline-install:hover { border-color: rgba(255,255,255,0.5); color: var(--accent); }
.offline-clear { color: var(--white-a40); }
.offline-clear:hover { border-color: rgba(255,255,255,0.5); color: var(--white-a80); }

:root[data-theme="dark"] .offline-section { border-top-color: var(--hairline); }
:root[data-theme="dark"] .offline-btn { border-color: var(--hairline); }
:root[data-theme="dark"] .offline-btn:hover { border-color: var(--hairline-strong); background: rgba(255,255,255,0.03); }
:root[data-theme="dark"] .offline-install,
:root[data-theme="dark"] .offline-clear,
:root[data-theme="dark"] .about-details summary { border-color: var(--hairline); }
:root[data-theme="dark"] .offline-install:hover,
:root[data-theme="dark"] .offline-clear:hover,
:root[data-theme="dark"] .about-details summary:hover { border-color: var(--hairline-strong); }

/* Footer bottom: grouped link columns (Site / Project / Contact) above a thin
   copyright line. This replaced the old single "copyright | links | contact" row
   so it can hold a fuller link set. By design the columns point only at
   destinations NOT already in the header nav - the header covers Home, About,
   Patches, Formats, Stats and Samples, so the footer lists Compare, Docs, Privacy
   plus the off-site project links and the contact button, never repeating them. */
.footer-bottom {
  border-top: 1px solid var(--border-on-dark);
  padding-top: 28px;
}
/* Two clusters: a left lead (back-to-home button + copyright) and the grouped
   link sections on the right. Two classes so this beats .footer-row's own
   display:flex (defined later at equal specificity) and keeps our alignment. */
.footer-row.footer-bottom {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 32px 64px;
}
.footer-lead {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 18px;
}
.footer-lead .footer-nav-btn { align-self: flex-start; }
/* Reciprocal linkback button (a small GIF badge). Block so it sits on its own
   line above the copyright (order swaps it ahead of the DOM-earlier .footer-
   copyright, both here and in the mobile reorder below). Its width is set
   inline by app.js/docs.js to match the "valjdakosta.com · 2026" line's
   rendered width (a flex stretch can't target one specific sibling once the
   nav columns render between them on phones); the native 88px attr is just
   the pre-JS fallback so there's no layout jump. */
.footer-linkback { display: block; width: 88px; line-height: 0; order: 1; }
.footer-copyright { order: 2; }
/* Its JS-driven width rarely lands on the gif's native 88px, so scaling would
   otherwise smooth-blur the pixel art; keep it crisp. */
.footer-linkback img { display: block; width: 100%; height: auto; image-rendering: pixelated; }
.footer-bottom .footer-meta { color: var(--white-a40); }
.footer-bottom .footer-meta a { color: rgba(255,255,255,0.5); }
/* The link sections, grouped together on the right of the bottom row (natural
   width, not stretched edge-to-edge - that left big empty gaps). Wraps below the
   lead block on narrow screens. */
.footer-cols {
  display: flex;
  flex-wrap: wrap;
  gap: 26px 56px;
  margin: 0;
}
.footer-col {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 9px;
}
.footer-col-label { margin: 0 0 3px; }
/* Column links take the same light footer-link colour as the rest of the footer
   (via .site-footer--about a, which flips correctly per theme); we only set the
   type here and drop the underline so they read as a tidy list. */
.footer-col a,
.footer-col .footer-contact {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  letter-spacing: 0.02em;
  text-decoration: none;
}
.footer-col .footer-contact { color: rgba(255,255,255,0.7); }
.footer-col a:hover,
.footer-col .footer-contact:hover { color: #fff; text-decoration: none; }
.footer-copyright { margin: 0; }
/* "Email me!" - a plain text button (no scrapeable address) that reveals the
   mailto only after a Turnstile challenge passes (see wireFooterContact). */
.footer-contact {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
  color: rgba(255,255,255,0.7);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.footer-contact:hover { color: #fff; }
.footer-contact:disabled { cursor: default; text-decoration: none; opacity: 0.7; }
/* Cross-page nav button in the footer bottom row (About ↔ Main page). Outlined
   to match the offline buttons; text colour comes from .site-footer--about a. */
.footer-nav-btn {
  align-self: center;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1px solid var(--border-on-dark);
  padding: 6px 14px;
  text-decoration: none;
  transition: border-color var(--dur-base) ease, color var(--dur-base) ease;
}
.footer-nav-btn:hover { border-color: rgba(255,255,255,0.5); }
/* The footer is single-sourced, so its "<- Main page" button lands on every page
   including the home page itself, where it points nowhere useful. Hide it there,
   keyed off the home-only drop hero (:has re-checks after SPA swaps). */
body:has(.quickdrop-hero) .footer-nav-btn { display: none; }
:root[data-theme="dark"] .footer-nav-btn { border-color: var(--hairline); }
:root[data-theme="dark"] .footer-nav-btn:hover { border-color: var(--hairline-strong); }
:root[data-theme="dark"] .site-footer--about {
  background: #141414;
  color: var(--fg);
}
/* Footer is dark in both themes, but its text/links are coloured var(--bg) -
   which flips to near-black in dark mode. Force them light. */
:root[data-theme="dark"] .site-footer--about a,
:root[data-theme="dark"] .footer-bottom .footer-mark { color: var(--fg); }
:root[data-theme="dark"] .about-defs > div { border-top-color: var(--hairline); }
:root[data-theme="dark"] .footer-bottom { border-top-color: var(--hairline); }
/* On phones, stack the bottom row: back button on top, then the link sections
   in an even two-column grid, then the valjdakosta.com copyright + linkback
   gif dropped to the very bottom (see the reorder below). */
@media (max-width: 700px) {
  /* Flex column on phones, with .footer-lead flattened via display:contents so
     its children (back button, copyright, linkback gif) become reorderable
     items alongside .footer-cols: back button on top, then the link sections,
     then the valjdakosta.com line and gif dropped to the very bottom. */
  /* gap: 0 - the base rule's 32px 64px row/column gap (for the desktop lead|cols
     row) would otherwise still apply between every flattened item here, stacking
     on top of their own margins and blowing out the spacing. */
  .footer-row.footer-bottom { display: flex; flex-direction: column; align-items: flex-start; gap: 0; }
  .footer-lead { display: contents; }
  .footer-lead .footer-nav-btn { order: 1; }
  .footer-cols {
    order: 2;
    margin: 18px 0 30px;
    display: grid;
    /* minmax(0,1fr), not 1fr, so a long label ("Supported file types") wraps
       inside its cell instead of overflowing the column. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 26px 20px;
  }
  /* Centred as a pair (align-self overrides the row's flex-start) so the
     signature reads like a centred sign-off at the very bottom of the footer. */
  .footer-linkback { order: 3; align-self: center; margin-bottom: 6px; }
  .footer-copyright { order: 4; align-self: center; }
}

/* ---------- ABOUT PAGE ---------- */
.about-block { margin-top: 16px; }
.about-block p { color: var(--muted); max-width: 60ch; line-height: 1.6; margin: 0 0 12px; }
.about-block p:last-child { margin-bottom: 0; }
.about-caps {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin: 0;
  padding: 0;
}
.about-caps > div {
  border-top: var(--bd-rule);
  padding: 12px 0;
}
.about-caps dt {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  color: var(--muted);
  margin-bottom: 2px;
}
.about-caps dd {
  margin: 0;
  font-size: var(--t-small);
  color: var(--fg);
  max-width: 60ch;
  line-height: 1.5;
}
.about-caps > div.didyouknow {
  border: 1px solid var(--fg);
  padding: 12px 16px;
  margin-bottom: 8px;
}
.dyk-list { margin: 0; padding-left: 1.1em; }
.dyk-list li { margin: 0 0 4px; }
.dyk-list li:last-child { margin-bottom: 0; }
.about-caps > div.didyouknow code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  background: var(--rule);
  padding: 1px 4px;
  border-radius: 0;
}
.fact-approx { color: var(--muted); font-size: 0.85em; white-space: nowrap; }

/* Ambiguous-extension pages (EXT_VARIANTS): each distinct format the extension
   names gets its OWN body card - title, what-it-is, how-to-tell and its own
   Did-you-know - so /formats/<ext> reads as several complete mini-pages stacked,
   one per filetype, instead of two rows sharing a single card. */
.fmt-variant-card {
  border: var(--bd-rule);
  padding: 16px 22px 20px;
  margin-top: 22px;
}
.fmt-variant-title {
  font-size: var(--t-h3);
  font-weight: 500;
  letter-spacing: -0.015em;
  line-height: 1.1;
  margin: 0;
}
.fmt-variant-card .about-caps { margin-top: 4px; }
.fmt-variant-card .about-caps > div:first-child { border-top: 0; padding-top: 6px; }

.about-formats {
  border-top: var(--bd-hairline);
  margin-top: 24px;
  padding-top: 0;
}
.about-formats summary {
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 14px 0;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 8px;
}
.about-formats summary::-webkit-details-marker { display: none; }
.about-formats summary::after {
  content: '+';
  font-size: 14px;
  color: var(--muted);
  transition: transform var(--dur-base) ease;
}
.about-formats[open] summary::after {
  content: '\2212';
}
.about-formats summary:hover { color: var(--accent); }

/* Patch notes */
.patch-entry {
  border-top: var(--bd-hairline);
  padding: 16px 0;
}
.patch-entry:first-child { border-top: none; }
.patch-version {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg);
  margin: 0;
}
.patch-date {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 2px 0 8px;
}
.patch-list {
  margin: 0;
  padding-left: 18px;
  font-size: var(--t-small);
  color: var(--muted);
  line-height: 1.6;
}
.patch-list li { padding: 1px 0; }

/* A combined multi-version entry can split its notes into labelled sections (e.g.
   browser vs desktop app) with a small mono subhead and a hairline rule between them. */
.patch-subhead {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 14px 0 6px;
}
.patch-rule {
  border: none;
  border-top: var(--bd-hairline);
  margin: 16px 0 0;
}

/* Each patch carries a short codename, shown between the version and the date. */
.patch-name {
  font-size: var(--t-h3);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--fg);
  margin: 2px 0 0;
}
.patch-milestone .patch-name { color: var(--accent); }

/* Inline emphasis inside patch notes */
.patch-list strong { color: var(--fg); font-weight: 600; }
.patch-list em { font-style: normal; color: var(--accent); }
.patch-list u { text-decoration-color: var(--accent); text-underline-offset: 2px; }
.patch-list a {
  color: var(--fg);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 2px;
}
.patch-list a:hover { color: var(--accent); text-decoration-color: var(--accent); }

/* Changelog "tl;dr" toggle - swaps every patch's bullet list for a short summary,
   and reveals the folded older entries so the whole history reads at a glance. */
.patch-head-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}
.patch-tldr-toggle {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 3px 8px;
  background: transparent;
  border: var(--bd-hairline);
  color: var(--fg);
  cursor: pointer;
  transition: background var(--dur-snappy) ease, color var(--dur-snappy) ease, border-color var(--dur-snappy) ease;
}
.patch-tldr-toggle:hover { background: var(--fg); color: var(--bg); }
.patch-tldr-toggle.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--bg);
}
/* The condensed digest replaces the full entry list in tl;dr mode: releases
   grouped in fives (1.0 and 2.0 kept on their own), each with a few short notes. */
.patch-digest { display: none; }
.tldr-mode .patch-entry,
.tldr-mode .about-formats { display: none; }
.tldr-mode .patch-digest { display: block; }
.patch-digest-group {
  border-top: var(--bd-hairline);
  padding: 14px 0;
}
.patch-digest-group:first-child { border-top: none; }
.patch-digest-range {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg);
  margin: 0 0 6px;
}
.patch-digest-group.is-milestone .patch-digest-range { color: var(--accent); }
.patch-digest-list {
  margin: 0;
  padding-left: 18px;
  font-size: var(--t-small);
  color: var(--muted);
  line-height: 1.6;
  max-width: 70ch;
}
.patch-digest-list li { padding: 1px 0; }
/* Emphasised keywords pop out of the muted digest body, same treatment as the
   full changelog's .patch-list strong. */
.patch-digest-list strong { color: var(--fg); font-weight: 600; }

/* Little category tags: New / Fix / Faster */
.patch-tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 9px;
  line-height: 1.4;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0 5px;
  margin-right: 6px;
  border: 1px solid var(--accent);
  color: var(--accent);
  border-radius: var(--radius);
  vertical-align: 1px;
}
.patch-tag.is-fix { border-color: var(--muted); color: var(--muted); }
.patch-tag.is-faster { border-color: var(--fg); color: var(--fg); }

/* The crowned 1.0 release entry */
.patch-milestone {
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  padding: 16px;
  margin: 18px 0;
  background: rgba(var(--accent-rgb), 0.06);
}
.patch-milestone .patch-version {
  color: var(--accent);
  font-size: var(--t-small);
}
.patch-milestone .patch-version::after {
  content: attr(data-badge);
  margin-left: 8px;
  font-size: 9px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  vertical-align: middle;
  animation: patchWoo 1.6s ease-in-out infinite;
}
@keyframes patchWoo {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.45; }
}
@media (prefers-reduced-motion: reduce) {
  .patch-milestone .patch-version::after { animation: none; }
}

/* ---------- FOOTER ---------- */
.site-footer {
  border-top: var(--bd-hairline);
  padding: 32px var(--pad-x);
  max-width: 1440px;
  margin: 0 auto;
}
.footer-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: var(--t-small);
}
.footer-mark {
  font-weight: 500;
}
.footer-meta {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.footer-meta a { color: inherit; }

/* ---------- QUICKDROP (fields lifted above section 01) ---------- */
.quickdrop {
  padding: 20px var(--pad-x) 0;
  max-width: 1440px;
  margin: 0 auto;
}

/* ---------- "Recently analysed" history panel (in the About footer) ---------- */
/* A peer footer section: the same top-hairline + padding as .offline-section, the
   mono/uppercase footer section heading, and dependency-list-style rows. Lives
   only in the dark footer, so it's styled directly for light-on-dark. */
.recent-history {
  border-top: 1px solid rgba(255, 255, 255, 0.15);
  padding-top: 24px;
  margin: 0 0 24px;
}
.recent-history-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}
.recent-history-head h2 {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.5);
  margin: 0;
}
.recent-clear {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  background: none;
  border: none;
  color: var(--white-a40);
  cursor: pointer;
  padding: 0;
  transition: color 0.12s ease;
}
.recent-clear:hover { color: var(--accent); }
.recent-list { list-style: none; margin: 0; padding: 0; }
/* Each entry is a <details>, so strip the global details chrome (top hairline,
   block padding/margin, the +/- marker) for a tight, divider-free list. */
.recent-item { border-top: none; padding: 0; margin: 0; }
.recent-item summary::after,
.recent-item[open] summary::after { content: none; }
.recent-summary {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 2px 0;
  cursor: pointer;
  list-style: none;
  line-height: 1.3;
  font-family: var(--font-sans);
  text-transform: none;
  letter-spacing: 0;
  color: var(--white-a80);
}
.recent-summary::-webkit-details-marker { display: none; }
.recent-summary-main {
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-width: 0;
}
.recent-name {
  font-size: var(--t-small);
  color: var(--white-a80);
  word-break: break-all;
}
/* Type and size shown inline next to the filename on desktop; the expand still
   carries them on narrow screens, where they would crowd the row. */
.recent-meta {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  color: var(--white-a40);
  white-space: nowrap;
}
@media (max-width: 700px) {
  .recent-meta { display: none; }
}
.recent-item[open] .recent-name { color: var(--accent); }
.recent-when {
  flex: none;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  color: var(--white-a40);
}
.recent-detail { margin: 2px 0 8px; border-collapse: collapse; }
.recent-detail th {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--white-a40);
  text-align: left;
  vertical-align: top;
  white-space: nowrap;
  padding: 2px 16px 2px 0;
}
.recent-detail td {
  font-size: var(--t-small);
  color: var(--white-a80);
  padding: 2px 0;
}
.recent-note { margin-top: 8px; font-size: var(--t-tiny); color: var(--white-a40); }
/* Replaces the three dropzones once a file is loaded; reloads to a clean page.
   Swiss treatment: a mono, tracked, uppercase label in a sharp-cornered box that
   inverts on hover, with an arrow that nudges forward. */
.analyse-next,
.export-data {
  display: flex;
  align-items: center;
  gap: 14px;
  width: max-content;
  max-width: calc(100% - 2 * var(--pad-x));
  margin: 22px auto;
  padding: 13px 22px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--fg);
  border-radius: 0;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease;
}
.analyse-next:hover { background: var(--fg); color: var(--bg); }
.analyse-next:active { transform: translateY(1px); }
.analyse-next[hidden] { display: none; }

/* "Export data" chip - same Swiss treatment as .analyse-next. Sits left of
   "Analyse next file?". */
.export-data:hover { background: var(--fg); color: var(--bg); }
.export-data:active { transform: translateY(1px); }
.export-data[hidden] { display: none; }

/* The two action chips share one centred row; their own auto side-margins are
   neutralised here so the gap (not the page) sets the spacing. When both are
   hidden the row collapses to nothing. */
.analyse-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 12px;
}
.analyse-actions .analyse-next,
.analyse-actions .export-data { margin: 22px 0; }
/* Post-analysis "About .EXT files" link, sitting alone above the footer. Same
   Swiss chip as .analyse-next; it's an <a>, so strip the underline. It carries
   its own top padding so it never sits flush against the line above it - the
   photo section's divider band (media files) or the results card (everything
   else, where the section that would have supplied top spacing is hidden). */
.format-guide-cta {
  text-decoration: none;
  margin: calc(var(--pad-y) / 2) auto;
}
/* Square down-arrow under "Analyse next file?" - jumps to the first analysed
   section. Same Swiss idiom as .analyse-next (square, hairline, invert hover). */
.scroll-to-data {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: 0 auto 22px;
  font-size: 1.2em;
  line-height: 1;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--fg);
  border-radius: 0;
  cursor: pointer;
  transition: background 0.18s ease, color 0.18s ease, transform 0.18s ease;
}
.scroll-to-data::after { content: '\2193'; transition: transform 0.18s ease; }
.scroll-to-data:hover { background: var(--fg); color: var(--bg); }
.scroll-to-data:hover::after { transform: translateY(3px); }
.scroll-to-data:active { transform: translateY(1px); }
.scroll-to-data[hidden] { display: none; }

/* Drill-down Back bar: shown when a nested file (from a folder/zip/archive tree)
   is being analysed, it steps back one container level. Sticky so it stays in
   view as the user scrolls through whichever section the nested file landed in. */
.anr-back-bar {
  position: sticky;
  top: 0;
  z-index: 6;
  display: block;
  /* Match the centred content column (1440px max, inset by --pad-x) so the bar
     lines up with the result cards instead of spanning the whole window. */
  width: calc(100% - 2 * var(--pad-x));
  max-width: calc(1440px - 2 * var(--pad-x));
  margin: 0 auto 16px;
  padding: 10px 14px;
  font-family: var(--font-sans);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.01em;
  text-align: left;
  color: var(--fg);
  background: var(--surface);
  border: var(--bd-hairline);
  cursor: pointer;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.anr-back-bar:hover { background: var(--bg); border-color: var(--fg); }
.anr-back-bar:active { transform: translateY(1px); }
.anr-back-arrow { color: var(--accent); margin-right: 8px; font-weight: 600; }
.anr-back-label { font-weight: 600; }
.anr-back-bar[hidden] { display: none; }
.quickdrop-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--gap);
}
@media (max-width: 700px) {
  .quickdrop-grid { grid-template-columns: 1fr 1fr; }
  .quickdrop-grid > :nth-child(2) { order: 2; grid-column: 1 / -1; }
  .quickdrop-grid > :nth-child(3) { order: 1; }
}
.quickdrop-zone { margin: 0 !important; display: flex; flex-direction: column; }
.quickdrop-zone .anr-dropzone-inner {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Top-align so the icon + heading line up across all three zones regardless
     of how much content (hints/buttons) sits below them. */
  justify-content: flex-start;
  padding: clamp(24px, 4vw, 48px) 18px;
}
.quickdrop-zone .anr-dropzone-inner strong {
  font-size: clamp(16px, 1.8vw, 22px);
  margin: 2px 0 4px;
  color: var(--accent);
}
.quickdrop-zone .anr-drop-icon {
  font-size: clamp(22px, 3.5vw, 34px);
  margin-bottom: 6px;
}
.quickdrop-zone .anr-hint {
  margin-top: 10px !important;
  /* Keep the format list to a single row. If it would wrap to a second line
     (e.g. the photo zone lists both photo and video extensions), truncate the
     overflow with an ellipsis instead - a taller hint would push this zone out
     of line with the other two. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  overflow: hidden;
}

/* Touch devices can't drag-drop a file, so the zones read "Pick …" instead of
   "Drop …", and the format lists are dropped for a cleaner tap target. The
   "Info" button still opens the full supported-formats list. */
.tap-word { display: none; }
@media (pointer: coarse) {
  .drop-word { display: none; }
  .tap-word { display: inline; }
  /* Photo/video and sound keep their format lists; only the "any file" zone
     hides its list (the Info button already opens the full catalogue). */
  #videoDrop .anr-hint { display: none; }
}

/* Format help button - matches .anr-audio-modes .anr-btn style */
.fmt-help-btn {
  position: absolute;
  bottom: 0;
  left: 0;
  background: var(--bg);
  border: none;
  border-top: var(--bd-hairline);
  border-right: var(--bd-hairline);
  border-radius: 0;
  font-family: var(--font-sans);
  font-size: var(--t-small);
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--fg);
  cursor: pointer;
  padding: 11px 8px;
  width: auto;
  z-index: 2;
  transition: background var(--dur-snappy) ease, color var(--dur-snappy) ease, border-color var(--dur-base) ease;
}
.fmt-help-btn:hover { background: var(--fg); color: var(--bg); border-top-color: var(--fg); border-right-color: var(--fg); }

/* OCR language picker modal (PDF page OCR; same menu as the image OCR dropdown) */
.anr-ocr-lang {
  position: fixed;
  inset: 0;
  z-index: 120;
  background: rgba(0,0,0,0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.anr-ocr-lang-inner {
  background: var(--bg);
  border: var(--bd-hairline);
  max-width: 420px;
  width: 100%;
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.anr-ocr-lang-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 8px;
}
.anr-ocr-lang-head h3 { margin: 0; font-size: var(--t-h3); }
.anr-ocr-lang-list {
  margin: 0 20px;
  padding: 0;
  list-style: none;
  background: var(--bg);
  border: var(--bd-hairline);
  max-height: 46vh;
  overflow-y: auto;
}
.anr-ocr-lang-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  align-items: center;
  padding: 12px 20px 20px;
}
.anr-ocr-lang-run { background: var(--fg); color: var(--bg); border-color: var(--fg); }
/* Square "?" button pinned to the bottom-left of the actions row (margin-right
   auto pushes Cancel/Run to the right). Toggles the OCR help panel above it. */
.anr-ocr-lang-help {
  margin-right: auto;
  width: 36px;
  min-width: 36px;
  height: 36px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 15px;
  line-height: 1;
  color: var(--muted);
  background: var(--bg);
  border: var(--bd-hairline);
  border-radius: 0;
  cursor: pointer;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.anr-ocr-lang-help:hover { color: var(--fg); border-color: var(--fg); }
.anr-ocr-lang-help.is-active { background: var(--fg); color: var(--bg); border-color: var(--fg); }
/* Occupies the same slot as the language list (it replaces it when toggled). */
.anr-ocr-lang-help-panel {
  margin: 0 20px;
  padding: 14px 16px;
  border: var(--bd-hairline);
  background: var(--bg);
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  line-height: 1.7;
  color: var(--muted);
  max-height: 46vh;
  overflow-y: auto;
}
.anr-ocr-lang-help-panel[hidden] { display: none; }
.anr-ocr-lang-help-panel strong { color: var(--fg); text-transform: uppercase; letter-spacing: 0.08em; }
.anr-ocr-lang-help-panel a { color: var(--accent); }
.anr-ocr-lang-remember {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 12px 20px 0;
  font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}
/* Square Swiss checkbox to match the custom range control - a hairline box that
   fills with the accent and shows a thin tick when ticked (no native rounding). */
.anr-ocr-lang-remember input {
  -webkit-appearance: none;
  appearance: none;
  flex: none;
  width: 15px;
  height: 15px;
  margin: 0;
  border: var(--bd-hairline);
  background: var(--bg);
  border-radius: 0;
  cursor: pointer;
  display: inline-grid;
  place-content: center;
  transition: background var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.anr-ocr-lang-remember input::before {
  content: '';
  width: 3px;
  height: 7px;
  margin-top: -1px;
  border: solid var(--accent-fg, #fff);
  border-width: 0 1.6px 1.6px 0;
  transform: rotate(45deg) scale(0);
  transition: transform var(--dur-fast) ease;
}
.anr-ocr-lang-remember input:hover { border-color: var(--fg); }
.anr-ocr-lang-remember input:checked {
  background: var(--accent);
  border-color: var(--accent);
}
.anr-ocr-lang-remember input:checked::before { transform: rotate(45deg) scale(1); }
.anr-ocr-lang-remember:hover { color: var(--fg); }

/* Format overlay */
.fmt-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(0,0,0,0.6);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
}
.fmt-overlay[hidden] { display: none; }
.fmt-overlay-inner {
  background: var(--bg);
  border: var(--bd-hairline);
  max-width: 760px;
  width: 100%;
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.fmt-overlay-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 0;
}
.fmt-overlay-header h2 {
  margin: 0;
  font-size: var(--t-h3);
}
.fmt-overlay-close {
  background: none;
  border: none;
  font-size: 22px;
  color: var(--muted);
  cursor: pointer;
  padding: 0 4px;
  line-height: 1;
  transition: color var(--dur-snappy) ease;
}
.fmt-overlay-close:hover { color: var(--fg); }
.fmt-overlay-search {
  margin: 12px 20px;
  padding: 8px 10px;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.04em;
  background: var(--surface);
  border: var(--bd-hairline);
  color: var(--fg);
  outline: none;
}
.fmt-overlay-search:focus { border-color: var(--accent); }
/* Inline search on the /formats hub page - filters the on-page catalog list live.
   Reuses .fmt-overlay-search for the look, but spans the column (no side margin). */
.fmt-page-search { margin: 0 0 24px; }
.fmt-page-search-input { display: block; width: 100%; margin: 0; }
.fmt-page-search-status { margin: 8px 0 0; font-family: var(--font-mono); font-size: 12px; letter-spacing: 0.04em; color: var(--muted); }
.fmt-page-search-status[hidden] { display: none; }
.fmt-overlay-body {
  padding: 0 20px 20px;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;          /* let the body scroll instead of growing the modal */
}
.fmt-section-label {
  /* Sans (Geist) spans weight 300-700, so the per-letter cursor-hover effect
     thins dramatically; the mono face only covers 400-600 and barely moved. */
  font-family: var(--font-sans);
  font-size: var(--t-h2);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--fg);
  font-weight: 700;
  margin: 30px 0 12px;
  padding-top: 18px;
  border-top: var(--bd-hairline);
}
.fmt-section-note {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  font-weight: 400;
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--muted);
}
.fmt-section-note::before { content: ' · '; }
/* First section ("Full analysis") needs no divider above it. */
.fmt-section-label:first-child {
  margin-top: 4px;
  padding-top: 0;
  border-top: none;
}
/* Collapsible per-format rows (shared by the overlay and the about page).
   Each row is a native <details class="fmt-item"> revealing its description on
   click. Sharp corners, accent on open, to match the rest of the site. */
.fmt-list { margin: 0; }
.fmt-item {
  border-top: var(--bd-hairline);
}
.fmt-item:first-child { border-top: none; }
.fmt-item.is-hidden { display: none; }
.fmt-item-summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 9px 0;
  position: relative;
}
.fmt-item-summary::-webkit-details-marker { display: none; }
.fmt-item-summary::after {
  content: '+';
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--muted);
  flex: none;
}
.fmt-item[open] > .fmt-item-summary::after { content: '\2212'; }
.fmt-item-summary:hover { color: var(--accent); }
/* Collapsed row: the extension tokens are not links yet - hovering the summary
   tints them all as one block (the "click to open" affordance), and a click
   falls through to the summary and just opens the row. */
.fmt-item:not([open]) > .fmt-item-summary:hover .fmt-item-ext { color: var(--accent); }
.fmt-item:not([open]) .fmt-item-ext { pointer-events: none; }
.fmt-item-head {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: 12px;
}
/* About page, the supported-formats overlay, and the /formats hub: stack the
   extension list under the group name instead of beside it. */
#aboutFormats .fmt-item-head,
#fmtBody .fmt-item-head,
.formats-page .fmt-item-head {
  flex-direction: column;
  gap: 3px;
}
.fmt-item-label {
  flex: none;
  min-width: 100px;
  font-family: var(--font-sans);
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--fg);
}
.fmt-item-exts {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.06em;
  word-spacing: 4px;
  text-transform: uppercase;
  color: var(--muted);
  line-height: 1.7;
  word-break: normal;
  overflow-wrap: break-word;
}
.fmt-item-ext { scroll-margin-top: 84px; }
/* Each extension is now a link to its own /formats page; keep it looking like
   the surrounding mono token until hovered. */
a.fmt-item-ext { color: inherit; text-decoration: none; }
a.fmt-item-ext:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
.fmt-item[open] > .fmt-item-summary { color: var(--accent); }
.fmt-item-desc {
  padding: 0 0 12px;
  font-family: var(--font-sans);
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--muted);
  max-width: 60ch;
  scroll-margin-top: 84px;
}
.fmt-item { scroll-margin-top: 84px; }
/* Per-format guide links inside a full row's description (link to /format/<ext>). */
.fmt-item-guides {
  margin: 0;
  padding: 0 0 12px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  line-height: 1.7;
  color: var(--muted);
  max-width: 60ch;
}

/* Breadcrumb on the per-format landing pages (/format/<ext>). */
.format-crumbs {
  position: relative;
  margin: 0 0 18px;
  padding-right: 56px;          /* room for the depth flag pinned top-right */
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  color: var(--muted);
}
.format-crumbs a { color: inherit; }
.format-crumbs span:not(.format-crumb-badge) { color: var(--fg); }
/* Depth flag (FULL = viewer, ID = identification) pinned to the right of the
   crumb row. Reuses the .fmt-item-badge look. */
.format-crumb-badge {
  position: absolute;
  top: -2px;
  right: 0;
  margin-left: 0;
}

/* ---- Category chips (modal filter) ---- */
.fmt-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 20px 12px;
}
.fmt-chip {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 5px 10px;
  border: var(--bd-hairline);
  background: var(--bg);
  color: var(--muted);
  cursor: pointer;
  border-radius: 0;
  white-space: nowrap;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.fmt-chip:hover { color: var(--fg); border-color: var(--fg); }
.fmt-chip.is-active { background: var(--fg); color: var(--bg); border-color: var(--fg); }

/* ---- Status line: result count + expand/collapse-all ---- */
.fmt-status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 20px 10px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  color: var(--muted);
}
.fmt-toggle-all {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  transition: color var(--dur-fast) ease;
}
.fmt-toggle-all:hover { color: var(--accent); }
.fmt-toggle-all:disabled { opacity: 0.4; cursor: default; }

/* ---- Per-row depth badge (FULL = viewer, ID = identification) ---- */
.fmt-item-badge {
  flex: none;
  margin-left: auto;          /* pushes the badge + the +/- glyph to the right */
  align-self: center;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1.4;
  padding: 2px 6px;
  border: var(--bd-hairline);
  color: var(--muted);
}
.fmt-item-badge.is-full { color: var(--accent); border-color: var(--accent); }
.fmt-item-badge.is-partial { color: var(--partial); border-color: var(--partial); }
.fmt-item[open] > .fmt-item-summary .fmt-item-badge.is-id { border-color: var(--fg); color: var(--fg); }

/* ---- Search match highlight ---- */
.fmt-mark {
  background: var(--accent);
  color: var(--accent-fg);
  border-radius: 0;
  padding: 0 1px;
}

/* ---- Empty state ---- */
.fmt-empty {
  margin: 28px 0 8px;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  color: var(--muted);
}
.fmt-empty[hidden] { display: none; }

/* First *visible* category header drops its divider (JS sets is-first-visible as
   the chip filter hides whole categories). */
.fmt-section-label.is-first-visible {
  margin-top: 4px;
  padding-top: 0;
  border-top: none;
}

/* Mobile: turn the centred modal into a bottom sheet; chips scroll sideways. */
@media (max-width: 700px) {
  .fmt-overlay { align-items: flex-end; padding: 0; }
  .fmt-overlay-inner {
    max-width: 100%;
    max-height: 90vh;
    border-left: none;
    border-right: none;
    border-bottom: none;
  }
  .fmt-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .fmt-chips::-webkit-scrollbar { display: none; }
}

/* Clickable "N supported formats" affordance (dropzone hint + about page). */
.fmt-count-link {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-color: var(--hairline);
  transition: color var(--dur-snappy) ease, text-decoration-color var(--dur-snappy) ease;
}
.fmt-count-link:hover { color: var(--accent); text-decoration-color: var(--accent); }
.about-formats-actions { margin: 8px 0 0; }
.fmt-overlay-count {
  font-family: var(--font-mono);
  font-size: 0.62em;
  letter-spacing: 0.04em;
  color: var(--muted);
  vertical-align: middle;
  margin-left: 6px;
}
.quickdrop-zone .anr-audio-modes {
  padding: 0;
  border-top: var(--bd-hairline);
  gap: 0;
  transition: border-color var(--dur-base) ease;
}
.quickdrop-zone .anr-audio-modes .anr-btn {
  flex: 1 1 0;
  margin: 0;
  border: none;
  border-right: var(--bd-hairline);
  border-radius: 0;
  padding: 11px 8px;
  min-width: 0;
}
.quickdrop-zone .anr-audio-modes .anr-btn:hover { border-right-color: var(--fg); }
.quickdrop-zone .anr-audio-modes .anr-btn:last-child { border-right: none; }

/* ============================================================================
   DROPZONE VARIANT - single "any file" hero zone (opt-in)
   ----------------------------------------------------------------------------
   Both layouts live in index.html at once; the `quickdrop--hero` class on
   <section class="quickdrop"> flips which one shows - WITH it the single hero
   dropzone (.quickdrop-hero), WITHOUT it the original three cards
   (.quickdrop-grid). Removing that one class is the whole switch; both stay
   wired in app.js. The hero reuses the base .anr-dropzone look (bordered box,
   accent-on-hover border, dashed dragover) and adds a divided action strip. */
.quickdrop-hero { display: none; }
.quickdrop--hero .quickdrop-grid { display: none; }
.quickdrop--hero .quickdrop-hero { display: block; }

/* Width + offset are tied to the nav bar: its left edge lands on the centre of
   the Photo link and its right edge on the centre of the Video link. The nav is
   `grid-template-columns: 1fr 1fr 1fr auto` with the search column hidden while
   no file is loaded (the only time this dropzone is visible), so Photo/Sound/
   Video are exact thirds - Photo's centre sits at 1/6 of the width, Video's at
   5/6. The nav and .quickdrop share the same content box (same max-width / auto
   margins / --pad-x), so 1/6 offset + 2/3 width reproduces that with no JS.
   Height is matched to the old separate cards by mirroring the .quickdrop-zone
   inner padding + icon/heading sizing (the base .anr-dropzone is much taller).
   Full-width on phones - see the 700px override below. */
.quickdrop-hero.anr-dropzone { width: 66.6667%; margin: 0 0 0 16.6667%; }
.quickdrop-hero .anr-dropzone-inner { padding: clamp(24px, 4vw, 48px) 18px; }
.quickdrop-hero .anr-drop-icon { font-size: clamp(22px, 3.5vw, 34px); margin-bottom: 6px; }
/* Keep the site's established red dropzone heading (the classic cards do this
   via .quickdrop-zone; the hero isn't one, so set it here) and match their size. */
.quickdrop-hero .anr-dropzone-inner strong { color: var(--accent); font-size: clamp(16px, 1.8vw, 22px); }

/* Divided action strip along the bottom (Record / Live spectrogram / Info),
   mirroring the audio-mode strip: equal flex cells split by hairlines. It sits
   outside the label, so the card's hover content-fade leaves the buttons crisp. */
.quickdrop-hero-actions {
  display: flex;
  border-top: var(--bd-hairline);
  transition: border-color var(--dur-base) ease;
}
.quickdrop-hero-actions .anr-btn {
  flex: 1 1 0;
  margin: 0;
  border: none;
  border-right: var(--bd-hairline);
  border-radius: 0;
  padding: 12px 10px;
  min-width: 0;
}
.quickdrop-hero-actions .anr-btn:last-child { border-right: none; }
.quickdrop-hero-actions .anr-btn:hover { border-right-color: var(--fg); }
/* Match the base card-hover: tint the strip's dividers accent along with the
   card border. */
.quickdrop-hero.anr-dropzone:hover .quickdrop-hero-actions { border-top-color: var(--accent); }
.quickdrop-hero.anr-dropzone:hover .quickdrop-hero-actions .anr-btn { border-right-color: var(--accent); }

/* Phones: keep the single hero dropzone, just render it normally - full width
   and flush left, dropping the desktop nav-aligned 2/3 offset (which would be an
   odd narrow strip on a small screen). */
@media (max-width: 700px) {
  .quickdrop-hero.anr-dropzone { width: 100%; margin-left: 0; }
}

@media (max-width: 420px) {
  .site-nav { padding-left: 14px; padding-right: 14px; }
  .quickdrop { padding-left: 14px; padding-right: 14px; }
  /* The full-bleed divider band's negative margins must match the padding
     override above (14px), not var(--pad-x), or it overshoots the page edge. */
  .quickdrop::after { margin-left: -14px; margin-right: -14px; }
  .quickdrop-grid { gap: 12px; }
  .quickdrop-zone .anr-dropzone-inner { padding: 18px 10px; }
  .quickdrop-zone .anr-dropzone-inner strong { font-size: 14px; }
  .quickdrop-zone .anr-drop-icon { font-size: 20px; margin-bottom: 4px; }
}

/* ----------------------------------------------------------------------------
   COMPARE - the "Compare" CTA on the home page + the /compare page input layout
   ----------------------------------------------------------------------------
   Home page: a compact square sits to the right of the hero dropzone (in the
   right sixth the nav-aligned hero leaves free), reusing the .anr-dropzone box +
   accent-on-hover glow, and links to /compare. The /compare page (compare.html)
   then shows two "any file" dropzones (A/B) + a Back / Compare row; app.js wires
   the zones, and renderers/compare.js merges the two analyses. */

/* Row wrapping the hero dropzone + the Compare CTA - only shown in hero mode
   (mirrors .quickdrop-hero). It spans the hero's nav-aligned box PLUS the right
   sixth, so the square lands where the hero's 2/3 width used to leave a gap. */
.quickdrop-hero-row { display: none; }
.quickdrop--hero .quickdrop-hero-row {
  display: flex;
  align-items: flex-start;   /* top edge collinear with the dropzone's top */
  gap: 14px;
  width: 83.3333%;
  margin: 0 0 0 16.6667%;
}
/* Keep the hero at its original nav-aligned 2/3 width (80% of the 83.33% row =
   66.67% of the page) so it stays visually centred; the compact Compare square
   sits in the right sixth the hero leaves free. */
.quickdrop-hero-row .quickdrop-hero.anr-dropzone { width: auto; flex: 0 0 80%; margin: 0; min-width: 0; }

/* The Compare square: same dropzone chrome (bordered box, accent-on-hover
   border) as a real dropzone, but a small fixed square that sits at the top of
   the row - not stretched to the dropzone's full height. */
.quickdrop-compare-cta {
  flex: 0 0 auto;
  margin: 0;                  /* base .anr-dropzone has margin-top:24px - zero it
                                 so the square's top lines up with the dropzone */
  width: clamp(58px, 7.2vw, 80px);
  aspect-ratio: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  text-align: center;
  text-decoration: none;
  padding: 6px;
}
.quickdrop-compare-cta strong { color: var(--fg); font-size: clamp(11px, 1vw, 13px); }

@media (max-width: 700px) {
  /* Full width, and stack: the hero spans the row, the Compare square drops below
     it aligned to the RIGHT edge (side-by-side would squeeze the square off-screen
     on a narrow phone). */
  .quickdrop--hero .quickdrop-hero-row {
    width: 100%;
    margin-left: 0;
    flex-direction: column;
    align-items: flex-end;
  }
  .quickdrop-hero-row .quickdrop-hero.anr-dropzone { flex: 0 0 auto; width: 100%; }
}

/* /compare page: intro copy above the two dropzones. */
.compare-intro { margin-bottom: var(--gap); }
.compare-intro-head {
  font-family: var(--font-mono);
  font-size: clamp(20px, 3vw, 28px);
  margin: 0 0 8px;
}
.compare-intro-lede { color: var(--muted); max-width: 62ch; margin: 0; }

/* /compare page: the two "any file" dropzones (A/B) + the Back / Compare row. */
.quickdrop-compare-zones {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--gap);
}
.quickdrop-compare-run { justify-content: center; gap: 12px; margin-top: var(--gap); }
.quickdrop-compare-zones .quickdrop-zone .anr-dropzone-inner [data-cmp-name] { color: var(--muted); }
.quickdrop-compare-zones .quickdrop-zone.is-set .anr-dropzone-inner [data-cmp-name] { color: var(--accent); }
@media (max-width: 560px) {
  .quickdrop-compare-zones { grid-template-columns: 1fr; }
}

/* Compare RESULTS: a thin identity strip + a Show-differences toggle, then one
   merged analysis - each field's label once on the left with file A's and file
   B's values beside it (Field | A | B). Content that isn't a readout table
   (previews, players, hex, histograms) drops to a side-by-side A | B split. */
.anr-cmp-strip { margin-bottom: 12px; }
.anr-cmp-strip.anr-cmp-same { border-color: var(--accent); }
/* The "Show differences" toggle sticks to the top of the viewport while the long
   merged comparison scrolls, so it stays reachable. A background band + hairline
   keep it legible over the content passing beneath. No negative side margins - on
   mobile they overran the content box and forced a slight horizontal scroll under
   the pinned bar; the band spans the padded content column instead. */
.anr-cmp-controls {
  position: sticky;
  top: 0;
  z-index: 5;
  display: grid;
  grid-template-columns: 26% 37% 37%;
  align-items: center;
  margin: 0 0 var(--gap);
  padding: 12px 0;
  background: var(--bg);
  border-bottom: var(--bd-hairline);
}
/* Each file's name pinned directly above its analysis column (aligned to the same
   26% | 37% | 37% grid the readout tables use), so it stays in view while the long
   comparison scrolls beneath the sticky bar. */
.anr-cmp-fname {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--accent);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  word-break: break-all;
  padding-right: 12px;
}
.anr-cmp-fname-b { padding-left: 13px; }
@media (max-width: 600px) {
  .anr-cmp-controls { grid-template-columns: 1fr 1fr; row-gap: 8px; }
  .anr-cmp-controls-btn { grid-column: 1 / -1; }
  .anr-cmp-fname-b { padding-left: 14px; }
}

/* Off-screen staging: each file is rendered here (laid out, so canvases/players
   work), then its real cells are moved into the merged view. */
.anr-cmp-staging { position: absolute; left: -99999px; top: 0; width: 960px; }

/* Titled sections (Photo / Sound / Video), mirroring the normal single-file page
   but stacked in the single-column compare flow. The generic File bucket has no
   section chrome, exactly like #unknownResults on the normal page. */
.anr-cmp-section { margin-top: 44px; }
.anr-cmp-section-head {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding-bottom: 12px;
  margin-bottom: 18px;
  border-bottom: 1px solid var(--fg);
}
.anr-cmp-section-head .section-num { font-size: var(--t-h1); line-height: 1; }
.anr-cmp-section-head .section-kicker { margin-top: 0; }

/* The merged Field | A | B readout: fixed columns so every card lines up and
   long values wrap inside their column instead of overflowing the page. The A|B
   boundary sits at 63% (26% field column + 37% A column); the legend and the
   side-by-side splits align to the same 63% line so left/right stay consistent. */
.anr-readout.anr-cmp { table-layout: fixed; width: 100%; }
.anr-readout.anr-cmp th { width: 26%; }
.anr-readout.anr-cmp td { width: 37%; }
/* B column: a little left padding so its values clear the divider line at 63%. */
.anr-readout.anr-cmp td:nth-child(3) { padding-left: 13px; }
/* Recursed sub-groups (a browse-as-archive tree, a video's sub-analysis) merge
   inline in the same flow - no extra chrome, they inherit the 63% divider. */
.anr-cmp-subgroup { margin: 0; }
/* Long unbreakable values (hashes, IDs) must wrap inside their fixed-width column,
   never spill into the neighbouring file's cell. overflow-wrap:anywhere breaks a
   64/128-char hex where needed while leaving normal words intact; the child rule
   catches values the renderer wrapped in a <code>/<span>. */
.anr-readout.anr-cmp th,
.anr-readout.anr-cmp td,
.anr-readout.anr-cmp td * { overflow-wrap: anywhere; word-break: break-word; min-width: 0; }
.anr-readout.anr-cmp td > * { max-width: 100%; }
.anr-cmp-absent { color: var(--muted); }

/* Mobile: the three-column Field | A | B table is too cramped (labels and values
   wrap to a sliver). Reflow each row so the grey field label sits on top, full
   width, and the two file values sit below it split 50/50 across the screen. */
@media (max-width: 600px) {
  .anr-readout.anr-cmp tr {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 14px;
    padding: 9px 0;
    border-bottom: var(--bd-rule);
  }
  .anr-readout.anr-cmp tr:last-child { border-bottom: none; }
  .anr-readout.anr-cmp th,
  .anr-readout.anr-cmp td {
    width: auto;
    padding: 1px 0;
    border-bottom: none;
  }
  .anr-readout.anr-cmp th { grid-column: 1 / -1; margin-bottom: 3px; }
  .anr-readout.anr-cmp td:nth-child(3) { padding-left: 0; }   /* no divider on mobile */
}

/* "Show differences": fade every row where A and B match, so the differing rows
   stand out. Nothing is removed. Matching side-by-side split blocks (forensics,
   edit history, container structure - the content that isn't a merged readout) fade
   too, so the toggle works on the fuller media sections as well. */
.anr-diff-only .anr-readout.anr-cmp tr:not(.is-diff) { opacity: 0.3; }
.anr-diff-only .anr-cmp-split.anr-cmp-split-same { opacity: 0.3; }

/* Side-by-side split for non-table content (image previews, players, hex, etc.).
   Column geometry MATCHES the merged table - an empty 26% gutter, then A and B at
   37% each - so the A|B break sits on the same 63% divider as the tables (no more
   "random" 50% break). min-width:0 + overflow-x:auto keep each side's wide content
   (progress bars, hex, long lines) inside its own column, not bleeding across. */
.anr-cmp-split { display: grid; grid-template-columns: 26% 37% 37%; gap: 0; margin-top: 12px; }
.anr-cmp-col { min-width: 0; overflow-x: auto; }
.anr-cmp-col > * { max-width: 100%; }
.anr-cmp-gutter { min-width: 0; }
.anr-cmp-split .anr-cmp-col:nth-child(2) { padding-right: 12px; }   /* A clears the line */
.anr-cmp-split .anr-cmp-col:nth-child(3) { padding-left: 13px; }    /* B clears the line */
.anr-cmp-col-tag {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-bottom: 6px;
}
/* Local sub-slots a video render creates for its frame/audio drill-downs. */
.anr-cmp-subslot:empty { display: none; }
@media (max-width: 760px) {
  .anr-cmp-split { grid-template-columns: 1fr; }
  .anr-cmp-gutter { display: none; }
  .anr-cmp-split .anr-cmp-col:nth-child(2),
  .anr-cmp-split .anr-cmp-col:nth-child(3) { padding-left: 0; padding-right: 0; }
}

/* ---------- PAGE-WIDE DROP OVERLAY (until first file) ---------- */
.page-drop {
  position: fixed;
  inset: 0;
  background: rgba(var(--bg-rgb), 0.95);
  z-index: 800;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  animation: anr-fade-in var(--dur-fast) ease;
}
.page-drop[hidden] { display: none; }
.page-drop::before {
  content: "";
  position: absolute;
  inset: 16px;
  border: 4px dashed var(--accent);
  pointer-events: none;
}
.page-drop-inner {
  text-align: center;
  padding: 24px;
  max-width: 90vw;
  position: relative;
}
.page-drop-icon {
  font-family: var(--font-mono);
  font-size: clamp(56px, 12vw, 140px);
  font-weight: 300;
  line-height: 1;
  display: block;
  color: var(--accent);
  margin-bottom: 4px;
}
.page-drop-inner strong {
  display: block;
  font-size: clamp(28px, 6vw, 64px);
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 8px 0;
  color: var(--fg);
}
.page-drop-inner p {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: clamp(11px, 1.6vw, 14px);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin: 8px 0 0;
}
@keyframes anr-fade-in { from { opacity: 0; } to { opacity: 1; } }

/* OBJ "add the .mtl" dropzone (model3d.js objMaterialsPrompt): drag the sibling
   material library (and any textures) onto it, or click to pick. */
.anr-mtl-drop {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  padding: 16px 18px;
  border: 1px dashed var(--hairline);
  color: var(--muted);
  font-size: var(--t-small);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}
.anr-mtl-drop:hover,
.anr-mtl-drop:focus-visible {
  border-color: var(--accent);
  color: var(--fg);
  outline: none;
}
.anr-mtl-drop.is-dragover {
  border-color: var(--accent);
  color: var(--fg);
  background: rgb(var(--accent-rgb) / 0.05);
}
.anr-mtl-drop strong { color: var(--fg); font-weight: 600; }
.anr-mtl-drop-ico {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: 300;
  line-height: 1;
  color: var(--accent);
  flex: none;
}

/* ---------- META-COLUMN SLOTS ---------- */
.section-meta-slot { margin-top: 18px; }
.section-meta-slot:empty { display: none; }
.section-meta-slot .anr-card { margin: 0; }
.section-meta-slot .anr-controls {
  padding: 10px 12px;
  gap: 6px;
  flex-direction: column;
  align-items: stretch;
  border-bottom: var(--bd-hairline);
}
.section-meta-slot .anr-control {
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
}
.section-meta-slot .anr-control select,
.section-meta-slot .anr-dropdown {
  width: 100%;
}
.section-meta-slot .anr-control .anr-btn {
  width: 100%;
}
.section-meta-slot .anr-ocr-text { font-size: 12px; max-height: 220px; margin-top: 14px; }

.section-meta-preview {
  border: var(--bd-hairline);
  background: var(--surface);
  padding: 6px;
}
.anr-preview-img-wrap {
  position: relative;
  overflow: hidden;
}
/* Bottom-right affordance signalling the thumbnail expands to a lightbox.
   pointer-events:none so it never intercepts the image's click. */
.anr-preview-img-wrap::after {
  content: "";
  position: absolute;
  bottom: 6px;
  right: 6px;
  width: 22px;
  height: 22px;
  background: rgba(0, 0, 0, 0.55) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 9V4h5M15 4h5v5M4 15v5h5M20 15v5h-5'/%3E%3C/svg%3E") center / 13px 13px no-repeat;
  border: 1px solid var(--border-on-dark);
  opacity: 0.85;
  pointer-events: none;
  transition: opacity var(--dur-fast) ease;
}
.anr-preview-img-wrap:hover::after { opacity: 1; }
.anr-checkerboard {
  background: repeating-conic-gradient(#d0d0d0 0% 25%, #fff 0% 50%) 0 0 / 16px 16px;
}
:root[data-theme="dark"] .anr-checkerboard {
  background: repeating-conic-gradient(#2a2a2a 0% 25%, #141414 0% 50%) 0 0 / 16px 16px;
}
.section-meta-preview img,
.section-meta-preview video {
  display: block;
  width: 100%;
  max-height: clamp(160px, 28vh, 260px);
  object-fit: contain;
  background: var(--media-bg);
  cursor: zoom-in;
}
.section-meta-preview video { cursor: pointer; }
.section-meta-preview-caption {
  margin: 8px 4px 2px;
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--muted);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  word-break: break-word;
  line-height: 1.4;
}
.anr-raw-warning {
  margin: 6px 4px 0;
  font-family: var(--font-mono);
  font-size: var(--t-micro);
  color: var(--accent);
  letter-spacing: 0.04em;
  line-height: 1.4;
  opacity: 0.8;
}
/* "Import XMP settings" button under the thumbnail (RAW files only). */
.anr-raw-xmp-import { margin-top: 8px; text-align: center; }

/* Dotenv secrets warning - the loud red "never share this" banner prepended
   above a .env file's analysis. Accent red with a heavy left rule and a soft
   accent glow so it can't be missed. */
.anr-env-warning {
  margin: 0 0 22px;
  padding: 16px 18px 16px 20px;
  border: var(--bd-hairline);
  border-color: var(--accent);
  border-left-width: 4px;
  background: rgba(var(--accent-rgb), 0.06);
  box-shadow: 0 0 0 1px rgba(var(--accent-rgb), 0.18), 0 6px 24px rgba(var(--accent-rgb), 0.14);
}
.anr-env-warning-title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: var(--t-h3);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--accent);
}
.anr-env-warning-title::before {
  content: "!";
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border: 2px solid var(--accent);
  border-radius: 50%;
  font-size: 15px;
  font-weight: 800;
  line-height: 1;
}
.anr-env-warning-body {
  margin: 10px 0 0;
  font-family: var(--font-sans);
  font-size: var(--t-small);
  line-height: var(--lh-prose);
  color: var(--fg);
}
.anr-env-warning-body code {
  font-family: var(--font-mono);
  font-size: 0.92em;
  padding: 1px 5px;
  background: rgba(var(--accent-rgb), 0.12);
  color: var(--accent);
  word-break: break-all;
}

/* Forensic signature-mismatch card: a normal integrity card flagged with the
   accent left border (calmer than the full .env alert above) so a renamed or
   disguised file reads as a flag without shouting. */
.anr-sig-flag {
  border-left: 3px solid var(--accent);
  padding-left: 16px;
  background: rgba(var(--accent-rgb), 0.04);
}
.anr-sig-flag > h3 { color: var(--accent); }
.anr-sig-flag-note {
  margin: 12px 0 0;
  font-family: var(--font-sans);
  font-size: var(--t-small);
  line-height: var(--lh-prose);
  color: var(--fg);
}

/* Mesh-integrity verdict badge. A compact status tag pinned to the right of the
   card heading, in the site's badge language (mono, hairline, sharp - same as
   .fmt-item-badge). Neutral ink for a clean watertight solid, accent when
   topological faults are found - so the card reads as a native 3D readout rather
   than borrowing the forensic .anr-sig-flag alert. */
.anr-mesh-card > h3 {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 10px;
}
.anr-mesh-verdict {
  flex: none;
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1.4;
  padding: 2px 6px;
  border: var(--bd-hairline);
  color: var(--muted);
}
.anr-mesh-verdict.is-ok { color: var(--fg); border-color: var(--fg); }
.anr-mesh-verdict.is-flag { color: var(--accent); border-color: var(--accent); }

/* ZIP entry-timestamp histogram (archive timing forensics). A row of bars whose
   heights show how many entries fall in each time bucket, earliest -> latest. */
.anr-ziphist {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 60px;
  padding: 5px;
  border: var(--bd-hairline);
  background: var(--surface);
}
.anr-ziphist-bar {
  flex: 1 1 0;
  min-height: 2px;
  background: var(--accent);
  opacity: 0.65;
  border-radius: 0;
  transition: opacity 0.12s ease;
}
.anr-ziphist-bar:hover { opacity: 1; }

/* On smaller viewports, meta column already drops below section-content; reorder so
   the preview thumb stays compact and the OCR/histogram sit clearly below it */
@media (max-width: 900px) {
  .section-meta-slot { margin-top: 16px; }
  .section-meta-preview img,
  .section-meta-preview video { max-height: 280px; }
  #videoPreview { display: none; }
}
@media (max-width: 700px) {
  .section-meta { display: block; }
  .section-meta-slot { max-width: 100%; }
}

/* ---------- LIGHTBOX ---------- */
.lightbox {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
  z-index: 1000;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 12px;
  padding-bottom: 5vh;
  cursor: zoom-out;
  -webkit-tap-highlight-color: transparent;
}
.lightbox:not([hidden]) { display: flex; }
.lightbox img {
  width: 100%;
  height: 100%;
  object-fit: fill;
  display: block;
  cursor: inherit;   /* show the wrapper's zoom-in / grab cursor */
  user-select: none;
  -webkit-user-drag: none;
}
.lightbox-close {
  position: fixed;
  top: 16px; right: 16px;
  z-index: 10;
  /* Solid high-contrast chip so it stays legible over a zoomed-in image of any
     colour, not just the dark backdrop. The ring + shadow separate it from
     bright content behind it. */
  background: var(--on-dark);
  border: 1px solid var(--on-dark);
  color: var(--media-bg);
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.7), 0 3px 10px rgba(0, 0, 0, 0.55);
  padding: 10px 14px;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
  min-height: 40px;
  min-width: 40px;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.lightbox-close:hover { background: var(--media-bg); color: var(--on-dark); border-color: var(--on-dark); }
.lightbox-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  max-height: 100%;
  overflow: hidden;
}
.lightbox-img-wrap {
  position: relative;
}
.lightbox-peaking {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 3;
}
.lightbox-peaking[hidden] { display: none; }
.lightbox-focus-map {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  image-rendering: pixelated;
  pointer-events: none;
}
.lightbox-focus-map[hidden] { display: none; }
.lightbox-focus-dot {
  position: absolute;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--on-dark);
  box-shadow: 0 0 0 1px rgba(0,0,0,0.4), 0 2px 6px rgba(0,0,0,0.5);
  transform: translate(-50%, -50%);
  pointer-events: none;
  z-index: 5;
}
.lightbox-focus-dot[hidden] { display: none; }
.lightbox-toolbar {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  margin-top: 8px;
}
.lightbox-toolbar:empty { display: none; }
/* Gallery action bar (Analyse / Download …) - reuses .lightbox-tool-btn chips. */
.lightbox-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
  margin-top: 8px;
}
.lightbox-actions:empty { display: none; }
/* Prev/next arrows for stepping through a gallery, on the left/right edges. Same
   high-contrast chip treatment as .lightbox-close so they stay legible over any
   image. Sharp-cornered, per the site's no-rounded-corners rule. */
.lightbox-nav {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  z-index: 10;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 64px;
  background: var(--on-dark);
  border: 1px solid var(--on-dark);
  color: var(--media-bg);
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.7), 0 3px 10px rgba(0, 0, 0, 0.55);
  font-family: var(--font-mono);
  font-size: 30px;
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.lightbox-nav[hidden] { display: none; }
.lightbox-nav:hover { background: var(--media-bg); color: var(--on-dark); border-color: var(--on-dark); }
.lightbox-prev { left: 16px; }
.lightbox-next { right: 16px; }
.lightbox-tool-btn {
  background: transparent;
  border: 1px solid var(--border-on-dark-strong);
  color: var(--white-a80);
  padding: 8px 14px;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.lightbox-tool-btn:hover {
  background: var(--border-on-dark);
  color: var(--on-dark);
}
.lightbox-tool-btn.is-active {
  background: var(--on-dark);
  color: var(--media-bg);
  border-color: var(--on-dark);
}
.lightbox-meta {
  position: fixed;
  bottom: 16px; left: 50%;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, 0.78);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  max-width: 90%;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 6px 12px;
  background: rgba(0,0,0,0.4);
}

/* ---------- UNKNOWN FILE RESULTS ---------- */
.anr-unknown-results:not([hidden]) {
  padding: 36px var(--pad-x) 40px;
  border-top: var(--bd-hairline);     /* heavy rule above the readout, matching the nav */
  border-bottom: var(--bd-hairline);
  max-width: 1440px;
  margin: 0 auto;
  background: var(--bg);
  scroll-margin-top: var(--nav-offset);
}
.anr-unknown-dump {
  font-family: var(--font-mono);
  font-size: 12px;
  background: var(--surface);
  border: var(--bd-hairline);
  padding: 14px;
  white-space: pre;
  overflow-x: auto;
  margin: 14px 0;
  line-height: 1.55;
}

/* Network-indicator (OSINT) list: each found URL/IP/domain on its own row with
   its monospace value and small lookup links trailing it. */
.anr-osint-list { margin: 8px 0 4px; }
.anr-osint-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  padding: 4px 0;
  border-bottom: 1px solid var(--hairline);
}
.anr-osint-val {
  font-family: var(--font-mono);
  font-size: 12px;
  word-break: break-all;
  flex: 1 1 60%;
  min-width: 0;
}
.anr-osint-lk {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 1px 6px;
  border: var(--bd-hairline);
  color: var(--accent);
  text-decoration: none;
  white-space: nowrap;
}
.anr-osint-lk:hover { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }

/* Lottie player: a checkerboard stage (animations are usually transparent) and a
   timeline scrubber that grows to fill the control row. */
.anr-lottie-stage {
  max-width: 480px;
  margin: 8px auto 0;
  min-height: 160px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: repeating-conic-gradient(#d8d8d8 0% 25%, #fff 0% 50%) 50% / 20px 20px;
  border: var(--bd-hairline);
}
.anr-lottie-stage svg { display: block; max-width: 100%; height: auto; }
.anr-lottie-range { flex: 1 1 auto; min-width: 80px; }

/* ASS/SSA styled subtitle preview: a dark "stage" so the per-style colours (often
   white/yellow on transparent) read the way they would over video. */
.anr-ass-stage {
  background: #14161a;
  border: var(--bd-hairline);
  padding: 8px 10px;
  border-radius: 0;
}
.anr-ass-stage .anr-lrc-time { color: #8a8f98; }
.anr-ass-line { white-space: pre-wrap; }

/* SQLite read-only query box. */
.anr-sql-input {
  width: 100%;
  box-sizing: border-box;
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 8px;
  background: var(--surface);
  border: var(--bd-hairline);
  color: var(--fg);
  resize: vertical;
}
.anr-sql-input:focus { outline: none; border-color: var(--accent); }
.anr-sql-result { margin-top: 6px; }

/* Byte-entropy heatmap: a 1px-tall canvas stretched full-width. image-rendering
   keeps the per-chunk colour columns crisp instead of blurred when scaled up. */
.anr-entropy-map {
  display: block;
  width: 100%;
  height: 44px;
  margin: 10px 0 0;
  border: var(--bd-hairline);
  image-rendering: pixelated;
  cursor: crosshair;
}

/* ---------- NAV FLASH (E6) ---------- */
@keyframes anr-nav-flash {
  0%   { background: var(--accent); color: var(--accent-fg); }
  100% { background: transparent;   color: var(--nav-fg); }
}
.nav-link.is-flash {
  animation: anr-nav-flash 0.6s ease-out;
}
.nav-link.is-active.is-flash {
  animation: none;
}

/* ---------- DARK MODE (E1) ---------- */
:root[data-theme="dark"] {
  --bg:        #0a0a0a;
  --fg:        #e8e8e8;
  --muted:     #888;
  --hairline:  #333;
  --rule:      #262626;
  --surface:   #141414;
  --accent:    #ff3347;
  --accent-fg: #ffffff;
  --partial:   #f2ad3a;   /* yellowish-orange: the "Partial" depth tag (dark theme) */
  --accent-rgb: 255, 51, 71;
  --bg-rgb:     10, 10, 10;
  --shadow-color: rgba(0, 0, 0, 0.6);
}
:root[data-theme="dark"] .anr-swatch span {
  background: var(--bg);
}

.dark-toggle {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 8px;
  background: var(--surface);
  border: var(--bd-hairline);
  color: var(--fg);
  cursor: pointer;
  white-space: nowrap;
}
.dark-toggle:hover {
  background: var(--fg);
  color: var(--bg);
}

/* ---------- CLEAR VIEW (low-vision accessibility mode, E2) ----------
   Opt-in via the "Clear view" chip: sets data-a11y="on" on <html> (persisted as
   the permanent anr-a11y key, applied before paint by THEME_SCRIPT). Three legs:
   1. Legibility - re-points existing design tokens (larger type, higher-contrast
      greys and rules) so every component that already draws from var(--t-*) /
      var(--muted) / var(--rule) scales up automatically, with no per-component
      rules. Fonts stay Geist, by design.
   2. Stillness - a blanket animation/transition kill (the reduced-motion pattern:
      near-zero durations rather than `animation: none`, so `forwards` animations
      still land on their end state and transitionend still fires) plus no
      view-transition cross-fade. The JS-driven motion (header letter sweep/hover)
      is gated in effects.js off the same attribute.
   3. Declutter - the drifting grid backdrop is hidden outright, and popups.js
      skips the promotional share nudge.
   Theme-independent bits sit in this first block; the per-theme contrast nudges
   follow. */
:root[data-a11y="on"] {
  --t-h3:    19px;
  --t-body:  19px;
  --t-small: 16px;
  --t-tiny:  14px;
  --t-micro: 13px;
  --t-h2:    clamp(24px, 2.6vw, 32px);
  --t-h1:    clamp(34px, 4.6vw, 60px);
  --lh-body:  1.6;
  --lh-prose: 1.75;
  /* Darker secondary text + visible rules against the light page (--muted carries
     most secondary copy; darkening it lifts it well past AA on white). */
  --muted: #3f3f3f;
  --rule:  #a8a8a8;
}
:root[data-theme="dark"][data-a11y="on"] {
  --muted:    #c4c4c4;
  --rule:     #565656;
  --hairline: #5a5a5a;
}
/* Thicker, higher-offset focus ring - easier to locate for low vision. */
:root[data-a11y="on"] a:focus-visible,
:root[data-a11y="on"] button:focus-visible,
:root[data-a11y="on"] summary:focus-visible,
:root[data-a11y="on"] select:focus-visible,
:root[data-a11y="on"] input:not([type="range"]):focus-visible,
:root[data-a11y="on"] [tabindex]:focus-visible {
  outline-width: 3px;
  outline-offset: 3px;
}
/* Always underline in-body links (not colour alone) in prose contexts. */
:root[data-a11y="on"] .site-sub a,
:root[data-a11y="on"] .site-main p a,
:root[data-a11y="on"] .anr-info a {
  text-decoration: underline;
  text-underline-offset: 0.15em;
}
/* Stillness: freeze every CSS animation and transition. Near-zero durations, not
   `animation: none`, so `forwards` animations (e.g. the Altium cross-probe ping)
   still snap to their end state and code awaiting transitionend still runs. */
:root[data-a11y="on"] *,
:root[data-a11y="on"] *::before,
:root[data-a11y="on"] *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}
/* No cross-fade on SPA page swaps - navigation becomes an instant cut. */
:root[data-a11y="on"]::view-transition-old(root),
:root[data-a11y="on"]::view-transition-new(root) {
  animation: none !important;
}
/* Declutter: drop the drifting grid backdrop entirely (it is both motion and
   visual noise behind the text). */
:root[data-a11y="on"] body::before { display: none; }
/* Separation: pull the page a clear step off the card colour. The framed panels
   (.section-meta / .section-content) already paint var(--bg) with a hairline;
   tinting the page behind them lifts them off the backdrop so they read as
   distinct cards (low vision benefits from the extra boundary). Deeper than the
   default var(--surface) wash, by request: light - page #e4e4e4, card #fff;
   dark - page #232323, card #0a0a0a. (:root IS <html>, so the tint goes on :root
   directly - a "html" descendant selector would match nothing.) */
:root[data-a11y="on"] { background: #e4e4e4; }
:root[data-theme="dark"][data-a11y="on"] { background: #232323; }
/* The chip shows its on-state with an accent fill, like a pressed control. */
#a11yToggle[aria-pressed="true"] {
  background: var(--accent);
  color: var(--accent-fg);
  border-color: var(--accent);
}
#a11yToggle[aria-pressed="true"]:hover {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}

/* ---------- CUSTOM DROPDOWN ---------- */
.anr-dropdown {
  position: relative;
  font-family: var(--font-mono);
  font-size: var(--t-small);
  user-select: none;
}
.anr-dropdown-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 5px 8px;
  cursor: pointer;
  white-space: nowrap;
}
.anr-dropdown-item:hover { background: var(--hairline); }
.anr-dropdown-item.is-selected { background: var(--fg); color: var(--bg); }
.anr-dropdown-item-size {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.85em;
}
.anr-dropdown-item.is-selected .anr-dropdown-item-size {
  color: var(--muted);
}

/* ---------- MOBILE TOUCH-TARGET POLISH ---------- */
@media (max-width: 700px) {
  .anr-btn { min-height: 40px; padding: 10px 14px; }
  .anr-toggle button { min-height: 36px; padding: 8px 12px; }
  .anr-control select { min-height: 36px; padding: 6px 8px; }
  /* Stay on the desktop grid (1fr 1fr 1fr auto) on phones too - just full-bleed
     it. An earlier flex layout here distributed the three links by content and
     then added each one's divider border (Photo 2px, Sound 1px, Video 0px) on
     top, so the buttons came out 1px stair-stepped instead of equal. Grid's 1fr
     is border-box, so it absorbs the dividers and the three stay exactly even -
     verified equal and overflow-free down to 320px. */
  .site-nav {
    padding-left: 14px; padding-right: 14px;
    /* Keep the bar pinned while scrolling on phones. Re-assert sticky and drop
       the auto side-margins (which can break position:sticky on iOS Safari);
       full-bleed on mobile so no centering margins are involved anyway. */
    position: -webkit-sticky;
    position: sticky;
    top: 0;
    z-index: 30;
    margin-left: 0;
    margin-right: 0;
    max-width: none;
  }
  .nav-link { padding: 14px 4px; gap: 6px; min-width: 0; }
  /* "I'm feeling lucky" is three words - in a cramped 1/3 grid cell it wraps.
     On the formats hub, let the middle column hug the label (auto) so the cell
     is exactly as wide as it needs to be, kept centred by the 1fr sides. */
  body:has(.about-page) .formats-hub-nav { grid-template-columns: 1fr auto 1fr; }
  .formats-hub-nav .nav-link-lucky { white-space: nowrap; }
  .nav-search {}
  .nav-search.is-open .nav-search-btn { display: block; }
  .nav-search.is-open .nav-search-input,
  .nav-search.is-open .nav-search-arrow { display: none; }
  /* About page: drop the search control on mobile so the section links get the
     full nav width. (The nav is a sibling of main.about-page, so :has() on the
     body is used to detect the page.) */
  body:has(.about-page) .nav-search { display: none; }
  /* Hiding search removes the 48px-tall button that otherwise sets the bar's
     height, so the links alone would make the nav noticeably shorter. Pad the
     links to keep the about-page nav the same height as the main one. */
  body:has(.about-page) .nav-link { padding-top: 16px; padding-bottom: 16px; }
}

/* ---------- Confirmation modal (mobile upload guard) ---------- */
/* Built in app.js by anrConfirm(). Swiss styling: flat bg, single hard border,
   monospace kicker/buttons, accent on the primary action. */
.anr-modal {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  transition: opacity 0.18s ease;
}
.anr-modal.is-open { opacity: 1; }
.anr-modal-card {
  width: 100%;
  max-width: 360px;
  background: var(--bg);
  border: var(--bd-hairline);
  padding: 24px;
  transform: translateY(8px);
  transition: transform 0.18s ease;
}
.anr-modal.is-open .anr-modal-card { transform: none; }
/* Scrollable modal body + chip grid for the treemap file-type filter popup. */
.anr-modal-scroll { max-height: 52vh; overflow-y: auto; margin: 2px 0; }
.anr-extfilter-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.anr-modal-kicker {
  margin: 0 0 12px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted);
}
.anr-modal-title {
  margin: 0 0 22px;
  font-size: var(--t-body);
  line-height: 1.35;
  color: var(--fg);
}
.anr-modal-actions { display: flex; gap: 10px; }
.anr-modal-btn {
  flex: 1;
  padding: 12px 14px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  cursor: pointer;
  border: var(--bd-hairline);
  background: var(--bg);
  color: var(--fg);
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.anr-modal-cancel:hover { background: var(--surface); }
.anr-modal-ok { background: var(--fg); color: var(--bg); }
.anr-modal-ok:hover { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }

/* ---------- COMPLETE-TIER FEATURE POPUP ----------
   The Complete download opens this picker instead of downloading directly: two
   optional packs (Languages, AI vocal separation), each a real .offline-btn with
   its own progress bar + Cached badge, added on top of the Everything download.
   The card is dark to match the footer's always-dark offline band, so the reused
   .offline-btn colours (white-on-dark) read correctly in both light and dark. */
.offline-feat-card {
  max-width: 460px;
  background: var(--media-bg);
  border: 1px solid var(--hairline-on-dark);
  color: var(--on-dark);
}
/* This popup is built once and reused (toggled via .is-open), unlike the modals
   that are created fresh and removed on close - so when closed it lingers in the
   DOM. Make the closed overlay inert (it's a fixed, full-screen box) or its
   opacity:0 self silently swallows every click across the whole page. visibility
   is delayed so the 0.18s opacity fade-out still plays. */
.offline-feat-modal:not(.is-open) {
  pointer-events: none;
  visibility: hidden;
  transition: opacity 0.18s ease, visibility 0s linear 0.18s;
}
.offline-feat-card .anr-modal-kicker { color: var(--muted-on-dark); }
.offline-feat-card .anr-modal-title { color: var(--on-dark); margin-bottom: 14px; }
.offline-feat-note {
  margin: 0 0 20px;
  font-family: var(--font-sans);
  font-size: var(--t-small);
  line-height: 1.45;
  color: var(--white-a80);
}
.offline-feat-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 22px; }
.offline-feat-list .offline-btn { width: 100%; }
/* Keep each pack's description visible in the popup even on mobile - the footer
   grid hides .offline-desc there for space, but a modal has room (higher
   specificity beats the media-query rule, no !important needed). */
.offline-feat-list .offline-desc { display: block; }
.offline-feat-card .anr-modal-cancel {
  background: transparent;
  color: var(--white-a80);
  border-color: var(--border-on-dark);
}
.offline-feat-card .anr-modal-cancel:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: var(--border-on-dark-strong);
  color: var(--on-dark);
}

/* Export chooser (see export-data.js showChooser): two stacked, left-aligned
   options - a strong title over a small grey description - above the Cancel
   action. Same hairline-box-inverts-on-hover idiom as the rest of the modal. */
/* The options sit two-by-two on desktop, so the card is widened past the modal's
   default 360px to give each column the same roomy width (and button aspect ratio)
   the single-column stack had. The card is NOT height-capped and its body does not
   scroll - it sizes to its content - because the 2x2 grid keeps it short enough to
   fit. On a narrow viewport the card returns to the default width and the options
   stack into four. */
.anr-export-card { max-width: 560px; }
.anr-export-choices {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin: 0 0 18px;
}
@media (max-width: 600px) {
  .anr-export-card { max-width: 360px; }
  .anr-export-choices { grid-template-columns: 1fr; }
}
.anr-export-opt {
  display: flex;
  flex-direction: column;
  gap: 5px;
  width: 100%;
  padding: 14px 16px;
  text-align: left;
  border: var(--bd-hairline);
  background: var(--bg);
  color: var(--fg);
  cursor: pointer;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.anr-export-opt strong {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.anr-export-opt span {
  font-size: var(--t-small);
  line-height: 1.45;
  color: var(--muted);
}
.anr-export-opt:hover { border-color: var(--accent); }
.anr-export-opt:hover span { color: inherit; }

/* Featured option (the "Complete report"): styled like a major-version entry in
   the changelog (.patch-milestone) - a solid accent border over a faint accent
   wash, with the title in the accent colour - marking it as the richest export. */
.anr-export-opt--featured {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.06);
}
.anr-export-opt--featured strong { color: var(--accent); }
.anr-export-opt--featured:hover {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.1);
}
/* Spectrogram "Save PNG" size prompt: two labelled selects above the actions. */
.anr-spec-save-fields { display: flex; gap: 12px; margin: 0 0 22px; }
.anr-spec-save-field {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}
.anr-spec-save-select {
  width: 100%;
  padding: 9px 10px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  border: var(--bd-hairline);
  background: var(--surface);
  color: var(--fg);
}
.anr-spec-save-select:focus { outline: none; border-color: var(--accent); }
/* AVI frame player: runtime dropped-frame count, called out in the accent colour. */
.anr-frame-drops { color: var(--accent); }
/* Contact modal: the Turnstile widget sits centred between the title and the
   status line / Close action (see openContactModal). */
.anr-contact-card .anr-suggest-turnstile {
  display: flex;
  justify-content: center;
  min-height: 65px;
  margin-bottom: 14px;
}
.anr-contact-card .anr-suggest-gate-status {
  margin: 0 0 18px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  color: var(--muted);
}

/* Share modal (see openShareModal): a friendly lead line, an optional native
   Share button, then a row of quick share targets (Copy link sits in that row as
   a chip) above the Close action. The title carries the heading, so pull the lead
   up under it. */
.anr-share-lead {
  margin: -14px 0 18px;
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--muted);
}
.anr-share-url {
  display: block;
  width: 100%;
  margin-bottom: 8px;
  padding: 11px 12px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.02em;
  border: var(--bd-hairline);
  background: var(--surface);
  color: var(--fg);
}
.anr-share-url:focus { outline: none; border-color: var(--accent); }
/* Link field + its own small Copy button on one row. */
.anr-share-urlrow { display: flex; gap: 8px; margin-bottom: 8px; }
.anr-share-urlrow .anr-share-url { flex: 1 1 auto; min-width: 0; margin-bottom: 0; }
.anr-share-urlcopy { flex: 0 0 auto; margin: 0; white-space: nowrap; }
.anr-share-urlcopy:hover { background: var(--fg); color: var(--bg); }
.anr-share-urlcopy.is-done { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.anr-share-nativewrap { margin-bottom: 8px; }
.anr-share-native { width: 100%; }
/* Copy + Email sit side by side as the two primary, always-visible targets. */
.anr-share-primary { display: flex; gap: 8px; margin-bottom: 8px; }
.anr-share-copy { flex: 1 1 0; margin: 0; }
.anr-share-copy:hover { background: var(--fg); color: var(--bg); }
.anr-share-copy.is-done { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.anr-share-email { flex: 1 1 0; display: flex; align-items: center; justify-content: center; }
/* "More platforms" toggle: borderless centred label with a hairline running out
   to each side (a labelled divider), not a boxed chip. Keeps the mono/uppercase
   type it inherits from .anr-modal-btn. */
.anr-share-more-toggle {
  width: 100%;
  margin-bottom: 8px;
  display: flex;
  align-items: center;
  gap: 12px;
  border: none;
  background: none;
  padding: 4px 0;
  color: var(--muted);
}
.anr-share-more-toggle::before,
.anr-share-more-toggle::after {
  content: '';
  flex: 1 1 0;
  height: 1px;
  background: var(--hairline);
}
.anr-share-more-toggle:hover { background: none; color: var(--fg); }
.anr-share-targets {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 18px;
}
/* Secondary targets stay collapsed until the toggle opens them. */
.anr-share-more { display: none; margin-bottom: 18px; }
.anr-share-more.is-open { display: flex; }
.anr-share-target {
  flex: 1 1 auto;
  text-align: center;
  padding: 9px 10px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-decoration: none;
  border: var(--bd-hairline);
  color: var(--fg);
  cursor: pointer;
  background: var(--bg);
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.anr-share-target:hover { background: var(--fg); color: var(--bg); }
/* Cancel just gets a little breathing room above it - the "More platforms"
   labelled divider already supplies the visual break, so no extra hairline. */
.anr-share-card .anr-modal-actions {
  padding-top: 10px;
}

/* ---------- Scene change detection (video) ---------- */
.anr-scene-timeline {
  position: relative;
  height: 24px;
  background: var(--surface);
  border: var(--bd-hairline);
  margin-bottom: 14px;
}
.anr-scene-marker {
  position: absolute;
  top: 2px;
  bottom: 2px;
  width: 2px;
  background: var(--accent);
  cursor: pointer;
}
.anr-scene-details { margin-top: 4px; }
.anr-scene-details summary {
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  padding: 6px 0;
}
.anr-scene-details summary:hover { color: var(--accent); }
.anr-scene-details[open] summary { color: var(--fg); margin-bottom: 10px; }
.anr-scene-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.anr-scene-thumb { cursor: pointer; text-align: center; }
.anr-scene-thumb img {
  width: 160px;
  height: 90px;
  object-fit: cover;
  display: block;
  border: var(--bd-hairline);
}
.anr-scene-thumb:hover img { border-color: var(--accent); }
.anr-scene-meta {
  display: block;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  margin-top: 3px;
}

/* ---------- UTILITY / REFACTOR CLASSES ---------- */
.is-hidden { display: none !important; }

/* Scrollable <pre> blocks (formerly inline max-height/overflow) */
.anr-pre-scroll    { max-height: 500px; overflow: auto; }
.anr-pre-scroll-sm { max-height: 300px; overflow: auto; }

/* Collapsible file tree (folders + archives) */
.anr-tree {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  line-height: 1.5;
  max-height: 560px;
  overflow: auto;
}
/* Reset the global `details` styling (border-top, padding, margin, +/- marker)
   so it doesn't bleed into the tree's folder rows. */
.anr-tree-dir {
  border-top: none;
  padding: 0;
  margin: 0;
}
.anr-tree-dir > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  padding: 5px 6px;
  user-select: none;
  font-size: var(--t-small);
  letter-spacing: 0;
  text-transform: none;
  transition: background 0.1s ease, color 0.1s ease;
}
.anr-tree-dir > summary::-webkit-details-marker { display: none; }
.anr-tree-dir > summary::after { content: none; }
.anr-tree-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 12px;
  flex-shrink: 0;
  color: var(--muted);
  font-size: 10px;
  transition: transform var(--dur-fast) ease, color var(--dur-fast) ease;
}
.anr-tree-dir[open] > summary > .anr-tree-icon { transform: rotate(90deg); }
.anr-tree-dir > summary:hover { background: var(--surface); color: var(--accent); }
.anr-tree-dir > summary:hover .anr-tree-icon { color: var(--accent); }
.anr-tree-dir > summary .anr-tree-name { font-weight: 500; }
.anr-tree-children {
  margin-left: 11px;
  padding-left: 12px;
  border-left: var(--bd-rule);
}
.anr-tree-file {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 6px;
  transition: background 0.1s ease, color 0.1s ease;
}
.anr-tree-lead {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 12px;
  flex-shrink: 0;
}
.anr-tree-dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius);
  flex-shrink: 0;
}
.anr-tree-file:hover { background: var(--surface); }
.anr-tree-name { word-break: break-all; flex: 1 1 auto; min-width: 0; }
.anr-tree-meta {
  color: var(--muted);
  white-space: nowrap;
  flex-shrink: 0;
  margin-left: auto;
  font-size: var(--t-tiny);
  font-variant-numeric: tabular-nums;
}
.anr-tree-file.is-clickable { cursor: pointer; }
.anr-tree-file.is-clickable:hover { background: var(--fg); color: var(--bg); }
.anr-tree-file.is-clickable:hover .anr-tree-meta { color: var(--bg); }
.anr-tree-copy {
  flex-shrink: 0;
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  font-size: 12px;
  line-height: 1;
  padding: 2px 4px;
  opacity: 0;
  transition: opacity 0.1s ease, color 0.1s ease;
}
.anr-tree-file:hover .anr-tree-copy { opacity: 1; }
.anr-tree-copy:hover { color: var(--accent); }
.anr-tree-file.is-clickable:hover .anr-tree-copy { color: var(--bg); }
.anr-tree-file.is-clickable:hover .anr-tree-copy:hover { color: var(--accent); }
/* Hidden files (dotfiles): overlaid with diagonal grey lines at 30% opacity so
   they read as hidden at a glance. Fixed grey (not a theme var) so the hatch stays
   legible over both the normal row and the inverted clickable-hover state; the
   overlay ignores pointer events so the row stays clickable and the name legible. */
.anr-tree-file.is-hidden { position: relative; }
.anr-tree-file.is-hidden::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: repeating-linear-gradient(
    45deg,
    rgba(128, 128, 128, 0.3) 0,
    rgba(128, 128, 128, 0.3) 1px,
    transparent 1px,
    transparent 7px
  );
}

/* Treemap + folder/archive visuals */
.anr-treemap-wrap { position: relative; }
.anr-treemap-wrap canvas { display: block; width: 100%; }
.anr-treemap-tooltip {
  position: absolute; pointer-events: none; z-index: 10;
  font-family: var(--font-mono); font-size: var(--t-tiny);
  background: var(--bg); border: var(--bd-hairline);
  padding: 6px 10px; max-width: 70%;
  opacity: 0; transition: opacity 0.1s;
}
.anr-treemap-tooltip.is-visible { opacity: 1; }
.anr-treemap-tooltip .anr-tt-name { word-break: break-all; color: var(--fg); }
.anr-treemap-tooltip .anr-tt-meta { color: var(--muted); margin-top: 2px; text-transform: uppercase; letter-spacing: 0.05em; }
.anr-treemap-breadcrumb {
  font-family: var(--font-mono); font-size: var(--t-tiny);
  letter-spacing: 0.05em;
  padding: 0 0 8px; margin-bottom: 8px;
  border-bottom: var(--bd-rule);
  display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
}
.anr-treemap-breadcrumb button {
  background: none; border: none; color: var(--muted);
  cursor: pointer; font-family: inherit; font-size: inherit;
  letter-spacing: inherit;
  padding: 0; text-transform: uppercase;
  transition: color var(--dur-fast) ease;
}
.anr-treemap-breadcrumb button:hover { color: var(--accent); }
.anr-crumb-sep { color: var(--muted); opacity: 0.5; }
.anr-crumb-current { color: var(--fg); text-transform: uppercase; }
.anr-treemap-status {
  font-family: var(--font-mono); font-size: var(--t-tiny);
  color: var(--muted); letter-spacing: 0.03em;
  padding: 8px 0 0; margin-top: 8px;
  border-top: var(--bd-rule);
  word-break: break-all;
}
.anr-treemap-content { margin-top: 14px; }
/* Cursor confirm popup (treemap file click) */
.anr-treemap-menu {
  position: fixed; z-index: 1000;
  background: var(--bg); border: var(--bd-hairline);
  font-family: var(--font-mono); font-size: var(--t-tiny);
  padding: 10px 11px; min-width: 150px; max-width: 260px;
}
/* Name on the left, a compact Copy button pinned top-right on the same line.
   min-width:0 + the gap guarantee the long name wraps under itself and never
   overlaps the button. */
.anr-tm-header { display: flex; align-items: flex-start; gap: 10px; }
.anr-tm-name { color: var(--fg); font-weight: 500; word-break: break-all; flex: 1 1 auto; min-width: 0; }
.anr-tm-copy { flex: 0 0 auto; padding: 2px 6px; font-size: 9px; }
.anr-tm-meta { color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-top: 2px; }
.anr-tm-q { color: var(--fg); margin: 9px 0 8px; }
.anr-tm-actions { display: flex; gap: 8px; justify-content: flex-end; }
.anr-tm-btn {
  font-family: var(--font-mono); font-size: var(--t-tiny);
  text-transform: uppercase; letter-spacing: 0.05em;
  padding: 5px 11px; border: var(--bd-hairline);
  background: var(--bg); color: var(--fg); cursor: pointer;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.anr-tm-btn:hover { background: var(--fg); color: var(--bg); }
.anr-tm-btn-ok { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.anr-tm-btn-ok:hover { background: var(--fg); color: var(--bg); border-color: var(--fg); }

/* Searchable list of a treemap "N files" aggregate block. A fixed-position
   panel with a sticky header + filter box over a scrollable list of size-sorted
   rows; each row analyses its file on click. */
.anr-treemap-list {
  position: fixed; z-index: 1000;
  display: flex; flex-direction: column;
  width: 320px; max-width: calc(100vw - 16px); max-height: 60vh;
  background: var(--bg); border: var(--bd-hairline);
  font-family: var(--font-mono); font-size: var(--t-tiny);
}
.anr-tml-header {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px; border-bottom: var(--bd-hairline);
}
.anr-tml-title { color: var(--fg); font-weight: 500; flex: 1 1 auto; min-width: 0; }
.anr-tml-close {
  flex: 0 0 auto; padding: 2px 7px; font-size: 11px;
  border: var(--bd-hairline); background: var(--bg); color: var(--muted);
}
.anr-tml-close:hover { background: var(--fg); color: var(--bg); border-color: var(--fg); }
.anr-tml-search {
  margin: 8px 10px 6px; padding: 6px 8px;
  font-family: var(--font-mono); font-size: var(--t-tiny);
  border: var(--bd-hairline); background: var(--surface); color: var(--fg);
}
.anr-tml-search:focus { outline: none; border-color: var(--accent); }
.anr-tml-list { overflow-y: auto; padding: 0 6px 8px; }
.anr-tml-row {
  display: flex; align-items: baseline; gap: 10px; width: 100%;
  padding: 5px 6px; border: 0; background: transparent;
  font-family: var(--font-mono); font-size: var(--t-tiny);
  color: var(--fg); text-align: left;
}
.anr-tml-row:hover { background: var(--accent); color: var(--accent-fg); }
.anr-tml-row-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.anr-tml-row-size { flex: 0 0 auto; color: var(--muted); }
.anr-tml-row:hover .anr-tml-row-size { color: var(--accent-fg); }
.anr-tml-note {
  padding: 8px 6px 4px; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.05em; font-size: 9px;
}
.anr-treemap-legend {
  display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: center;
  margin-left: auto;
}
.anr-legend-item {
  font-family: var(--font-mono); font-size: var(--t-tiny);
  text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--muted); display: inline-flex; align-items: center; gap: 5px;
}
.anr-legend-swatch {
  display: inline-block; width: 8px; height: 8px;
  flex-shrink: 0;
}
/* File types show-more */
.anr-ext-more {
  border-top: var(--bd-hairline);
  margin-top: 0;
  padding-top: 0;
}
.anr-ext-more summary {
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 10px 0;
  list-style: none;
  display: flex; align-items: center; gap: 6px;
  user-select: none;
}
.anr-ext-more summary::-webkit-details-marker { display: none; }
.anr-ext-more summary::after {
  content: '+'; font-size: 13px; color: var(--muted);
  transition: color var(--dur-snappy) ease;
}
.anr-ext-more[open] summary::after { content: '\2212'; }
.anr-ext-more summary:hover { color: var(--accent); }
.anr-ext-more summary:hover::after { color: var(--accent); }
/* Controls bar for treemap/tree toggle */
.anr-view-controls {
  display: flex; flex-wrap: wrap; gap: 10px 22px;
  align-items: center;
  padding: 0 0 14px;
  border-bottom: var(--bd-hairline);
  margin-bottom: 14px;
}
/* When the extension filter follows the legend, it owns the single separator. */
.anr-view-controls:has(+ .anr-treemap-extfilter) {
  border-bottom: none; padding-bottom: 6px; margin-bottom: 8px;
}

/* Treemap extension filter chips */
.anr-treemap-extfilter {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  padding: 0 0 14px; margin-bottom: 14px;
  border-bottom: var(--bd-hairline);
}
.anr-extchip {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono); font-size: var(--t-tiny);
  letter-spacing: 0.04em;
  padding: 4px 9px;
  background: var(--surface); color: var(--fg);
  border: var(--bd-hairline); cursor: pointer;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.anr-extchip:hover { border-color: var(--accent); color: var(--accent); }
.anr-extchip.is-active {
  background: var(--accent); color: var(--accent-fg); border-color: var(--accent);
}
.anr-extchip-n { font-size: 0.85em; opacity: 0.65; }
.anr-extchip.is-active .anr-extchip-n { opacity: 0.85; }

/* SVG inspector */
.anr-svg-preview {
  border: var(--bd-hairline);
  border-radius: var(--radius);
  padding: 12px;
  overflow: auto;
  max-height: 500px;
  background: repeating-conic-gradient(var(--surface) 0% 25%, var(--bg) 0% 50%) 50% / 16px 16px;
}
.anr-svg-error { color: var(--accent); }
.anr-svg-palette { display: flex; flex-wrap: wrap; gap: 6px; }
.anr-svg-swatch-item { text-align: center; }
.anr-svg-swatch {
  width: 32px;
  height: 32px;
  border-radius: var(--radius);
  border: var(--bd-hairline);
  cursor: pointer;
}
.anr-svg-swatch-label {
  font-size: 10px;
  max-width: 50px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: 2px;
}

/* STL 3D viewer */
.anr-stl-viewport {
  width: 100%;
  height: 440px;
  border: var(--bd-hairline);
  background: var(--media-bg);
  position: relative;
  overflow: hidden;
}
.anr-stl-viewport:fullscreen { height: 100vh; }
.anr-stl-canvas { display: block; touch-action: none; cursor: grab; }
.anr-stl-canvas:active { cursor: grabbing; }

/* Scroll-zoom toggle (wheelZoomToggle in util.js), overlaid bottom-right of
   every viewer that zooms on a plain wheel scroll (STL 3D, LUT cubes, PCB 2D/3D
   boards; the interactive 3D viewers use the shared .anr-cam stack instead).
   Sharp-cornered like every other control on the site;
   dark-translucent so it reads on both the dark WebGL canvas and the cream PCB
   stage. Hidden on small screens (no wheel on touch - pinch still zooms) and
   in fullscreen (no page underneath to scroll). */
.anr-wheelzoom {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 5;
  font-family: var(--font-mono);
  font-size: 11px;
  line-height: 1;
  padding: 5px 10px;
  border-radius: 0;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(10, 12, 16, 0.72);
  color: #eef2ff;
  cursor: pointer;
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.3);
}
.anr-wheelzoom:hover { border-color: rgba(255, 255, 255, 0.45); }
.anr-wheelzoom.is-off { color: rgba(238, 242, 255, 0.55); border-style: dashed; }
.anr-stl-viewport:fullscreen .anr-wheelzoom { display: none; }
@media (max-width: 700px) { .anr-wheelzoom { display: none; } }
/* Positioned stage wrapped around the 2D board SVG, so the toggle anchors to
   the drawing area rather than the toolbar below it. */
.anr-wheelzoom-stage { position: relative; }

/* XLSX spreadsheet viewer */
.anr-xlsx-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; }
.anr-xlsx-tab {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 4px 12px;
  background: var(--bg);
  border: var(--bd-hairline);
  cursor: pointer;
  color: var(--fg);
}
.anr-xlsx-tab.is-active { background: var(--fg); color: var(--bg); }
.anr-xlsx-table-wrap { overflow: auto; max-height: 540px; border: var(--bd-hairline); }
.anr-xlsx-table { border-collapse: collapse; font-size: 12px; font-family: var(--font-mono); }
.anr-xlsx-table th, .anr-xlsx-table td {
  border: var(--bd-rule);
  padding: 3px 8px;
  text-align: left;
  white-space: nowrap;
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.anr-xlsx-table th { background: var(--surface); position: sticky; top: 0; font-weight: 600; }
.anr-xlsx-rownum, .anr-xlsx-corner { background: var(--surface); position: sticky; left: 0; color: var(--muted); }

/* ---------- TABLE-ANALYSIS WORKBENCH (tablekit.js) ---------- */
/* The [?] help toggle beside the "Workbench" title reuses the shared
   .anr-info-btn / .anr-info-panel pattern (see h3help in util.js). */
.anr-tk-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0 0 10px; }
.anr-tk-search {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  padding: 6px 10px;
  background: var(--bg);
  color: var(--fg);
  border: var(--bd-hairline);
  min-width: 220px;
  flex: 1 1 220px;
}
.anr-tk-status { margin-left: auto; white-space: nowrap; display: inline-flex; align-items: center; }
/* These menus are <details>, so they'd otherwise inherit the global details
   chrome (a border-top rule, a big top margin, and a +/- glyph) - which drew a
   line above the buttons and bumped them onto their own row. Reset it so the
   summary reads as a plain toolbar button, inline with the search box. */
.anr-tk-colsmenu { position: relative; border-top: none; margin: 0; padding: 0; align-self: stretch; }
/* Fill the toolbar line height so the button matches the (taller) search field
   beside it, with the label vertically centred. */
.anr-tk-colsmenu > summary { list-style: none; cursor: pointer; text-transform: none; letter-spacing: normal; display: inline-flex; align-items: center; height: 100%; box-sizing: border-box; }
.anr-tk-colsmenu > summary::-webkit-details-marker { display: none; }
.anr-tk-colsmenu > summary::after { content: none; }
.anr-tk-colslist {
  position: absolute;
  top: 100%; left: 0;
  z-index: 30;
  margin-top: 4px;
  padding: 8px;
  background: var(--bg);
  border: var(--bd-hairline);
  min-width: 220px;
  /* Never spill past the viewport edges. */
  max-width: calc(100vw - 24px);
  max-height: min(300px, 60vh);
  overflow: auto;
  box-sizing: border-box;
}
.anr-tk-colrow { display: flex; align-items: center; gap: 8px; padding: 3px 0; font-size: var(--t-small); }
.anr-tk-colrow span { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.anr-tk-colrow button { padding: 1px 6px; }
/* Split-column dropdown: labelled rows inside the shared .anr-tk-colslist panel.
   Right-anchored (the Split button sits towards the right of the toolbar, so a
   left-anchored panel would spill off the right edge). */
.anr-tk-splitmenu .anr-tk-colslist { left: auto; right: 0; }
.anr-tk-splitbody { min-width: 240px; }
.anr-tk-splitrow { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 0 0 8px; font-size: var(--t-small); }
.anr-tk-splitrow select { flex: 1 1 auto; min-width: 0; }
.anr-tk-splitmenu .anr-btn-row { margin: 8px 0 0; }

/* One scrolling container, one table - see the comment in tablekit.js's
   buildGrid(). The header (position:sticky; top:0) and row-number column
   (position:sticky; left:0) pin against this container; because header and
   body are the same table they can't desync (no scrollbar-offset gap). */
.anr-tk-scroll { position: relative; overflow: auto; max-height: 410px; border: var(--bd-hairline); margin: 0 0 12px; }
/* table-layout: fixed + an explicit <colgroup> keeps column widths stable
   regardless of which rows the virtualised body currently holds. The table
   also gets an explicit pixel width (set in JS) so it can be wider than the
   viewport - enabling horizontal scroll - instead of being shrunk to fit the
   container as table-layout:fixed with an auto width would do. */
.anr-tk-table { border-collapse: collapse; font-size: 12px; font-family: var(--font-mono); table-layout: fixed; }
.anr-tk-table th, .anr-tk-table td {
  border: var(--bd-rule);
  padding: 3px 8px;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  height: 28px;
  box-sizing: border-box;
}
/* Leading/trailing virtualisation spacer rows - bare height, no chrome. */
.anr-tk-spacerrow td { border: none; padding: 0; }
.anr-tk-th { position: relative; background: var(--surface); font-weight: 600; cursor: pointer; }
.anr-tk-sticky-row { position: sticky; top: 0; z-index: 2; }
.anr-tk-sticky-col { position: sticky; left: 0; z-index: 1; }
.anr-tk-corner { z-index: 3; }
.anr-tk-rownum, .anr-tk-corner { background: var(--surface); color: var(--muted); }
.anr-tk-th.has-filter { color: var(--accent); }
.anr-tk-thlabel { display: inline-block; padding-right: 4px; }
.anr-tk-filterbtn {
  font-family: var(--font-mono);
  font-size: 10px;
  background: transparent;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 0 2px;
}
.anr-tk-filterbtn:hover { color: var(--fg); }
.anr-tk-filterpop {
  position: absolute;
  top: 100%; left: 0;
  z-index: 20;
  background: var(--bg);
  border: var(--bd-hairline);
  padding: 8px;
  white-space: nowrap;
  font-weight: 400;
  cursor: default;
}
.anr-tk-filterpop.is-hidden { display: none; }
.anr-tk-filterinput {
  font-family: var(--font-mono);
  font-size: 12px;
  padding: 4px 6px;
  background: var(--bg);
  color: var(--fg);
  border: var(--bd-hairline);
  width: 90px;
  margin: 0 4px 6px 0;
}
.anr-tk-cell { user-select: none; }
.anr-tk-cell.is-selected { background: rgba(var(--accent-rgb, 230, 0, 35), 0.18); }
.anr-tk-rownum { cursor: pointer; }
/* Header / row-number cell shown as the anchor of a whole-column / whole-row
   selection - a firmer accent tint than the cells so the header/gutter reads
   as the header of the selection. */
.anr-tk-th.is-selected, .anr-tk-rownum.is-selected {
  background: rgba(var(--accent-rgb, 230, 0, 35), 0.28);
  color: var(--fg);
}

.anr-tk-statsbar {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--fg);
  padding: 8px 0;
  border-top: var(--bd-hairline);
  border-bottom: var(--bd-hairline);
  margin: 0 0 12px;
}
.anr-tk-statline strong { color: var(--muted); font-weight: 500; }

.anr-tk-gbrow { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 16px; font-size: var(--t-small); }
/* "of [value]" greyed when the calculation is Count (value unused). */
.anr-tk-gbrow .is-dim { opacity: 0.4; }
.anr-tk-gbrow select:disabled { opacity: 0.4; cursor: not-allowed; }
/* The [?] panel for the summary control breaks onto its own full-width line. */
.anr-tk-gbrow .anr-info-panel { flex-basis: 100%; margin: 10px 0 0; }
.anr-tk-gbresult { flex-basis: 100%; margin-top: 8px; }
/* Summary result: a scrollable framed table with a pinned header, so a long
   list of groups scrolls in place instead of growing the page. */
.anr-tk-gbscroll { max-height: 260px; overflow: auto; border: var(--bd-hairline); }
.anr-tk-gbtable { width: 100%; margin: 0; }
.anr-tk-gbtable thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface);
  text-align: left;
}

/* Chart builder. Live-redraw: no "build" button. A row of labelled fields
   (type buttons + only the pickers the current type consumes) sits above the
   canvas, with suggestions surfaced inline above them. */
.anr-tk-chart { margin: 0 0 16px; }
.anr-tk-suggrow { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; margin: 0 0 12px; }
.anr-tk-suggrow .anr-btn-row { margin: 0; }
/* Each setting on its own row: Chart type, then one row per picker the active
   type uses (X axis / Values / Options ...). More readable than a packed row. */
.anr-tk-chart-controls { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; margin: 0 0 12px; }
.anr-tk-datafields { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; width: 100%; }
/* [?] help panel: one entry per line rather than a run-on paragraph. */
.anr-tk-help-item { display: block; margin-top: 7px; }
.anr-tk-help-item:first-child { margin-top: 0; }
/* One labelled control: a small caption over its input. */
.anr-tk-field { display: flex; flex-direction: column; gap: 5px; }
.anr-tk-fieldlabel {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}
.anr-tk-typerow { margin: 0; }
.anr-tk-ylist { display: flex; flex-wrap: wrap; gap: 10px 14px; width: 100%; padding-top: 3px; }
.anr-tk-ycheck { display: inline-flex; align-items: center; gap: 5px; font-size: var(--t-small); cursor: pointer; }
/* Square Swiss checkboxes for the value list + column-hide list, matching the
   OCR-language box (hairline square, accent fill + thin tick when ticked). */
.anr-tk-ycheck input,
.anr-tk-colrow input[type="checkbox"] {
  -webkit-appearance: none;
  appearance: none;
  flex: none;
  width: 15px;
  height: 15px;
  margin: 0;
  border: var(--bd-hairline);
  background: var(--bg);
  border-radius: 0;
  cursor: pointer;
  display: inline-grid;
  place-content: center;
  transition: background var(--dur-fast) ease, border-color var(--dur-fast) ease;
}
.anr-tk-ycheck input::before,
.anr-tk-colrow input[type="checkbox"]::before {
  content: '';
  width: 3px;
  height: 7px;
  margin-top: -1px;
  border: solid var(--accent-fg, #fff);
  border-width: 0 1.6px 1.6px 0;
  transform: rotate(45deg) scale(0);
  transition: transform var(--dur-fast) ease;
}
.anr-tk-ycheck input:hover,
.anr-tk-colrow input[type="checkbox"]:hover { border-color: var(--fg); }
.anr-tk-ycheck input:checked,
.anr-tk-colrow input[type="checkbox"]:checked { background: var(--accent); border-color: var(--accent); }
.anr-tk-ycheck input:checked::before,
.anr-tk-colrow input[type="checkbox"]:checked::before { transform: rotate(45deg) scale(1); }
.anr-tk-bins { width: 90px; }
.anr-tk-charthost { width: 100%; height: 320px; margin-top: 12px; border: var(--bd-hairline); }
.anr-tk-canvas { width: 100%; height: 100%; display: block; }
.anr-tk-chart-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 8px; }
.anr-tk-chart-caption { margin: 0; }

/* ---- Workbench on narrow / touch screens ---- */
@media (max-width: 700px) {
  /* Toolbar: search gets its own full-width row; the row-count status flows
     onto its own line instead of being stranded far right. */
  .anr-tk-search { flex-basis: 100%; min-width: 0; }
  .anr-tk-status { margin-left: 0; flex-basis: 100%; white-space: normal; }
  /* 16px on inputs/selects stops iOS zooming the page in on focus, and gives
     bigger tap targets. */
  .anr-tk-card input, .anr-tk-card select { font-size: 16px; }
  /* The filter gear is a tiny tap target at 10px - enlarge it for touch. */
  .anr-tk-filterbtn { font-size: 14px; padding: 4px 6px; }
  .anr-tk-filterpop { max-width: calc(100vw - 24px); box-sizing: border-box; white-space: normal; }
  /* Let the grid and chart take a sensible share of a short screen. */
  .anr-tk-scroll { max-height: 60vh; }
  .anr-tk-charthost { height: 260px; }
  /* Dropdown panels: no fixed min-widths that could exceed a small viewport. */
  .anr-tk-colslist, .anr-tk-splitbody { min-width: 0; }
}

/* EPUB reader */
.anr-epub-nav { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
.anr-epub-viewport {
  border: var(--bd-rule);
  padding: 24px;
  max-height: 600px;
  overflow: auto;
  line-height: 1.6;
}
.anr-epub-content img { max-width: 100%; height: auto; }
.anr-epub-content h1, .anr-epub-content h2, .anr-epub-content h3 { margin: 1em 0 0.4em; }
.anr-epub-content p { margin: 0 0 0.8em; }

/* PPTX slide viewer */
/* Slides tile 2-per-row by default and 3-per-row once the viewport is wide
   enough (900px, the tablet breakpoint), never the old single column. The grid
   gap replaces each slide's former bottom margin. */
.anr-pptx-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 18px;
}
@media (min-width: 900px) {
  .anr-pptx-grid { grid-template-columns: repeat(3, 1fr); }
}
/* container-type: each slide is a query container, so the text/padding inside
   scale from its OWN rendered width (cqw) - a slide ~960px wide in PowerPoint, so
   30px ≈ 3.1cqw. That keeps a thumbnail looking like a shrunk slide instead of
   full-size "zoomed in" text, and the same slide reads at full size in the wide
   lightbox. clamp() floors keep tiny thumbnails legible and ceilings stop the
   lightbox over-scaling. */
.anr-pptx-slide {
  container-type: inline-size;
  position: relative;
  border: var(--bd-hairline);
  background: var(--bg);
  margin: 0;
  /* % padding resolves against the parent width (≈ the slide's own width in both
     the grid cell and the lightbox), so it stays proportional. The slide's own
     box can't use cqw - those resolve against an ANCESTOR container, not itself. */
  padding: 3.3% 3.75%;
  overflow: hidden;
  cursor: pointer;
}
.anr-pptx-grid .anr-pptx-slide:hover { border-color: var(--accent); }
.anr-pptx-num {
  position: absolute;
  top: 8px;
  right: 12px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted);
}
.anr-pptx-title { font-size: clamp(11px, 3.1cqw, 30px); font-weight: 600; margin-bottom: 1.5cqw; }
.anr-pptx-title p { margin: 0 0 0.4cqw; }
.anr-pptx-body { font-size: clamp(8px, 1.6cqw, 15px); line-height: 1.5; }
.anr-pptx-body p { margin: 0 0 0.6cqw; }
.anr-pptx-img { max-width: 60%; height: auto; margin: 1cqw 0; border: var(--bd-rule); }
/* Apple iWork embedded preview image (Pages/Numbers/Keynote). */
.anr-iwork-preview { display: block; max-width: 100%; height: auto; margin: 10px 0; border: var(--bd-rule); }
/* PSD layer tree: one row per layer, indented for groups, with a small canvas
   thumbnail. Hidden layers are dimmed. */
.anr-psd-layers { margin-top: 8px; display: flex; flex-direction: column; gap: 1px; }
.anr-psd-layer { display: flex; align-items: center; gap: 8px; padding: 3px 4px; border-radius: 0; }
.anr-psd-layer:hover { background: var(--hover, rgba(127,127,127,0.08)); }
.anr-psd-layer.is-hidden { opacity: 0.45; }
/* Hidden/invisible layer kept visible in the tree (greyed), plus the forensic
   highlight for layers carrying content the flattened image drops. */
.anr-psd-layer.anr-psd-off { opacity: 0.5; }
.anr-psd-layer.anr-psd-flagged { background: rgba(var(--accent-rgb), 0.08); box-shadow: inset 2px 0 0 var(--accent); }
.anr-psd-meta { flex: 1 1 auto; }
.anr-psd-export { flex: 0 0 auto; margin-left: auto; }
.anr-psd-thumb { flex: 0 0 30px; width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border: var(--bd-rule); background: repeating-conic-gradient(#bbb 0% 25%, #fff 0% 50%) 50% / 10px 10px; color: var(--muted); font-size: 12px; }
.anr-psd-thumb canvas { display: block; max-width: 28px; max-height: 28px; }
.anr-psd-meta { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.anr-psd-name { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.anr-psd-sub { font-size: 11px; color: var(--muted); font-family: var(--font-mono); }
/* Font viewer: live specimen lines, variable-axis sliders, and a glyph grid. */
.anr-font-specimen { margin-top: 8px; }
.anr-font-line { margin: 6px 0; line-height: 1.25; word-break: break-word; }
/* Per-script pangram rows (Japanese / Cyrillic / Arabic / … samples) */
.anr-font-scripts { display: flex; flex-direction: column; gap: 6px; }
.anr-font-script-row { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.anr-font-script-row .anr-font-line { margin: 2px 0; flex: 1 1 240px; min-width: 0; }
.anr-font-script-label { flex: 0 0 84px; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }
.anr-font-axes { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 14px; padding-bottom: 12px; border-bottom: var(--bd-rule); }
.anr-font-axes-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
.anr-font-axes-title { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); }
.anr-font-playall { font-size: 11px; padding: 3px 10px; }
.anr-font-playall.is-playing { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.anr-font-axis { display: grid; grid-template-columns: minmax(110px, 1fr) 2fr auto auto; align-items: center; gap: 10px; font-size: 12px; }
.anr-font-axis-name { font-family: var(--font-mono); color: var(--muted); }
.anr-font-axis-val { font-family: var(--font-mono); min-width: 48px; text-align: right; }
.anr-font-axis .anr-range { width: 100%; min-width: 0; }
.anr-font-axis-play { width: 26px; height: 26px; font-size: 10px; }
.anr-font-axis-play.is-playing { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
.anr-font-axis-play.is-playing:hover { background: var(--fg); color: var(--bg); border-color: var(--fg); }
@media (max-width: 560px) {
  .anr-font-axis { grid-template-columns: 1fr auto auto; }
  .anr-font-axis-name { grid-column: 1 / -1; }
}
.anr-font-glyphs { margin-top: 8px; display: grid; grid-template-columns: repeat(auto-fill, 44px); gap: 4px; }
.anr-font-glyph { width: 44px; height: 44px; border: var(--bd-rule); border-radius: 0; background: var(--card, transparent); }
/* DjVu page viewer: pager bar + rendered page canvas. */
.anr-djvu-bar { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.anr-djvu-status { font-family: var(--font-mono); font-size: 12px; color: var(--muted); }
.anr-djvu-canvas { display: block; max-width: 100%; height: auto; border: var(--bd-rule); background: #fff; }
/* MOBI/AZW3 e-book reader: sandboxed section frame. */
.anr-ebook-frame { width: 100%; height: 600px; border: var(--bd-rule); background: #fff; }
/* DWG drawing: rendered SVG on a white field so black CAD strokes stay visible. */
.anr-dwg-wrap { overflow: auto; max-height: 640px; border: var(--bd-rule); background: #fff; padding: 8px; }
.anr-dwg-wrap svg { max-width: 100%; height: auto; display: block; }

/* ----- Altium schematic / PCB viewer ----- */
/* Schematic/PCB canvas: paper off-white, so the boards read like a printed
   drawing (shared by the Altium and KiCad viewers). */
.anr-altium-wrap { border: var(--bd-hairline); background: #f4f0e6; position: relative; }
.anr-altium-svg {
  display: block;
  width: 100%;
  height: clamp(360px, 60vh, 620px);
  touch-action: none;
  cursor: grab;
  background:
    radial-gradient(130% 130% at 50% 0%, rgba(0, 0, 0, 0.03), transparent 58%),
    #f4f0e6;
}
.anr-altium-svg.is-grabbing { cursor: grabbing; }
.anr-altium-svg text { font-family: var(--font-sans); user-select: none; }
.anr-altium-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 10px;
  border-top: var(--bd-hairline);
  background: var(--bg);
}
.anr-altium-bar .anr-btn { padding: 5px 11px; font-size: 12px; }
/* Layer toggle chips: a colour swatch + the layer name; dim when toggled off. */
.anr-altium-layer { display: inline-flex; align-items: center; gap: 7px; }
.anr-altium-layer:not(.is-on) { opacity: 0.4; }
.anr-altium-layer.is-on { border-color: var(--accent); }
.anr-altium-swatch {
  width: 11px; height: 11px;
  border-radius: 0;
  box-shadow: 0 0 4px currentColor;
  flex: none;
}
.anr-altium-pads th, .anr-altium-pads td { text-align: left; padding-right: 18px; white-space: nowrap; }

/* ----- KiCad board: 3D flip + Top/Bottom modes ----- */
/* Mode bar sits above the viewer (3D / Top / Bottom). The active mode chip
   borrows the layer-chip highlight. */
.anr-board-modebar { border-top: none; border-bottom: var(--bd-hairline); }
.anr-board-mode.is-on { border-color: var(--accent); }
.anr-board-host { position: relative; }
.anr-pcb3d-hint { margin-left: auto; align-self: center; }

/* The 3D stage: same cream canvas as the flat boards, with perspective. The
   board is a thin slab - top side on the front face, bottom side on the back
   (a real rotateY(180) flip), with FR4-green edge walls for thickness. */
.anr-pcb3d {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: clamp(360px, 60vh, 620px);
  position: relative;   /* anchors the scroll-zoom toggle */
  touch-action: none; cursor: grab; overflow: hidden;
  /* Black stage behind the board, matching the STL / G-code 3D viewers. The board's
     own FR4 faces stay their material colour; only the surround goes black. */
  background:
    radial-gradient(120% 120% at 50% 0%, rgba(255, 255, 255, 0.04), transparent 60%),
    #000000;
}
.anr-pcb3d:active { cursor: grabbing; }
/* cam = 2D zoom/pan (no perspective), scene = the perspective. Keeping zoom/pan
   outside the perspective stops them from warping the projection. */
.anr-pcb3d-cam { position: relative; will-change: transform; }
/* No transition during live zoom/pan (it would lag the gesture); the JS adds
   .is-animating only for Reset / double-tap, so the camera eases home in step
   with the board's rotation instead of the zoom snapping. */
.anr-pcb3d-cam.is-animating { transition: transform 0.45s cubic-bezier(0.4, 0.1, 0.2, 1); }
.anr-pcb3d-scene { position: relative; perspective: 2200px; }
.anr-pcb3d-board {
  position: relative;
  transform-style: preserve-3d;
  transition: transform 0.45s cubic-bezier(0.4, 0.1, 0.2, 1);
  will-change: transform;
}
/* Live per-frame rotation - a drag, the view cube, or auto-rotate - sets the
   transform every frame, so the ease above has to come off or it lags and rubbers. */
.anr-pcb3d-board.is-dragging,
.anr-pcb3d-board.is-spinning { transition: none; }
.anr-pcb3d-face, .anr-pcb3d-wall { position: absolute; left: 50%; top: 50%; }
.anr-pcb3d-face {
  background: transparent;   /* the board silhouette + shadow are drawn in the SVG */
  backface-visibility: hidden;
  overflow: hidden;
}
.anr-pcb3d-svg { display: block; }
.anr-pcb3d-svg text { font-family: var(--font-sans); user-select: none; }
.anr-pcb3d-wall { background: linear-gradient(180deg, #5d6c2c, #3d4a1a); }

/* Cross-probe ping: a ring that pulses over a part when its BOM row is clicked. */
.anr-altium-ping {
  transform-box: fill-box;
  transform-origin: center;
  animation: anr-altium-ping 1.5s ease-out forwards;
  pointer-events: none;
}
@keyframes anr-altium-ping {
  0%   { opacity: 0;   transform: scale(0.3); }
  18%  { opacity: 1; }
  100% { opacity: 0;   transform: scale(2.1); }
}

/* ----- Combined Altium project workbench (folder drop) ----- */
.anr-altium-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
  padding-bottom: 12px;
  border-bottom: var(--bd-hairline);
}
.anr-altium-tab { padding: 6px 13px; font-size: 13px; }
.anr-altium-tab.is-on { border-color: var(--accent); color: var(--accent); }
.anr-altium-tabpanel > .anr-readout { margin-bottom: 16px; }
.anr-altium-tabpanel > h3 { margin: 22px 0 10px; }
.anr-altium-tabpanel > h3:first-child { margin-top: 0; }
/* The BOM can be wider than a narrow viewport; let it scroll inside its card
   instead of stretching the whole page. */
.anr-altium-bomwrap { margin-top: 6px; max-width: 100%; overflow-x: auto; }
/* BOM: shrink-to-fit (NOT width:100%, which dumps all the slack into the first
   column and crushes the rest until their headers wrap a letter at a time).
   Headers stay on one line; only the description column wraps, capped in width. */
.anr-altium-bom { border-collapse: collapse; }
.anr-altium-bom th, .anr-altium-bom td { text-align: left; padding: 5px 16px 5px 0; vertical-align: top; }
.anr-altium-bom th { white-space: nowrap; color: var(--muted); font-weight: 600; }
.anr-altium-bom td { white-space: nowrap; }
.anr-altium-bom-desc, td.anr-altium-bom-desc { max-width: 360px; white-space: normal; overflow-wrap: anywhere; color: var(--muted); font-size: 12px; }
/* Phones: stop the BOM from forcing the page wide. Let the value and footprint
   columns wrap and break long unbroken tokens (part numbers, footprint library
   paths) rather than running off the side; cap the description to the screen. */
@media (max-width: 700px) {
  .anr-altium-bom td { white-space: normal; overflow-wrap: anywhere; }
  .anr-altium-bom-desc, td.anr-altium-bom-desc { max-width: min(360px, 46vw); }
}
/* Designator / sheet cross-probe buttons: compact, accent on hover. */
.anr-altium-desig {
  padding: 2px 9px;
  font-size: 12px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.anr-altium-desig:hover { border-color: var(--accent); color: var(--accent); }

/* KiCad symbol / footprint picker (a dropdown above its viewer). */
.anr-kicad-select { padding: 6px 11px; font-size: 13px; margin-bottom: 12px; max-width: 100%; }
.anr-kicad-symhost { margin-top: 4px; }
.anr-kicad-symhost > .anr-readout { margin-bottom: 12px; }

/* ----- SPICE / LTspice .raw waveform viewer ----- */
.anr-spice { margin-top: 4px; }
.anr-spice-legend { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.anr-spice-chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; font-size: 12px; font-family: var(--font-mono);
  border: var(--bd-hairline); border-radius: 0; background: transparent;
  color: var(--ink, inherit); cursor: pointer; transition: opacity .12s, border-color .12s;
}
.anr-spice-chip.is-off { opacity: .42; }
.anr-spice-swatch { width: 10px; height: 10px; border-radius: 0; background: var(--tc, #888); flex: none; }
.anr-spice-track { position: relative; border: var(--bd-hairline); border-radius: var(--radius); background: #f4f0e6; overflow: hidden; }
.anr-spice-canvas { display: block; width: 100%; }
.anr-spice-guide { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(20,30,55,0.55); pointer-events: none; }
.anr-spice-readout {
  position: absolute; top: 8px; pointer-events: none; z-index: 2;
  background: rgba(255,255,255,0.94); border: var(--bd-hairline); border-radius: 0;
  padding: 6px 8px; font-size: 12px; font-family: var(--font-mono); color: #1a2233;
  box-shadow: 0 4px 14px rgba(20,30,55,0.16); max-width: 60%;
}
.anr-spice-ro-x { font-weight: 600; margin-bottom: 3px; }
.anr-spice-ro-row { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.anr-spice-ro-name { color: #444; }
.anr-spice-ro-val { margin-left: auto; font-weight: 600; }
.anr-spice-op td:last-child { font-family: var(--font-mono); }

/* ----- IPC-D-356 netlist viewer ----- */
.anr-ipc-maphost { border: var(--bd-hairline); border-radius: var(--radius); background: #f4f0e6; padding: 8px; }
.anr-ipc-svg { display: block; width: 100%; max-height: 520px; }
.anr-ipc-swatch { display: inline-block; width: 10px; height: 10px; border-radius: 0; vertical-align: -1px; }
.anr-ipc-nets td.anr-ipc-conns { max-width: 420px; white-space: normal; color: var(--muted); font-size: 12px; }
.anr-ipc-nets, .anr-ipc-comps { border-collapse: collapse; }
.anr-ipc-nets th, .anr-ipc-nets td, .anr-ipc-comps th, .anr-ipc-comps td { text-align: left; padding: 4px 16px 4px 0; vertical-align: top; }
.anr-ipc-nets th, .anr-ipc-comps th { white-space: nowrap; color: var(--muted); font-weight: 600; }
.anr-ipc-special td { color: var(--muted); }
.anr-pptx-notes { margin-top: 14px; font-size: 13px; color: var(--muted); }
.anr-pptx-notes summary { cursor: pointer; }
/* Speaker notes are noise in a thumbnail - only show them in the open lightbox. */
.anr-pptx-grid .anr-pptx-notes { display: none; }

/* ----- Slide lightbox (a slide moved into the shared .fmt-overlay) ----- */
.anr-pptx-lightbox .fmt-overlay-inner { max-width: min(95vw, 1080px); }
.anr-pptx-slide.anr-pptx-lightboxed {
  width: 100%;
  margin: 0;
  overflow: auto;            /* long content scrolls within the slide-shaped box */
  cursor: default;
}
.anr-pptx-slide.anr-pptx-lightboxed:hover { border-color: var(--hairline); }

/* CSV / TSV data table */
.anr-table-wrap {
  overflow: auto;
  max-height: 500px;
  border: var(--bd-hairline);
  border-radius: var(--radius);
}
.anr-table-data {
  width: auto;
  min-width: 100%;
  white-space: nowrap;
}
.anr-table-sticky {
  position: -webkit-sticky;
  position: sticky;
  top: 0;
  background: var(--bg);
  z-index: 1;
}

/* Click-to-expand table cells (CSV/TSV + XLSX viewers). A cell is clipped to one
   line; clicking it pops up the whole value (showCellPopup). The pointer + hover
   tint signal it's interactive. CSV cells get the same max-width/ellipsis clip the
   XLSX cells already carry, so a long value is visibly truncated until expanded. */
.anr-cell-click { cursor: pointer; }
.anr-cell-click:hover { background: var(--surface); color: var(--accent); }
.anr-table-data .anr-cell-click {
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The centred cell-value popup - reuses the .anr-modal overlay; the card is a touch
   wider than the default modal so long cell text reads comfortably. */
.anr-cell-card { max-width: 520px; }
.anr-cell-value {
  margin: 0 0 22px;
  padding: 12px 14px;
  max-height: 52vh;
  overflow-y: auto;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  line-height: 1.5;
  color: var(--fg);
  background: var(--surface);
  border: var(--bd-hairline);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  -webkit-user-select: text;
  user-select: text;
}
.anr-cell-empty {
  margin: 0 0 22px;
  color: var(--muted);
  font-style: italic;
}
.anr-cell-card .anr-modal-btn.is-done {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-fg);
}

/* JSON / XML syntax highlighting */
.anr-syn-key     { font-weight: 700; }
.anr-syn-str     { color: #5a9e5a; }
.anr-syn-num     { color: #5b9fd6; }
.anr-syn-kw      { color: #e89a2e; font-weight: 700; }
.anr-syn-comment { color: var(--muted); }
.anr-syn-tag     { color: #5b9fd6; font-weight: 700; }
.anr-syn-attr    { color: #e89a2e; }
.anr-syn-error   { color: var(--accent); margin: 4px 0; }
.anr-fmt-summary { cursor: pointer; font-weight: 700; margin: 8px 0; }

/* Waveform selection overlay */
.anr-wave-wrap { position: relative; display: inline-block; width: 100%; }
.anr-wave-overlay {
  position: absolute;
  top: 0;
  left: 0;
  pointer-events: none;
  background: transparent;
}
/* Standalone control strip (unlike the stacked spectrogram .anr-controls, which
   omit their bottom border to butt against the next row) - restore the bottom
   hairline so it reads as a closed box. */
.anr-sel-controls { gap: 8px 14px; margin-top: 6px; align-items: center; border-bottom: var(--bd-hairline); }
.anr-sel-label { font-size: 0.85em; opacity: 0.8; }
.anr-btn-sm { font-size: 0.82em; padding: 3px 10px; }
/* Editable start/end time fields for the waveform selection - a mono uppercase
   label + a sharp-cornered text box, matching the .anr-control input treatment. */
.anr-sel-times { display: inline-flex; align-items: center; gap: 12px; }
.anr-sel-times label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.anr-sel-time {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  padding: 4px 6px;
  /* content-box so the ch width is pure text room (the global border-box reset
     would otherwise eat it into the padding and clip the clock). 9ch fits
     mm:ss.mmm up to two-digit minutes. */
  box-sizing: content-box;
  width: 9ch;
  text-align: right;
  border: var(--bd-hairline);
  background: var(--bg);
  color: var(--fg);
  border-radius: 0;
  font-variant-numeric: tabular-nums;
}
.anr-sel-time:focus-visible { outline: none; border-color: var(--accent); }

/* Hidden video probe for off-screen frame capture. Kept renderable (NOT
   display:none) and in the DOM so the browser gives it a decode surface. iOS
   Safari often won't decode something this small/hidden; video.js handles that
   by falling back to a visible player. */
.anr-video-probe {
  position: fixed;
  left: 0;
  top: 0;
  width: 1px;
  height: 1px;
  opacity: 0.01;
  pointer-events: none;
  z-index: -1;
}
/* Non-blocking "Analysing…" badge over the video player while scene detection
   runs in the background. pointer-events:none keeps the player interactive. */
.anr-video-analysing {
  position: absolute;
  top: 52px;
  right: 14px;
  z-index: 2;
  pointer-events: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 10px;
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--on-dark);
  background: rgba(0, 0, 0, 0.62);
  border: 1px solid var(--border-on-dark);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
}
.anr-video-analysing::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent);
  animation: anr-blink 1s steps(2, end) infinite;
}

/* ---------- MOBILE LAYOUT TUNING ---------- */
@media (max-width: 700px) {
  /* Result content lines up with the dropzones (14px from the screen edge)
     instead of being pushed in by the section-content box. The cards are
     transparent, so the section-content frame is what we pull in. */
  .section .grid { padding-left: 14px; padding-right: 14px; }
  /* The frame sits 14px from the screen edge (via the grid padding); a small
     inner gutter keeps text from touching its border. */
  .section-content {
    padding-left: 14px;
    padding-right: 14px;
  }
  .anr-unknown-results:not([hidden]) { padding-left: 14px; padding-right: 14px; }

  /* The per-format CTA defaults to the wider --pad-x gutter, which left its box
     inset a few px versus the 14px-gutter content card below it. Match the body
     gutter and let the button fill the width so its border lines up exactly with
     the .section-content frame. */
  .format-cta { padding-left: 14px; padding-right: 14px; }
  .format-cta-btn { width: 100%; text-align: center; }

  /* Spectrogram settings (segmented groups): stack the groups vertically, each
     separated by a top hairline, and pack each group's controls into a tidy
     2-up grid so nothing overlaps and they take far less vertical space. */
  .anr-spec-card .anr-controls {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }
  .anr-spec-card .anr-control-group {
    flex-direction: column;
    align-self: stretch;
    gap: 8px;
    padding: 12px 0;
    border-left: none;
    border-top: var(--bd-hairline);
  }
  .anr-spec-card .anr-control-group:first-child { border-top: none; padding-top: 0; }
  .anr-spec-card .anr-control-group:last-child  { padding-bottom: 0; }
  .anr-spec-card .anr-control-group-items {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px 12px;
    align-items: center;
  }
  .anr-spec-card .anr-control { min-width: 0; }
  .anr-spec-card .anr-control > label { white-space: nowrap; flex: 0 0 auto; }
  /* select/range flex into the space left after the label - no fixed width,
     so the label and field never overlap in a narrow cell. */
  .anr-spec-card .anr-control select { flex: 1 1 auto; min-width: 0; }
  .anr-spec-card .anr-control input[type="range"] { flex: 1 1 auto; width: auto; min-width: 0; }
  .anr-spec-card .anr-control .anr-toggle { display: flex; flex: 1 1 auto; }
  .anr-spec-card .anr-control .anr-toggle button { flex: 1 1 auto; }
  /* The Axis toggle and the Sensitivity slider are the widest controls - give
     each its own full-width cell so the slider has room to drag and the label
     can't crush it to zero width. */
  .anr-spec-card .anr-control:has(.anr-toggle),
  .anr-spec-card .anr-control:has(input[type="range"]) { grid-column: 1 / -1; }
  .anr-spec-card .anr-control:has(input[type="range"]) input[type="range"] { width: 100%; }
  /* The range wrap stands in for the bare slider, so it takes the same full-width
     flex treatment (the slit inside stays pinned to its 100% position). */
  .anr-spec-card .anr-control .anr-range-wrap { flex: 1 1 auto; width: auto; min-width: 0; }
  .anr-spec-card .anr-control .anr-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    width: 100%;
  }
  /* Actions row: the "|" separator (a bare grid cell here) is noise on the stacked
     mobile grid - hide it. Isolate and AI separation then fall into a single row
     (one grid column each) above the plain Save PNG / Fullscreen actions, so the
     two "pull a part out of the mix" tools read as a pair. */
  .anr-spec-card .anr-spec-actdiv { display: none; }

  /* ---- Separate / Isolate panel: stack cleanly on a phone ---- */
  /* The AI separator has moved out to the Actions row, so this row now holds just
     the EQ presets ([Underwater][Radio][Hollow]   [Clear]). Stacking it keeps the
     presets from wrapping untidily and Clear from floating onto its own line. */
  .anr-iso-preset-row { flex-direction: column; align-items: stretch; gap: 10px; }
  .anr-iso-seg-div { display: none; }                 /* divider is meaningless once stacked */
  /* Keep the EQ presets compact - small content-width buttons in a wrapping row,
     matching the desktop look - rather than stretching each to fill a 2-up grid. */
  .anr-iso-presets { display: flex; flex-wrap: wrap; gap: 8px; }
  .anr-iso-presets .anr-btn { width: auto; }
  /* Clear (a sibling of the presets) drops its desktop right-float and becomes its
     own full-width row beneath the grid. */
  .anr-iso-preset-clear { margin-left: 0; width: 100%; }
  /* AI model picker: label on its own line, the two tiers as a full-width 2-up. */
  .anr-iso-modelrow { flex-direction: column; align-items: stretch; gap: 8px; }
  .anr-iso-modelseg { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
  .anr-iso-modelseg .anr-btn { width: 100%; }

  /* AI stem result rows (Vocals / Instrumental): on desktop the label, scrubber
     and Analyse / Download WAV sit inline on one row. On a phone that squeezes the
     scrubber and wraps the buttons untidily - so stack them: label on its own line,
     a full-width scrubber under it, then Analyse / Download WAV as a full-width
     2-up (same as the preset grid). */
  .anr-iso-stem-head .anr-iso-stem-label { flex: 0 0 100%; min-width: 0; }
  .anr-iso-stem-head .anr-player { flex: 0 0 100%; margin-bottom: 0; }
  .anr-iso-stem-head > .anr-btn { flex: 1 1 0; min-width: 0; }
}

/* ---------- GRABBABLE PLAYHEAD + SPECTROGRAM TRANSPORT ---------- */
/* A wider invisible hit-area around the 1px line so it's easy to grab and drag;
   the visible line stays 1px via the ::before. */
.anr-playhead.is-grabbable {
  width: 13px;
  margin-left: -6px;
  background: transparent;
  pointer-events: auto;
  cursor: col-resize;
}
.anr-playhead.is-grabbable::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 0;
  bottom: 0;
  width: 1px;
  /* Same --playhead override as the base rule: once grabbable, THIS is the visible
     line (the element itself becomes a transparent 13px hit-area), so a themed
     surface has to be able to colour it here too. */
  background: var(--playhead, var(--on-dark));
}

/* Grab-to-pan: drag the spectrogram body to scroll it horizontally (mouse/pen).
   A plain click still seeks; the cursor only hints grab when there's room to pan. */
.anr-spec-pan .anr-spec-canvas { cursor: pointer; }
.anr-spec-pan.is-pannable .anr-spec-canvas { cursor: grab; }
.anr-spec-pan.is-panning,
.anr-spec-pan.is-panning .anr-spec-canvas { cursor: grabbing; }
.anr-spec-pan.is-panning { user-select: none; }

.anr-spec-transport {
  margin-top: 10px;
}


/* ---------- RENDERED MARKDOWN ---------- */
.anr-md-body {
  font-family: var(--font-sans);
  font-size: var(--t-body);
  line-height: 1.65;
  color: var(--fg);
  max-width: 72ch;
  word-wrap: break-word;
  overflow-wrap: anywhere;
}
.anr-md-body > :first-child { margin-top: 0; }
.anr-md-body > :last-child { margin-bottom: 0; }

.anr-md-h { font-family: var(--font-sans); font-weight: 600; line-height: 1.25; margin: 1.6em 0 0.6em; }
.anr-md-h1 { font-size: 1.8em; padding-bottom: 0.3em; border-bottom: var(--bd-rule); }
.anr-md-h2 { font-size: 1.45em; padding-bottom: 0.25em; border-bottom: var(--bd-rule); }
.anr-md-h3 { font-size: 1.2em; }
.anr-md-h4 { font-size: 1.05em; }
.anr-md-h5 { font-size: 0.95em; }
.anr-md-h6 { font-size: 0.9em; color: var(--muted); }

.anr-md-p { margin: 0 0 1em; }

.anr-md-link { color: var(--accent); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); }
.anr-md-link:hover { border-bottom-color: var(--accent); }

.anr-md-body strong { font-weight: 700; }
.anr-md-body em { font-style: italic; }
.anr-md-body del { opacity: 0.65; }

.anr-md-list { margin: 0 0 1em; padding-left: 1.6em; }
.anr-md-list li { margin: 0.25em 0; }
.anr-md-list .anr-md-list { margin-bottom: 0; }
li.anr-md-task { list-style: none; margin-left: -1.4em; }
li.anr-md-task input { margin-right: 0.5em; vertical-align: middle; }

.anr-md-quote {
  margin: 0 0 1em;
  padding: 0.2em 1em;
  border-left: 3px solid var(--rule);
  color: var(--muted);
}
.anr-md-quote > :last-child { margin-bottom: 0; }

.anr-md-hr { border: none; border-top: var(--bd-rule); margin: 1.8em 0; }

.anr-md-icode {
  font-family: var(--font-mono);
  font-size: 0.88em;
  background: var(--surface);
  border: var(--bd-rule);
  border-radius: var(--radius);
  padding: 0.1em 0.35em;
}
.anr-md-codewrap { position: relative; margin: 0 0 1em; }
.anr-md-codelang {
  position: absolute;
  top: 0; right: 0;
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  background: var(--surface);
  border: var(--bd-rule);
  border-top: none; border-right: none;
  padding: 2px 8px;
}
.anr-md-code {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  line-height: 1.5;
  background: var(--surface);
  border: var(--bd-rule);
  padding: 14px 16px;
  margin: 0;
  overflow: auto;
}
.anr-md-code code { background: none; border: none; padding: 0; font-size: inherit; }

.anr-md-img { max-width: 100%; height: auto; display: block; margin: 1em 0; border: var(--bd-rule); }

.anr-md-tablewrap { overflow-x: auto; margin: 0 0 1em; }
.anr-md-table { border-collapse: collapse; font-size: var(--t-small); width: auto; }
.anr-md-table th, .anr-md-table td { border: var(--bd-rule); padding: 6px 12px; text-align: left; }
.anr-md-table th { background: var(--surface); font-weight: 600; }
.anr-md-table tbody tr:nth-child(even) { background: color-mix(in srgb, var(--surface) 55%, transparent); }

/* Markdown rendered onto the "See full document" lightbox sheet. .anr-page is a
   fixed white paper in BOTH themes, but the markdown palette is theme-driven, so in
   dark mode the body text keeps the off-white --fg and disappears on the white page
   (and code/quote/table surfaces go dark). Pin everything to paper colours here. */
.anr-page .anr-md-body { color: #111; max-width: none; }
.anr-page .anr-md-h6 { color: #555; }
.anr-page .anr-md-quote { color: #555; border-left-color: #cfcfcf; }
.anr-page .anr-md-hr { border-top: 1px solid #dddddd; }
.anr-page .anr-md-link { color: #0b5ec7; border-bottom-color: rgba(11, 94, 199, 0.35); }
.anr-page .anr-md-link:hover { border-bottom-color: #0b5ec7; }
.anr-page .anr-md-icode,
.anr-page .anr-md-code,
.anr-page .anr-md-codelang { background: #f2f2f2; color: #111; border-color: #dcdcdc; }
.anr-page .anr-md-codelang { color: #666; }
.anr-page .anr-md-img { border-color: #dddddd; }
.anr-page .anr-md-table th, .anr-page .anr-md-table td { border-color: #cccccc; }
.anr-page .anr-md-table th { background: #f2f2f2; }
.anr-page .anr-md-table tbody tr:nth-child(even) { background: #f7f7f7; }

/* ---------- Visitor badge (header) + /stats page ---------- */

/* Live visitor count above the header Status line. Inherits the .site-meta dl
   grid; ships a "..." placeholder that app.js swaps for the real total. */
.visit-link { color: inherit; text-decoration: none; font-variant-numeric: tabular-nums; }
.visit-link:hover { color: var(--accent); }

/* The header version number links to the changelog: keep the meta-value look,
   accent on hover (matches .visit-link). */
.version-link { color: inherit; text-decoration: none; }
.version-link:hover { color: var(--accent); }

.stats-page .section-content { max-width: 760px; }

.stats-totals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--gap);
  margin: 28px 0 40px;
}
.stats-total-card {
  border: var(--bd-rule);
  padding: 22px 24px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.stats-total-num {
  font-family: var(--font-mono);
  font-size: clamp(34px, 6vw, 56px);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.stats-total-label {
  font-size: var(--t-small);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}

/* ---- Trend graph (visitors + files over time) ---- */
.stats-trends-card {
  border: var(--bd-rule);
  padding: 22px 24px;
  margin: 36px 0 0;
}
.stats-trends-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin: 0 0 6px;
}
.stats-trends-title { font-size: var(--t-h3); margin: 0; }
/* Segmented per-day / cumulative switch. */
.stats-trends-modes { display: inline-flex; border: var(--bd-rule); }
.stats-trends-mode {
  font: inherit;
  font-size: var(--t-small);
  background: none;
  border: 0;
  color: var(--muted);
  padding: 6px 14px;
  cursor: pointer;
  transition: color var(--dur-snappy) ease, background var(--dur-snappy) ease;
}
.stats-trends-mode + .stats-trends-mode { border-left: var(--bd-rule); }
.stats-trends-mode:hover { color: var(--fg); }
.stats-trends-mode.is-on { background: var(--accent); color: var(--accent-fg); }

.stats-trends-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  margin: 0 0 14px;
  font-size: var(--t-small);
  color: var(--muted);
}
/* Each key is a button that toggles its series in/out of the chart. */
.stats-trends-key {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: inherit;
  font-size: var(--t-small);
  color: var(--muted);
  background: none;
  border: 0;
  padding: 2px 0;
  cursor: pointer;
  transition: opacity var(--dur-snappy) ease;
}
.stats-trends-key:hover { color: var(--fg); }
/* Deselected series: dimmed + struck through, swatch hollowed out. */
.stats-trends-key.is-off { opacity: 0.45; text-decoration: line-through; }
.stats-trends-swatch {
  width: 12px;
  height: 3px;
  border-radius: 0;
  display: inline-block;
  transition: background var(--dur-snappy) ease;
}
.stats-trends-key--visitors .stats-trends-swatch { background: var(--accent); }
.stats-trends-key--files .stats-trends-swatch { background: var(--fg); opacity: 0.5; }
.stats-trends-key.is-off .stats-trends-swatch { background: var(--muted); }

.stats-trends-chart { width: 100%; position: relative; }
.stats-trend-svg { display: block; width: 100%; height: auto; overflow: visible; transition: opacity var(--dur-base) ease; }
.stats-trend-grid { stroke: var(--rule); stroke-width: 1; }
.stats-trend-axis {
  fill: var(--muted);
  font-family: var(--font-mono);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}
.stats-trend-axis--y { text-anchor: end; }
.stats-trend-area { fill: rgba(var(--accent-rgb), 0.1); stroke: none; }
.stats-trend-line { fill: none; stroke-linejoin: round; stroke-linecap: round; }
.stats-trend-line--visitors { stroke: var(--accent); stroke-width: 2; }
.stats-trend-line--files { stroke: var(--fg); stroke-width: 1.6; opacity: 0.5; }
.stats-trend-dot { stroke: var(--bg); stroke-width: 1; }
.stats-trend-dot--visitors { fill: var(--accent); }
.stats-trend-dot--files { fill: var(--fg); opacity: 0.5; }
/* A series group fades when toggled off in the legend. */
.stats-trend-series { transition: opacity var(--dur-slow) ease; }
/* Hover affordances: crosshair guide, enlarged focus dots, transparent hit area. */
.stats-trend-cross { stroke: var(--muted); stroke-width: 1; stroke-dasharray: 3 3; pointer-events: none; transition: opacity var(--dur-fast) ease; }
.stats-trend-focus { stroke: var(--bg); stroke-width: 1.5; pointer-events: none; transition: opacity var(--dur-fast) ease; }
.stats-trend-focus--visitors { fill: var(--accent); }
.stats-trend-focus--files { fill: var(--fg); }
.stats-trend-hit { fill: transparent; }
/* Floating hover tooltip, above the cursor. */
.stats-trend-tip {
  position: absolute;
  z-index: 5;
  transform: translateX(-50%);
  pointer-events: none;
  background: var(--bg);
  border: var(--bd-hairline);
  box-shadow: var(--shadow-popover);
  padding: 6px 9px;
  font-size: var(--t-tiny);
  line-height: 1.4;
  white-space: nowrap;
  color: var(--fg);
}
.stats-trend-tip-date { font-weight: 600; margin-bottom: 3px; }
.stats-trend-tip-row { display: flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }
.stats-trend-tip-row strong { margin-left: auto; padding-left: 10px; font-family: var(--font-mono); }
.stats-trend-tip-swatch { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex: none; }
.stats-trend-tip-swatch--visitors { background: var(--accent); }
.stats-trend-tip-swatch--files { background: var(--fg); opacity: 0.5; }
.stats-trends-note { font-size: var(--t-small); color: var(--muted); margin: 14px 0 0; }

/* On phones the chart switches to a taller, near-1:1-width viewBox (trendLayout()
   in app.js); trim the card padding so the plot keeps its width, and give the
   mode/legend controls fuller tap targets. */
@media (max-width: 700px) {
  .stats-trends-card { padding: 18px 14px; }
  .stats-trends-head { margin-bottom: 10px; }
  .stats-trends-mode { padding: 8px 16px; }
  .stats-trends-legend { gap: 22px; margin-bottom: 16px; }
  .stats-trends-key { padding: 6px 0; }
}

.stats-status {
  font-size: var(--t-small);
  color: var(--muted);
  border: var(--bd-rule);
  padding: 12px 16px;
  margin: 20px 0;
}

.stats-table-head { font-size: var(--t-h3); margin: 8px 0 6px; }
.stats-table-note { font-size: var(--t-small); color: var(--muted); margin: 0 0 16px; }

.stats-table { width: 100%; border-collapse: collapse; font-size: var(--t-body); }
.stats-table th {
  text-align: left;
  font-size: var(--t-tiny);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 600;
  padding: 0 12px 8px;
}
.stats-table td { padding: 9px 12px; vertical-align: middle; }
/* Row dividers are drawn as a 1px background line on the ROW, not a border on
   each cell. A per-cell border-bottom rounds to a device pixel independently per
   column, so with the count column's fractional width the line stepped up ~1px
   across the right of the table; a single row-wide background line can't split at
   a column boundary. */
.stats-table thead tr { background: linear-gradient(var(--hairline), var(--hairline)) no-repeat bottom / 100% 1px; }
.stats-table tbody tr { background: linear-gradient(var(--rule), var(--rule)) no-repeat bottom / 100% 1px; }
.stats-table .stats-rank { width: 2.5em; color: var(--muted); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.stats-table .stats-count { width: 7em; text-align: right; }
.stats-table .stats-share { width: 6em; text-align: right; color: var(--muted); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.stats-table th.stats-count, .stats-table th.stats-share { text-align: right; }
.stats-empty { color: var(--muted); text-align: center; padding: 24px 12px; }

.stats-ext-name { font-family: var(--font-mono); font-weight: 600; }
/* Supported extensions link to their format guide: keep the text colour at rest,
   turn the website accent (red) on hover - no underline. */
a.stats-ext-link { color: inherit; text-decoration: none; transition: color var(--dur-snappy) ease; }
a.stats-ext-link:hover { color: var(--accent); text-decoration: none; }
/* The pooled "Unsupported types" row is a category label, not an extension, so it
   drops the monospace and sits muted. */
.stats-ext-name--group { font-family: inherit; color: var(--muted); }
/* Only unsupported types carry a tag now, and it stays grey (the muted base
   above) instead of the old accent - a quiet wish-list marker. */

/* Count and share are right-aligned numeric columns. */
.stats-count-num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

.stats-toggle {
  margin-top: 18px;
  font: inherit;
  font-size: var(--t-small);
  background: none;
  border: var(--bd-hairline);
  color: inherit;
  padding: 8px 16px;
  cursor: pointer;
}
.stats-toggle:hover { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }

/* Asteroids easter-egg leaderboard card under the stats body. */
.stats-scores-card {
  margin-top: 36px;
  border: var(--bd-rule);
  padding: 22px 24px;
}
.stats-scores-head { font-size: var(--t-h3); margin: 0 0 14px; }
.stats-scores-note { margin: -6px 0 14px; color: var(--muted); font-size: var(--t-small); }
.stats-scores-list {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: anr-rank;
}
.stats-score-row {
  counter-increment: anr-rank;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 12px;
  padding: 9px 0;
  border-bottom: var(--bd-hairline);
}
/* Wave / final blow / date sit inline right after the name; the score is pushed
   to the right edge. */
.stats-score-run {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.02em;
  color: var(--muted);
}
.stats-score-num { margin-left: auto; }
.stats-score-row:last-child { border-bottom: 0; }
/* The reigning #1 score number launches Asteroids - underline on hover/focus. */
.stats-score-num--play { cursor: pointer; }
.stats-score-num--play:hover,
.stats-score-num--play:focus-visible { text-decoration: underline; outline: none; }
.stats-score-name {
  font-family: var(--font-mono);
  letter-spacing: 0.22em;
  font-size: var(--t-body);
}
.stats-score-name::before {
  content: counter(anr-rank) ".";
  color: var(--muted);
  letter-spacing: 0;
  margin-right: 12px;
  font-variant-numeric: tabular-nums;
}
.stats-score-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--accent);
  font-weight: 600;
}

/* Hidden /atari page: the "Play game" call to action under the leaderboard. */
.atari-play-row {
  margin-top: 32px;
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 12px;
}
/* Dev-only "reset cache" button: a small square arrow button sitting next to Play,
   matching the in-game hard-reload (square corners, hairline border, invert on hover -
   all from .anr-btn). Fixed square so it never renders rectangular. */
.atari-reset-btn.anr-btn {
  flex: none;
  width: 52px;
  height: 52px;
  padding: 0;
  font-size: 22px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.atari-play-btn.anr-btn {
  font-size: var(--t-body);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 14px 40px;
  background: var(--accent);
  color: var(--accent-fg);
  border-color: var(--accent);
}
.atari-play-btn.anr-btn:hover {
  background: var(--fg);
  color: var(--bg);
  border-color: var(--fg);
}

/* ============================================================================
   Document "page preview" sheets (paged.js) - shared by DOCX/ODT/ODS/ODP and
   the legacy DOC/XLS/PPT viewers. Pages render as paper sheets regardless of
   theme, the way the PDF page previews do.
   ============================================================================ */
.anr-page-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 16px;
  margin: 6px 0 14px;
}
.anr-page-fig { margin: 0; width: 200px; }
.anr-page-thumb {
  position: relative;
  overflow: hidden;
  width: 200px;
  background: #fff;
  border: var(--bd-hairline);
  cursor: zoom-in;
  transition: box-shadow var(--dur-fast, 0.15s) ease;
}
.anr-page-thumb:hover { box-shadow: 0 6px 20px rgba(0, 0, 0, 0.30); }
.anr-page-thumb-inner {
  width: 760px;
  transform-origin: top left;
  pointer-events: none;
}
.anr-page-thumb-inner .anr-page {
  border: none;
  box-shadow: none;
  margin: 0;
}
.anr-page-cap {
  text-align: center;
  font-family: var(--font-mono);
  font-size: 11px;
  opacity: 0.7;
  margin-top: 6px;
}
/* Ghost sheet: an empty page-shaped tile standing in for a preview that is still
   being built (paged.js's pagePreviewSkeleton / ghostSheet, and the PPTX slide
   grid). It holds exactly the space the real page lands in, so a slow unzip-and-
   paginate no longer leaves the results looking finished and then shoves them
   down when the pages arrive. The slow accent sweep is what says "still coming"
   rather than "blank and broken", so it deliberately reads as pending, not paper:
   surface-coloured, not the white of a real sheet. */
.anr-ghost-sheet {
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border: var(--bd-hairline);
}
.anr-ghost-sheet::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(115deg,
    transparent 38%,
    rgb(var(--accent-rgb) / 0.16) 50%,
    transparent 62%);
  animation: anr-ghost-sweep 1.6s linear infinite;
}
/* Tiles that fill in one at a time (comic pages) keep the box and drop only the
   sweep, so the frame around the picture never moves. */
.anr-ghost-sheet.is-loaded::after { content: none; }
@keyframes anr-ghost-sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100%); }
}
@media (prefers-reduced-motion: reduce) {
  .anr-ghost-sheet::after { animation: none; }
}
/* Stands in for the "Page 1" caption while the page count is still unknown. */
.anr-page-cap-ghost {
  display: inline-block;
  width: 46px;
  height: 0.85em;
  vertical-align: -0.1em;
  background: var(--hairline);
  opacity: 0.35;
}
.anr-page {
  box-sizing: border-box;
  width: 760px;
  max-width: 100%;
  min-height: 1075px;
  padding: 56px;
  background: #ffffff;
  color: #111111;
  border: 1px solid #d8d8d8;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18);
  font-family: Georgia, "Times New Roman", serif;
  font-size: 15px;
  line-height: 1.7;
  overflow-wrap: break-word;
  word-break: break-word;
}
.anr-page :is(h1, h2, h3, h4, h5, h6) { color: #111; line-height: 1.3; }
.anr-page img { max-width: 100%; height: auto; }
.anr-page table { border-collapse: collapse; max-width: 100%; }
.anr-page td, .anr-page th { border: 1px solid #c9c9c9; }
/* Spreadsheet page: let wide grids scroll horizontally inside the sheet. */
.anr-page--sheet {
  min-height: 0;
  padding: 28px;
  font-family: var(--font-sans, system-ui), sans-serif;
  font-size: 13px;
  line-height: 1.4;
  overflow-x: auto;
}
.anr-page--sheet table { width: max-content; min-width: 100%; }
.anr-page--sheet td, .anr-page--sheet th { padding: 4px 8px; white-space: nowrap; }
.anr-page--sheet th {
  background: #f3f3f3;
  font-weight: 600;
  position: sticky;
  top: 0;
}
.anr-page--sheet .anr-sheet-name {
  font-family: var(--font-mono);
  font-size: 12px;
  font-weight: 600;
  margin: 0 0 12px;
  color: #333;
}
/* Presentation page: landscape slide. */
.anr-page--slide {
  width: 760px;
  min-height: 0;
  aspect-ratio: 4 / 3;
  padding: 40px 48px;
  font-family: var(--font-sans, system-ui), sans-serif;
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow: hidden;
}
.anr-page--slide .anr-slide-title { font-size: 26px; font-weight: 700; line-height: 1.25; }
.anr-page--slide .anr-slide-body { font-size: 17px; line-height: 1.5; }
.anr-page--slide img { max-height: 60%; object-fit: contain; align-self: flex-start; }

/* Lightbox stage holding a cloned page, stepped through with prev/next. Single
   tap/drag selects text; double-click/double-tap zooms (handled in paged.js). */
.anr-doc-lightbox { cursor: default; }
.anr-doc-stage {
  max-width: 92vw;
  max-height: 82vh;
  overflow: auto;
  background: transparent;
  -webkit-overflow-scrolling: touch;
  user-select: text;
  -webkit-user-select: text;
  cursor: auto;
}
.anr-page--lightbox {
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
  margin: 0 auto;
  user-select: text;
  -webkit-user-select: text;
}

/* PDF page viewer: a canvas with a selectable, position-aligned text layer
   overlaid (PDF.js v4 text-layer styles). Single tap/drag selects, double-click
   zooms (handled in pdf.js). */
/* Reserve the scrollbar gutter so a vertical scrollbar (tall / zoomed page)
   never eats into the content width - that width loss is what clipped a sliver
   off the side and made an otherwise-fitting page pannable sideways. */
.anr-pdf-stage { text-align: center; scrollbar-gutter: stable; }
/* Grab-to-pan: a hand cursor when the page overflows and can be dragged, and a
   closed hand plus suppressed selection while a drag is in progress. */
.anr-pdf-stage.is-grabbable { cursor: grab; }
.anr-pdf-stage.is-panning {
  cursor: grabbing;
  user-select: none;
  -webkit-user-select: none;
}
.anr-pdf-pagebox {
  position: relative;
  display: inline-block;
  line-height: 0;
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.5);
}
.anr-pdf-pagebox > canvas { display: block; }
.textLayer {
  position: absolute;
  text-align: initial;
  inset: 0;
  overflow: clip;
  opacity: 1;
  line-height: 1;
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
  forced-color-adjust: none;
  transform-origin: 0 0;
  caret-color: CanvasText;
  z-index: 2;
  user-select: text;
  -webkit-user-select: text;
}
.textLayer :is(span, br) {
  color: transparent;
  position: absolute;
  white-space: pre;
  cursor: text;
  transform-origin: 0% 0%;
}
.textLayer span.markedContent { top: 0; height: 0; }
.textLayer ::selection { background: rgba(0, 100, 255, 0.30); }
.textLayer .endOfContent {
  display: block;
  position: absolute;
  inset: 100% 0 0;
  z-index: -1;
  cursor: default;
  user-select: none;
}

/* Per-page selectable text card (pagedTextCard), shared by the doc viewers. */
.anr-pagetext-wrap { max-height: 460px; overflow: auto; margin-top: 4px; }
.anr-pagetext-block { margin: 0 0 14px; }
.anr-pagetext-head { display: flex; align-items: center; gap: 10px; margin: 0 0 4px; }
.anr-pagetext-label { font-size: 11px; opacity: 0.7; font-family: var(--font-mono); }
.anr-pagetext {
  white-space: pre-wrap;
  word-break: break-word;
  margin: 0;
  font-family: var(--font-mono);
  font-size: 12.5px;
  line-height: 1.55;
  user-select: text;
  -webkit-user-select: text;
}
@media (max-width: 540px) {
  .anr-page { padding: 28px; min-height: 0; }
  .anr-page--slide { padding: 24px; }
}

/* ============================================================================
   Backlog viewers: Jupyter notebooks, email, HAR, JSON supersets, NFO art,
   draw.io / DXF diagrams. Swiss-minimal, theme-token driven.
   ============================================================================ */

/* Shared small subheading inside a card (attachments, message body, ...) */
.anr-subhead {
  font-family: var(--font-mono);
  font-size: var(--t-tiny);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 22px 0 10px;
}

/* ---------- Jupyter notebook ---------- */
.anr-nb-flow { display: flex; flex-direction: column; gap: 18px; }
.anr-nb-cell { border-left: 2px solid var(--rule); padding-left: 14px; }
.anr-nb-cell--code { border-left-color: var(--accent); }
.anr-nb-prompt {
  font-family: var(--font-mono); font-size: var(--t-tiny);
  color: var(--accent); margin-bottom: 4px; letter-spacing: 0.05em;
}
.anr-nb-code, .anr-nb-out {
  font-family: var(--font-mono); font-size: 12.5px; line-height: 1.5;
  white-space: pre-wrap; word-break: break-word; margin: 0;
  user-select: text; -webkit-user-select: text;
}
.anr-nb-code { background: var(--surface); padding: 12px 14px; border-radius: 0; }
.anr-nb-out { margin-top: 8px; padding: 8px 14px; border-left: 2px solid var(--rule); color: var(--muted); }
.anr-nb-out--result { color: var(--fg); }
.anr-nb-out--err { color: var(--accent); border-left-color: var(--accent); }
.anr-nb-img { max-width: 100%; height: auto; margin-top: 8px; border: var(--bd-rule); }
.anr-nb-md { line-height: 1.6; }
.anr-nb-md h1, .anr-nb-md h2, .anr-nb-md h3 { margin: 0.5em 0 0.3em; }
.anr-nb-mdcode { background: var(--surface); padding: 12px 14px; border-radius: 0; font-family: var(--font-mono); font-size: 12.5px; white-space: pre-wrap; }

/* ---------- Email ---------- */
.anr-email-html {
  border: var(--bd-rule); padding: 16px; border-radius: 0;
  max-height: 640px; overflow: auto; background: var(--bg);
  user-select: text; -webkit-user-select: text;
}
.anr-email-html img { max-width: 100%; height: auto; }

/* ---------- HAR request table ---------- */
.anr-har-table { width: 100%; border-collapse: collapse; font-family: var(--font-mono); font-size: 12px; }
.anr-har-table th {
  text-align: left; font-weight: 500; color: var(--muted);
  border-bottom: var(--bd-hairline); padding: 6px 8px;
  text-transform: uppercase; letter-spacing: 0.08em; font-size: var(--t-tiny);
}
.anr-har-table td { padding: 5px 8px; border-bottom: var(--bd-rule); vertical-align: top; }
.anr-har-num { text-align: right; white-space: nowrap; }
.anr-har-url { word-break: break-all; max-width: 0; }
.anr-har-2xx { color: #1a8a3a; }
.anr-har-3xx { color: #b07d00; }
.anr-har-4xx, .anr-har-5xx { color: var(--accent); }

/* ---------- JSON value tree ---------- */
.anr-json-tree { font-family: var(--font-mono); font-size: 12.5px; line-height: 1.7; user-select: text; -webkit-user-select: text; }
.anr-json-children { padding-left: 18px; border-left: var(--bd-rule); margin-left: 4px; }
.anr-json-sum { cursor: pointer; list-style: none; }
.anr-json-sum::-webkit-details-marker { display: none; }
.anr-json-sum::before { content: "\25B8"; color: var(--muted); display: inline-block; width: 1em; transition: transform var(--dur-snappy) ease; }
details[open] > .anr-json-sum::before { transform: rotate(90deg); }
.anr-json-key { color: var(--accent); }
.anr-json-brace { color: var(--muted); }
.anr-json-leaf { padding-left: 1em; }
.anr-json-string { color: #1a8a3a; }
.anr-json-number { color: #2d6fd6; }
.anr-json-boolean, .anr-json-null { color: #b07d00; }

.anr-code-src { max-height: 560px; overflow: auto; background: var(--surface); padding: 14px; border-radius: 0; }

/* ---------- NFO scene art ---------- */
.anr-nfo-art {
  font-family: var(--font-mono); font-size: 13px; line-height: 1.2;
  white-space: pre; overflow-x: auto; tab-size: 8;
  background: var(--surface); padding: 18px; border-radius: 0;
  user-select: text; -webkit-user-select: text;
}

/* ---------- Diagram preview (draw.io / DXF) ---------- */
.anr-diagram-stage {
  border: var(--bd-rule); border-radius: 0; background: var(--bg);
  max-height: 70vh; overflow: auto; padding: 12px;
}
.anr-diagram-svg { display: block; width: 100%; height: auto; max-height: 68vh; }
.anr-dg-shape { fill: var(--surface); stroke: var(--fg); stroke-width: 1.5; }
.anr-dg-edge { stroke: var(--muted); stroke-width: 1.5; }
.anr-dg-label { fill: var(--fg); font-family: var(--font-sans); font-size: 12px; text-anchor: middle; dominant-baseline: middle; }
.anr-dg-elabel { fill: var(--muted); font-family: var(--font-sans); font-size: 10px; text-anchor: middle; }
.anr-dxf-svg { background: var(--bg); }
.anr-dxf-line { fill: none; stroke: var(--fg); stroke-width: 0.6; vector-effect: non-scaling-stroke; }
.anr-dxf-shape { fill: none; stroke: var(--fg); stroke-width: 0.6; vector-effect: non-scaling-stroke; }
.anr-dxf-text { fill: var(--accent); font-family: var(--font-sans); }

/* Per-format page CTA - sits between the nav strip and the content card,
   inviting a visitor who landed on a "what is a .X file" page to actually
   analyse one. */
.format-cta {
  padding: 28px var(--pad-x) 0;
  display: flex;
  justify-content: center;
}
/* The CTA already gives breathing room under the nav, so collapse the big
   default section top-padding (--pad-y) that would otherwise leave a huge gap
   beneath the button. */
.format-cta + .section { padding-top: 28px; }
/* Same visual language as .header-btn (surface fill, hairline border, invert on
   hover) - just scaled up for a primary call to action. */
.format-cta-btn {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--accent-fg);
  background: var(--accent);
  border: var(--bd-hairline);
  border-color: var(--accent);
  padding: 11px 24px;
  transition: background var(--dur-base) ease, color var(--dur-base) ease, filter var(--dur-base) ease;
}
.format-cta-btn:hover { background: var(--accent); color: var(--accent-fg); filter: brightness(1.08); }
.format-cta-btn span { transition: transform var(--dur-snappy) ease; display: inline-block; }
.format-cta-btn:hover span { transform: translateX(4px); }

/* ---- Gyro / IMU timeline (sony-rtmd.js, gcsv.js) --------------------------
   Mirrors the spectrogram aesthetic: a dark, horizontally-zoomable canvas in a
   hairline frame, mono labels, square corners, and a white playhead. */
.anr-imu { margin-top: 14px; }
.anr-imu-video {
  width: 100%;
  max-height: 240px;
  display: block;
  background: var(--media-bg);
  border: var(--bd-hairline);
}
.anr-imu-ctl {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
  margin: 12px 0 6px;
}
.anr-imu-ctl-label {
  font-family: var(--font-mono);
  font-size: var(--t-small, 12px);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
.anr-imu-ctl-hint { margin: 0; }
.anr-imu-zoom {
  font-family: var(--font-mono);
  font-size: var(--t-small, 12px);
  padding: 4px 8px;
  border: var(--bd-hairline);
  background: var(--bg);
  color: var(--fg);
  border-radius: 0;
}
.anr-imu-scroller {
  overflow-x: auto;
  overflow-y: hidden;
  border: var(--bd-hairline);
  background: var(--media-bg);
  cursor: crosshair;
  touch-action: pan-y;
}
.anr-imu-track { position: relative; }
.anr-imu-canvas { display: block; }
.anr-imu-playhead {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  background: #fff;
  box-shadow: 0 0 4px rgba(0, 0, 0, 0.9);
  pointer-events: none;
  left: 0;
  z-index: 2;
}
.anr-imu-legend {
  margin: 6px 0 2px;
  font-size: var(--t-small, 12px);
}

/* Mini video players (section-03, gyro): the site transport overlays the video
   and only appears on hover / keyboard focus, like a normal video player. */
.anr-video-hoverui { position: relative; display: block; }
.anr-video-hoverui > video { display: block; width: 100%; }
.anr-video-hoverui > .anr-player {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  margin: 0;
  padding: 8px;
  gap: 8px;
  border: none;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.82), rgba(0, 0, 0, 0));
  color: #fff;
  opacity: 0;
  transition: opacity 0.15s ease;
  pointer-events: none;
}
.anr-video-hoverui:hover > .anr-player,
.anr-video-hoverui:focus-within > .anr-player {
  opacity: 1;
  pointer-events: auto;
}
/* On touch (no hover), keep the transport visible so it stays usable. */
@media (hover: none) {
  .anr-video-hoverui > .anr-player { opacity: 1; pointer-events: auto; }
}

/* ---------- /samples gallery -------------------------------------------------
   Small floating buttons (chips), one per example file, generated by
   tools/prerender-samples.mjs. They flow and wrap with the page width. Each runs
   the normal analyser pipeline inline, flagged as a sandboxed sample. Styling
   reuses the site's Swiss/monochrome tokens: sharp corners, hairline border,
   accent on hover. */
/* The intro/gallery section has no left meta rail, so - like the patch/stats/
   privacy pages - drop the 12-column grid and let its single content column span
   the full centred width (matching the analysis area below) instead of sitting in
   the right-hand column. Scoped to the intro so the media analysis sections keep
   their two-column (meta aside + content) layout. */
.samples-page .samples-intro .grid { display: block; }
.samples-page .samples-intro .section-content { grid-column: auto; }
/* Let the heading run the full width of the card and wrap at its edge, rather
   than at the default 22ch (which broke it at roughly two thirds). */
.samples-page .samples-intro .section-head { max-width: none; }
/* On mobile the samples card goes full-bleed: the frame (background, top/bottom
   border) spans the whole screen width by dropping the 14px grid gutter and the
   card's left/right border. The content keeps a 14px inner gutter so the heading
   and chips don't touch the screen edges. (More specific than the generic
   .section .grid / .section-content mobile rules above, so it wins.) */
@media (max-width: 700px) {
  .samples-page .samples-intro .grid { padding-left: 0; padding-right: 0; }
  .samples-page .samples-intro .section-content {
    padding-left: 14px;
    padding-right: 14px;
    border-left: none;
    border-right: none;
  }
  /* Three chips per row: let them flex to fill the width and stay square via
     aspect-ratio instead of the fixed 132px desktop size. */
  .samples-page .sample-gallery { grid-template-columns: repeat(3, 1fr); }
  .samples-page .sample-chip { width: auto; height: auto; aspect-ratio: 1 / 1; }
}
/* The samples are grouped into the formats page's domain categories (Images,
   Audio, Video, 3D / CAD, ...). Each group is a titled section with a hairline
   divider above it, echoing the /formats section labels. */
.sample-groups { margin-top: 24px; }
.sample-group {
  margin-top: 22px;
  padding-top: 18px;
  border-top: var(--bd-hairline);
}
.sample-group:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}
.sample-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, 132px);
  gap: 12px;
}
.sample-chip {
  position: relative;          /* positioning context for the depth tag */
  display: flex;
  flex-direction: column;
  width: 132px;
  height: 132px;
  padding: 0;
  background: var(--bg);
  border: var(--bd-hairline);
  cursor: pointer;
  overflow: hidden;
  transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
  /* The tile text is sized in cqw (a share of the chip's own width) so it scales
     with the chip - the fixed 132px desktop size or the flexible mobile thirds. */
  container-type: inline-size;
}
/* Depth tag in the thumbnail's top-right corner - Full / Partial / ID, the same
   badge (and colours) the /formats list uses, so the gallery says at a glance how
   deeply each sample is analysed. */
.sample-depth {
  position: absolute;
  top: 5px;
  right: 5px;
  z-index: 1;
  font-family: var(--font-mono);
  font-size: 8px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: 1.3;
  padding: 1px 4px;
  background: var(--bg);
  border: var(--bd-hairline);
  color: var(--muted);
  pointer-events: none;
}
.sample-depth.is-full { color: var(--accent); border-color: var(--accent); }
.sample-depth.is-partial { color: var(--partial); border-color: var(--partial); }
/* Top three quarters: the extension drawn big and black (Geist - the site's
   sans). Size steps down for longer extensions (set in prerender-samples.mjs)
   so even "KICAD_PCB" fits the tile. */
.sample-thumb {
  flex: 3 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  padding: 6px;
  font-family: var(--font-sans);
  font-weight: 700;
  font-size: 28cqw;
  line-height: 1;
  letter-spacing: 0.01em;
  color: var(--fg);
  overflow: hidden;
}
.sample-thumb--mid { font-size: 20cqw; }
.sample-thumb--long { font-size: 15cqw; }
.sample-thumb--xlong { font-size: 11cqw; word-break: break-all; text-align: center; }
/* Bottom quarter: the filename, vertically centred and wrapped to at most two
   lines (with ellipsis) so even a long name always fits. The inner span does the
   line-clamp; the outer cell centres it. */
.sample-name {
  flex: 1 1 0;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  width: 100%;
  box-sizing: border-box;
  padding: 4px 6px;
  border-top: var(--bd-hairline);
  background: var(--bg-soft, var(--bg));
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1.15;
  color: var(--fg);
  text-align: center;
  overflow: hidden;
}
.sample-name > span {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  word-break: break-word;
}
.sample-chip:hover,
.sample-chip:focus-visible {
  border-color: var(--accent);
  outline: none;
  transform: translateY(-4px);
  box-shadow: 0 10px 22px rgba(0, 0, 0, 0.14);
}
.sample-chip:hover .sample-thumb,
.sample-chip:focus-visible .sample-thumb { color: var(--accent); }
.sample-chip:active { transform: translateY(-1px); }
.sample-chip.is-loading { opacity: 0.55; pointer-events: none; }

/* "More" reveal: the extra chips are dropped from the grid until the More button
   is clicked (app.js adds .is-expanded to the gallery), then they fade/slide in.
   No animation fill so hover transforms still work once the reveal finishes. */
.sample-chip--extra { display: none; }
/* The .sample-chip display:flex would otherwise beat the hidden attribute's
   UA display:none, so re-assert it - this is how the More button disappears once
   it's clicked (app.js sets .hidden on it). */
.sample-chip[hidden] { display: none; }
.sample-gallery.is-expanded .sample-chip--extra {
  display: flex;
  animation: sample-more-reveal 0.32s ease;
}
@keyframes sample-more-reveal {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .sample-gallery.is-expanded .sample-chip--extra { animation: none; }
}

/* Cursor-following info popup, shown while hovering a sample chip (wired in
   app.js). Fixed-positioned and pointer-events:none so it never blocks the chip
   it describes; JS sets its left/top to sit just above the cursor. */
.sample-pop {
  position: fixed;
  z-index: 999;
  max-width: 260px;
  padding: 8px 11px;
  background: var(--bg);
  border: var(--bd-hairline);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 12px;
  line-height: 1.35;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -100%) translateY(-6px);
  transition: opacity 0.12s ease;
}
.sample-pop.is-on { opacity: 1; }
.sample-pop-head {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 3px;
}
.sample-pop-ext {
  font-family: var(--font-mono);
  font-weight: 700;
  color: var(--accent);
}
.sample-pop-label { font-weight: 600; }
.sample-pop-size { margin-left: auto; color: var(--muted); font-size: 11px; }
.sample-pop-desc { color: var(--muted); }
