/* base.css — loaded on every page: reset, design tokens, site chrome
   (header/nav/footer), and shared typography/layout primitives.
   The Google Fonts (Noto Sans JP / Noto Serif JP) used throughout are
   loaded via a <link> + preconnect in each page's own <head>, not an
   @import here -- @import chains behind this file's own download
   instead of fetching in parallel with it, which was adding ~3s to
   FCP/LCP sitewide (confirmed via Lighthouse on docs/index.html). */

:root {
  --ink: #1a2332;
  --ink-soft: #4a5568;
  --paper: #fdfdfb;
  --paper-alt: #f4f2ec;
  --accent: #2d5a4a;
  --accent-soft: #e8f0ec;
  --warn: #a8503d;
  --line: #d9d5c9;
  --max-width: 1140px;
  --font-serif: Georgia, "Noto Serif JP", serif;
  --font-sans: system-ui, "Noto Sans JP", sans-serif;
}
/* system-ui already has CJK glyph coverage on most Japanese-locale
   systems, so it renders before the fallback to "Noto Sans JP" ever
   triggers -- put Noto Sans JP first specifically on JA pages so it
   actually gets used, rather than whatever the OS's default UI font
   happens to be. --font-serif doesn't need this: Georgia has no CJK
   glyphs at all, so it already falls through to Noto Serif JP. */
:lang(ja) {
  --font-sans: "Noto Sans JP", system-ui, sans-serif;
}
/* Body copy is set in --font-serif (Georgia + Noto Serif JP) by
   default -- on Japanese pages, use the sans stack instead so body
   text actually reads in Noto Sans JP, not Noto Serif JP. */
:lang(ja) body {
  font-family: var(--font-sans);
}
* { box-sizing: border-box; }
/* Keeps anchor-jump targets (#doi-1-X etc.) and scrollIntoView()
   calls (expand_hash_details.js) from landing right under the now-
   sticky header, which would otherwise hide the heading they scroll
   to. */
/* overflow-x:hidden is a deliberate safety net, not just tidiness: any
   single overflowing descendant anywhere on the page (a long unbroken
   URL, a wide table, etc.) otherwise widens the whole document, and
   every margin:0 auto-centered container (header included) then
   centers itself within that inflated width -- visible as the entire
   page shifting right with blank space on the left on a narrow
   viewport, not just a local overflow where it occurs. */
html { scroll-padding-top: 5rem; overflow-x: hidden; }
a {
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  /* Recently-added DOI citation links now show the full URL as their
     own visible text (e.g. https://doi.org/10.5281/zenodo.21658621) --
     a single unbroken "word" with no natural break point, which
     overflows its container on a narrow screen without this. */
  overflow-wrap: break-word;
}
a:hover { border-bottom-color: currentColor; }
a:visited { color: var(--accent); }
/* Prose links (page body / docs body, not nav/footer/sidebar which
   already carry their own fully self-contained link styling) get a
   permanently visible underline instead of a background fill -- a
   colored-box highlight read as too visually loud/sticker-like
   against the page's warm, cream/serif palette. A plain colored
   underline is quieter and still reads clearly at a glance. */
main a,
.docs-content a {
  border-bottom-color: currentColor;
}
body {
  margin: 0;
  font-family: var(--font-serif);
  background: var(--paper);
  color: var(--ink);
  line-height: 1.85;
}
header.site-header {
  background: var(--ink);
  color: var(--paper);
  position: sticky;
  top: 0;
  z-index: 100;
}
.site-header-inner {
  max-width: var(--max-width);
  margin: 0 auto;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 1.4rem;
  padding: 0.85rem 1.5rem;
  /* Queried below instead of the viewport: max-width above caps this
     box at a fixed px past a certain viewport width, so a container
     query is the only way to see the box's own actual width (which a
     viewport media query can't -- it only sees the window, not that
     this box stopped growing). */
  container-type: inline-size;
}
header.site-header a.wordmark-link {
  text-decoration: none;
  flex-shrink: 0;
}
header.site-header .wordmark {
  display: block;
  height: 40px;
  /* Same reasoning as .footer-logo below: the source SVG has no
     width/height of its own, only a viewBox, so width: auto can't
     resolve until the file loads -- this is the page's LCP element
     on most pages, so reserving its box immediately matters even
     more here than for the footer logo. */
  aspect-ratio: 268.031 / 81.049;
  width: 132px;
}
.lang-switch {
  flex-shrink: 0;
  font-family: var(--font-sans);
  font-size: 0.78rem;
  color: #cdd8d1;
  text-decoration: none;
  padding: 0.3rem 0.6rem;
  border: 1px solid #4a5c52;
  border-radius: 3px;
}
.lang-switch:hover { background: rgba(255,255,255,0.1); }

