/* === Reset === */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font: inherit;
  font-size: 100%;
  vertical-align: baseline;
}

html {
  line-height: 1;
}

ol, ul {
  list-style: none;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

caption, th, td {
  text-align: left;
  font-weight: normal;
  vertical-align: middle;
}

q, blockquote {
  quotes: none;
}
q:before, q:after, blockquote:before, blockquote:after {
  content: "";
  content: none;
}

a img {
  border: none;
}

article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary {
  display: block;
}

[hidden] {
  display: none !important;
}

/* === Brand Tokens === */
:root {
  color-scheme: dark;

  /* Core Greens */
  --g: #4CAF50;
  --gl: #81C784;
  --gp: #C8E6C9;

  /* Dark Foundation */
  --bk: #0D0D0D;
  --dk: #1A1A1A;
  --ch: #2D2D2D;

  /* Warm Accents */
  --em: #E8531E;
  --am: #F5A623;
  --ru: #C0392B;

  /* Neutrals */
  --n1: #F5F5F5;
  --n2: #E0E0E0;
  --n3: #BDBDBD;
  --n4: #9E9E9E;
  --n5: #757575;
  --n6: #424242;

  /* Font Awesome */
  --fa-font-solid: normal 900 1em/1 "Font Awesome 6 Solid";
  --fa-font-brands: normal 400 1em/1 "Font Awesome 6 Brands";
}

/* === Base === */
html, body {
  background: var(--bk);
  color: var(--n1);
}

html {
  height: 100%;
}

body {
  min-height: 100%;
  font-family: 'Outfit', sans-serif;
  font-weight: 400;
  line-height: 1.5;
  position: relative;
  overflow-x: hidden;
}

body::before {
  content: "";
  position: fixed;
  top: -50%;
  left: -50%;
  width: 200%;
  height: 200%;
  background:
    radial-gradient(ellipse at 60% 50%, rgba(76, 175, 80, 0.06) 0%, transparent 60%),
    radial-gradient(ellipse at 30% 70%, rgba(232, 83, 30, 0.04) 0%, transparent 50%);
  pointer-events: none;
  z-index: 0;
  animation: drift 20s ease-in-out infinite alternate;
}

/* === Accessibility === */
a:focus-visible,
button:focus-visible {
  outline: 2px solid var(--g);
  outline-offset: 4px;
  border-radius: 2px;
}

.skip-link {
  position: absolute;
  top: -100%;
  left: 0;
  padding: 0.5em 1em;
  background: var(--n1);
  color: var(--bk);
  font-family: 'Outfit', sans-serif;
  font-size: 1rem;
  z-index: 1000;
  text-decoration: none;
}

.skip-link:focus {
  top: 0;
}

/* === Links === */
a:link,
a:visited {
  color: var(--g);
  transition: color 0.2s;
}

a:hover {
  color: var(--gl);
}

a:active {
  color: var(--g);
}

/* === Layout === */
.site {
  position: relative;
  z-index: 1;
  width: 90%;
  max-width: 720px;
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 3rem 0;
  box-sizing: border-box;
}

/* === Divider === */
.divider {
  height: 1px;
  border: none;
  background: linear-gradient(90deg, transparent, rgba(76, 175, 80, 0.2), transparent);
  margin: 2rem auto;
  max-width: 400px;
}

/* === Hero === */
.hero {
  margin-bottom: 1.5rem;
}

.hero-logo-wrap {
  position: relative;
  display: inline-block;
}

.hero-logo {
  border-radius: 24px;
  border: 2px solid rgba(76, 175, 80, 0.2);
  width: 250px;
  height: 250px;
  transition: border-color 0.3s;
  /* A transparent shadow rather than none: box-shadow can't interpolate
     between a shadow and `none`, so without this the glow would cut out at
     the end of the settle instead of fading. */
  box-shadow: 0 0 0 rgba(145, 70, 255, 0);
}

/* Live state — the logo breathes. Four seconds in and out is roughly a
   resting breath, which is what keeps it from reading as an alert: slow
   enough to notice out of the corner of your eye, never fast enough to
   nag. Scale, glow and border move together so it's one gesture rather
   than three effects sharing a timer. */
.hero-logo-wrap.is-live .hero-logo {
  border-color: #9146FF;
  animation: live-breathe 4s ease-in-out infinite;
}

/* Coming on air. The mirror of the problem below, and it has the same cause:
   you cannot transition INTO an animation either. Adding .is-live applies
   live-breathe immediately, so the border simply appears at the breath's
   opening value — a resting green one frame, dim purple the next, with the
   swell only arriving two seconds later.
   So the arrival is its own animation: one swell from rest, up through the
   breath's peak, landing exactly on the breath's opening frame. That last
   keyframe MUST stay identical to live-breathe's 0% or the handover shows as
   a jump. The duration is mirrored by ARRIVE_MS in js/twitch-status.js. */
.hero-logo-wrap.is-live.is-arriving .hero-logo {
  animation: live-arrive 1.1s cubic-bezier(0.22, 0.61, 0.36, 1) forwards;
}

@keyframes live-arrive {
  0% {
    transform: scale(1);
    border-color: rgba(76, 175, 80, 0.2);
    box-shadow: 0 0 0 rgba(145, 70, 255, 0);
  }
  55% {
    transform: scale(1.045);
    border-color: #B084FF;
    box-shadow:
      0 0 28px rgba(145, 70, 255, 0.75),
      0 0 60px rgba(145, 70, 255, 0.35);
  }
  100% {
    transform: scale(1);
    border-color: rgba(145, 70, 255, 0.35);
    box-shadow: 0 0 0 rgba(145, 70, 255, 0);
  }
}

/* Coming off air. A transition cannot ease out of an animation on its own:
   the spec builds the style a transition compares against WITHOUT declarative
   animations in it, so at the moment .is-live goes away there is no purple
   border or glow on record to start from and the live look is simply gone
   between one frame and the next. js/twitch-status.js copies the animation's
   current values onto the element as inline styles first, which gives this
   something real to move away from, then drops them. */
.hero-logo-wrap.is-settling .hero-logo {
  transition:
    border-color 0.7s ease-out,
    box-shadow 0.7s ease-out,
    transform 0.7s ease-out;
}

/* Logo is only a link while live — the anchor has no href otherwise, so
   these only ever apply on air. */
.hero-logo-link {
  display: inline-block;
  line-height: 0;
}

.hero-logo-link[href] {
  cursor: pointer;
}

.hero-logo-link[href]:hover .hero-logo {
  border-color: #A970FF;
  transform: scale(1.02);
}

/* Hover has to stop the breathing to be felt at all: an animation outranks a
   plain declaration, so while live the rule above would be ignored for both
   transform and border-color. Switching the animation off hands them back.
   The jump is imperceptible — the breath only travels 2.5%. */
.hero-logo-wrap.is-live .hero-logo-link[href]:hover .hero-logo {
  animation: none;
  transform: scale(1.03);
  border-color: #A970FF;
  box-shadow: 0 0 30px rgba(145, 70, 255, 0.5);
}

.hero-logo-link[href] .hero-logo {
  transition: border-color 0.3s, transform 0.3s, box-shadow 0.3s;
}

/* "I've just gone live" — a slot sitting immediately above the logo with the
   line clipped inside it. The text starts translated down, so it's hidden
   behind the logo's top edge, and rises into view: the logo appears to push
   it out. Clipping is what sells it — the logo PNG has transparency, so
   simply stacking the text behind it would show through. */
.live-shout-slot {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 2px);
  transform: translateX(-50%);
  width: max-content;
  max-width: 92vw;
  overflow: hidden;
  padding: 0.35rem 0.25rem;
  pointer-events: none;
}

