/* boot.css — the HELIOS power-on sequence
 *
 * Injected into every page by _work/boot.mjs. Colours and type stacks are copies of
 * the site's own tokens so the sequence reads as the same instrument, not a splash
 * screen bolted on: same #0a0a0a ink, same condensed display face, same mono, same
 * 45° hatch that sits behind the pages.
 *
 * The cover itself is `html.kb-run::before` — a pseudo-element, not a node. It paints
 * before the first frame (the gate script in <head> adds the class ahead of any body
 * content), and because it adds nothing to the DOM there is nothing for React to find
 * during hydration that it did not render. The animated overlay is appended by
 * boot.js, which runs after the document is parsed.
 */

:root {
  --kb-bg: #0a0a0a;
  --kb-ink: #fafafa;
  --kb-dim: #6f6f6f;
  --kb-line: #2b2b2b;
  --kb-sunk: #131313;
  --kb-display: "Arial Narrow", "Roboto Condensed", Arial, sans-serif;
  --kb-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}

/* Two classes, deliberately split. `kb-run` owns the cover and is dropped the instant
   the exit starts, so the iris has the real page behind it to reveal. `kb-lock` owns
   the scroll lock and survives until the overlay is gone. Fusing them into one class
   means the collapse reveals the black cover instead of the site. */
html.kb-lock { overflow: hidden; }
html.kb-run::before {
  content: "";
  position: fixed;
  inset: 0;
  background: var(--kb-bg);
  z-index: 2147483000;
}

.kboot {
  position: fixed;
  inset: 0;
  z-index: 2147483001;
  background: var(--kb-bg);
  color: var(--kb-ink);
  font-family: var(--kb-mono);
  clip-path: inset(0 0 0 0);
  -webkit-font-smoothing: antialiased;
}
.kboot::before {
  content: "";
  position: absolute;
  inset: 0;
  /* The same 45° hatch the pages sit on, inverted. The stripes have to be MUCH weaker
     here than on paper: the eye adapts to a large dark field and a stripe that would
     be invisible on white reads as dirt across black. */
  background-image: repeating-linear-gradient(45deg, transparent 0 9px, rgba(255, 255, 255, .022) 9px 10px);
  opacity: 0;
  transition: opacity .5s ease;
}
.kboot.kb-hatch::before { opacity: 1; }

/* ---------------------------------------------------------------- frame ---- */
.kb-frame {
  position: absolute;
  inset: 22px;
  display: grid;
  grid-template-rows: 54px 1fr 54px;
  border: 1px solid var(--kb-line);
  opacity: 0;
  transition: opacity .18s ease;
}
.kb-frame.kb-in { opacity: 1; }

.kb-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 22px;
  font-size: 12px;
  letter-spacing: .16em;
}
.kb-bar--top { border-bottom: 1px solid var(--kb-line); }
.kb-bar--bot { border-top: 1px solid var(--kb-line); }

.kb-brand { display: flex; align-items: center; gap: 11px; }
.kb-brand svg { display: block; flex: none; }
.kb-tag { color: var(--kb-dim); }

/* ----------------------------------------------------------- left column ---- */
.kb-body {
  display: grid;
  grid-template-columns: 1fr 380px;
  min-height: 0;
}
.kb-left {
  padding: clamp(18px, 4vh, 42px) 30px;
  display: flex;
  flex-direction: column;
  min-width: 0;
  border-right: 1px solid var(--kb-line);
}

.kb-mark {
  font-family: var(--kb-display);
  font-weight: 700;
  font-size: clamp(58px, 12vh, 118px);
  line-height: .86;
  letter-spacing: .005em;
  margin: 0;
  white-space: nowrap;
}
.kb-mark span {
  display: inline-block;
  opacity: 0;
  transform: translateY(9px);
  filter: blur(5px);
}
.kb-mark span.kb-on {
  opacity: 1;
  transform: none;
  filter: none;
  transition: opacity .3s ease, transform .3s cubic-bezier(.2,.9,.3,1), filter .3s ease;
}

.kb-sub {
  margin: 12px 0 0;
  font-size: 12px;
  letter-spacing: .3em;
  color: var(--kb-dim);
  opacity: 0;
  transition: opacity .4s ease;
}
.kb-sub.kb-on { opacity: 1; }