nav.site-nav {
  display: flex;
  flex-wrap: wrap;
  margin-left: auto;
}
nav.site-nav a {
  color: #eef5f0;
  text-decoration: none;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  padding: 0.5rem 0.8rem;
  letter-spacing: 0.02em;
  border-radius: 3px;
}
nav.site-nav a:hover, nav.site-nav a.current { background: rgba(255,255,255,0.16); }

/* Documentation dropdown: "Documentation" itself stays a normal link
   to docs/index.html (clicking the label navigates, same as before);
   a separate small caret button toggles the panel via
   nav_dropdown.js's .is-open class. Hover also previews the panel on
   desktop as a convenience -- .is-open is what actually works on
   touch, where hover doesn't fire reliably. */
.nav-dropdown { position: relative; display: flex; align-items: stretch; }
.nav-dropdown > a {
  color: #eef5f0;
  text-decoration: none;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  padding: 0.5rem 0.3rem 0.5rem 0.8rem;
  letter-spacing: 0.02em;
  border-radius: 3px 0 0 3px;
}
.nav-dropdown > a:hover { background: rgba(255,255,255,0.16); }
.nav-dropdown-toggle {
  background: none;
  border: none;
  color: #eef5f0;
  font-size: 0.7em;
  padding: 0.5rem 0.6rem 0.5rem 0.1rem;
  border-radius: 0 3px 3px 0;
  cursor: pointer;
}
.nav-dropdown-toggle:hover, .nav-dropdown.is-open .nav-dropdown-toggle { background: rgba(255,255,255,0.16); }
.nav-dropdown-panel {
  display: none;
  flex-direction: column;
  gap: 0.9rem;
  padding: 1rem 1.2rem;
  width: 260px;
}
.nav-dropdown:hover .nav-dropdown-panel, .nav-dropdown.is-open .nav-dropdown-panel { display: flex; }
.nav-dropdown-group { display: flex; flex-direction: column; gap: 0.35rem; }
.nav-dropdown-heading {
  font-family: var(--font-sans);
  font-size: 0.82rem;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 0 0 0.2rem;
}
/* nav.site-nav prefixed on purpose: nav.site-nav a's own color rule
   (0,1,2 specificity) otherwise beats a bare .nav-dropdown-panel a
   (0,1,1), leaving these links in the header's light color against
   the panel's light background -- nearly invisible. */
nav.site-nav .nav-dropdown-panel a {
  position: relative;
  color: var(--ink);
  text-decoration: none;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  padding: 0.15rem 0 0.15rem 0.85rem;
  white-space: normal;
}
/* A plain stack of links makes it hard to tell where one item ends
   and the next begins, especially once a label wraps to 2 lines --
   a bullet marks the start of each item regardless of how many lines
   its text takes. A hanging indent (the a's own left padding above,
   with the bullet positioned into that space) keeps wrapped lines
   aligned under the text, not under the bullet. */
