/* =============================================================
   S2D2 project page.
   One centred column. Two measures. No motion.
   Replaces base.css + layout.css + components.css.
   ============================================================= */

*, *::before, *::after { box-sizing: border-box; }

html { font-size: 100%; -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--ff-body);
  font-size: var(--fs-body);
  /* Body weight 200 and bold at 500, as measured on the reference page: it never
     uses 700 anywhere, which is most of why the page reads light rather than loud. */
  font-weight: 200;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

strong, b { font-weight: 500; }

/* Links are body-coloured with a hairline underline offset from the baseline, and
   FADE on hover rather than brightening — the reference page's treatment. */
a {
  color: var(--accent);
  text-decoration: underline var(--underline) 1px;
  text-underline-offset: 2px;
}
a:hover, a:focus-visible { color: var(--accent-hover); text-decoration-color: var(--underline); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.skip-link {
  position: absolute; left: -9999px; top: 0;
  background: var(--ground); color: var(--accent);
  padding: var(--s-3) var(--s-4); z-index: 10;
}
.skip-link:focus { left: 0; }

/* ---- the two measures ---- */
.prose { max-width: var(--w-prose); margin-inline: auto; padding-inline: var(--s-5); }
/* A lede sitting above a media block aligns with that block's left edge rather than
   being centred, so the eye reads one column. The Abstract keeps the centred
   narrow measure, which is what the corpus does for the abstract specifically. */
.media > .lede { max-width: var(--w-prose); margin: 0 0 var(--s-4); }
.media { max-width: var(--w-media); margin-inline: auto; padding-inline: var(--s-5); }
.media-note { text-align: center; margin-inline: auto; }

/* ---- header block ---- */
.head { padding: var(--s-8) 0 var(--s-6); text-align: center; }
.head h1 {
  margin: 0 0 var(--s-2);
  font-family: var(--ff-display);
  font-size: var(--fs-h1); font-weight: 400; line-height: 1.1;
}
@media (max-width: 700px) { .head h1 { font-size: 2rem; } }
.head .venue {
  margin: 0 0 var(--s-5);
  font-size: var(--fs-venue); color: var(--muted); font-weight: 400;
}
.head .authors {
  margin: 0 0 var(--s-2);
  font-size: var(--fs-author); line-height: 1.5;
}
.head .affil, .head .corr {
  margin: 0; font-size: var(--fs-affil); color: var(--muted); line-height: 1.5;
}
.head sup { font-size: 0.72em; }

/* The author-visibility toggle and everything it drove (body[data-anon], .anon-note,
   .identity-inline / .anon-inline, scripts/anon.js) were REMOVED, not just hidden.
   Review here is single-anonymous -- authors are visible to reviewers -- so the
   control could not serve a submission, and it was never blinding in any case:
   og:url and og:image carry the author's GitHub username and CSS cannot reach meta
   tags. A control that cannot do what its label implies is worse than no control.
   Real blinding would need anonymous HOSTING. .identity itself stays: it is the
   author block, and tools/check_page.py asserts it is present. */

/* ---- link row: the only button group on the page ----
   Seven resources do not sit in one row at the 720px prose measure -- the four in the
   first tier alone measure 652px against 672px of content width -- so the row is split
   into two TIERS rather than left to wrap wherever the box runs out. The split is
   semantic (the paper and what runs it; then the three data releases), and proximity
   carries it: 24px between tiers against 12px within, a 2:1 ratio, which is what makes
   the second line read as a group instead of an overflow. A tier that does overflow --
   a webfont failure widens the labels, and a 390px viewport stacks everything -- wraps
   INSIDE its own tier, so the grouping survives the degraded case too. */
.links {
  display: flex; flex-direction: column; gap: var(--s-5);
  align-items: center; margin: var(--s-6) 0 var(--s-2);
}
.link-tier {
  display: flex; flex-wrap: wrap; gap: var(--s-3);
  justify-content: center;
}
.btn {
  /* <button> does not inherit font-family, so the BibTeX copy button
     rendered in the browser's default UI face. */
  font-family: inherit;
  display: inline-block; padding: 7px 16px;
  border: 1px solid var(--rule-strong); border-radius: 3px;
  font-size: var(--fs-small); color: var(--text); background: var(--ground);
}
a.btn { color: var(--accent); }
a.btn:hover { border-color: var(--accent); color: var(--accent-hover); text-decoration: none; }
.btn[aria-disabled="true"] {
  color: var(--faint); border-style: dashed; cursor: default;
}
.btn[aria-disabled="true"]:hover { border-color: var(--rule-strong); }
.links-note {
  margin: var(--s-3) 0 0; text-align: center;
  font-size: var(--fs-small); color: var(--muted);
}

/* ---- sections: one plain noun heading, no eyebrow, no rule ---- */
section { padding: 0 0 var(--s-6); }
/* Serif headings at weight 400 with a hairline rule above, which is what gives the
   reference page its sectioning without any chrome. Overriding the UA's bold is the
   point: a bold Computer Modern heading loses the LaTeX look entirely. */
h2 {
  margin: var(--s-7) 0 var(--s-2);
  padding-top: var(--s-5);
  border-top: 2px solid var(--rule);
  font-family: var(--ff-display);
  font-size: var(--fs-h2); font-weight: 400; line-height: 1.2;
}
/* A long single word -- "Acknowledgements" is the offender -- cannot be broken by
   default, so at 200% text zoom on a 320px screen it pushed the whole PAGE into
   horizontal scroll rather than overflowing its own box. <html lang="en"> is set, so
   hyphens:auto breaks it at real syllables. */
h1, h2, h3 { overflow-wrap: break-word; hyphens: auto; }
h3 {
  margin: var(--s-6) 0 var(--s-2);
  font-family: var(--ff-display);
  font-size: var(--fs-h3); font-weight: 400; line-height: 1.3;
}
p { margin: 0 0 var(--s-4); }
.abstract p { text-align: justify; hyphens: auto; line-height: 1.65; }
.note { font-size: var(--fs-small); color: var(--muted); line-height: 1.55; }
.media > .note { max-width: var(--w-prose); }

/* ---- figures: media measure, caption at prose size ---- */
figure { margin: 0 0 var(--s-6); }
/* Hero figures sit INSET from the column at 95% and centred, so the page breathes
   instead of every image running edge to edge. Measured on the reference page: a
   912px video inside a 960px column, with the caption narrower again at 896px. */
figure img { display: block; width: var(--fig-width); height: auto; margin-inline: auto; }
figcaption {
  max-width: var(--w-prose);
  margin: var(--s-4) auto var(--s-2);
  text-align: center;
  font-size: var(--fs-small); color: var(--muted); line-height: 1.4;
}
figcaption strong { color: var(--text); font-weight: 500; }

/* Two figures side by side (the imbalance chart and the frequency/IoU scatter read
   as one argument). Collapses on narrow screens like .triptych does. */
.pair { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--s-4); align-items: end; }
.pair figure { margin: 0; }
@media (max-width: 700px) { .pair { grid-template-columns: 1fr; } }
.media-note { max-width: var(--w-prose); margin-top: var(--s-3); }

