/* ===========================================================================
   Sommerset - site styles

   Light "daytime sunset" theme. Palette is built on her real brand assets:
     - #af1982 is her exact Twitch channel accent colour (sampled from the
       channel page, not guessed)
     - the sunset ramp riffs on the purple-to-pink sunset pin on Mochi's
       Youtooz plush, and on the name itself (Sommer-set)

   Contrast note: on a cream ground the pale end of the ramp (gold, coral,
   hot pink) is decorative only - it fails text contrast. Anything that has
   to be *read* uses --ink-accent (#af1982, ~6.2:1 on the page ground) or
   darker. Keep that split if you retheme.
   =========================================================================== */

/* --- Tokens --------------------------------------------------------------- */
:root {
  --safe-right: env(safe-area-inset-right, 0px);
  /* Sunset ramp - decorative */
  --sunset-gold:   #ffc46b;
  --sunset-coral:  #ff7a59;
  --sunset-pink:   #ff4d9d;
  --sunset-magenta:#af1982;   /* her Twitch channel accent, exact */
  --sunset-purple: #6d28a8;

  /* Readable accents - these pass contrast on the light ground */
  --ink-accent:      #af1982;
  --ink-accent-deep: #8e1276;

  /* Surfaces - warm cream rather than flat white */
  --bg:           #fffaf5;
  --bg-elevated:  #fff3ea;
  --bg-card:      #ffffff;
  --border:       rgba(175, 25, 130, 0.13);
  --border-bright: rgba(175, 25, 130, 0.28);

  /* Text - deep plum, not black */
  --text:         #2a1230;
  --text-muted:   #6d5570;
  --text-dim:     #8a7590;

  --accent:        var(--ink-accent);
  --accent-bright: var(--sunset-pink);
  --accent-deep:   var(--ink-accent-deep);

  --radius:    14px;
  --radius-lg: 20px;

  --wrap:   1180px;
  --gutter: clamp(20px, 5vw, 48px);

  --font-body:    'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-display: 'Space Grotesk', 'Inter', system-ui, sans-serif;

  /* Light themes need shadow where dark ones used glow. */
  --shadow:    0 14px 38px rgba(112, 20, 84, 0.12);
  --shadow-sm: 0 3px 12px rgba(112, 20, 84, 0.07);
  --glow:      0 8px 24px rgba(175, 25, 130, 0.3);
}

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

html {
  scroll-behavior: smooth;
  /* No text inflation when a phone turns sideways. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img, svg, iframe { max-width: 100%; }
h1, h2, h3 { font-family: var(--font-display); line-height: 1.1; margin: 0; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
a { color: inherit; }

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

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--ink-accent);
  color: #fff;
  padding: 12px 20px;
  border-radius: 0 0 var(--radius) 0;
  font-weight: 600;
}
.skip-link:focus { left: 0; }

/* --- Header --------------------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255, 250, 245, 0.86);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border);
}

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  min-height: 68px;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: -0.02em;
  text-decoration: none;
  color: var(--text);
  flex-shrink: 0;
}
/* Her Twitch profile image, in place of the old gradient dot. Both axes are
   set explicitly so the width/height attributes can't skew it. */
.brand-mark {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  box-shadow: 0 2px 8px rgba(112, 20, 84, 0.22);
}
.site-footer .brand-mark { width: 24px; height: 24px; }
/* The footer carries the same script wordmark as the header, a touch
   smaller (.brand-word itself is defined just below). */
.site-footer .brand-word { height: 30px; }

/* Her own gold script wordmark in the header, in place of the typeset
   name. Height drives the size and width stays auto so the aspect ratio
   survives; the shadow is what keeps pale gold legible on cream. */
.brand-word {
  display: block;
  height: 34px;
  width: auto;
  filter: drop-shadow(0 1px 1px rgba(112, 20, 84, 0.25));
}

/* Menu plus the Twitch call to action share the right-hand end. */
.nav-right {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.2vw, 30px);
}

.nav-menu {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 26px);
}
/* Plain text links with a rule under the current page, rather than pills.
   The rule is a pseudo-element scaled from 0 so it can animate, and it sits
   on the link's own box so it tracks the word width. */
.nav-menu a {
  position: relative;
  padding: 6px 1px;
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.95rem;
  font-weight: 500;
  white-space: nowrap;
  transition: color 0.18s ease;
}
.nav-menu a::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 2px;
  border-radius: 2px;
  background: var(--ink-accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.2s ease;
}
.nav-menu a:hover { color: var(--text); }
.nav-menu a:hover::after { transform: scaleX(1); opacity: 0.45; }
.nav-menu a[aria-current='page'] { color: var(--text); font-weight: 700; }
.nav-menu a[aria-current='page']::after { transform: scaleX(1); }

/* Smaller than the hero buttons - it sits in a 68px bar. */
.btn.nav-cta {
  padding: 10px 20px;
  font-size: 0.9rem;
  white-space: nowrap;
}

/* Hide the extra CTA before it crowds the navigation on smaller tablets. */
/* .btn.nav-cta, not .nav-cta: .btn sets display further down the file and
   would win otherwise. */
@media (max-width: 920px) {
  .btn.nav-cta { display: none; }
}
/* Give the brand and links their own rows on phones, with roomy tap targets. */
@media (max-width: 680px) {
  .nav {
    flex-direction: column;
    gap: 4px;
    padding-top: 10px;
    padding-bottom: 8px;
  }
  .site-header .brand {
    min-height: 44px;
    flex-shrink: 0;
    white-space: nowrap;
  }
  .brand-word { height: 28px; }
  .nav-right { width: 100%; min-width: 0; }
  /* Six pages don't fit across a 320px phone, so the row scrolls sideways
     instead of bringing the hamburger back. Links stretch to fill wider
     phones and only overflow when they run out of room. site.js sets
     data-more while there is more to the right, and the faded edge is the
     hint. */
  .nav-menu {
    width: 100%;
    gap: 4px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }
  .nav-menu[data-more] {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  }
  .nav-menu::-webkit-scrollbar { display: none; }
  .nav-menu a {
    display: flex;
    flex: 1 0 auto;
    align-items: center;
    justify-content: center;
    min-height: 44px;
    padding: 8px 10px;
    font-size: 0.95rem;
    white-space: nowrap;
  }
  .nav-menu a::after {
    left: 25%;
    right: 25%;
    bottom: 0;
  }
}

/* Light/dark switch, added by js/site.js at the end of .nav-right. Small and
   round so it reads as a utility, not another nav item. */
.theme-toggle {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 1px solid var(--border-bright);
  border-radius: 50%;
  background: var(--bg-card);
  color: var(--text-muted);
  cursor: pointer;
  transition: color 0.18s ease, border-color 0.18s ease, transform 0.18s ease;
}
.theme-toggle svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.theme-toggle:hover {
  color: var(--ink-accent);
  border-color: var(--ink-accent);
  transform: rotate(-12deg);
}
/* No header on the links page, so it floats in the corner there. */
.theme-toggle.is-floating {
  position: fixed;
  top: 14px;
  right: 14px;
  z-index: 60;
  box-shadow: var(--shadow-sm);
}
/* On phones the nav stacks, so pin it to the brand row's top right rather
   than letting it eat into the sideways-scrolling menu. */
@media (max-width: 680px) {
  .nav { position: relative; }
  .site-header .theme-toggle {
    position: absolute;
    top: 14px;
    right: var(--gutter);
  }
}


/* --- Buttons -------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 13px 24px;
  border-radius: 999px;
  font-weight: 600;
  font-size: 0.96rem;
  text-decoration: none;
  border: 1px solid transparent;
  cursor: pointer;
  transition: transform 0.18s ease, box-shadow 0.18s ease,
              background 0.18s ease, border-color 0.18s ease;
}
.btn:hover { transform: translateY(-2px); }
.btn .icon { width: 18px; height: 18px; }

/* Gradient stops are all dark enough to keep white label text >= 4.5:1. */
.btn-primary {
  background: linear-gradient(115deg, #cf2489 0%, var(--ink-accent) 55%, var(--ink-accent-deep) 100%);
  color: #fff;
  box-shadow: var(--glow);
}
.btn-primary:hover { box-shadow: 0 14px 34px rgba(175, 25, 130, 0.4); }

.btn-ghost {
  background: var(--bg-card);
  border-color: var(--border-bright);
  color: var(--text);
  box-shadow: var(--shadow-sm);
}
.btn-ghost:hover {
  background: #fff;
  border-color: var(--ink-accent);
  color: var(--ink-accent);
}

/* --- Hero ------------------------------------------------------------------
   Daytime sunset sky: warm peach at the top fading into the page ground,
   with the sun sitting on the horizon rule at the base.                     */
.hero {
  position: relative;
  padding: clamp(72px, 13vw, 132px) 0 clamp(64px, 10vw, 110px);
  overflow: hidden;
  isolation: isolate;
  background: linear-gradient(175deg,
              #ffe4d0 0%,
              #ffd9e6 34%,
              #fde8f0 62%,
              var(--bg) 100%);
}
/* Soft colour wash so the sky isn't a flat band */
.hero::before {
  content: '';
  position: absolute;
  top: -300px;
  left: 50%;
  transform: translateX(-50%);
  width: min(1200px, 160vw);
  height: 720px;
  background: radial-gradient(ellipse at center,
              rgba(255, 196, 107, 0.4) 0%,
              rgba(255, 122, 89, 0.16) 44%,
              transparent 72%);
  pointer-events: none;
  z-index: -2;
}

/* Two columns: copy left, portrait right. The circular portrait doubles as
   the sun in the sunset motif, so there's no separate sun disc. */
.hero-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(28px, 5vw, 72px);
}