.live-shout {
  font-family: 'Syne', sans-serif;
  font-weight: 700;
  font-size: clamp(0.8rem, 2.6vw, 1.05rem);
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #C8A6FF;
  text-shadow: 0 0 18px rgba(145, 70, 255, 0.5);
  white-space: nowrap;
  /* Parked below the slot until the rise animation runs, so nothing flashes
     into place before JS gets to it. */
  transform: translateY(120%);
}

/* Two animations on one element, with the properties split cleanly between
   them: the rise owns transform, the breath owns opacity and text-shadow. A
   property in both would go to whichever is declared later in the shorthand,
   silently, so the split is the thing that keeps this predictable. Both start
   the moment we go live — the same moment the logo starts breathing — which
   is what keeps the two in step. */
.live-shout.live-shout-rise {
  animation:
    shout-rise 0.7s cubic-bezier(0.2, 0.8, 0.2, 1) forwards,
    live-shout-breathe 4s ease-in-out infinite;
}

/* Declared after the rise so its animation replaces that shorthand outright.
   It has to be an animation rather than a transition: JS adds is-leaving
   while live-shout-rise is still on the element, and a forwards-filled
   animation outranks plain declarations — so the transform and opacity this
   used to carry were silently ignored and the old line never dropped away. */
.live-shout.is-leaving {
  animation: shout-fall 0.22s ease-in forwards;
}

