/* ============================================================
   deslop.media · AI Futures — HUB TAKEOVER · "Assembly" (Claude Design).
   Loaded ONLY by /aifutures/hub-preview.html (the staged hub
   takeover). It does NOT touch franchise.css or any live asset; it
   layers a full-viewport intro (#hub-intro) ABOVE the existing
   diptych and styles the 5-act "Assembly" animation + the settle
   handoff into the two reading cards (#hub-cards).

   Tokens, Inter / Inter Tight and the ONE easing are inherited from
   franchise.css (this page loads franchise.css first). Dark-native,
   flat, single Volt (#CEFF00), no-crown, Economist register. The
   canvas (particles + 270° area-true gauge + dials) is drawn by
   hub-intro.js; the DOM act layers below are cross-faded by opacity.
   ============================================================ */

/* JetBrains Mono — the label/meta face. franchise.css self-hosts
   Inter + Inter Tight but not the mono; reuse the panel's same-origin
   woff2 by ABSOLUTE path (no CDN, no reader-IP leak). */
@font-face {
  font-family: 'JetBrains Mono';
  src: url('/aifutures/ai-speedometer/fonts/jetbrains-mono-latin-var.woff2') format('woff2');
  font-weight: 400 600; font-style: normal; font-display: swap;
}

/* Motion keyframes — the noise takes blink, the hint pulses. */
@keyframes hubBlink { 0%, 100% { opacity: 0.55; } 50% { opacity: 0.12; } }

/* ────────────────────────────────────────────────────────────────
   THE TAKEOVER — full-viewport intro, mobile-first @375 and desktop.
   Fixed-position over the page; released (display:none) on settle.
   ──────────────────────────────────────────────────────────────── */
#hub-intro {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  background: var(--aff-bg);
  /* iOS safe-area + dynamic viewport so the full-screen takeover is
     truly full on mobile (no jump when the URL bar collapses). */
  min-height: 100vh;
  min-height: 100dvh;
  padding: env(safe-area-inset-top) 20px env(safe-area-inset-bottom);
  box-sizing: border-box;
  opacity: 1;
  transition: opacity .45s var(--aff-ease);
}
/* Settled / dismissed — the intro fades, then is removed from flow so
   the cards beneath take full interactivity and the page scrolls. */
#hub-intro.is-done {
  opacity: 0;
  pointer-events: none;
}
#hub-intro[hidden] { display: none !important; }

/* Inner overlay: the stage fills the whole takeover; the brand whisper
   floats top, and the progress rail + controls pin to the bottom over
   the canvas art. Everything but the stage sits at z-index 2. */
.hub-intro__inner {
  position: absolute;
  inset: 0;
  width: 100%;
  margin: 0;
  display: block;
}

/* Brand whisper at the very top — brand FIRST, never replaced. Quiet,
   so the takeover reads as a deslop.media surface without shouting. */
.hub-intro__brand {
  display: inline-flex;
  align-items: baseline;
  font-family: var(--aff-font-editorial);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: -0.01em;
  color: var(--aff-text-2);
}
.hub-intro__brand .aff-dot {
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--aff-volt); display: inline-block;
  margin: 0 1px 1px;
}
.hub-intro__brand .hub-intro__franchise {
  color: var(--aff-text-3); font-weight: 500; margin-left: 6px;
}

/* ────────────────────────────────────────────────────────────────
   ===== THE VISUAL STAGE =====
   Full-viewport canvas (particles + gauge + dials) with five DOM act
   layers cross-faded above it. The stage fills the takeover so the
   gauge geometry (cy = H*0.63 desktop / H*0.605 mobile) has room to
   read; the brand whisper, caption, rail and controls float over it.
   ──────────────────────────────────────────────────────────────── */
