/* ==========================================================================
   base.css — reset, typography, and shared UI primitives.
   Primitives (use these, don't re-invent): .container .section .section-head
   .btn .chip .badge .tag .panel .ticks .link .kbd .mono .eyebrow .caret
   .dot-live .sr-only .reveal .serif-em .grad-text .icon .noscript
   ========================================================================== */

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

html {
  overflow-x: hidden; /* fallback for Safari < 16 (no overflow: clip) */
  overflow-x: clip; /* safety net: one overflowing child must never widen the mobile layout viewport */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scroll-behavior: smooth;
  /* THE anchor offset (only one; no per-section scroll-margin): #hash jumps and scrollIntoView clear the fixed nav */
  scroll-padding-top: calc(var(--nav-h) + 12px);
  background: var(--bg);
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: var(--fs-md);
  line-height: var(--lh-body);
  font-feature-settings: "ss01", "cv11";
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}

img,
svg,
video,
canvas {
  display: block;
  max-width: 100%;
}

h1,
h2,
h3,
h4,
p,
ul,
ol,
figure,
blockquote,
dl,
dd {
  margin: 0;
}

ul[role="list"],
ol[role="list"],
.list-reset {
  list-style: none;
  padding: 0;
  margin: 0;
}

a {
  color: inherit;
  text-decoration: none;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

::selection {
  background: rgba(var(--accent-rgb), 0.32);
  color: var(--text);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

/* slim scrollbars */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line-strong) transparent;
}
::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-thumb {
  background: var(--line-strong);
  border-radius: 10px;
  border: 2px solid var(--bg);
}
::-webkit-scrollbar-track {
  background: transparent;
}

/* Chinese prose breaks between words, never inside one (目前, 算法, 博士学位). word-break: auto-phrase is the
   standards route (Chrome/Edge 119+, so far it segments Japanese only); every engine gains strict line-break rules (no line
   starts with punctuation). For Chinese, core.js also inserts <wbr> at word boundaries (see .is-phrased below).
   Deliberately no text-wrap here: the modules choose balance / pretty per element. The hero lede is phrased by hero.js.
   Not applied to pre / code. */
:root[data-lang="zh"] :is(p, li, dd, figcaption, .section-head__kicker):not(.hero__lede) {
  word-break: auto-phrase;
  line-break: strict;
}
/* core.js (Site phrasing) marks prose it has segmented with <wbr>: break only at those (a last-resort break-word keeps a
   very long unbroken run from overflowing) */
:root[data-lang="zh"] .is-phrased:not(.hero__lede) {
  word-break: keep-all;
  overflow-wrap: break-word;
}

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */
.container {
  width: 100%;
  max-width: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow {
  max-width: calc(var(--container-narrow) + var(--gutter) * 2);
}

main {
  position: relative;
  z-index: var(--z-content);
}
/* <main tabindex="-1"> receives focus from the skip link; it is not an interactive control, so no ring */
main:focus {
  outline: none;
}

.section {
  position: relative;
  padding-block: var(--section-pad);
}

.skip-link {
  position: fixed;
  top: 10px;
  left: 10px;
  z-index: var(--z-skip);
  padding: 10px 16px;
  border-radius: var(--r-sm);
  background: var(--accent);
  color: var(--text-inverse);
  font-weight: 600;
  transform: translateY(-160%);
  transition: transform var(--dur-2) var(--ease);
}
.skip-link:focus {
  transform: none;
}

/* --------------------------------------------------------------------------
   Typography helpers
   -------------------------------------------------------------------------- */
.mono {
  font-family: var(--font-mono);
  font-feature-settings: "ss01", "zero";
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-3);
}

.muted {
  color: var(--text-2);
}

/* the editorial accent: italic serif inside a sans headline */
.serif-em {
  font-family: var(--font-serif);
  font-style: italic;
  font-weight: 400;
  letter-spacing: -0.01em;
}
:root[data-lang="zh"] .serif-em {
  font-family: var(--font-sans);
  font-style: normal;
  font-weight: 600;
}