/* Transform only — the breath owns opacity now. The slot clips anything below
   its own top edge, so the line is hidden until it emerges regardless; the
   fade this used to carry was never what sold the effect. */
@keyframes shout-rise {
  from { transform: translateY(120%); }
  to   { transform: translateY(0); }
}

@keyframes shout-fall {
  from { transform: translateY(0);    opacity: 1; }
  to   { transform: translateY(120%); opacity: 0; }
}

/* Glow plus a shallow dip in opacity — glow alone was invisible against a
   dark page. 0.82 is as far down as this can go: #C8A6FF over #0D0D0D at that
   alpha still clears 5:1, so the line stays readable at the bottom of every
   breath, which is the whole constraint. */
@keyframes live-shout-breathe {
  0%, 100% {
    opacity: 1;
    text-shadow: 0 0 10px rgba(145, 70, 255, 0.3);
  }
  50% {
    opacity: 0.82;
    text-shadow: 0 0 30px rgba(145, 70, 255, 0.95);
  }
}

/* The reduced-motion counterpart: glow only, nothing moves, nothing fades. */
@keyframes live-shout-glow-still {
  0%, 100% { text-shadow: 0 0 10px rgba(145, 70, 255, 0.3); }
  50%      { text-shadow: 0 0 30px rgba(145, 70, 255, 0.95); }
}

/* Tint the Twitch icon while live so the topbar agrees with the hero. */
.topbar-links a.twitch-status.is-live,
.topbar-links a.twitch-status.is-live:link,
.topbar-links a.twitch-status.is-live:visited {
  color: #9146FF;
}

@media (prefers-reduced-motion: reduce) {
  /* No travel: the line is simply there, in place, from the start. It keeps
     the glow pulse though — same reasoning as the logo's, and the two still
     share a cadence. Opacity is pinned at 1 here, so nothing fades either. */
  .live-shout,
  .live-shout.live-shout-rise {
    animation: live-shout-glow-still 6s ease-in-out infinite;
    transform: none;
    opacity: 1;
  }

  .live-shout.is-leaving {
    animation: none;
    transform: none;
    opacity: 1;
  }

  .hero-logo-link[href]:hover .hero-logo {
    transform: none;
  }
}

.live-badge {
  display: none;
  position: absolute;
  top: 12px;
  left: 12px;
  background: rgba(13, 13, 13, 0.85);
  color: var(--n1);
  font-family: 'JetBrains Mono', monospace;
  font-weight: 600;
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  align-items: center;
  gap: 0.35rem;
  pointer-events: none;
}

/* Fades in with the arrival rather than appearing fully formed — the same
   entrance the now-playing card already uses, so the two arrive together. */
.hero-logo-wrap.is-live .live-badge {
  display: inline-flex;
  animation: fadeUp 0.4s ease-out both;
}