.triptych { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-3); }
.triptych .panel { margin: var(--s-2) 0 0; font-size: var(--fs-small); color: var(--muted); text-align: center; }
@media (max-width: 700px) { .triptych { grid-template-columns: 1fr; } }

/* wide diagrams scroll rather than shrink below the legibility floor */
.scrollable { overflow-x: auto; }
.scrollable img { min-width: 640px; }

/* The page's two HTML results tables became one generated figure, and the ~20 rules
   that styled them are gone with them -- including `tbody tr.excluded td`, whose
   forced-colors italics README.md went on advertising as a gated invariant long after
   the last <table> was deleted. Dead selectors are not free: they are what a stale
   sentence points at when someone checks whether it is still true. The predicate
   distinction those rules carried now lives in the chart's axis labels and caption,
   and in tools/check_content.py's manifest comparison.

   Shown only when scripting is off: the viewer's fallback note. */
.js .nojs-note { display: none; }

/* ---- BibTeX ---- */
pre {
  margin: 0; padding: var(--s-4);
  background: var(--ground-sunk); border: 1px solid var(--rule);
  border-radius: 3px; overflow-x: auto;
}
code { font-family: var(--ff-mono); font-size: var(--fs-micro); line-height: 1.5; }
p code, li code { background: var(--ground-sunk); padding: 1px 4px; border-radius: 2px; }

/* ---- viewer ---- */
.viewer-shell {
  border: 1px solid var(--rule); border-radius: 3px; overflow: hidden;
  background: #101216;
}
/* viewer3d.js injects its own canvas and calls renderer.setSize(stage.clientWidth,
   stage.clientHeight), so the STAGE must carry a fixed height. Leaving it
   content-sized makes canvas and stage grow off each other without bound. */
#viewer3d-stage { position: relative; height: 520px; }
#viewer3d-stage canvas { display: block; width: 100%; height: 100%; }
#viewer3d-loading { position: absolute; left: 0; top: 0; color: #C9CDD2; }
/* viewer3d.js toggles these classes; the rules used to live in the stylesheets
   this file replaced, so without them the "Loading scene..." note never hid and
   the failure note rendered unstyled. */
#viewer3d-loading.viewer3d__fallback {
  display: block; max-width: 46ch; padding: var(--s-4);
}
/* Must come AFTER .viewer3d__fallback: equal specificity, so source order decides.
   Without this, a fired watchdog left display:block and .is-hidden did not hide --
   a working viewer kept a "could not load" notice above it. */