.hero-portrait {
  position: relative;
  flex: none;
  width: clamp(200px, 26vw, 340px);
  aspect-ratio: 1;
}
/* Warm halo behind the portrait */
.hero-portrait::before {
  content: '';
  position: absolute;
  inset: -14%;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(255, 196, 107, 0.6) 0%,
    rgba(255, 122, 89, 0.35) 45%,
    transparent 70%);
  z-index: -1;
}
/* Direct child only: the orbiting stickers are imgs inside this box too, and
   they must not inherit the circular mask, the white ring or the shadow. */
.hero-portrait > img {
  width: 100%;
  height: 100%;
  border-radius: 50%;
  object-fit: cover;
  display: block;
  border: 5px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 18px 44px rgba(112, 20, 84, 0.26);
}

@media (max-width: 820px) {
  .hero-inner {
    grid-template-columns: 1fr;
    justify-items: start;
  }
  /* Portrait first so the page opens on her face, not a wall of type. */
  .hero-portrait { order: -1; width: clamp(150px, 42vw, 210px); }
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 7px 16px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.72);
  border: 1px solid rgba(175, 25, 130, 0.26);
  color: var(--ink-accent);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}
/* Status dot, shared by the hero eyebrow and the live card. Grey and still
   by default - it only goes red and pulses under .is-live, which nothing
   sets until her status has actually come back live. */
.live-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--text-dim);
  flex: none;
}
.is-live .live-dot {
  background: #e8363d;
  box-shadow: 0 0 0 0 rgba(232, 54, 61, 0.6);
  animation: pulse 2.2s infinite;
}
/* Offline, the eyebrow is a label rather than an announcement. */
[data-live-eyebrow]:not(.is-live) {
  color: var(--text-muted);
  border-color: var(--border);
}
@keyframes pulse {
  70%  { box-shadow: 0 0 0 11px rgba(232, 54, 61, 0); }
  100% { box-shadow: 0 0 0 0 rgba(232, 54, 61, 0); }
}

/* Ramp runs orange -> pink -> magenta -> purple: every stop stays legible
   on the pale sky, unlike the gold end used in the dark theme. */
.hero h1 {
  margin: 26px 0 0;
  font-size: clamp(3rem, 12vw, 7.5rem);
  font-weight: 700;
  letter-spacing: -0.045em;
  background: linear-gradient(115deg,
              #ff7a2f 0%,
              #ff3d86 34%,
              var(--sunset-magenta) 68%,
              var(--sunset-purple) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Her script wordmark in place of the typeset name. The image is written on
   by sliding a soft-edged mask across it (a 300%-wide gradient, opaque on
   the left third, clear on the right), and the glint is a highlight band
   clipped to the letters by using the same image as its mask. */
/* The shadow lives on the h1, not the img: a mask clips everything its
   element paints outside its own box, so on the img it cut the shadow off
   in a faint rectangle. Kept tight, as on the header wordmark - a wide
   glow spreads into a word-shaped block on the pale sky. */
.hero h1.hero-name {
  position: relative;
  width: min(100%, 520px);
  margin-left: -6px;              /* the S's tail overhangs the image box */
  background: none;
  color: inherit;
  filter: drop-shadow(0 1px 1px rgba(112, 20, 84, 0.3))
          drop-shadow(0 3px 5px rgba(112, 20, 84, 0.12));
}
.hero-wordmark {
  display: block;
  width: 100%;
  height: auto;
  -webkit-mask-image: linear-gradient(90deg, #000 40%, transparent 60%);
          mask-image: linear-gradient(90deg, #000 40%, transparent 60%);
  -webkit-mask-size: 300% 100%;
          mask-size: 300% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  animation: wordmark-write 1.8s cubic-bezier(0.45, 0.05, 0.3, 1) 0.15s both;
}
.hero-name::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  /* Plain translucent white, not a blend mode: mix-blend-mode makes the h1
     its own compositing group, which clips the wordmark's glow to the h1's
     box and leaves a hard-edged rectangle around the name. */
  background: linear-gradient(105deg,
              transparent 38%,
              rgba(255, 250, 225, 0.35) 46%,
              rgba(255, 255, 255, 0.6) 50%,
              rgba(255, 250, 225, 0.35) 54%,
              transparent 62%) no-repeat;
  background-size: 250% 100%;
  background-position: 160% 0;
  -webkit-mask: url(../assets/wordmark.webp) center / 100% 100% no-repeat;
          mask: url(../assets/wordmark.webp) center / 100% 100% no-repeat;
  animation: wordmark-glint 6s ease-in-out 2.1s infinite;
}
@keyframes wordmark-write {
  from {
    -webkit-mask-position: 100% 0;
            mask-position: 100% 0;
    transform: translateY(6px);
  }
  to {
    -webkit-mask-position: 0 0;
            mask-position: 0 0;
    transform: none;
  }
}
/* One sweep at the start of each cycle, then a long rest. */
@keyframes wordmark-glint {
  0%        { background-position: 160% 0; }
  22%, 100% { background-position: -60% 0; }
}
/* The global reduced-motion rule shrinks durations to ~0, which would turn
   an infinite glint into a flicker, so it is switched off outright. */
@media (prefers-reduced-motion: reduce) {
  .hero-wordmark { animation: none; -webkit-mask-image: none; mask-image: none; }
  .hero-name::after { animation: none; display: none; }
}

.hero-tagline {
  margin-top: 20px;
  max-width: 52ch;
  font-size: clamp(1.05rem, 2.2vw, 1.3rem);
  color: #57405c;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 38px;
}

.creator-code {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}
.creator-code-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  max-width: 100%;
  padding: 8px 14px;
  border: 1px solid var(--border-bright);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.55);
  color: var(--ink-accent-deep);
  font: inherit;
  font-weight: 600;
  font-size: 0.88rem;
  cursor: pointer;
}
.creator-code-button strong { font-weight: 700; }
.creator-code-button:hover { background: var(--bg-card); }
.creator-code-button svg { flex-shrink: 0; }
a.creator-code-button { text-decoration: none; }
.creator-code-tip[hidden] { display: none; }
.creator-code-status {
  display: block;
  flex-basis: 100%;
  margin-top: 4px;
  min-height: 1.65em;
  font-size: 0.8rem;
  color: var(--text-muted);
}

/* Horizon rule closing the hero. Pinned to the base of the section so it
   reads as the skyline, rather than landing on top of the button row. */
.horizon {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  border: 0;
  margin: 0;
  background: linear-gradient(90deg,
              transparent,
              rgba(255, 122, 89, 0.55) 22%,
              rgba(255, 77, 157, 0.7) 50%,
              rgba(175, 25, 130, 0.45) 78%,
              transparent);
}

/* --- Sections ------------------------------------------------------------- */
.section { padding: clamp(60px, 9vw, 104px) 0; }
.section-alt {
  background: var(--bg-elevated);
  border-block: 1px solid var(--border);
}

.section-head { margin-bottom: 44px; max-width: 60ch; }
.section-head h2 {
  font-size: clamp(1.9rem, 4.6vw, 2.9rem);
  letter-spacing: -0.03em;
}
.section-head p { margin-top: 14px; color: var(--text-muted); }

.section-kicker {
  display: block;
  margin-bottom: 12px;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-accent);
}

/* --- Live panel ------------------------------------------------------------
   Two states in one card. Offline it is a short designed band - no 16/9
   letterbox of empty cream, because there is no video to letterbox. Live it
   becomes the player and takes the full 16/9 it now deserves.               */
.live-section { padding-top: clamp(44px, 7vw, 76px); }

.live-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: border-color 0.25s ease, box-shadow 0.25s ease;
}
.live-card.is-live {
  border-color: var(--border-bright);
  box-shadow: var(--shadow);
}

.live-screen {
  position: relative;
  display: grid;
  place-items: center;
  min-height: clamp(240px, 30vw, 340px);
  padding: 32px var(--gutter);
  background: linear-gradient(165deg, #fff3ea 0%, #ffe6f0 100%);
}
.live-card.is-live .live-screen {
  min-height: 0;
  padding: 0;
  aspect-ratio: 16 / 9;
  background: #1a1029;   /* stays dark - it's video */
}
.live-screen iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.live-state {
  display: grid;
  gap: 13px;
  justify-items: center;
  text-align: center;
}
.live-emote { width: 68px; height: 68px; }
.live-msg {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.1rem, 2.6vw, 1.45rem);
  letter-spacing: -0.02em;
}
.live-sub {
  max-width: 46ch;
  color: var(--text-muted);
  font-size: 0.94rem;
}