.kb-log {
  list-style: none;
  margin: clamp(16px, 4vh, 40px) 0 0;
  padding: 0;
  font-size: 13px;
  line-height: 2.3;
  letter-spacing: .06em;
}
.kb-log li {
  display: flex;
  align-items: baseline;
  gap: 10px;
  opacity: 0;
  transform: translateX(-5px);
}
.kb-log li.kb-on {
  opacity: 1;
  transform: none;
  transition: opacity .22s ease, transform .22s ease;
}
.kb-log i { font-style: normal; color: var(--kb-dim); }
.kb-log u { flex: 1; text-decoration: none; border-bottom: 1px dotted #303030; transform: translateY(-4px); }
.kb-log b { font-weight: 400; color: var(--kb-dim); transition: color .2s ease; }
.kb-log li.kb-ok b { color: var(--kb-ink); }

/* The fleet strip. Fifty ticks for fifty jets: they light in a sweep, then one locks
   and the rest fall back — the whole premise of the game, stated in two seconds
   without a sentence. It also gives the left column something to do with the vertical
   space under the log, which was otherwise a dead 300px. */
.kb-fleet { margin-top: auto; padding-top: 26px; opacity: 0; transition: opacity .5s ease; }
.kb-fleet.kb-on { opacity: 1; }
.kb-fleet-head {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  letter-spacing: .2em;
  color: var(--kb-dim);
  margin-bottom: 11px;
}
/* 50 marks have to read as 50, not as one dashed rule — hence a gap wide enough to
   separate them (about 11px of tick at 1440) rather than the 3px that fused them. */
.kb-ticks { display: flex; gap: clamp(3px, .5vw, 7px); height: 30px; align-items: flex-end; }
.kb-ticks i {
  flex: 1;
  height: 13px;
  background: #262626;
  transition: height .18s ease, background .18s ease;
}
.kb-ticks i.kb-lit { height: 22px; background: #6f6f6f; }
.kb-ticks i.kb-win { height: 30px; background: var(--kb-ink); }
.kb-ticks i.kb-rest { height: 8px; background: #1c1c1c; }

/* ---------------------------------------------------------- right column ---- */
.kb-right {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 26px;
  padding: 20px;
}

/* The sun: a solid core with rings that travel outward and fade. Rings are DOM
   elements rather than a canvas so they inherit the frame's own rasterisation and
   stay crisp at any device pixel ratio.
   The two radii are chosen against each other, in the 196px box: the core is r=36px
   and the reticle sits at r≈50px, so the rings start inside the dial and blow past it
   as they fade. An earlier pass had the reticle out at r≈95 with a 36px core inside —
   a small dot in a large empty ring, which read as a loading spinner. */
.kb-sun { position: relative; width: 196px; height: 196px; flex: none; }
.kb-sun i {
  position: absolute;
  inset: 34px;
  border: 1px solid var(--kb-ink);
  border-radius: 50%;
  opacity: 0;
}
.kb-sun .kb-core {
  position: absolute;
  inset: 62px;
  border-radius: 50%;
  background: var(--kb-ink);
  transform: scale(0);
}
.kb-sun.kb-live .kb-core {
  transform: scale(1);
  transition: transform .5s cubic-bezier(.2,.9,.25,1);
}
.kb-sun.kb-live i { animation: kb-ring 2.4s cubic-bezier(.25,.6,.4,1) infinite; }
.kb-sun.kb-live i:nth-of-type(2) { animation-delay: .8s; }
.kb-sun.kb-live i:nth-of-type(3) { animation-delay: 1.6s; }
@keyframes kb-ring {
  0%   { transform: scale(.37); opacity: 0; }
  12%  { opacity: .55; }
  100% { transform: scale(1); opacity: 0; }
}
/* Radiating ticks around the sun — the reticle the site uses in its section kickers,
   enlarged. Named `kb-rays`, NOT `kb-ticks`: the fleet strip below owns that name, and
   giving two different elements one class means each silently inherits the other's
   box model (this cost a debug cycle — the sun lost its reticle and the fleet strip
   was absolutely positioned off its parent by the sun's rules). */
.kb-rays { position: absolute; inset: -14px; opacity: 0; transition: opacity .5s ease; }
.kb-sun.kb-live .kb-rays { opacity: .5; }
.kb-rays line { stroke: var(--kb-dim); stroke-width: 1; }

.kb-status {
  font-size: 13px;
  letter-spacing: .3em;
  color: var(--kb-dim);
  transition: color .3s ease;
}
.kb-status.kb-ok { color: var(--kb-ink); }

/* ------------------------------------------------------------- progress ---- */
.kb-track {
  flex: 1;
  max-width: 520px;
  height: 9px;
  border: 1px solid var(--kb-line);
  padding: 2px;
  margin-right: 18px;
}
.kb-fill {
  height: 100%;
  width: 0;
  background: var(--kb-ink);
}
.kb-pct { font-size: 12px; letter-spacing: .16em; color: var(--kb-dim); min-width: 58px; text-align: right; }
.kb-pct.kb-ok { color: var(--kb-ink); }

/* On a phone the right column is gone, and the status line went with it — which left
   the footer as plain percentage and nothing confirming the sequence actually
   resolved. This is the same text, in the bar, below 860px only. Both nodes are
   written by one setStatus() so they cannot drift apart. */
.kb-status--m { display: none; font-size: 10px; letter-spacing: .14em; margin-right: 12px; }

/* --------------------------------------------------------------- exit ------ */
/* Iris collapse: the whole overlay squeezes to a hairline, then the seam blows out
   white. clip-path on the fixed overlay reveals the real page underneath, which is
   already painted and waiting. */
.kboot.kb-out {
  clip-path: inset(calc(50% - 1px) 0 calc(50% - 1px) 0);
  transition: clip-path .34s cubic-bezier(.76,0,.24,1);
}
.kb-seam {
  position: absolute;
  left: 0; right: 0; top: calc(50% - 1px);
  height: 2px;
  background: var(--kb-ink);
  opacity: 0;
  box-shadow: 0 0 22px 5px rgba(250, 250, 250, .55);
}
.kboot.kb-out .kb-seam {
  opacity: 1;
  transition: opacity .12s ease .22s;
  animation: kb-seamfade .5s ease .34s forwards;
}
@keyframes kb-seamfade { to { opacity: 0; box-shadow: 0 0 0 0 rgba(250, 250, 250, 0); } }

/* -------------------------------------------------------------- mobile ----- */
@media (max-width: 860px) {
  .kb-frame { inset: 12px; grid-template-rows: 48px 1fr 48px; }
  .kb-body { grid-template-columns: 1fr; }
  .kb-right { display: none; }
  .kb-status--m { display: inline; }
  .kb-left { border-right: 0; padding: 20px 18px; }
  .kb-mark { font-size: clamp(46px, 15vw, 84px); }
  .kb-bar { padding: 0 14px; font-size: 10px; letter-spacing: .1em; }
}
@media (max-width: 420px) {
  .kb-log { font-size: 11px; line-height: 1.8; }
}
