/* ==========================================================================
   theme.css — all styling for the storefront
   --------------------------------------------------------------------------
   Everything is driven by CSS custom properties, so the palette can be changed
   in one block (or per page, as the previews do) without touching a rule.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Design tokens
   -------------------------------------------------------------------------- */

:root {
  /* Colour — overridden per page by the page's own :root block.
     Oceanic palette: sea green as the primary, ocean blue as the accent,
     on a deep-water navy background. */
  --brand: #2ec8a6;
  --accent: #1f9ed6;
  --bg: #06121a;
  --surface: #0d2029;
  --surface-2: #16323c;
  --border: #24454f;
  --text: #e7f4f6;
  --text-muted: #8fb2bd;
  --success: #34d399;
  --danger: #f87171;

  /* Shape & motion */
  --radius: 14px;
  --radius-sm: 9px;
  --shadow: 0 18px 40px -22px rgb(0 0 0 / 0.85);
  --transition: 160ms ease;

  /* Layout */
  --max-width: 1240px;
  --gutter: clamp(16px, 3vw, 32px);
  --card-min: 240px;

  --font-body: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, "Noto Sans", sans-serif;
  --font-display: var(--font-body);

  /* Measured from the notice bar itself by theme.js, so the page reserve and
     the toast offset always match the real height. */
  --tebex-bar-height: 0px;
}

/* --------------------------------------------------------------------------
   2. Reset & base
   -------------------------------------------------------------------------- */

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

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* Subtle brand glow behind the header. Purely decorative. */
body::before {
  content: "";
  position: fixed;
  inset: -20% 0 auto 0;
  height: 480px;
  background: radial-gradient(
    ellipse at 50% 0%,
    color-mix(in srgb, var(--brand) 26%, transparent),
    transparent 70%
  );
  pointer-events: none;
  z-index: 0;
}

img,
svg,
iframe {
  max-width: 100%;
  display: block;
}

/* Elements toggled with the `hidden` attribute (badges, notices, empty states)
   must actually hide - several components set `display` themselves, and an
   author rule beats the user-agent rule that `hidden` relies on. */
[hidden] {
  display: none !important;
}

a {
  color: inherit;
  text-decoration: none;
}

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

h1,
h2,
h3,
h4 {
  font-family: var(--font-display);
  line-height: 1.2;
  margin: 0 0 0.5em;
  letter-spacing: -0.01em;
}

h1 { font-size: clamp(1.9rem, 4vw, 2.6rem); }
h2 { font-size: clamp(1.4rem, 2.6vw, 1.9rem); }
h3 { font-size: 1.15rem; }

p { margin: 0 0 1em; }

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

.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;
}

.skip-link {
  position: absolute;
  left: -999px;
  top: 0;
  z-index: 100;
  padding: 10px 16px;
  background: var(--brand);
  color: #fff;
  border-radius: 0 0 var(--radius-sm) 0;
}

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

/* --------------------------------------------------------------------------
   3. Layout shell
   -------------------------------------------------------------------------- */

.shell {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 300px;
  gap: var(--gutter);
  max-width: var(--max-width);
  margin: 0 auto;
  padding: var(--gutter);
  align-items: start;
}

.shell--full {
  grid-template-columns: minmax(0, 1fr);
}

.shell__main {
  min-width: 0;
}

.sidebar {
  position: sticky;
  top: calc(var(--gutter) + 76px);
  display: grid;
  gap: 16px;
}

@media (max-width: 1024px) {
  .shell {
    grid-template-columns: minmax(0, 1fr);
  }

  .sidebar {
    position: static;
  }
}

/* --------------------------------------------------------------------------
   4. Header
   -------------------------------------------------------------------------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(14px);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: 16px;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 14px var(--gutter);
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  font-size: 1.1rem;
  flex-shrink: 0;
}

.brand img {
  height: 34px;
  width: auto;
}

/* The supplied crest is a square PNG with the artwork's black background
   baked in (no alpha channel), so it is presented as a deliberate crest
   badge rather than a logo that appears to have a stray black box. */