nav.site-nav .nav-dropdown-panel a::before {
  content: "\2022";
  position: absolute;
  left: 0;
  top: 0.15rem;
  color: var(--accent);
  opacity: 0.6;
}
nav.site-nav .nav-dropdown-panel a:hover { background: none; text-decoration: underline; }
@container (min-width: 48.76em) {
  .nav-dropdown-panel {
    position: absolute;
    top: 100%;
    /* Roughly centered under the trigger rather than right-anchored:
       a right-anchored panel's right edge stays fixed as it grows,
       so its center drifts further left of the trigger the wider it
       gets -- the "shifted off to the left" look. True 50% centering
       (-50%) ran off the right edge instead, since the trigger sits
       right of the header's true center -- -58% nudges it back left
       just enough to clear that. */
    left: 50%;
    right: auto;
    transform: translateX(-58%);
    z-index: 20;
    /* No gap between trigger and panel (a margin here would create a
       dead zone the pointer crosses on the way down, breaking :hover
       before it reaches the panel and closing it prematurely). */
    margin-top: 0;
    background: var(--paper);
    border: 1px solid var(--line);
    border-radius: 6px;
    box-shadow: 0 6px 20px rgba(0,0,0,0.14);
    /* As doc groups are added, the panel can get taller than the
       viewport on shorter screens -- scroll internally rather than
       running off the bottom edge. */
    max-height: calc(100vh - 4.5rem);
    overflow-y: auto;
    /* A single tall column (the base/mobile layout) got uncomfortably
       long once there were 4 groups -- lay them out side by side
       instead, 2 per row here and 4 per row at 1200px+ below, so the
       panel grows wider rather than taller. */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 1.6rem;
    row-gap: 0.9rem;
    width: 560px;
  }
  .nav-dropdown:hover .nav-dropdown-panel, .nav-dropdown.is-open .nav-dropdown-panel {
    display: grid;
  }
}
@supports not (container-type: inline-size) {
  @media (min-width: 48.76em) {
    .nav-dropdown-panel {
      position: absolute;
      top: 100%;
      left: 50%;
      right: auto;
      transform: translateX(-58%);
      z-index: 20;
      margin-top: 0;
      background: var(--paper);
      border: 1px solid var(--line);
      border-radius: 6px;
      box-shadow: 0 6px 20px rgba(0,0,0,0.14);
      max-height: calc(100vh - 4.5rem);
      overflow-y: auto;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      column-gap: 1.6rem;
      row-gap: 0.9rem;
      width: 560px;
    }
    .nav-dropdown:hover .nav-dropdown-panel, .nav-dropdown.is-open .nav-dropdown-panel {
      display: grid;
    }
  }
}
/* 4 columns once there's room for them. A plain viewport media query
   on purpose, not a container query: .site-header-inner is capped at
   --max-width (1140px) and never grows past it, so a container query
   here could never fire -- what actually matters is how much room
   the viewport itself leaves to the left of the panel's right-
   anchored edge, which only the real viewport width can answer. */
@media (min-width: 1200px) {
  .nav-dropdown-panel {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: 1.8rem;
    width: 1040px;
  }
}

/* Hamburger toggle, hidden by default -- only takes over below the
   breakpoint where 7 nav links + the lang switch no longer fit
   comfortably on one line (nav_toggle.js supplies the interaction).
   The breakpoint itself is in em, not px (see below), so it tracks a
   visitor's browser font-size/zoom setting instead of only a fixed
   physical width -- someone with a larger font size needs the
   hamburger at a wider nominal viewport, or the nav wraps into an
   ugly multi-line mess instead of collapsing cleanly. */
.nav-toggle {
  display: none;
  order: 2;
  background: none;
  border: 1px solid #4a5c52;
  border-radius: 3px;
  color: #eef5f0;
  font-size: 1.05rem;
  line-height: 1;
  padding: 0.4rem 0.7rem;
  cursor: pointer;
  margin-left: auto;
}
.nav-toggle:hover { background: rgba(255,255,255,0.1); }

/* Queried against .site-header-inner's own width (see its
   container-type above), not the viewport: max-width caps that box
   at a fixed px past a certain viewport width, so on a wide screen a
   viewport media query would never see it get any narrower no matter
   how much a visitor's browser font-size/zoom setting inflates the
   nav's rem-based content -- confirmed as a real case (Edge at a 24px
   default font-size still overflowed a 1405px-wide viewport, since
   the header box itself was still capped at 1140px). The container's
   width reflects that cap directly, and em here still scales with
   font-size the same way it would in a @media query, so this single
   rule replaces what used to need a width breakpoint plus a JS
   fallback for the case the breakpoint couldn't catch. */
