/* DMZ Ragnarok shared styles.

   The chrome IS the game's chrome: panels, buttons, slots and the
   scrollbar are the mod's own PNGs from assets/ui/, drawn as CSS
   nine-slices, and the palette is sampled out of those same files. This
   replaced a neon magenta and cyan night-city theme that looked nothing
   like what a player sees after they click Play.

   Everything below references the design-system tokens in :root.
   No magic numbers past this block: spacing, type, radius, motion and
   elevation all come from the scale so the pages stay consistent. */

:root {
  /* -----------------------------------------------------------------
     COLOR
     Taken from the GAME, not chosen for the web. Every value below was
     sampled out of the actual GUI textures the mod draws its screens
     with (assets/ui/, see that folder's README), so the site and the
     in-game menus are the same object rather than two things that were
     asked to look similar:

       panel interior   #000f39   panel.png at its centre
       panel rim        #6b3602   panel.png outer border
       button interior  #000d32   button.png
       button rim       #dd9923   button.png top rim
       hover rim        #ffda80   button_hover.png top rim
       dragon-ball stud #f8b028   button_hover.png corner stud

     This replaces the old magenta/cyan neon palette. The variable NAMES
     are kept exactly as they were, including --ki and --magenta, so
     every existing rule keeps resolving; what they point at is now the
     gold accent. The one place magenta still lives is the logo art
     itself, which is brand and stays as drawn.
     ----------------------------------------------------------------- */
  --bg: #00081f;                       /* void, one step under the panels */
  --bg-2: #000d2c;
  --bg-3: #00144d;                     /* the lighter navy inside the panel rim */
  --panel: rgba(0,15,57,0.86);         /* panel.png interior */
  --panel-2: rgba(8,25,70,0.90);       /* button_hover.png interior */
  --line: rgba(221,153,35,0.34);       /* gold hairline */
  --line-strong: rgba(255,218,128,0.58);
  --text: #eef2ff;
  --muted: #9fb0d4;
  --blue: #29b6f6;                     /* kept for the few informational accents */
  --blue-2: #7fc9ff;
  --cyan: #4fc3f7;
  --ki: #dd9923;                       /* primary accent is now the button rim gold */
  --ki-2: #ffda80;
  --magenta: #dd9923;                  /* alias kept so nothing breaks */
  --magenta-2: #b87a12;
  --gold: #dd9923;
  --gold-2: #ffda80;
  --orange: #f8b028;                   /* the lit dragon-ball stud */
  --yellow: #ffd93d;
  --danger: #ff3b47;
  --amber: #ffb547;

  /* -----------------------------------------------------------------
     SPACING SCALE (4px base). Reference these, never raw px.
     ----------------------------------------------------------------- */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 22px;
  --space-6: 28px;
  --space-8: 44px;
  --space-10: 64px;
  --space-12: 96px;

  /* -----------------------------------------------------------------
     TYPE SCALE + WEIGHTS + LINE HEIGHTS.
     One display face (Orbitron) for headings and controls, one body
     face (Rajdhani). Weight hierarchy is fixed: body 500, emphasis 600,
     headings 700. Nothing goes ultra-thin.
     ----------------------------------------------------------------- */
  --font-display: 'Orbitron', 'Rajdhani', sans-serif;
  --font-body: 'Rajdhani', 'Segoe UI', system-ui, sans-serif;

  --fs-eyebrow: 0.8rem;
  --fs-sm: 0.92rem;
  --fs-body: 1rem;
  --fs-lead: clamp(1rem, 2.2vw, 1.3rem);
  --fs-h3: 1.15rem;
  --fs-h2: clamp(1.8rem, 4vw, 2.5rem);
  --fs-h1: clamp(2.2rem, 5vw, 3.4rem);
  --fs-hero: clamp(2.6rem, 7vw, 5rem);

  --fw-body: 500;
  --fw-medium: 600;
  --fw-bold: 700;

  --lh-tight: 1.15;                    /* headings */
  --lh-body: 1.6;                      /* copy */

  /* -----------------------------------------------------------------
     RADIUS. Three values only.
     ----------------------------------------------------------------- */
  --radius-sm: 8px;                    /* controls, small chips */
  --radius: 14px;                      /* cards, buttons, panels */
  --radius-pill: 999px;               /* badges, role tags, dots */

  /* -----------------------------------------------------------------
     MOTION. Named easing curves and durations. Every transition below
     picks from this set so timing reads intentional, not random.
     ----------------------------------------------------------------- */
  --ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);      /* UI settle */
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);       /* pulses */
  --dur-fast: 120ms;                   /* press / lift */
  --dur-base: 200ms;                   /* color / border */
  --dur-slow: 320ms;                   /* shadow / entrance */

  /* -----------------------------------------------------------------
     ELEVATION. Shadow tokens. The neon glows are part of the brand but
     scoped to the primary accent and the active state, not sprayed on
     every hover.
     ----------------------------------------------------------------- */
  --shadow-1: 0 2px 10px -4px rgba(10,6,24,0.6);
  --shadow-2: 0 12px 34px -18px rgba(10,6,24,0.75);
  --glow-ki: 0 0 20px -6px rgba(248,176,40,0.7);     /* lit dragon-ball stud */
  --glow-cyan: 0 0 20px -6px rgba(79,195,247,0.6);

  --maxw: 1120px;

  /* -----------------------------------------------------------------
     CONTENT MEASURES. Text columns pick from this small set so no two
     paragraphs get an arbitrary reading width. Narrow is for tight
     subtitle prose under a heading, standard is the default lead /
     intro measure, wide is a roomier prose column. --maxw above stays
     the full page container; these are for copy inside it.
     ----------------------------------------------------------------- */
  --measure-narrow: 600px;             /* subtitles under a heading */
  --measure: 640px;                    /* default lead / intro copy */
  --measure-wide: 860px;               /* roomy prose column */

  /* -----------------------------------------------------------------
     BLUR. Backdrop blur strengths for translucent surfaces. Two steps:
     a light frost for panels sitting on the page, a heavier frost for
     the sticky nav that overlaps scrolling content.
     ----------------------------------------------------------------- */
  --blur-panel: blur(6px);
  --blur-nav: blur(12px);
}

* { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  font-family: var(--font-body);
  font-weight: var(--fw-body);
  background:
    /* faint CSS starfield high in the sky */
    radial-gradient(1.5px 1.5px at 12% 8%, rgba(238,242,255,0.7), transparent 60%),
    radial-gradient(1.5px 1.5px at 78% 14%, rgba(238,242,255,0.55), transparent 60%),
    radial-gradient(1px 1px at 40% 6%, rgba(238,242,255,0.6), transparent 60%),
    radial-gradient(1px 1px at 63% 20%, rgba(180,220,255,0.5), transparent 60%),
    radial-gradient(1.5px 1.5px at 88% 5%, rgba(238,242,255,0.5), transparent 60%),
    radial-gradient(1px 1px at 25% 18%, rgba(200,210,255,0.5), transparent 60%),
    /* Sky glow, now gold and cold blue rather than magenta and cyan. Kept
       weak: the panels below carry the identity, and a strong wash behind
       them muddies the gold rims that are the whole point. */
    radial-gradient(1100px 620px at 82% -8%, rgba(221,153,35,0.13), transparent 60%),
    radial-gradient(960px 560px at 10% 4%, rgba(41,110,200,0.14), transparent 58%),
    /* deep navy ground, matching the panel interior */
    linear-gradient(180deg, var(--bg) 0%, var(--bg-2) 55%, var(--bg-3) 100%);
  background-attachment: fixed;
  color: var(--text);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
}

h1, h2, h3, .brand {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  letter-spacing: 0.02em;
  line-height: var(--lh-tight);
  margin: 0 0 var(--space-3);
}

p { margin: 0 0 var(--space-4); }
p:last-child { margin-bottom: 0; }

a { color: var(--blue-2); text-decoration: none; transition: color var(--dur-base) var(--ease-out); }
a:hover { color: #fff; }

.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 var(--space-5); }

/* ---------------------------------------------------------------
   NAV. Identical markup and placement on every page.
   --------------------------------------------------------------- */
/* Navy glass with a gold underline, matching the rim on the panels
   below it rather than the cyan hairline it used to carry. */
.nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(0,13,44,0.80);
  backdrop-filter: var(--blur-nav);
  -webkit-backdrop-filter: var(--blur-nav);
  border-bottom: 1px solid var(--gold);
  box-shadow: 0 1px 0 rgba(0,0,0,0.5), 0 6px 22px rgba(0,0,0,0.35);
}
.nav-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: var(--space-3) var(--space-5);
  display: flex;
  align-items: center;
  gap: var(--space-5);
}
.brand {
  font-size: var(--fs-h3);
  color: #fff;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  text-transform: uppercase;
}
/* Brand mark: a small ki dot. Sized to the brand text, not oversized. */
.brand .spark {
  width: 0.7em; height: 0.7em; border-radius: var(--radius-pill);
  background: var(--magenta);
  box-shadow: var(--glow-ki);
  flex: none;
}
/* Brand logo. Replaces the old text wordmark in the nav. Sits on the dark
   translucent nav bar, which gives it its own contrast, and scales by height
   so its aspect never distorts. */
.brand img {
  display: block;
  height: 38px;
  width: auto;
}
.nav-links { margin-left: auto; display: flex; gap: var(--space-1); }
.nav-links a {
  color: var(--muted);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm);
  font-weight: var(--fw-medium);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  transition: background var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}
.nav-links a:hover {
  color: #fff;
  background: rgba(79,195,247,0.14);
}
.nav-links a.active {
  color: #fff;
  background: linear-gradient(180deg, var(--magenta-2), var(--magenta));
}
/* Language switcher. Reuses the nav-link look (a real <button>) so it sits
   in the same row as the links without new visual language. Its label text
   (EN / ES) is filled in by the i18n script. */
.nav-links .lang-toggle {
  color: var(--muted);
  background: rgba(79,195,247,0.08);
  border: 1px solid var(--line-strong);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-sm);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--fs-sm);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  cursor: pointer;
  transition: background var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.nav-links .lang-toggle:hover,
.nav-links .lang-toggle:focus-visible {
  color: #fff;
  background: rgba(79,195,247,0.16);
  border-color: var(--cyan);
  outline: none;
}

/* ---------------------------------------------------------------
   HERO
   --------------------------------------------------------------- */