.brand__crest {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: #000;
  overflow: hidden;
  box-shadow: 0 0 0 1px rgb(0 0 0 / 0.6), 0 6px 18px -10px var(--brand);
}

.brand__crest img {
  height: 100%;
  width: 100%;
  object-fit: contain;
  border-radius: 9px;
}

.brand__mark {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--brand), var(--accent));
  color: #08090c;
  font-weight: 800;
}

.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-left: auto;
}

.site-nav a {
  padding: 8px 12px;
  border-radius: 999px;
  color: var(--text-muted);
  font-weight: 500;
  font-size: 0.94rem;
  transition: background var(--transition), color var(--transition);
}

.site-nav a:hover,
.site-nav a[aria-current="page"] {
  background: var(--surface-2);
  color: var(--text);
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: 8px;
}

/* Signed-in label plus its log-out control, kept on the header baseline. */
.account {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* Scoped so the generic .btn display rule cannot win on specificity order. */
.site-header .nav-toggle {
  display: none;
  margin-left: auto;
}

@media (max-width: 860px) {
  .site-header .nav-toggle {
    display: inline-flex;
  }

  .site-nav {
    position: absolute;
    left: var(--gutter);
    right: var(--gutter);
    top: calc(100% + 8px);
    flex-direction: column;
    margin: 0;
    padding: 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow);
    display: none;
  }

  .site-nav.is-open {
    display: flex;
  }

  .site-nav a {
    border-radius: var(--radius-sm);
  }

  .header-actions .label {
    display: none;
  }
}

/* Realm switcher — every category is a realm, so switching between them
   should never require going back to the homepage. */
.realm-switcher {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 18px;
}

.realm-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-muted);
  font-weight: 600;
  font-size: 0.94rem;
  transition: border-color var(--transition), color var(--transition),
    background var(--transition);
}

.realm-chip:hover {
  color: var(--text);
  border-color: color-mix(in srgb, var(--brand) 60%, var(--border));
}

.realm-chip[aria-current="page"] {
  border-color: transparent;
  background: linear-gradient(135deg, var(--brand), var(--accent));
  color: #08090d;
}

.realm-chip__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.75;
}

/* Second row of the switcher: the Gold / Tags groups of the realm being
   viewed. Deliberately lighter than the realm row above it, because it is a
   step deeper into the same realm. */
.realm-switcher--groups {
  margin-top: -10px;
}

.realm-chip--group,
.realm-chip--back {
  padding: 7px 14px;
  font-size: 0.88rem;
  background: transparent;
}

.realm-chip--back {
  border-style: dashed;
  font-weight: 500;
}

.realm-chip--back:hover {
  border-style: solid;
}

/* --------------------------------------------------------------------------
   5. Buttons & form controls
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 18px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  transition: transform var(--transition), filter var(--transition),
    background var(--transition), border-color var(--transition);
}

.btn:hover:not(:disabled) {
  transform: translateY(-1px);
  filter: brightness(1.08);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn--primary {
  background: linear-gradient(135deg, var(--brand), var(--accent));
  color: #08090c;
}

.btn--ghost {
  background: transparent;
  border-color: var(--border);
  color: var(--text-muted);
}

.btn--ghost:hover:not(:disabled) {
  color: var(--text);
  border-color: var(--brand);
}

/* Destructive actions read as quiet grey until you reach for them, then red. */
.btn--danger {
  background: transparent;
  border-color: color-mix(in srgb, var(--danger) 45%, var(--border));
  color: var(--danger);
}

.btn--danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--danger) 16%, transparent);
  border-color: var(--danger);
  color: var(--danger);
}

.btn--block {
  width: 100%;
}

.btn--sm {
  padding: 7px 13px;
  font-size: 0.9rem;
}

.btn--icon {
  position: relative;
  padding: 9px 12px;
}

