/* ============================================================================
 * theme.css — Anycast arcade design system
 * Tokens + primitives derived from the ANYCAST OCTOBER flyer.
 * PHASE 2: these tokens are emitted from D1 `events.theme_json` into a
 * <style> block so each event can re-skin without a deploy.
 * ==========================================================================*/

/* ==========================================================================
   FONTS — self-hosted.
   Previously an @import from fonts.googleapis.com, which disclosed every
   visitor's IP to Google on page load (including while they were typing
   allergy details into the registration form), could not carry an SRI
   hash, and failed over file://. All three families are SIL OFL 1.1, so
   self-hosting is expressly permitted.

   Inter is a variable font: one file serves 400-800, which is why there
   is a single src for five weights.
   ========================================================================== */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/inter-var-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* Accented names (José, Müller, Łukasz) must not fall back mid-word. */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/inter-var-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7,
    U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F,
    U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F,
    U+A720-A7FF;
}

@font-face {
  font-family: 'Anton';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/anton-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Press Start 2P';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/press-start-2p-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* -- colour, sampled from the flyer -------------------------------- */
  --bg-deep: #0b0614;
  --bg-indigo: #1b1046;
  --bg-violet: #2a1a5e;
  --orange: #f6821f;
  --orange-bright: #fbad41;
  --orange-deep: #b8561a;
  --orange-shadow: #7a3810;
  --purple-neon: #7b5cff;
  --cyan-neon: #3ba5ff;

  /* Bare "r, g, b" companions to the hexes above.
   *
   * Glows, borders and translucent panels need rgba() with varying alpha,
   * and you cannot derive that from a hex custom property in CSS alone.
   * Keeping these in sync is what makes the admin theme editor actually
   * re-skin the whole page instead of just the few solid-colour elements.
   * store.js writes both halves whenever a colour changes. */
  --orange-rgb: 246, 130, 31;
  --purple-rgb: 123, 92, 255;
  --cyan-rgb: 59, 165, 255;
  --bg-deep-rgb: 11, 6, 20;

  --text: #ffffff;
  --text-muted: #b9b4cc;
  --text-dim: #948ead; /* 4.6:1 even over the lightest gradient stop */

  --panel-bg: rgba(var(--bg-deep-rgb), 0.66);
  --panel-border: rgba(var(--orange-rgb), 0.45);
  --hairline: rgba(255, 255, 255, 0.1);

  /* -- type ----------------------------------------------------------- */
  --font-display: 'Anton', 'Haettenschweiler', 'Arial Narrow', 'Impact',
    sans-serif;
  --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Helvetica Neue', Arial, sans-serif;
  --font-arcade: 'Press Start 2P', 'Courier New', monospace;
  /* UI chrome: labels, tabs, small caps. Referenced by app.css and
     admin.css, which previously used an undefined token. */
  --font-ui: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Helvetica Neue', Arial, sans-serif;

  /* -- geometry -------------------------------------------------------- */
  --radius: 14px;
  --radius-sm: 8px;
  --page-max: 940px;

  /* -- motion ---------------------------------------------------------- */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ==========================================================================
   Reset
   ========================================================================== */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

/* The `hidden` attribute must actually hide things.
 *
 * `[hidden] { display: none }` comes from the UA stylesheet, and ANY author
 * rule that sets `display` beats it — author origin wins over user-agent
 * origin regardless of specificity. `.notfound { display: grid }` was
 * silently doing exactly that: the "we can't find that ticket" panel
 * rendered above every valid ticket, on every confirmation page.
 *
 * !important is warranted here because this is a semantic guarantee, not a
 * style preference. JavaScript across this site toggles `el.hidden` to show
 * and hide the not-found card, the optional form questions, the admin tab
 * panels and the submit notices; every one of those becomes a latent bug the
 * moment someone gives the element a display value. Anything that genuinely
 * needs to be laid out while hidden should use a different mechanism. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  min-height: 100vh;
  font-family: var(--font-body);
  color: var(--text);
  background-color: var(--bg-deep);
  line-height: 1.55;
  overflow-x: hidden;
}

/* The violet dome from the flyer.
 *
 * This lives on the (already fixed) starfield canvas rather than on <body>
 * with `background-attachment: fixed`. iOS Safari handles fixed background
 * attachment badly — it silently degrades to `scroll` and can cause heavy
 * repaints while scrolling. Painting it on an element that is genuinely
 * position:fixed avoids the problem entirely, and it still renders if the
 * canvas JS never runs. */
#starfield {
  background-color: var(--bg-deep);
  background-image:
    radial-gradient(
      120% 78% at 50% -12%,
      var(--bg-violet) 0%,
      var(--bg-indigo) 38%,
      rgba(var(--bg-deep-rgb), 0) 78%
    ),
    radial-gradient(
      90% 50% at 50% 108%,
      rgba(var(--purple-rgb), 0.16) 0%,
      rgba(var(--bg-deep-rgb), 0) 70%
    );
}

