/* The capability map. Loaded only by /universe, so it can afford to be
 * specific — but every colour still comes from the site's tokens except the
 * seven axis hues, which are data (they live in src/data/universe.json and are
 * written into the SVG as attributes, not declared here). */

.u-wrap {
  padding: clamp(2rem, 5vh, 3.5rem) var(--gutter) clamp(3rem, 8vh, 5rem);
}

.u-head {
  max-width: var(--measure-lede);
  margin: 0 0 clamp(1.5rem, 4vh, 2.5rem);
}

.u-kicker {
  margin: 0 0 1rem;
  font-size: var(--text-xs);
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--text-accent-dim);
}

.u-kicker span {
  margin-right: 0.55rem;
  color: var(--text-accent);
}

.u-head h1 {
  margin: 0;
  font-size: var(--text-display);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -0.03em;
  color: var(--text-hi);
}

.u-lede {
  margin: 1rem 0 0;
  font-size: var(--text-lg);
  line-height: var(--lh-body, 1.6);
  color: var(--text-dim);
}

/* ── toolbar ─────────────────────────────────────────────────────────────── */
.u-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  justify-content: space-between;
  margin: 0 0 1rem;
}

.u-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.u-chip,
.u-reset {
  padding: 0.4rem 0.95rem;
  border: 1px solid rgb(var(--blue-500-rgb) / 0.3);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: var(--text-xs);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.u-chip:hover,
.u-reset:hover,
.u-chip:focus-visible,
.u-reset:focus-visible {
  color: var(--text-hi);
  border-color: var(--line-hover);
}

.u-chip.is-on {
  background: rgb(var(--blue-500-rgb) / 0.18);
  color: var(--text-hi);
  border-color: rgb(var(--blue-500-rgb) / 0.55);
}

/* ── stage ───────────────────────────────────────────────────────────────── */
/* The map takes the whole window width, at the owner's word.
 *
 * Everything else on the page keeps the site's gutter, so this one block breaks
 * out of it rather than the section losing its measure — a 1600px-wide column
 * of prose would be unreadable. 100vw + the negative margin is the standard
 * full-bleed trick; `calc(50% - 50vw)` is what keeps it centred no matter what
 * padding the parent has. */
.u-stagewrap {
  position: relative;
  background: var(--surface-raised);
  overflow: hidden;
  border-block: 1px solid var(--line-navy);
}

.u-bleed {
  width: 100vw;
  margin-inline: calc(50% - 50vw);
}

/* A scrollbar makes 100vw wider than the page, which would push a horizontal
 * scrollbar onto every viewport. Only bleed when there is room to. */
@supports (width: 100dvw) {
  .u-bleed {
    width: 100dvw;
    margin-inline: calc(50% - 50dvw);
  }
}

.u-svg {
  display: block;
  width: 100%;
  /* Full width means the 1600x1000 viewBox would otherwise be 1000px tall on a
   * wide screen and push everything off the fold. Cap it against the viewport
   * and let preserveAspectRatio letterbox the map inside. */
  height: min(76vh, 900px);
  /* --u-k is the live zoom factor, written by universe.js. Type divides by it
   * so a label is the same size on screen whether the view is wide or close. */
  --u-k: 1;
}

.u-stage {
  transition: none;   /* the animation is rAF in JS, not CSS — CSS would fight it */
}

.u-spoke {
  stroke-width: 1;
  opacity: 0.28;
  pointer-events: none;   /* scenery, not a target — and never a hover trigger */
}

.u-stem {
  stroke-width: 0.75;
  opacity: 0.2;
  pointer-events: none;
}

.u-xlink {
  stroke-width: 1.1;
  stroke-dasharray: 4 4;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease;
}

.u-xlink.is-on {
  opacity: 0.75;
}

.u-hub,
.u-node,
.u-core {
  cursor: pointer;
}

/* ── the orbit ────────────────────────────────────────────────────────────
 *
 * The constellation turns around the mark at the middle, at the owner's word.
 * Two animations, not one: .u-orbit swings the whole body about the centre of
 * the viewBox, and .u-spin takes the same angle back off each node about its
 * own anchor, so the type stays upright instead of hanging upside down at the
 * bottom of the circle.
 *
 * transform-box/transform-origin are both spelled out, and the pair is load
 * bearing. For a <g> inside a translated parent, `view-box` + `0 0` resolves
 * to the element's own origin (measured: a child at 1200,800 rotated 90° about
 * 0 0 lands where it should); `fill-box` resolves to the corner of its
 * bounding box instead and the label swings off on an arc of its own.
 *
 * One turn is 260s — slower than the slowest hero ring, because these are
 * targets you have to be able to hit. */
.u-orbit {
  transform-box: view-box;
  /* the centre of the canvas, published by the generator with the viewBox */
  transform-origin: var(--u-cx, 50%) var(--u-cy, 50%);
  animation: u-orbit 260s linear infinite;
}

.u-spin {
  transform-box: view-box;
  transform-origin: 0 0;
  animation: u-orbit-back 260s linear infinite;
}

@keyframes u-orbit {
  to { transform: rotate(360deg); }
}

@keyframes u-orbit-back {
  to { transform: rotate(-360deg); }
}

/* The orbit holds still whenever it matters.
 *
 * On hover, because a label you are reaching for should not walk away from the
 * cursor. On selection, because flyTo() reads the node's position at the
 * moment of the click and a target that keeps moving lands off-centre — the
 * pause is what makes that reading true. Both resume where they stopped. */
/* :has() rather than .u-orbit:hover, and that is not a stylistic choice. The
 * lines and the nodes are two separate .u-orbit groups turning in step;
 * pausing one on its own would let the nodes stop while their stems carried on
 * turning underneath them. Hovering has to reach both, which means asking the
 * <svg>. Where :has() is missing, nothing pauses and the two stay in step —
 * less convenient, never broken.
 *
 * Selection does NOT pause it. The tour loops for ever at 3.2s a stop, so a
 * selection is this page's resting state; pausing on it would mean the map
 * never turned at all. universe.js follows the chosen node round instead. */
.u-svg:has(.u-node:hover, .u-hub:hover) .u-orbit,
.u-svg:has(.u-node:hover, .u-hub:hover) .u-spin {
  animation-play-state: paused;
}

/* Constant on screen, whatever the zoom.
 *
 * The chip is a drawn rect and the type is type; if only the type counter
 * scaled they would come apart as soon as anyone zoomed. So the whole glyph —
 * mark, title, chip, meta — is scaled by 1/k together. --u-inv is written by
 * universe.js beside --u-k; the 1 fallback is what the page shows with the
 * script off. */
.u-fixed {
  transform-box: view-box;
  transform-origin: 0 0;
  transform: scale(var(--u-inv, 1));
}

/* ── a node ───────────────────────────────────────────────────────────────
 * A ring in the axis hue with a filled centre, and three lines beside it:
 * what it is called, what kind of thing it is, and where it lives. The ring
 * places it by area, the fill by type — so neither has to be read to be seen.
 */
/* The capsule that surrounds a chosen node.
 *
 * Filled, not just outlined: it has to lift its block off a map that is busy
 * behind it, and an outline alone would have spokes and other labels running
 * through the middle of the name.
 *
 * Nothing re-orders the DOM to get it on top. That was tried — SVG has no
 * z-index, so the chosen node was re-appended to the end of its group — and
 * re-inserting an element restarts the CSS animations on it, which reset
 * .u-spin and left every touched label tilted. It is also unnecessary:
 * measured across all sixty nodes, no later sibling's ink reaches another
 * node's capsule. See the note in universe.js. */
.u-capsule {
  fill: var(--surface-overlay);
  stroke-width: 1.3;
  opacity: 0;
  transition: opacity 0.18s ease;
}

.u-node:hover .u-capsule,
.u-node:focus-visible .u-capsule,
.u-node.is-picked .u-capsule {
  opacity: 1;
}

.u-mark-halo {
  stroke-width: 0;
  opacity: 0;
  transition: opacity 0.16s ease, stroke-width 0.16s ease;
}

.u-hub-label {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.u-node-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: -0.005em;
}

/* Dark type on the solid plate, not the white it looks like: white tops out at
 * 3.51:1 on these seven hues, --text-on-inverse holds 5.3:1 at worst (aiml)
 * and 10.08:1 at best (commerce). */
.u-chip-text {
  font-size: 6.6px;
  font-weight: 700;
  letter-spacing: 0.09em;
  fill: var(--text-on-inverse);
}

.u-node-meta {
  font-size: 8px;
  letter-spacing: 0.01em;
  fill: var(--text-hint);
}

.u-core-label {
  font-size: calc(22px / var(--u-k));
  font-weight: 700;
  fill: var(--text-hi);
}

.u-node,
.u-hub {
  transition: opacity 0.18s ease;
}

/* Focus effect: once something is chosen, everything else steps back. */
.u-svg.is-focused .u-node,
.u-svg.is-focused .u-hub {
  opacity: 0.3;
}

.u-svg.is-focused .is-picked {
  opacity: 1;
}

.u-node.is-dim {
  opacity: 0.12;
  pointer-events: none;
}

.u-node:hover,
.u-hub:hover,
.u-node:focus-visible,
.u-hub:focus-visible,
.u-core:focus-visible {
  opacity: 1;
}

/* The chosen state.
 *
 * What was here before was the browser's own focus ring, and nothing had
 * turned it off. A UA outline is drawn around the whole <g> — mark, title,
 * chip and meta — so picking a node put a large rounded rectangle in the
 * middle of the map, cutting through whatever was behind it. The ring belongs
 * on the mark, which is where the node actually is.
 *
 * It is replaced, not merely removed: a halo in the node's own hue, plus the
 * title going to full white. Both are visible against the stage (the hues
 * measure 5.57:1 at worst on it), so keyboard focus is still obvious.
 *
 * :focus, :focus-visible — both, and that is not belt-and-braces.
 *
 * The first version turned it off for :focus-visible only, on the reasoning
 * that a mouse click never raises that state. It does here: these are <g>
 * elements made focusable with tabindex, not real buttons, and a click on one
 * leaves it as the active element with the ring drawn. The owner saw it as a
 * blue rounded rectangle sitting outside the capsule, tilted — tilted because
 * the ring wraps .u-node, which rides the orbit, while everything inside rides
 * .u-spin and stays level. That tilt is what identifies it.
 *
 * Removing it costs nothing in accessibility: the capsule itself answers
 * :focus-visible, so a keyboard user still gets an obvious, high-contrast
 * indicator — and a better one, because it is drawn where the node is rather
 * than around a bounding box that includes three lines of label. */
.u-node:focus,
.u-hub:focus,
.u-core:focus,
.u-node:focus-visible,
.u-hub:focus-visible,
.u-core:focus-visible {
  outline: none;
}

.u-hub:hover .u-mark-halo,
.u-hub:focus-visible .u-mark-halo,
.u-hub.is-picked .u-mark-halo {
  stroke-width: 1.6;
  opacity: 0.6;
}

.u-node:hover .u-node-title,
.u-node:focus-visible .u-node-title,
.u-node.is-picked .u-node-title {
  fill: var(--text-hi);
}

.u-node:hover .u-node-meta,
.u-node:focus-visible .u-node-meta,
.u-node.is-picked .u-node-meta {
  fill: var(--text-dim);
}

@media (prefers-reduced-motion: reduce) {
  .u-node,
  .u-hub,
  .u-xlink,
  .u-mark-halo,
  .u-capsule {
    transition: none;
  }

  /* A map that orbits is motion nobody asked for. Stop it dead — the layout
   * is designed at angle zero, so this is the picture, not a fallback. */
  .u-orbit,
  .u-spin {
    animation: none;
  }
}

/* ── panel ───────────────────────────────────────────────────────────────── */
.u-panel {
  position: relative;
  margin: 1rem 0 0;
  padding: 1.4rem 1.5rem;
  border: 1px solid var(--line-blue);
  border-radius: var(--r-md, 16px);
  background: var(--surface-overlay);
}

.u-panel-close {
  position: absolute;
  top: 0.7rem;
  right: 0.9rem;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
}

.u-panel-close:hover,
.u-panel-close:focus-visible {
  color: var(--text-hi);
}

.u-panel h2 {
  margin: 0.35rem 0 0;
  font-size: var(--text-xl);
  line-height: 1.2;
  color: var(--text-hi);
}

.u-panel-kind {
  margin: 0;
  font-size: var(--text-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-hint);
}

.u-panel-blurb {
  margin: 0.7rem 0 0;
  font-size: var(--text-base);
  line-height: var(--lh-body, 1.6);
  color: var(--text-body);
}

.u-panel-where,
.u-panel-rel {
  margin: 0.6rem 0 0;
  font-size: var(--text-sm);
  color: var(--text-dim);
}

.u-link {
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--text-accent);
  font: inherit;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.u-panel-cta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.25rem;
  margin: 1.1rem 0 0;
}