.basket-count {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 20px;
  height: 20px;
  padding: 0 5px;
  border-radius: 999px;
  background: var(--brand);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 700;
  display: grid;
  place-items: center;
}

.field {
  display: grid;
  gap: 6px;
  margin-bottom: 16px;
}

.field > label,
.field > .field__label {
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.field__hint {
  font-size: 0.85rem;
  color: var(--text-muted);
}

input[type="text"],
input[type="number"],
input[type="email"],
input[type="url"],
select,
textarea {
  width: 100%;
  padding: 11px 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  transition: border-color var(--transition), box-shadow var(--transition);
}

input::placeholder,
textarea::placeholder {
  color: color-mix(in srgb, var(--text-muted) 80%, transparent);
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--brand);
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 28%, transparent);
}

/* --------------------------------------------------------------------------
   6. Cards, hero, packages
   -------------------------------------------------------------------------- */

.card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
  overflow: hidden;
}

.card__body {
  padding: 18px;
}

.hero {
  display: grid;
  gap: 18px;
  padding: clamp(24px, 5vw, 44px);
  margin-bottom: var(--gutter);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background:
    linear-gradient(135deg,
      color-mix(in srgb, var(--brand) 20%, var(--surface)) 0%,
      var(--surface) 55%),
    var(--surface);
}

.hero__eyebrow {
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--accent);
}

.hero__content {
  max-width: 62ch;
  color: var(--text-muted);
}

.hero__content > :last-child {
  margin-bottom: 0;
}

/* Oversized version of the crest for the homepage hero. */
.hero__crest {
  display: grid;
  place-items: center;
  width: clamp(120px, 18vw, 176px);
  height: clamp(120px, 18vw, 176px);
  padding: 6px;
  border: 1px solid color-mix(in srgb, var(--brand) 35%, var(--border));
  border-radius: 26px;
  background: #000;
  overflow: hidden;
  box-shadow:
    0 0 0 1px rgb(0 0 0 / 0.6),
    0 30px 70px -40px var(--brand),
    0 0 90px -50px var(--accent);
}

.hero__crest img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  border-radius: 20px;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

/* Homepage hero: copy on the left, crest on the right. */
.hero--brand {
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(20px, 4vw, 44px);
}

/* The headline IS the welcome message, so it carries the hero on its own. */
.hero--brand h1 {
  font-size: clamp(2.1rem, 5.2vw, 3.4rem);
  margin-bottom: 0;
}

.hero--brand .hero__actions {
  margin-top: 24px;
}

/* Log in: the in-game name field and the button sit together as one clear
   call to action rather than hiding behind a link. */
.login-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  max-width: 580px;
  margin-top: 24px;
  padding: 16px;
  border: 1px solid color-mix(in srgb, var(--brand) 30%, var(--border));
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--surface) 82%, transparent);
}

.login-form .field {
  flex: 1 1 240px;
  margin: 0;
}

.login-form .btn {
  flex: 0 0 auto;
  white-space: nowrap;
}

.login-status {
  margin-top: 24px;
  font-size: 1.02rem;
}

@media (max-width: 760px) {
  .hero--brand {
    grid-template-columns: minmax(0, 1fr);
  }

  .hero--brand .hero__crest {
    order: -1;
  }
}

.section-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 14px;
}

.section-head p {
  margin: 0;
  color: var(--text-muted);
}

.package-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--card-min), 1fr));
  gap: 18px;
}

/* List display type on category.html */
.package-grid--list {
  grid-template-columns: 1fr;
}

.package-grid--list .package-card {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr);
}

.package-grid--list .package-card__media {
  height: 100%;
  aspect-ratio: auto;
}

@media (max-width: 640px) {
  .package-grid--list .package-card {
    grid-template-columns: 1fr;
  }
}

.package-card {
  position: relative; /* anchors the badges and the title's stretched link */
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
  transition: transform var(--transition), border-color var(--transition),
    box-shadow var(--transition);
}

.package-card:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--brand) 55%, var(--border));
  box-shadow: var(--shadow);
}