img,
svg {
  display: block;
  max-width: 100%;
}

button,
input,
select,
textarea {
  font: inherit;
  color: inherit;
}

a {
  color: var(--orange-bright);
  text-decoration-color: rgba(251, 173, 65, 0.4);
  text-underline-offset: 3px;
}

a:hover {
  text-decoration-color: currentColor;
}

/* Visible focus for keyboard users everywhere. */
:focus-visible {
  outline: 3px solid var(--cyan-neon);
  outline-offset: 3px;
  border-radius: 4px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ==========================================================================
   Ambient layers: starfield canvas, scanlines, neon page frame
   ========================================================================== */
/* <canvas> is a REPLACED element: with `inset: 0` and `width: auto` the used
 * width comes from the intrinsic canvas size (300x150 by default), not from
 * the inset box — so it does NOT stretch to the viewport the way a <div>
 * would. The explicit 100%/100% is required, and without it the background
 * gradient below only covers the top-left corner. */
#starfield {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
}

/* CRT scanlines — very low contrast so text stays legible. */
.scanlines::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    rgba(255, 255, 255, 0.025) 0px,
    rgba(255, 255, 255, 0.025) 1px,
    transparent 1px,
    transparent 3px
  );
  mix-blend-mode: overlay;
}

/* Everything real sits above the ambient layers. */
.page {
  position: relative;
  z-index: 3;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 34px 26px 90px;
}

/* -- small screens ------------------------------------------------------
 * Narrow phones (iPhone SE is 320px) lose a lot of usable width to the
 * page gutter, so it shrinks. */
@media (max-width: 480px) {
  .page {
    padding: 24px 16px 70px;
  }
}

@media (max-width: 360px) {
  .page {
    padding: 20px 12px 64px;
  }
}

/* ==========================================================================
   Typography primitives
   ========================================================================== */

/* Big stacked flyer headline: orange face + hard extrude + halo. */
.display {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0.012em;
  line-height: 0.84;
  margin: 0;
  color: var(--orange-bright);
  text-transform: uppercase;
  text-shadow:
    0 2px 0 var(--orange-deep),
    0 4px 0 var(--orange-deep),
    0 6px 0 #9c4a16,
    0 8px 0 var(--orange-shadow),
    0 10px 22px rgba(0, 0, 0, 0.6),
    0 0 34px rgba(var(--orange-rgb), 0.55),
    0 0 76px rgba(var(--orange-rgb), 0.3);
}

/* Small uppercase arcade chrome — used sparingly, it is hard to read at length */
.arcade-label {
  font-family: var(--font-arcade);
  font-size: 0.6rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--orange);
  line-height: 1.8;
}

.eyebrow {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.34em;
  text-transform: uppercase;
  color: var(--orange);
  margin: 0;
}

.lede {
  font-size: clamp(1.02rem, 2.4vw, 1.4rem);
  font-weight: 600;
  color: #efecff;
  margin: 0;
}

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

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

/* Short glowing rule used under the tagline on the flyer. */
.rule {
  width: min(420px, 70%);
  height: 1px;
  margin: 26px auto;
  border: 0;
  background: linear-gradient(
    to right,
    transparent,
    rgba(var(--orange-rgb), 0.75),
    transparent
  );
  box-shadow: 0 0 10px rgba(var(--orange-rgb), 0.5);
}

/* ==========================================================================
   Panels
   ========================================================================== */