.live-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 15px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.8);
  border: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 0.74rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.live-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 14px 20px;
  padding: 16px 22px;
  border-top: 1px solid var(--border);
}
.live-bar-main {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 14px;
  min-width: 0;
}
.live-bar-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.live-bar-actions .btn[hidden] { display: none; }
.live-channel { font-weight: 600; font-size: 0.95rem; }
.live-meta { color: var(--text-muted); font-size: 0.9rem; }

.btn-sm { padding: 9px 19px; font-size: 0.88rem; }

/* --- Clips ---------------------------------------------------------------- */
.clips-grid {
  display: grid;
  /* min() so the track can go under 320px on a 320px phone. A bare
     minmax(320px, 1fr) is wider than the content column there and pushes
     the whole page into a horizontal scroll. */
  grid-template-columns: repeat(auto-fill, minmax(min(320px, 100%), 1fr));
  gap: 26px;
}
.clips-grid.is-empty { display: block; }

.clip {
  position: relative;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform 0.22s ease, border-color 0.22s ease, box-shadow 0.22s ease;
}
.clip:hover {
  transform: translateY(-4px);
  border-color: var(--border-bright);
  box-shadow: var(--shadow);
}

/* Even 3-up grid. A featured clip spanning two columns was tried and
   dropped: clip cards are video + caption, so any span leaves either a gap
   beside the tall card or dead space inside it. With six clips this divides
   cleanly at every breakpoint instead.

   `.clip-feature` is still emitted on the first clip as a styling hook if
   you want to emphasise it some other way (a border, a ribbon). */
@media (min-width: 900px) {
  .clips-grid {
    grid-template-columns: repeat(3, 1fr);
    align-items: start;
  }
}

.clip-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #1a1029;   /* stays dark - it's video */
}
.clip-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

.clip-meta {
  padding: 18px 22px 22px;
  border-top: 1px solid var(--border);
}
.clip-meta h3 { font-size: 1.12rem; letter-spacing: -0.01em; }
.clip-meta p { margin-top: 7px; color: var(--text-muted); font-size: 0.92rem; }

.clips-empty {
  padding: 48px 32px;
  text-align: center;
  background: var(--bg-card);
  border: 1px dashed var(--border-bright);
  border-radius: var(--radius-lg);
  color: var(--text-muted);
}
.clips-empty strong { color: var(--text); }
.clips-empty code {
  background: rgba(175, 25, 130, 0.1);
  color: var(--ink-accent);
  padding: 2px 7px;
  border-radius: 6px;
  font-size: 0.88em;
}
.clips-empty .btn { margin-top: 22px; }

.clips-more { margin-top: 42px; text-align: center; }

/* --- Socials -------------------------------------------------------------- */
.socials {
  display: flex;
  flex-wrap: wrap;
  gap: 13px;
}
.social-chip {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  padding: 13px 22px;
  border-radius: 999px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  color: var(--text-muted);
  text-decoration: none;
  font-weight: 600;
  font-size: 0.94rem;
  box-shadow: var(--shadow-sm);
  transition: color 0.18s ease, border-color 0.18s ease,
              transform 0.18s ease, box-shadow 0.18s ease;
}
.social-chip .icon { width: 20px; height: 20px; flex: none; }
.social-chip:hover {
  color: var(--ink-accent);
  border-color: var(--ink-accent);
  transform: translateY(-3px);
  box-shadow: var(--shadow);
}

/* --- Contact -------------------------------------------------------------- */
.contact-card {
  position: relative;
  padding: clamp(36px, 6vw, 64px);
  border-radius: var(--radius-lg);
  background:
    radial-gradient(ellipse at 50% 120%, rgba(255, 196, 107, 0.4), transparent 62%),
    linear-gradient(140deg, #fff0f6 0%, #ffe6d9 100%);
  border: 1px solid rgba(175, 25, 130, 0.2);
  box-shadow: var(--shadow);
  text-align: center;
  overflow: hidden;
}
.contact-card h2 {
  font-size: clamp(1.7rem, 4vw, 2.5rem);
  letter-spacing: -0.03em;
}
.contact-card p { margin-top: 14px; color: var(--text-muted); }
.contact-card .btn { margin-top: 30px; }

.contact-email {
  margin-top: 18px;
  font-size: 0.9rem;
  color: var(--text-dim);
}
.contact-email a {
  color: var(--ink-accent);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid rgba(175, 25, 130, 0.35);
}
.contact-email a:hover { border-bottom-color: var(--ink-accent); }

/* --- About page ----------------------------------------------------------- */
.page-head {
  padding: clamp(56px, 10vw, 96px) 0 clamp(32px, 5vw, 52px);
  position: relative;
  overflow: hidden;
  /* Straight down and fully resolved to --bg before the edge, so there's no
     seam where the sky meets the body below. */
  background: linear-gradient(180deg, #ffe9dc 0%, #fdeaf1 52%, var(--bg) 92%);
}
.page-head::before {
  content: '';
  position: absolute;
  top: -260px;
  left: 50%;
  transform: translateX(-50%);
  width: min(960px, 150vw);
  height: 560px;
  background: radial-gradient(ellipse at center,
              rgba(255, 196, 107, 0.38) 0%,
              rgba(255, 122, 89, 0.12) 46%,
              transparent 72%);
  pointer-events: none;
}
.page-head > * { position: relative; }

/* Copy left, portrait right, mirroring the home hero but with a rounded
   rectangle rather than a circle so the two pages don't read identically. */
.page-head-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(28px, 5vw, 64px);
}
.page-head-portrait {
  margin: 0;
  width: clamp(170px, 23vw, 290px);
  flex: none;
}
.page-head-portrait img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: 5px solid rgba(255, 255, 255, 0.85);
  box-shadow: 0 16px 40px rgba(112, 20, 84, 0.22);
}
@media (max-width: 820px) {
  .page-head-inner { grid-template-columns: 1fr; }
  .page-head-portrait {
    width: clamp(150px, 45vw, 220px);
    margin-top: 8px;
  }
}

.page-head h1 {
  font-size: clamp(2.4rem, 7vw, 4.2rem);
  letter-spacing: -0.04em;
}
.page-head p { margin-top: 18px; max-width: 58ch; color: #57405c; font-size: 1.08rem; }

/* The body section directly under a page head doesn't need the full
   between-sections gap on top of the head's own padding. */
.page-head + .section { padding-top: clamp(24px, 3vw, 40px); }

/* Left-aligned inside the centred .wrap, so body copy lines up with the
   page heading above it rather than floating to the middle. */
.prose { max-width: 68ch; }
.prose h2 {
  margin-top: 48px;
  font-size: clamp(1.4rem, 3vw, 1.85rem);
  letter-spacing: -0.02em;
}
.prose > h2:first-child { margin-top: 0; }
.prose p { margin-top: 16px; color: var(--text-muted); }
.prose ul {
  margin-top: 16px;
  display: grid;
  gap: 11px;
}
/* Bulleted lists only - the stats and crew grids are cards, not bullets. */
.prose > ul:not(.stats):not(.crew) li {
  position: relative;
  padding-left: 26px;
  color: var(--text-muted);
}
.prose > ul:not(.stats):not(.crew) li::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 0.62em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--sunset-coral), var(--sunset-magenta));
}
.prose code {
  background: rgba(175, 25, 130, 0.1);
  color: var(--ink-accent);
  padding: 2px 8px;
  border-radius: 6px;
  font-size: 0.9em;
}
.prose a { color: var(--ink-accent); font-weight: 500; }
.prose strong { color: var(--text); }

/* Pull quote */
.pull-quote {
  margin: 0 0 8px;
  padding: 28px 32px;
  border-left: 3px solid transparent;
  border-image: linear-gradient(180deg, var(--sunset-coral), var(--sunset-pink), var(--sunset-magenta)) 1;
  background: linear-gradient(90deg, #fff0f6 0%, rgba(255, 240, 246, 0) 78%);
}
.pull-quote p {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 2.3vw, 1.3rem);
  line-height: 1.5;
  color: var(--text);
}
.pull-quote cite {
  display: block;
  margin-top: 14px;
  font-style: normal;
  font-size: 0.88rem;
  color: var(--text-dim);
}