.grad-text {
  background: var(--accent-grad);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}

.sr-only {
  position: absolute !important;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.icon {
  display: inline-block;
  flex: none;
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.2em;
}
[data-icon] {
  display: inline-flex;
  align-items: center;
}

/* blinking terminal caret */
.caret {
  display: inline-block;
  width: 0.55em;
  height: 1.05em;
  margin-left: 0.15em;
  vertical-align: -0.16em;
  background: var(--accent);
  animation: caret-blink 1.05s steps(1) infinite;
}
@keyframes caret-blink {
  50% {
    opacity: 0;
  }
}

/* status dot: static (no ping ring) in both themes; soft glow in light, the single accent hue in dark */
.dot-live {
  display: inline-block;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ok);
}
:root[data-theme="light"] .dot-live {
  box-shadow: 0 0 10px var(--ok);
}
:root:not([data-theme="light"]) .dot-live {
  background: var(--accent);
}

/* --------------------------------------------------------------------------
   Inline link with animated underline
   -------------------------------------------------------------------------- */
.link {
  position: relative;
  color: var(--text);
  background: linear-gradient(currentColor, currentColor) 0 100% / 100% 1px no-repeat;
  padding-bottom: 1px;
  transition: color var(--dur-2) var(--ease), background-size var(--dur-3) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .link:hover {
    color: var(--accent);
    background-size: 100% 2px;
  }
}
.link--accent {
  color: var(--accent);
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */
.btn {
  --_bg: transparent;
  --_bd: var(--line-strong);
  --_fg: var(--text);
  position: relative;
  isolation: isolate;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6em;
  padding: 0.82em 1.3em;
  border: 1px solid var(--_bd);
  border-radius: var(--r-pill);
  background: var(--_bg);
  color: var(--_fg);
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: 1;
  letter-spacing: 0.01em;
  white-space: nowrap;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  transition: transform var(--dur-2) var(--ease), background var(--dur-2), border-color var(--dur-2),
    box-shadow var(--dur-2), color var(--dur-2);
}
/* Hover lift without a strobe: the button moves up 2px on :hover, which would drop a pointer resting on its
   bottom 2 px out of the hit box (hover off, drop back, hover on, repeat). A transparent strip below the box
   (::after, sits behind the content, counts as part of the button for hit testing) keeps the pointer inside.
   Pattern for any lifted element: extend the hit area like this, or lift an inner element / key the lift to a
   static wrapper's :hover. Keep the strip taller than the lift. */
@media (hover: hover) and (pointer: fine) {
  .btn::after {
    content: "";
    position: absolute;
    inset: 0 0 -4px 0;
    z-index: -1;
    border-radius: inherit;
  }
  .btn:hover {
    transform: translateY(-2px);
  }
}
.btn:active {
  transform: translateY(0) scale(0.98);
}

.btn--primary {
  --_bg: var(--accent);
  --_bd: transparent;
  --_fg: var(--text-inverse);
  font-weight: 600;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.1) inset, 0 10px 30px -10px rgba(var(--accent-rgb), 0.7);
}
@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover {
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18) inset, 0 16px 42px -10px rgba(var(--accent-rgb), 0.85);
  }
}
/* dark: flat accent fill, dark text, no halo (light keeps its soft shadow) */
:root:not([data-theme="light"]) .btn--primary,
:root:not([data-theme="light"]) .btn--primary:hover {
  box-shadow: none;
}

