/* ==========================================================================
   Emergent Behavior Studios
   Design tokens sampled from the Desktop Ball press kit artwork.
   Contrast against --ink: --text 16:1, --text-muted 7.6:1, --accent 6.4:1.
   ========================================================================== */

/* ---------- Fonts ---------- */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/poppins-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/fonts/poppins-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('/fonts/poppins-800.woff2') format('woff2');
}

/* ---------- Tokens ---------- */
:root {
  --ink: #0E0C18;
  --surface: #171528;
  --surface-2: #201D33;
  --line: rgba(191, 199, 236, 0.12);
  --text: #ECEAF4;
  --text-muted: #9CA2B1;
  --accent: #EF61A7;
  --accent-hot: #FA7DC8;
  --accent-deep: #CD3A77;
  --teal: #41EAD4;

  --font: 'Poppins', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --measure: 62ch;
  --radius: 14px;
  --gap: clamp(1rem, 3vw, 2rem);
  --section-y: clamp(4rem, 10vw, 8rem);
  --header-h: 64px;

  /* Per-product highlight. Desktop Ball is pink; the FIG page swaps it to teal
     so underlines, focus rings and glows follow the page rather than the site. */
  --brand: var(--accent);

  /* Overshoot: things settle like a ball, not like a panel. */
  --ease-spring: cubic-bezier(0.34, 1.5, 0.64, 1);
  --ease-out: cubic-bezier(0.22, 1, 0.36, 1);

  /* How strongly the ball overlay sits over the page. 1 = fully opaque balls;
     lower it toward ~0.7 to soften them over the hero copy. */
  --ball-max-opacity: 1;
}

.page-fig { --brand: var(--teal); }

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

html {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-h) + 1rem);
  -webkit-text-size-adjust: 100%;
  scrollbar-width: thin;
  scrollbar-color: var(--accent-deep) var(--ink);
}

body {
  font-family: var(--font);
  background: var(--ink);
  color: var(--text);
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, video { display: block; max-width: 100%; height: auto; }
a { color: inherit; }

::selection { background: var(--brand); color: var(--ink); }

/* Author-level `video { display: block }` above beats the UA stylesheet's
   `[hidden] { display: none }`, so the hidden attribute would do nothing on
   the hero fallback video and the faded overlay. Restore it explicitly. */
[hidden] { display: none !important; }

/* ---------- Accessibility helpers ---------- */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.skip-link {
  position: absolute;
  top: 0; left: 0;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  background: var(--accent);
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
  transform: translateY(-120%);
  transition: transform 0.3s var(--ease-spring);
}
.skip-link:focus { transform: translateY(0); }

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 3px;
  border-radius: 4px;
}

/* ---------- Layout ---------- */
.container {
  width: min(100% - 2.5rem, 1120px);
  margin-inline: auto;
}

.section { padding-block: var(--section-y); }
.section--alt { background: var(--surface); }

.section__header { max-width: var(--measure); }
.section__body {
  max-width: var(--measure);
  color: var(--text-muted);
  font-size: clamp(1.05rem, 2.2vw, 1.2rem);
  text-wrap: pretty;
}
.section__header + .section__body { margin-top: 1.25rem; }

.eyebrow {
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 0.75rem;
}

h1, h2, h3 { line-height: 1.12; font-weight: 800; letter-spacing: -0.02em; }
h1 { font-size: clamp(2.4rem, 6.5vw, 4.5rem); }
h2 { font-size: clamp(1.9rem, 4.5vw, 3rem); }
h3 { font-size: clamp(1.3rem, 3vw, 1.75rem); }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  min-height: 52px;
  padding: 0.85rem 1.75rem;
  border: 2px solid transparent;
  border-radius: 999px;
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease,
              color 0.2s ease, box-shadow 0.25s ease,
              transform 0.3s var(--ease-spring);
}

/* Pink is reserved for the single primary action: Wishlist on Steam. */
.btn--primary {
  background: var(--accent);
  color: var(--ink);
}
.btn--primary:hover {
  background: var(--accent-hot);
  transform: translateY(-3px);
  box-shadow: 0 14px 30px -14px var(--accent);
}
.btn--primary:active { background: var(--accent-deep); }

