/* ═══════════════════════════════════════════════════════════════
   Knowledge Galaxy — domains, regions, and articles in one night sky

   Stars, dust and glow are painted on one canvas by kb-galaxy.js; this
   file styles only the plate, the chrome, and the clickable labels that
   sit over the canvas.

   Colour has exactly one source: the --kb-* tokens in extra.css.
   No raw hex or rgba() below — see the header of extra.css.
   ═══════════════════════════════════════════════════════════════ */

.kb-galaxy-scope {
  --kb-galaxy-ring: color-mix(in srgb, var(--kb-space-blue) 70%, transparent);
  --kb-galaxy-halo: color-mix(in srgb, var(--kb-space-glow) 26%, transparent);
  --kb-galaxy-shade: color-mix(in srgb, var(--kb-space) 85%, transparent);
}

/* The host settles geometry before any fetch; site chrome floats above
   the viewport so the galaxy starts at the top without a header-sized
   gap in document flow. JavaScript adds the reveal class from scroll, focus,
   and pointer state; the html qualifier outranks Material's autohide rule. */
html:has([data-kbgalaxy]) .md-header {
  position: fixed;
  transform: translateY(-100%);
  opacity: 0;
  pointer-events: none;
  transition: transform 180ms ease, opacity 180ms ease;
}

html:has([data-kbgalaxy]) .md-header.kb-galaxy-header--revealed {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
}

html:has([data-kbgalaxy]) .md-main__inner {
  margin-top: 0;
}

html:has([data-kbgalaxy]) .md-path {
  display: none;
}

html:has([data-kbgalaxy]) .md-content__inner::before {
  height: 0;
}

/* Loading, rendered, failed and JS-off pages share the same plate. */
.kb-world {
  display: block;
  position: relative;
  width: 100vw;
  height: 100svh;
  min-height: 100svh;
  margin: 0 0 2.2rem calc(50% - 50vw);
  overflow: hidden;
  background:
    radial-gradient(ellipse 70% 55% at 50% 45%,
      color-mix(in srgb, var(--kb-space-2) 70%, transparent) 0%,
      transparent 75%),
    var(--kb-space);
  color: var(--kb-space-fg);
  isolation: isolate;
}

/* extra.css pads the article 1.9rem on the left only at this width, so
   the plain 50% - 50vw centring lands 0.95rem short of the viewport edge. */
@media screen and (min-width: 90em) {
  .kb-world { margin-left: calc(50% - 50vw - 0.95rem); }
}

/* ── Fallback links ──────────────────────────────────────────────
   Keep the article list in flow in every state. */
.kb-world-fallback {
  margin: 0.8rem 0 2rem;
}

.kb-galaxy__fallback {
  position: absolute;
  z-index: 4;
  left: 1.15rem;
  bottom: 1.05rem;
}

.kb-world .kb-galaxy__fallback {
  color: var(--kb-space-blue);
}

.kb-galaxy {
  position: absolute;
  z-index: 1;
  inset: 0;
}

/* Material's `.md-typeset canvas`-style max-width rules must not squash it. */
.kb-galaxy > canvas.kb-galaxy-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
}

.kb-galaxy-overlay {
  position: absolute;
  inset: 0;
  transition: opacity 180ms ease;
}

.kb-galaxy-overlay.is-leaving {
  opacity: 0;
  pointer-events: none;
}

/* ── Plate chrome ────────────────────────────────────────────── */
.kb-galaxy__title,
.kb-galaxy__hint {
  position: absolute;
  z-index: 4;
  color: var(--kb-space-faint);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  pointer-events: none;
}

.kb-galaxy__title {
  top: 1.1rem;
  left: 1.25rem;
  color: var(--kb-space-fg);
  font-weight: 600;
}

.kb-galaxy__hint {
  right: 1.25rem;
  bottom: 1.1rem;
}

.kb-galaxy__back {
  position: absolute;
  z-index: 5;
  top: 0.75rem;
  left: 0.9rem;
  min-height: 2.2rem;
  padding: 0.4rem 0.75rem;
  border: 1px solid color-mix(in srgb, var(--kb-space-blue) 45%, transparent);
  border-radius: 999px;
  color: var(--kb-space-fg);
  background: var(--kb-galaxy-shade);
  font-size: 0.68rem;
  font-weight: 500;
  line-height: 1;
  cursor: pointer;
  transition: border-color 160ms ease, background-color 160ms ease;
}

