/* Homepage topology wheel: a radial browser over the frozen Universal v5
   contract - 13 skeletons on the inner ring, a skeleton's variants fanning out
   on the outer one.

   Square by design and the dial fills it, with the read-out docked over the
   bottom corners the dial cannot use. Studio palette in literal hex, because the
   --studio-* custom properties are only defined on the Data Driven Twin page. */

/* Fills its column. Height is explicit rather than an aspect ratio, because a
   square tied to the column width either leaves the row short or makes the dial
   as tall as the column is wide. */
/* This page does not set a global border-box, so the wheel sets its own: without
   it the read-out's padding and border land outside its declared width and it
   eats 38 px of the dial. */
.tw-root,
.tw-root *,
.tw-root *::before,
.tw-root *::after { box-sizing: border-box; }

.tw-root {
  position: relative;
  width: 100%;
  height: 992px;
  overflow: hidden;
  border: 1px solid rgba(151, 169, 206, 0.15);
  border-radius: 18px;
  background: #05070d;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.025), 0 24px 60px rgba(0, 0, 0, 0.3);
  font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: #f5f7fc;
}

/* Same treatment as the cortex graph next door: drifting auroras under a static
   lattice, so the two live surfaces read as one family. */
.tw-field {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.tw-aurora {
  position: absolute;
  border-radius: 50%;
  filter: blur(48px);
  opacity: 0.5;
  mix-blend-mode: screen;
}

.tw-aurora-a {
  left: 4%; top: 2%; width: 62%; height: 62%;
  background: radial-gradient(circle, rgba(168, 146, 255, 0.5), transparent 66%);
  animation: tw-drift-a 19s ease-in-out infinite;
}

.tw-aurora-b {
  right: -6%; bottom: 4%; width: 58%; height: 58%;
  background: radial-gradient(circle, rgba(85, 230, 209, 0.36), transparent 66%);
  animation: tw-drift-b 23s ease-in-out infinite;
}

.tw-aurora-c {
  left: 26%; bottom: -12%; width: 52%; height: 52%;
  background: radial-gradient(circle, rgba(245, 201, 106, 0.2), transparent 68%);
  animation: tw-drift-a 27s ease-in-out infinite reverse;
}

@keyframes tw-drift-a {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50% { transform: translate3d(7%, 5%, 0) scale(1.09); }
}

@keyframes tw-drift-b {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1.04); }
  50% { transform: translate3d(-8%, -6%, 0) scale(0.94); }
}

.tw-lattice {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(151, 169, 206, 0.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(151, 169, 206, 0.045) 1px, transparent 1px);
  background-size: 40px 40px;
}

/* One slow sweep, the same cue the replay player uses to say "this is live". */
.tw-scan {
  position: absolute;
  left: 0;
  right: 0;
  height: 132px;
  background: linear-gradient(180deg, transparent, rgba(85, 230, 209, 0.055), transparent);
  animation: tw-sweep 11s linear infinite;
}

@keyframes tw-sweep {
  from { transform: translateY(-140px); }
  to { transform: translateY(660px); }
}

/* ------------------------------------------------------------------- the dial */

.tw-dial {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
}

/* Shifted down for the corpus tab; bottom is a starting value only, fitDial()
   measures the read-out and overrides it. */
.tw-dial { top: 52px; bottom: 148px; }

/* Size is set in JS, not here. A square viewBox gives the svg an intrinsic 1:1
   ratio, and as a centred grid item every percentage height tried resolved
   against that ratio rather than the grid area - in a 926x488 box it sized
   itself 926x926 and spilled out of the panel. fitDial() measures the box and
   writes the short side in px, which cannot be argued with. */
.tw-dial svg {
  display: block;
  overflow: visible;
  user-select: none;
  -webkit-user-select: none;
}

/* The tick collar turns slowly whatever else is happening - the idle heartbeat
   that keeps the dial from reading as a static chart. */