.btn--ghost {
  background: transparent;
  border-color: var(--line);
  color: var(--text);
}
.btn--ghost:hover { border-color: var(--text); transform: translateY(-3px); }

.btn--teal { background: var(--teal); color: var(--ink); }
.btn--teal:hover {
  filter: brightness(1.1);
  transform: translateY(-3px);
  box-shadow: 0 14px 30px -14px var(--teal);
}

/* Every button takes the press. Last, so a click always beats the hover lift. */
.btn:active {
  transform: translateY(1px) scale(0.97);
  transition-duration: 0.08s;
}

/* ---------- Prose links ---------- */
.section__body a,
.factsheet a {
  color: var(--text);
  text-decoration-thickness: 2px;
  text-underline-offset: 4px;
  text-decoration-color: color-mix(in srgb, var(--brand) 55%, transparent);
  transition: color 0.2s ease, text-decoration-color 0.2s ease;
}
.section__body a:hover,
.factsheet a:hover { color: var(--accent-hot); text-decoration-color: var(--accent-hot); }

/* ---------- Motion ---------- */
.reveal {
  opacity: 0;
  transform: translateY(22px) scale(0.99);
  transition: opacity 0.6s var(--ease-out), transform 0.7s var(--ease-out);
  transition-delay: var(--reveal-delay, 0s);
}
.reveal.is-visible { opacity: 1; transform: none; }

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

/* Above-the-fold copy arrives line by line on load. An animation rather than a
   .reveal because this content must never depend on JavaScript to be seen. */
.intro-in > * { animation: rise-in 0.75s var(--ease-out) both; }
.intro-in > :nth-child(2) { animation-delay: 0.07s; }
.intro-in > :nth-child(3) { animation-delay: 0.14s; }
.intro-in > :nth-child(4) { animation-delay: 0.21s; }
.intro-in > :nth-child(n+5) { animation-delay: 0.28s; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--ink) 72%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s ease, background-color 0.25s ease,
              box-shadow 0.25s ease;
}
.site-header.is-scrolled {
  border-bottom-color: var(--line);
  background: color-mix(in srgb, var(--ink) 92%, transparent);
  box-shadow: 0 10px 30px -22px rgba(0, 0, 0, 0.95);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  min-height: var(--header-h);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 800;
  text-decoration: none;
  letter-spacing: -0.01em;
  flex-shrink: 0;
}
.brand img { width: 32px; height: 32px; transition: transform 0.3s var(--ease-spring); }
.brand:hover img { animation: ball-bounce 0.7s var(--ease-out); }
.brand:active img { transform: translateY(2px) scale(0.9); }

/* Pinky does what Pinky does: a hop with a little squash on the landing. */
@keyframes ball-bounce {
  0%   { transform: translateY(0)    scale(1, 1); }
  25%  { transform: translateY(-8px) scale(0.94, 1.08); }
  50%  { transform: translateY(0)    scale(1.14, 0.86); }
  72%  { transform: translateY(-4px) scale(0.98, 1.04); }
  88%  { transform: translateY(0)    scale(1.06, 0.94); }
  100% { transform: translateY(0)    scale(1, 1); }
}

/* Text-only brand, used on the Floating Island Generator page where the Pinky
   mark would be off-product. The studio name is long, so it runs a little
   smaller than the Desktop Ball wordmark. */
.brand--text { font-size: 0.95rem; letter-spacing: 0; }
@media (max-width: 480px) { .brand--text { font-size: 0.85rem; } }

.site-nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  margin-inline-start: auto;
  font-size: 0.95rem;
}
.site-nav a {
  position: relative;
  text-decoration: none;
  color: var(--text-muted);
  padding-block: 0.5rem;
  transition: color 0.2s ease;
  white-space: nowrap;
}
.site-nav a::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0.15rem;
  height: 2px;
  border-radius: 2px;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s var(--ease-out);
}
.site-nav a:hover::after,
.site-nav a[aria-current='page']::after { transform: scaleX(1); }
.site-nav a:hover { color: var(--text); }
.site-nav a[aria-current='page'] { color: var(--text); }