.hero {
  position: relative;
  padding: var(--space-12) 0 var(--space-10);
  text-align: center;
  overflow: hidden;
  /* THE game's own main-menu background. assets/ui/space-bg.jpg is a
     JPEG of textures/gui/mainmenu/background.png, byte-identical art to
     what a player sees behind the menu the moment the client opens, so
     arriving on the site and launching the game are the same image.

     The scrim is weaker than the old one (0.48/0.62 against 0.62/0.72):
     that art is already dark, and the previous wash was tuned for a
     brighter city photo. */
  background-image:
    linear-gradient(180deg, rgba(0,8,31,0.48) 0%, rgba(0,13,44,0.62) 55%, var(--bg-2) 100%),
    url('ui/space-bg.jpg');
  background-size: cover, cover;
  background-position: center, center;
  border-bottom: 1px solid var(--line-strong);
}
.hero .kanji {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 42vw;
  font-weight: 900;
  color: rgba(221,153,35,0.07);
  pointer-events: none;
  user-select: none;
  line-height: 1;
}
.hero h1 {
  position: relative;
  font-size: var(--fs-hero);
  text-transform: uppercase;
  background: linear-gradient(180deg, #ffffff 20%, var(--blue-2) 58%, var(--magenta) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: #ffffff;               /* solid fallback if clip unsupported */
  color: transparent;
  /* one restrained neon halo for readability over the busy image */
  text-shadow:
    0 0 30px rgba(79,195,247,0.35),
    0 2px 10px rgba(0,0,0,0.55);
}
/* Hero wordmark. The logo is a wide transparent PNG, so it scales by width
   with a cap and keeps its own aspect (never stretched). A drop shadow lifts
   the bright lettering off the star field for contrast. Kept inside the h1
   so the page still has a real top-level heading for accessibility and SEO. */
.hero-logo {
  position: relative;
  margin: 0 auto var(--space-4);
  line-height: 0;
}
.hero-logo img {
  display: block;
  width: 100%;
  max-width: 620px;
  height: auto;
  margin: 0 auto;
  filter: drop-shadow(0 4px 18px rgba(0,0,0,0.55));
}
.hero .tag {
  position: relative;
  color: #dcd6f5;
  font-size: var(--fs-lead);
  max-width: var(--measure);
  margin: var(--space-2) auto 0;
  text-shadow: 0 1px 8px rgba(0,0,0,0.6);
}
.cta-row {
  position: relative;
  margin-top: var(--space-8);
  display: flex;
  gap: var(--space-3);
  justify-content: center;
  flex-wrap: wrap;
}

/* ---------------------------------------------------------------
   BUTTONS. The actual in-game button art, not a CSS impression of it.

   assets/ui/button.png and button_hover.png are the two rows of the
   mod's own buttons.png sheet (264x38 each: a gold-rimmed navy bar with
   a dragon-ball stud at each end, dull when idle and lit on hover).

   THREE-slice, not nine. The source is only 38px tall, so a nine-slice
   with a 19px cap top AND bottom would leave a zero-height middle band
   and the button interior would render as a gap on anything taller than
   38px. Slicing horizontally only (`0 19 0 19 fill`) keeps the two end
   studs at their authored size, stretches the bar between them, and
   stretches the whole strip vertically, which is near enough to
   identity because the art is uniform down its length apart from the
   1px rims.
   --------------------------------------------------------------- */
.btn {
  /* THE ASPECT LOCK. The art is 264x38 with a 19px end cap. Draw it into a
     box of any other height and the whole strip stretches vertically: the
     round dragon-ball studs go oval and the 1px gold rims fatten. That is
     what "the buttons look stretched" was.

     So height and cap are both derived from one scale factor. At scale 1
     the button is exactly 38px tall with 19px caps, which is 1:1 with the
     art and therefore no stretch at all. Any other size must go through
     --btn-scale so the two move together; never set a bare height on a
     .btn. */
  --btn-scale: 1;
  height: calc(38px * var(--btn-scale));
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0 var(--space-4);
  font-weight: var(--fw-bold);
  font-family: var(--font-display);
  font-size: calc(var(--fs-sm) * var(--btn-scale));
  letter-spacing: 0.04em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  color: var(--gold-2);
  background: transparent;
  border-style: solid;
  border-width: 0 calc(19px * var(--btn-scale));
  border-image: url('ui/button.png') 0 19 fill / 0 calc(19px * var(--btn-scale)) / 0 stretch;
  /* border-image is not animatable, so the lift and the text colour carry
     the transition and the art swaps instantly on hover, exactly as it
     does in game. */
  transition: transform var(--dur-fast) var(--ease-out),
              color var(--dur-base) var(--ease-out),
              filter var(--dur-base) var(--ease-out);
}
.btn:hover {
  transform: translateY(-2px);
  color: #fff;
  border-image-source: url('ui/button_hover.png');
  filter: drop-shadow(0 0 10px rgba(248,176,40,0.45));
}
.btn:active { transform: translateY(0); }
.btn:focus-visible {
  outline: 2px solid var(--gold-2);
  outline-offset: 3px;
}
/* Primary and ghost are the SAME art, in the same IDLE state.

   Primary used to set button_hover.png as its resting source, on the idea
   that a call to action should "start already lit". That was wrong: the
   lit art is the HOVER state, with the dragon-ball studs burning orange,
   so every primary button on the site looked permanently moused-over and
   the cursor made no difference to it. A hover state that is always on is
   not emphasis, it is a broken affordance.

   Emphasis is carried by the LABEL instead, which is the one thing the
   two-state art leaves free: white and slightly glowing against the
   ghost button's flat gold. The studs stay dull until the pointer is
   actually over the button, on primary exactly as on everything else. */
.btn-primary {
  color: #fff;
  text-shadow: 0 0 10px rgba(255,218,128,0.55);
}
.btn-ghost { color: var(--gold-2); }
/* Bigger call to action. Scaling BOTH the height and the cap keeps the art
   at its authored 264:38 proportion, so the studs stay circular at 1.5x
   where a bare `height: 57px` would have stretched them by half again. */
.btn-lg { --btn-scale: 1.5; padding: 0 var(--space-6); }
/* Progress state for the launcher download. The button fills left to
   right with real transfer progress while the jar streams. This is the
   only genuinely async action on the static pages, so it is the only
   place a progress indicator exists. */
.btn.is-busy {
  position: relative;
  overflow: hidden;
  cursor: progress;
  pointer-events: none;
}
.btn.is-busy .btn-progress {
  position: absolute;
  inset: 0;
  width: 0%;
  background: rgba(255,255,255,0.22);
  transition: width var(--dur-base) var(--ease-out);
  pointer-events: none;
}
.btn .btn-progress { display: none; }
.btn.is-busy .btn-label { position: relative; z-index: 1; }

/* ---------------------------------------------------------------
   SECTIONS
   --------------------------------------------------------------- */
section.block { padding: var(--space-10) 0; }
section.block.tight { padding-top: 0; }
.eyebrow {
  color: var(--cyan);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin-bottom: var(--space-2);
}
.section-head { text-align: center; margin-bottom: var(--space-8); }
.section-head h2 {
  font-size: var(--fs-h2);
  background: linear-gradient(90deg, var(--blue-2), #ffffff 45%, var(--ki-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: #ffffff;
  color: transparent;
}
.section-head p { color: var(--muted); max-width: var(--measure-narrow); margin: var(--space-2) auto 0; }

/* ---------------------------------------------------------------
   CARDS. Uniform radius, uniform padding, 4px lift, no spray glow.
   --------------------------------------------------------------- */
.grid { display: grid; gap: var(--space-5); }
/* The staff list is FLEX, not grid, and that is the whole reason it exists as its own
   class. A three-column grid leaves an incomplete final row jammed against the left
   edge, which is what put Raadley off to one side. Flex with justify-content: center
   centres whatever the last row happens to hold, so the roster stays balanced as
   people are added or removed and nobody has to re-count the columns by hand. */
.staff-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-5);
}
/* Exactly three per row, computed from the container rather than left to fall out of
   a fixed basis and whatever width the page happens to be. flex-grow and flex-shrink
   are both zero so a row of three never stretches to fill and a row of one never
   widens to the full container; the last row just centres whatever it holds. The two
   breakpoints match .grid-3 so the staff list steps down with everything else. */