.package-card__media {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  background: var(--surface-2);
  overflow: hidden;
}

.package-card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 320ms ease;
}

.package-card:hover .package-card__media img {
  transform: scale(1.04);
}

/* Packages without an uploaded image still need to look deliberate, so the
   empty state becomes a branded tile showing the package amount. */
.package-card__media--empty {
  display: grid;
  place-items: center;
  background:
    radial-gradient(120% 120% at 50% 0%,
      color-mix(in srgb, var(--brand) 28%, transparent),
      transparent 72%),
    linear-gradient(160deg, var(--surface-2), var(--surface));
  color: var(--text-muted);
  font-size: 0.85rem;
}

.package-card__media--empty span {
  padding: 0 14px;
  text-align: center;
  font-size: clamp(1.15rem, 3vw, 1.75rem);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--text);
  overflow-wrap: anywhere;
}

.package-card__body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  flex: 1;
  padding: 16px;
}

.package-card__title {
  margin: 0;
  font-size: 1.05rem;
}

/* Realm cards carry no badge, so their name is the visual anchor. */
.realm-card__title {
  font-size: 1.4rem;
  margin-bottom: 8px;
}

.package-card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
}

/* The title's stretched link makes the whole card clickable, so anything else
   clickable inside the card has to sit above it - otherwise "Add" and the
   basket count take the click meant for the link, and vice versa. */
.package-card__media,
.package-card__badges,
.package-card__footer {
  position: relative;
  z-index: 1;
}

.package-card__excerpt {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.92rem;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.package-card__footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: auto;
  padding-top: 6px;
}

.price {
  font-weight: 700;
  font-size: 1.05rem;
}

.price__currency {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-muted);
  margin-left: 4px;
}

.price--was {
  font-weight: 500;
  font-size: 0.9rem;
  color: var(--text-muted);
  text-decoration: line-through;
  margin-right: 6px;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  background: var(--surface-2);
  color: var(--text-muted);
}

/* Only card and module bodies are flex columns, where a badge would otherwise
   stretch to the full width. Badges in the header sit in a flex row and must
   stay centred - an unscoped align-self: flex-start pinned them to the top. */
.package-card__body > .badge,
.module__body > .badge {
  align-self: flex-start;
}

.badge--discount {
  background: color-mix(in srgb, var(--success) 22%, transparent);
  color: var(--success);
}

.badge--featured {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: var(--accent);
}

/* Bonus Gold packages — the bonus is parsed out of the package name. */
.badge--bonus {
  background: color-mix(in srgb, var(--brand) 20%, transparent);
  color: var(--brand);
}

.badge--sold {
  background: color-mix(in srgb, var(--danger) 22%, transparent);
  color: var(--danger);
}

.package-card__badges {
  position: absolute;
  top: 10px;
  left: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  z-index: 2;
}

/* --------------------------------------------------------------------------
   7. Package detail page
   -------------------------------------------------------------------------- */

.package-detail {
  display: grid;
  gap: var(--gutter);
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  align-items: start;
}

/* The gallery spans both rows so the buy box sits under the description in
   the second column, instead of being pushed onto a new row on the left. */
.package-detail > .gallery {
  grid-column: 1;
  grid-row: 1 / span 2;
}

.package-detail > .purchase {
  grid-column: 2;
  grid-row: 2;
}

@media (max-width: 900px) {
  .package-detail {
    grid-template-columns: minmax(0, 1fr);
  }

  .package-detail > .gallery,
  .package-detail > .purchase {
    grid-column: auto;
    grid-row: auto;
  }
}

.gallery {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

.gallery__viewport {
  position: relative;
  aspect-ratio: 16 / 9;
  background: var(--surface-2);
}

.gallery__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 240ms ease;
  pointer-events: none;
}

.gallery__slide.is-active {
  opacity: 1;
  pointer-events: auto;
}

.gallery__slide img,
.gallery__slide iframe {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
}

.gallery__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: color-mix(in srgb, var(--bg) 75%, transparent);
  color: var(--text);
  cursor: pointer;
}