@container (max-width: 48.75em) { /* 780px at the default 16px browser font size */
  .nav-toggle { display: block; }
  .wordmark-link { order: 0; }
  .lang-switch { order: 1; margin-left: 0; }
  nav.site-nav {
    display: none;
    order: 3;
    flex-basis: 100%;
    flex-direction: column;
    margin-left: 0;
    margin-top: 0.6rem;
  }
  nav.site-nav.is-open { display: flex; }
  nav.site-nav a {
    padding: 0.75rem 0.5rem;
    border-radius: 0;
    border-bottom: 1px solid rgba(255,255,255,0.1);
  }
  nav.site-nav a:last-child { border-bottom: none; }
  .nav-dropdown { flex-wrap: wrap; }
  .nav-dropdown > a {
    flex: 1 1 auto;
    padding: 0.75rem 0.5rem;
    border-radius: 0;
    border-bottom: 1px solid rgba(255,255,255,0.1);
  }
  .nav-dropdown-toggle {
    /* Bigger tap target on touch screens -- the caret glyph itself is
       tiny, so the clickable area is padded well past it rather than
       relying on precise taps. */
    padding: 0.9rem 1.3rem;
    font-size: 1em;
    min-width: 44px;
    border-bottom: 1px solid rgba(255,255,255,0.1);
  }
  .nav-dropdown.is-open > a, .nav-dropdown.is-open .nav-dropdown-toggle { border-bottom-color: transparent; }
  .nav-dropdown-panel {
    flex-basis: 100%;
    width: auto;
    flex-direction: column;
    gap: 0.8rem;
    padding: 0.4rem 0.5rem 0.6rem 1.2rem;
    background: rgba(255,255,255,0.05);
    border-bottom: 1px solid rgba(255,255,255,0.1);
  }
  .nav-dropdown-heading { color: #cdd8d1; }
  nav.site-nav .nav-dropdown-panel a { color: #eef5f0; }
  /* At mobile widths the panel must open/close only via the JS-driven
     .is-open toggle. The base rule above also opens it on :hover,
     which is harmless with a mouse but can get "stuck" on touch
     devices (there is no pointer to leave, so the tapped element can
     stay :hover-matched after JS has already removed .is-open),
     leaving the accordion open with no way to close it. Overriding
     :hover to display:none here (same selector, later in the
     cascade, so it wins) removes that trap. */
  .nav-dropdown:hover .nav-dropdown-panel { display: none; }
  .nav-dropdown.is-open .nav-dropdown-panel { display: flex; }
}
/* Fallback for browsers without container-query support: the old
   viewport-width version, which at least still covers a plain narrow
   window even if not the font-size-driven case above. */
@supports not (container-type: inline-size) {
  @media (max-width: 48.75em) {
    .nav-toggle { display: block; }
    .wordmark-link { order: 0; }
    .lang-switch { order: 1; margin-left: 0; }
    nav.site-nav {
      display: none;
      order: 3;
      flex-basis: 100%;
      flex-direction: column;
      margin-left: 0;
      margin-top: 0.6rem;
    }
    nav.site-nav.is-open { display: flex; }
    nav.site-nav a {
      padding: 0.75rem 0.5rem;
      border-radius: 0;
      border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    nav.site-nav a:last-child { border-bottom: none; }
    .nav-dropdown { flex-wrap: wrap; }
    .nav-dropdown > a {
      flex: 1 1 auto;
      padding: 0.75rem 0.5rem;
      border-radius: 0;
      border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    .nav-dropdown-toggle {
      /* Same larger touch target as the container-query version above. */
      padding: 0.9rem 1.3rem;
      font-size: 1em;
      min-width: 44px;
      border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    .nav-dropdown.is-open > a, .nav-dropdown.is-open .nav-dropdown-toggle { border-bottom-color: transparent; }
    .nav-dropdown-panel {
      flex-basis: 100%;
      width: auto;
      flex-direction: column;
      gap: 0.8rem;
      padding: 0.4rem 0.5rem 0.6rem 1.2rem;
      background: rgba(255,255,255,0.05);
      border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    .nav-dropdown-heading { color: #cdd8d1; }
    nav.site-nav .nav-dropdown-panel a { color: #eef5f0; }
    /* Same touch-stuck-hover fix as the container-query version above. */
    .nav-dropdown:hover .nav-dropdown-panel { display: none; }
    .nav-dropdown.is-open .nav-dropdown-panel { display: flex; }
  }
}

main { max-width: var(--max-width); margin: 0 auto; padding: 0 1.5rem; }

section.page-block { padding: 3.2rem 0; }

.page-label {
  font-family: var(--font-sans);
  font-size: 0.72rem;
  letter-spacing: 0.15em;
  color: var(--accent);
  text-transform: uppercase;
  margin-bottom: 0.6rem;
}
/* Several docs pages use a bare filename (e.g. build_notta_tta_comp_
   db.py) as the h1 -- one long unbroken word with no natural wrap
   point, which was forcing the entire page into horizontal overflow
   on phone-width screens. Applies to all headings as a general
   backstop; harmless when there's nothing that needs to break. */
h1, h2, h3 { overflow-wrap: break-word; }
h1 { font-size: 1.9rem; margin: 0 0 1.4rem; letter-spacing: 0.01em; }
/* Color alone (accent vs. --ink black body text) isn't enough contrast
   to reliably read as "more prominent than body text" -- a muted
   accent color is always going to lose that comparison to solid black
   on a light background, regardless of which hue it is. Bold weight
   and letter-spacing give it a second, color-independent cue. */
h2 { font-size: 1.35rem; font-weight: 700; letter-spacing: 0.01em; margin: 2rem 0 0.7rem; color: var(--accent); }
h3 {
  font-size: 1.12rem;
  font-weight: 700;
  font-family: var(--font-sans);
  letter-spacing: 0.02em;
  color: var(--ink);
  margin: 1.8rem 0 0.5rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--line);
}
p { color: var(--ink-soft); margin: 0 0 1rem; }
.lede { font-size: 1.08rem; color: var(--ink); }

/* Video embeds -- shared by demonstrations.html (full-width) and,
   with the .compact modifier, inline video-plus-explanation pairings
   elsewhere (e.g. SQL Reference walkthroughs). */
/* demonstrations.html: a card grid instead of one long vertical stack
   of full-width videos (which, once the placeholder is replaced by a
   loaded iframe, had no border/background of its own -- just a video
   floating directly on the page, repeated three times). */
.demo-grid {
  display: grid;
  /* min(380px, 100%) instead of a bare 380px -- on a narrow phone
     viewport, the content column itself can be under 380px after
     padding, and a fixed minmax floor wider than that forces
     horizontal overflow/scroll no matter how few columns auto-fit
     drops to. Capping the minimum at 100% of the container means it
     can never be wider than what's actually available. */
  grid-template-columns: repeat(auto-fit, minmax(min(380px, 100%), 1fr));
  gap: 1.8rem;
  margin: 1.8rem 0;
}
.demo-card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1.4rem;
}
.demo-card h2 { margin-top: 0; }

/* Compact video grid for demonstrations.html's SQL-verification
   section: many entry points into Documentation rather than the
   full explanation itself, so each card is much lighter than
   .demo-card above. A <ul> (not a <div>) so filter.js -- which only
   recognizes <li> children -- can search it once it grows past
   MIN_ITEMS. */
.sql-video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
  gap: 1.2rem;
  margin: 1.4rem 0 1.8rem;
  list-style: none;
  padding: 0;
}
.sql-video-grid.single-item {
  grid-template-columns: minmax(220px, 340px);
}
.sql-video-card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 1rem;
}
.sql-video-card h3 { margin: 0.7rem 0 0.3rem; font-size: 0.95rem; border-bottom: none; padding-bottom: 0; }
.sql-video-card p { font-size: 0.82rem; margin: 0 0 0.5rem; }
.sql-video-card .sql-video-model {
  font-size: 0.74rem;
  font-style: italic;
  color: var(--ink-soft);
  margin: 0 0 0.4rem;
}
.sql-video-card .sql-video-link { font-size: 0.82rem; margin: 0; }
.video-block { margin: 1.6rem 0 2.4rem; }
.demo-card .video-block { margin: 0.8rem 0 0; }
.demo-card .video-caption { margin-top: 0.9rem; }
/* No max-width here: the SQL walkthrough pages that use .compact
   place these inline between paragraphs of prose, so the video
   should span the same width as the surrounding text column, not sit
   narrower and misaligned with it. */
.video-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: var(--paper-alt);
  border: 1px dashed var(--line);
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.video-frame iframe { width: 100%; height: 100%; border: 0; }
.video-lazy-play {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
}
.video-lazy-play img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.video-lazy-icon {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 3.4rem;
  height: 3.4rem;
  border-radius: 50%;
  background: rgba(20, 20, 16, 0.72);
  color: #fdfdfb;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.3rem;
  line-height: 1;
  /* nudge the glyph to look centered inside the circle, not the play
     triangle's bounding box (which has extra space on its right) */
  padding-left: 0.2rem;
}
.video-lazy-play:hover .video-lazy-icon { background: var(--accent); }
.video-placeholder {
  font-family: var(--font-sans);
  font-size: 0.85rem;
  color: var(--ink-soft);
  text-align: center;
  padding: 1rem;
}
/* New DB / Old DB badge -- shared by docs.css's SQL walkthrough pages
   and demonstrations.html's SQL-video grid. */
.db-tag {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  padding: 0.15rem 0.5rem;
  border-radius: 3px;
  margin-right: 0.5rem;
  vertical-align: middle;
}
.db-tag-old { background: var(--paper); border: 1px solid var(--ink-soft); color: var(--ink-soft); }
.db-tag-new { background: var(--accent-soft); border: 1px solid var(--accent); color: var(--accent); }

.video-caption {
  font-family: var(--font-sans);
  font-size: 0.85rem;
  color: var(--ink-soft);
  margin-top: 0.6rem;
}

/* video_lazy.js: the demonstrations.html lightbox */
.video-lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 3rem 2rem;
  background: rgba(10, 12, 9, 0.82);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  opacity: 0;
  transition: opacity 0.25s ease;
}
.video-lightbox.is-visible { opacity: 1; }
.video-lightbox-inner {
  position: relative;
  width: 100%;
  max-width: 1100px;
  transform: scale(0.96);
  transition: transform 0.25s ease;
}
.video-lightbox.is-visible .video-lightbox-inner { transform: scale(1); }
.video-lightbox-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: 8px;
  overflow: hidden;
  box-shadow:
    0 0 0 1px rgba(127, 184, 156, 0.3),
    0 0 60px rgba(127, 184, 156, 0.15),
    0 25px 70px rgba(0, 0, 0, 0.55);
}
.video-lightbox-frame iframe { display: block; width: 100%; height: 100%; border: 0; }
.video-lightbox-close {
  position: absolute;
  top: -3.2rem;
  right: 0;
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.25);
  color: #fdfdfb;
  font-size: 1.3rem;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.2s ease;
}
.video-lightbox-close:hover {
  background: var(--accent);
  border-color: var(--accent);
  transform: rotate(90deg);
}
footer.site-footer {
  text-align: center;
  padding: 2rem 1rem 3rem;
  font-family: var(--font-sans);
  font-size: 0.75rem;
  color: #9a9484;
  border-top: 1px solid var(--line);
  margin-top: 2rem;
}
/* Unlike the header, the footer follows the page's own light/dark
   background (no fixed-dark override), so it needs the opposite pair
   of logo files from the header: the standard dark-text mark for
   light mode, the header's light-text variant for dark mode. Both
   images are always present; CSS just picks which one paints,
   following prefers-color-scheme the same way the rest of the site's
   theming does (no manual light/dark toggle exists here). */
.footer-logo {
  display: block;
  height: 28px;
  /* Not width: auto -- the source SVGs declare only a viewBox, no
     width/height, so the browser has no way to resolve "auto" until
     the file has actually loaded, and reserves no space until then
     (a real, measured CLS contributor: 0.001-0.539 depending on the
     page). aspect-ratio is known immediately, matching the SVGs'
     own viewBox (268.031 x 81.049). */
  aspect-ratio: 268.031 / 81.049;
  width: 93px;
  margin: 0 auto 1rem;
}
.footer-logo-dark { display: none; }
@media (prefers-color-scheme: dark) {
  .footer-logo-light { display: none; }
  .footer-logo-dark { display: block; }
}
.footer-links {
  margin-bottom: 0.7rem;
  font-size: 0.8rem;
}
.footer-links a {
  color: var(--accent);
  text-decoration: none;
  margin: 0 0.6rem;
}
.footer-links a:hover { text-decoration: underline; }
.footer-links a.current { color: #9a9484; text-decoration: none; cursor: default; }

/* Two-column hero (lede text + illustrative screenshot), used by
   index.html and publications.html. */
.hero-grid {
  display: flex;
  gap: 2.2rem;
  align-items: flex-start;
  flex-wrap: wrap;
}
.hero-text { flex: 1 1 380px; min-width: 0; }
.hero-image {
  flex: 1 1 300px;
  max-width: 420px;
  min-width: 0;
  margin: 0;
}
.hero-image img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: 6px;
}
.hero-image figcaption {
  font-family: var(--font-sans);
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--ink-soft);
  margin-top: 0.6rem;
}