.kb-galaxy__back[hidden] {
  display: none;
}

.kb-galaxy__back:hover,
.kb-galaxy__back:focus-visible {
  border-color: var(--kb-space-blue);
  outline: none;
}

.kb-galaxy[data-level="L1"] .kb-galaxy__title,
.kb-galaxy[data-level="L2"] .kb-galaxy__title {
  left: 8rem;
}

/* ── Starfield ───────────────────────────────────────────────── */
.kb-galaxy-star {
  position: absolute;
  top: var(--star-y);
  left: var(--star-x);
  width: var(--star-size);
  height: var(--star-size);
  border-radius: 50%;
  background: var(--kb-space-star);
  opacity: var(--star-opacity);
  transform: translate(-50%, -50%);
  animation: kb-galaxy-twinkle var(--star-period) ease-in-out var(--star-delay) infinite;
  pointer-events: none;
}

@keyframes kb-galaxy-twinkle {
  0%, 100% { opacity: var(--star-opacity); }
  50%      { opacity: calc(var(--star-opacity) * 0.3); }
}

/* ── Nodes ───────────────────────────────────────────────────────
   An anchor is a hit disc centred on its star or cluster; the caption
   hangs below it. --hit comes from the camera, so the disc always
   matches what the canvas draws underneath. */
/* (0,3,1) outranks Material's `.md-typeset a:focus` / `:hover` accent. */
.kb-world .kb-galaxy a.kb-galaxy-node,
.kb-world .kb-galaxy a.kb-galaxy-node:focus {
  color: var(--kb-space-fg);
}

.kb-galaxy-node {
  position: absolute;
  top: var(--node-y);
  left: var(--node-x);
  z-index: 2;
  width: calc(var(--hit) * 2);
  height: calc(var(--hit) * 2);
  border-radius: 50%;
  color: var(--kb-space-fg);
  transform: translate(-50%, -50%);
  -webkit-tap-highlight-color: transparent;
  animation: kb-galaxy-arrive 420ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
  animation-delay: calc(var(--order) * 28ms);
}

@keyframes kb-galaxy-arrive {
  from { opacity: 0; translate: 0 0.35rem; }
  to   { opacity: 1; translate: 0 0; }
}

.kb-galaxy-node__ring {
  position: absolute;
  inset: 0;
  border: 1px solid var(--kb-galaxy-ring);
  border-radius: 50%;
  background: radial-gradient(circle, var(--kb-galaxy-halo), transparent 70%);
  opacity: 0;
  transform: scale(0.92);
  transition: opacity 200ms ease, transform 200ms ease;
  pointer-events: none;
}

.kb-galaxy-node__caption {
  position: absolute;
  top: calc(100% + 0.35rem);
  left: 50%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  width: max-content;
  max-width: 12rem;
  text-align: center;
  /* A soft dark backing so constellation lines pass behind the words. */
  transform: translateX(-50%);
  pointer-events: none;
  text-shadow: 0 0 0.6rem var(--kb-space), 0 0 0.2rem var(--kb-space);
}

/* Constellation lines only exist on the article layer; there a soft dark
   backing lets them pass behind the words. */
.kb-galaxy[data-level="L2"] .kb-galaxy-node__caption {
  padding: 0.2rem 0.7rem;
  background: radial-gradient(closest-side, var(--kb-space) 65%, transparent);
}

.kb-galaxy-node__label {
  font-size: 0.84rem;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0.02em;
}

.kb-galaxy[data-level="L0"] .kb-galaxy-node__label {
  font-size: 1.05rem;
  letter-spacing: 0.04em;
}

.kb-galaxy-node__count {
  color: var(--kb-space-blue);
  font-size: 0.66rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.06em;
}

.kb-galaxy-node__step {
  color: var(--kb-space-blue);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.08em;
}

/* The description stays hidden on the deeper layers until a node is
   engaged; on the home sky it is the only hint of what a galaxy holds. */