.gallery__nav--prev { left: 12px; }
.gallery__nav--next { right: 12px; }

.gallery__thumbs {
  display: flex;
  gap: 8px;
  padding: 10px;
  overflow-x: auto;
}

.gallery__thumb {
  flex: 0 0 auto;
  width: 74px;
  height: 48px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  overflow: hidden;
  cursor: pointer;
}

.gallery__thumb[aria-current="true"] {
  border-color: var(--brand);
}

.gallery__thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.purchase {
  display: grid;
  gap: 16px;
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.purchase__price {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.purchase__price .price {
  font-size: 1.7rem;
}

.qty {
  display: flex;
  align-items: center;
  gap: 10px;
}

.qty__step {
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  font-size: 1.1rem;
  cursor: pointer;
}

.qty input {
  width: 64px;
  text-align: center;
  -moz-appearance: textfield;
}

/* The prototype basket renders the quantity as text beside the same +/- steps. */
.qty__value {
  min-width: 34px;
  text-align: center;
  font-weight: 700;
}

/* Steppers sit in table rows, where the standalone 36px size is too heavy. */
.table .qty {
  gap: 6px;
}

.table .qty__step {
  width: 30px;
  height: 30px;
  font-size: 1rem;
}

.prose {
  color: var(--text-muted);
}

.prose h2,
.prose h3,
.prose strong {
  color: var(--text);
}

.prose ul,
.prose ol {
  padding-left: 1.2em;
}

.prose img {
  border-radius: var(--radius-sm);
}

.notice {
  display: flex;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text-muted);
  font-size: 0.92rem;
}

/* --------------------------------------------------------------------------
   8. Sidebar modules
   -------------------------------------------------------------------------- */

.module {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  overflow: hidden;
}

.module__head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--border);
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.module__body {
  padding: 16px;
}

.module__body > :last-child {
  margin-bottom: 0;
}

.progress {
  height: 8px;
  border-radius: 999px;
  background: var(--surface-2);
  overflow: hidden;
}

.progress__bar {
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--brand), var(--accent));
  transition: width 600ms ease;
}

.progress__meta {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  margin-top: 8px;
  font-size: 0.86rem;
  color: var(--text-muted);
}

.server-status {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
}

.dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--danger);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--danger) 22%, transparent);
}

.dot--online {
  background: var(--success);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--success) 22%, transparent);
}

.donator {
  display: flex;
  align-items: center;
  gap: 12px;
}

.donator img {
  width: 46px;
  height: 46px;
  border-radius: 10px;
  background: var(--surface-2);
}

.payments {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 10px;
  font-size: 0.9rem;
}

.payments li {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  padding-bottom: 10px;
  border-bottom: 1px dashed var(--border);
}

.payments li:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.muted {
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   8b. Basket: line table, promo code, totals
   -------------------------------------------------------------------------- */

.table {
  width: 100%;
  border-collapse: collapse;
}

.table th,
.table td {
  padding: 12px 10px;
  text-align: left;
  vertical-align: middle;
  border-bottom: 1px solid var(--border);
}

.table th {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.table tbody tr:last-child th,
.table tbody tr:last-child td {
  border-bottom: 0;
}

.table__num {
  text-align: right;
  white-space: nowrap;
}

.table__item {
  display: flex;
  align-items: center;
  gap: 10px;
}

.table__thumb {
  flex: 0 0 auto;
  width: 58px;
  height: 34px;
  object-fit: contain;
  border-radius: 6px;
  background: var(--surface-2);
}

/* Wide basket tables scroll rather than squashing on small screens. */
.table-scroll {
  overflow-x: auto;
}

.table__remove {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--text-muted);
  cursor: pointer;
  white-space: nowrap;
}

input[type="checkbox"] {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--brand);
}

