/* ═══════════════════════════════════════════════════════════════════
   THE CINEMATIC LAYER

   Every public page already opened with a roomPlaque, and the good ones
   already carried a painting behind the title. What they did not have was
   a MOMENT — the art sat at 28% opacity behind ~150px of padding, flat and
   still, and then the page started. This turns that opener into something
   you arrive at.

   Four parts, in the order they hit you:
     1. a slow drift on the backdrop, so the page is breathing when you land
     2. an ember under-glow and a vignette, so the art has depth instead of
        being a flat wash
     3. drifting sparks over the whole page — the guild's answer to the
        starfield behind a good sci-fi page
     4. chapter cards for long-form pages, so a wall of guide text reads as
        a sequence of beats instead of one endless scroll

   ALL of it is off under prefers-reduced-motion. Nothing here uses an
   inline style or an inline script — production CSP is style-src 'self'
   with no unsafe-inline, so both would be blocked outright.
   ═══════════════════════════════════════════════════════════════════ */

/* ── 1. THE OPENER ─────────────────────────────────────────────────
   Taller and bottom-aligned, so the title sits ON the art rather than
   above it. The art plaque keeps its existing ::before (which inherits the
   pgart-- background image); this changes its scale and adds a second
   layer over it for depth. */
.room-plaque--art {
  display: grid;
  align-content: end;
  min-height: clamp(200px, 30vh, 340px);
  padding-top: clamp(2.4rem, 8vh, 5rem);
  padding-bottom: clamp(1.2rem, 2.5vh, 2rem);
  /* NO overflow:hidden here. The first version had it, to contain a
     transform:scale() drift, and it quietly broke the thing it sat on
     top of: ::before is inset by NEGATIVE margins so the painting bleeds
     past the content column to the full width of the page, and
     overflow:hidden clipped it straight back to the column. Measured at
     1600px: the art painted 81 -> 1593 and was being cut to 153 -> 1521,
     leaving a black bar down each side and a hard vertical edge on the
     art. It looked like the image had failed to load. */
}
.room-plaque--art::before {
  /* Was a static wash at 0.28. A 28s drift is slow enough that you never
     catch it moving, but the page is not frozen when you arrive.
     The drift moves the BACKGROUND, not the element. A transform would
     grow the box past the bleed and need clipping to contain it, and
     clipping is what broke the bleed in the first place. Panning
     background-position under `background-size: cover` cannot overflow
     anything, so nothing needs containing. */
  opacity: 0.42;
  background-position: center 42%;
  animation: cine-drift 28s ease-in-out infinite alternate;
}
/* The depth pass: warm light rising from the bottom edge, darkness pulled
   into the top corners, and a scrim so the title always has something
   solid to sit on no matter how busy the painting behind it is. */