/* Pre-collapse data-collapse="N" lists via CSS, matching what
   filter.js's render() does on load anyway -- without this, the
   browser paints the full, uncollapsed list first (since nothing in
   the plain HTML hides the extra items) and only shrinks it once the
   deferred script runs, which is a real, visible layout shift
   whenever that list sits near the initial viewport (confirmed via
   Lighthouse CLS: 0.539 on docs/index.html's 12-item SQL walkthroughs
   list collapsing to 6). Gated on html.js -- set by a tiny synchronous
   script at the top of <head>, before body content is parsed -- so a
   no-JS visitor still sees every item, matching filter.js's own
   progressive-enhancement contract. Only the two data-collapse values
   actually used sitewide (6 and 9) are covered; a future list using a
   different N would need a matching rule added here too. */
html.js [data-collapse="6"] > li:nth-child(n+7) { display: none; }
html.js [data-collapse="9"] > li:nth-child(n+10) { display: none; }

/* Reserves the search box's height before filter.js ever runs, so
   dropping the box in later (see filter.js) doesn't push the list
   below it down -- a real, measured CLS contributor on pages that
   lack this slot. Gated on html.js (see the data-collapse rule
   above) so a no-JS visitor, who never gets a search box at all,
   doesn't see blank reserved space instead. min-height is an
   approximation of .list-filter-wrap's own rendered height at the
   default (unwrapped) single-line layout -- not pixel-exact at
   every viewport width (the row can wrap onto two lines on very
   narrow screens), but eliminates the bulk of the shift. flow-root
   stops the box's own bottom margin from collapsing through this
   element, which would otherwise under-reserve by that amount. */