/* Now playing — box art + category, top-right of the hero logo. Only
   rendered while live; JS toggles [hidden] and the reset above makes that
   display: none !important. */
.now-playing {
  position: absolute;
  top: -10px;
  right: -10px;
  width: 76px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 0.4rem;
  box-sizing: border-box;
  background: rgba(13, 13, 13, 0.9);
  border: 1px solid rgba(145, 70, 255, 0.45);
  border-radius: 8px;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  text-align: center;
  animation: fadeUp 0.4s ease-out both;
}

.now-playing-art {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: 4px;
}

.now-playing-game {
  font-family: 'JetBrains Mono', monospace;
  font-weight: 500;
  font-size: 0.6rem;
  line-height: 1.25;
  letter-spacing: 0.04em;
  color: var(--n2);
  text-transform: uppercase;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

@media (max-width: 400px) {
  .now-playing {
    width: 64px;
    top: -8px;
    right: -8px;
  }
}

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

.live-dot {
  width: 6px;
  height: 6px;
  background: var(--ru);
  border-radius: 50%;
  animation: live-dot-pulse 2s ease-in-out infinite;
}

@keyframes live-dot-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.2; }
}

/* Amounts matter more than intent here. The first pass moved 2.5% over two
   seconds — about 3px per side on a 250px logo — and measured a 3.7%
   brightness swing in the glow beside it, which is under the threshold where
   the eye picks movement up at all. It read as static. 4.5% and a halo twice
   the strength is still unmistakably a breath rather than a throb, but you
   can actually see it happen. */
@keyframes live-breathe {
  0%, 100% {
    transform: scale(1);
    border-color: rgba(145, 70, 255, 0.35);
    box-shadow: 0 0 0 rgba(145, 70, 255, 0);
  }
  50% {
    transform: scale(1.045);
    border-color: #B084FF;
    box-shadow:
      0 0 28px rgba(145, 70, 255, 0.75),
      0 0 60px rgba(145, 70, 255, 0.35);
  }
}

/* Reduced motion, but not reduced signal.
 *
 * Switching the breath off entirely is what made the live logo look dead on
 * a machine with Windows' animation effects turned off — which is a common
 * setting, not an edge case. The request that setting expresses is about
 * MOVEMENT: travel, parallax, zoom, the things that cause vestibular trouble.
 * A colour and glow fade moves nothing.
 *
 * So the scale is dropped and the pulse stays, slowed to six seconds and
 * softened. That's 0.17Hz — three orders of magnitude below the three-per-
 * second flash threshold — and the border never drops below a visible purple,
 * so the live state is carried by colour on its own even at the trough. */
@media (prefers-reduced-motion: reduce) {
  .live-dot {
    animation: none;
  }

  .hero-logo-wrap.is-live .hero-logo {
    animation: live-breathe-soft 6s ease-in-out infinite;
  }

  /* Its own arrival, landing on live-breathe-soft's opening frame rather than
     live-breathe's — the two rest at different border alphas, and handing over
     to the wrong one shows. */
  .hero-logo-wrap.is-live.is-arriving .hero-logo {
    animation: live-arrive-soft 1.1s ease-out forwards;
  }
}

@keyframes live-arrive-soft {
  0% {
    transform: scale(1);
    border-color: rgba(76, 175, 80, 0.2);
    box-shadow: 0 0 0 rgba(145, 70, 255, 0);
  }
  55% {
    transform: scale(1.03);
    border-color: #B084FF;
    box-shadow:
      0 0 26px rgba(145, 70, 255, 0.7),
      0 0 56px rgba(145, 70, 255, 0.3);
  }
  100% {
    transform: scale(1);
    border-color: rgba(145, 70, 255, 0.5);
    box-shadow: 0 0 0 rgba(145, 70, 255, 0);
  }
}

/* Asked for: the logo grows with the pulse here too, not only colour and
   glow. It's 3% against the full breath's 4.5%, over six seconds rather than
   four — slow enough that it reads as the border swelling rather than the
   image jumping, which is what keeps it tolerable for someone who asked for
   less movement. Worth knowing it applies to every visitor with that setting,
   not just this browser. */