@media (max-width: 640px) {
  /* A five column basket table cannot shrink onto a phone, so each row
     becomes its own block: package and line price on top, realm beneath it,
     then the quantity stepper with the remove control opposite. */
  .table thead {
    display: none;
  }

  .table,
  .table tbody,
  .table tr,
  .table td {
    display: block;
  }

  .table tr {
    position: relative;
    padding: 14px 0;
    border-bottom: 1px solid var(--border);
  }

  .table tbody tr:last-child {
    border-bottom: 0;
  }

  .table td {
    padding: 0;
    border: 0;
  }

  .table td:nth-child(2) {
    margin-top: 4px;
    font-size: 0.92rem;
    color: var(--text-muted);
  }

  .table td:nth-child(3) {
    margin-top: 10px;
  }

  /* Line price and the remove control float to the right of the block. */
  .table td:nth-child(4) {
    position: absolute;
    top: 14px;
    right: 0;
  }

  .table td:nth-child(5) {
    position: absolute;
    bottom: 14px;
    right: 0;
  }

  .table .qty__step {
    width: 28px;
    height: 28px;
    font-size: 0.95rem;
  }

  .table__thumb {
    display: none;
  }
}

.promo__row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.promo__row input {
  flex: 1 1 auto;
}

.applied {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 14px;
}

.applied__chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 5px 11px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--success) 18%, transparent);
  color: var(--success);
  font-size: 0.82rem;
  font-weight: 700;
}

.applied__chip button {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font-size: 1rem;
  line-height: 1;
  cursor: pointer;
}

/* The live theme wraps the chip's remove button in a small POST form. */
.applied__chip form {
  display: inline-flex;
  margin: 0;
}

.totals {
  margin: 0 0 16px;
}

.totals > div {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 9px 0;
  border-bottom: 1px dashed var(--border);
}

.totals > div:last-child {
  border-bottom: 0;
}

.totals dt {
  color: var(--text-muted);
}

.totals dd {
  margin: 0;
  font-weight: 600;
}

.totals__discount dd {
  color: var(--success);
}

.totals__grand dt,
.totals__grand dd {
  font-size: 1.15rem;
  font-weight: 700;
}

.totals__grand dt {
  color: var(--text);
}

/* --------------------------------------------------------------------------
   9. Footer & toast
   -------------------------------------------------------------------------- */

.site-footer {
  position: relative;
  z-index: 1;
  margin-top: calc(var(--gutter) * 2);
  border-top: 1px solid var(--border);
  background: var(--surface);
}

.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 22px var(--gutter);
  color: var(--text-muted);
  font-size: 0.9rem;
}

.site-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
}

.toast {
  position: fixed;
  right: var(--gutter);
  bottom: var(--gutter);
  z-index: 60;
  max-width: min(380px, calc(100vw - 2 * var(--gutter)));
  padding: 13px 16px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  color: var(--text);
  box-shadow: var(--shadow);
  /* The message and its dismiss button belong on one line. */
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}

.toast span {
  min-width: 0;
}

.toast--success { border-left-color: var(--success); }
.toast--error { border-left-color: var(--danger); }

.auth-card {
  max-width: 460px;
  margin: clamp(40px, 9vh, 96px) auto;
  padding: clamp(22px, 4vw, 34px);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.provider-list {
  display: grid;
  gap: 10px;
  margin: 0 0 18px;
  padding: 0;
  list-style: none;
}

/* --------------------------------------------------------------------------
   9b. Tebex notice bar
   -------------------------------------------------------------------------- */

/* Merchant-of-record disclosure, pinned to the bottom of every page. It is
   dismissible, but only for the browser session (see theme.js) so the store
   never silently loses the statement. */
.tebex-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 70;
  border-top: 1px solid color-mix(in srgb, var(--brand) 40%, var(--border));
  background: color-mix(in srgb, var(--surface-2) 96%, transparent);
  backdrop-filter: blur(12px);
  box-shadow: 0 -18px 40px -26px rgb(0 0 0 / 0.9);
}

.tebex-bar__inner {
  display: flex;
  align-items: center;
  gap: 14px;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 12px var(--gutter);
}