html.js .list-filter-slot {
  display: flow-root;
  min-height: 3.6rem;
}

/* filter.js search box, shared by any page using data-filterable */
.list-filter-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem;
  margin: 0 0 0.9rem;
}
.list-filter-box {
  position: relative;
  flex: 1 1 160px;
  max-width: 320px;
}
.list-filter {
  display: block;
  width: 100%;
  padding: 0.5rem 1.8rem 0.5rem 0.8rem;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--paper);
  color: var(--ink);
}
.list-filter:focus { outline: none; border-color: var(--accent); }
/* Custom clear button instead -- Firefox has no native type="search"
   cancel control at all, so this keeps the behavior consistent. */
.list-filter::-webkit-search-cancel-button { display: none; }
.list-filter-clear {
  position: absolute;
  right: 0.4rem;
  top: 50%;
  transform: translateY(-50%);
  width: 1.3rem;
  height: 1.3rem;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-soft);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
}
.list-filter-clear:hover { background: var(--paper-alt); color: var(--accent); }
.list-sort-toggle {
  font-family: var(--font-sans);
  font-size: 0.82rem;
  padding: 0.45rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--paper);
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
  flex-shrink: 0;
}
.list-sort-toggle:hover { border-color: var(--accent); color: var(--accent); }
.list-filter-count {
  font-family: var(--font-sans);
  font-size: 0.78rem;
  color: var(--ink-soft);
}
.list-show-more {
  display: block;
  width: 100%;
  margin: -0.6rem 0 1.4rem;
  padding: 0.6rem;
  font-family: var(--font-sans);
  font-size: 0.82rem;
  color: var(--accent);
  background: var(--paper-alt);
  border: 1px dashed var(--line);
  border-radius: 4px;
  cursor: pointer;
  text-align: center;
}
.list-show-more:hover { border-color: var(--accent); }