/* Header CTA stays hidden until the hero CTA scrolls away. */
.site-header__cta {
  min-height: 42px;
  padding: 0.5rem 1.1rem;
  font-size: 0.9rem;
  white-space: nowrap;
  flex-shrink: 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-10px) scale(0.94);
  transition: opacity 0.25s ease, transform 0.4s var(--ease-spring), visibility 0.25s;
}
.site-header__cta.is-shown { opacity: 1; visibility: visible; transform: none; }

.nav-toggle { display: none; }

@media (max-width: 1080px) {
  .site-nav { gap: 1.1rem; font-size: 0.88rem; }
}

@media (max-width: 960px) {
  .site-header__cta { display: none; }
}

@media (max-width: 820px) {
  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px; height: 44px;
    margin-inline-start: auto;
    background: none;
    border: 1px solid var(--line);
    border-radius: 10px;
    cursor: pointer;
    color: var(--text);
    transition: border-color 0.2s ease, transform 0.3s var(--ease-spring);
  }
  .nav-toggle:hover { border-color: var(--text-muted); }
  .nav-toggle:active { transform: scale(0.9); }
  .nav-toggle__bars,
  .nav-toggle__bars::before,
  .nav-toggle__bars::after {
    content: '';
    display: block;
    width: 18px; height: 2px;
    background: currentColor;
    border-radius: 2px;
    transition: transform 0.3s var(--ease-out), background-color 0.2s ease;
  }
  .nav-toggle__bars { position: relative; }
  .nav-toggle__bars::before { position: absolute; top: -6px; }
  .nav-toggle__bars::after { position: absolute; top: 6px; }

  /* Open state folds the bars into a close cross. */
  .nav-toggle[aria-expanded='true'] .nav-toggle__bars { background: transparent; }
  .nav-toggle[aria-expanded='true'] .nav-toggle__bars::before { transform: translateY(6px) rotate(45deg); }
  .nav-toggle[aria-expanded='true'] .nav-toggle__bars::after { transform: translateY(-6px) rotate(-45deg); }

  .site-nav {
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    flex-direction: column;
    align-items: flex-start;
    gap: 0;
    margin-inline-start: 0;
    padding: 0.5rem 1.25rem 1.5rem;
    background: var(--ink);
    border-bottom: 1px solid var(--line);
    font-size: 1.05rem;
    display: none;
  }
  .site-nav.is-open { display: flex; animation: nav-drop 0.28s var(--ease-out); }
  .site-nav.is-open a { animation: rise-in 0.32s var(--ease-out) both; }
  .site-nav.is-open a:nth-child(2) { animation-delay: 0.04s; }
  .site-nav.is-open a:nth-child(3) { animation-delay: 0.08s; }
  .site-nav.is-open a:nth-child(4) { animation-delay: 0.12s; }
  .site-nav.is-open a:nth-child(n+5) { animation-delay: 0.16s; }
  .site-nav a {
    width: 100%;
    padding-block: 0.9rem;
    border-bottom: 1px solid var(--line);
  }
  .site-nav a:last-child { border-bottom: 0; }
  /* Full-width underline would read as a divider here, so it becomes a dash. */
  .site-nav a::after { right: auto; width: 1.5rem; bottom: 0.55rem; }
}

@keyframes nav-drop {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: none; }
}

/* ---------- Footer ---------- */
.site-footer {
  border-top: 1px solid var(--line);
  padding-block: 3rem;
  background: var(--surface);
}
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2rem;
  align-items: center;
  justify-content: space-between;
}
.site-footer__links { display: flex; flex-wrap: wrap; gap: 1.5rem; }
.site-footer__links a {
  position: relative;
  color: var(--text-muted);
  text-decoration: none;
  transition: color 0.2s ease;
}
.site-footer__links a::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: -4px;
  height: 2px;
  border-radius: 2px;
  background: var(--brand);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s var(--ease-out);
}
.site-footer__links a:hover { color: var(--text); }
.site-footer__links a:hover::after { transform: scaleX(1); }
.site-footer__legal { color: var(--text-muted); font-size: 0.9rem; }
.music-credit {
  margin: 1rem 0 0;
  color: var(--text-muted);
  font-size: 0.75rem;
  text-align: center;
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: min(100svh, 900px);
  margin-top: calc(var(--header-h) * -1);
  padding-block: calc(var(--header-h) + 4rem) clamp(3rem, 8vw, 6rem);
  overflow: hidden;
  isolation: isolate;
}

.hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Two-layer scrim: a vertical gradient for nav and copy legibility, plus a
   left-weighted radial so the right side of the desktop footage stays visible. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(120% 90% at 12% 70%,
      color-mix(in srgb, var(--ink) 88%, transparent) 0%,
      transparent 62%),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ink) 78%, transparent) 0%,
      color-mix(in srgb, var(--ink) 20%, transparent) 38%,
      color-mix(in srgb, var(--ink) 92%, transparent) 100%);
}

.hero__inner { max-width: 46rem; }

/* The ball overlay passes over this copy by design, so the headline and subline
   carry their own shadow. Without it, a basketball or a burst of scoring text
   drifting behind a glyph makes the line briefly hard to read. */
.hero__title {
  margin-bottom: 1.25rem;
  text-wrap: balance;
  text-shadow: 0 2px 18px rgba(14, 12, 24, 0.85), 0 1px 3px rgba(14, 12, 24, 0.7);
}
.hero__subline {
  max-width: 34rem;
  margin-bottom: 2rem;
  font-size: clamp(1.05rem, 2.4vw, 1.3rem);
  color: var(--text);
  text-shadow: 0 1px 12px rgba(14, 12, 24, 0.9);
}
.hero__actions { display: flex; flex-wrap: wrap; gap: 0.85rem; }

@media (max-width: 820px) {
  .hero { align-items: center; text-align: center; }
  .hero__inner { max-width: none; }
  .hero__subline { margin-inline: auto; }
  .hero__actions { justify-content: center; }
  .hero__actions .btn { flex: 1 1 15rem; }
  /* Mobile has no room to place copy away from busy footage, so the scrim
     carries more of the contrast. Weighted toward the lower half where the
     copy sits, keeping the desktop proof visible up top. */
  .hero__scrim {
    background: linear-gradient(to bottom,
      color-mix(in srgb, var(--ink) 62%, transparent) 0%,
      color-mix(in srgb, var(--ink) 72%, transparent) 32%,
      color-mix(in srgb, var(--ink) 90%, transparent) 100%);
  }
}

/* ---------- Beats ---------- */
.beats { display: grid; gap: clamp(3rem, 8vw, 6rem); }

.beat {
  display: grid;
  gap: clamp(1.5rem, 4vw, 3rem);
  align-items: center;
}
.beat__text { max-width: var(--measure); }
.beat__text h2 { margin-bottom: 0.75rem; }
.beat__text p {
  color: var(--text-muted);
  font-size: clamp(1rem, 2.1vw, 1.15rem);
  text-wrap: pretty;
}

.beat__media img {
  width: 100%;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  transition: transform 0.5s var(--ease-out), border-color 0.3s ease, box-shadow 0.4s ease;
}
.beat__media:hover img {
  transform: translateY(-5px);
  border-color: color-mix(in srgb, var(--accent) 35%, transparent);
  box-shadow: 0 26px 50px -32px rgba(0, 0, 0, 0.95);
}

/* The image lands a beat after its copy, so a section reads left to right. */
.beat.reveal .beat__media {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.6s var(--ease-out) 0.14s, transform 0.7s var(--ease-out) 0.14s;
}
.beat.reveal.is-visible .beat__media { opacity: 1; transform: none; }

@media (min-width: 900px) {
  .beat { grid-template-columns: 1fr 1fr; }
  .beat--flip .beat__text { order: 2; }
}

/* ---------- Trailer facade ---------- */
.trailer-facade {
  position: relative;
  margin-top: 2rem;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--line);
  aspect-ratio: 16 / 9;
  background: var(--surface-2);
}
.trailer-facade__poster {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 0.7s var(--ease-out);
}
.trailer-facade:hover .trailer-facade__poster { transform: scale(1.03); }