.btn--ghost {
  --_bg: rgba(255, 255, 255, 0.02);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
@media (hover: hover) and (pointer: fine) {
  .btn--ghost:hover {
    --_bd: var(--line-accent);
    --_bg: rgba(var(--accent-rgb), 0.08);
  }
}

.btn--sm {
  padding: 0.6em 0.95em;
  font-size: var(--fs-xs);
}
.btn--icon {
  width: 2.7em;
  height: 2.7em;
  padding: 0;
}
.btn .icon {
  width: 1.1em;
  height: 1.1em;
}

/* --------------------------------------------------------------------------
   Chips (toggle filters) — set aria-pressed="true" for the active state
   -------------------------------------------------------------------------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
  padding: 0.5em 0.95em;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, 0.02);
  color: var(--text-2);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.03em;
  line-height: 1;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
  transition: color var(--dur-1), border-color var(--dur-2), background var(--dur-2), box-shadow var(--dur-2);
}
@media (hover: hover) and (pointer: fine) {
  .chip:hover {
    color: var(--text);
    border-color: var(--line-strong);
  }
}
.chip[aria-pressed="true"],
.chip.is-active {
  color: var(--accent);
  border-color: var(--line-accent);
  background: rgba(var(--accent-rgb), 0.1);
  box-shadow: var(--glow-sm);
}
.chip__count {
  color: var(--text-3);
  font-size: var(--fs-micro);
}
.chip[aria-pressed="true"] .chip__count {
  color: inherit; /* no opacity multiplier: it dropped the pressed count below AA in light */
}

/* --------------------------------------------------------------------------
   Badge (venue / status) — color via --badge: "r, g, b"
   -------------------------------------------------------------------------- */
.badge {
  --badge: var(--venue-preprint);
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
  padding: 0.36em 0.7em;
  border: 1px solid rgba(var(--badge), 0.5);
  border-radius: var(--r-xs);
  background: rgba(var(--badge), 0.12);
  color: rgb(var(--badge));
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: none; /* venue names keep their real casing: NeurIPS, arXiv, MedIA */
  line-height: 1;
  white-space: nowrap;
}
/* status badges are shouted: NEW / NOW / ORAL / AWARD (and the news category tag) */
.badge--new,
.badge--award,
.badge--now,
.news__tag {
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.badge--neurips {
  --badge: var(--venue-neurips);
}
.badge--eccv {
  --badge: var(--venue-eccv);
}
.badge--cvpr {
  --badge: var(--venue-cvpr);
}
.badge--journal {
  --badge: var(--venue-journal);
}
.badge--iccv {
  --badge: var(--venue-iccv);
}
.badge--iclr {
  --badge: var(--venue-iclr);
}
.badge--aaai {
  --badge: var(--venue-aaai);
}
.badge--acmmm {
  --badge: var(--venue-acmmm);
}
.badge--preprint {
  --badge: var(--venue-preprint);
}
.badge--new {
  --badge: var(--accent-3-rgb);
}
.badge--award {
  --badge: var(--accent-3-rgb);
}
/* dark: softer outline (text stays the full venue hue: >= 4.5:1 on its 12% tint, also under the hover spotlight) */
:root:not([data-theme="light"]) .badge {
  border-color: rgba(var(--badge), 0.36);
}
/* light theme: pull the text ink toward --text so any hue clears 4.5:1 on its own 12% tint (fills/borders keep the hue) */
@supports (color: color-mix(in srgb, red, blue)) {
  :root[data-theme="light"] .badge {
    color: color-mix(in srgb, rgb(var(--badge)) 86%, var(--text));
  }
}

.tag {
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  letter-spacing: 0.02em;
  color: var(--text-3);
}
.tag::before {
  content: "#";
  opacity: 0.6;
  margin-right: 1px;
}

.kbd {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 0.22em 0.5em;
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: var(--r-xs);
  background: rgba(255, 255, 255, 0.04);
  color: var(--text-2);
  font-family: var(--font-mono);
  font-size: var(--fs-micro);
  line-height: 1;
}

/* --------------------------------------------------------------------------
   Panel — glass card with hairline border and a cursor-following spotlight.
   fx.js sets --mx / --my on hover. Add .ticks for viewfinder corner marks.
   -------------------------------------------------------------------------- */
.panel {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--panel);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  transition: border-color var(--dur-2), background var(--dur-2), box-shadow var(--dur-3) var(--ease),
    transform var(--dur-3) var(--ease);
}
.panel::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(460px circle at var(--mx, 50%) var(--my, 0%), rgba(var(--accent-rgb), 0.11), transparent 62%);
  transition: opacity var(--dur-2);
}
/* dark: the hover spotlight is a whisper, not a coloured glow */
:root:not([data-theme="light"]) .panel::before {
  background: radial-gradient(460px circle at var(--mx, 50%) var(--my, 0%), rgba(var(--accent-rgb), 0.055), transparent 62%);
}
@media (hover: hover) and (pointer: fine) {
  .panel:hover {
    border-color: var(--line-strong);
  }
  .panel:hover::before {
    opacity: 1;
  }
}