/* Stats */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 18px;
  margin-top: 44px;
}
.stats li {
  padding: 26px 22px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  text-align: center;
}
.stat-value {
  display: block;
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 4vw, 2.3rem);
  font-weight: 700;
  letter-spacing: -0.03em;
  background: linear-gradient(120deg, #ff7a2f, var(--sunset-magenta));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.stat-label {
  display: block;
  margin-top: 6px;
  font-size: 0.84rem;
  color: var(--text-dim);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

/* --- The crew (pets) ------------------------------------------------------
   Deliberately understated: small cards, paw glyph, no baby talk.          */
/* 240px minimum keeps this at two columns inside the prose measure, so an
   even number of pets lands as a tidy grid instead of orphaning the last. */
.crew {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 16px;
  margin-top: 20px;
}
.crew li {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  padding: 20px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  transition: border-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
}
.crew li:hover {
  border-color: var(--border-bright);
  transform: translateY(-3px);
  box-shadow: var(--shadow);
}
.crew .paw {
  width: 30px;
  height: 30px;
  flex: none;
  /* Pink heart paw pads, same as the ones on the Mochi plush. */
  color: var(--sunset-pink);
}
.crew-name {
  display: block;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.02rem;
  color: var(--text);
}
.crew-role {
  display: block;
  margin-top: 2px;
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-accent);
  font-weight: 600;
}
.crew-note { margin-top: 8px; font-size: 0.9rem; color: var(--text-muted); }

/* --- Photo strip ---------------------------------------------------------- */
/* Four across, then two by two. Fixed counts rather than auto-fit, which
   gave three and an orphan at tablet widths. Keep the photo count even. */
.photo-strip {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 20px;
}
@media (max-width: 900px) {
  .photo-strip { grid-template-columns: repeat(2, 1fr); }
}
.photo-strip li {
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--bg-card);
  box-shadow: var(--shadow-sm);
  transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.photo-strip li:nth-child(even) { transform: translateY(14px); }
.photo-strip li:hover {
  transform: translateY(-6px);
  box-shadow: var(--shadow);
}
.photo-strip li:nth-child(even):hover { transform: translateY(8px); }
.photo-strip img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}
@media (max-width: 560px) {
  /* The stagger reads as misalignment once they're stacked. */
  .photo-strip li:nth-child(even) { transform: none; }
  .photo-strip li:nth-child(even):hover { transform: translateY(-6px); }
}

/* --- Figures in prose -----------------------------------------------------
   These sit in the text flow rather than floating into the right margin.
   Floating was tried and reverted: the source photos are tall portraits, so
   a float ran a single image past several unrelated sections. Capping the
   height keeps each figure beside the text it belongs to. */
.prose-figure {
  margin: 30px 0;
}
.prose-figure img {
  display: block;
  width: auto;
  /* `height: auto` is load-bearing. The <img> carries width/height attributes
     so the space is reserved before it loads, and those act as presentational
     hints (height: 1055px here). Overriding only `width` left that hint in
     place, so max-width and max-height then clamped the two axes
     independently and stretched the picture. */
  height: auto;
  max-width: min(100%, 400px);
  max-height: 520px;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.prose-figure figcaption {
  margin-top: 10px;
  max-width: 400px;
  font-size: 0.86rem;
  color: var(--text-dim);
}

/* Pair a figure with the paragraph it illustrates once there's width for it. */
@media (min-width: 880px) {
  .prose-figure-pair {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: start;
    gap: 32px;
  }
  .prose-figure-pair .prose-figure { margin-top: 0; }
  .prose-figure-pair .prose-figure img { max-width: 300px; }
}

/* --- Art layer: stickers ---------------------------------------------------
   Her own Twitch emotes, scattered loose in the page margins.

   The one rule that keeps this from turning into confetti over the copy:
   stickers only ever live OUTSIDE the content column. The wrap is 1180px, so
   anything past about 560px from the centre line is clear of text - and below
   1200px of viewport there is no margin left to sit in, so the whole layer
   switches off rather than shuffling on top of the words. Phones get their
   liveliness from the emote rail and the crew band instead.

   Two elements per sticker on purpose: the outer one owns the position and
   the tilt, the inner img owns the drift. One element cannot do both, because
   the animation would keep overwriting the placement transform.            */
.sticker-layer {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
}
.sticker {
  position: absolute;
  top: var(--y, 50%);
  width: var(--s, 72px);
  /* The tilt widens the footprint: a 76px square at 10deg needs about 88px of
     room, so half of it is ~0.58 x the width. --edge is that plus a few px of
     air, and the clamp uses it as a hard stop at both ends - a sticker can be
     pulled inward but it can never hang off the screen. */
  --edge: calc(var(--s, 72px) * 0.58 + 6px);
  left: clamp(var(--edge), calc(50% + var(--x, 0px)), calc(100% - var(--edge)));
  transform: translate(-50%, -50%) rotate(var(--r, 0deg));
}
.sticker img {
  display: block;
  width: 100%;
  height: auto;
  filter: drop-shadow(0 8px 16px rgba(112, 20, 84, 0.16));
  animation: sticker-drift var(--dur, 7s) ease-in-out infinite;
  animation-delay: var(--d, 0s);
}
@keyframes sticker-drift {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-12px) rotate(4deg); }
}

/* Anything in the wrap has to sit above the art, or the stickers bleed
   through the clip cards and the contact panel. */
.section, .hero { position: relative; }
.section > .wrap, .hero > .wrap { position: relative; z-index: 1; }

/* Below this there is no margin left to sit in, so the whole layer switches
   off rather than shuffling on top of the copy.

   1360px is the tightest breakpoint that actually holds, and the budget is
   worth writing down because two things eat into it that are easy to forget:
   the scrollbar takes ~17px off the layout width the media query reported,
   and the tilt makes each sticker's real footprint ~15% wider than --s. So at
   1360 there is ~1343px to play with: the wrap caps at 1180px, leaving a
   channel from the screen edge to the text at 129px, and each sticker needs
   ~44px either side of its centre. That is why every one of them is centred
   590-600px out with --s no more than 76px and --r no more than 10deg.
   Anything outside those numbers clips or lands on a paragraph. */
@media (max-width: 1359px) {
  .sticker-layer { display: none; }
}

/* The hero's stickers are the exception. They hang off the portrait itself
   rather than the page margin, sized as a percentage of it, so they orbit it
   all the way down to phone widths instead of switching off. */
.hero-portrait > .sticker {
  z-index: 2;
  width: var(--s, 34%);
}
@media (max-width: 820px) {
  /* Portrait is much smaller once the hero stacks - drop the crowded pair,
     and reel the survivors in, or the left one hangs off the screen edge. */
  .hero-portrait > .sticker[data-drop] { display: none; }
  .hero-portrait > .sticker[data-side="left"]  { --x: -36%; }
  .hero-portrait > .sticker[data-side="right"] { --x: 40%; }
}

/* A small emote tucked beside a section kicker. In flow, so unlike the
   scattered ones it works at every width. */
.kicker-emote {
  width: 30px;
  height: 30px;
  vertical-align: -9px;
  margin-right: 8px;
  filter: drop-shadow(0 2px 5px rgba(112, 20, 84, 0.18));
}

/* --- Emote rail -----------------------------------------------------------
   A marquee of her sub emotes. Content, not decoration: these are the tokens
   her chat actually types, so they get a heading and a link rather than
   being hidden in the margins.

   The track holds the list twice and slides exactly -50%, which puts copy two
   where copy one started - the seam never shows. Hovering parks it so you can
   actually look at one, and reduced-motion stops it outright and lets the rail
   scroll by hand.                                                          */