.u-cta,
.u-cta-second {
  font-size: var(--text-sm);
  text-decoration: none;
}

.u-cta {
  color: var(--text-hi);
  font-weight: 600;
}

.u-cta-second {
  color: var(--text-dim);
}

.u-cta:hover,
.u-cta-second:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ── the same thing as text ──────────────────────────────────────────────── */
.u-listing {
  margin: clamp(2.5rem, 6vh, 4rem) 0 0;
}

.u-listing h2 {
  margin: 0;
  font-size: var(--text-2xl);
  color: var(--text-hi);
}

.u-listing-lede {
  margin: 0.6rem 0 1.5rem;
  color: var(--text-dim);
  font-size: var(--text-sm);
}

.u-axis {
  border-top: 1px solid var(--line-navy);
}

.u-axis summary {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  padding: 0.95rem 0;
  cursor: pointer;
  font-size: var(--text-lg);
  color: var(--text-hi);
}

.u-axis summary em {
  margin-left: auto;
  font-style: normal;
  font-size: var(--text-xs);
  color: var(--text-hint);
}

.u-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex: 0 0 auto;
  background: var(--text-accent);
}

.u-axis-blurb {
  margin: 0 0 1rem;
  color: var(--text-dim);
  font-size: var(--text-sm);
}

.u-list {
  margin: 0 0 1.5rem;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.85rem;
}