.ticks::after {
  content: "";
  position: absolute;
  inset: 9px;
  z-index: 1;
  pointer-events: none;
  opacity: 0.5;
  transition: opacity var(--dur-2), inset var(--dur-3) var(--ease);
  background:
    linear-gradient(var(--accent), var(--accent)) top left / 11px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) top left / 1px 11px no-repeat,
    linear-gradient(var(--accent), var(--accent)) top right / 11px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) top right / 1px 11px no-repeat,
    linear-gradient(var(--accent), var(--accent)) bottom left / 11px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) bottom left / 1px 11px no-repeat,
    linear-gradient(var(--accent), var(--accent)) bottom right / 11px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) bottom right / 1px 11px no-repeat;
}
@media (hover: hover) and (pointer: fine) {
  .ticks:hover::after {
    opacity: 0.95;
    inset: 6px;
  }
}
/* dark: quieter viewfinder marks */
:root:not([data-theme="light"]) .ticks::after {
  opacity: 0.32;
}
@media (hover: hover) and (pointer: fine) {
  :root:not([data-theme="light"]) .ticks:hover::after {
    opacity: 0.6;
  }
}

/* --------------------------------------------------------------------------
   Section heading block (markup lives in index.html)
   -------------------------------------------------------------------------- */
.section-head {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  column-gap: var(--s-4);
  row-gap: var(--s-3);
  margin-bottom: clamp(36px, 6vw, 72px);
}
.section-head__idx {
  color: var(--accent);
  font-size: var(--fs-xs);
  letter-spacing: var(--tracking-wide);
}
.section-head__rule {
  height: 1px;
  background: linear-gradient(90deg, var(--line-strong), transparent);
}
.section-head__title {
  grid-column: 1 / -1;
  font-size: var(--fs-h2);
  font-weight: 600;
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-tight);
}
.section-head__title .serif-em {
  color: var(--accent);
  padding-right: 0.04em;
}
.section-head__kicker {
  grid-column: 1 / -1;
  max-width: 60ch;
  color: var(--text-2);
  font-size: var(--fs-lg);
  text-wrap: balance; /* no orphaned last word ('AI.') */
}

/* --------------------------------------------------------------------------
   Reveal-on-scroll. The hidden state is opt-in: it only applies once fx.js has
   armed its observers and added html.fx-ready (end of its init), so a missing or
   crashed fx.js / core.js / data.js can never leave the page blank. As a second
   net, the inline head script adds html.reveal-failsafe after 5 s without
   fx-ready, which forces everything visible. fx.js adds .is-in via
   IntersectionObserver. Stagger children with style="--i:N".
   -------------------------------------------------------------------------- */
html.js.fx-ready .reveal {
  opacity: 0;
  transform: translate3d(0, 12px, 0);
  transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out);
  transition-delay: calc(var(--i, 0) * 70ms); /* fallback: engines without min() */
  transition-delay: calc(min(var(--i, 0), 4) * 70ms); /* a wave, not a queue: the late items of a long list start at most ~0.28 s late */
  will-change: opacity, transform;
}
html.js.fx-ready .reveal.is-in {
  opacity: 1;
  transform: none;
  will-change: auto;
}
html.reveal-failsafe .reveal {
  opacity: 1 !important;
  transform: none !important;
  filter: none !important;
  transition: none !important;
}

/* --------------------------------------------------------------------------
   Fixed WebGL backdrop canvas (owned by hero.js / hero-gl.js)
   -------------------------------------------------------------------------- */
.gl {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  height: 100lvh;
  z-index: var(--z-gl);
  pointer-events: none;
  touch-action: pan-y;
}