.panel {
  position: relative;
  background: var(--panel-bg);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius);
  padding: 30px clamp(18px, 4vw, 38px);
  backdrop-filter: blur(6px);
  box-shadow:
    0 0 24px rgba(var(--orange-rgb), 0.15),
    inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

.panel--quiet {
  border-color: rgba(255, 255, 255, 0.12);
  box-shadow: none;
}

.panel__title {
  font-family: var(--font-arcade);
  font-size: 0.78rem;
  letter-spacing: 0.4em;
  text-transform: uppercase;
  color: var(--orange-bright);
  text-align: center;
  margin: 0 0 26px;
  text-shadow: 0 0 16px rgba(var(--orange-rgb), 0.6);
}

/* ==========================================================================
   Buttons
   ========================================================================== */
.btn {
  --btn-accent: var(--orange);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 15px 30px;
  border: 1px solid var(--btn-accent);
  border-radius: 10px;
  background: linear-gradient(
    180deg,
    rgba(var(--orange-rgb), 0.24),
    rgba(var(--orange-rgb), 0.08)
  );
  color: #fff;
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition:
    transform 0.16s var(--ease),
    box-shadow 0.16s var(--ease),
    background 0.16s var(--ease);
  box-shadow:
    0 0 18px rgba(var(--orange-rgb), 0.28),
    inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.btn:hover:not(:disabled) {
  transform: translateY(-2px);
  background: linear-gradient(
    180deg,
    rgba(var(--orange-rgb), 0.4),
    rgba(var(--orange-rgb), 0.16)
  );
  box-shadow:
    0 0 30px rgba(var(--orange-rgb), 0.5),
    inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.btn:active:not(:disabled) {
  transform: translateY(0);
}

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* The primary CTA — arcade cabinet start button. */
.btn--start {
  font-family: var(--font-arcade);
  font-size: 0.86rem;
  letter-spacing: 0.1em;
  padding: 20px 42px;
  border-width: 2px;
  border-radius: 12px;
}

.btn--ghost {
  background: rgba(255, 255, 255, 0.04);
  border-color: rgba(255, 255, 255, 0.24);
  box-shadow: none;
  font-weight: 600;
  text-transform: none;
  letter-spacing: 0.02em;
}

.btn--ghost:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.42);
  box-shadow: none;
}

.btn--block {
  width: 100%;
}

/* ==========================================================================
   Motion
   ========================================================================== */
@keyframes pulse-glow {
  0%,
  100% {
    opacity: 1;
    filter: drop-shadow(0 0 6px rgba(var(--orange-rgb), 0.8));
  }
  50% {
    opacity: 0.55;
    filter: drop-shadow(0 0 16px rgba(var(--orange-rgb), 1));
  }
}

@keyframes ring-spin {
  to {
    transform: rotate(360deg);
  }
}

@keyframes rise-in {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Reveal-on-scroll.
 *
 * The hidden state is scoped to .has-reveal, which fx.js only adds once it has
 * confirmed IntersectionObserver is available and it is about to start
 * observing. Without that guard, any JS failure would leave the agenda
 * permanently invisible — content must never depend on script to be readable.
 */
.has-reveal .reveal {
  opacity: 0;
}

.has-reveal .reveal.is-visible {
  animation: rise-in 0.62s var(--ease) forwards;
}

/* Respect the OS setting. Nothing below is load-bearing for comprehension. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .has-reveal .reveal {
    opacity: 1;
  }

  .scanlines::after {
    display: none;
  }
}

/* ==========================================================================
   SKIP LINK
   Off-screen until focused. Every page puts a preview bar, a logo and
   (on admin) a warning plus five tabs ahead of the real content.
   ========================================================================== */
.skip-link {
  position: absolute;
  left: 8px;
  top: -60px;
  z-index: 200;
  padding: 11px 18px;
  border-radius: 0 0 8px 8px;
  background: var(--orange);
  color: #1a0d02;
  font-family: var(--font-ui);
  font-size: 0.82rem;
  font-weight: 700;
  text-decoration: none;
  transition: top 0.14s ease-out;
}

.skip-link:focus {
  top: 0;
  outline: 3px solid var(--cyan-neon);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .skip-link {
    transition: none;
  }
}