.u-list li {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: 0.2rem 1rem;
  align-items: baseline;
}

.u-kind {
  font-size: var(--text-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-hint);
}

.u-blurb {
  grid-column: 2;
  font-size: var(--text-sm);
  color: var(--text-dim);
}

.u-soon {
  font-style: normal;
  font-size: var(--text-2xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-hint);
}

@media (max-width: 40rem) {
  .u-list li {
    grid-template-columns: 1fr;
  }

  .u-blurb {
    grid-column: 1;
  }
}

.u-tools {
  display: flex;
  gap: 0.5rem;
}

/* ── the mark at the middle ───────────────────────────────────────────────
 *
 * The geometry and the class names are the hero's, so the centre of the map is
 * the logo rather than a drawing of it — change the brand and this follows.
 *
 * One thing is overridden, and only here: the O. styles.css fills it with
 * --blue-700 (#2e56a4), which works in the hero because the bright binary fan
 * sits behind it. On this stage the ground is near-black and that navy measures
 * 2.82:1 against it — the middle of the map, and the thing the eye is supposed
 * to start from, was the hardest part to see. --blue-500 is the next step up
 * the same blue and measures 6.44:1.
 *
 * Scoped to .u-core-mark so the hero and the nav are untouched. */
.u-core-mark .o-left,
.u-core-mark .o-right {
  fill: var(--blue-500);
}

/* The core is a button; make that legible on hover the way the nodes are. */
.u-core:hover .u-core-mark,
.u-core:focus-visible .u-core-mark {
  filter: brightness(1.18);
}