/* transform-box must be view-box: without it a px transform-origin on an SVG
   group resolves against the element's own bbox and the collar wobbles instead
   of spinning about the dial centre. */
.tw-collar,
.tw-collar-inner { transform-box: view-box; transform-origin: 200px 200px; }
.tw-collar { animation: tw-spin 64s linear infinite; }
.tw-collar-inner { animation: tw-spin 96s linear infinite reverse; }

@keyframes tw-spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.tw-wedge {
  cursor: pointer;
  transform-box: fill-box;
  transform-origin: center;
  transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.18s ease;
}

.tw-wedge.tw-hot { transform: scale(1.07); }

.tw-wedge path { transition: fill 0.18s ease, stroke 0.18s ease, stroke-width 0.18s ease; }
.tw-wedge image { transition: opacity 0.18s ease; }
.tw-wedge.tw-hot image, .tw-wedge.tw-open image { opacity: 1; }

/* No font-size here on purpose: a CSS declaration beats the font-size
   presentation attribute, so setting it would silently override the per-label
   size arcText() computes. */
.tw-wedge text {
  pointer-events: none;
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-weight: 600;
  letter-spacing: 0.03em;
  fill: rgba(245, 247, 252, 0.72);
  transition: fill 0.18s ease;
}

.tw-wedge.tw-hot text,
.tw-wedge.tw-open text { fill: #ffffff; }
.tw-wedge.tw-dim { opacity: 0.3; }

/* A wedge lights briefly at random, so something is always breathing even with
   the pointer nowhere near the dial. */
.tw-wedge.tw-spark path { animation: tw-spark 1.5s ease-out; }

@keyframes tw-spark {
  0% { fill-opacity: 1; stroke-opacity: 1; }
  100% { fill-opacity: 0.2; stroke-opacity: 0.42; }
}

/* No fill-mode: its final keyframe would pin transform and block the hover
   scale for the rest of the element's life. */
.tw-variant { animation: tw-fan 0.26s cubic-bezier(0.2, 0.8, 0.2, 1);
              transform-box: fill-box; transform-origin: center; }

@keyframes tw-fan {
  from { opacity: 0; transform: scale(0.88) rotate(-2deg); }
  to { opacity: 1; transform: scale(1) rotate(0); }
}

/* Pulses run hub-to-wedge along the open spoke, matching the signal packets on
   the cortex graph. */
.tw-pulse { animation: tw-travel 1.5s cubic-bezier(0.35, 0, 0.65, 1) infinite; }

@keyframes tw-travel {
  0% { opacity: 0; }
  12% { opacity: 1; }
  85% { opacity: 0.85; }
  100% { opacity: 0; }
}

.tw-hub-ring { animation: tw-breathe 3.4s ease-in-out infinite;
               transform-box: view-box; transform-origin: 200px 200px; }

@keyframes tw-breathe {
  0%, 100% { opacity: 0.4; transform: scale(1); }
  50% { opacity: 0.9; transform: scale(1.035); }
}

.tw-hub-label { text-anchor: middle; pointer-events: none;
                font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; }
.tw-hub-count { font-size: 27px; font-weight: 800; fill: #f5f7fc; }
.tw-hub-unit { font-size: 8.6px; letter-spacing: 0.2em; fill: #8d97ad; text-transform: uppercase; }
.tw-hub-pair { font-size: 9.4px; letter-spacing: 0.02em; fill: #a892ff; font-weight: 600; }
.tw-hub-hit { cursor: pointer; }

/* The icon breathes on the same 3.4 s beat as the ring around it, so the hub
   reads as one pulsing object rather than a still picture inside a moving ring. */
.tw-hub-icon {
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.22s ease;
  transform-box: view-box;
  transform-origin: 200px 200px;
}
.tw-hub-icon.tw-on { opacity: 0.95; animation: tw-icon-breathe 3.4s ease-in-out infinite; }

.tw-hub-hit:hover + .tw-hub-label { opacity: 0.9; }

@keyframes tw-icon-breathe {
  0%, 100% { opacity: 0.82; transform: scale(0.955); }
  50% { opacity: 1; transform: scale(1.045); }
}

/* --------------------------------------------------------------- the read-out */

/* A strip along the bottom at every width. Putting it beside the circle sounded
   right, but the panel is about half the viewport, so the dial only comes out
   ahead once the panel is wider than it is tall - which needs a ~2200 px window.
   At every real width the strip gives a visibly bigger circle. */
.tw-side {
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 14px;
  z-index: 3;
  display: grid;
  gap: 5px;
  padding: 12px 16px 13px;
  border: 1px solid rgba(151, 169, 206, 0.16);
  border-radius: 14px;
  background: rgba(6, 9, 16, 0.86);
  backdrop-filter: blur(12px);
  pointer-events: none;
}

.tw-eyebrow {
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: #8d97ad;
}

.tw-title { font-size: 21px; font-weight: 650; line-height: 1.2; }

.tw-pair {
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 13px;
  color: #a892ff;
  word-break: break-all;
}

.tw-summary {
  font-size: 15px;
  line-height: 1.5;
  color: #c3ccdf;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.tw-facts { display: flex; flex-wrap: wrap; gap: 6px; }

.tw-fact {
  padding: 4px 9px;
  border: 1px solid rgba(151, 169, 206, 0.18);
  border-radius: 999px;
  background: rgba(16, 21, 34, 0.7);
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.05em;
  color: #8d97ad;
  white-space: nowrap;
}

.tw-fact.tw-trained {
  border-color: rgba(85, 230, 209, 0.42);
  background: rgba(85, 230, 209, 0.11);
  color: #8ff3e6;
}

.tw-note { font-size: 11px; line-height: 1.45; color: #8d97ad; }

.tw-hint {
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #6c778f;
}

.tw-error { color: #ff7a8d; font-size: 12.5px; line-height: 1.5; }

@media (prefers-reduced-motion: reduce) {
  .tw-collar, .tw-collar-inner, .tw-aurora, .tw-scan,
  .tw-hub-ring, .tw-pulse, .tw-variant { animation: none; }
}

@media (max-width: 640px) {
  .tw-root { height: 772px; }
  .tw-title { font-size: 18px; }
  .tw-summary { -webkit-line-clamp: 2; }
}

/* ------------------------------------------------------------------ corpus */

/* Sits above the dial, which gives up the height for it. */
#twStats {
  position: absolute;
  z-index: 4;
  left: 14px;
  right: 14px;
  top: 12px;
  display: grid;
  justify-items: center;
}

.tw-stats-body {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 6px;
}

.tw-stat {
  display: grid;
  gap: 1px;
  justify-items: center;
  padding: 7px 11px;
  border: 1px solid rgba(151, 169, 206, 0.16);
  border-radius: 11px;
  background: rgba(6, 9, 16, 0.86);
  backdrop-filter: blur(10px);
}

.tw-stat b {
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: 0.01em;
  color: #f5f7fc;
  font-variant-numeric: tabular-nums;
}

.tw-stat small {
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #8d97ad;
}

/* Which row the strip is showing. */
.tw-stats-scope {
  margin-top: 6px;
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 9.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #6c778f;
  transition: color 0.18s ease;
}

.tw-stats-scope.tw-scope-on { color: #cfc4ff; }

/* A brief lift when a figure changes, so a swap is visible without animating a
   count-up nobody asked to read. */
.tw-stat-flip b { animation: tw-flip 0.34s ease-out; }

@keyframes tw-flip {
  0% { opacity: 0.25; transform: translateY(-3px); }
  100% { opacity: 1; transform: none; }
}

.tw-stat b { display: inline-block; }

.tw-stat.tw-stat-muted { opacity: 0.38; }