.trailer-facade__play {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  width: 100%;
  background: color-mix(in srgb, var(--ink) 45%, transparent);
  border: 0;
  color: var(--text);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.2s ease;
}
.trailer-facade__play:hover { background: color-mix(in srgb, var(--ink) 25%, transparent); }

.trailer-facade__icon {
  width: 72px; height: 72px;
  border-radius: 50%;
  background: var(--accent);
  position: relative;
  box-shadow: 0 14px 40px -14px var(--accent);
  transition: transform 0.3s var(--ease-spring), background-color 0.2s ease,
              box-shadow 0.3s ease;
}
.trailer-facade__play:hover .trailer-facade__icon,
.trailer-facade__play:focus-visible .trailer-facade__icon {
  transform: scale(1.09);
  background: var(--accent-hot);
  box-shadow: 0 18px 50px -12px var(--accent-hot);
}
.trailer-facade__play:active .trailer-facade__icon { transform: scale(0.94); }

/* A ring pushing outward, like the ball just landed. */
.trailer-facade__icon::before {
  content: '';
  position: absolute;
  inset: -8px;
  border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--accent) 60%, transparent);
  opacity: 0;
}
.trailer-facade__play:hover .trailer-facade__icon::before {
  animation: ping 1.8s var(--ease-out) infinite;
}
@keyframes ping {
  0%   { opacity: 0.75; transform: scale(0.88); }
  100% { opacity: 0;    transform: scale(1.3); }
}
.trailer-facade__icon::after {
  content: '';
  position: absolute;
  top: 50%; left: 54%;
  transform: translate(-50%, -50%);
  border-style: solid;
  border-width: 14px 0 14px 22px;
  border-color: transparent transparent transparent var(--ink);
}

.trailer-facade iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* ---------- Closer ---------- */
.closer { text-align: center; }
.closer__text {
  max-width: 40rem;
  margin: 0 auto 2rem;
  font-size: clamp(1.3rem, 3.2vw, 1.9rem);
  font-weight: 600;
  line-height: 1.35;
  text-wrap: balance;
}

/* ---------- Press ----------
   No header-height compensation here: unlike the hero pages, this page does not
   pull content up under the header, so the sticky header already occupies its
   own space and .section padding alone is correct. */
.press-actions { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-top: 2rem; }

.factsheet { display: grid; gap: 1rem; margin-top: 2rem; }
.factsheet > div {
  display: grid;
  grid-template-columns: minmax(9rem, 14rem) 1fr;
  gap: 1rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--line);
  transition: border-color 0.3s ease;
}
.factsheet > div:hover { border-bottom-color: color-mix(in srgb, var(--accent) 45%, transparent); }
.factsheet dt { color: var(--text-muted); }
.factsheet dd { margin: 0; }

@media (max-width: 620px) {
  .factsheet > div { grid-template-columns: 1fr; gap: 0.15rem; }
}

.asset-grid {
  display: grid;
  gap: 1.25rem;
  margin-top: 2rem;
  padding: 0;
  list-style: none;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.asset-grid a {
  display: block;
  text-decoration: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-2);
  transition: border-color 0.25s ease, box-shadow 0.3s ease,
              transform 0.35s var(--ease-spring);
}
.asset-grid a:hover,
.asset-grid a:focus-visible {
  border-color: var(--text-muted);
  transform: translateY(-4px);
  box-shadow: 0 18px 34px -22px rgba(0, 0, 0, 0.95);
}
.asset-grid a:active { transform: translateY(-1px) scale(0.99); transition-duration: 0.08s; }
.asset-grid img {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  transition: transform 0.5s var(--ease-out);
}
.asset-grid a:hover img,
.asset-grid a:focus-visible img { transform: scale(1.05); }
.asset-grid span {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.85rem 1rem;
  font-size: 0.92rem;
  color: var(--text-muted);
  transition: color 0.2s ease;
}
.asset-grid a:hover span,
.asset-grid a:focus-visible span { color: var(--text); }
/* The download affordance arrives on hover rather than sitting there always.
   The second content declaration gives it empty alt text so it stays out of the
   link's accessible name; browsers without alt-text support keep the first. */