@keyframes live-breathe-soft {
  0%, 100% {
    transform: scale(1);
    border-color: rgba(145, 70, 255, 0.5);
    box-shadow: 0 0 0 rgba(145, 70, 255, 0);
  }
  50% {
    transform: scale(1.03);
    border-color: #B084FF;
    box-shadow:
      0 0 26px rgba(145, 70, 255, 0.7),
      0 0 56px rgba(145, 70, 255, 0.3);
  }
}

.wordmark {
  font-family: 'Syne', sans-serif;
  font-weight: 800;
  font-size: clamp(2rem, 6vw, 3.2rem);
  color: var(--n1);
  margin-top: 1.25rem;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.wordmark span {
  color: var(--g);
}

.tagline {
  font-family: 'Outfit', sans-serif;
  font-weight: 300;
  font-size: 1rem;
  color: var(--n4);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  margin-top: 0.5rem;
}

/* === Hero Glow (on body::before — see body rule) === */
@keyframes drift {
  0%   { transform: translate(0, 0) rotate(0deg); }
  100% { transform: translate(-2%, 1%) rotate(1deg); }
}

@media (prefers-reduced-motion: reduce) {
  body::before {
    animation: none;
  }
}

/* === Top Bar === */
.topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background: rgba(13, 13, 13, 0.92);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  border-bottom: 1px solid rgba(76, 175, 80, 0.1);
}

.topbar-inner {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0.8rem 2rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.topbar-brand {
  font-family: 'Syne', sans-serif;
  font-weight: 800;
  font-size: 1.1rem;
  letter-spacing: 0.05em;
  color: var(--n1);
}

.topbar-brand span {
  color: var(--g);
}

.topbar-links {
  display: flex;
  gap: 1.25rem;
  align-items: center;
  font-size: 1.1rem;
}

.topbar-links a,
.topbar-links a:link,
.topbar-links a:visited {
  color: var(--n4);
  text-decoration: none;
  transition: color 0.3s;
}

.topbar-links a:hover {
  color: var(--g);
}

/* === Fade-Up Animation === */
@keyframes fadeUp {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.fade-up {
  opacity: 0;
  animation: fadeUp 0.6s ease-out forwards;
}

.fade-up-1 { animation-delay: 0s; }
.fade-up-2 { animation-delay: 0.15s; }
.fade-up-3 { animation-delay: 0.3s; }
.fade-up-4 { animation-delay: 0.45s; }
.fade-up-5 { animation-delay: 0.6s; }

@media (prefers-reduced-motion: reduce) {
  .fade-up {
    opacity: 1;
    animation: none;
  }
}

/* === Utilities === */

/* Visible to screen readers, not to eyes. Used for context that sighted
   users get from layout — e.g. "Load position 01:" before a mod name. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* === Content pages (everything except the hero homepage) === */
.page {
  position: relative;
  z-index: 1;
  width: 90%;
  max-width: 860px;
  margin: 0 auto;
  padding: 6rem 0 4rem;
  box-sizing: border-box;
}

.page-header {
  margin-bottom: 2rem;
}

.page-eyebrow {
  display: block;
  font-family: 'JetBrains Mono', monospace;
  font-weight: 500;
  font-size: 0.7rem;
  color: var(--em);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  margin-bottom: 0.4rem;
}

.page-title {
  font-family: 'Syne', sans-serif;
  font-weight: 800;
  font-size: clamp(1.8rem, 5vw, 2.6rem);
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--n1);
}

.page-intro {
  margin-top: 0.75rem;
  max-width: 60ch;
  color: var(--n3);
  font-size: 0.95rem;
}

/* Server facts — a definition list, so the label/value pairing is real
   structure rather than two spans that happen to sit next to each other. */
.server-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-top: 1.5rem;
}

.server-fact {
  background: var(--dk);
  border: 1px solid rgba(255, 255, 255, 0.05);
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
}