.staff-grid > * { flex: 0 0 calc((100% - 2 * var(--space-5)) / 3); }
@media (max-width: 860px) {
  .staff-grid > * { flex: 0 0 calc((100% - var(--space-5)) / 2); }
}
@media (max-width: 520px) {
  .staff-grid > * { flex: 0 0 100%; }
}
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }

/* The mod's own panel.png as a true nine-slice. 351x332 with a 17px
   corner cap; the corners carry the orange dragon-ball studs and the
   interior is a flat navy that stretches without a seam, which is why
   `stretch` is correct here and no tiling is needed.

   The drawn cap is 20px rather than the source's 17. The game draws this
   art at a gui scale of about 3, so a 17px corner lands near 51 real
   pixels on a player's screen; 20px on a ~380px card keeps the studs at
   the same PROPORTION of the panel they have in game, which is what the
   eye actually reads, instead of matching the raw number and coming out
   visibly smaller than the original. */
.panel-art,
.card {
  background: transparent;
  border-style: solid;
  border-width: 20px;
  border-image: url('ui/panel.png') 17 fill / 20px / 0 stretch;
  padding: var(--space-4) var(--space-5);
  transition: transform var(--dur-fast) var(--ease-out),
              filter var(--dur-slow) var(--ease-out);
}
.card:hover {
  transform: translateY(-4px);
  filter: drop-shadow(0 6px 18px rgba(0,0,0,0.55))
          drop-shadow(0 0 12px rgba(221,153,35,0.28));
}
/* Icon tile. This deliberately does NOT use the game's slot.png.
   That texture is an inventory slot BACKING, not a frame: it is a
   near-featureless dark square with a faint purple gradient and no rim,
   so at 48px on a card it renders as a murky box rather than as a tile,
   and it is the one piece of the old purple set still in the folder.
   A flat navy square with the same gold hairline as everything else is
   both more honest and closer to what the panels around it look like. */