.asset-grid span::after {
  content: '\2193';
  content: '\2193' / '';
  color: var(--accent);
  opacity: 0;
  transform: translateY(-5px);
  transition: opacity 0.25s ease, transform 0.3s var(--ease-spring);
}
.asset-grid a:hover span::after,
.asset-grid a:focus-visible span::after { opacity: 1; transform: none; }

/* ---------- Ball overlay ----------
   Desktop Ball playing on top of the page itself. Fixed and click-through, so
   it reads as an overlay on your screen rather than page content. Sits above
   the sections but BELOW the sticky header (z-index 50), so the nav and the
   header Wishlist button are never obscured. */
.ball-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  pointer-events: none;
  display: none;
  /* --ball-opacity is driven by the scroll fade in hero.js.
     --ball-max-opacity is the knob: turn it down (e.g. 0.75) to make the ball
     sit more softly over the copy without touching any JavaScript. */
  opacity: calc(var(--ball-opacity, 1) * var(--ball-max-opacity, 1));
  will-change: opacity;
}
.ball-overlay.is-active { display: block; }
.ball-overlay video {
  width: 100%;
  height: 100%;
  /* Desktop viewports are close to the clip's 16:9, so cover crops almost
     nothing and the ball reaches the screen edges. */
  object-fit: cover;
}

/* Portrait phones are nowhere near 16:9: cover would show only ~26% of the
   frame and throw most of the ball action off-screen. The clip is transparent,
   so contain produces no letterbox bars - just a band of action with the whole
   composition intact. */
@media (max-aspect-ratio: 1/1) {
  .ball-overlay video { object-fit: contain; }
}

/* ---------- Hero pattern ----------
   Stands in for a desktop wallpaper without pretending to be a real screenshot:
   two soft brand-coloured light sources over the ink ground, plus a faint dot
   grid that reads as a desktop icon grid. Pure CSS, so it costs no bytes and
   no extra request. */
.hero__pattern {
  position: absolute;
  inset: 0;
  z-index: -3;
  background-color: var(--ink);
  background-image:
    radial-gradient(circle at 1px 1px,
      rgba(191, 199, 236, 0.17) 1px, transparent 0),
    radial-gradient(90% 70% at 18% 12%,
      rgba(239, 97, 167, 0.26) 0%, transparent 60%),
    radial-gradient(80% 80% at 85% 30%,
      rgba(74, 87, 149, 0.38) 0%, transparent 62%),
    radial-gradient(70% 60% at 60% 105%,
      rgba(51, 42, 72, 0.70) 0%, transparent 70%);
  background-size: 34px 34px, 100% 100%, 100% 100%, 100% 100%;
  /* One tile of grid travel, so the loop is seamless: the wallpaper breathes
     instead of sitting dead behind the ball. */
  animation: wallpaper-drift 44s ease-in-out infinite alternate;
}

@keyframes wallpaper-drift {
  from { background-position: 0 0, 0% 0%, 100% 0%, 50% 100%; }
  to   { background-position: 34px 34px, 8% 6%, 92% 8%, 44% 94%; }
}

/* Vignette keeps the edges dark so the headline never fights the pattern. */
.hero__pattern::after {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(120% 100% at 50% 45%,
    transparent 35%, color-mix(in srgb, var(--ink) 85%, transparent) 100%);
}

/* ==========================================================================
   Floating Island Generator page
   Same family as Desktop Ball, teal instead of pink, quieter and more editorial.
   ========================================================================== */

.page-fig h2 { color: var(--text); }

/* ---------- FIG hero ---------- */
.fig-hero {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: min(88svh, 780px);
  margin-top: calc(var(--header-h) * -1);
  padding-block: calc(var(--header-h) + 5rem) clamp(3rem, 8vw, 5.5rem);
  overflow: hidden;
  isolation: isolate;
}
.fig-hero__media {
  position: absolute;
  inset: 0;
  z-index: -2;
  width: 100%; height: 100%;
  object-fit: cover;
}
/* Weighted to the lower-left so the copy sits on solid contrast while the
   islands stay visible through the top and right of the frame. */
