/* Rocket Blob holding page.
 *
 * Temporary pre-launch surface. Everything here is self-contained so that
 * removing this directory, index.html and the two redirect exceptions takes the
 * page off the site in one change.
 *
 * The CSP is style-src 'self' with no 'unsafe-inline', so there is no inline
 * style anywhere on the page and no web font is fetched: the wordmark's
 * distressed edge is a mask over the monospace stack the rest of the site uses.
 */

*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  --ink: #050706;
  --green: #22c67d;
  --green-hi: #4ade94;
  --grey: #7c8896;
  --white: #e8f2ec;
  --purple: #8b5cf6;
  --mono: 'SF Mono', 'Fira Code', 'Cascadia Code', ui-monospace, Menlo, Consolas, monospace;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: var(--ink);
  color: var(--white);
  font-family: var(--mono);
  touch-action: none;
  -webkit-text-size-adjust: 100%;
  -webkit-touch-callout: none;
  user-select: none;
}

/* The width and height are not decoration. A canvas is a replaced element with
   an intrinsic size, so `inset: 0` alone does not stretch it: `width: auto`
   resolves to the width attribute and the over-constrained `right` is dropped.
   The attribute is the backing store, which is the viewport times the device
   pixel ratio — so on any screen above 1x the field was being laid out at twice
   the viewport, anchored to the top left, and the head sat off to the right and
   below where the column expected it. At 1x the two happened to agree, which is
   why this only ever showed up on a real phone. */
canvas#blob {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  display: block;
}

/* Restrained tech texture: a faint grid with a scanline wash over it. Both sit
   well below the particle field in contrast so they never compete with it. */
.texture {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(to right, rgba(34, 198, 125, 0.045) 0 1px, transparent 1px 72px),
    repeating-linear-gradient(to bottom, rgba(34, 198, 125, 0.045) 0 1px, transparent 1px 72px),
    repeating-linear-gradient(to bottom, rgba(0, 0, 0, 0.30) 0 1px, transparent 1px 3px);
}

.texture::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at 50% 42%, transparent 34%, rgba(5, 7, 6, 0.72) 100%);
}

.shell {
  position: fixed;
  inset: 0;
  z-index: 2;
  display: grid;
  grid-template-rows: auto 1fr auto auto;
  align-items: center;
  justify-items: center;
  padding: clamp(18px, 3.2vh, 34px) 20px calc(clamp(16px, 2.6vh, 26px) + env(safe-area-inset-bottom));
  pointer-events: none;
  text-align: center;
}

.shell > * { pointer-events: auto; }

/* The head's row. It draws nothing and catches nothing; it exists so the canvas
   has a measurable box to compose the particle field into, which is what keeps
   the head and the column below it in the same coordinate space. */
.stage {
  grid-row: 2;
  align-self: stretch;
  justify-self: stretch;
  width: 100%;
  pointer-events: none;
}

/* ---------------- wordmark ---------------- */

.mast { grid-row: 1; }