.card .ic {
  width: 3em; height: 3em;
  display: grid; place-items: center;
  background: linear-gradient(180deg, rgba(0,20,77,0.9), rgba(0,13,44,0.9));
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  color: var(--gold-2);
  font-size: var(--fs-h3);
  margin-bottom: var(--space-4);
}
.card h3 { font-size: var(--fs-h3); color: #fff; }
.card p { color: var(--muted); font-size: var(--fs-sm); }
.card code {
  background: rgba(79,195,247,0.12);
  border: 1px solid rgba(79,195,247,0.22);
  border-radius: var(--radius-sm);
  padding: 0 var(--space-1);
  font-size: 0.9em;
  color: var(--blue-2);
}

/* ---------------------------------------------------------------
   STAFF
   --------------------------------------------------------------- */
.staff-card { text-align: center; }
.staff-card .avatar {
  width: 96px; height: 96px;
  margin: 0 auto var(--space-4);
  border-radius: var(--radius-pill);
  background: linear-gradient(135deg, rgba(56,32,104,0.8), rgba(35,20,64,0.9));
  border: 2px solid var(--line-strong);
  display: grid; place-items: center;
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 1.8rem;
  color: var(--blue-2);
  overflow: hidden;
}
/* <picture> wraps the img for the animated avatars, and a picture element is
   inline by default, so it needs the same box as the img or the circle is
   filled by a shrink-wrapped element instead of the image. */
.staff-card .avatar img,
.staff-card .avatar picture { display: block; width: 100%; height: 100%; object-fit: cover; }
.staff-card h3 { margin-bottom: var(--space-1); }
.role {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  margin-top: var(--space-2);
}
.role.owner   { background: rgba(255,59,71,0.18);  color: #ff9aa2; border: 1px solid rgba(255,59,71,0.4); }
.role.admin   { background: rgba(255,45,149,0.16); color: var(--ki-2); border: 1px solid rgba(255,45,149,0.4); }
.role.mod     { background: rgba(79,195,247,0.16); color: var(--blue-2); border: 1px solid rgba(79,195,247,0.4); }
.role.builder { background: rgba(255,217,61,0.16); color: #ffe27a; border: 1px solid rgba(255,217,61,0.4); }
/* Artist. Given the dragon-ball stud orange rather than another blue or
   yellow, so it does not read as a variant of mod or builder. */
.role.artist  { background: rgba(248,176,40,0.16); color: var(--orange); border: 1px solid rgba(248,176,40,0.45); }
.staff-card .bio { color: var(--muted); font-size: var(--fs-sm); margin-top: var(--space-3); }

/* ---------------------------------------------------------------
   CALLOUT
   --------------------------------------------------------------- */
/* Same panel art as a card, just given more room to breathe. Using the
   identical nine-slice rather than a second bespoke surface is the point:
   in game there is one panel, so on the site there is one panel. */
.callout {
  background: transparent;
  border-style: solid;
  border-width: 20px;
  border-image: url('ui/panel.png') 17 fill / 20px / 0 stretch;
  padding: var(--space-6) var(--space-8);
  text-align: center;
}
.callout h2 {
  background: linear-gradient(90deg, var(--gold), #ffffff 45%, var(--gold-2));
  -webkit-background-clip: text;
  background-clip: text;
  color: #ffffff;
  color: transparent;
}
.callout h3 { font-size: var(--fs-h3); color: #fff; }
.callout.left { text-align: left; }
.callout.spaced { margin-top: var(--space-6); }
/* Intro paragraph inside a callout. Own class so it stops borrowing the
   hero .tag styling and its inline margin hacks. */
.callout .lead {
  color: var(--muted);
  font-size: var(--fs-lead);
  max-width: var(--measure);
  margin: 0 auto var(--space-5);
}
.callout.left .lead { margin-left: 0; margin-right: 0; }
/* A cta-row that sits directly under callout copy needs no extra top gap. */
.callout .cta-row { margin-top: 0; }
.badge-soon {
  display: inline-block;
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: 0.7rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--yellow);
  border: 1px solid rgba(255,217,61,0.5);
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3);
  margin-bottom: var(--space-4);
}

/* ---------------------------------------------------------------
   FOOTER. Identical on every page.
   --------------------------------------------------------------- */
footer {
  border-top: 1px solid var(--line);
  padding: var(--space-6) 0;
  color: var(--muted);
  font-size: var(--fs-sm);
}
.foot-inner { display: flex; justify-content: space-between; gap: var(--space-3); flex-wrap: wrap; }

/* ---------------------------------------------------------------
   PAGE HEADER (non-home)
   --------------------------------------------------------------- */
.page-head { padding: var(--space-12) 0 var(--space-6); text-align: center; }
.page-head h1 {
  font-size: var(--fs-h1);
  text-transform: uppercase;
  background: linear-gradient(180deg, #ffffff 25%, var(--blue-2) 70%, var(--magenta));
  -webkit-background-clip: text;
  background-clip: text;
  color: #ffffff;
  color: transparent;
}
.page-head p { color: var(--muted); max-width: var(--measure-narrow); margin: var(--space-2) auto 0; }

/* ---------------------------------------------------------------
   ENTRANCE MOTION. One purpose: cards settle in on scroll-in so the
   grids do not pop. Staggered per column via inline delay in markup.
   Falls back to fully visible when JS or motion is unavailable.
   --------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .reveal { opacity: 0; transform: translateY(14px); }
  .reveal.in {
    opacity: 1; transform: none;
    transition: opacity var(--dur-slow) var(--ease-out),
                transform var(--dur-slow) var(--ease-out);
  }
}

/* ---------------------------------------------------------------
   RESPONSIVE
   --------------------------------------------------------------- */
@media (max-width: 700px) {
  .hero {
    background-image:
      linear-gradient(180deg, rgba(21,12,43,0.64) 0%, rgba(27,16,53,0.74) 55%, var(--bg-2) 100%),
      url('space-hero-sm.jpg');
    padding: var(--space-12) 0 var(--space-8);
  }
}
@media (max-width: 860px) {
  .grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
  .nav-links a { padding: var(--space-2) var(--space-3); }
  .brand span.full { display: none; }
  .brand img { height: 32px; }
}
@media (max-width: 520px) {
  .grid-3, .grid-4 { grid-template-columns: 1fr; }
}

/* Brand-dot pulse. Motion only, on the one accent mark. */
@keyframes neonPulse {
  0%, 100% { box-shadow: 0 0 12px 2px rgba(255,45,149,0.6); }
  50%      { box-shadow: 0 0 20px 5px rgba(255,45,149,0.9); }
}
@media (prefers-reduced-motion: no-preference) {
  .brand .spark { animation: neonPulse 3.2s var(--ease-in-out) infinite; }
}

/* ---------------------------------------------------------------- Kinetic Hosting affiliate bar
   Sits ABOVE the nav on every page, as the first thing in <body>. Kept as a plain <a> wrapping the
   banner image plus a text line so it degrades sensibly: if the image fails to load, the alt text and
   the pitch line still carry the message and the link still works.

   The banner is authored at 837x80. max-width caps it at its native size so it is never upscaled into
   a blurry mess on a wide monitor, while width:100% lets it shrink on a phone. Explicit width/height
   attributes on the <img> reserve the space so the page does not jump as it loads. */
/* The slot gives the banner a contained place rather than a full-bleed strip: same max-width and
   horizontal padding as .wrap, styled as a card below. It sits ABOVE the nav (the author's preference),
   so a small top+bottom margin separates it from the top of the window and from the nav under it. */
.kinetic-slot {
  max-width: var(--maxw);
  margin: var(--space-3) auto var(--space-3);
  padding: 0 var(--space-5);
  box-sizing: border-box;
}
/* The banner art changed shape: it was a 837x80 strip (10.5:1) and is now
   720x300 (2.4:1). Stretched across the full slot width the way the strip
   was, it would draw roughly 480px tall and swallow the fold, so the bar
   is now a ROW: art on the left at a capped width, pitch text beside it.
   It stacks back to a column on narrow screens, where a row would squeeze
   both halves. */
.kinetic-bar {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  width: 100%;
  text-decoration: none;
  background: transparent;
  border-style: solid;
  border-width: 20px;
  border-image: url('ui/panel.png') 17 fill / 20px / 0 stretch;
  padding: var(--space-2) var(--space-4);
  box-sizing: border-box;
  text-align: left;
}
.kinetic-bar-img {
  display: block;
  flex: none;
  width: 100%;
  max-width: 260px;
  height: auto;
}
.kinetic-bar-text {
  display: block;
  font-size: 0.86rem;
  line-height: 1.4;
  color: var(--muted);
}
@media (max-width: 760px) {
  .kinetic-bar { flex-direction: column; text-align: center; gap: var(--space-3); }
  .kinetic-bar-img { max-width: 320px; }
}
/* Second line: the call to action, mirroring the in-game banner's "Click the banner to get your server
   now!". Brighter than the pitch above it so it reads as the instruction, not more description. */
.kinetic-bar-cta {
  display: inline-block;
  margin-top: 3px;
  color: #ffe27a;
}
.kinetic-bar:hover .kinetic-bar-text { color: #ffffff; }
.kinetic-bar:hover .kinetic-bar-cta { color: #fff3bf; }
/* The bar is promotional, not navigation: on a short screen it should not eat the fold. The CTA line
   goes with the pitch text it belongs to. */
@media (max-height: 620px) {
  .kinetic-bar-text { display: none; }
}

/* ---------------------------------------------------------------
   SCROLLBAR. The game's own scrollbar track and thumb.

   scrollbar.png is a 23x222 vertical track with a 5px cap. WebKit's
   scrollbar pseudo-elements accept border-image the same as any box, so
   the page scrolls against the same furniture the in-game lists do.
   Firefox gets the two-colour `scrollbar-color` fallback, which is all
   it exposes; there is no way to give it the art, and a JS scrollbar
   replacement is not worth breaking native scrolling for.
   --------------------------------------------------------------- */
html { scrollbar-color: var(--gold) var(--bg-2); scrollbar-width: thin; }
::-webkit-scrollbar { width: 14px; height: 14px; }
::-webkit-scrollbar-track {
  background: var(--bg-2);
  border-left: 1px solid var(--line);
}
::-webkit-scrollbar-thumb {
  border-style: solid;
  border-width: 5px;
  border-image: url('ui/scrollbar.png') 5 fill / 5px / 0 stretch;
  background: transparent;
}
::-webkit-scrollbar-corner { background: var(--bg-2); }

/* ---------------------------------------------------------------
   STORE. A category rail beside a product grid, which is the shape a
   storefront wants: the categories stay put while the goods scroll.

   Everything sits on the same panel.art nine-slice as the rest of the
   site, so the store does not become its own visual island.
   --------------------------------------------------------------- */
.store-layout {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: var(--space-6);
  align-items: start;
  padding-bottom: var(--space-10);
}
.store-rail-inner { position: sticky; top: 92px; }
.store-cats { list-style: none; margin: var(--space-2) 0 var(--space-5); padding: 0; }
.store-cats a {
  display: block;
  padding: var(--space-2) var(--space-3);
  color: var(--muted);
  border-left: 2px solid transparent;
  font-weight: var(--fw-medium);
  transition: color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out);
}
.store-cats a:hover { color: var(--gold-2); border-left-color: var(--gold); }
.store-rail-note p { color: var(--muted); font-size: var(--fs-sm); margin: var(--space-2) 0 0; }

.store-section { padding-top: var(--space-8); }
/* The sticky nav would otherwise cover a section the rail jumped to. */
.store-section { scroll-margin-top: 96px; }
.store-section-head { margin-bottom: var(--space-5); }
.store-section-head h2 { font-size: var(--fs-h2); color: #fff; }
.store-section-head p { color: var(--muted); max-width: var(--measure-wide); margin: var(--space-2) 0 0; }

.store-grid { display: grid; gap: var(--space-5); grid-template-columns: repeat(3, 1fr); }
.store-grid-4 { grid-template-columns: repeat(4, 1fr); }

.store-card {
  position: relative;
  display: flex;
  flex-direction: column;
  /* The badge, the heading and the price are all left aligned, and the
     button is pushed to the bottom by .perks so every card in a row ends
     on the same line whatever its perk count. */
  align-items: stretch;
  padding: var(--space-4) var(--space-5) var(--space-5);
  transition: transform var(--dur-fast) var(--ease-out),
              filter var(--dur-slow) var(--ease-out);
}
.store-card:hover {
  transform: translateY(-4px);
  filter: drop-shadow(0 6px 18px rgba(0,0,0,0.55))
          drop-shadow(0 0 12px rgba(221,153,35,0.28));
}
/* A fixed-height band so the six badges share one baseline even though
   VIP+ and MVP+ are authored with wings above the plate. */
.store-card-head {
  display: flex;
  align-items: flex-end;
  /* Sized to the TALLEST badge at the locked width: VIP+ is 112x46, so at
     150 wide it stands 62 tall. Bottom aligned, so every plate sits on one
     baseline and only the wings above it differ. */
  min-height: 66px;
  margin-bottom: var(--space-2);
}
.store-card h3 { font-size: var(--fs-h3); color: #fff; margin-bottom: var(--space-1); }
.store-card .price {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 1.7rem;
  color: var(--gold-2);
  line-height: 1.1;
  margin: 0 0 var(--space-4);
}
.store-card .perks {
  list-style: none;
  padding: 0;
  margin: 0 0 var(--space-5);
  color: var(--muted);
  font-size: var(--fs-sm);
  flex: 1;
}
.store-card .perks li { padding-left: 1.1em; position: relative; margin-bottom: var(--space-1); }
/* The dragon-ball stud, reused as the list bullet. */
.store-card .perks li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.55em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--orange);
  box-shadow: 0 0 6px rgba(248,176,40,0.6);
}
.store-card .btn { width: 100%; }
.store-card.is-compact .store-card-head { min-height: 0; justify-content: center; }
.store-card.is-compact { text-align: center; }
/* Reserve two lines for the title whether or not it needs them. "30 minutes"
   wraps at this column width and the other three do not, so without this its
   price and button sat a line lower than the rest of the row. */
.store-card.is-compact h3 {
  min-height: calc(2 * var(--fs-h3) * var(--lh-tight));
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Belt and braces for the same problem: push the button to the bottom of the
   card so the row's buttons align even if a title ever needs three lines. */
.store-card.is-compact .btn { margin-top: auto; }
.store-card.is-compact .price { margin-bottom: var(--space-3); }

/* Featured tier. A flag rather than a different panel, so the card still
   reads as one of the six. */
.store-flag {
  position: absolute;
  top: -10px; right: var(--space-4);
  background: var(--orange);
  color: #21160a;
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px var(--space-2);
  border-radius: var(--radius-sm);
  box-shadow: 0 0 12px rgba(248,176,40,0.5);
}

/* Weekly ladder. A table, because it is the SAME six tiers at another
   price and six more cards would double the page for no new information. */
.store-table-wrap { padding: var(--space-4) var(--space-5) var(--space-5); }
.store-table { width: 100%; border-collapse: collapse; }
.store-table th {
  text-align: left;
  font-family: var(--font-display);
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
  padding: 0 var(--space-3) var(--space-2);
  border-bottom: 1px solid var(--line);
}
.store-table td {
  padding: var(--space-2) var(--space-3);
  border-bottom: 1px solid rgba(221,153,35,0.14);
  vertical-align: middle;
}
.store-table tr:last-child td { border-bottom: 0; }
.store-table td:first-child { display: flex; align-items: center; gap: var(--space-3); }
.store-table .num {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  color: var(--gold-2);
  white-space: nowrap;
}
.store-table .num.muted { color: var(--muted); font-weight: var(--fw-body); }
.store-table-cta { margin-top: var(--space-5); text-align: center; }

/* Wide single product, for a one-off that does not need a grid cell. */
.store-wide {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-5);
}
.store-wide-body { flex: 1; }
.store-wide-body h3 { font-size: var(--fs-h3); color: #fff; }
.store-wide-body p { color: var(--muted); font-size: var(--fs-sm); margin: var(--space-1) 0 0; }
.store-wide-buy { text-align: right; flex: none; }
.store-wide-buy .price {
  font-family: var(--font-display);
  font-weight: var(--fw-bold);
  font-size: 1.7rem;
  color: var(--gold-2);
  margin-bottom: var(--space-2);
  line-height: 1.1;
}

.store-note { color: var(--muted); font-size: var(--fs-sm); text-align: center; margin-top: var(--space-8); }

@media (max-width: 1000px) {
  /* The rail stops being a rail and becomes a scrolling row of chips
     above the grid: at this width a 220px column costs more than it gives. */
  .store-layout { grid-template-columns: 1fr; }
  .store-rail-inner { position: static; }
  .store-cats { display: flex; gap: var(--space-2); overflow-x: auto; margin-bottom: var(--space-3); }
  .store-cats a { border-left: 0; border-bottom: 2px solid transparent; white-space: nowrap; }
  .store-cats a:hover { border-left-color: transparent; border-bottom-color: var(--gold); }
  .store-rail-note { display: none; }
  .store-grid, .store-grid-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 620px) {
  .store-grid, .store-grid-4 { grid-template-columns: 1fr; }
  .store-wide { flex-direction: column; text-align: center; }
  .store-wide-buy { text-align: center; }
}

/* ---------------------------------------------------------------
   STORE ARTWORK. The badge, the gem and the LR token are the actual
   in-game art for the thing being sold, so a buyer sees what they will
   be wearing or holding rather than a stock icon.

   All three are small pixel art (badges ~110x32, gems 32x32, the token
   64x64) shown larger than authored, so `pixelated` is mandatory: the
   browser's default smooth upscale turns them to mush, which is the
   same mistake that once made the in-game menus blurry.
   --------------------------------------------------------------- */
.store-badge,
.store-gem,
.store-lr {
  image-rendering: pixelated;
  display: block;
  /* THE STRETCH FIX. .store-card is `display:flex; flex-direction:column`,
     and align-items defaults to `stretch`, which stretches a cross-axis
     `auto` dimension to the full card width. With a fixed height that
     destroyed the aspect: VIP+ (112x46) was being drawn at the same width
     as VIP (112x31) and came out half again too wide. align-self keeps
     each badge at its own intrinsic proportion. */
  align-self: flex-start;
}
/* WIDTH-driven, not height-driven, and that is the whole point.

   The six badges are all 107 to 114 pixels wide but 31 to 46 tall,
   because VIP+ and MVP+ have wings and effects ABOVE the plate while the
   plate itself is the same size on every tier. Locking the HEIGHT (the
   obvious first move, and what this used to do) therefore shrank exactly
   those two: the badges came out anywhere from 112 to 166 wide and the
   plates visibly did not match.

   Locking the WIDTH instead makes every plate the same size, which is
   what the eye reads as "the same size", and lets the taller art simply
   reach further up out of its band. Height stays auto so nothing is
   stretched, which was the earlier bug. */
.store-badge {
  width: 150px;
  height: auto;
  margin-bottom: var(--space-3);
  filter: drop-shadow(0 2px 6px rgba(0,0,0,0.6));
}
.store-gem {
  width: 48px;
  height: 48px;
  margin-bottom: var(--space-2);
  filter: drop-shadow(0 0 10px rgba(120,190,255,0.35));
}
.store-lr {
  width: 96px;
  height: 96px;
  flex: none;
  filter: drop-shadow(0 0 16px rgba(248,176,40,0.45));
}
/* Small badge for the weekly table, where it labels a row rather than
   heading a card. align-self is reset because the table cell is a flex
   ROW: flex-start there would pin it to the top of the row instead of
   centring it against the tier name. */
.store-badge.is-inline {
  width: 92px;
  height: auto;
  margin-bottom: 0;
  align-self: center;
  flex: none;
}

/* ---------------------------------------------------------------
   STORE IDENTITY + CHECKOUT STATE

   Tebex will not accept a package into a basket until it knows which
   Minecraft account the purchase is for, and this store returns no auth
   links, so the name is asked for once here and cached. It sits above
   the catalogue rather than inside the checkout modal because a buyer
   who types it after choosing is a buyer who can get it wrong after
   they have already committed.
   --------------------------------------------------------------- */
.store-ident {
  padding: var(--space-4) var(--space-5);
  margin-bottom: var(--space-5);
}
.store-ident label {
  display: block;
  font-family: var(--font-display);
  font-size: var(--fs-eyebrow);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: var(--space-2);
}
.store-ident input {
  width: 100%;
  max-width: 320px;
  font-family: var(--font-body);
  font-size: var(--fs-body);
  font-weight: var(--fw-medium);
  color: var(--text);
  background: rgba(0,8,31,0.85);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-3);
}
.store-ident input:focus {
  outline: none;
  border-color: var(--gold-2);
  box-shadow: 0 0 0 3px rgba(221,153,35,0.18);
}
.store-ident input.is-error { border-color: var(--danger); }
.store-ident-note {
  color: var(--muted);
  font-size: var(--fs-sm);
  margin: var(--space-2) 0 0;
}
/* Hidden until it has something to say, so an empty line does not sit
   under the field reserving space for a message that may never come. */
.store-status { display: none; margin: var(--space-2) 0 0; font-size: var(--fs-sm); color: var(--gold-2); }
.store-status.is-shown { display: block; }
.store-status.is-error { color: #ff9aa2; }

/* The buy controls are real <button> elements now, not links, because they
   run a purchase flow rather than navigating. Strip the UA chrome so they
   inherit the button art exactly as the anchors did. */
button.btn {
  -webkit-appearance: none;
  appearance: none;
  font: inherit;
  font-family: var(--font-display);
}
button.btn:disabled { opacity: 0.6; cursor: progress; }
/* Compact variant for the one inside a table row. */
.btn-row { --btn-scale: 0.8; padding: 0 var(--space-3); }