.hub-intro__stage {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  z-index: 0;
}
/* Brand whisper — top-left over the stage (matches the design chrome). */
.hub-intro__brand {
  position: absolute;
  top: clamp(14px, 3.4vw, 22px);
  left: clamp(16px, 4vw, 30px);
  z-index: 3;
  margin: 0;
}
/* Skip — top-right over the stage. */
.hub-intro__controls {
  position: absolute;
  top: clamp(12px, 3vw, 20px);
  right: clamp(14px, 4vw, 26px);
  z-index: 3;
}
/* Progress rail — bottom-centre over the stage. */
.hub-intro__rail {
  position: absolute;
  bottom: clamp(18px, 4vw, 30px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 3;
}

.hub-intro__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

/* ── the five DOM act layers — absolutely positioned, cross-faded by
   opacity (only the active layer is visible). Text stays in the DOM
   for accessibility/SEO. hub-intro.js toggles .is-on. ── */
.hub-act {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  /* outgoing layer clears quickly so headlines at the same anchor don't
     superimpose during the cross-fade. */
  transition: opacity .34s var(--aff-ease);
}
.hub-act.is-on {
  opacity: 1;
  /* incoming layer fades in a touch slower, after the outgoing has mostly
     cleared — reads as one act handing off to the next, not a ghost. */
  transition: opacity .6s var(--aff-ease) .12s;
}
/* the active layer's interactive children (router-safe: none here) */
.hub-act.is-on [data-syn],
.hub-act.is-on .hub-center { pointer-events: none; }

/* centred act block (Act 0 headline, Act 4 settle beat) */
.hub-act__center {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 24px;
}
.hub-act__center > * { position: relative; z-index: 2; }

/* top-anchored act block (Acts 1–3 eyebrow + headline + subhead) */
.hub-act__top {
  position: absolute;
  top: clamp(58px, 10vh, 116px);
  left: 0; right: 0;
  text-align: center;
  padding: 0 24px;
}

/* bottom-anchored act block (Act 2/3 meta lines) */
.hub-act__foot {
  position: absolute;
  bottom: clamp(92px, 15vh, 132px);
  left: 0; right: 0;
  text-align: center;
  padding: 0 24px;
}

/* ── Type roles (from the design tokens) ── */
.hub-eyebrow {
  font-family: var(--aff-font-mono);
  font-size: clamp(9px, 2.8vw, 11px);
  font-weight: 600;
  letter-spacing: 0.18em;
  color: var(--aff-volt);
}
.hub-h1 {
  font-family: var(--aff-font-editorial);
  font-weight: 700;
  font-size: clamp(34px, 8.5vw, 68px);
  letter-spacing: -0.035em;
  line-height: 0.98;
  margin-top: 16px;
  color: var(--aff-text);
  text-shadow: 0 2px 28px rgba(16, 24, 32, 0.92);
}
.hub-h2 {
  font-family: var(--aff-font-editorial);
  font-weight: 700;
  font-size: clamp(22px, 5.6vw, 38px);
  letter-spacing: -0.03em;
  line-height: 1.05;
  margin-top: 10px;
  color: var(--aff-text);
}
.hub-h2--wide {
  font-size: clamp(26px, 6.6vw, 46px);
  line-height: 1.04;
  margin-top: 14px;
  max-width: 18ch;
  margin-left: auto; margin-right: auto;
}
.hub-sub {
  font-family: var(--aff-font-sans);
  font-size: clamp(14px, 3.6vw, 18px);
  color: var(--aff-text-2);
  margin: 16px auto 0;
  max-width: 28ch;
  line-height: 1.5;
  text-shadow: 0 1px 18px rgba(16, 24, 32, 0.9);
}
.hub-sub--wide {
  font-size: clamp(12px, 3vw, 15px);
  max-width: 44ch;
  margin-top: 10px;
  text-shadow: none;
}
.hub-settle {
  font-family: var(--aff-font-editorial);
  font-weight: 600;
  font-size: clamp(16px, 4.4vw, 22px);
  letter-spacing: -0.015em;
  color: var(--aff-text);
  margin: 16px auto 0;
  max-width: 30ch;
  line-height: 1.3;
}

/* Act 0 — the noisy field of conflicting takes */
.hub-noise {
  position: absolute;
  font-family: var(--aff-font-mono);
  font-size: clamp(11px, 3vw, 13px);
  color: var(--aff-text-2);
  white-space: nowrap;
  animation: hubBlink 3.4s ease-in-out infinite;
  z-index: 1;
}
.hub-noise--i { font-family: var(--aff-font-sans); font-style: italic; font-size: clamp(13px, 3.4vw, 16px); }
.hub-noise:nth-of-type(2n) { color: var(--aff-text-3); animation-duration: 3.9s; }
.hub-noise:nth-of-type(3n) { animation-duration: 2.9s; animation-delay: .5s; }
.hub-noise:nth-of-type(4n) { animation-duration: 4.2s; animation-delay: .2s; }

/* Act 1 — the two synthesis captions (JS positions left/top) */
.hub-syn {
  position: absolute;
  transform: translate(-50%, 0);
  width: 260px; max-width: 80vw;
  text-align: center;
}
.hub-syn__line {
  font-family: var(--aff-font-editorial);
  font-weight: 600;
  font-size: clamp(15px, 3.6vw, 19px);
  letter-spacing: -0.015em;
  color: var(--aff-text);
  line-height: 1.22;
  margin-top: 6px;
}

/* Acts 2 & 3 — the gauge centre readout (JS positions left/top) */
.hub-center {
  position: absolute;
  transform: translate(-50%, -50%);
  text-align: center;
  z-index: 5;
}
.hub-center__label {
  font-family: var(--aff-font-mono);
  font-size: 9px; font-weight: 500;
  letter-spacing: 0.16em;
  color: var(--aff-text-3);
}
.hub-center__band {
  font-family: var(--aff-font-editorial);
  font-weight: 800;
  font-size: clamp(26px, 6vw, 40px);
  letter-spacing: -0.02em;
  color: var(--aff-text);
  line-height: 1;
  margin: 3px 0;
}
.hub-center__note {
  font-family: var(--aff-font-mono);
  font-size: 8.5px; font-weight: 500;
  letter-spacing: 0.14em;
  color: var(--aff-text-2);
}

/* Act 3 — the four dial labels (JS positions left/top under each dial) */
.hub-dial-label {
  position: absolute;
  transform: translateX(-50%);
  font-family: var(--aff-font-mono);
  font-size: 9px;
  letter-spacing: 0.08em;
  color: var(--aff-text-2);
  white-space: nowrap;
}
/* @375 the four dials sit close; shrink the labels + drop tracking so the
   long ones (OVERBUILD / WORKFORCE) don't touch. */
@media (max-width: 639px) {
  .hub-dial-label { font-size: 7.5px; letter-spacing: 0.02em; }
  /* Top-of-takeover overlap @375: the brand whisper (top-left) and the
     Build/Skip controls (top-right) are two independently absolute-positioned
     elements at the same top band — fine at desktop widths, but the two-pill
     control row is wide enough that its left edge can collide with the brand
     text at narrow viewports (no shared container gives them mutual collision
     avoidance). Drop the controls below the brand row instead of widening it;
     spacing-only, no layout/behavior change. */
  .hub-intro__controls { top: clamp(46px, 11vw, 54px); }
}

/* Act 2/3 foot meta */
.hub-foot__meta {
  font-family: var(--aff-font-mono);
  font-size: clamp(8.5px, 2.4vw, 10px);
  letter-spacing: 0.08em;
  color: var(--aff-text-2);
  line-height: 1.8;
}
.hub-foot__names { color: var(--aff-text-3); }
.hub-foot__tag {
  font-family: var(--aff-font-mono);
  font-size: 9px;
  letter-spacing: 0.16em;
  color: var(--aff-text-3);
  margin-top: 9px;
}

/* The act caption — the visible headline copy now lives IN each act
   layer (accessible DOM, cross-faded). This element is kept as the
   aria-live announcement so screen-reader users hear each act's one-
   line summary as it changes; it is visually hidden. */
.hub-intro__cap {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap;
}

/* Act counter rail (1 · 2 · 3 · 4 · 5) — quiet progress, mono. */
.hub-intro__rail {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--aff-font-mono);
  font-size: 10px;
  letter-spacing: 0.1em;
}
.hub-intro__dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--aff-border);
  transition: background .2s var(--aff-ease), transform .2s var(--aff-ease);
}
.hub-intro__dot.is-active { background: var(--aff-volt); transform: scale(1.25); }
.hub-intro__dot.is-past { background: var(--aff-text-3); }