.server-fact dt {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--n5);
}

.server-fact dd {
  font-size: 0.9rem;
  color: var(--n2);
  margin-top: 0.15rem;
}

/* === Mods: controls === */
.mods-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 2rem 0 1rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(76, 175, 80, 0.15);
}

.mods-filter {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  flex: 1 1 260px;
}

.mods-filter label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--n4);
}

.mods-filter input {
  font-family: 'Outfit', sans-serif;
  font-size: 0.95rem;
  color: var(--n1);
  background: var(--dk);
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 6px;
  padding: 0.55rem 0.75rem;
  width: 100%;
  box-sizing: border-box;
}

.mods-filter input:focus-visible {
  outline: 2px solid var(--g);
  outline-offset: 2px;
  border-color: transparent;
}

.mods-filter input::placeholder {
  color: var(--n5);
}

.mods-count {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.75rem;
  color: var(--n4);
  padding-bottom: 0.6rem;
}

/* === Jump nav (shared: mods tiers, command groups) === */
.page-jump {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.75rem;
  margin-bottom: 2.5rem;
}

.page-jump-label {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--n5);
}

.page-jump-links {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.page-jump-links a {
  font-size: 0.8rem;
  text-decoration: none;
  color: var(--gl);
  background: rgba(76, 175, 80, 0.08);
  border: 1px solid rgba(76, 175, 80, 0.2);
  border-radius: 999px;
  padding: 0.2rem 0.65rem;
}

.page-jump-links a:hover {
  background: rgba(76, 175, 80, 0.16);
}

/* === Section headings (shared: mod tiers, command groups) === */
.tier {
  margin-bottom: 2.5rem;
  scroll-margin-top: 5rem; /* clear the fixed topbar when jumped to */
}

.section-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  border-bottom: 1px solid rgba(76, 175, 80, 0.2);
  padding-bottom: 0.5rem;
}

.section-heading {
  font-family: 'Syne', sans-serif;
  font-weight: 700;
  font-size: 1.1rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--n1);
}

.tier-number {
  color: var(--g);
  margin-right: 0.5rem;
}

.section-count {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.7rem;
  color: var(--n5);
}

.section-note {
  margin-top: 0.6rem;
  max-width: 65ch;
  font-size: 0.85rem;
  color: var(--n4);
}

/* === Mods: entries === */
.mod-list {
  margin-top: 1rem;
}

.mod {
  display: flex;
  gap: 0.9rem;
  padding: 0.9rem 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.mod:last-child {
  border-bottom: none;
}

/* Load-order rail. aria-hidden in the markup — the same information is in
   the mod name as visually-hidden text, so it isn't read out twice. */
.mod-order {
  flex: 0 0 3.2rem;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.8rem;
  color: var(--n5);
  padding-top: 0.15rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.mod-body {
  flex: 1 1 auto;
  min-width: 0;
}

.mod-name {
  font-family: 'Syne', sans-serif;
  font-weight: 600;
  font-size: 1rem;
  color: var(--n1);
}

.mod-flags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.35rem;
}

/* Flags carry their meaning as text, never as colour alone. */
.mod-flag {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--am);
  background: rgba(245, 166, 35, 0.1);
  border: 1px solid rgba(245, 166, 35, 0.3);
  border-radius: 4px;
  padding: 0.1rem 0.4rem;
}

.mod-desc {
  margin-top: 0.4rem;
  font-size: 0.9rem;
  line-height: 1.55;
  color: var(--n3);
  max-width: 68ch;
}

.mod-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.5rem;
}

.mod-id {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.7rem;
  color: var(--n5);
  background: var(--dk);
  border-radius: 3px;
  padding: 0.1rem 0.35rem;
  word-break: break-all;
}

.mod-link {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  text-decoration: none;
  color: var(--gl);
  border-bottom: 1px solid rgba(129, 199, 132, 0.4);
}

.mod-link:hover {
  color: var(--gp);
}