.room-plaque--art::after {
  content: '';
  position: absolute;
  /* --bleed, not 50vw. keep.css sets it to the viewport MINUS the left
     rail on desktop, and ::before is already inset that way; using 50vw
     here would run this layer under the rail and misalign the two. */
  inset: 0 calc(50% - var(--bleed, 100vw) / 2);
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(120% 78% at 50% 118%, rgba(232, 123, 30, 0.30), transparent 62%),
    radial-gradient(90% 60% at 50% 0%, rgba(0, 0, 0, 0.55), transparent 70%),
    linear-gradient(to bottom, transparent 30%, rgba(8, 8, 9, 0.86) 92%, var(--bg, #080809) 100%);
}
/* A page with no painting still gets the ember glow, so "no art chosen"
   reads as a deliberate dark opener rather than an unfinished one. */
.room-plaque:not(.room-plaque--art) {
  padding-top: clamp(2rem, 5vh, 3.4rem);
}
.room-plaque:not(.room-plaque--art)::after {
  content: '';
  position: absolute;
  inset: 0 calc(50% - var(--bleed, 100vw) / 2);
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(70% 130% at 50% 130%, rgba(232, 123, 30, 0.16), transparent 68%);
}
.room-plaque--art .room-plaque__name {
  font-size: clamp(2rem, 5.6vw, 3.6rem);
  line-height: 1.04;
  text-shadow: 0 3px 22px rgba(0, 0, 0, 0.85), 0 1px 3px rgba(0, 0, 0, 0.9);
}
.room-plaque--art .room-plaque__plain {
  max-width: 62ch;
  color: #c3b49a;
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.9);
}
/* The quiet index marker above a big title, borrowed from how a good game
   manual opens a section: a short rule, then a letterspaced label. It says
   where you are before the title says what this is. */
.room-plaque__eyebrow {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.5rem;
  font-family: var(--font-head, 'Cinzel', serif);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--gold, #c9a84c);
}
.room-plaque__eyebrow::before {
  content: '';
  width: clamp(18px, 4vw, 34px);
  height: 1px;
  background: linear-gradient(to right, var(--fire, #e87b1e), transparent);
  flex: none;
}
/* The rule under the title — what actually makes a heading read as a title
   card rather than an h1. */
.room-plaque__rule {
  display: block;
  width: 68px;
  height: 2px;
  margin: 0.85rem 0 0;
  border: 0;
  background: linear-gradient(to right, var(--fire, #e87b1e), var(--gold, #c9a84c) 60%, transparent);
}
.room-plaque--center .room-plaque__eyebrow { justify-content: center; }
.room-plaque--center .room-plaque__rule { margin-left: auto; margin-right: auto; }

@keyframes cine-drift {
  from { background-position: 46% 38%; }
  to   { background-position: 54% 50%; }
}

/* ── 2. AMBIENT EMBERS, EVERYWHERE ─────────────────────────────────
   The first draft of this section grew its own twelve-spark starfield.
   That was wasted work: .embers-css in v4.css already does it, and does
   it better — 24 spans on golden-ratio columns with pre-warmed loops so
   the field looks settled the instant it paints, a three.js upgrade on
   t-high, and a t-low rule that strips it on cheap phones.

   The real problem was never that the effect did not exist. Measured
   across eight live pages, the ember field appeared on exactly one:

     /            embers 2   hero 8
     /world       embers 0   hero 0
     /raids       embers 0   hero 0
     /about       embers 0   hero 0
     /community   embers 0   hero 0
     /events      embers 0   hero 0   (and no room plaque either)
     /team        embers 0   hero 0
     /merch       embers 0   hero 0

   The whole cinematic system was built for the front door and never
   carried past it. So this adds one modifier — fixed instead of absolute,
   so the field sits behind the page and stays put while you scroll — and
   the layout mounts it on every room. */
.embers-css--room {
  position: fixed;
  z-index: 0;
  /* Behind content but in front of the page background. The content
     wrapper is lifted to z-index 1 to sit over it. */
}
/* Thinner than the homepage hero's: this one is behind ALL the text on
   the page, not behind a single hero image, so it has to stay a hint. */
.embers-css--room i { opacity: 0; }
.embers-css--room i { animation-name: ember-rise-room; }
@keyframes ember-rise-room {
  0%   { transform: translate(0, 0) scale(1); opacity: 0; }
  6%   { opacity: 0.42; }
  55%  { opacity: 0.26; }
  100% { transform: translate(var(--drift, 2vw), -108vh) scale(0.35); opacity: 0; }
}
/* The page content has to win over the fixed field behind it. */
.keep-main { position: relative; z-index: 1; }

/* ── 3. CHAPTER CARDS ──────────────────────────────────────────────
   For pages that are genuinely long-form — a guide, a rulebook, a manual.
   A numbered card per chapter lets you see the shape of the thing before
   you read a word of it. */
.chapter {
  position: relative;
  margin: 0 0 clamp(1rem, 2.4vh, 1.8rem);
  padding: clamp(1.3rem, 3vw, 2.2rem);
  border: 1px solid var(--border, rgba(201, 168, 76, 0.18));
  border-radius: 12px;
  background:
    linear-gradient(180deg, rgba(232, 123, 30, 0.045), transparent 42%),
    var(--bg-2, #0f0f12);
  scroll-margin-top: calc(var(--header-h, 68px) + 1.5rem);
}
/* A hairline of ember down the left edge — the cheapest way to make a card
   read as one of a sequence rather than a lone box. */
.chapter::before {
  content: '';
  position: absolute;
  left: -1px; top: 18%; bottom: 18%;
  width: 2px;
  border-radius: 2px;
  background: linear-gradient(to bottom, transparent, var(--fire, #e87b1e) 30%, var(--gold, #c9a84c) 70%, transparent);
  opacity: 0.75;
}
.chapter__eyebrow {
  margin: 0 0 0.3rem;
  font-family: var(--font-head, 'Cinzel', serif);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--gold, #c9a84c);
  opacity: 0.85;
}
.chapter__title {
  margin: 0 0 1.1rem;
  font-family: var(--font-head, 'Cinzel', serif);
  font-size: clamp(1.3rem, 3vw, 1.85rem);
  font-weight: 800;
  letter-spacing: 0.02em;
  color: var(--text-bright, #f0eadb);
}
.chapter h3 {
  margin: 1.6rem 0 0.5rem;
  font-family: var(--font-head, 'Cinzel', serif);
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--gold-bright, #f0d080);
}
.chapter h3:first-of-type { margin-top: 0; }
.chapter p { margin: 0 0 0.75rem; line-height: 1.72; color: var(--text, #d8d1c2); }
.chapter p:last-child { margin-bottom: 0; }
.chapter ul { margin: 0 0 0.85rem; padding-left: 1.15rem; }
.chapter li { margin: 0 0 0.4rem; line-height: 1.66; color: var(--text, #d8d1c2); }
.chapter a { color: var(--gold-bright, #f0d080); }
/* A command name. Should read as something you TYPE, not something you
   read — hence the mono face and the keycap treatment. */
.cmd {
  font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace;
  font-size: 0.92em;
  font-weight: 600;
  color: var(--gold-bright, #f0d080);
  background: rgba(201, 168, 76, 0.1);
  border: 1px solid rgba(201, 168, 76, 0.24);
  border-radius: 5px;
  padding: 0.08em 0.42em;
  white-space: nowrap;
}
/* The pull-out line every good manual has. */
.chapter__note {
  margin: 1rem 0 0;
  padding: 0.7rem 0.9rem;
  border: 1px solid rgba(232, 123, 30, 0.34);
  border-left-width: 3px;
  border-radius: 6px;
  background: rgba(232, 123, 30, 0.07);
  font-size: 0.94rem;
  color: #f0dfc0;
}
.chapter__note b { color: var(--gold-bright, #f0d080); }

/* ── 4. MOTION OFF ─────────────────────────────────────────────────
   One block covering everything above. The sparks are REMOVED rather than
   merely paused: a dozen frozen dots is worse than none at all. */
@media (prefers-reduced-motion: reduce) {
  .room-plaque--art::before { animation: none; background-position: center 42%; }
  .embers-css--room { display: none; }
}
/* fx-core's own tier check strips the homepage embers on weak devices; the
   room field has to honour the same verdict or the cheapest phones get the
   effect the tiering exists to spare them. */
html.t-low .embers-css--room { display: none; }

/* ── 5. THE OTHER THREE HEADERS ───────────────────────────────────
   Every page in the app goes through the same layout(), so the ember
   field and this stylesheet already reach all 105 of them. What differs
   is the header, and there are four kinds:

     .room-plaque                  the 68 public pages      (section 1)
     .page-header / .admin-header  the 38 dashboard + admin manager pages
     .auth-room                    signing in, out, resetting
     .hp-dash-hero                 the member's own dashboard

   The treatment is scaled to what each page is FOR, which is why this is
   not one shared rule. A staff manager page is a work surface somebody
   has open all day: it gets the depth and the typography and none of the
   motion, because a drifting painting behind a data table is a thing you
   would fix within a week. Signing in is an arrival, so that one gets
   more. */

/* ── Dashboard and admin managers ── */
.page-header {
  position: relative;
  padding-bottom: 0.2rem;
}
/* Warm light pooling behind the title band. Sits behind content and is
   pointer-transparent, so nothing in the header stops being clickable. */
.page-header::before {
  content: '';
  position: absolute;
  left: -2rem; right: -2rem; top: -2.5rem;
  height: 190px;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(60% 100% at 12% 0%, rgba(232, 123, 30, 0.16), transparent 70%);
}
/* The rule under the title — the one thing that makes a heading read as a
   title card rather than an h1. Drawn on the title itself, because these
   headers are built in ~38 different places and none of them would have
   an element to hang it on. */
.page-header .page-title::after {
  content: '';
  display: block;
  width: 64px;
  height: 2px;
  margin-top: 0.75rem;
  border-radius: 2px;
  background: linear-gradient(to right, var(--fire, #e87b1e), var(--gold, #c9a84c) 60%, transparent);
}
.page-header .eyebrow {
  letter-spacing: 0.2em;
  opacity: 0.9;
}

/* ── Signing in ── */
/* An arrival, not a work surface, so this one gets real atmosphere: a
   pool of firelight behind the card, as if the door opened onto it. */
.auth-room {
  position: relative;
}
.auth-room::before {
  content: '';
  position: absolute;
  left: 50%;
  top: -6vh;
  width: min(140vw, 1100px);
  height: 520px;
  transform: translateX(-50%);
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(50% 50% at 50% 40%, rgba(232, 123, 30, 0.20), transparent 70%),
    radial-gradient(70% 60% at 50% 70%, rgba(201, 168, 76, 0.10), transparent 75%);
}

/* ── The member's own dashboard ── */
/* Already had a gradient and an ember left edge. It was missing depth,
   and the name at the top of it was not carrying the page. */
.hp-dash-hero {
  position: relative;
  overflow: hidden;
}
.hp-dash-hero::after {
  content: '';
  position: absolute;
  inset: auto -10% -60% 20%;
  height: 220px;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(50% 100% at 50% 100%, rgba(232, 123, 30, 0.22), transparent 70%);
}
/* The glow is decorative and must not swallow the content sitting on it. */
.hp-dash-hero > * { position: relative; z-index: 1; }

/* ── 6. A NOTE ON MOTION, FOR THE NEXT PERSON ─────────────────────
   Admin managers get NO scroll reveal, and that is on purpose rather than
   by omission. Motion on a page somebody keeps open for an hour reading
   tables is not atmosphere, it is a delay between them and the number
   they came for.

   No rule is needed to enforce it. Those pages wrap everything in
   <main><div class="forge-shell"><div class="forge-content">, and the
   tagger in fx-core only ever touches DIRECT <section>/<article>
   children of <main> — so there is nothing for it to match. The first
   draft of this file added an !important block to switch the motion back
   off again, which would have been a rule fighting a rule that never
   fired. If a future change flattens that shell, this is the paragraph
   that explains why the reveal suddenly turned up on the admin pages. */

/* ── 7. THE WALL PAGES ─────────────────────────────────────────────
   404, 403 and "coming soon". A coverage check across all 105 page
   modules turned these up as a FIFTH header family that nothing here
   styled — .signpost, .forbidden and .coming-soon, none of which use a
   room plaque or a page header. They were missed because I enumerated
   the families by looking at the pages I was thinking about, and nobody
   thinks about the 404 page.

   Which is backwards: landing on one of these is already a small
   disappointment, and a bare heading on a flat background makes it feel
   like the site broke rather than like you took a wrong turn. */
.signpost,
.forbidden,
.coming-soon {
  position: relative;
  padding-top: clamp(1.5rem, 5vh, 3rem);
}
.signpost::before,
.forbidden::before,
.coming-soon::before {
  content: '';
  position: absolute;
  left: 50%;
  top: -4vh;
  width: min(130vw, 900px);
  height: 420px;
  transform: translateX(-50%);
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(50% 50% at 50% 45%, rgba(232, 123, 30, 0.17), transparent 72%);
}
.signpost__title::after,
.forbidden .display-title::after,
.coming-soon .display-title::after {
  content: '';
  display: block;
  width: 68px;
  height: 2px;
  margin: 0.9rem 0 0;
  border-radius: 2px;
  background: linear-gradient(to right, var(--fire, #e87b1e), var(--gold, #c9a84c) 60%, transparent);
}
/* These three centre their content, so the rule has to centre with it. */
.signpost, .forbidden, .coming-soon { text-align: center; }
.signpost__title::after,
.forbidden .display-title::after,
.coming-soon .display-title::after { margin-left: auto; margin-right: auto; }

/* ── 8. THE CREATOR PAGE ───────────────────────────────────────────
   /c/<handle>. A sixth family, .hp-profile-hero, and it already had the
   most work done of any of them: a real 200px banner image, an avatar
   overlapping it, per-creator theme colours.

   So this is the lightest touch in the file — it needs the same lit-from-
   behind quality as everything else and nothing more. It has to be a
   box-shadow rather than a gradient layer, because the hero sets
   overflow:hidden to clip its own banner and would clip a ::before too. */
.hp-profile-hero {
  box-shadow: 0 18px 60px -22px rgba(232, 123, 30, 0.38);
}
.hp-profile-hero__name::after {
  content: '';
  display: block;
  width: 56px;
  height: 2px;
  margin: 0.6rem 0 0;
  border-radius: 2px;
  background: linear-gradient(to right, var(--fire, #e87b1e), var(--gold, #c9a84c) 60%, transparent);
}

/* ═══════════════════════════════════════════════════════════════════
   LIVE CHARACTER PREVIEW

   The character sheet is a long form and every picture on it was decided
   when the page was rendered, so picking a new race showed the old race
   until you saved. This is the panel that answers immediately.

   It floats beside the pickers on a wide screen and sits above them on a
   phone, where a sticky panel would eat the screen you are choosing on.
   ═══════════════════════════════════════════════════════════════════ */
.rpglive {
  --rpg-live-accent: #c9a84c;
  margin: 0 0 1.25rem;
}
.rpglive__inner {
  border: 1px solid color-mix(in srgb, var(--rpg-live-accent) 38%, transparent);
  border-radius: 14px;
  padding: 1rem 1rem 1.15rem;
  background:
    radial-gradient(120% 90% at 50% 0%,
      color-mix(in srgb, var(--rpg-live-accent) 12%, transparent) 0%,
      transparent 70%),
    rgba(12, 10, 8, 0.55);
  box-shadow: 0 0 34px -18px var(--rpg-live-accent);
  text-align: center;
}
.rpglive__eyebrow {
  margin: 0 0 .6rem;
  font-size: .68rem;
  letter-spacing: .16em;
  text-transform: uppercase;
  opacity: .62;
}
.rpglive__art { margin: 0; }
.rpglive__img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border-radius: 10px;
  display: block;
  border: 1px solid color-mix(in srgb, var(--rpg-live-accent) 30%, transparent);
  /* No transition on src swaps — a cross-fade here reads as lag when you
     are clicking through a grid of thirteen races. */
}
.rpglive__img[hidden] { display: none; }
.rpglive__cap {
  margin: .4rem 0 0;
  font-size: .78rem;
  opacity: .82;
  overflow-wrap: anywhere;
}
.rpglive__name {
  margin: .75rem 0 .1rem;
  font-size: 1.05rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.rpglive__meta { margin: 0; font-size: .76rem; opacity: .68; }
.rpglive__dot { margin: 0 .35rem; }

/* Wide screens: the preview rides alongside the pickers and stays put while
   you scroll the long list of classes. */
@media (min-width: 1080px) {
  .hp-form--withlive {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 15rem;
    grid-auto-flow: dense;
    column-gap: 1.5rem;
    align-items: start;
  }
  .hp-form--withlive > * { grid-column: 1; min-width: 0; }
  .rpglive {
    grid-column: 2 !important;
    grid-row: 1 / span 30;
    position: sticky;
    top: 1.25rem;
    margin: 0;
  }
}

/* The picker grids get a clearer "this one is chosen" state, because the
   preview now depends on knowing which card that is. */
.pick__input:checked + .pick__body .pick__art {
  outline: 2px solid var(--rpg-live-accent, #c9a84c);
  outline-offset: 2px;
}

/* ═══════════════════════════════════════════════════════════════════
   PUBLIC CHARACTER PAGE

   This page was built with six inline style="" attributes and the CSP
   (style-src 'self', no 'unsafe-inline') dropped every one of them
   silently. Measured on the real page: the header asked for
   border-left:4px solid <theme> and computed to 0px none. A member's
   chosen colour did nothing on their own profile, and the page had no
   picture on it at all.

   --rpg-theme is set from a data attribute by js/rpg-theme.js, because
   the CSSOM is not restricted by CSP the way a style attribute is.
   ═══════════════════════════════════════════════════════════════════ */
.rpgpub { --rpg-theme: #c89a3a; }
.rpgpub__head {
  border-left: 4px solid var(--rpg-theme);
  padding-left: 14px;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}
.rpgpub__face {
  width: 108px;
  height: 108px;
  border-radius: 10px;
  object-fit: cover;
  border: 2px solid var(--rpg-theme, #c89a3a);
  flex: 0 0 auto;
}
.rpgpub__banner {
  width: 100%;
  max-height: 240px;
  object-fit: cover;
  border-radius: 8px;
  border: 1px solid #2c2624;
  margin-bottom: 12px;
  display: block;
}
.rpgpub__stat { padding: 12px; }
.rpgpub__statval { font-size: 1.4em; color: var(--rpg-theme, #c89a3a); }

/* Rarity chips. Each was an inline background the CSP threw away, so every
   chip on every profile rendered the same default grey. */
.rarity { color: #0c0c0c; }
.rarity--common    { background: #9a9a9a; }
.rarity--uncommon  { background: #5ec27a; }
.rarity--rare      { background: #5ea1ff; }
.rarity--epic      { background: #a86bff; }
.rarity--legendary { background: #ffaa3a; }
.rarity--mythic    { background: #ff4f87; }
.rarity--relic     { background: #ff7e3a; }
.rarity--secret    { background: #888888; }
.rarity--event     { background: #3affe0; }
.rarity--founder   { background: #ffd56b; }
.rarity--unknown   { background: #bbbbbb; }

@media (max-width: 560px) {
  .rpgpub__face { width: 84px; height: 84px; }
}

/* ═══════════════════════════════════════════════════════════════════
   TWITCH LINK PAGE

   Four inline style attributes here were dropped by the CSP
   (style-src 'self'), so the link code — the one thing the page exists
   to show — rendered as plain body text.

   The unlink confirmation is a checkbox rather than a confirm() dialog,
   because script-src-attr 'none' blocks inline handlers and the old
   onsubmit never ran. Enforced server-side too; this only makes it look
   like the decision it is.
   ═══════════════════════════════════════════════════════════════════ */
.tl-code {
  margin: 1rem 0;
  padding: 1.25rem;
  border: 1px solid var(--gold, #c9a84c);
  border-radius: 8px;
  background: rgba(201, 168, 76, 0.05);
  text-align: center;
}
.tl-code__label,
.tl-code__hint { margin: 0; }
.tl-code__hint { font-size: 0.85em; }
.tl-code__value {
  font-family: var(--font-head), monospace;
  font-size: 2rem;
  letter-spacing: 0.4em;
  margin: 0.5rem 0;
  color: var(--gold-bright, #e8c46a);
  overflow-wrap: anywhere;
}

.tl-unlink {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  align-items: flex-start;
  max-width: 46rem;
}
.tl-unlink__confirm {
  display: flex;
  gap: .55rem;
  align-items: flex-start;
  font-size: .9rem;
  line-height: 1.45;
}
.tl-unlink__confirm input { margin-top: .25rem; flex: 0 0 auto; }

/* ═══════════════════════════════════════════════════════════════════
   GUIDE RAIL — the chapter navigator on /how-to-play
   ═══════════════════════════════════════════════════════════════════
   A twelve-screen manual with a flat list of anchors at the top is a
   manual nobody finishes: once you scroll past the contents you have no
   idea where you are or how much is left. The rail stays put, marks the
   chapter you are actually reading, and lets you jump without scrolling
   back up.

   Everything is namespaced .guide-* so it cannot touch the .chapter card
   language above, which every other long page uses.

   No JS is required for any of it to WORK — the rail is plain anchors and
   the chapters are plain sections. guide-rail.js only adds the
   highlighting, the filter and the progress bar on top.
   ═══════════════════════════════════════════════════════════════════ */

.guide-shell {
  display: grid;
  grid-template-columns: minmax(210px, 250px) minmax(0, 1fr);
  gap: clamp(1rem, 2.5vw, 2rem);
  align-items: start;
}

/* One column on anything narrow. The rail becomes a collapsible strip at
   the top rather than eating half a phone screen. */
@media (max-width: 900px) {
  .guide-shell { grid-template-columns: minmax(0, 1fr); }
}

.guide-rail {
  position: sticky;
  top: calc(var(--header-h, 68px) + 1rem);
  max-height: calc(100vh - var(--header-h, 68px) - 2rem);
  display: flex;
  flex-direction: column;
  padding: 1rem 0.9rem;
  border: 1px solid var(--border, rgba(201, 168, 76, 0.18));
  border-radius: 12px;
  background:
    linear-gradient(180deg, rgba(232, 123, 30, 0.05), transparent 40%),
    var(--bg-2, #0f0f12);
}
@media (max-width: 900px) {
  .guide-rail { position: static; max-height: none; margin-bottom: 1.2rem; }
  /* On a phone the full list stood 367px tall — 45% of the first screen of
     a manual spent on its table of contents before a word of the manual.
     Capped so it scrolls inside itself and the guide starts near the top.
     Not hidden behind a toggle: on a twelve-chapter page, seeing that the
     chapters exist is most of what the rail is for. */
  /* ~5 links of 29px. Measured, not guessed: the first attempt capped at
     34vh = 276px on a 812px phone while the list was 261px, so it changed
     nothing at all and looked applied. */
  .guide-rail__list { max-height: 150px; }
}

.guide-rail__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin: 0 0 0.6rem;
}
.guide-rail__eyebrow {
  margin: 0;
  font-family: var(--font-head, 'Cinzel', serif);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--gold, #c9a84c);
  opacity: 0.9;
}
/* "3 of 12" — how much is left, which a list of links never tells you. */
.guide-rail__count {
  font-size: 0.7rem;
  color: var(--ink-dim, #9a9384);
  font-variant-numeric: tabular-nums;
}

.guide-rail__filter {
  width: 100%;
  margin: 0 0 0.6rem;
  padding: 0.42rem 0.6rem;
  font: inherit;
  font-size: 0.82rem;
  color: var(--text, #d8d1c2);
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid var(--border, rgba(201, 168, 76, 0.18));
  border-radius: 7px;
}
.guide-rail__filter::placeholder { color: var(--ink-dim, #9a9384); }
.guide-rail__filter:focus-visible {
  outline: 2px solid var(--gold, #c9a84c);
  outline-offset: 1px;
}

.guide-rail__list {
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.guide-rail__item { margin: 0; }
.guide-rail__item[hidden] { display: none; }

.guide-rail__link {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.34rem 0.5rem;
  border-radius: 7px;
  border-left: 2px solid transparent;
  font-size: 0.84rem;
  line-height: 1.35;
  color: var(--text, #d8d1c2);
  text-decoration: none;
}
.guide-rail__link:hover { background: rgba(201, 168, 76, 0.08); }
.guide-rail__link:focus-visible {
  outline: 2px solid var(--gold, #c9a84c);
  outline-offset: -2px;
}
.guide-rail__n {
  min-width: 1.1em;
  font-variant-numeric: tabular-nums;
  font-size: 0.74rem;
  color: var(--ink-dim, #9a9384);
}

/* The chapter you are actually looking at. */
.guide-rail__link[aria-current='true'] {
  background: rgba(232, 123, 30, 0.1);
  border-left-color: var(--fire, #e87b1e);
  color: var(--text-bright, #f0eadb);
}
.guide-rail__link[aria-current='true'] .guide-rail__n { color: var(--gold-bright, #f0d080); }

/* Chapters already read, so the rail shows progress and not just position. */
.guide-rail__link[data-read='1']:not([aria-current='true']) { opacity: 0.62; }

.guide-rail__empty {
  margin: 0.4rem 0.5rem;
  font-size: 0.78rem;
  color: var(--ink-dim, #9a9384);
}

/* How far down the whole manual you are. */
.guide-progress {
  position: sticky;
  top: var(--header-h, 68px);
  z-index: 3;
  height: 2px;
  margin: 0 0 0.8rem;
  background: rgba(201, 168, 76, 0.14);
  border-radius: 2px;
  overflow: hidden;
}
.guide-progress__bar {
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--fire, #e87b1e), var(--gold, #c9a84c));
}

/* Someone who asked their system not to animate does not want smooth
   scrolling either — guide-rail.js checks the same query before it
   scrolls, so the two never disagree. */
@media (prefers-reduced-motion: no-preference) {
  .guide-shell { scroll-behavior: smooth; }
  .guide-progress__bar { transition: width 0.15s linear; }
}

/* The rail is navigation. On paper it is noise. */
@media print {
  .guide-rail, .guide-progress { display: none; }
  .guide-shell { display: block; }
}

/* ═══════════════════════════════════════════════════════════════════
   YOUR CODEX — /codex
   ═══════════════════════════════════════════════════════════════════
   Three collections (items, enemies, pets) as a card grid, with the
   entries you have not found yet rendered as ??? .

   Every panel is in the DOM at once and the tabs only change which is
   shown, so the page works with script blocked — you land on Items and
   the tab links are real anchors to the other two.
   ═══════════════════════════════════════════════════════════════════ */

.cdx { margin-top: 0.4rem; }

.cdx-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin: 0 0 1rem;
  border-bottom: 1px solid var(--border, rgba(201, 168, 76, 0.18));
}
.cdx-tabs { display: flex; gap: 0.25rem; flex-wrap: wrap; }
.cdx-tab {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.5rem 0.85rem;
  border-bottom: 2px solid transparent;
  font-family: var(--font-head, 'Cinzel', serif);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim, #9a9384);
  text-decoration: none;
}
.cdx-tab:hover { color: var(--text-bright, #f0eadb); }
.cdx-tab:focus-visible { outline: 2px solid var(--gold, #c9a84c); outline-offset: -2px; }
.cdx-tab[aria-selected='true'] {
  color: var(--gold-bright, #f0d080);
  border-bottom-color: var(--fire, #e87b1e);
}
.cdx-tab__n { font-size: 0.7rem; opacity: 0.7; font-variant-numeric: tabular-nums; }

.cdx-reveal {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0 0.2rem 0.5rem;
  font-size: 0.8rem;
  color: var(--ink-dim, #9a9384);
  cursor: pointer;
}

/* Only the selected panel is shown. Without script none is hidden, which
   is the point — every collection stays readable. */
.cdx[data-cdx-ready] .cdx-panel { display: none; }
.cdx[data-cdx-ready] .cdx-panel[data-cdx-open] { display: block; }

.cdx-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  margin: 0 0 0.7rem;
}
.cdx-search {
  flex: 1 1 240px;
  max-width: 420px;
  padding: 0.5rem 0.7rem;
  font: inherit;
  font-size: 0.88rem;
  color: var(--text, #d8d1c2);
  background: rgba(0, 0, 0, 0.35);
  border: 1px solid var(--border, rgba(201, 168, 76, 0.18));
  border-radius: 8px;
}
.cdx-search::placeholder { color: var(--ink-dim, #9a9384); }
.cdx-search:focus-visible { outline: 2px solid var(--gold, #c9a84c); outline-offset: 1px; }
.cdx-result { margin: 0; font-size: 0.78rem; color: var(--ink-dim, #9a9384); }

.cdx-progress {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  margin: 0 0 1rem;
  padding: 0.55rem 0.8rem;
  border: 1px solid var(--border, rgba(201, 168, 76, 0.18));
  border-radius: 9px;
  background: rgba(0, 0, 0, 0.25);
}
.cdx-progress__label {
  font-family: var(--font-head, 'Cinzel', serif);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--gold, #c9a84c);
  white-space: nowrap;
}
.cdx-progress__track {
  flex: 1;
  height: 6px;
  border-radius: 4px;
  background: rgba(201, 168, 76, 0.14);
  overflow: hidden;
}
.cdx-progress__fill {
  height: 100%;
  /* Zero by default. The real width is applied by codex.js through the
     CSSOM because style-src forbids an inline style attribute — with the
     script blocked an EMPTY bar beside an honest count is right, and a
     full one is a lie. */
  width: 0;
  background: linear-gradient(90deg, var(--fire, #e87b1e), var(--gold, #c9a84c));
}
.cdx-progress__count {
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
  color: var(--text-bright, #f0eadb);
  white-space: nowrap;
}
.cdx-note { margin: 0 0 1rem; font-size: 0.84rem; color: var(--ink-dim, #9a9384); }
.cdx-note a { color: var(--gold-bright, #f0d080); }

.cdx-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: 0.7rem;
}

.cdx-card {
  padding: 0.75rem 0.85rem;
  border: 1px solid var(--border, rgba(201, 168, 76, 0.18));
  border-radius: 10px;
  background: var(--bg-2, #0f0f12);
}
.cdx-card[hidden] { display: none; }
.cdx-card__head { display: flex; gap: 0.65rem; align-items: center; margin: 0 0 0.6rem; }
.cdx-card__art {
  width: 48px; height: 48px;
  border-radius: 8px;
  object-fit: cover;
  background: rgba(0, 0, 0, 0.4);
  flex: 0 0 auto;
}
.cdx-card__art--none {
  display: block;
  border: 1px dashed rgba(201, 168, 76, 0.16);
}
.cdx-card__id { min-width: 0; }
.cdx-card__name {
  margin: 0;
  font-family: var(--font-head, 'Cinzel', serif);
  font-size: 0.94rem;
  font-weight: 700;
  color: var(--text-bright, #f0eadb);
  overflow-wrap: anywhere;
}
.cdx-card__kind {
  margin: 0.1rem 0 0;
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-dim, #9a9384);
}

.cdx-card__stats { margin: 0; display: grid; gap: 0.22rem; }
.cdx-stat { display: flex; justify-content: space-between; gap: 0.6rem; }
.cdx-stat dt { margin: 0; font-size: 0.76rem; color: var(--ink-dim, #9a9384); }
.cdx-stat dd {
  margin: 0;
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  color: var(--text, #d8d1c2);
  text-align: right;
  overflow-wrap: anywhere;
}

/* Not found yet: dim, no art, every value ???. */
.cdx-card--unknown { border-style: dashed; opacity: 0.55; }
.cdx-card--unknown .cdx-card__name { color: var(--ink-dim, #9a9384); letter-spacing: 0.1em; }

.cdx-empty { margin: 0.8rem 0 0; font-size: 0.85rem; color: var(--ink-dim, #9a9384); }

/* ── Utilities replacing dead inline styles ──────────────────────────
   Four style="" attributes across the admin pages were being discarded
   by style-src 'self' — a flex form that never became a flex row, an
   input that never got its width, two headings that never got their
   spacing. Nothing looked broken enough to notice, which is exactly the
   problem with an inline style under this CSP. See
   scripts/check-inline-styles.js.
   ─────────────────────────────────────────────────────────────────── */
.u-mt-sm { margin-top: 0.6rem; }
.u-mt-lg { margin-top: 2rem; }
.u-row-inline { display: flex; gap: 0.4rem; align-items: center; }
.u-input-token { width: 230px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }

/* The one line that tells a new member what fills a ??? in. */
.cdx-how {
  margin: 0 0 0.9rem;
  font-size: 0.85rem;
  line-height: 1.6;
  color: var(--ink-dim, #9a9384);
}

/* ═══════════════════════════════════════════════════════════════════
   THE CODE OF CONDUCT — shown in full on the membership application
   ═══════════════════════════════════════════════════════════════════
   It replaced a single line and a link. Long prose that people must
   actually read needs a shape: numbered sections, a tag saying how
   serious each one is, and terms bold against their explanation. No
   accordion — the zero-tolerance list and the 90-day probation are
   exactly the parts nobody would expand.
   ═══════════════════════════════════════════════════════════════════ */

.coc {
  margin: 0 0 1.2rem;
  padding: clamp(1rem, 2.5vw, 1.6rem);
  border: 1px solid var(--border, rgba(201, 168, 76, 0.18));
  border-radius: 12px;
  background:
    linear-gradient(180deg, rgba(232, 123, 30, 0.04), transparent 38%),
    var(--bg-2, #0f0f12);
}
.coc__title {
  margin: 0 0 0.4rem;
  font-family: var(--font-head, 'Cinzel', serif);
  font-size: clamp(1.1rem, 2.6vw, 1.4rem);
  font-weight: 800;
  color: var(--text-bright, #f0eadb);
}
.coc__intro { margin: 0 0 1.1rem; line-height: 1.7; color: var(--text, #d8d1c2); }

.coc__section { margin: 0 0 1.1rem; padding-top: 0.9rem; border-top: 1px solid rgba(201, 168, 76, 0.12); }
.coc__section:first-of-type { border-top: none; padding-top: 0; }

.coc__heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0 0 0.35rem;
  font-family: var(--font-head, 'Cinzel', serif);
  font-size: 1rem;
  font-weight: 700;
  color: var(--gold-bright, #f0d080);
}
.coc__n {
  display: inline-grid;
  place-items: center;
  width: 1.5rem; height: 1.5rem;
  border-radius: 50%;
  background: rgba(232, 123, 30, 0.14);
  color: var(--fire, #e87b1e);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}
/* How serious this section is, at a glance. */
.coc__tag {
  margin-left: auto;
  padding: 0.12rem 0.5rem;
  border: 1px solid rgba(201, 168, 76, 0.28);
  border-radius: 999px;
  font-family: var(--font-body, system-ui, sans-serif);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-dim, #9a9384);
  white-space: nowrap;
}
.coc__section:first-of-type .coc__tag {
  border-color: rgba(255, 79, 135, 0.45);
  color: #ff7fa5;
}

.coc__lede { margin: 0 0 0.7rem; line-height: 1.65; color: var(--text, #d8d1c2); }

.coc__points { margin: 0; }
.coc__points dt {
  margin: 0.6rem 0 0.15rem;
  font-weight: 700;
  color: var(--text-bright, #f0eadb);
}
.coc__points dd {
  margin: 0 0 0 0;
  padding-left: 0.9rem;
  border-left: 2px solid rgba(201, 168, 76, 0.18);
  line-height: 1.65;
  color: var(--text, #d8d1c2);
}

.coc__version {
  margin: 1rem 0 0;
  padding-top: 0.7rem;
  border-top: 1px solid rgba(201, 168, 76, 0.12);
  font-size: 0.78rem;
  color: var(--ink-dim, #9a9384);
}

/* The signed agreement, embedded on the staff application panel.
   An iframe rather than inlined markup: the page's own CSP strips the
   inline styles the document is built from, so inlined it renders as
   unstyled black text. The route it points at sends a tighter policy of
   its own that allows inline styles and nothing else. */
.coc-doc {
  display: block;
  width: 100%;
  height: 520px;
  margin: 0.6rem 0 0.8rem;
  border: 1px solid var(--border, rgba(201, 168, 76, 0.18));
  border-radius: 8px;
  background: #efe9dc;
}
@media (max-width: 700px) { .coc-doc { height: 380px; } }

/* ── The agreement tick ──────────────────────────────────────────────
   .hp-form__label is 0.76rem, uppercase, 0.16em letter-spacing, gold —
   right for a field name like HANDLE, and the worst possible treatment
   for the single most important sentence on the page. Two lines of
   letter-spaced capitals is exactly what people skip.

   This is the line someone is legally agreeing to, so it reads as
   ordinary prose at ordinary size, and the box lines up with the first
   line of text instead of floating beside it.
   ─────────────────────────────────────────────────────────────────── */
.hp-form__label--consent {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-family: var(--font-body, 'Crimson Pro', Georgia, serif);
  font-size: 1rem;
  line-height: 1.55;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-bright, #f0eadb);
  cursor: pointer;
}
.hp-form__label--consent input[type="checkbox"] {
  flex: 0 0 auto;
  width: 1.1rem;
  height: 1.1rem;
  margin: 0.22rem 0 0;
  accent-color: var(--fire, #e87b1e);
  cursor: pointer;
}
/* The required star should not be the loudest thing in the sentence. */
.hp-form__label--consent .hp-form__req { opacity: 0.75; }