.tebex-bar p {
  flex: 1 1 auto;
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--text-muted);
}

.tebex-bar strong {
  color: var(--text);
}

.tebex-bar__close {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--text-muted);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  transition: color var(--transition), border-color var(--transition);
}

.tebex-bar__close:hover {
  color: var(--text);
  border-color: var(--brand);
}

/* Dismissed for this session: the bar goes and its reserved space with it.
   The attribute is set before first paint by the page's own inline script, so reloading a page
   never flashes the notice back into view. */
[data-tebex-dismissed] .tebex-bar {
  display: none;
}

html:not([data-tebex-dismissed]) body {
  padding-bottom: calc(var(--tebex-bar-height) + 10px);
}

html:not([data-tebex-dismissed]) .toast {
  bottom: calc(var(--tebex-bar-height) + var(--gutter));
}

/* --------------------------------------------------------------------------
   9c. Cookie consent
   -------------------------------------------------------------------------- */

.consent {
  position: fixed;
  inset: 0;
  z-index: 90; /* above the notice bar (70) and toasts (60) */
  display: grid;
  place-items: center;
  padding: var(--gutter);
}

.consent__backdrop {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(3px);
}

.consent__card {
  position: relative;
  width: min(560px, 100%);
  max-height: calc(100vh - 2 * var(--gutter));
  overflow-y: auto;
  padding: clamp(20px, 3vw, 28px);
  border: 1px solid color-mix(in srgb, var(--brand) 32%, var(--border));
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow);
}

.consent__title {
  margin-bottom: 10px;
  font-size: 1.3rem;
}

.consent__text {
  color: var(--text-muted);
  font-size: 0.94rem;
}

.consent__text p:last-child {
  margin-bottom: 0;
}

.consent__text a {
  color: var(--brand);
  text-decoration: underline;
}

/* Accept and reject carry the same weight on purpose: a consent dialog must
   never make saying no the harder option, so the two controls are also the
   same size rather than sized by their label. */
.consent__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 18px 0 0;
}

.consent__actions .btn {
  flex: 1 1 0;
}

.consent__note {
  margin: 14px 0 0;
  font-size: 0.82rem;
  color: var(--text-muted);
}

.consent__note a {
  color: inherit;
  text-decoration: underline;
}

@media (max-width: 520px) {
  .consent {
    place-items: end center;
    padding: 0;
  }

  .consent__card {
    width: 100%;
    max-height: 80vh;
    border-radius: var(--radius) var(--radius) 0 0;
  }

  .consent__actions .btn {
    flex: 1 1 100%;
  }
}

/* A button that has to sit in a row of links - the footer's cookie settings. */
.linkish {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.linkish:hover {
  color: var(--brand);
}

/* --------------------------------------------------------------------------
   10. Accessibility preferences
   -------------------------------------------------------------------------- */

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

  .package-card:hover,
  .btn:hover:not(:disabled) {
    transform: none;
  }
}

/* Light mode: set the config "Colour scheme" option to "light" and the
   palette below is applied via data-theme on <html>. */
[data-theme="light"] {
  --bg: #f1f8f9;
  --surface: #ffffff;
  --surface-2: #e5f1f3;
  --border: #d1e3e7;
  --text: #082026;
  --text-muted: #4a6b74;
  --shadow: 0 16px 34px -26px rgb(8 32 38 / 0.5);
}

/* --------------------------------------------------------------------------
   11. Motion
   --------------------------------------------------------------------------
   Movement only: opacity, translate, scale and transform. No size, spacing or
   colour changes here, so the design is exactly as it was - it just arrives
   smoothly and answers the pointer.

   Entrances animate the independent `translate` property rather than
   `transform`, because the card hover already owns `transform` (the 3px lift
   and the image zoom). Two rules fighting over one property is how animation
   starts looking janky, and this way they cannot.
   -------------------------------------------------------------------------- */

@keyframes rise-in {
  from { translate: 0 10px; }
  to   { translate: 0 0; }
}