/* Out of flow, so a hidden note never pushes its caption off its star. */
.kb-galaxy-node__note {
  position: absolute;
  top: calc(100% + 0.2rem);
  left: 50%;
  width: max-content;
  max-width: 13rem;
  transform: translateX(-50%);
  color: var(--kb-space-faint);
  font-size: 0.68rem;
  line-height: 1.4;
  opacity: 0;
  transition: opacity 200ms ease;
}

.kb-galaxy[data-level="L0"] .kb-galaxy-node__note {
  opacity: 1;
}

/* Deeper down the note is a hover preview laid over other captions, so it
   gets an opaque card of its own. */
.kb-galaxy:not([data-level="L0"]) .kb-galaxy-node__note {
  padding: 0.35rem 0.55rem;
  border: 1px solid color-mix(in srgb, var(--kb-space-blue) 22%, transparent);
  border-radius: 0.4rem;
  background: var(--kb-space);
}

.kb-galaxy-node:focus-visible {
  outline: none;
}

/* The engaged node rises above its neighbours, so its preview note is
   never painted under another star's caption. */
.kb-galaxy-node:hover,
.kb-galaxy-node:focus-visible {
  z-index: 5;
}

.kb-galaxy-node:focus-visible .kb-galaxy-node__ring,
.kb-galaxy-node.is-selected .kb-galaxy-node__ring {
  opacity: 1;
  transform: scale(1);
}

.kb-galaxy-node:focus-visible .kb-galaxy-node__note { opacity: 1; }

@media (hover: hover) and (pointer: fine) {
  .kb-galaxy-node:hover .kb-galaxy-node__ring { opacity: 1; transform: scale(1); }
  .kb-galaxy-node:hover .kb-galaxy-node__note { opacity: 1; }
  .kb-world .kb-galaxy a.kb-galaxy-node:hover { color: var(--kb-space-blue); }
}

/* Leaving for an article: everything but the picked star dims. */
.kb-world.is-selecting .kb-galaxy-node:not(.kb-galaxy-node--diving),
.kb-world.is-selecting .kb-galaxy-canvas {
  opacity: 0.25;
  transition: opacity 240ms ease;
}

.kb-galaxy-group {
  position: absolute;
  top: var(--node-y);
  left: var(--node-x);
  z-index: 1;
  color: var(--kb-space-faint);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  white-space: nowrap;
  transform: translate(-50%, -50%);
  pointer-events: none;
  animation: kb-galaxy-arrive 420ms ease both;
}

.kb-galaxy-live {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Mobile ──────────────────────────────────────────────────── */
@media (max-width: 44rem) {
  .kb-galaxy-node__caption { max-width: 10rem; }
  /* Articles run down one arm on a phone; captions sit beside the star. */
  .kb-galaxy[data-level="L2"] .kb-galaxy-node__caption {
    top: 50%;
    left: calc(100% - 0.2rem);
    padding: 0.1rem 0.3rem;
    align-items: flex-start;
    max-width: calc(100vw - var(--node-x) - var(--hit) - 1rem);
    text-align: left;
    transform: translateY(-50%);
  }
  .kb-galaxy[data-level="L2"] .kb-galaxy-node__note {
    left: 0;
    max-width: 100%;
    text-align: left;
    transform: none;
  }
  .kb-galaxy-node__label { font-size: 0.76rem; }
  .kb-galaxy[data-level="L0"] .kb-galaxy-node__label { font-size: 0.9rem; }
  .kb-galaxy[data-level="L0"] .kb-galaxy-node__note { display: none; }
  .kb-galaxy__title { top: 0.85rem; left: 0.9rem; }
  .kb-galaxy[data-level="L1"] .kb-galaxy__title,
  .kb-galaxy[data-level="L2"] .kb-galaxy__title { left: 7.4rem; right: 0.9rem; }
  .kb-galaxy__hint { display: none; }
  .kb-galaxy-group { transform: translateY(-50%); }
}

/* ── Reduced motion ──────────────────────────────────────────────
   Motion is removed outright, not merely shortened. */
@media (prefers-reduced-motion: reduce) {
  html:has([data-kbgalaxy]) .md-header {
    transition: none;
  }

  .kb-galaxy-overlay,
  .kb-galaxy-group,
  .kb-galaxy-node,
  .kb-galaxy-node__ring,
  .kb-galaxy-node__note,
  .kb-galaxy-star,
  .kb-galaxy__back {
    transition: none !important;
    animation: none !important;
  }
}