#viewer3d-loading.is-hidden { display: none; }
/* No JS means no watchdog to retract a stale "Loading scene..."; the .nojs-note
   beside it carries the truth. `.js` is set by an inline script in <head>, so this
   never flashes when JS is on. Specificity (1,1,1) deliberately outranks the
   .viewer3d__fallback rule above. */
html:not(.js) #viewer3d-loading { display: none; }
#bibtex-copy[hidden] { display: none; }  /* .btn's display would otherwise win */
.viewer3d__fallback-text { margin: 0; color: #C9CDD2; font-size: var(--fs-small); line-height: 1.55; }
.viewer3d__fallback-text a { color: #E8A05C; }
.viewer-bar {
  display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4);
  align-items: center; padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--rule); background: var(--ground);
}
.viewer-bar .group { display: flex; flex-wrap: wrap; gap: var(--s-2); align-items: center; }
.viewer-bar label, .seg-label { font-size: var(--fs-small); color: var(--muted); }
/* The readout ships with empty placeholder spans; show it only once filled, so a
   degraded path renders nothing rather than stray punctuation. */
#viewer3d-stat-row:not([data-ready]) { display: none; }
/* wrap: the four-button View control is wider than a 375px bar once a reader sets a
   large minimum font size, and it was the one child of .viewer-bar that could not
   break -- "Ground truth" fell off the edge. Inert at normal sizes. */
.seg { display: inline-flex; flex-wrap: wrap; border: 1px solid var(--rule-strong); border-radius: 3px; }
/* No overflow:hidden here: it clipped the 2px-offset focus ring on every button
   inside, leaving keyboard users with no visible focus. Round the ends instead. */
.seg button:first-child { border-radius: 2px 0 0 2px; }
.seg button:last-child  { border-radius: 0 2px 2px 0; }
.seg button {
  font: inherit; font-size: var(--fs-small); padding: 5px 12px;
  background: var(--ground); color: var(--text);
  border: 0; border-right: 1px solid var(--rule-strong); cursor: pointer;
}
.seg button:last-child { border-right: 0; }
.seg button[aria-pressed="true"],
.seg button.is-active { background: var(--text); color: var(--ground); }
@media (forced-colors: active) {
  .seg button[aria-pressed="true"],
  .seg button.is-active {
    forced-color-adjust: none;
    background: Highlight; color: HighlightText;
  }
}
.viewer-legend {
  display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4);
  font-size: var(--fs-micro); color: var(--muted); margin-top: var(--s-3);
}
/* ---- footer ---- */
footer {
  margin-top: var(--s-7); padding: var(--s-6) 0 var(--s-8);
  border-top: 1px solid var(--rule);
  font-size: var(--fs-micro); color: var(--muted);
}
footer p { margin: 0 0 var(--s-2); }

/* ---- two breakpoints, as the corpus does ---- */
@media (max-width: 900px) {
  :root { --fs-h1: 2rem; --fs-h2: 1.5rem; }
  .head { padding-top: var(--s-7); }
  #viewer3d-stage { height: 400px; }
}
@media (max-width: 560px) {
  :root { --fs-h1: 1.75rem; --fs-h2: 1.25rem; }
  .prose, .media { padding-inline: var(--s-4); }
  #viewer3d-stage { height: 320px; }
}

/* No page motion by design, so this rule currently guards future additions rather
   than doing work. It does NOT reach the viewer, despite what this comment used to
   say: OrbitControls damping and the Auto-rotate checkbox run from
   requestAnimationFrame, which no CSS duration override can stop, and there is no
   scroll-behavior: smooth here either. Honouring the preference in the viewer needs
   matchMedia('(prefers-reduced-motion: reduce)') in viewer3d.js. */
@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}


/* ---- print ----------------------------------------------------------------
   An academic reader prints this. Without these rules the viewer printed as a
   520px block of dark ink with its controls beside it, the UI-only buttons came
   along, and external links lost their URLs. */
@media print {
  :root { --ground: #fff; }
  body { color: #000; }

  /* Interactive-only chrome: meaningless on paper. */
  .viewer-shell, .viewer-legend, #bibtex-copy, .skip-link { display: none !important; }

  /* Say why the viewer is absent rather than leaving a silent gap. */
  #viewer::after {
    content: "The interactive 3D comparison is not printable. The same comparison appears in the qualitative figure above.";
    display: block; font-size: 10pt; font-style: italic; color: #444;
  }

  /* A printed link a reader cannot follow is a dead end. */
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; word-break: break-all; }

  /* Keep figures whole across page breaks. */
  figure, .triptych { break-inside: avoid; page-break-inside: avoid; }
  h2 { break-after: avoid; page-break-after: avoid; }

  /* Ink economy and legibility on paper. */
  .btn { border: 1px solid #999; }
  pre { border: 1px solid #999; background: transparent; }
}