@media (prefers-color-scheme: dark) {
  :root {
    --ink: #e8e6df;
    --ink-soft: #a8a496;
    --paper: #1c1f1a;
    --paper-alt: #24281f;
    --accent: #7fb89c;
    --accent-soft: #202a24;
    --warn: #d98b76;
    --line: #3a3d33;
  }
  header.site-header { background: #10130f; }
}

/* analytics_consent.js: cookie consent banner (built by JS, not
   present in any page's HTML) */
.cookie-consent {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 200;
  background: var(--ink);
  padding: 1rem 1.5rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.8rem 1.5rem;
  font-family: var(--font-sans);
  font-size: 0.85rem;
  box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.2);
}
.cookie-consent p {
  margin: 0;
  max-width: 640px;
  color: #eef5f0;
  line-height: 1.6;
}
.cookie-consent p a { color: #cdd8d1; }
.cookie-consent-actions { display: flex; gap: 0.6rem; flex-shrink: 0; }
.cookie-consent button {
  font-family: var(--font-sans);
  font-size: 0.82rem;
  padding: 0.5rem 1.1rem;
  border-radius: 4px;
  cursor: pointer;
  border: 1px solid transparent;
}
.cookie-consent-accept {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}
.cookie-consent-decline {
  background: transparent;
  color: #eef5f0;
  border-color: #4a5c52;
}
.cookie-consent-decline:hover { background: rgba(255, 255, 255, 0.1); }
