/* ============================================================
   1337 — mockup stylesheet
   Reference grammar: pure black, thin rules, glass cards,
   pill buttons, tight negative tracking, mono micro-labels.
   ============================================================ */

:root{
  --bg:            #000000;
  --bg-lift:       rgba(255,255,255,0.028);
  --bg-lift-2:     rgba(255,255,255,0.05);
  --line:          rgba(255,255,255,0.09);
  --line-2:        rgba(255,255,255,0.14);

  --txt:           #ffffff;
  --txt-2:         rgba(255,255,255,0.62);
  --txt-3:         rgba(255,255,255,0.38);
  --txt-4:         rgba(255,255,255,0.22);

  --blue-deep:     #0d1a4d;
  --blue:          #1c37a8;
  --blue-mid:      #2f50d8;
  --blue-lit:      #6d8bff;
  --blue-glow:     rgba(47,80,216,0.30);

  --blurple:       #5865f2;
  --blurple-lit:   #7b86f7;

  --ease:          cubic-bezier(.66,.02,.2,1);
  --ease-out:      cubic-bezier(.2,.8,.25,1);
  --dur:           .78s;

  --gap:           clamp(14px, 1.7vw, 26px);
  --visible:       3;          /* always three across, every breakpoint */

  --pad:           clamp(22px, 5vw, 64px);
  --maxw:          1280px;
}

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

/* Android Chrome flashes a translucent grey box over whatever you
   tap, for the fraction of a second before any :active/:hover style
   of ours takes over. Suppressed globally — every interactive
   element already has its own feedback, this was just fighting it. */
*{ -webkit-tap-highlight-color:transparent; }