.emote-section { padding-bottom: 0; }
.emote-rail {
  position: relative;
  overflow: hidden;
  /* Asymmetric on purpose: the extra room underneath is where the hovered
     emote name sits, and the rail clips its own overflow. */
  padding: 26px 0 56px;
  background: linear-gradient(90deg, #fff3ea, #ffe9f2 50%, #fff3ea);
  border-block: 1px solid var(--border);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
.emote-track {
  --gap: 30px;
  display: flex;
  align-items: center;
  gap: var(--gap);
  /* The trailing pad is load-bearing, not spacing. With N items twice over,
     the track is 2N items plus 2N-1 gaps, so half of it lands half a gap
     short of item N and the loop jumps 15px every time round. One extra gap
     of padding on the end makes the width exactly 2N items plus 2N gaps, and
     -50% then lands precisely on item N. Keep the two in step - that is why
     both read from --gap. */
  padding-right: var(--gap);
  width: max-content;
  animation: emote-rail 54s linear infinite;
}
@keyframes emote-rail {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
.emote-rail:hover .emote-track,
.emote-rail:focus-within .emote-track { animation-play-state: paused; }

.emote-rail li { position: relative; flex: none; }
.emote-rail img {
  display: block;
  width: 62px;
  height: 62px;
  transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Lifted as it grows, so it expands into the padding above rather than down
   over its own name. */
.emote-rail li:hover img { transform: translateY(-6px) scale(1.35) rotate(-6deg); }
/* Token name, only while you're looking at one. */
.emote-rail .emote-name {
  position: absolute;
  left: 50%;
  /* Clear of the scaled, tilted emote above it: at 1.35x the 62px art
     still hangs about 9px below the row after the lift, so the name starts
     below that. */
  bottom: -35px;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--ink-accent);
  opacity: 0;
  transition: opacity 0.2s ease;
}
.emote-rail li:hover .emote-name { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .emote-track { animation: none; }
  /* Nothing is moving now, so let people push it along themselves. */
  .emote-rail { overflow-x: auto; scrollbar-width: thin; }
}

/* --- Crew band ------------------------------------------------------------
   The four of them, drawn rather than photographed - see js/crew-art.js for
   why. The disc behind each portrait is the same warm halo the hero portrait
   sits in, so the band belongs to the sunset like everything else.         */
.crew-band { background: var(--bg-elevated); border-block: 1px solid var(--border); }
.crew-cards {
  display: grid;
  /* 190px, not 210px: five cards - four animals and the chair - have to
     make one row at the full wrap width, or the chair sits alone on row 2. */
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 22px;
}
.crew-card {
  position: relative;
  padding: 28px 20px 24px;
  text-align: center;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
}
.crew-card:hover {
  transform: translateY(-6px);
  border-color: var(--border-bright);
  box-shadow: var(--shadow);
}
.crew-portrait {
  position: relative;
  width: clamp(104px, 15vw, 136px);
  margin: 0 auto 14px;
  aspect-ratio: 1;
}
.crew-portrait::before {
  content: '';
  position: absolute;
  inset: -6%;
  border-radius: 50%;
  background: radial-gradient(circle,
    rgba(255, 196, 107, 0.32) 0%,
    rgba(255, 122, 89, 0.14) 52%,
    transparent 72%);
}
.crew-portrait .pet-art {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: visible;
  transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* The chair's portrait is an emote rather than a drawing, so it letterboxes
   itself into the same square the drawings fill edge to edge. */
.crew-portrait img.pet-art { object-fit: contain; }

/* Stand-in glyph, for before the emote file exists. It is one flat shape
   next to four shaded drawings, so it takes the dim text tone rather than
   full ink - at portrait size the silhouette otherwise lands like a hole. */
.chair-art { color: var(--text-dim); }

/* They perk up when you go near them. */
.crew-card:hover .pet-art { transform: translateY(-5px) rotate(-4deg) scale(1.06); }

.crew-card .crew-name { font-size: 1.15rem; }
.crew-card .crew-role { margin-top: 4px; }
.crew-card .crew-note { margin-top: 10px; }

/* Crew cards on the About page keep the old inline layout, but swap the paw
   glyph for the real portrait. */
.crew .pet-art { width: 46px; height: 46px; flex: none; }

@media (prefers-reduced-motion: reduce) {
  .sticker img { animation: none; }
}

/* --- Cloud band -----------------------------------------------------------
   Her own channel artwork, used as a divider so the page sets into her
   sunset before the footer. Decorative only - no text sits on it. */
.cloud-band {
  height: clamp(96px, 13vw, 170px);
  background: url('../assets/clouds.webp') center / cover no-repeat;
  /* Fade in from the page ground, and ease off again at the base so the
     deep blue end doesn't butt hard against the footer's cream. */
  -webkit-mask-image: linear-gradient(180deg, transparent 0%, #000 62%, #000 86%, rgba(0, 0, 0, 0.45) 100%);
  mask-image: linear-gradient(180deg, transparent 0%, #000 62%, #000 86%, rgba(0, 0, 0, 0.45) 100%);
}

/* --- Footer --------------------------------------------------------------- */
.site-footer {
  position: relative;
  padding: 56px 0 44px;
  border-top: 1px solid var(--border);
  background: linear-gradient(180deg, var(--bg-elevated), #ffeadf);
}
.footer-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 28px;
}
/* Six chips no longer fit in one line beside the full page list, so on
   wider screens they sit as a tidy three-by-two block instead of leaving
   one chip stranded on a second row. Phones keep the plain wrap. */
.footer-socials { gap: 10px; }
@media (min-width: 761px) {
  .footer-socials {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
  }
  .footer-socials li { display: flex; }
  .footer-socials .social-chip { flex: 1; justify-content: center; }
}
.footer-socials .social-chip { padding: 10px 16px; font-size: 0.88rem; }
.footer-legal { color: var(--text-dim); font-size: 0.86rem; }
.footer-legal a { color: var(--text-muted); }

/* Build credit. Deliberately quieter than the legal line above it - it is a
   signature, not a second copyright notice. */
.footer-credit {
  margin-top: 4px;
  color: var(--text-dim);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
  opacity: 0.75;
}

/* Footer paw - the easter egg trigger. Quiet until hovered. */
.paw-btn {
  background: none;
  border: 0;
  padding: 6px;
  margin-left: 2px;
  cursor: pointer;
  color: var(--text-dim);
  line-height: 0;
  border-radius: 8px;
  transition: color 0.2s ease, transform 0.2s ease;
}
.paw-btn svg { width: 18px; height: 18px; }
.paw-btn:hover,
.paw-btn:focus-visible { color: var(--sunset-pink); transform: rotate(-12deg) scale(1.15); }
.paw-btn:active { transform: rotate(-12deg) scale(0.9); }
/* Squash and spring on every press, so the button answers each click. */
.paw-btn.is-pressed { animation: paw-press 0.42s ease-out; }
@keyframes paw-press {
  0%   { transform: rotate(-12deg) scale(0.82); }
  40%  { transform: rotate(8deg) scale(1.3); }
  70%  { transform: rotate(-6deg) scale(1.05); }
  100% { transform: rotate(0deg) scale(1); }
}

/* Paw prints sprinkled by the easter egg. */
.paw-print {
  position: fixed;
  z-index: 80;
  pointer-events: none;
  color: var(--sunset-pink);
  opacity: 0;
  /* All three are overwritten per-print from JS. --dx/--dy are the offset
     from where the print lands back to the button, so the print can start
     underneath the paw and fly out to its spot. */
  --rot: 0deg;
  --dx: 0px;
  --dy: 0px;
  animation: paw-pop 0.85s forwards;
}
.paw-print svg { width: 100%; height: 100%; }
/* Out from under the button, overshoot on landing, then lift away. No held
   frame anywhere in the middle: an earlier version sat at full opacity from
   36% to 70% and the effect looked like it had got stuck.

   The easing is set per segment rather than once on the shorthand, because
   the two halves want opposite curves. The flight out is expo-ish so the
   print is where it is going almost immediately, which is what makes the
   button feel responsive. The exit is a plain ease-in; sharing the expo curve
   with it dropped the print to 6% opacity a third of the way through the
   fade, so it blinked out rather than leaving. */
@keyframes paw-pop {
  0% {
    opacity: 0;
    transform: translate(var(--dx), var(--dy)) scale(0.25) rotate(var(--rot, 0deg));
    animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  }
  30% {
    opacity: 1;
    transform: translate(0, 0) scale(1.18) rotate(var(--rot, 0deg));
    animation-timing-function: ease-out;
  }
  48% {
    opacity: 1;
    transform: translate(0, 0) scale(1) rotate(var(--rot, 0deg));
    animation-timing-function: ease-in;
  }
  100% {
    opacity: 0;
    transform: translate(0, -14px) scale(0.9) rotate(var(--rot, 0deg));
  }
}
@media (prefers-reduced-motion: reduce) {
  /* No animation, so the prints simply appear where they land and the JS
     timeout clears them. */
  .paw-print { animation: none; opacity: 0.75; }
  .paw-btn.is-pressed { animation: none; }
}

/* --- Watch page ------------------------------------------------------------
   YouTube cards are facades: a thumbnail and a play button that swap for the
   real iframe on click, so the page doesn't pull six players on load. */
.video-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 26px;
  align-items: start;
}
.video {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  transition: transform 0.22s ease, box-shadow 0.22s ease, border-color 0.22s ease;
}
.video:hover {
  transform: translateY(-4px);
  border-color: var(--border-bright);
  box-shadow: var(--shadow);
}

.video-facade,
.video > iframe {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
  padding: 0;
  margin: 0;
  background: #1a1029;
}
.video-facade {
  position: relative;
  cursor: pointer;
  overflow: hidden;
}
.video-facade img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.video-play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: #fff;
  transition: background 0.2s ease;
  background: rgba(20, 4, 26, 0.18);
}
.video-play svg {
  width: 62px;
  height: 62px;
  border-radius: 50%;
  padding: 12px;
  background: linear-gradient(135deg, #cf2489, var(--ink-accent));
  box-shadow: 0 8px 22px rgba(112, 20, 84, 0.45);
  transition: transform 0.2s ease;
}
.video-facade:hover .video-play { background: rgba(20, 4, 26, 0.06); }
.video-facade:hover .video-play svg { transform: scale(1.09); }

.video-meta {
  padding: 16px 20px 20px;
  border-top: 1px solid var(--border);
}
.video-meta h3 { font-size: 1.02rem; letter-spacing: -0.01em; }
.video-meta p { margin-top: 6px; font-size: 0.85rem; color: var(--text-dim); }

/* YouTube Shorts and TikToks: the YouTube facade stood upright. Three
   across fills the content width, so the cards run as wide as a
   landscape card and as tall as that makes them. */
.vertical-grid {
  display: grid;
  /* Same tracks as .video-grid, so the columns line up with the row above. */
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 26px;
  align-items: start;
}
.vertical-grid .video-facade,
.vertical-grid .video > iframe {
  aspect-ratio: 9 / 16;
}
.vertical-grid .video-meta h3 a {
  color: inherit;
  text-decoration: none;
}
.vertical-grid .video-meta h3 a:hover { color: var(--ink-accent); }

/* Videos and Shorts share the YouTube section, each under its own label. */
.video-group + .video-group { margin-top: 48px; }
.video-group-title {
  margin-bottom: 18px;
  font-size: 1.15rem;
  letter-spacing: -0.01em;
}

/* X renders inside its own embed script, which sets its own width. These
   give it a row to sit in and stop a slow/blocked script from collapsing
   the layout. */
.post-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 22px;
  align-items: start;
  justify-items: center;
}
.post-grid > li {
  width: 100%;
  max-width: 360px;
  min-height: 120px;
}
.post-grid blockquote {
  margin: 0 auto;
}
/* Before the embed script upgrades them, the blockquotes are a bare link.
   Give that fallback enough shape to look deliberate rather than broken. */
.post-grid blockquote a {
  display: inline-block;
  padding: 14px 18px;
  color: var(--ink-accent);
  font-weight: 600;
  text-decoration: none;
}

/* --- Footer page links ----------------------------------------------------
   Setup, Emotes and Links live here rather than in the header: three page
   links is the most the header fits on a 320px phone (see README). */
.footer-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 14px 0 10px;
}
.footer-nav a {
  color: var(--text-muted);
  font-size: 0.88rem;
  font-weight: 600;
  text-decoration: none;
}
.footer-nav a:hover { color: var(--ink-accent); }

/* --- Achievements ---------------------------------------------------------
   Results cards, from the achievements concept. The big figure runs the
   full sunset ramp like the stat numbers do - it is display type, and the
   title underneath carries the same information in readable ink. */
.moments-section { padding-top: 0; }
.achievements {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}
.prose .achievements { margin-top: 24px; }
.achievement {
  padding: 22px 20px 24px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.achievement:first-child { background: linear-gradient(140deg, #fff1e1, #fff 82%); }
.achievement:last-child  { background: linear-gradient(140deg, #ffedf5, #fff 82%); }
/* Two classes deep so .prose p (on About) cannot take the colour back. */
.achievements .achievement-result {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 2.9rem;
  line-height: 1;
  letter-spacing: -0.05em;
  background: linear-gradient(120deg, #ff7a2f, var(--sunset-magenta));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  margin: 0 0 14px;
}
.achievement-result sup { font-size: 0.45em; vertical-align: super; letter-spacing: -0.02em; }
/* A word instead of a placing: smaller, but the same 46px line so cards in
   a row still line their titles up. */
.achievements .achievement-result.is-word { font-size: 2.2rem; line-height: 2.9rem; margin-top: -0.35rem; }
.achievement h3 { font-size: 1.05rem; letter-spacing: -0.02em; color: var(--text); }
.prose .achievement p.achievement-detail,
.achievement-detail { margin-top: 6px; font-size: 0.86rem; color: var(--text-muted); }

/* --- Setup page -----------------------------------------------------------
   Grouped label/value lists, used for the gear and for her settings. */
.page-head-actions { margin-top: 26px; }
/* Packed in columns rather than a grid: the groups run from two to eight
   rows, and grid rows left tall holes under the short ones. */
.spec-grid {
  columns: 3 280px;
  column-gap: 18px;
}
.spec-group {
  break-inside: avoid;
  margin-bottom: 18px;
  padding: 24px 24px 12px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.spec-group h3 {
  font-size: 0.78rem;
  font-family: var(--font-body);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-accent);
  margin-bottom: 8px;
}
.spec-group dl { margin: 0; }
.spec-group dl > div {
  padding: 12px 0;
  border-top: 1px solid var(--border);
}
.spec-group dl > div:first-child { border-top: 0; }
.spec-group dt { font-size: 0.8rem; color: var(--text-dim); }
.spec-group dd { margin: 2px 0 0; font-weight: 600; color: var(--text); }
.spec-group dd a { color: var(--ink-accent); }

/* Affiliate disclosure. Quiet, but plain text in the body colour family
   rather than fine print: it has to be noticed to count. */
.gear-disclosure {
  margin-bottom: 22px;
  max-width: 62ch;
  font-size: 0.88rem;
  color: var(--text-muted);
}
.gear-disclosure[hidden] { display: none; }

/* --- Emote gallery ------------------------------------------------------- */
.emote-section .clips-more { margin-top: 30px; }
.emote-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 14px;
}
.emote-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 20px 10px 16px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  font: inherit;
  color: var(--text);
  cursor: pointer;
  transition: border-color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
}
.emote-tile img {
  width: 72px;
  height: 72px;
  transition: transform 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.emote-tile:hover { border-color: var(--border-bright); transform: translateY(-3px); box-shadow: var(--shadow); }
.emote-tile:hover img { transform: scale(1.12) rotate(-5deg); }
.emote-tile-name {
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--ink-accent);
  overflow-wrap: anywhere;
}
.emote-tile.is-copied { border-color: var(--ink-accent); }
.emote-tile.is-copied .emote-tile-name::after { content: ' ✓'; }
.emote-tier {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin: 34px 0 14px;
  font-size: 1.25rem;
}
.emote-tier:first-child { margin-top: 0; }
.emote-tier-count {
  font-family: var(--font-body);
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--text-muted);
}
.emote-status {
  min-height: 1.65em;
  margin-bottom: 14px;
  font-size: 0.9rem;
  color: var(--text-muted);
}

/* --- Links page -----------------------------------------------------------
   The link-in-bio page. No header or footer, one 480px column on the hero's
   sunset sky, buttons tall enough to hit with a thumb. */
.links-page {
  min-height: 100vh;
  background: linear-gradient(175deg, #ffe4d0 0%, #ffd9e6 30%, #fde8f0 58%, var(--bg) 100%) no-repeat;
}
.links-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  max-width: 480px;
  margin-inline: auto;
  padding: 48px 16px 40px;
  text-align: center;
}
.links-avatar {
  width: 112px;
  height: 112px;
  border-radius: 50%;
  object-fit: cover;
  border: 3px solid #fff;
  box-shadow: var(--shadow);
}
.links-name { margin-top: 14px; }
.links-name img {
  display: block;
  height: 46px;
  width: auto;
  filter: drop-shadow(0 1px 1px rgba(112, 20, 84, 0.25));
}
.links-tagline { margin: 8px 0 16px; color: var(--text-muted); font-size: 0.95rem; }

.links-list {
  display: grid;
  gap: 12px;
  /* minmax(0) so a long video title ellipsises instead of widening the column. */
  grid-template-columns: minmax(0, 1fr);
  width: 100%;
  margin-top: 24px;
}
.link-btn {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 60px;
  padding: 12px 18px;
  background: var(--bg-card);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  color: var(--text);
  text-align: left;
  text-decoration: none;
  font-weight: 600;
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.link-btn:hover { transform: translateY(-2px); border-color: var(--ink-accent); box-shadow: var(--shadow); }
.link-btn .icon { width: 22px; height: 22px; flex: none; color: var(--ink-accent); }
.link-text { flex: 1; min-width: 0; }
.link-text small {
  display: block;
  margin-top: 1px;
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.link-chevron { width: 18px; height: 18px; flex: none; color: var(--text-dim); }
.link-btn.is-primary {
  background: linear-gradient(115deg, #cf2489 0%, var(--ink-accent) 55%, var(--ink-accent-deep) 100%);
  border-color: transparent;
  color: #fff;
  box-shadow: var(--glow);
}
.link-btn.is-primary .icon,
.link-btn.is-primary .link-chevron,
.link-btn.is-primary small { color: #fff; }

.links-code { margin-top: 20px; }
.links-socials { justify-content: center; gap: 10px; margin-top: 8px; }
.links-socials .social-chip { padding: 10px 16px; font-size: 0.86rem; }
.links-foot { margin-top: 32px; font-size: 0.82rem; color: var(--text-dim); }
.links-foot a { color: var(--ink-accent); font-weight: 600; }

/* --- Toby Run (play.html) --------------------------------------------------
   The canvas sets its own height from its width; see resize() in
   js/toby-run.js. touch-action stops a drag-to-duck from scrolling. */
.game-card {
  position: relative;
  overflow: hidden;
  background: var(--bg-card);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
.game-canvas {
  display: block;
  width: 100%;
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  cursor: pointer;
}
.game-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 20px;
  padding: 14px 20px 0;
  border-top: 1px solid var(--border);
}
.game-scores { display: flex; gap: 22px; color: var(--text-muted); font-size: 0.92rem; }
/* The look toggles, together at the right whether they fit one row or two. */
.game-looks {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
  margin-left: auto;
}
.game-scores strong { color: var(--text); font-family: var(--font-display); font-size: 1.05rem; }
.game-look {
  padding: 7px 16px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: none;
  font: 600 0.86rem var(--font-body);
  color: var(--text-muted);
  cursor: pointer;
}
.game-look[aria-pressed]::after { content: ": off"; }
.game-look[aria-pressed="true"] {
  background: var(--bg-card);
  color: var(--ink-accent);
  box-shadow: var(--shadow-sm);
}
.game-look[aria-pressed="true"]::after { content: ": on"; }
/* A second game sharing the card (js/toby-run.js, "Beta games") swaps
   these in and out, and picks the game with a switch above the card. */
.game-canvas[hidden],
.game-scores[hidden],
.game-looks[hidden],
.game-help[hidden] { display: none; }
.game-switch { margin-bottom: 14px; }
.game-status { min-height: 1.65em; padding: 6px 20px 14px; font-size: 0.88rem; color: var(--text-muted); }

/* A phone on its side: these lines were reading far too big. */
@media (orientation: landscape) and (max-height: 500px) {
  .game-status, .lb-status { font-size: 0.78rem; }
}

/* --- Full screen ------------------------------------------------------------
   js/toby-run.js puts .is-full on the card (and .game-full on <html>) when a
   phone turns sideways or the Full screen button is pressed: the card covers
   the window, the page underneath can't scroll, and the score and status
   lines give way to the canvas (which draws the score itself).            */
html.game-full,
html.game-full body { overflow: hidden; overscroll-behavior: none; }
/* The section's .wrap is its own stacking layer, under the sticky header. */
html.game-full .game-section > .wrap { z-index: 200; }
.game-card.is-full {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  flex-direction: column;
  /* The canvas fills every pixel, under a notch too: js/toby-run.js keeps
     the score clear of it (--safe-right) and the toggle sits inside it. */
  padding: 0;
  border: 0;
  border-radius: 0;
  background: #1b0f22;
}
.game-card.is-full .game-bar,
.game-card.is-full .game-status { display: none; }
/* Full screen on and off, in the field's top-left corner (top set by
   js/toby-run.js, under any competition or replay bar). */
.game-full-toggle {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 9px;
  background: rgba(255, 250, 245, 0.7);
  color: #2a1230;
  cursor: pointer;
  transition: background 0.15s;
}
.game-full-toggle:hover,
.game-full-toggle:focus-visible { background: rgba(255, 250, 245, 0.95); }
.game-card.is-full .game-full-toggle { left: max(10px, env(safe-area-inset-left)); }
.game-help {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 26px;
  margin-top: 20px;
  font-size: 0.9rem;
  color: var(--text-muted);
}
.game-help kbd {
  display: inline-block;
  min-width: 1.9em;
  padding: 1px 7px;
  border: 1px solid var(--border-bright);
  border-bottom-width: 2px;
  border-radius: 6px;
  background: var(--bg-card);
  font: 600 0.8rem var(--font-body);
  text-align: center;
  color: var(--text);
}

/* Leaderboard under the game, drawn by js/leaderboard.js. Same card as the
   game; rows are rank, Twitch picture, name, score. */
.lb-card {
  max-width: 720px;
  overflow: hidden;
  background: var(--bg-card);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}
.lb-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 20px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}
.lb-tabs {
  display: inline-flex;
  padding: 4px;
  border-radius: 999px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
}
.lb-tabs button {
  padding: 7px 16px;
  border: 0;
  border-radius: 999px;
  background: none;
  font: 600 0.86rem var(--font-body);
  color: var(--text-muted);
  cursor: pointer;
}
.lb-tabs button[aria-pressed="true"] {
  background: var(--bg-card);
  color: var(--ink-accent);
  box-shadow: var(--shadow-sm);
}
/* The boards (four, with a competition) don't fit across a phone at full
   size, so the switch takes the whole row and the buttons tighten up. */
@media (max-width: 480px) {
  .lb-tabs { display: flex; width: 100%; }
  .lb-tabs button {
    flex: 1 1 auto;
    min-width: 0;
    padding: 7px 4px;
    font-size: 0.78rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}
.lb-account {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.88rem;
  color: var(--text-muted);
}
.lb-who strong { color: var(--text); }
.lb-link {
  padding: 4px 2px;
  border: 0;
  background: none;
  font: 600 0.82rem var(--font-body);
  color: var(--ink-accent);
  cursor: pointer;
}
.lb-link:hover { text-decoration: underline; }
.lb-list { margin: 0; padding: 6px 0; list-style: none; }
.lb-row {
  display: grid;
  grid-template-columns: 2.2em 32px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 9px 20px;
}
.lb-row + .lb-row { border-top: 1px solid var(--border); }
.lb-rank {
  font: 700 0.95rem var(--font-display);
  color: var(--text-dim);
  text-align: right;
}
.lb-row.is-top .lb-rank { color: var(--ink-accent); }
.lb-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--bg-elevated);
  flex: none;
}
.lb-name {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
}
a.lb-name:hover { color: var(--ink-accent); }
/* Name and award badges share the name column; a long name gives way
   to the badges, never the other way round. */
.lb-who { display: flex; align-items: center; gap: 6px; min-width: 0; }
.lb-badges { display: flex; gap: 3px; flex: none; font-size: 0.9rem; line-height: 1; }
.lb-badge { white-space: nowrap; }
.lb-badge-vip { display: inline-grid; place-items: center; }
/* Waiting for review: only moderators see these rows, greyed out. */
.lb-row.is-review { opacity: 0.5; }
.lb-row.is-review:hover, .lb-row.is-review:focus-within { opacity: 0.85; }
.lb-review-tag {
  flex: none;
  padding: 1px 7px;
  border-radius: 999px;
  background: rgba(224, 173, 53, 0.18);
  color: var(--text);
  font-size: 0.72rem;
  font-weight: 700;
  white-space: nowrap;
}
.lb-review { padding: 12px 20px; border-bottom: 1px solid var(--border); background: rgba(224, 173, 53, 0.07); }
.lb-review[hidden] { display: none; }
.lb-review-title { margin: 0 0 8px; font-weight: 700; font-size: 0.92rem; }
.lb-review-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.lb-review-item p { margin: 0; }
.lb-review-head { display: flex; gap: 10px; align-items: baseline; }
.lb-review-head span { font: 700 0.95rem var(--font-display); color: var(--text-muted); }
.lb-review-why, .lb-review-stats { font-size: 0.82rem; color: var(--text-muted); }
.lb-review-actions { display: flex; gap: 14px; margin-top: 4px !important; }
/* The "⋯" menu holding a row's moderator actions. */
.lb-more-wrap { position: relative; display: inline-flex; }
.lb-more { padding: 0 4px; font-size: 1.1rem; line-height: 1; }
/* Fixed, and placed by js/leaderboard.js, since .lb-card clips overflow. */
.lb-menu {
  position: fixed;
  z-index: 20;
  display: flex;
  flex-direction: column;
  min-width: 150px;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-elevated);
  box-shadow: 0 10px 30px rgba(42, 18, 48, 0.18);
}
.lb-menu[hidden] { display: none; }
.lb-menu-item {
  padding: 8px 10px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 0.9rem;
  text-align: left;
  cursor: pointer;
}
.lb-menu-item:hover,
.lb-menu-item:focus-visible { background: rgba(255, 77, 157, 0.1); color: var(--ink-accent); outline: none; }
.lb-badge-streamer,
.lb-badge-dev {
  display: inline-grid;
  place-items: center;
  width: 17px;
  height: 17px;
  border-radius: 4px;
  background: #e91916;
}
/* The developer's: same tile as the streamer's camera, in violet. */
.lb-badge-dev { background: #6d3fd8; }
.lb-score { font: 700 1rem var(--font-display); color: var(--text); }
.lb-row.is-me { background: rgba(255, 77, 157, 0.08); }
.lb-you { border-top: 1px dashed var(--border-bright) !important; }
/* Replay and moderation buttons take a fifth column. While any row has
   them, every row keeps that column at one width (wide enough for Replay,
   or for Replay and ⋯ for moderators), so scores line up down the board
   whether or not a row has a Replay button. */
.lb-list:has(.lb-mod) .lb-row { grid-template-columns: 2.2em 32px minmax(0, 1fr) auto var(--lb-mod-w, 3.4rem); }
.lb-list:has(.lb-more) { --lb-mod-w: 5.3rem; }
.lb-mod { display: flex; justify-content: flex-end; gap: 8px; }
.lb-empty { padding: 22px 20px; color: var(--text-muted); }
.lb-status {
  min-height: 1.65em;
  padding: 0 20px 14px;
  font-size: 0.88rem;
  color: var(--text-muted);
}

/* --- Season bar -----------------------------------------------------------
   One line under the header while a season in `seasons` (js/config.js) is
   running. Dusk purple with cream text and a pale gold link, all well above
   4.5:1. */
.season-bar {
  background: linear-gradient(90deg, #2f2350, #4a2f63 55%, #2f2350);
  color: #fff3ea;
  font-size: 0.9rem;
}
.season-bar-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 4px 12px;
  padding-block: 9px;
  text-align: center;
}
.season-bar a {
  color: #ffc46b;
  font-weight: 700;
  text-underline-offset: 3px;
}
.season-bar a:hover { color: #ffe0a8; }

/* --- Toby Run wardrobe ----------------------------------------------------
   Rows of pill buttons, one per slot. Locked ones are dimmed, say what
   they need and carry a thin progress bar. */
.wardrobe {
  margin-top: 36px;
  padding: 24px clamp(18px, 3vw, 28px) 26px;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.wardrobe-head h2 { font-size: 1.4rem; letter-spacing: -0.02em; }
.wardrobe-head p { margin-top: 6px; color: var(--text-muted); font-size: 0.92rem; }
.wardrobe-slots { display: grid; gap: 18px; margin-top: 20px; }
.wardrobe-slot h3 {
  margin-bottom: 8px;
  font: 700 0.75rem var(--font-body);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-accent);
}
.wardrobe-slot ul { display: flex; flex-wrap: wrap; gap: 10px; }
.wardrobe-item {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 2px 8px;
  min-height: 44px;
  padding: 8px 16px;
  border: 1px solid var(--border-bright);
  border-radius: 999px;
  background: var(--bg-card);
  color: var(--text);
  font: 600 0.9rem var(--font-body);
  text-align: left;
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease;
}
.wardrobe-item:hover { border-color: var(--ink-accent); }
.wardrobe-item[aria-pressed='true'] {
  background: linear-gradient(115deg, #cf2489 0%, var(--ink-accent) 55%, var(--ink-accent-deep) 100%);
  border-color: transparent;
  color: #fff;
}
/* Locked: readable, not greyed into illegibility. The need line and the
   bar carry the information; the button just can't be pressed. */
.wardrobe-item.is-locked {
  cursor: not-allowed;
  border-style: dashed;
  border-radius: 14px;
  background: var(--bg-elevated);
  color: var(--text-muted);
}
.wardrobe-item.is-locked:hover { border-color: var(--border-bright); }
.wardrobe-item small { font-weight: 500; font-size: 0.78rem; color: var(--text-muted); }
.wardrobe-swatch {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px var(--border-bright);
}
.wardrobe-progress {
  flex-basis: 100%;
  height: 4px;
  border-radius: 4px;
  background: rgba(175, 25, 130, 0.12);
  overflow: hidden;
}
.wardrobe-progress span {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--sunset-coral), var(--sunset-magenta));
}
.lb-offline strong { color: var(--text); }

/* Competitions (js/leaderboard.js). The bar sits on top of the game card
   while one is on or just finished; the admin row sits under the board
   tabs for moderators. The run switch borrows the board tabs' look. */
.comp-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 20px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--border);
  background: rgba(255, 77, 157, 0.08);
}
.comp-bar[hidden] { display: none; }
.comp-info {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  margin: 0;
  font-size: 0.9rem;
  color: var(--text-muted);
}
.comp-info strong { color: var(--text); }
.comp-live { color: var(--ink-accent) !important; }
.comp-clock { font: 700 0.95rem var(--font-display); color: var(--text); font-variant-numeric: tabular-nums; }
.comp-switch {
  display: inline-flex;
  padding: 4px;
  border-radius: 999px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
}
.comp-switch button {
  padding: 6px 14px;
  border: 0;
  border-radius: 999px;
  background: none;
  font: 600 0.84rem var(--font-body);
  color: var(--text-muted);
  cursor: pointer;
}
.comp-switch button[aria-pressed="true"] {
  background: var(--bg-card);
  color: var(--ink-accent);
  box-shadow: var(--shadow-sm);
}
.comp-switch button:disabled { opacity: 0.45; cursor: default; }
/* The replay verdict: a match is plain good news, a mismatch or a slow
   pace is what a moderator is looking for. */
.replay-ok { color: #1f8a4c !important; }
.replay-flag { color: #c62828 !important; font-weight: 700; }
.lb-admin {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 12px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--border);
  font-size: 0.88rem;
  color: var(--text-muted);
}
.lb-admin[hidden] { display: none; }
.lb-admin-label { font-weight: 600; color: var(--text); }
.lb-select {
  padding: 6px 10px;
  border: 1px solid var(--border-bright);
  border-radius: 10px;
  background: var(--bg-card);
  font: 600 0.84rem var(--font-body);
  color: var(--text);
}

/* --- Dark theme -------------------------------------------------------------
   The sunset after dark: deep plum night instead of cream, the same ramp
   for decoration. Set by data-theme="dark" on <html> (see the inline script
   in each page's <head> and initThemeToggle in js/site.js).

   Contrast note, mirroring the one at the top: on the dark ground #af1982
   is too dim to read, so --ink-accent becomes a light pink. Anything that
   paints white text on the accent (the primary buttons) keeps the original
   deep magentas, set explicitly below, or the labels would wash out. */
:root[data-theme='dark'] {
  color-scheme: dark;

  --ink-accent:      #ff7cc8;
  --ink-accent-deep: #ffa6d9;

  --bg:           #170d1c;
  --bg-elevated:  #1f1226;
  --bg-card:      #26172d;
  --border:       rgba(255, 124, 200, 0.14);
  --border-bright: rgba(255, 124, 200, 0.3);

  --text:         #f8eaf4;
  --text-muted:   #c9b2cc;
  --text-dim:     #a08aa5;

  --shadow:    0 14px 38px rgba(0, 0, 0, 0.45);
  --shadow-sm: 0 3px 12px rgba(0, 0, 0, 0.3);
  --glow:      0 8px 24px rgba(207, 36, 137, 0.35);

  /* The light theme's hard-coded #57405c body copy */
  --text-soft: #d8c3d9;
}

[data-theme='dark'] .site-header { background: rgba(23, 13, 28, 0.86); }

[data-theme='dark'] .btn-primary,
[data-theme='dark'] .link-btn.is-primary,
[data-theme='dark'] .wardrobe-item[aria-pressed='true'] {
  background: linear-gradient(115deg, #cf2489 0%, var(--sunset-magenta) 55%, #8e1276 100%);
}
[data-theme='dark'] .video-play svg {
  background: linear-gradient(135deg, #cf2489, var(--sunset-magenta));
}
[data-theme='dark'] .btn-ghost:hover { background: #311d38; }
[data-theme='dark'] .replay-ok { color: #7ddc9f !important; }
[data-theme='dark'] .replay-flag { color: #ff8a80 !important; }

/* Skies: dusk at the top, settling into the night ground. */
[data-theme='dark'] .hero,
[data-theme='dark'] .links-page {
  background: linear-gradient(175deg, #34173a 0%, #2d1534 34%, #22112a 62%, var(--bg) 100%) no-repeat;
}
[data-theme='dark'] .page-head {
  background: linear-gradient(180deg, #321738 0%, #261330 52%, var(--bg) 92%);
}
[data-theme='dark'] .hero::before,
[data-theme='dark'] .page-head::before { opacity: 0.45; }

[data-theme='dark'] .hero-tagline,
[data-theme='dark'] .page-head p { color: var(--text-soft); }

/* White rings and frosted-white chips become frosted night. */
[data-theme='dark'] .hero-portrait > img,
[data-theme='dark'] .page-head-portrait img { border-color: rgba(255, 214, 236, 0.22); }
[data-theme='dark'] .links-avatar { border-color: var(--border-bright); }
[data-theme='dark'] .brand-mark { border-color: rgba(255, 214, 236, 0.4); }
[data-theme='dark'] .eyebrow,
[data-theme='dark'] .live-pill { background: rgba(38, 23, 45, 0.78); }
[data-theme='dark'] .creator-code-button { background: rgba(38, 23, 45, 0.55); }

[data-theme='dark'] .live-screen {
  background: linear-gradient(165deg, #2a1631 0%, #321a36 100%);
}
[data-theme='dark'] .contact-card {
  background:
    radial-gradient(ellipse at 50% 120%, rgba(255, 122, 89, 0.22), transparent 62%),
    linear-gradient(140deg, #2e1734 0%, #2f1a2a 100%);
  border-color: var(--border-bright);
}
[data-theme='dark'] .pull-quote {
  background: linear-gradient(90deg, rgba(255, 124, 200, 0.09) 0%, rgba(255, 124, 200, 0) 78%);
}
[data-theme='dark'] .emote-rail {
  background: linear-gradient(90deg, var(--bg-elevated), #2a1531 50%, var(--bg-elevated));
}
[data-theme='dark'] .site-footer {
  background: linear-gradient(180deg, var(--bg-elevated), #241329);
}
[data-theme='dark'] .achievement:first-child { background: linear-gradient(140deg, #33201f, var(--bg-card) 82%); }
[data-theme='dark'] .achievement:last-child  { background: linear-gradient(140deg, #331a2e, var(--bg-card) 82%); }

/* The ramp text ends on #af1982, which disappears into the night, so the
   display numbers run orange to hot pink instead. */
[data-theme='dark'] .stat-value,
[data-theme='dark'] .achievements .achievement-result {
  background-image: linear-gradient(120deg, #ff9a4d, var(--sunset-pink));
}

[data-theme='dark'] .clips-empty code,
[data-theme='dark'] .prose code { background: rgba(255, 124, 200, 0.12); }
