/* The hero's arc reactor (reactor.js, reactor-scene.js). The canvas is half
   as wide again as the reactor itself, so its glow has room; its edges fade
   into the page through a radial mask, and it is cleared to nothing, so the
   name can pass behind it. Under it sits a still of the reactor, shown
   until the 3D model has drawn, and kept where WebGL is missing. */
.reactor {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 1;
  width: calc(var(--core) * 1.5);
  aspect-ratio: 1;
  translate: -50% -50%;
}

.reactor__poster,
.reactor__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  -webkit-mask: radial-gradient(closest-side, #000 68%, transparent);
  mask: radial-gradient(closest-side, #000 68%, transparent);
  pointer-events: none;
}

/* The live canvas must paint above the still, which comes later in the
   markup and would otherwise cover it. */
.reactor__canvas {
  z-index: 1;
  opacity: 0;
  transition: opacity 500ms;
}

.reactor.is-live .reactor__canvas {
  opacity: 1;
}

/* The canvas lets the page through, so the still goes once the model is up. */
.reactor.is-live .reactor__poster {
  opacity: 0;
  transition: opacity 500ms;
}

/* Beside the reactor, the name tucks in behind its rim. The still has a
   background of its own, so this waits for the live model. */
@media (min-width: 720px) {
  .hero__stage:has(.reactor.is-live) .hero__name {
    z-index: 0;
    column-gap: calc(var(--core) * 0.7);
  }
}

/* The reactor itself is the button: hold to charge, let go to pulse. */
.reactor__hit {
  position: absolute;
  top: 50%;
  left: 50%;
  z-index: 2;
  width: var(--core);
  aspect-ratio: 1;
  border-radius: 50%;
  translate: -50% -50%;
  cursor: grab;
  touch-action: pan-y;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

.reactor__hit:focus-visible {
  outline-offset: 4px;
}

.reactor.is-dragging .reactor__hit {
  cursor: grabbing;
}

.reactor-still .reactor__hit {
  cursor: default;
}

/* The pulse: a ring of light that runs out across the hero. */
.reactor__wave {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--core);
  aspect-ratio: 1;
  margin: calc(var(--core) / -2) 0 0 calc(var(--core) / -2);
  border: 2px solid var(--accent);
  border-radius: 50%;
  box-shadow: 0 0 48px var(--accent-glow), inset 0 0 48px var(--accent-glow);
  pointer-events: none;
}

/* The name catches the light of each pulse. */
@media (prefers-reduced-motion: no-preference) {
  .hero.is-pulsed .hero__name {
    animation: name-flash 1100ms var(--ease-out);
  }
}

@keyframes name-flash {
  from {
    color: hsl(var(--h) 100% 92%);
    text-shadow: 0 0 40px var(--accent-glow), 0 0 90px var(--accent-glow);
  }
}

/* The status line, under the reactor. */
.reactor__panel {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  margin-top: 14px;
}

.reactor__status {
  color: var(--muted);
  text-align: center;
}

.reactor__status:empty {
  display: none;
}

@media (max-width: 719px) {
  /* The reactor joins the column above the name; its canvas overhangs the
     sides, and the hero clips it. */
  .reactor {
    position: relative;
    top: auto;
    left: auto;
    order: -1;
    flex: none;
    margin: calc(var(--core) * -0.25) 0;
    translate: none;
  }

  .reactor__panel {
    margin-top: 12px;
  }
}

/* Callouts: on wide screens the credentials hang off the reactor's rim on
   leader lines; reactor.js places each one. */
.hero__lines {
  display: none;
}

.hero--hud .hero__lines {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}

.hero__lines path {
  fill: none;
  stroke: var(--line-2);
  stroke-width: 1;
}

.hero__lines rect {
  fill: var(--accent);
}

/* No rise for the list itself here: a moving list would carry the
   callouts placed inside it. */
.hero--hud .hero__creds {
  margin: 0;
  animation: none;
}

.hero--hud .hero__creds li {
  position: absolute;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  max-width: 15em;
  font-size: 11.5px;
  text-align: left;
}

.hero--hud .hero__creds li.is-left {
  align-items: flex-end;
  text-align: right;
}

.hero--hud .hero__creds b {
  margin: 0;
  color: var(--text);
  font: 500 26px/1 var(--font-sans);
  letter-spacing: -0.03em;
  text-transform: none;
}

@media (prefers-reduced-motion: no-preference) {
  :root:not(.gated) .hero--hud .hero__creds li {
    animation: fade 700ms ease backwards;
    animation-delay: calc(1300ms + var(--n, 0) * 150ms);
  }

  .hero--hud .hero__creds li:nth-child(2) { --n: 1; }
  .hero--hud .hero__creds li:nth-child(3) { --n: 2; }

  :root:not(.gated) .hero__lines:not(.is-drawn) path {
    stroke-dasharray: 1;
    stroke-dashoffset: 1;
    animation: draw-line 800ms var(--ease) forwards;
    animation-delay: calc(1000ms + var(--i) * 150ms);
  }

  :root:not(.gated) .hero__lines:not(.is-drawn) rect {
    animation: fade 400ms ease backwards;
    animation-delay: calc(1000ms + var(--i) * 150ms);
  }
}

@keyframes fade {
  from { opacity: 0; }
}

@keyframes draw-line {
  to { stroke-dashoffset: 0; }
}

/* The gate (gate.js) --------------------------------------------------- */
/* Before the site wakes, the page is dark: everything is hidden but the
   unpowered reactor and the gate's controls under it. The head script in
   index.html adds `gated` before the first paint. */
/* The scrollbar's space is kept while the page cannot scroll, so nothing
   shifts sideways when it wakes. */
html {
  scrollbar-gutter: stable;
}

.gated body {
  overflow: hidden;
}

.gated .skip-link,
.gated .progress,
.gated .nav,
.gated main > section:not(.hero),
.gated .footer,
.gated .hero__name,
.gated .hero > :not(.hero__stage) {
  visibility: hidden;
  opacity: 0;
}

/* Waking: once the reactor is up, the nav and the rest of the page fade in
   (the hero's own parts rise, in home.css). */
.waking .nav,
.waking main > section:not(.hero),
.waking .footer {
  transition: opacity 900ms ease 500ms;
}

.gated .reactor__poster {
  filter: brightness(0.16) saturate(0.4);
}

.reactor__poster {
  transition: filter 1200ms ease;
}

.gate {
  display: none;
}

.gated .gate {
  position: absolute;
  top: calc(100% + 16px);
  left: 50%;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: max-content;
  max-width: calc(100vw - 2 * var(--gutter));
  translate: -50% 0;
  transition: opacity 400ms ease;
}

.waking .gate {
  opacity: 0;
  pointer-events: none;
}

.gate__status {
  color: var(--muted);
}

.gate__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
}

.gate__icon {
  width: 1.05em;
  height: 1.05em;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.gate__mic[aria-pressed="true"] {
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: 0 0 24px var(--accent-glow);
}

.gate__hint {
  color: var(--dim);
}

/* On a phone the name sits under the reactor; while it is hidden it gives
   up its space, so the gate's controls sit right under the reactor. */
@media (max-width: 719px) {
  .gated .hero__name { display: none; }
}

/* No keyboard to type on, as a rule, on a touch screen. */
@media (pointer: coarse) {
  .gate__hint { display: none; }
}

@media (prefers-reduced-motion: no-preference) {
  .gated:not(.waking) .gate {
    animation: fade 900ms ease 400ms backwards;
  }

  .gate__mic[aria-pressed="true"] .gate__icon {
    animation: listen 1.2s ease-in-out infinite;
  }
}

@keyframes listen {
  50% { opacity: 0.35; }
}
