:root {
  --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Apple SD Gothic Neo", "Malgun Gothic", sans-serif;
  --font-monospace: "JetBrains Mono", "Cascadia Code", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
  --font-size-editor: 16px;
  --line-height-relaxed: 1.7;
  --font-weight-semibold: 600;

  --content-max-width: 48rem;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-6: 24px;
  --sp-8: 32px;
  --radius-m: 6px;

  --bg-primary: #ffffff;
  --bg-secondary: #f6f7f8;
  --text-normal: #2a2d34;
  --text-muted: #6a707c;
  --text-faint: #868d97;
  --accent: #4a6cf0;
  --accent-hover: #3a5be0;
  --border: rgba(0, 0, 0, 0.1);
  --border-strong: rgba(0, 0, 0, 0.18);
  /* See DARK_TOKENS above for why this outruns the callout backgrounds' 8%. */
  --sidebar-active-bg: color-mix(in srgb, var(--accent) 16%, transparent);
  /* Callout accents — same names as the consuming app's tokens so injection stays drop-in. */
  --callout-note: #2f6fed;    --callout-note-bg: rgba(47, 111, 237, 0.08);
  --callout-tip: #0a8a57;     --callout-tip-bg: rgba(10, 138, 87, 0.08);
  --callout-warning: #b45309; --callout-warning-bg: rgba(180, 83, 9, 0.08);
  --callout-danger: #c0392b;  --callout-danger-bg: rgba(192, 57, 43, 0.08);
  --callout-quote: #6a707c;   --callout-quote-bg: rgba(106, 112, 124, 0.08);
}

/* The system-preference path: applies unless a script has explicitly asked
   for light instead. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {

    --bg-primary: #1e1f23;
    --bg-secondary: #181a1d;
    --text-normal: #dadde2;
    --text-muted: #9aa0aa;
    --text-faint: #6b7178;
    --accent: #6d8bff;
    --accent-hover: #859dff;
    --border: rgba(255, 255, 255, 0.1);
    --border-strong: rgba(255, 255, 255, 0.18);
    /* Derived from --accent, not a fixed hex, so a consumer who overrides only
       --accent (the one customization path README.md documents) still gets a
       matching tint instead of a leftover default-blue wash. Higher than the
       callout backgrounds' 12% below despite sharing the same color-mix
       technique: a callout carries three other signals (an icon, a bold
       title, a left border) that make its own background tint supporting
       texture, while this tint is the *only* thing (with color+weight,
       already shown insufficient alone — see the commit that added this
       property) telling a reader apart from every sibling entry in a long,
       deeply nested tree. The same value that reads as "enough" next to an
       icon reads as "did anything change?" carrying that alone. */
    --sidebar-active-bg: color-mix(in srgb, var(--accent) 22%, transparent);
    /* Callout accents — same names as the consuming app's tokens so injection stays drop-in. */
    --callout-note: #6d8bff;    --callout-note-bg: rgba(109, 139, 255, 0.12);
    --callout-tip: #34d399;     --callout-tip-bg: rgba(52, 211, 153, 0.12);
    --callout-warning: #f0b350; --callout-warning-bg: rgba(240, 179, 80, 0.12);
    --callout-danger: #ff6b5e;  --callout-danger-bg: rgba(255, 107, 94, 0.12);
    --callout-quote: #9aa0aa;   --callout-quote-bg: rgba(154, 160, 170, 0.12);
  }
}

/* The explicit-override path: applies regardless of system preference. Higher
   specificity than the plain ":root" block above (an attribute selector added
   to it), so source order does not matter here. */
:root[data-theme="dark"] {

    --bg-primary: #1e1f23;
    --bg-secondary: #181a1d;
    --text-normal: #dadde2;
    --text-muted: #9aa0aa;
    --text-faint: #6b7178;
    --accent: #6d8bff;
    --accent-hover: #859dff;
    --border: rgba(255, 255, 255, 0.1);
    --border-strong: rgba(255, 255, 255, 0.18);
    /* Derived from --accent, not a fixed hex, so a consumer who overrides only
       --accent (the one customization path README.md documents) still gets a
       matching tint instead of a leftover default-blue wash. Higher than the
       callout backgrounds' 12% below despite sharing the same color-mix
       technique: a callout carries three other signals (an icon, a bold
       title, a left border) that make its own background tint supporting
       texture, while this tint is the *only* thing (with color+weight,
       already shown insufficient alone — see the commit that added this
       property) telling a reader apart from every sibling entry in a long,
       deeply nested tree. The same value that reads as "enough" next to an
       icon reads as "did anything change?" carrying that alone. */
    --sidebar-active-bg: color-mix(in srgb, var(--accent) 22%, transparent);
    /* Callout accents — same names as the consuming app's tokens so injection stays drop-in. */
    --callout-note: #6d8bff;    --callout-note-bg: rgba(109, 139, 255, 0.12);
    --callout-tip: #34d399;     --callout-tip-bg: rgba(52, 211, 153, 0.12);
    --callout-warning: #f0b350; --callout-warning-bg: rgba(240, 179, 80, 0.12);
    --callout-danger: #ff6b5e;  --callout-danger-bg: rgba(255, 107, 94, 0.12);
    --callout-quote: #9aa0aa;   --callout-quote-bg: rgba(154, 160, 170, 0.12);
}