/* Controls row — Build/Replay chip + Skip. Mono pills, franchise tab look. */
.hub-intro__controls {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  justify-content: center;
}
.hub-intro__btn {
  font-family: var(--aff-font-mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--aff-text-2);
  background: transparent;
  border: 1px solid var(--aff-border);
  border-radius: 6px;
  padding: 8px 14px;
  cursor: pointer;
  transition: color .2s var(--aff-ease), border-color .2s var(--aff-ease), background .2s var(--aff-ease);
}
.hub-intro__btn:hover { color: var(--aff-text); border-color: var(--aff-text-3); }
.hub-intro__btn:focus-visible { outline: 2px solid var(--aff-volt); outline-offset: 2px; }
.hub-intro__btn--skip {
  color: var(--aff-volt);
  border-color: var(--aff-border);
}
.hub-intro__btn--skip:hover { color: var(--aff-volt-hover); border-color: var(--aff-volt); }

/* ────────────────────────────────────────────────────────────────
   THE CARDS — the existing diptych, wrapped in #hub-cards so the
   takeover can reveal them. They start hidden while the intro plays,
   then scale-in from below on settle. Repeat visitors (hubIntroSeen)
   skip the intro and these are shown immediately, no motion.
   ──────────────────────────────────────────────────────────────── */
#hub-cards {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .5s var(--aff-ease), transform .5s var(--aff-ease);
}
#hub-cards.is-revealed {
  opacity: 1;
  transform: none;
}
/* No-intro path (repeat visit / reduced-motion / no-JS): show at rest. */
#hub-cards.no-intro {
  opacity: 1;
  transform: none;
  transition: none;
}

/* ────────────────────────────────────────────────────────────────
   REDUCED MOTION — the takeover paints its settled face and the cards
   are shown at rest; no transforms, no autoplay. hub-intro.js also
   short-circuits, but this is the CSS belt-and-braces.
   ──────────────────────────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  #hub-intro { transition: opacity .2s var(--aff-ease); }
  #hub-cards { transition: opacity .2s var(--aff-ease); transform: none; }
  #hub-cards.is-revealed { transform: none; }
  .hub-intro__cap { transition: opacity .2s var(--aff-ease); }
  .hub-intro__dot { transition: none; }
}