/* Nothing animates from fully transparent. A paused animation holds its first
   keyframe, so `from { opacity: 0 }` is a blank page on a browser that is not
   running animations - a background tab, a phone in battery saver. Movement
   carries the entrance instead, and this decorative fade stops at 35%. */
@keyframes fade-in {
  from { opacity: 0.35; }
  to   { opacity: 1; }
}

@keyframes dialog-in {
  from { opacity: 0.35; translate: 0 8px; scale: 0.99; }
  to   { opacity: 1;    translate: 0 0;   scale: 1; }
}

@keyframes toast-in {
  from { translate: 0 8px; scale: 0.98; }
  to   { translate: 0 0;   scale: 1; }
}

@keyframes bar-in {
  from { translate: 0 100%; }
  to   { translate: 0 0; }
}

html {
  scroll-behavior: smooth;
}

/* Cards, chips, panels and table rows rise in. No fill-mode and no animation
   delay on purpose: if a browser ever declines to run an animation - a
   background tab, a throttled phone - the content is simply there rather than
   hidden. Motion is a bonus, never a prerequisite for reading the store. */
.package-grid > *,
.table tbody tr,
.realm-switcher .realm-chip,
.module,
.card,
.purchase,
.gallery,
.section-head {
  animation: rise-in 380ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.hero {
  animation: rise-in 460ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Feedback on press. transform only, so the hover states keep their say. */
.btn:active:not(:disabled) {
  transform: translateY(0) scale(0.985);
}

.realm-chip {
  transition: border-color var(--transition), color var(--transition),
    background var(--transition), transform var(--transition);
}

.realm-chip:hover {
  transform: translateY(-1px);
}

/* Overlays come in rather than appearing - same rule, never from zero. */
.toast {
  animation: toast-in 260ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.consent:not([hidden]) {
  animation: dialog-in 240ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.consent__backdrop {
  animation: fade-in 240ms ease;
}

.tebex-bar {
  animation: bar-in 320ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* --------------------------------------------------------------------------
   12. Loading, and changing pages
   --------------------------------------------------------------------------
   Every page ships the design's markup, so a visitor never meets a blank
   screen - but the parts the API fills in would flash their example content
   before the real values arrive. An inline script in each page's head marks the
   document as loading before the first paint, and the renderer takes the mark
   away once it has drawn. If that never happens - a failed request, scripting
   off - the inline timer removes it anyway, so nothing can stay hidden.

   `visibility` rather than `opacity`: the box keeps its size, so revealing the
   content does not shift the page, and nothing here can leave text unreadable.
   -------------------------------------------------------------------------- */

html[data-loading] .package-grid,
html[data-loading] .package-detail,
html[data-loading] .realm-switcher,
html[data-loading] .table,
html[data-loading] [data-basket-page] {
  visibility: hidden;
}

@keyframes page-in {
  from { opacity: 0.4; translate: 0 6px; }
  to   { opacity: 1;   translate: 0 0; }
}

/* The page eases in, and eases out again before the next one loads, so a click
   reads as one movement instead of two hard cuts. */
.shell__main {
  animation: page-in 220ms ease-out;
}

html[data-leaving] .shell__main,
html[data-leaving] .site-header,
html[data-leaving] .site-footer {
  opacity: 0.4;
  translate: 0 -6px;
  transition: opacity 150ms ease, translate 150ms ease;
}

/* Where the browser can do the same job itself, it does: the page that is
   leaving is photographed, the next one is built underneath, and the two
   dissolve into each other. Only the content is swapped, so the header, the
   footer and the notices are identical on both sides of the change and simply
   stay where they are - nothing can flash, because nothing is rebuilt.

   The fade above is kept for the browsers, and the file:// opens, where the
   page really is reloaded. */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 240ms;
  animation-timing-function: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Section 10's reduced-motion rules do not reach these: the browser's own
   transition pseudo-elements are not matched by a universal selector. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation-duration: 0.01ms;
  }
}