.fig-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    /* Covers the copy column. The source footage is bright sky, so this has to
       do more work than the Desktop Ball hero's scrim over dark desktop UI. */
    linear-gradient(105deg,
      color-mix(in srgb, var(--ink) 94%, transparent) 0%,
      color-mix(in srgb, var(--ink) 80%, transparent) 42%,
      color-mix(in srgb, var(--ink) 30%, transparent) 78%,
      transparent 100%),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ink) 70%, transparent) 0%,
      color-mix(in srgb, var(--ink) 40%, transparent) 45%,
      color-mix(in srgb, var(--ink) 92%, transparent) 100%);
}
.fig-hero__inner { max-width: 44rem; }
.fig-hero h1 {
  margin-bottom: 1.25rem;
  text-wrap: balance;
  text-shadow: 0 2px 18px rgba(14, 12, 24, 0.8);
}
.fig-hero__sub {
  max-width: 36rem;
  margin-bottom: 2rem;
  font-size: clamp(1.05rem, 2.3vw, 1.25rem);
  text-shadow: 0 1px 12px rgba(14, 12, 24, 0.85);
}
.fig-hero__actions { display: flex; flex-wrap: wrap; gap: 0.85rem; }

@media (max-width: 820px) {
  .fig-hero { align-items: center; text-align: center; }
  .fig-hero__inner { max-width: none; }
  .fig-hero__sub { margin-inline: auto; }
  .fig-hero__actions { justify-content: center; }
  .fig-hero__actions .btn { flex: 1 1 16rem; }
  .fig-hero__scrim {
    background: linear-gradient(to bottom,
      color-mix(in srgb, var(--ink) 66%, transparent) 0%,
      color-mix(in srgb, var(--ink) 88%, transparent) 100%);
  }
}

/* ---------- Lede ---------- */
.fig-lede { max-width: 46rem; }
.fig-lede h2 { margin-bottom: 1.25rem; }
.fig-lede p {
  font-size: clamp(1.1rem, 2.4vw, 1.3rem);
  line-height: 1.6;
  color: var(--text-muted);
}
.fig-lede p + p { margin-top: 1.25rem; }
.fig-lede p:last-child { color: var(--text); }

/* ---------- Preset gallery ----------
   Deliberately understated: no captions and no invented preset names (only
   three of the seven are named publicly), so the images carry it alone. */
.fig-gallery__head { max-width: 46rem; margin-bottom: clamp(2rem, 5vw, 3rem); }
.fig-gallery__head p {
  margin-top: 1rem;
  color: var(--text-muted);
  font-size: clamp(1rem, 2.1vw, 1.12rem);
}