/* --------------------------------------------------------------------------
   Reduced motion
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    transition-delay: 0ms !important;
  }
  html.js.fx-ready .reveal {
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
  }
  .caret {
    animation: none;
  }
}

/* In-page pause (Site.motion, WCAG 2.2.2): same effect as the OS reduced-motion block above, driven by html.motion-off
   (toggled by core.js, restored before first paint by the inline <head> script). */
html.motion-off {
  scroll-behavior: auto;
}
html.motion-off *,
html.motion-off *::before,
html.motion-off *::after {
  animation-duration: 0.001ms !important;
  animation-delay: 0ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.001ms !important;
  transition-delay: 0ms !important;
}
html.motion-off.js.fx-ready .reveal {
  opacity: 1 !important;
  transform: none !important;
  filter: none !important;
}
html.motion-off .caret {
  animation: none !important;
}

/* --------------------------------------------------------------------------
   Touch targets: on coarse pointers every control reaches --tap (44px)
   -------------------------------------------------------------------------- */
@media (pointer: coarse) {
  .btn,
  .btn--sm,
  .chip {
    min-height: var(--tap);
  }
  .btn--icon {
    min-width: var(--tap);
  }
  /* inline text links: enlarge the hit area without changing layout (WCAG 2.5.8 spacing) */
  .link::after {
    content: "";
    position: absolute;
    inset: -10px -4px;
  }
}

/* --------------------------------------------------------------------------
   No JavaScript: the sections are rendered client-side, so hide their empty
   shells, the dead controls and the anchors that point at them. The <noscript>
   summary (.noscript) sits directly under the hero.
   -------------------------------------------------------------------------- */
html.no-js .section,
html.no-js .hero__cta,
html.no-js .hero__scroll,
html.no-js .hero__hud,
html.no-js .hero__typed,
html.no-js .hero__name::after,
html.no-js [data-cmdk-open],
html.no-js #site-footer:empty {
  display: none !important;
}
html.no-js .hero {
  min-height: 0;
  padding-bottom: 8px;
}
.noscript {
  position: relative;
  width: 100%;
  max-width: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding: 8px var(--gutter) var(--s-9);
  color: var(--text);
  line-height: var(--lh-body);
}
.noscript > * {
  max-width: 760px;
}
.noscript p {
  margin-top: var(--s-4);
}
.noscript a {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}
.noscript__note {
  color: var(--text-2);
  font-size: var(--fs-sm);
}

/* --------------------------------------------------------------------------
   Small screens
   -------------------------------------------------------------------------- */
@media (max-width: 520px) {
  :root {
    --section-pad: clamp(48px, 7.5vh, 68px);
  }
  .section-head {
    margin-bottom: 28px;
  }
}

/* --------------------------------------------------------------------------
   Forced colors (Windows High Contrast): selection must not depend on color
   alone. Every "on" state becomes a Highlight-filled control with a heavier
   border; panels and chips get an explicit hairline.
   -------------------------------------------------------------------------- */
@media (forced-colors: active) {
  .panel,
  .chip,
  .badge,
  .kbd,
  .btn {
    border: 1px solid CanvasText;
  }
  [aria-pressed="true"],
  [aria-selected="true"],
  [aria-current]:not([aria-current="false"]),
  .chip.is-active,
  .is-active {
    border: 2px solid Highlight !important; /* !important: module rules with higher specificity must not win here */
    background: Highlight !important;
    color: HighlightText !important;
    box-shadow: none !important;
    forced-color-adjust: none;
  }
  [aria-pressed="true"] *,
  [aria-selected="true"] *,
  [aria-current]:not([aria-current="false"]) *,
  .is-active * {
    color: HighlightText !important;
  }
  /* non-color cue for toggle chips */
  .chip[aria-pressed="true"]::before {
    content: "\2713";
  }
  .section-head__rule {
    background: CanvasText;
  }
  .caret,
  .dot-live {
    forced-color-adjust: none;
    background: Highlight;
  }
  :focus-visible {
    outline: 2px solid Highlight;
  }
}