/* --- caller tokens --- */
/* Only what differs from canopy's defaults; everything else is inherited. */
:root { --accent: #0a7c5a; --accent-hover: #096a4d; }
@media (prefers-color-scheme: dark) {
  :root { --accent: #4ecfa2; --accent-hover: #6fdcb5; }
}

/* Search UI — pairs with assets/search.js and the `.canopy-search` form
   canopy's shell emits. Loaded only when a build carries both, so a site
   with no search never pays for this either. Reuses canopy's own tokens
   (--sp-*, --border, --radius-m, --bg-primary, --text-*) rather than
   introducing new ones. */

.canopy-search {
  position: relative;
}

/* Reserve room so the shortcut badge below doesn't sit on top of typed
   text — canopy's own input padding only accounts for its search icon on
   the left. */
.canopy-search input[type="search"] {
  padding-right: 3rem;
}

/* Advertises Ctrl+K/Cmd+K (search.js). An inert glyph, not a button — same
   masked-icon-adjacent technique canopy's own shell uses for its search and
   theme-toggle icons: pointer-events: none lets a click straight through to
   the input underneath it. Hidden once the input has focus, so it doesn't
   crowd a query being typed, and on narrow viewports, where canopy's own
   shell (styles.ts, the `max-width: 40rem` breakpoint) collapses the input
   to an icon-sized 2.25rem with no room for a badge — and where a physical
   keyboard, the whole reason the shortcut exists, is unlikely anyway. */
.canopy-search-shortcut {
  position: absolute;
  top: 50%;
  right: var(--sp-2);
  transform: translateY(-50%);
  padding: 0.05em 0.4em;
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  font-size: 0.75em;
  line-height: 1.4;
  color: var(--text-muted);
  background: var(--bg-secondary);
  pointer-events: none;
}
.canopy-search input[type="search"]:focus ~ .canopy-search-shortcut {
  display: none;
}
@media (max-width: 40rem) {
  .canopy-search-shortcut {
    display: none;
  }
}

.canopy-search-results {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 10;
  margin: var(--sp-2) 0 0;
  padding: var(--sp-2) 0;
  width: min(24rem, 90vw);
  max-height: 60vh;
  overflow-y: auto;
  list-style: none;
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}

.canopy-search-results li {
  padding: var(--sp-2) var(--sp-3);
}

.canopy-search-results li.is-active,
.canopy-search-results li:hover {
  background: var(--bg-secondary);
}

.canopy-search-results a {
  display: block;
  font-weight: var(--font-weight-semibold);
  color: var(--text-normal);
  text-decoration: none;
}

.canopy-search-snippet {
  margin: var(--sp-1, 4px) 0 0;
  font-size: 0.85em;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* Scrollspy — pairs with assets/scrollspy.js. Styles the `.canopy-outline`
   link whose section is currently in view. Reuses canopy's own tokens
   (--accent, --font-weight-semibold) rather than introducing new ones, and
   targets the standard `aria-current` attribute rather than a new class so
   the same rule serves any script that sets it, not just this one. */

.canopy-outline a[aria-current="location"] {
  color: var(--accent);
  font-weight: var(--font-weight-semibold);
}

/* Image lightbox — pairs with assets/image-lightbox.js. Reuses canopy's own
   tokens (--bg-primary, --text-normal, --radius-m, --sp-*) rather than
   introducing new ones. z-index 30 sits above search's 10 and the mobile
   nav overlay's 20, since the lightbox can open while either is present. */

.canopy-content img {
  cursor: zoom-in;
}

body.canopy-lightbox-open {
  overflow: hidden;
}

.canopy-lightbox-overlay {
  position: fixed;
  inset: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-8);
  background: rgba(0, 0, 0, 0.85);
}

.canopy-lightbox-overlay[hidden] {
  display: none;
}

.canopy-lightbox-overlay img {
  max-width: 90vw;
  max-height: 90vh;
  object-fit: contain;
  cursor: default;
  border-radius: var(--radius-m);
}

.canopy-lightbox-close {
  position: fixed;
  top: var(--sp-4);
  right: var(--sp-4);
  width: 2.5rem;
  height: 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  background: var(--bg-primary);
  color: var(--text-normal);
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

.canopy-lightbox-close::before {
  content: "\00d7";
}

.canopy-lightbox-close:hover {
  background: var(--bg-secondary);
}