.fig-gallery {
  display: grid;
  gap: clamp(0.75rem, 1.6vw, 1.1rem);
  padding: 0;
  list-style: none;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 640px)  { .fig-gallery { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1000px) { .fig-gallery { grid-template-columns: repeat(4, 1fr); } }

.fig-gallery img {
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border-radius: 10px;
  border: 1px solid var(--line);
  /* The source shots have a bright sky; easing it back keeps the grid sitting
     inside the page's palette instead of punching a hole in it. */
  filter: saturate(0.82) brightness(0.88);
  transition: filter 0.4s ease, border-color 0.3s ease,
              transform 0.4s var(--ease-spring);
}
.fig-gallery li:hover img,
.fig-gallery li:focus-within img {
  filter: none;
  transform: translateY(-4px) scale(1.02);
  border-color: color-mix(in srgb, var(--teal) 45%, transparent);
}

/* ---------- Steps ---------- */
.fig-section-title { max-width: var(--measure); margin-bottom: clamp(2rem, 5vw, 3rem); }

.fig-steps {
  display: grid;
  gap: clamp(1.75rem, 4vw, 2.5rem);
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: figstep;
}
@media (min-width: 900px) { .fig-steps { grid-template-columns: repeat(3, 1fr); } }

.fig-steps li {
  counter-increment: figstep;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.fig-steps li::before {
  content: counter(figstep, decimal-leading-zero);
  display: block;
  margin-bottom: 0.85rem;
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--teal);
}
.fig-steps h3 { margin-bottom: 0.6rem; font-size: clamp(1.15rem, 2.4vw, 1.4rem); }
.fig-steps p { color: var(--text-muted); }

/* ---------- Quotes ---------- */
.fig-quotes { display: grid; gap: clamp(1.5rem, 3.5vw, 2.5rem); }
@media (min-width: 820px) { .fig-quotes { grid-template-columns: 1fr 1fr; } }

.fig-quotes figure { margin: 0; }
.fig-quotes blockquote { margin: 0; }
.fig-quotes blockquote p {
  font-size: clamp(1.15rem, 2.6vw, 1.5rem);
  line-height: 1.45;
  font-weight: 600;
  text-wrap: pretty;
}
.fig-quotes blockquote p::before { content: '\201C'; }
.fig-quotes blockquote p::after { content: '\201D'; }
.fig-quotes figcaption {
  margin-top: 1rem;
  font-size: 0.92rem;
  color: var(--text);
}
.fig-quotes figcaption span {
  display: block;
  color: var(--text-muted);
  font-size: 0.85rem;
}

/* ---------- Closer ---------- */
.fig-closer { text-align: center; }
.fig-closer__text {
  max-width: 34rem;
  margin: 0 auto 2rem;
  font-size: clamp(1.35rem, 3.2vw, 2rem);
  font-weight: 700;
  line-height: 1.3;
  text-wrap: balance;
}

/* ---------- Preset lightbox ----------
   The gallery thumbnails are cropped and dimmed; this shows the complete render
   at full resolution. Built on <dialog> so Escape, focus trapping and the
   backdrop are the browser's job. */
.fig-gallery__zoom {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  border-radius: 10px;
  transition: transform 0.2s var(--ease-spring);
}
.fig-gallery__zoom:active { transform: scale(0.98); }
.fig-gallery__zoom:focus-visible { outline: 2px solid var(--teal); outline-offset: 4px; }

.has-modal { overflow: hidden; }

.lightbox {
  display: none;
  max-width: 100vw;
  max-height: 100vh;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: clamp(1rem, 4vw, 3rem);
  border: 0;
  background: transparent;
  overflow: hidden;
}
.lightbox[open] {
  display: grid;
  place-items: center;
  animation: lightbox-fade 0.25s var(--ease-out);
}
.lightbox[open]::backdrop { animation: lightbox-fade 0.25s var(--ease-out); }
.lightbox[open] .lightbox__img { animation: lightbox-pop 0.35s var(--ease-spring); }

@keyframes lightbox-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes lightbox-pop {
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: none; }
}
.lightbox::backdrop {
  background: rgba(6, 5, 12, 0.9);
  backdrop-filter: blur(4px);
}

.lightbox__img {
  /* Viewport units, not percentages: `max-height: 100%` on a replaced element
     inside the grid resolved against an indefinite basis and let tall images
     spill past the bottom of the screen. Second declaration wins where svh is
     supported, which is what mobile browser chrome needs. */
  max-width: min(92vw, 1600px);
  max-height: 84vh;
  max-height: 84svh;
  width: auto;
  height: auto;
  border-radius: 12px;
  border: 1px solid var(--line);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
  cursor: default;
  transition: opacity 0.2s ease;
}
/* While the full file streams in, the upscaled thumbnail stands in. Easing it
   back makes the swap read as a load rather than a glitch. */
.lightbox.is-loading .lightbox__img { opacity: 0.55; }

.lightbox__close {
  position: fixed;
  top: clamp(0.75rem, 2vw, 1.5rem);
  right: clamp(0.75rem, 2vw, 1.5rem);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  font-size: 1.9rem;
  line-height: 1;
  color: var(--text);
  background: color-mix(in srgb, var(--ink) 75%, transparent);
  border: 1px solid var(--line);
  border-radius: 50%;
  cursor: pointer;
  transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}
.lightbox__close:hover {
  background: var(--ink);
  border-color: var(--text-muted);
  transform: scale(1.05);
}
.lightbox__close:focus-visible { outline: 2px solid var(--teal); outline-offset: 3px; }