html{ scroll-behavior:smooth; scroll-padding-top:96px; }
html,body{ margin:0; padding:0; background:var(--bg); overflow-x:clip; }
body{
  font-family:'Inter',system-ui,-apple-system,'Segoe UI',sans-serif;
  color:var(--txt); line-height:1.6;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
}
a{ color:inherit; text-decoration:none; }
h1,h2,h3,p,dl,dd{ margin:0; }
dl{ display:block; }
::selection{ background:var(--blue-mid); color:#fff; }
::-webkit-scrollbar{ width:6px; }
::-webkit-scrollbar-track{ background:transparent; }
::-webkit-scrollbar-thumb{ background:#21212a; border-radius:3px; }
::-webkit-scrollbar-thumb:hover{ background:#2e2e3a; }
:focus-visible{ outline:1px solid var(--blue-lit); outline-offset:3px; border-radius:4px; }

.mono{
  font-family:'Geist Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11px; letter-spacing:.14em; text-transform:uppercase;
  color:var(--txt-3); font-weight:400;
}

/* ── page load bar ────────────────────────────────────────────
   Every [data-reveal] element starts at opacity:0 and waits for
   the deferred script, so on a slow connection there's a beat of
   pure black with no signal that anything is coming. This is a
   2px accent bar that creeps while the page boots and completes
   on load. Purely additive — it's fixed and pointer-events:none,
   and if JS never runs it simply never appears. */
.loadbar{
  position:fixed; left:0; top:0; height:2px; width:100%; z-index:9500;
  pointer-events:none; opacity:0; transition:opacity .3s ease .15s;
  background:linear-gradient(90deg,var(--blue-mid),var(--blue-lit));
  transform-origin:0 50%; transform:scaleX(0);
  box-shadow:0 0 12px 1px var(--blue-glow);
}
.js .loadbar{ opacity:1; }
.loadbar.is-going{ transform:scaleX(.7); transition:transform 1.6s var(--ease-out); }
.loadbar.is-done{
  transform:scaleX(1); opacity:0;
  transition:transform .32s var(--ease-out), opacity .4s ease .22s;
}

/* ── cursor glow ──────────────────────────────────────────────
   A soft light that trails the pointer across the whole page.
   Sits early in the DOM at z-index 0 so it paints above the page
   background but beneath every section's content — sections are
   transparent, so the light reads through them while text stays
   fully legible on top.

   The lag is the whole effect: transitioning transform means the
   light eases toward the cursor instead of being welded to it,
   which is what separates this from looking like a stuck sprite.
   Parked off-screen until the first real pointer move so it never
   flashes in the top-left corner on load. */
.cursor-glow{
  position:fixed; top:0; left:0;
  width:var(--cg-size,560px); aspect-ratio:1;
  margin:calc(var(--cg-size,560px) / -2) 0 0 calc(var(--cg-size,560px) / -2);
  z-index:0; pointer-events:none;
  border-radius:50%;
  background:radial-gradient(circle closest-side,
    rgba(112,140,255,.16) 0%,
    rgba(70,96,220,.09)  38%,
    rgba(47,80,216,.03)  62%,
    rgba(47,80,216,0)    78%);
  opacity:0;
  transform:translate3d(-9999px,-9999px,0);
  transition:transform .19s var(--ease-out), opacity .5s ease, scale .35s var(--ease-out);
  will-change:transform;
}
/* Revealed on first move, hidden again when the pointer leaves
   the window — otherwise it hangs frozen at the last position. */
.cursor-glow.is-live{ opacity:1; }
/* Cards carry their own hover lighting (.pack-shine, the member
   card bloom). Dimming here stops the two stacking into a bright
   patch and keeps the card's own highlight the one you read. */
.cursor-glow.is-over-card{ opacity:.3; scale:.82; }

/* Touch has no pointer to follow, and the effect is decorative —
   don't ship it to devices that can't use it. */
@media (hover:none),(pointer:coarse){
  .cursor-glow{ display:none; }
}
@media (max-width:820px){
  .cursor-glow{ display:none; }
}

/* ── grain overlay ────────────────────────────────────────────
   A barely-visible static noise texture over the entire viewport —
   the detail that stops a flat dark UI from reading as a template.
   Deliberately static rather than animated: a flickering grain
   texture repainting every frame is a real battery/GPU cost on
   OLED and mobile for a purely decorative layer, and constant
   motion here would compete with the content instead of just
   adding texture to it.

   Sits above absolutely everything (frame, load bar, nav, modal)
   so the texture reads as part of the "shot," uniformly, rather
   than stopping at whichever layer happened to be on top. Non-
   interactive and low enough opacity that it never affects
   legibility. */
.grain-overlay{
  position:fixed; inset:0; z-index:9600; pointer-events:none;
  opacity:.035; mix-blend-mode:overlay;
  background-repeat:repeat; background-size:128px 128px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch' result='t'/%3E%3CfeColorMatrix in='t' type='matrix' values='0 0 0 0 1  0 0 0 0 1  0 0 0 0 1  0 0 0 0.9 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ── rounded window frame ─────────────────────────────────── */
.frame{
  position:fixed; inset:8px; z-index:9000; pointer-events:none;
  border:1px solid var(--line); border-radius:24px;
  box-shadow:0 0 0 12px var(--bg);
}

/* ── reveal ──────────────────────────────────────────────── */
.js [data-reveal]{ opacity:0; }
.js [data-reveal].is-in{
  opacity:1;
  animation:revealIn .9s var(--ease-out) var(--d,0ms) backwards;
}
@keyframes revealIn{
  from{ opacity:0; translate:0 20px; }
  to{   opacity:1; translate:none;   }
}

/* ── shared bits ─────────────────────────────────────────── */
.eyebrow{
  display:inline-flex; align-items:center; gap:10px;
  font-family:'Geist Mono',ui-monospace,monospace;
  font-size:11px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--txt-3); font-weight:400;
}
.eyebrow .dot{
  width:5px; height:5px; border-radius:50%; background:var(--blue-mid);
  box-shadow:0 0 10px 1px var(--blue-glow); flex:none;
}
.eyebrow .num{
  font-style:normal; color:var(--blue-lit);
  /* Small accent tick under the section number — the palette has a
     good blue that barely appears outside the scenepacks hero. */
  position:relative; padding-bottom:2px;
}
.eyebrow .num::after{
  content:''; position:absolute; left:0; right:0; bottom:0; height:1px;
  background:linear-gradient(90deg,var(--blue-lit),rgba(109,139,255,0));
}

/* ── section glow ─────────────────────────────────────────────
   The scenepacks hero's blue wash is the best-looking thing on the
   site and it appears nowhere else. This puts a much fainter
   version behind each numbered section so scrolling has some
   colour variation instead of an even field of near-black. */
.section{ position:relative; isolation:isolate; }
.section::before{
  content:''; position:absolute; z-index:-1; pointer-events:none;
  left:50%; top:0; translate:-50% 0;
  width:min(1200px,130vw); height:min(520px,60vh);
  background:radial-gradient(50% 50% at 50% 30%,
    rgba(47,80,216,.10) 0%, rgba(47,80,216,.04) 45%, rgba(0,0,0,0) 74%);
}
/* Alternate which side the wash sits on so consecutive sections
   don't read as the same beat repeated. */
.section:nth-of-type(even)::before{ left:22%; }
.section:nth-of-type(odd)::before{ left:78%; }

.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:10px;
  border:0; border-radius:9999px; font-family:inherit; font-weight:600;
  line-height:1; cursor:pointer; white-space:nowrap;
  transition:transform .2s var(--ease-out), background .2s ease,
             color .2s ease, border-color .2s ease, box-shadow .3s ease;
}
.btn .ic{ width:19px; height:19px; flex:none; }
.btn-ghost{
  border:1px solid var(--line-2); color:var(--txt-2);
  padding:9px 20px; font-size:13px; font-weight:500; background:transparent;
}
.btn-ghost:hover{ color:#fff; border-color:rgba(255,255,255,.4); }

/* ── nav ─────────────────────────────────────────────────── */
.nav{
  position:fixed; top:8px; left:8px; right:8px; z-index:100;
  border-radius:24px 24px 0 0;
  transition:background .35s ease, backdrop-filter .35s ease;
}
.nav.is-scrolled{
  background:rgba(0,0,0,.72);
  backdrop-filter:blur(14px) saturate(140%);
  -webkit-backdrop-filter:blur(14px) saturate(140%);
}
.nav-inner{
  max-width:var(--maxw); margin:0 auto; padding:16px var(--pad);
  display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:20px;
}
.nav-logo{
  justify-self:start; font-family:'Geist','Inter',sans-serif;
  font-size:17px; font-weight:700; letter-spacing:-.04em;
  color:var(--blue-lit);
  opacity:0; transform:translateY(-6px); pointer-events:none;
  transition:opacity .4s ease, transform .4s var(--ease-out);
}
.nav.is-scrolled .nav-logo{ opacity:1; transform:none; pointer-events:auto; }
.nav-links{ justify-self:center; display:flex; gap:clamp(18px,2.2vw,34px); }
.nav-links a{
  font-size:13.5px; font-weight:500; color:var(--txt-3);
  transition:color .18s ease;
}
.nav-links a:hover{ color:#fff; }
.nav-cta{ justify-self:end; }

.nav-toggle{
  display:none; justify-self:end; width:38px; height:38px;
  align-items:center; justify-content:center; position:relative;
  border:1px solid var(--line-2); border-radius:10px; background:transparent;
  color:var(--txt-2); cursor:pointer;
}
.nav-toggle-ic{ position:absolute; width:19px; height:19px; }
.nav-toggle-close{ opacity:0; transform:scale(.7) rotate(-45deg); }
.nav-toggle.is-open .nav-toggle-open{ opacity:0; transform:scale(.7) rotate(45deg); }
.nav-toggle.is-open .nav-toggle-close{ opacity:1; transform:none; }
.nav-toggle-ic{ transition:opacity .2s ease, transform .25s var(--ease-out); }

.nav-mobile-menu{
  display:none; flex-direction:column;
  max-width:var(--maxw); margin:0 auto;
  padding:4px var(--pad) 14px;
}
.nav-mobile-menu a{
  padding:13px 4px; font-size:15px; font-weight:500; color:var(--txt-2);
  border-top:1px solid var(--line);
}
.nav-mobile-menu a:active,
.nav-mobile-menu a:focus-visible{ color:#fff; }

/* ── hero ────────────────────────────────────────────────── */
.hero{
  position:relative; min-height:100svh;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; padding:120px var(--pad) 72px;
}
.hero-glow{
  position:absolute; left:50%; top:14%; width:min(1500px,140vw); height:min(760px,80vh);
  transform:translate(-50%,0); pointer-events:none; z-index:0;
  background:radial-gradient(48% 50% at 50% 40%,
    rgba(47,80,216,.22) 0%, rgba(20,32,110,.11) 42%, rgba(0,0,0,0) 72%);
  transition:transform .6s cubic-bezier(.2,.8,.3,1);
  will-change:transform;
}
.hero > *:not(.hero-glow){ position:relative; z-index:1; }
.hero-eyebrow{ margin-bottom:clamp(24px,4vh,44px); }

.wordmark{
  font-family:'Geist','Inter',sans-serif;
  font-weight:600; letter-spacing:-.055em; line-height:.82;
  font-size:clamp(5.5rem,21vw,17rem);
  margin:0 0 clamp(28px,5vh,54px);
  background:linear-gradient(180deg,
    #4a67ea 0%, #2743c0 30%, #152678 66%, #0a1240 100%);
  -webkit-background-clip:text; background-clip:text;
  -webkit-text-fill-color:transparent; color:transparent;
  filter:drop-shadow(0 18px 60px rgba(47,80,216,.28));
  user-select:none;
}
.wordmark.is-scrambling{ font-variant-numeric:tabular-nums; }

.hero-body{ max-width:660px; }
.hero-q{
  font-family:'Geist','Inter',sans-serif; font-weight:300;
  font-size:clamp(1.6rem,3.4vw,2.5rem); letter-spacing:-.035em; line-height:1.12;
  margin-bottom:22px;
}
.lead{
  font-size:clamp(14px,1.05vw,15.5px); line-height:1.78; color:var(--txt-2);
  margin-bottom:16px;
}
.lead em{ font-style:normal; color:rgba(255,255,255,.88); font-weight:500; }
.lead.dim{ color:var(--txt-3); }

.scroll-cue{
  display:flex; flex-direction:column; align-items:center; gap:12px;
  margin-top:clamp(40px,7vh,80px);
}
.scroll-cue-line{
  width:1px; height:52px; background:linear-gradient(180deg,rgba(255,255,255,.35),rgba(255,255,255,0));
  position:relative; overflow:hidden;
}
.scroll-cue-line::after{
  content:''; position:absolute; left:0; top:-52px; width:1px; height:52px;
  background:linear-gradient(180deg,rgba(0,0,0,0),var(--blue-lit));
  animation:cueRun 2.4s var(--ease) infinite;
}
@keyframes cueRun{ 0%{ transform:translateY(0) } 60%,100%{ transform:translateY(104px) } }
.scroll-cue-txt{
  font-family:'Geist Mono',ui-monospace,monospace; font-size:10px;
  letter-spacing:.22em; text-transform:uppercase; color:var(--txt-4);
  transition:color .2s ease;
}
.scroll-cue:hover .scroll-cue-txt{ color:var(--txt-2); }

/* ── section scaffolding ─────────────────────────────────── */
.section{ padding:clamp(90px,13vh,150px) var(--pad); }
.section-head{ max-width:var(--maxw); margin:0 auto clamp(40px,6vh,66px); }
.team .section-head{ margin-bottom:clamp(30px,4.5vh,52px); }
.section-title{
  font-family:'Geist','Inter',sans-serif; font-weight:600;
  font-size:clamp(1.8rem,3.9vw,3rem); letter-spacing:-.045em; line-height:1.04;
  margin:18px 0 14px;
}
.section-sub{ font-size:14px; color:var(--txt-3); max-width:52ch; }
/* Links embedded inline within body copy (e.g. "Discord server" in the
   scenepacks intro) — underlined so it reads as clickable within a run
   of plain text, brightening slightly on hover like the nav links. */
.inline-link{
  color:var(--blue-lit); text-decoration:underline; text-underline-offset:2px;
  transition:color var(--t-fast, .16s) ease;
}
.inline-link:hover,
.inline-link:focus-visible{ color:#fff; }
.section-sub + .section-sub{ margin-top:12px; }

/* ── team carousel ───────────────────────────────────────
   The viewport is the same rail the server grid uses below, so the
   two sections line up edge to edge. Arrows straddle that edge. */
.team{
  min-height:100svh; display:flex; flex-direction:column; justify-content:center;
  padding-top:clamp(78px,10vh,120px); padding-bottom:clamp(70px,9vh,110px);
}

.carousel{
  position:relative; width:100%; max-width:calc(var(--maxw) * .84); margin:0 auto; outline:none;
}
.carousel-viewport{
  width:100%; overflow:hidden;
  padding:16px 0;            /* headroom so the card hover-lift isn't clipped */
}
.carousel-track{
  display:flex; align-items:flex-start; gap:var(--gap); will-change:transform;
  transition:transform var(--dur) var(--ease);
}
.carousel:hover .person:not(:hover){ opacity:.55; }

.arrow{
  position:absolute; top:50%; z-index:5;
  width:clamp(40px,3.2vw,52px); aspect-ratio:1; border-radius:50%;
  border:1px solid var(--line-2); color:var(--txt-2);
  background:rgba(8,9,14,.86);
  -webkit-backdrop-filter:blur(10px); backdrop-filter:blur(10px);
  display:grid; place-items:center; cursor:pointer; padding:0;
  transition:color .2s ease, border-color .2s ease, background .2s ease;
}
.arrow-prev{ left:0;  transform:translate(calc(-100% - clamp(60px,7vw,120px)),-50%); }
.arrow-next{ right:0; transform:translate(calc(100% + clamp(60px,7vw,120px)),-50%); }
.arrow svg{ width:19px; height:19px; transition:transform .2s var(--ease-out); }
.arrow:hover{ color:#fff; border-color:rgba(109,139,255,.55); background:rgba(20,28,64,.9); }
.arrow-next:hover svg{ transform:translateX(2px); }
.arrow-prev:hover svg{ transform:translateX(-2px); }
.arrow:active svg{ transform:scale(.88); }

.carousel-meta{
  display:flex; align-items:center; gap:18px;
  width:100%; max-width:var(--maxw); margin:clamp(24px,3.5vh,40px) auto 0;
}
.progress{
  display:block; width:150px; height:1px; background:var(--line-2); overflow:hidden;
}
.progress i{
  display:block; height:100%; width:20%; background:var(--blue-lit);
  transition:transform .5s var(--ease);
}

/* ── person card (1:2) ───────────────────────────────────
   Sized as a container: every interior measure is in cqi, so the
   card stays a true scale model of itself at any rail width. */
.person{
  position:relative;
  flex:0 0 calc((100% - var(--gap) * (var(--visible) - 1)) / var(--visible));
  aspect-ratio:1/2;
  container-type:inline-size;
  display:flex; flex-direction:column;
  padding:clamp(12px,4.4cqi,22px);
  background:linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,.012));
  border:1px solid var(--line); border-radius:clamp(10px,4.5cqi,20px); overflow:hidden;
  transition:border-color .3s ease, transform .45s var(--ease-out),
             background .3s ease, opacity .3s ease;
}
.person::before{
  content:''; position:absolute; left:0; right:0; top:0; height:1px; z-index:2;
  background:linear-gradient(90deg,rgba(0,0,0,0),var(--blue-lit),rgba(0,0,0,0));
  transform:scaleX(0); transform-origin:50% 50%;
  transition:transform .5s var(--ease-out);
}
.person::after{
  content:''; position:absolute; inset:0; pointer-events:none; opacity:0;
  background:radial-gradient(70% 42% at 50% 0%, rgba(47,80,216,.20), rgba(0,0,0,0) 72%);
  transition:opacity .45s ease;
}
.person:hover{
  border-color:rgba(109,139,255,.30);
  transform:translateY(-8px);
  opacity:1 !important;
}
.person:hover::before{ transform:scaleX(1); }
.person:hover::after{ opacity:1; }

.person-head,
.person-id,
.person-stats,
.person-links{ flex:none; position:relative; }
.person-head{ display:flex; align-items:center; justify-content:space-between; gap:8px; }
.person-head .mono{ font-size:clamp(8.5px,2.4cqi,11px); }
.chip{
  font-family:'Geist Mono',ui-monospace,monospace;
  font-size:clamp(8px,2.2cqi,10px);
  letter-spacing:.14em; text-transform:uppercase; color:var(--txt-3);
  border:1px solid var(--line); border-radius:9999px;
  padding:.45em .85em; line-height:1;
}

.person-portrait{
  position:relative; flex:none; aspect-ratio:1/1;
  margin:clamp(10px,4cqi,20px) 0 0;
  width:100%;
  display:grid; place-items:center;
}
.avatar-ring{
  position:relative; width:88%; aspect-ratio:1/1; border-radius:50%; overflow:hidden;
  display:grid; place-items:center;
  background:
    radial-gradient(90% 70% at 50% 8%, rgba(47,80,216,.30), rgba(0,0,0,0) 68%),
    linear-gradient(165deg,#0c1230 0%,#05070f 58%,#02030a 100%);
  border:1px solid rgba(255,255,255,.07);
}
.avatar-ring::after{
  content:''; position:absolute; inset:0;
  background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(0,0,0,0) 40%);
}
.monogram{
  font-family:'Geist','Inter',sans-serif; font-weight:300; line-height:1;
  font-size:clamp(30px,19cqi,80px); letter-spacing:-.05em;
  color:rgba(255,255,255,.14);
  transition:color .45s ease, transform .6s var(--ease-out);
}
.person:hover .monogram{ color:rgba(160,183,255,.42); transform:translateY(-3px); }

.person-avatar{
  position:absolute; inset:0; z-index:1;
  width:100%; height:100%; object-fit:cover;
  opacity:0; transition:opacity .5s ease;
}
.person-avatar.is-loaded{ opacity:1; }
.person-avatar[hidden]{ display:none; }

.person-discord{
  display:inline-flex; align-items:center; gap:6px;
  margin-top:.5em; color:var(--txt-4); text-transform:none; letter-spacing:.02em;
  outline:none; /* custom focus treatment below, no default box */
}
.person-discord[hidden]{ display:none; }
.discord-ic{
  width:12px; height:12px; flex:none; color:var(--blurple); fill:currentColor;
  transition:transform .2s var(--ease-out);
}
.person-discord:hover .discord-ic,
.person-discord:focus-visible .discord-ic{ transform:scale(1.08); }

/* Username stays in the DOM (so screen readers always announce it)
   but is visually collapsed until the icon is hovered/focused. */
.person-discord-name{
  display:inline-block;
  max-width:0;
  opacity:0;
  transform:translateX(-6px);
  overflow:hidden;
  white-space:nowrap;
  transition:
    max-width .32s cubic-bezier(.22,1,.36,1),
    opacity .18s ease,
    transform .32s cubic-bezier(.22,1,.36,1);
}
.person-discord:hover .person-discord-name,
.person-discord:focus-visible .person-discord-name{
  max-width:180px;
  opacity:1;
  transform:translateX(0);
  transition:
    max-width .32s cubic-bezier(.22,1,.36,1),
    opacity .28s ease .06s,
    transform .32s cubic-bezier(.22,1,.36,1);
}

.person-id{ margin-top:clamp(12px,5.4cqi,26px); }
.person-name{
  font-family:'Geist','Inter',sans-serif; font-weight:500;
  font-size:clamp(15px,6cqi,26px); letter-spacing:-.035em; line-height:1.1;
}
.person-role{
  margin-top:.55em; min-height:3.2em;
  font-size:clamp(9.5px,2.9cqi,12.5px); font-weight:500; letter-spacing:.02em;
  line-height:1.6; color:var(--blue-lit); opacity:.85;
}
.person-bio{
  flex:none; position:relative;
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:4; overflow:hidden;
  margin-top:clamp(10px,4cqi,18px);
  padding-top:clamp(10px,4cqi,18px); border-top:1px solid var(--line);
  font-size:clamp(10px,3.1cqi,13px); line-height:1.7; color:var(--txt-3);
}

.person-stats{ padding-top:clamp(12px,4cqi,20px); }
.person-stats > div{
  display:flex; align-items:baseline; justify-content:space-between; gap:8px;
  padding:clamp(5px,1.9cqi,9px) 0; border-top:1px solid rgba(255,255,255,.055);
}
.person-stats dt{
  font-family:'Geist Mono',ui-monospace,monospace; font-size:clamp(8px,2.2cqi,10px);
  letter-spacing:.14em; text-transform:uppercase; color:var(--txt-4);
}
.person-stats dd{ font-size:clamp(9.5px,2.9cqi,12.5px); color:var(--txt-2); font-weight:500; }

.person-links{
  display:flex; gap:clamp(4px,1.4cqi,7px); margin-top:auto;
  padding-top:clamp(9px,3.2cqi,15px);
  border-top:1px solid var(--line);
}
.person-links a,
.person-links button{
  flex:1; text-align:center; padding:clamp(5px,1.9cqi,9px) 0; border-radius:7px;
  font-family:'Geist Mono',ui-monospace,monospace;
  font-size:clamp(8px,2.2cqi,10px); letter-spacing:.12em;
  color:var(--txt-3); background:rgba(255,255,255,.035);
  border:0; cursor:pointer;
  transition:color .18s ease, background .18s ease;
}
.person-links a:hover,
.person-links button:hover{ color:#fff; background:rgba(47,80,216,.24); }

/* Laz-only: icon-based row (Instagram/TikTok/Payhip/Discord) instead of
   text abbreviations. Scoped so every other profile is unaffected. */
.person-links--social{
  gap:clamp(3px,1.1cqi,6px);
}
.person-links--social a,
.person-links--social button{
  position:relative;
  display:flex; align-items:center; justify-content:center;
  padding:clamp(6px,2.1cqi,10px) 0;
  border:0; background:transparent; font:inherit; color:inherit; cursor:pointer;
  border-radius:7px;
  transition:color .18s ease, background .18s ease;
}
.person-links--social a:hover,
.person-links--social button:hover{ color:#fff; background:rgba(47,80,216,.24); }
.person-links--social .social-ic{
  width:clamp(12px,3.6cqi,15px); height:clamp(12px,3.6cqi,15px);
  flex:none; color:var(--blurple);
  transition:transform .18s var(--ease-out), filter .22s ease;
}
.person-links--social a:hover .social-ic,
.person-links--social a:focus-visible .social-ic,
.person-links--social button:hover .social-ic,
.person-links--social button:focus-visible .social-ic{
  transform:scale(1.12);
  filter:drop-shadow(0 0 6px rgba(88,101,242,.85)) drop-shadow(0 0 2px rgba(88,101,242,.6));
}

/* Discord icon keeps the same fade + slide reveal the standalone
   handle line used to have, now as a small floating tooltip so it
   doesn't disturb the evenly-spaced icon row. */
.social-tooltip-wrap{ outline:none; }
.social-tooltip{
  position:absolute; bottom:100%; left:50%; margin-bottom:9px;
  font-family:'Geist Mono',ui-monospace,monospace;
  font-size:10px; letter-spacing:.04em; white-space:nowrap;
  color:var(--txt-2); background:rgba(10,11,18,.94);
  border:1px solid var(--line-2); border-radius:6px;
  padding:5px 9px; pointer-events:none;
  opacity:0; transform:translate(-50%, 4px);
  transition:opacity .18s ease, transform .28s cubic-bezier(.22,1,.36,1);
}
.person-links--social a:hover .social-tooltip,
.person-links--social a:focus-visible .social-tooltip,
.person-links--social button:hover .social-tooltip,
.person-links--social button:focus-visible .social-tooltip{
  opacity:1; transform:translate(-50%, 0);
  transition:opacity .28s ease .06s, transform .32s cubic-bezier(.22,1,.36,1);
}
/* Tap-triggered reveal for touch devices (see script.js) — desktop
   keeps the pure-CSS hover/focus behaviour above untouched. */
.social-tooltip-wrap.tt-show .social-tooltip{
  opacity:1; transform:translate(-50%, 0);
  transition:opacity .28s ease .06s, transform .32s cubic-bezier(.22,1,.36,1);
}

/* three-up never collapses, so shed detail as the column narrows */
@container (max-width:236px){ .person-bio{ display:none; } }
@container (max-width:152px){ .person-stats,.person-links{ display:none; } }

/* ── servers ─────────────────────────────────────────────── */
.server-grid{
  max-width:var(--maxw); margin:0 auto;
  display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:clamp(14px,1.6vw,22px);
}
.server{
  position:relative; display:flex; flex-direction:column;
  container-type:inline-size;
  padding:clamp(14px,7cqi,34px);
  background:linear-gradient(180deg,rgba(255,255,255,.04),rgba(255,255,255,.01));
  border:1px solid var(--line); border-radius:clamp(10px,4.5cqi,20px); overflow:hidden;
  transition:border-color .3s ease, transform .45s var(--ease-out);
}
.server::after{
  content:''; position:absolute; inset:0; pointer-events:none; opacity:0;
  background:radial-gradient(80% 55% at 50% 0%, rgba(47,80,216,.16), rgba(0,0,0,0) 70%);
  transition:opacity .45s ease;
}
.server:hover{ border-color:rgba(109,139,255,.30); transform:translateY(-5px); }
.server:hover::after{ opacity:1; }

.server-tile{
  position:relative; width:clamp(32px,15cqi,54px); aspect-ratio:1;
  border-radius:clamp(8px,3.9cqi,14px);
  display:block; object-fit:cover;
  margin-bottom:clamp(12px,7cqi,26px);
  color:var(--blue-lit); font-size:clamp(7.5px,3cqi,11px); letter-spacing:.1em;
  background:linear-gradient(160deg,rgba(47,80,216,.22),rgba(13,26,77,.18));
  border:1px solid rgba(109,139,255,.22);
  overflow:hidden;
}
/* One-shot confirmation that a server's member count just finished
   updating with live data — not a loop, just a single soft
   heartbeat so it reads as "that's live" rather than a decoration
   running the whole time you're on the page. */
@keyframes serverTilePulse{
  0%,100%{ scale:1; }
  45%{ scale:1.07; }
}
.server-tile.is-pulsing{ animation:serverTilePulse .5s ease; }
.server-name{
  position:relative; font-family:'Geist','Inter',sans-serif; font-weight:500;
  font-size:clamp(14px,6cqi,23px); letter-spacing:-.03em;
}
.server-desc{
  position:relative; margin-top:clamp(7px,3.4cqi,12px);
  font-size:clamp(10px,3.5cqi,13.5px); line-height:1.72;
  color:var(--txt-3); max-width:38ch;
}
.server-meta{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  margin-top:clamp(14px,7.3cqi,28px);
  padding-top:clamp(11px,5.2cqi,20px); border-top:1px solid var(--line);
}
.server-stats{
  position:relative; display:flex; gap:clamp(14px,8.8cqi,34px);
}
.server-stats dt{
  font-family:'Geist Mono',ui-monospace,monospace; font-size:clamp(7.5px,2.5cqi,9.5px);
  letter-spacing:.14em; text-transform:uppercase; color:var(--txt-4);
  margin-bottom:6px;
}
.server-stats dd{
  font-family:'Geist','Inter',sans-serif; font-size:clamp(12px,4.9cqi,19px);
  font-weight:400; letter-spacing:-.02em; color:#fff;
}
/* Wraps .server-meta + .server-go as one unit and pushes that whole
   group to the bottom of the card via margin-top:auto — see the
   comment on .server-footer below for why this couldn't just be
   margin-top:auto directly on .server-go. */
.server-footer{ margin-top:auto; }
.server-go{
  position:relative; z-index:2; margin-top:clamp(13px,6.8cqi,26px);
  display:inline-flex; align-items:center; justify-content:center; gap:9px;
  width:100%; border-radius:9999px;
  padding:clamp(10px,4.6cqi,15px) clamp(14px,5cqi,20px);
  font-size:clamp(10.5px,3.4cqi,13.5px); font-weight:600; color:#fff;
  background:var(--blurple);
  box-shadow:0 8px 26px rgba(88,101,242,.22);
  transition:background .2s ease, box-shadow .3s ease, transform .2s var(--ease-out);
}
.server-go .ic{ width:clamp(14px,5.5cqi,18px); height:clamp(14px,5.5cqi,18px); flex:none; }
.server-go i{ font-style:normal; transition:transform .25s var(--ease-out); }
.server:hover .server-go{ background:var(--blurple-lit); box-shadow:0 12px 36px rgba(88,101,242,.38); transform:translateY(-2px); }
.server:hover .server-go i{ transform:translateX(4px); }

/* invisible full-card link, sits behind everything so the whole
   card is clickable while icons/button above remain independently
   clickable (real <a> nested in another <a> isn't valid HTML) */
.server-hit{ position:absolute; inset:0; z-index:1; border-radius:inherit; }

.server-socials{
  position:relative; z-index:2;
  display:flex; align-items:center; gap:clamp(4px,1.8cqi,8px);
}
.server-socials a{
  position:relative; z-index:2;
  display:flex; align-items:center; justify-content:center;
  width:clamp(26px,9cqi,34px); height:clamp(26px,9cqi,34px);
  border-radius:8px; color:var(--blue-lit); background:rgba(47,80,216,.08);
  transition:color .18s ease, background .18s ease;
}
.server-socials a:hover,
.server-socials a:focus-visible{ color:var(--blurple); background:rgba(47,80,216,.24); }
.server-socials .social-ic{
  width:clamp(13px,4.2cqi,16px); height:clamp(13px,4.2cqi,16px); flex:none;
  filter:drop-shadow(0 0 4px rgba(88,101,242,.55)) drop-shadow(0 0 1px rgba(88,101,242,.5));
  transition:transform .18s var(--ease-out), filter .22s ease;
}
.server-socials a:hover .social-ic,
.server-socials a:focus-visible .social-ic{
  transform:scale(1.12);
  filter:drop-shadow(0 0 6px rgba(88,101,242,.85)) drop-shadow(0 0 2px rgba(88,101,242,.6));
}

@container (max-width:210px){ .server-desc{ display:none; } }
@container (max-width:140px){ .server-stats{ display:none; } }

/* ── join ────────────────────────────────────────────────── */
.join{
  position:relative; text-align:center;
  padding:clamp(100px,16vh,190px) var(--pad) clamp(100px,16vh,180px);
  border-top:1px solid var(--line);
}
.join-glow{
  position:absolute; left:50%; bottom:-25%; width:min(1100px,120vw); height:min(620px,70vh);
  transform:translateX(-50%); pointer-events:none;
  background:radial-gradient(50% 50% at 50% 50%, rgba(88,101,242,.17) 0%, rgba(0,0,0,0) 70%);
}
.join > *:not(.join-glow){ position:relative; z-index:1; }
.join-title{
  font-family:'Geist','Inter',sans-serif; font-weight:300;
  font-size:clamp(2rem,5vw,3.6rem); letter-spacing:-.04em; line-height:1.06;
}
.join-sub{
  margin:20px auto 42px; max-width:44ch; font-size:14.5px; color:var(--txt-3);
}
.btn-discord{
  background:var(--blurple); color:#fff; font-size:15px; padding:17px 38px;
  box-shadow:0 10px 40px rgba(88,101,242,.24);
}
.btn-discord:hover{
  background:var(--blurple-lit); transform:translateY(-3px);
  box-shadow:0 16px 54px rgba(88,101,242,.42);
}
.btn-discord:active{ transform:translateY(-1px); }

/* ── members ─────────────────────────────────────────────── */
.members-cta{ max-width:var(--maxw); margin:0 auto; }
.btn-members{
  position:relative; overflow:hidden; isolation:isolate;
  border:1px solid var(--line-2); color:#fff;
  padding:13px 30px; font-size:13.5px; font-weight:600;
  background:rgba(255,255,255,.04);
}
.btn-members::before{
  content:''; position:absolute; inset:-60%; z-index:-1;
  background:radial-gradient(55% 55% at 50% 50%, rgba(88,101,242,.5), rgba(88,101,242,0) 72%);
  opacity:0; transform:scale(.5);
  transition:opacity .45s ease, transform .55s var(--ease-out);
}
.btn-members .ic{ transition:transform .3s var(--ease-out); }
.btn-members:hover{
  border-color:rgba(109,139,255,.5); background:rgba(47,80,216,.14);
  transform:translateY(-3px);
  box-shadow:0 14px 40px rgba(88,101,242,.26);
}
.btn-members:hover::before{ opacity:1; transform:scale(1); }
.btn-members:hover .ic{ transform:scale(1.1); }
.btn-members:active{ transform:translateY(-1px); }

/* ── members page (standalone) ──────────────────────────────
   Reused by /members/index.html. Kept in the shared stylesheet
   so both pages stay visually identical by construction rather
   than by copy-pasted CSS. */
.members-hero{
  padding-top:clamp(150px,20vh,210px);
  max-width:var(--maxw); margin:0 auto;
}
.members-hero .section-title{ margin-top:16px; }

.member-group{ border-top:1px solid var(--line); }

.group-head{
  max-width:var(--maxw); margin:0 auto clamp(36px,5.5vh,58px);
  display:flex; align-items:center; gap:clamp(16px,2.6vw,24px); flex-wrap:wrap;
}
.group-icon{
  width:clamp(56px,7vw,84px); aspect-ratio:1; flex:none;
  border-radius:clamp(14px,1.6vw,20px); object-fit:cover;
  border:1px solid rgba(109,139,255,.24);
  box-shadow:0 14px 40px rgba(47,80,216,.22);
}
.group-head-text{ flex:1 1 auto; min-width:180px; }
.group-head-text .eyebrow{ margin-bottom:8px; }
.group-head-text .section-title{ margin:0; }
.group-stat{ margin-left:auto; text-align:right; }
.group-stat dt{
  font-family:'Geist Mono',ui-monospace,monospace; font-size:9.5px;
  letter-spacing:.14em; text-transform:uppercase; color:var(--txt-4); margin-bottom:5px;
}
.group-stat dd{
  font-family:'Geist','Inter',sans-serif; font-size:clamp(17px,2.4vw,22px);
  font-weight:400; letter-spacing:-.02em; color:#fff;
}

.member-grid{
  max-width:var(--maxw); margin:0 auto;
  display:grid; grid-template-columns:repeat(auto-fill,minmax(148px,1fr));
  gap:var(--gap);
}
.member-card{
  position:relative; display:flex; flex-direction:column; align-items:center;
  text-align:center; gap:12px;
  padding:clamp(22px,4vw,30px) clamp(10px,2vw,16px) clamp(18px,3vw,24px);
  background:linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,.012));
  border:1px solid var(--line); border-radius:16px; overflow:hidden;
  transition:border-color .3s ease, transform .4s var(--ease-out), background .3s ease;
}
.member-card::after{
  content:''; position:absolute; inset:0; pointer-events:none; opacity:0;
  background:radial-gradient(70% 55% at 50% 0%, rgba(47,80,216,.20), rgba(0,0,0,0) 72%);
  transition:opacity .45s ease;
}
.member-card:hover{ border-color:rgba(109,139,255,.30); transform:translateY(-6px); }
.member-card:hover::after{ opacity:1; }
.member-avatar{
  position:relative; width:60px; aspect-ratio:1; border-radius:50%;
  display:grid; place-items:center; overflow:hidden;
  background:
    radial-gradient(90% 70% at 50% 8%, rgba(47,80,216,.30), rgba(0,0,0,0) 68%),
    linear-gradient(165deg,#0c1230 0%,#05070f 58%,#02030a 100%);
  border:1px solid rgba(255,255,255,.07);
  font-family:'Geist','Inter',sans-serif; font-weight:300; font-size:20px;
  letter-spacing:-.03em; color:rgba(255,255,255,.16);
  transition:color .4s ease, transform .5s var(--ease-out);
}
.member-card:hover .member-avatar{ color:rgba(160,183,255,.48); transform:translateY(-2px); }
.member-avatar-img{
  position:absolute; inset:0; z-index:1;
  width:100%; height:100%; object-fit:cover;
  opacity:0; transition:opacity .5s ease;
}
.member-avatar-img.is-loaded{ opacity:1; }
.member-avatar-img[hidden]{ display:none; }
.member-name{
  position:relative; font-family:'Geist','Inter',sans-serif; font-weight:500;
  font-size:13.5px; letter-spacing:-.02em; color:#fff;
}
.member-role{
  position:relative; font-size:10.5px; font-weight:500; letter-spacing:.02em;
  color:var(--blue-lit); opacity:.8;
}
.member-status{
  position:relative; display:block; text-align:center;
  font-size:9.5px; font-weight:500; letter-spacing:.02em; color:var(--txt-3);
}
.member-status i{
  display:inline-block; width:6px; height:6px; border-radius:50%;
  margin-right:6px; vertical-align:middle;
}
.member-status--available i{
  background:var(--blue-lit);
  box-shadow:0 0 5px 1px rgba(109,139,255,.75), 0 0 10px 2px rgba(109,139,255,.35);
}
.member-status--unavailable i{
  background:#ff6161;
  box-shadow:0 0 5px 1px rgba(255,97,97,.7), 0 0 10px 2px rgba(255,97,97,.32);
}
.member-links{
  position:relative; z-index:2;
  display:flex; align-items:center; justify-content:center; gap:6px;
  margin-top:2px;
}
.member-links a,
.member-links button{
  position:relative;
  display:flex; align-items:center; justify-content:center;
  width:24px; height:24px; border-radius:6px;
  color:var(--txt-3); background:rgba(255,255,255,.035);
  border:0; font:inherit; cursor:pointer;
  transition:color .18s ease, background .18s ease;
}
.member-links a:hover,
.member-links button:hover{ color:var(--blurple); background:rgba(47,80,216,.22); }
.member-links .social-ic{
  width:12px; height:12px; flex:none; color:var(--blurple);
  filter:drop-shadow(0 0 3px rgba(88,101,242,.55)) drop-shadow(0 0 1px rgba(88,101,242,.4));
  transition:transform .18s var(--ease-out), filter .22s ease;
}
.member-links a:hover .social-ic,
.member-links a:focus-visible .social-ic,
.member-links button:hover .social-ic,
.member-links button:focus-visible .social-ic{
  transform:scale(1.12);
  filter:drop-shadow(0 0 6px rgba(88,101,242,.85)) drop-shadow(0 0 2px rgba(88,101,242,.6));
}
.member-links a:hover .social-tooltip,
.member-links a:focus-visible .social-tooltip,
.member-links button:hover .social-tooltip,
.member-links button:focus-visible .social-tooltip{
  opacity:1; transform:translate(-50%, 0);
  transition:opacity .28s ease .06s, transform .32s cubic-bezier(.22,1,.36,1);
}
/* text-abbreviation variant (X / YT / DC style placeholders) */
.member-links--text a{
  width:auto; padding:0 7px;
  font-family:'Geist Mono',ui-monospace,monospace;
  font-size:8.5px; letter-spacing:.1em;
}

@media (max-width:520px){
  .group-stat{ margin-left:0; text-align:left; order:1; flex-basis:100%; }
}

/* ── footer ──────────────────────────────────────────────── */
.foot{ padding:36px var(--pad) 44px; border-top:1px solid var(--line); }
.foot-inner{
  max-width:var(--maxw); margin:0 auto;
  display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:20px;
}
.foot-logo{
  justify-self:start; font-family:'Geist','Inter',sans-serif;
  font-size:15px; font-weight:700; letter-spacing:-.04em;
  background:linear-gradient(180deg, #4a67ea 0%, #2743c0 55%, #152678 100%);
  -webkit-background-clip:text; background-clip:text;
  -webkit-text-fill-color:transparent; color:transparent;
  filter:drop-shadow(0 0 10px rgba(47,80,216,.45));
}
.foot-links{ justify-self:center; display:flex; gap:clamp(16px,2vw,28px); }
.foot-links a{ font-size:12.5px; color:var(--txt-3); transition:color .18s ease; }
.foot-links a:hover{ color:#fff; }
.foot-copy{
  justify-self:end; font-family:'Geist Mono',ui-monospace,monospace;
  font-size:10.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--txt-4);
}

/* ── responsive ──────────────────────────────────────────── */
@media (max-width:820px){
  .nav-links{ display:none; }
  .nav-inner{ grid-template-columns:1fr auto; }
  .nav-logo{ opacity:1; transform:none; pointer-events:auto; }
  .nav-toggle{ display:flex; }
  .nav.is-menu-open{ background:rgba(0,0,0,.92); backdrop-filter:blur(14px) saturate(140%); -webkit-backdrop-filter:blur(14px) saturate(140%); border-radius:24px 24px 18px 18px; }
  .nav.is-menu-open .nav-mobile-menu{ display:flex; }
  .team{ min-height:auto; }
  .foot-inner{ grid-template-columns:1fr; justify-items:center; text-align:center; gap:18px; }
  .foot-logo,.foot-copy,.foot-links{ justify-self:center; }
}
@media (max-width:520px){
  .carousel-meta .progress{ width:90px; }
  /* At 3-across on a phone each card is ~110px wide — under the
     container-query thresholds that hide the bio/stats/link row
     entirely. Dropping to 1-across gives each card the room it
     needs, so that content (including social icons) actually shows. */
  :root{ --visible:1; }
  /* Same reasoning for the server cards: 3-across leaves each one
     too narrow for its stats row and the "Join server" button ends
     up squashed into a near-circle instead of a full-width pill. */
  .server-grid{ grid-template-columns:1fr; }
  /* The arrows are normally parked out in the page margins, which
     works on desktop but pushes them fully off-screen on a phone
     with no side margin to sit in. Pull them back onto the card
     rail itself so they're visible and tappable. */
  /* Smaller on mobile so they don't overlap into the card's own
     content (they were covering part of the name) now that they
     sit on the rail itself instead of out in the margins. */
  .arrow{ width:32px; }
  .arrow svg{ width:15px; height:15px; }
  .arrow-prev{ left:6px; transform:translateY(-50%); }
  .arrow-next{ right:6px; transform:translateY(-50%); }
}

@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  *,*::before,*::after{
    animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
  }
  .js [data-reveal]{ opacity:1; }
  .js [data-reveal].is-in{ animation:none; }
  /* Scroll-linked effects are driven by custom properties, so they
     aren't covered by the animation/transition overrides above —
     pin them to their rest values explicitly. */
  .scenepacks-glow{
    transform:translateX(-50%) !important; opacity:1 !important;
  }
  .pack-cover{ transform:none !important; }
  /* Decorative pointer-tracking light — remove entirely rather
     than let the .01ms override snap it around the screen. */
  .cursor-glow{ display:none !important; }
  /* Placeholder sheen and load bar are decorative motion — hold
     them still rather than letting the .01ms override strobe them. */
  .pack-thumb::after{ animation:none !important; }
  .loadbar{ display:none !important; }
}

/* ── scenepacks page ─────────────────────────────────────────
   Reused by /scenepacks/index.html. Deliberately a step above the
   rest of the site's card treatment (deeper shadow, cursor
   spotlight, its own reveal timing) since this is meant to feel
   like the flagship section — same tokens, same grammar, just
   turned up. */
.scenepacks-hero{ position:relative; }
.scenepacks-glow{
  position:absolute; left:50%; top:-8%; width:min(1100px,120vw); height:min(560px,60vh);
  pointer-events:none; z-index:0;
  background:radial-gradient(50% 50% at 50% 40%,
    rgba(88,101,242,.20) 0%, rgba(47,80,216,.09) 44%, rgba(0,0,0,0) 72%);
  /* --sy is written by the scroll-linked driver in script.js (0 at rest,
     rising as the hero leaves). Kept as a custom property so the whole
     effect degrades to "no transform" if the JS never runs. */
  transform:translateX(-50%) translateY(calc(var(--sy,0) * 90px)) scale(calc(1 + var(--sy,0) * .18));
  opacity:calc(1 - var(--sy,0) * .55);
  will-change:transform,opacity;
}
.scenepacks-hero > *:not(.scenepacks-glow){ position:relative; z-index:1; }

/* ── poster parallax ──────────────────────────────────────────
   The cover art drifts a little slower than the card frame while
   the card crosses the viewport, so posters feel set *inside* the
   frame instead of pasted onto it. --py is written per-card by the
   scroll driver; see the .pack-cover rules further down. */

.pack-library{ padding:0 var(--pad) clamp(90px,13vh,150px); }

/* ── scenepacks editor spotlight ──────────────────────────────
   A compact, centered row of "made by" cards sitting between the
   hero copy and the library toolbar. Reuses the .member-card
   look from /members so the same avatar-fetch script (matching on
   [data-team-slug]) and hover treatment apply with no extra JS. */
.pack-editors{
  max-width:var(--maxw); margin:clamp(8px,2vh,20px) auto clamp(56px,7vh,84px);
  padding:0 var(--pad);
  display:flex; flex-direction:column; align-items:center; gap:22px;
}
.pack-editors-label{
  font-family:'Geist Mono',ui-monospace,monospace; font-size:11px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--txt-3);
}
.pack-editors-grid{
  display:flex; flex-wrap:wrap; justify-content:center; gap:clamp(14px,2vw,22px);
}
.pack-editor-card{ width:150px; flex:0 0 auto; }

.pack-filters{
  max-width:var(--maxw); margin:0 auto clamp(30px,4.4vh,48px);
  display:flex; flex-wrap:wrap; gap:10px;
}
.pack-filters{ position:relative; }
/* Sized and positioned by JS from the active pill's own geometry —
   see the filter click handler in script.js. Starts at opacity 0
   and width 0 so there's no flash-of-wrong-size before the first
   measurement runs; .is-ready is added right after that. */
.pack-filter-indicator{
  position:absolute; top:0; left:0; height:100%; width:0;
  border-radius:9999px; z-index:0; pointer-events:none;
  background:rgba(88,101,242,.16); border:1px solid rgba(109,139,255,.5);
  opacity:0;
  transition:transform .32s cubic-bezier(.2,.8,.25,1), width .32s cubic-bezier(.2,.8,.25,1), opacity .2s ease;
}
.pack-filters.is-ready .pack-filter-indicator{ opacity:1; }
/* Once the indicator is live, the active pill's own background steps
   aside — otherwise the two would stack and the slide would be
   invisible under a pill that's already fully coloured. Text stays
   white either way, indicator or not, so nothing regresses without
   JS. */
.pack-filters.is-ready .pack-filter.is-active{ background:transparent; border-color:transparent; }
.pack-filter{
  position:relative; z-index:1; /* sits above the sliding indicator */
  border:1px solid var(--line-2); border-radius:9999px; background:transparent;
  color:var(--txt-3); font-family:inherit; font-size:12.5px; font-weight:500;
  padding:8px 18px; cursor:pointer;
  transition:color .22s ease, border-color .22s ease, background .22s ease;
}
.pack-filter:hover{ color:#fff; border-color:rgba(255,255,255,.32); }
.pack-filter:active{ transform:scale(.96); transition:transform 90ms ease; }
.pack-filter.is-active{
  color:#fff; background:rgba(88,101,242,.16); border-color:rgba(109,139,255,.5);
}

.pack-grid{
  max-width:var(--maxw); margin:0 auto;
  display:grid; grid-template-columns:repeat(auto-fill,minmax(170px,1fr));
  gap:clamp(18px,2vw,28px);
}

.pack-card{
  position:relative; display:flex; flex-direction:column;
  border-radius:20px; overflow:hidden; isolation:isolate;
  background:linear-gradient(180deg,rgba(255,255,255,.045),rgba(255,255,255,.015));
  border:1px solid var(--line);
  box-shadow:0 1px 0 rgba(255,255,255,.03) inset, 0 18px 40px -30px rgba(0,0,0,.9);
  transition:border-color .4s ease, box-shadow .5s var(--ease-out),
             translate .5s var(--ease-out), opacity .35s ease, scale .35s var(--ease-out);
}
.pack-card:hover{
  border-color:rgba(109,139,255,.4);
  translate:0 -8px;
  box-shadow:0 1px 0 rgba(255,255,255,.05) inset, 0 32px 70px -26px rgba(47,80,216,.48);
}
/* The actual click target is .pack-hit, an invisible button covering
   the card — :active on it doesn't touch the card visually on its
   own, so :has() reaches up to give the whole card a press response
   instead of just the invisible overlay doing nothing visible. */
.pack-card:has(.pack-hit:active){
  translate:0 -4px; scale:.98;
  transition:translate 90ms ease, scale 90ms ease;
}
.pack-card[hidden]{ display:none; }
.pack-card[data-reveal].is-in{
  animation:packRevealIn 1s var(--ease-out) var(--d,0ms) backwards;
}
@keyframes packRevealIn{
  from{ opacity:0; translate:0 28px; scale:.96; }
  to{   opacity:1; translate:0 0;    scale:1;   }
}

.pack-hit{
  position:absolute; inset:0; z-index:3; border-radius:inherit;
  padding:0; border:0; background:none; cursor:pointer;
}

.pack-thumb{
  position:relative; aspect-ratio:2/3; overflow:hidden; flex:none;
  background:linear-gradient(155deg,var(--tint-a,#0e1020),var(--tint-b,#191d33));
}
/* Poster placeholder ───────────────────────────────────────
   Covers are lazy-loaded, so without this each card is a bare
   rectangle until its image lands — 68 of them popping in one by
   one. A tinted panel with a slow sheen reads as "loading on
   purpose" instead of "broken", and fades out once the image
   decodes. Set by the loader in script.js. */
.pack-thumb::after{
  content:''; position:absolute; inset:0; z-index:0; pointer-events:none;
  background:
    linear-gradient(100deg,
      rgba(255,255,255,0) 20%,
      rgba(255,255,255,.05) 42%,
      rgba(255,255,255,0) 64%),
    linear-gradient(155deg, rgba(47,80,216,.12), rgba(0,0,0,.35));
  background-size:220% 100%, 100% 100%;
  animation:packThumbSheen 1.9s linear infinite;
  transition:opacity .45s ease;
}
.pack-thumb.is-loaded::after{ opacity:0; animation:none; }
@keyframes packThumbSheen{
  from{ background-position:120% 0, 0 0; }
  to{   background-position:-120% 0, 0 0; }
}
.pack-mono{
  position:absolute; right:-4%; bottom:-16%; z-index:0;
  font-family:'Geist','Inter',sans-serif; font-weight:700;
  font-size:clamp(90px,13vw,160px); letter-spacing:-.06em; line-height:1;
  color:rgba(255,255,255,.14); user-select:none; pointer-events:none;
  transition:translate .6s var(--ease-out), scale .6s var(--ease-out), opacity .5s ease;
}
.pack-card:hover .pack-mono{ translate:-3% -3%; scale:1.06; opacity:.22; }

.pack-cover{
  position:absolute; inset:0; z-index:0;
  width:100%; height:100%; object-fit:cover;
  object-position:var(--cover-pos, 50% 30%);
  /* --py is written per-card by the scroll-linked driver in script.js.
     Oversized by default so the drift can never expose a bare edge;
     with no JS --py stays 0 and this is just a static 1.06 crop. */
  transform:translateY(calc(var(--py,0) * 1px));
  scale:1.06;
  /* Fades in once decoded — see .pack-thumb.is-loaded. Starts visible
     for the no-JS case so covers never get stuck hidden. */
  opacity:1;
}
.js .pack-cover{ opacity:0; }
.js .pack-thumb.is-loaded .pack-cover{ opacity:1; }
.pack-card:hover .pack-cover{ scale:1.1; }
.pack-cover{ transition:scale .6s var(--ease-out), opacity .5s var(--ease-out); }

.pack-vignette{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(180deg, rgba(0,0,0,0) 45%, rgba(0,0,0,.55) 100%);
}
.pack-shine{
  position:absolute; inset:0; z-index:1; opacity:0; pointer-events:none;
  background:radial-gradient(240px circle at var(--mx,50%) var(--my,35%),
    rgba(255,255,255,.22), rgba(255,255,255,0) 62%);
  transition:opacity .4s ease;
}
.pack-card:hover .pack-shine{ opacity:1; }

/* status chips (New / Ongoing / Remake) — sit where the old
   quality badge used to, just on the opposite corner so several
   can stack without crowding. */
.pack-status{
  position:absolute; top:14px; left:14px; z-index:2;
  display:flex; flex-direction:column; align-items:flex-start; gap:6px;
}
.status-chip{
  font-family:'Geist Mono',ui-monospace,monospace; font-size:9.5px; font-weight:600;
  letter-spacing:.08em; text-transform:uppercase; color:#fff;
  padding:4px 9px; border-radius:9999px; border:1px solid rgba(255,255,255,.18);
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
}
.status-chip--new{ background:rgba(47,80,216,.55); border-color:rgba(109,139,255,.55); }
.status-chip--ongoing{ background:rgba(20,140,95,.55); border-color:rgba(70,205,150,.5); }
.status-chip--remake{ background:rgba(150,60,190,.5); border-color:rgba(205,120,235,.5); }
.status-chip--upcoming{ background:rgba(200,140,20,.55); border-color:rgba(230,180,60,.5); }

/* Discord "Server Tag" badge — the small clan-style tag Discord
   itself shows next to members of a tagged server. Same pill/blur
   language as the scenepack status chips, in the brand's own
   blurple since this represents an official Discord identity
   marker rather than a pack-freshness state. */
.server-tag-badge{
  position:absolute; top:14px; right:14px; z-index:2;
  font-family:'Geist Mono',ui-monospace,monospace; font-size:9.5px; font-weight:600;
  letter-spacing:.08em; text-transform:uppercase; color:#fff;
  padding:4px 9px; border-radius:9999px;
  background:rgba(88,101,242,.4); border:1px solid rgba(123,134,247,.55);
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
}

.pack-body{ position:relative; z-index:4; padding:20px 22px 24px; pointer-events:none; }
.pack-tag{
  font-family:'Geist Mono',ui-monospace,monospace; font-size:10.5px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--blue-lit);
}
.pack-title{
  margin-top:8px; font-family:'Geist','Inter',sans-serif; font-weight:500;
  font-size:clamp(17px,1.6vw,21px); letter-spacing:-.02em; color:#fff;
}
.pack-meta{ margin-top:8px; font-size:12.5px; color:var(--txt-3); }
.pack-credit{ margin-top:4px; font-size:11px; color:var(--txt-4); }
.pack-credit a{ color:var(--blue-lit); font-weight:500; text-decoration:none; position:relative; pointer-events:auto; }
.pack-credit a:hover{ text-decoration:underline; }

/* ── scenepacks: search + sort toolbar ──────────────────────── */
.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
}

.pack-toolbar{
  max-width:var(--maxw); margin:0 auto clamp(24px,3.4vh,36px);
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:16px;
}
.pack-search{
  position:relative; flex:1 1 240px; max-width:340px;
  display:flex; align-items:center; gap:10px;
  border:1px solid var(--line-2); border-radius:9999px;
  padding:10px 16px; background:rgba(255,255,255,.03);
  transition:border-color .22s ease, background .22s ease;
}
.pack-search:focus-within{
  border-color:rgba(109,139,255,.5); background:rgba(47,80,216,.08);
}
.pack-search .ic{ width:16px; height:16px; flex:none; color:var(--txt-3); }
.pack-search:focus-within .ic{ color:var(--blue-lit); }
.pack-search-input{
  flex:1; min-width:0; border:0; outline:0; background:none;
  font-family:inherit; font-size:13px; color:#fff;
}
.pack-search-input::placeholder{ color:var(--txt-3); }
.pack-search-input::-webkit-search-cancel-button{ filter:invert(1); opacity:.5; cursor:pointer; }

.pack-toolbar-controls{
  display:flex; flex-wrap:wrap; align-items:center; gap:12px;
}

.pack-sort-wrap{ display:inline-flex; }
.pack-sort{
  appearance:none; -webkit-appearance:none;
  border:1px solid var(--line-2); border-radius:9999px; background:transparent;
  color:var(--txt-2); font-family:inherit; font-size:12.5px; font-weight:500;
  padding:9px 34px 9px 16px; cursor:pointer;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff99' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 12px center; background-size:14px;
  transition:border-color .22s ease, color .22s ease, transform 90ms ease;
}
.pack-sort:hover{ color:#fff; border-color:rgba(255,255,255,.32); }
.pack-sort:active{ transform:scale(.97); }
.pack-sort:focus-visible{ border-color:rgba(109,139,255,.5); }
.pack-sort option{ background:#0a0a0a; color:#fff; }

.pack-empty{
  max-width:var(--maxw); margin:0 auto clamp(30px,4vh,44px);
  font-size:13.5px; color:var(--txt-3);
}
.pack-empty[hidden]{ display:none; }

.pack-grid{ transition:opacity .28s ease; }
.pack-grid.is-sorting{ opacity:.35; }
/* A card being filtered out gets its own quicker, more definite
   fade+shrink on top of the grid's blanket dim, so leaving reads as
   a distinct action rather than "the whole grid went dark for a
   moment." Still resolves inside the same 180ms swap window the
   filter logic already uses, so the actual hide/show DOM swap stays
   the single synced moment it was deliberately built to be — this
   only adds a farewell on top, it doesn't touch that timing. */
.pack-card.is-leaving{
  opacity:.15; scale:.94;
  transition:opacity .15s ease, scale .15s ease;
}

@media (max-width:560px){
  .pack-toolbar{ flex-direction:column; align-items:stretch; }
  .pack-search{ flex:0 0 auto; max-width:none; width:100%; }
  .pack-toolbar-controls{ justify-content:space-between; }
  /* Phones get exactly two packs per row — auto-fill would otherwise
     drop to a single wide column and make the library endless to scroll. */
  .pack-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; }
  .pack-body{ padding:14px 14px 16px; }
  .pack-title{ font-size:14px; }
  .pack-meta, .pack-credit{ font-size:11px; }
}

/* ── scenepacks: detail modal ─────────────────────────────────
   Clicking a card opens this instead of navigating straight to
   Rentry, so people can see what's actually in the pack first. */
.pack-modal{
  position:fixed; inset:0; z-index:100;
  display:flex; align-items:center; justify-content:center;
  padding:24px;
}
.pack-modal[hidden]{ display:none; }
.pack-modal-backdrop{
  position:absolute; inset:0;
  background:rgba(2,3,8,.72);
  backdrop-filter:blur(0px); -webkit-backdrop-filter:blur(0px);
  opacity:0;
  transition:opacity .28s ease, backdrop-filter .4s ease, -webkit-backdrop-filter .4s ease;
}
.pack-modal.is-open .pack-modal-backdrop{
  opacity:1;
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
}

.pack-modal-panel{
  position:relative; z-index:1; width:min(760px,100%); max-height:min(720px,88vh);
  display:grid; grid-template-columns:minmax(0,260px) 1fr;
  /* The single implicit row also defaults to min-content sizing, so
     it would stretch to fit a long body and push it past the panel's
     max-height. minmax(0,1fr) lets the row be shorter than its
     content, which is the other half of making the body scrollable. */
  grid-template-rows:minmax(0,1fr);
  border-radius:22px; overflow:hidden; isolation:isolate;
  background:linear-gradient(180deg,rgba(255,255,255,.05),rgba(255,255,255,.015)), #07070c;
  border:1px solid var(--line); box-shadow:0 40px 100px -30px rgba(0,0,0,.85);
  opacity:0; translate:0 18px; scale:.97;
  transition:opacity .32s var(--ease-out), translate .38s var(--ease-out), scale .38s var(--ease-out);
}
.pack-modal.is-open .pack-modal-panel{ opacity:1; translate:0 0; scale:1; }

.pack-modal-close{
  position:absolute; top:14px; right:14px; z-index:3;
  width:32px; height:32px; display:grid; place-items:center;
  border-radius:50%; border:1px solid rgba(255,255,255,.16);
  background:rgba(0,0,0,.45); color:#fff; cursor:pointer;
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
  transition:background .18s ease, border-color .18s ease, transform .18s ease;
}
.pack-modal-close svg{ width:15px; height:15px; }
.pack-modal-close:hover{ background:rgba(47,80,216,.35); border-color:rgba(109,139,255,.5); transform:scale(1.06); }

.pack-modal-cover{
  position:relative; min-height:220px; background:#0a0a10;
}
.pack-modal-cover .pack-thumb{
  position:absolute; inset:0; aspect-ratio:auto; height:100%; border-radius:0;
}

.pack-modal-body{
  position:relative; padding:26px 28px 28px;
  display:flex; flex-direction:column;
  /* min-height:0 is what actually makes this scroll. As a grid item
     it defaults to min-height:auto, meaning it refuses to shrink
     below its content — so with a long extras list the body just
     grew past the panel, got clipped by the panel's overflow:hidden,
     and overflow-y:auto never had an overflowing box to scroll.
     Explicitly allowing it to shrink gives the scroll container a
     real height to work against. */
  min-height:0;
  overflow-y:auto;
  /* Don't hand the scroll off to the page once this hits its end. */
  overscroll-behavior:contain;
}
.pack-modal-status{ display:flex; flex-wrap:wrap; gap:6px; margin-bottom:12px; }
.pack-modal-status:empty{ display:none; margin:0; }
.pack-modal-tag{
  font-family:'Geist Mono',ui-monospace,monospace; font-size:10.5px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--blue-lit);
}
.pack-modal-title{
  margin-top:8px; font-family:'Geist','Inter',sans-serif; font-weight:600;
  font-size:clamp(20px,2.4vw,26px); letter-spacing:-.02em; color:#fff;
}
.pack-modal-years{ margin-top:6px; font-size:13px; color:var(--txt-3); }

.pack-modal-section{ margin-top:18px; }
.pack-modal-section h4{
  font-family:'Geist Mono',ui-monospace,monospace; font-size:10px; font-weight:500;
  letter-spacing:.12em; text-transform:uppercase; color:var(--txt-4);
}
.pack-modal-section p{ margin-top:6px; font-size:13.5px; line-height:1.55; color:var(--txt-2); }
.pack-modal-aesthetics{ color:var(--txt-3); }
.pack-modal-aesthetics[hidden]{ display:none; }

.pack-modal-credit{ margin-top:18px; font-size:12px; color:var(--txt-4); }
.pack-modal-credit a{ color:var(--blue-lit); font-weight:500; text-decoration:none; }
.pack-modal-credit a:hover{ text-decoration:underline; }

.pack-modal-cta{ margin-top:22px; align-self:flex-start; }
.pack-modal-nolink{ margin-top:22px; font-size:12.5px; color:var(--txt-4); }
.pack-modal-nolink[hidden]{ display:none; }

@media (max-width:640px){
  .pack-modal{ padding:14px; align-items:flex-end; }
  .pack-modal-panel{
    grid-template-columns:1fr; max-height:min(640px,92vh);
    /* Rows sized to content: with `1fr` the body row was capped at
       the leftover space and clipped, same failure as desktop. */
    grid-template-rows:auto auto;
    overflow-y:auto;
    overscroll-behavior:contain;
    -webkit-overflow-scrolling:touch;
  }
  /* One scroll container, not two. Previously the panel AND the body
     were both scrollable and nested, so a touch drag inside the body
     was ambiguous — it would catch the inner element, hit its end
     immediately, and appear to lock up. The panel now does all the
     scrolling. */
  .pack-modal-body{ overflow:visible; min-height:auto; }
  .pack-modal-cover{ min-height:200px; aspect-ratio:16/10; }
  .pack-modal-cover .pack-thumb{ position:relative; height:100%; }
}