.mods-footnote {
  margin-top: 2rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(76, 175, 80, 0.15);
  font-size: 0.8rem;
  color: var(--n5);
  max-width: 60ch;
}

@media (max-width: 520px) {
  /* Stack the order rail above the entry rather than squeezing the text
     into a narrow column. */
  .mod {
    flex-direction: column;
    gap: 0.25rem;
  }

  .mod-order {
    text-align: left;
  }
}

/* === Stream commands === */
.cmd-group {
  margin-bottom: 2.5rem;
  scroll-margin-top: 5rem; /* clear the fixed topbar when jumped to */
}

.cmd-list {
  margin-top: 1rem;
}

/* A <dl> where each command is a <div> of dt + dd — valid HTML, and the only
   way to give one command its own grid row. */
.cmd {
  display: grid;
  grid-template-columns: minmax(8rem, 11rem) 1fr;
  gap: 0.4rem 1rem;
  align-items: start;
  padding: 0.9rem 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.cmd:last-child {
  border-bottom: none;
}

.cmd-term {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}

.cmd-name,
.cmd-alias {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.85rem;
  font-weight: 500;
  border-radius: 5px;
  padding: 0.2rem 0.5rem;
  background: var(--dk);
  white-space: nowrap;
}

.cmd-name {
  color: var(--gl);
  border: 1px solid rgba(76, 175, 80, 0.25);
}

/* !so next to !shoutout — same command, quieter treatment. */
.cmd-alias {
  color: var(--n4);
  border: 1px solid rgba(255, 255, 255, 0.1);
  font-size: 0.75rem;
}

.cmd-flags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

/* Amber, not green: these two commands cost you your chat privileges for a
   bit, and that should be visible before you type it — not a surprise. */
.cmd-flag {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--am);
  border: 1px solid rgba(245, 166, 35, 0.35);
  border-radius: 3px;
  padding: 0.12rem 0.35rem;
}

.cmd-body {
  min-width: 0;
}

.cmd-desc {
  font-size: 0.9rem;
  color: var(--n2);
}

.cmd-line {
  margin-top: 0.45rem;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.45rem;
}

.cmd-label {
  flex: 0 0 auto;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.6rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--n4);
}

.cmd-snippet {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.8rem;
  color: var(--gp);
  background: var(--dk);
  border-radius: 4px;
  padding: 0.15rem 0.4rem;
  overflow-wrap: anywhere;
}

/* What the bot says back. The reset kills q's quote marks site-wide, so put
   them back here — they're what distinguishes a reply from an instruction. */
.cmd-reply {
  font-size: 0.85rem;
  font-style: italic;
  color: var(--n4);
  quotes: '\201C' '\201D';
}

.cmd-reply::before {
  content: open-quote;
}

.cmd-reply::after {
  content: close-quote;
}

.cmd-linkout {
  font-size: 0.8rem;
  overflow-wrap: anywhere;
}

@media (max-width: 640px) {
  /* The command and its explanation stack; a 8rem term column would wrap
     "!russianroulette" to three lines. */
  .cmd {
    grid-template-columns: 1fr;
  }
}

/* === Topbar page links === */
.topbar-nav {
  display: flex;
  gap: 1rem;
  margin-left: auto;
  margin-right: 1.25rem;
}

.topbar-page-link,
.topbar-page-link:link,
.topbar-page-link:visited {
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--n4);
  text-decoration: none;
  transition: color 0.3s;
}

.topbar-page-link:hover {
  color: var(--g);
}

/* Current page marked structurally with aria-current; this is the visual
   half of the same information. */
.topbar-page-link[aria-current="page"] {
  color: var(--n1);
  border-bottom: 1px solid var(--g);
}

a.topbar-brand,
a.topbar-brand:link,
a.topbar-brand:visited {
  color: var(--n1);
  text-decoration: none;
}

/* Brand + three page links + five social icons is a lot for a phone. Let the
   bar wrap rather than overflow, and give content pages room for the taller
   bar that results. */