.wordmark {
  font-size: clamp(1.7rem, 6.4vw, 4.2rem);
  font-weight: 800;
  letter-spacing: 0.055em;
  line-height: 1;
  white-space: nowrap;
  /* The distress: thin horizontal notches cut through the glyphs. Kept shallow
     so the wordmark stays legible at every size. */
  -webkit-mask-image: repeating-linear-gradient(to bottom,
    #000 0 5px, rgba(0, 0, 0, 0.72) 5px 6px, #000 6px 13px, rgba(0, 0, 0, 0.55) 13px 14px);
  mask-image: repeating-linear-gradient(to bottom,
    #000 0 5px, rgba(0, 0, 0, 0.72) 5px 6px, #000 6px 13px, rgba(0, 0, 0, 0.55) 13px 14px);
}

.wm-stonk { color: var(--white); }
.wm-funbot { color: var(--green); }

.tagline {
  margin-top: 10px;
  font-size: clamp(0.5rem, 1.5vw, 0.66rem);
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  color: rgba(124, 136, 150, 0.75);
  white-space: nowrap;
  /* Halo fragments drift through this line and the two below it, so each keeps
     a little of its own ground. */
  text-shadow: 0 0 8px rgba(5, 7, 6, 0.9);
}

/* ---------------- flanking labels ---------------- */

/* Decorative only. "SOON" rather than the storyboard's "LIVE" — the product is
   not live and the page must not say otherwise. */
.flank {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.56rem;
  letter-spacing: 0.34em;
  color: rgba(124, 136, 150, 0.5);
  pointer-events: none;
}

.flank-l { left: 26px; }
.flank-r { right: 26px; }

/* ---------------- copy ---------------- */

.copy {
  grid-row: 3;
  position: relative;
  display: grid;
  width: 100%;
}

.panel {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  transition: opacity 220ms ease;
}

.panel[hidden] { display: none; }

.title {
  font-size: clamp(0.86rem, 2.3vw, 1.15rem);
  font-weight: 700;
  letter-spacing: 0.30em;
  text-indent: 0.30em;
  color: var(--white);
  text-shadow: 0 0 10px rgba(5, 7, 6, 0.92);
}

.status {
  margin-top: 10px;
  font-size: clamp(0.66rem, 1.7vw, 0.82rem);
  letter-spacing: 0.36em;
  text-indent: 0.36em;
  color: var(--green);
  min-height: 1.2em;
  text-shadow: 0 0 10px rgba(5, 7, 6, 0.92);
}

/* The final line is deliberately lower-case and unspaced — it reads as an
   admission, not as a status badge. */
body.state-final .status {
  letter-spacing: 0.10em;
  text-indent: 0.10em;
  text-transform: none;
  color: rgba(232, 242, 236, 0.82);
}

.cta {
  margin-top: clamp(18px, 3vh, 28px);
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  min-height: 48px;
  padding: 0 clamp(18px, 3vw, 28px);
  font: inherit;
  font-size: clamp(0.72rem, 1.8vw, 0.86rem);
  font-weight: 700;
  letter-spacing: 0.24em;
  text-indent: 0.24em;
  color: var(--green);
  background: #000;
  border: 1px solid var(--green);
  border-radius: 3px;
  cursor: pointer;
  transition: box-shadow 180ms ease, color 180ms ease, border-color 180ms ease, opacity 180ms ease;
}

.cta .bracket { color: rgba(34, 198, 125, 0.55); text-indent: 0; }

.cta:hover:not(:disabled),
.cta:focus-visible {
  color: var(--green-hi);
  border-color: var(--green-hi);
  box-shadow: 0 0 0 1px rgba(74, 222, 148, 0.25), 0 0 22px rgba(34, 198, 125, 0.22);
}

.cta:focus-visible { outline: 2px solid var(--green-hi); outline-offset: 3px; }
.cta:disabled { opacity: 0.32; cursor: default; }

.note {
  margin-top: 14px;
  font-size: 0.55rem;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  color: rgba(124, 136, 150, 0.55);
  opacity: 0;
  transition: opacity 320ms ease;
}

body.state-final .note { opacity: 1; }

/* ---------------- crash panel ---------------- */

.panel-crash { gap: 8px; }

.crash-exe {
  font-size: clamp(0.8rem, 2.1vw, 1rem);
  font-weight: 700;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  color: var(--green-hi);
  text-shadow: 1px 0 0 rgba(139, 92, 246, 0.75), -1px 0 0 rgba(34, 198, 125, 0.6);
}

/* The debris is still flying behind this, so the terminal lines carry their own
   contrast rather than relying on an empty background. */
.crash-line {
  font-size: clamp(0.58rem, 1.5vw, 0.7rem);
  letter-spacing: 0.14em;
  color: rgba(232, 242, 236, 0.92);
  text-shadow: 0 0 10px rgba(5, 7, 6, 0.95), 0 0 3px rgba(5, 7, 6, 1);
}

.crash-line:first-of-type { color: var(--green-hi); }

/* ---------------- links ---------------- */

.links {
  grid-row: 4;
  margin-top: clamp(18px, 3.4vh, 34px);
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 0.62rem;
  letter-spacing: 0.3em;
}

.links a {
  color: rgba(124, 136, 150, 0.8);
  text-decoration: none;
  padding: 8px 6px;
  text-indent: 0.3em;
  transition: color 160ms ease;
}

.links a:hover, .links a:focus-visible { color: var(--green); }
.links a:focus-visible { outline: 2px solid var(--green-hi); outline-offset: 2px; }
.links .sep { color: rgba(124, 136, 150, 0.35); }

/* ---------------- sequence dressing ---------------- */

body.state-boot .mast,
body.state-boot .copy,
body.state-boot .links,
body.state-boot .flank { opacity: 0; }

.mast, .copy, .links, .flank { transition: opacity 520ms ease 160ms; }

/* Impact: a short shove, then the glitch takes over. */
body.state-impact .shell { animation: shove 460ms steps(2, end) 1; }

@keyframes shove {
  0%   { transform: translate3d(0, 0, 0); }
  18%  { transform: translate3d(-9px, 5px, 0); }
  36%  { transform: translate3d(7px, -4px, 0); }
  54%  { transform: translate3d(-5px, -3px, 0); }
  72%  { transform: translate3d(4px, 2px, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* Glitch: text tears sideways and separates into green/purple. Restrained and
   short — the page has to read as broken, not as an unreadable hacker screen. */
body.state-glitch .panel-crash { animation: tear 260ms steps(3, end) infinite; }
body.state-glitch .wordmark { animation: split 200ms steps(2, end) infinite; }

@keyframes tear {
  0%   { transform: translate3d(0, 0, 0); }
  33%  { transform: translate3d(-6px, 1px, 0); }
  66%  { transform: translate3d(5px, -1px, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

@keyframes split {
  0%   { transform: translate3d(0, 0, 0); filter: none; }
  50%  { transform: translate3d(3px, 0, 0);
         filter: drop-shadow(-3px 0 0 rgba(139, 92, 246, 0.8)) drop-shadow(3px 0 0 rgba(34, 198, 125, 0.6)); }
  100% { transform: translate3d(0, 0, 0); filter: none; }
}

/* The head is rebuilding and nothing is claimed yet, so the lower copy stays
   out of the way until the FINAL state has something true to say. */
body.state-rebuild .copy { opacity: 0; transition: opacity 180ms ease; }

/* ---------------- mobile ----------------
 *
 * A genuine vertical composition rather than a scaled desktop: the head keeps
 * the upper two-thirds, the copy stacks under it, the flanking labels go (there
 * is no room for them beside the head), and the button stays thumb-sized.
 */

@media (max-width: 700px) {
  .flank { display: none; }

  /* Four rows, and the slack all belongs to the stage: the head grows into it
     instead of the layout growing a dead zone. The spacer row that used to sit
     under the links is gone — it bought the copy a little height by leaving a
     band of nothing below the fold, which is the opposite of composed. The
     bottom padding is the deliberate breathing room, and it is real: enough to
     sit the links clear of the browser's own toolbar rather than on it. */
  .shell {
    grid-template-rows: auto 1fr auto auto;
    padding: clamp(12px, 2.2vh, 20px) 16px calc(clamp(20px, 3.4vh, 34px) + env(safe-area-inset-bottom));
  }

  .wordmark { letter-spacing: 0.035em; }
  .tagline { margin-top: 6px; letter-spacing: 0.3em; text-indent: 0.3em; }

  /* The gaps below are tightened rather than the type being shrunk — the sizes
     are the desktop ones. The head is sized to the stage row, so the copy sits
     just under it without either one being compressed to make room. */
  .title { font-size: 0.92rem; }
  .status { margin-top: 6px; letter-spacing: 0.28em; text-indent: 0.28em; }
  .note { margin-top: 10px; }

  .cta {
    margin-top: 16px;
    min-height: 54px;
    width: min(320px, 84vw);
    justify-content: center;
  }

  .links { margin-top: 14px; gap: 6px; }

  /* 44px minimum tap target, and the muted grey goes to full opacity: at 0.8
     over this background it measured about 3.9:1, under the 4.5:1 floor. */
  .links a {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    min-width: 44px;
    padding: 0 14px;
    color: var(--grey);
  }
}

/* The bottom of the layout viewport sits behind the browser's own toolbar on a
   phone, which is what put the button and the links out of sight. Sized to the
   small viewport instead, the whole column fits above the chrome. The canvas
   keeps the full window, so the particle field still bleeds behind it — and the
   head is composed against the stage row inside this box, not against the
   window, so the two cannot disagree about where the middle of the page is. */
@supports (height: 100svh) {
  @media (max-width: 700px) {
    .shell { bottom: auto; height: 100svh; }
  }
}

@media (max-height: 560px) {
  .tagline, .note { display: none; }
  .cta { min-height: 42px; }
}

/* ---------------- reduced motion ----------------
 *
 * Every control still works; only the aggressive movement goes. No shove, no
 * flashing loops, and the reveal is a plain fade.
 */

@media (prefers-reduced-motion: reduce) {
  body.state-impact .shell,
  body.state-glitch .panel-crash,
  body.state-glitch .wordmark { animation: none; }

  .mast, .copy, .links, .flank { transition: opacity 200ms ease; }
  .cta, .note, .panel, .links a { transition: none; }
}