@media (max-width: 640px) {
  .topbar-inner {
    padding: 0.6rem 1rem;
    flex-wrap: wrap;
    gap: 0.4rem 0.75rem;
  }

  .topbar-nav {
    margin-right: 0.5rem;
    gap: 0.7rem;
  }

  .topbar-page-link {
    font-size: 0.7rem;
  }

  .topbar-links {
    gap: 0.9rem;
    font-size: 1rem;
  }

  .page {
    padding-top: 7.5rem;
  }
}

/* === Broadcasts Section === */
#broadcasts {
  margin-top: 0;
  text-align: center;
}

.broadcasts-label {
  font-family: 'JetBrains Mono', monospace;
  font-weight: 500;
  font-size: 0.7rem;
  color: var(--em);
  text-transform: uppercase;
  letter-spacing: 0.15em;
  margin-bottom: 0.25rem;
}

.broadcasts-heading {
  font-family: 'Syne', sans-serif;
  font-weight: 700;
  font-size: 1.2rem;
  color: var(--n1);
  margin-bottom: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

#broadcasts-list {
  display: flex;
  flex-direction: row;
  gap: 0.75rem;
  align-items: stretch;
  overflow-x: auto;
  overflow-y: hidden;
  padding-inline: 1rem;
  scrollbar-width: thin;
  scroll-snap-type: x proximity;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 1.5rem, #000 calc(100% - 1.5rem), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 1.5rem, #000 calc(100% - 1.5rem), transparent 100%);
}

.broadcast-card {
  display: flex;
  flex-direction: column;
  flex: 0 0 280px;
  scroll-snap-align: start;
  background: var(--dk);
  border: 1px solid rgba(255, 255, 255, 0.04);
  border-radius: 8px;
  /* Padded all round: the thumbnail is inset on every side and the box art
     overhangs its bottom edge, so nothing bleeds and nothing is clipped —
     hence no overflow: hidden here. */
  padding: 0.5rem;
  text-decoration: none;
  color: var(--n1);
  transition: border-color 0.2s, background 0.2s;
}

.broadcast-card:hover,
.broadcast-card:focus-visible {
  border-color: rgba(76, 175, 80, 0.3);
  background: var(--ch);
}

.broadcast-card:link,
.broadcast-card:visited {
  color: var(--n1);
}

/* Positioning context for the box art, which hangs over the thumbnail's
   bottom edge. line-height: 0 kills the inline-image gap under the img. */
.broadcast-media {
  position: relative;
  line-height: 0;
}

.broadcast-thumb {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: 6px;
}

/* Box art straddling the thumbnail's bottom-right corner: mostly over the
   image, overhanging into the row below, where the category name sits to
   its left. The shadow is what separates it from the frame grab behind. */
.broadcast-boxart {
  position: absolute;
  right: 8px;
  bottom: -34px;
  width: 64px;
  height: auto;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.65);
}

.broadcast-info {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  text-align: left;
  overflow: hidden;
  padding-top: 0.5rem;
}

/* Category name, level with the overhanging art. min-height reserves the
   band the art hangs into so the title always clears it; the padding keeps
   long names from running underneath it. Both only apply when there IS art
   — see .has-boxart, set by the JS once the image loads. */
.broadcast-game {
  font-family: 'JetBrains Mono', monospace;
  font-weight: 500;
  font-size: 0.7rem;
  line-height: 1.3;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--n2);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.broadcast-card.has-boxart .broadcast-game {
  min-height: 36px;
  padding-right: 78px;
}

/* Without art the category is just the first line of the info block. */
.broadcast-card.has-boxart .broadcast-title {
  margin-top: 0.15rem;
}

.broadcast-title {
  font-family: 'Syne', sans-serif;
  font-weight: 600;
  font-size: 0.9rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.broadcast-meta {
  font-family: 'JetBrains Mono', monospace;
  font-weight: 400;
  font-size: 0.75rem;
  color: var(--n4);
}

.broadcasts-empty {
  font-family: 'Outfit', sans-serif;
  font-weight: 200;
  font-size: 0.9rem;
  color: var(--n5);
}

