@import url('fonts.css');

:root {
  --bg: #fdfdfc;
  --ink: #212124;
  /* Contrast-checked against --bg (WCAG): muted ~4.9:1 (passes AA body
     text), faint ~3.4:1 (passes AA for the small uppercase labels/hints
     it's actually used on). Previous values (muted 3.3:1, faint 2:1) read
     as too washed-out in light mode. */
  --muted: #6f6f76;
  --faint: #8a8a91;
  --line: #ececef;
  --soft: #f6f6f4;

  --serif: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --sans: "IBM Plex Sans", -apple-system, "Segoe UI", system-ui, sans-serif;

  --human1: #a32626;
  --human2: #26518f;
  --orc1: #2f5230;
  --orc2: #3d4440;
  --dwarf1: #5a5f66;
  --dwarf2: #5b3f26;
  --elf1: #1f6e5c;
  --elf2: #8a6d1f;
  --undead1: #5b3a7e;
  --undead2: #44484c;

  --a1: var(--human1);
  --a2: var(--human2);

  --q-worn: #8a8a8f;
  --q-fine: #2e8b3d;
  --q-superior: #2f6db3;
  --q-excellent: #b8860b;
  --q-masterwork: #b5651d;
  --q-flawless: #b8556b;
  --q-pristine: #5f7690;
  --q-exalted: #9c4fa0;
  --q-transcendent: #3f4aa0;
  --q-legendary: #7d3bbd;
  --q-mythic: #c22b2b;

  /* The Mirrorfiend's own color (feature 190): a metallic dark steel blue,
     "mirrored glass", distinct from the Mythic-quality red it briefly
     reused. Dark mode gets a lighter, more saturated version below so it
     still reads clearly against the dark background. */
  --mirrorfiend: #33566f;

  /* Membership/Favor gold (feature 46): the same tone as the gold resource
     chip and the Iron Vault, so Favor reads in that familiar register. */
  --gold: #c8a13a;

  /* Monthly Champion titles (feature: leaderboard champion titles): silver
     and bronze to sit alongside --gold, contrast-checked against --bg the
     same way as --muted/--faint above. */
  --q-silver: #6b7785;
  --q-bronze: #9a5b32;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* Always reserve room for the vertical scrollbar so the centered .app does not
   jump sideways between a tall page/tab (scrollbar shown) and a short one
   (scrollbar hidden). This is what made Camp's Buildings tab look slightly
   wider/shifted versus the shorter Forge tab and other pages. */
html {
  scrollbar-gutter: stable;
  overflow-x: hidden;
}

body {
  font: 400 13.5px/1.6 var(--sans);
  font-variant-numeric: tabular-nums;
  background: var(--bg);
  color: var(--ink);
  -webkit-text-size-adjust: 100%;
  /* Tells the browser which native UI (checkboxes, radios, selects,
     scrollbars, date pickers) to draw: without this, dark mode still gets
     light-mode native controls, a plain white checkbox punching through
     the dark page. */
  color-scheme: light;
}

body::before {
  content: "";
  position: fixed;
  z-index: 30;
  top: 0;
  left: 0;
  bottom: 0;
  width: 5px;
  background: linear-gradient(to bottom, var(--a1) 0 50%, var(--a2) 50% 100%);
}

body.theme-orc {
  --a1: var(--orc1);
  --a2: var(--orc2);
}

body.theme-dwarf {
  --a1: var(--dwarf1);
  --a2: var(--dwarf2);
}

body.theme-elf {
  --a1: var(--elf1);
  --a2: var(--elf2);
}

body.theme-undead {
  --a1: var(--undead1);
  --a2: var(--undead2);
}

body.dark {
  color-scheme: dark;
  --bg: #131416;
  --ink: #dddee1;
  /* Same contrast-checked pass as the light values above: muted ~8:1,
     faint ~5:1 against --bg. Previous faint (2.9:1) read as too dark,
     nearly lost against the background. */
  --muted: #a8abb1;
  --faint: #82858c;
  --line: #25272b;
  --soft: #1d1f22;
  --q-worn: #9a9aa0;
  --q-fine: #57b967;
  --q-superior: #6ba3e0;
  --q-excellent: #d9a825;
  --q-masterwork: #d98a3e;
  --q-flawless: #dd8098;
  --q-pristine: #93aac5;
  --q-exalted: #c67fca;
  --q-transcendent: #7a84d6;
  --q-legendary: #ab74e0;
  --q-mythic: #e05555;
  --mirrorfiend: #7fa8c9;
  --q-silver: #b8c2cc;
  --q-bronze: #d99a6c;
  --wyrm-black: #a7afbd;
  --wyrm-black-bar-a: #6b7280;
  --wyrm-black-bar-b: #aab2c0;
}

a {
  color: var(--a2);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
  text-underline-offset: 3px;
}

input,
button,
select {
  font: inherit;
}

.app {
  max-width: 1000px;
  margin: 0 auto;
  padding: 26px 24px 64px;
}

body.guest-shell .app {
  max-width: 900px;
}

.kow-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.brand-group {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0;
}

.ea-badge {
  font: 600 9px/1 var(--sans);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
  cursor: default;
  margin-top: -3px;
  margin-bottom: -6px;
  transform: translateX(3px);
}

.brand {
  display: inline-flex;
  align-items: center;
  color: var(--ink);
}

/* The wordmark is now an image (see <x-brand-logo>): a black logo for light
   mode, a white one for dark. Both render; CSS reveals the one that matches
   the theme, so it needs no JS and can't flash during the pre-paint theme
   script. 7:1 wordmark, sized by height. */
.brand-logo {
  display: block;
  height: 26px;
  width: auto;
}

.brand-logo-dark {
  display: none;
}

body.dark .brand-logo-light {
  display: none;
}

body.dark .brand-logo-dark {
  display: block;
}

.brand:visited {
  color: var(--ink);
}

.brand:hover {
  color: var(--ink);
  text-decoration: none;
}

.brand::first-letter {
  color: #c22b2b;
}

body.dark .brand::first-letter {
  color: #ff6b6b;
}

.header-actions {
  display: flex;
  gap: 14px;
  align-items: center;
  font-size: 11px;
  letter-spacing: 0.5px;
  text-transform: uppercase;
}

.energy-counter {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0;
  color: var(--muted);
}

.energy-counter .energy-icon {
  display: inline-flex;
  color: var(--a1);
}

.energy-counter b {
  color: var(--ink);
  font-size: 13px;
  letter-spacing: 0.2px;
}

.header-actions button,
.header-actions .icon-btn,
.btn-ghost {
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  padding: 6px 10px;
  cursor: pointer;
}

.btn-danger {
  border: 1px solid #b43a3a;
  background: transparent;
  color: #b43a3a;
  padding: 6px 10px;
  cursor: pointer;
}

.btn-danger:hover {
  background: color-mix(in srgb, #b43a3a 10%, transparent);
}

.icon-btn {
  position: relative;
  width: 38px;
  height: 38px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}

.icon-btn:hover {
  text-decoration: none;
}

/* Ravens header icon (layouts/game-shell.blade.php): same look as every
   other header icon-btn by default, only picking up the accent color
   once there's actually an unread raven worth noticing. */
.icon-btn.has-unread {
  color: var(--a1);
}

.icon-btn svg {
  display: block;
}

/* Unread-count bubble on a header icon button (currently just Ravens),
   distinct from .nav-badge: that one sits inline in a menu link's text,
   this one floats over the icon's corner since there's no label text
   here to sit next to. */
.icon-btn-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 3px;
  border-radius: 999px;
  background: var(--a1);
  color: var(--bg);
  font: 700 10px/16px var(--sans);
  text-align: center;
}

[data-theme-toggle] .icon-moon {
  display: none;
}

body.dark [data-theme-toggle] .icon-sun {
  display: none;
}

body.dark [data-theme-toggle] .icon-moon {
  display: inline-flex;
}

.hero {
  margin-top: 70px;
  max-width: 560px;
}

.hero h1 {
  font: 600 54px/1.02 var(--serif);
}

.hero h1 em {
  color: var(--a1);
  font-style: italic;
  font-weight: 500;
}

.hero .tag {
  margin-top: 14px;
  color: var(--muted);
  font: italic 500 17px/1.4 var(--serif);
}

.hero .pitch {
  margin-top: 18px;
  color: color-mix(in srgb, var(--ink) 86%, var(--muted));
}

.gate {
  margin-top: 44px;
  display: flex;
  flex-wrap: wrap;
  gap: 14px 18px;
  align-items: flex-end;
  max-width: 560px;
}

/* Account settings forms: one full-width field per line (unlike .gate's
   side-by-side row, capped at 560px), save button left-aligned on its own
   line below, no width cap so the field/textarea can use the whole page. */
.stacked-form {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 14px;
  margin-top: 14px;
}

/* A button that stretches to the full row width once the layout is narrow
   enough that a fixed-width primary action starts looking cramped/off-center
   (Bounty Board's "Place Bounty"), left untouched on wider screens. */
@media (max-width: 620px) {
  .btn-block-mobile {
    width: 100%;
  }
}

/* An inline checkbox + label as one clickable row (Account notifications). */
.checkbox-row {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
  font-size: 14px;
}

.checkbox-row input[type="checkbox"] {
  width: 16px;
  height: 16px;
  flex: none;
  accent-color: var(--a1);
  cursor: pointer;
}

/* .field's flex: 1 1 150px (below) sets a 150px basis meant as a
   min-*width* in .gate's horizontal row; inside .stacked-form's *column*
   flex direction that basis applies to height instead, forcing every
   single-line field to a ~150px-tall box regardless of its actual content.
   Cancel it here so a field is exactly as tall as its label + input. */
.stacked-form .field {
  flex: none;
  width: 100%;
}

/* Account page: tighter rhythm than the site-wide .section/.sub defaults
   (25px + 24px between every heading and its content) so a save button
   reads as belonging to the section right above it, not the next one
   down. Scoped to this page only, every other page keeps the roomier
   default. */
.account-sections .section {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
}

.account-sections .section:first-child {
  margin-top: 0;
  padding-top: 0;
  border-top: 0;
}

.account-sections .sub {
  margin-bottom: 10px;
}

/* The Security tab's low-key link to account deletion (no tab button of its
   own, see profile/edit.blade.php): same divider rhythm as .section but
   without becoming a whole new section. */
.account-danger-link {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
}

.stacked-form .raven-body-field {
  margin: 0;
}

/* Account > Notifications: the per-category checkboxes, indented a touch
   under the master "Send me push notifications" row so the hierarchy (one
   switch gates several finer ones) reads visually too. */
.notify-categories {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-left: 24px;
  border-left: 1px solid var(--line);
}

/* Groups the per-event checkboxes visually (Camp / Smith / War / Energy)
   without splitting them into separate forms; each checkbox still saves and
   gates independently. */
.notify-group-label {
  margin-top: 8px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--faint);
}

.notify-categories .notify-group-label:first-child {
  margin-top: 0;
}

/* The native app / browser opt-in buttons below the Notifications form, one
   block per channel, each device's own status line right under its button. */
.notify-devices {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 18px;
}

.notify-devices p.sub:first-child {
  margin-bottom: 6px;
}

.notify-devices .sub[role="status"] {
  margin-top: 6px;
  margin-bottom: 0;
}

.field {
  flex: 1 1 150px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.field label {
  display: block;
  margin-bottom: 4px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
}

/* The donate modal's label sits directly under a .sub paragraph rather than
   another field, so it needs its own breathing room above it. */
#donate-modal label {
  margin-top: 10px;
}

.field input,
.field select {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  padding: 6px 0;
}

/* A range slider (Bot Profiles' aggression/economy/building split) isn't a
   text field: the rule above's border-bottom/padding reads as a stray line
   under the track, and the readout percentage sits in the <label> itself
   (see admin/bots.blade.php), not a separate line. */
.field input[type="range"] {
  border-bottom: 0;
  padding: 8px 0 0;
  accent-color: var(--a1);
}

/* Excludes .pill-toggle-option: that label is a radio pill (Equip Title,
   Auction House item/gem offers, resource-type pickers), never the plain
   field-label-plus-readout this rule targets, and its own child spans carry
   deliberate quality/resource coloring (.q-*, .resource-chip) that this
   rule's flat ink color was stomping via higher selector specificity. */
.field label:not(.pill-toggle-option) span {
  float: right;
  color: var(--ink);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}

/* Feature 40: contact form message box. The rule above covers input/select but
   not textarea, so the message field needs its own full-width, boxed styling;
   a darker fill on dark mode so it reads as a real fillable panel. */
.policy-page .field textarea {
  width: 100%;
  min-height: 190px;
  resize: vertical;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--soft);
  color: var(--ink);
  padding: 11px 13px;
  font: inherit;
}

.policy-page .field textarea:focus {
  outline: none;
  border-color: var(--a1);
}

body.dark .policy-page .field textarea {
  background: color-mix(in srgb, var(--bg) 72%, #000 28%);
}

.btn-primary {
  display: inline-block;
  font: 600 11px/1 var(--sans);
  letter-spacing: 2px;
  text-transform: uppercase;
  text-decoration: none;
  text-align: center;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--ink);
  padding: 12px 22px;
  cursor: pointer;
}

/* :not([disabled]), not just a plain :hover: disabling a button the
   instant it's clicked (Wraithcall's Attack does this) can leave the
   pointer still resting on it without a mouseleave ever firing, since a
   disabled element stops receiving pointer events entirely. Without this
   guard the button gets stuck looking hovered (filled solid) underneath
   its own dimmed [disabled] opacity instead of returning to its plain
   outlined look. */
.btn-primary:not([disabled]):hover {
  background: var(--a1);
  border-color: var(--a1);
  color: var(--bg);
  text-decoration: none;
}

.btn-primary[disabled],
.btn-ghost[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}

/* Compact challenge-action buttons on a public profile (Spy/Duel/War/
   Raven): a row of pill buttons instead of a grid of large .attribute-card
   blocks, with the energy cost shown inline as an icon+number rather than
   a separate prose line. */
/* The [hidden] attribute must win over any author display rule (e.g. a hidden
   .action-row would otherwise stay visible because .action-row sets display:flex). */
[hidden] {
  display: none !important;
}

.action-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

/* Reply sits directly under a raven's message body, so it needs breathing room. */
.raven-reply {
  margin-top: 18px;
}

.btn-action {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
}

.btn-action .resource-chip {
  text-transform: none;
  letter-spacing: normal;
  font-weight: 600;
}

/* .btn-primary:hover inverts to a solid --a1 background; without this,
   .resource-chip.energy's hard-coded var(--a1) text would render
   invisible (amber-on-amber) on hover. :not([disabled]) for the same
   reason as .btn-primary:hover above: a disabled button can stay stuck
   matching :hover with no fill to compensate for, which would otherwise
   force this chip's text to inherit the button's plain (non-inverted)
   color instead of its own. */
.btn-action:not([disabled]):hover .resource-chip {
  color: inherit;
}

/* Same reasoning as .attribute-card-actions .upgrade-hint: the default
   .upgrade-hint opens to the left, which would overlap a neighboring
   button in this horizontal row. Drop it below instead. */
.action-row .upgrade-hint {
  right: auto;
  left: auto;
  top: calc(100% + 8px);
  transform: translateX(var(--hint-shift, 0px));
}

.btn-plus {
  width: 34px;
  height: 34px;
  padding: 0;
  font-size: 20px;
  line-height: 1;
  letter-spacing: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.btn-plus-locked {
  opacity: 0.45;
  cursor: help;
}

.upgrade-tooltip {
  position: relative;
  display: inline-flex;
  text-decoration: none;
}

.upgrade-hint {
  position: absolute;
  right: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%) translateX(var(--hint-shift, 0px));
  z-index: 100;
  background: color-mix(in srgb, var(--bg) 92%, #000 8%);
  border: 1px solid var(--line);
  color: var(--ink);
  padding: 6px 9px;
  border-radius: 3px;
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
}

.upgrade-tooltip:hover .upgrade-hint,
.upgrade-tooltip:focus-within .upgrade-hint,
.upgrade-tooltip.is-open .upgrade-hint {
  opacity: 1;
}

.energy-trigger,
.hp-trigger,
.resource-trigger {
  border: 0;
  background: transparent;
  font: inherit;
  color: inherit;
  line-height: inherit;
  margin: 0;
  padding: 0;
  cursor: pointer;
}

/* Shared multi-line hint box for every header chip's hover/tap popup
   (energy, HP, and all four resources): stacked lines, anchored below the
   chip instead of the .upgrade-hint default (which assumes a right-aligned
   trigger like a locked upgrade button). */
.energy-hint,
.hp-hint,
.chip-hint {
  display: flex;
  flex-direction: column;
  gap: 4px;
  right: 0;
  left: auto;
  top: calc(100% + 10px);
  transform: translateX(var(--hint-shift, 0px));
  white-space: nowrap;
  text-transform: none;
  letter-spacing: 0.2px;
  text-align: left;
}

.section {
  margin-top: 25px;
}

/* The dashboard Events block sits above the rest of the page; give it a little
   extra breathing room below so the live queues read as their own group. */
#events {
  margin-bottom: 40px;
}

/* The first section on a page sits right under the nav; the 25px top rhythm
   is only meant to separate one section from the next, never to pad the top
   of the page (most visible on the public profile, whose h1 lives inside a
   .section). */
.page > .section:first-child {
  margin-top: 0;
}

.page .sub.event-line {
  margin-bottom: 4px;
}

.section h2,
.page h3 {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--faint);
  margin-top: 10px;
  margin-bottom: 12px;
}

.races {
  display: grid;
  gap: 2px;
  max-width: 560px;
}

.race {
  display: flex;
  align-items: baseline;
  gap: 16px;
  padding: 11px 0;
  border-bottom: 1px solid var(--line);
}

.race:first-child {
  border-top: 1px solid var(--line);
}

.swatch {
  width: 7px;
  height: 22px;
  border-radius: 2px;
  flex: none;
}

.race .name {
  width: 84px;
  font: 600 17px/1 var(--serif);
}

.race .words {
  font: italic 500 14px/1.3 var(--serif);
  color: var(--muted);
}

.human .swatch { background: linear-gradient(var(--human1) 50%, var(--human2) 50%); }
.orc .swatch { background: linear-gradient(var(--orc1) 50%, var(--orc2) 50%); }
.dwarf .swatch { background: linear-gradient(var(--dwarf1) 50%, var(--dwarf2) 50%); }
.elf .swatch { background: linear-gradient(var(--elf1) 50%, var(--elf2) 50%); }
.undead .swatch { background: linear-gradient(var(--undead1) 50%, var(--undead2) 50%); }

.layout {
  margin-top: 28px;
  display: grid;
  grid-template-columns: 175px 1fr;
  gap: 44px;
  align-items: start;
}

.realm-nav {
  position: sticky;
  /* Set to the real, measured height of the sticky .kow-top header by
     kow.js (it varies with content: status badges, admin extras, wrapped
     resource chips), falling back to a rough guess before JS runs. Without
     the live measurement, a taller-than-usual header hides the first link
     (Profile) underneath it while scrolling. */
  top: var(--realm-nav-top, 96px);
  align-self: start;
}

.realm-nav a {
  display: block;
  padding: 6px 0;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.realm-nav a:hover,
.realm-nav a.active {
  color: var(--a1);
  text-decoration: none;
}

.realm-nav-mobile {
  display: none;
}

/* Desktop nav links (feature: sidebar redesign): a left accent tick marks
   the active page instead of a plain color swap, and hover nudges the
   whole row right instead of just recoloring it. Kept on its own class
   (not the bare `.realm-nav a`) so the mobile bottom tab bar, the footer's
   online/admin rows below and this active/hover treatment never collide. */
.realm-nav-desktop a.realm-nav-link {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-left: 14px;
  transition: color .12s ease, padding-left .12s ease;
}

.realm-nav-desktop a.realm-nav-link:hover {
  color: var(--ink);
  padding-left: 18px;
}

.realm-nav-desktop a.realm-nav-link.active {
  color: var(--ink);
}

.realm-nav-desktop a.realm-nav-link.active::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 14px;
  background: var(--a1);
}

.realm-nav a.realm-nav-admin {
  margin-top: 18px;
  padding-top: 12px;
  padding-left: 14px;
  border-top: 1px solid var(--line);
  font-size: 10px;
  letter-spacing: 2px;
  color: var(--faint);
}

.realm-nav a.realm-nav-admin:hover,
.realm-nav a.realm-nav-admin.active {
  color: var(--ink);
}

/* Group separators: a hairline fading to transparent on the right, echoing
   the accent-tick treatment above, instead of a solid full-width rule. */
/* Margin (not padding) carries the extra gap, so every link keeps the same
   symmetric 6px top/bottom padding regardless of whether it follows a
   divider; the active accent tick above is centered on that padding box,
   so asymmetric padding here would have thrown it out of line with the
   text on divider items. */
.realm-nav-desktop a.nav-divider {
  margin-top: 14px;
}

.realm-nav-desktop a.nav-divider::after {
  content: "";
  position: absolute;
  left: 14px;
  right: 0;
  top: -7px;
  height: 1px;
  background: linear-gradient(90deg, var(--line) 55%, transparent);
}

.realm-nav-online {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

/* `.realm-nav a { display: block }` outranks a bare `.presence` class (same
   specificity fight as `.realm-nav-admin-stats` below), which drops the
   `.presence-dot` out of flex layout and collapses it, so the ancestor
   selector needs to win here too. */
.realm-nav .realm-nav-online a.presence {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 14px;
}

.realm-nav-online .presence {
  font-size: 10px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--faint);
}

.realm-nav-online [data-vital="realm-online"] {
  color: var(--muted);
  font-weight: 600;
}

.page h1,
.page h2 {
  font: 600 32px/1.05 var(--serif);
  margin-bottom: 8px;
}

/* A page's <h1> plus its wiki-help-link, side by side on one line; the
   row carries the margin the bare h1 used to, so wrapping it never
   changes the page's vertical rhythm. */
.title-help {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}

.title-help h1,
.title-help h2 {
  margin-bottom: 0;
}

/* World Event pages (Wraithcall/Twin Wyrms/Mirrorfiend): the title, scene
   line and clock as one centered header block, same "hero" treatment the
   dashboard already gives its own profile header (.profile-hero). */
.event-hero {
  text-align: center;
  padding: 6px 0 22px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 6px;
}

.event-hero .title-help {
  justify-content: center;
}

/* The scene-setting line under the title reads as a flavor sentence, not a
   form label, so it gets the same italic serif treatment as other in-world
   narration (battle report finales, ravens); the "time left"/"starts in"
   clock sits on its own small uppercase line beneath rather than folded
   into that sentence. */
.event-scene {
  font: italic 500 14.5px/1.55 var(--serif);
  color: var(--muted);
  margin: 0 auto 4px;
  max-width: 480px;
}

.event-clock {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0;
}

/* The pending "waiting for the event to start" state reads as an event in
   itself, not a footnote, so its own icon moves above the title (instead of
   inline with it) and its countdown gets the same big-serif treatment as a
   page's own h1, colored like the event it belongs to rather than the muted
   uppercase label the running "time left" clock uses. */
.event-hero.is-waiting {
  padding-top: 4px;
}

.event-hero .event-waiting-icon {
  display: block;
  margin: 0 auto 10px;
  color: var(--q-legendary);
}

.event-hero .twinwyrms-title-icons.event-waiting-icon {
  display: flex;
  justify-content: center;
  margin-right: 0;
}

.event-clock-pending {
  font: 600 30px/1.1 var(--serif);
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  margin: 10px 0 0;
}

/* Active-heroes line: one plain sentence, no section heading. Normal sans,
   not italic serif - the numbers in it need to stay easy to read at a
   glance, not read as flavor text. */
.event-heroes {
  font: 400 13px/1.5 var(--sans);
  color: var(--faint);
}

.event-heroes span {
  color: var(--muted);
  font-weight: 600;
}

/* Same row, scaled down for a section's <h3> instead of the page's own
   <h1>: tabbed pages (Camp, Gear) carry more than one distinct wiki topic,
   so each tab's own heading gets its own help link straight to that
   topic's anchor rather than only the page-level one. */
.title-help.is-sm {
  gap: 6px;
  margin-bottom: 18px;
}

/* A small "view more" link riding along on a .title-help row, pushed to
   the far right of the heading instead of sitting on its own line below
   the section's content. */
.section-action-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: auto;
  font-size: 11px;
  color: var(--muted);
}

.section-action-link:hover {
  color: var(--ink);
}

/* In a narrow flex row (dashboard's two-column layout), a flex item shrinks
   to fit before its text is allowed to overflow, so a longer heading like
   "Friends & Enemies" can wrap to two lines; align-items: center then
   centers the wiki-help-link against both lines instead of the first one,
   reading as misaligned. Keep every .title-help.is-sm heading on one line;
   there's always room, the flex row itself just needs to stop shrinking it. */
.title-help.is-sm h3 {
  margin-top: 0;
  margin-bottom: 0;
  white-space: nowrap;
  flex: none;
}

/* The small uppercase .page h3 label has no descenders, so its glyphs sit
   near the top of their own line box; centering the wiki-help-link icon
   against the full line box (align-items: center) then reads as sitting
   too low next to it. Nudge it up a couple pixels to match the text's
   visual weight instead of its box center. */
.title-help.is-sm .wiki-help-link {
  margin-top: -4px;
}

/* Gem Pouch sits mid-section, below the Satchel table, unlike the other
   .title-help.is-sm rows which open their own section and so already get
   the section's own top spacing. */
.title-help.is-sm.gem-pouch-head {
  margin-top: 24px;
}

/* Titles section (feature 89): pull the title list up closer to its own
   "Titles" heading than the generic .title-help.is-sm gap allows. */
#titles {
  margin-bottom: 2px;
}

.wraithcall-title-icon {
  vertical-align: -4px;
  margin-right: 6px;
  color: var(--q-legendary);
}

/* The Twin Wyrms' own page title: both wyrms, not one purple mark, so it
   reads the same "two, bonded" idea the rest of the page (two bars, two
   target pills) already carries. The second is a mirror image of the
   first (Pale Wyrm faces its own twin) rather than a second copy of the
   same artwork, with a small overlap so the pair reads as one mark. */
.twinwyrms-title-icons {
  display: inline-flex;
  align-items: center;
  vertical-align: -4px;
  margin-right: 6px;
}

.twinwyrms-title-icons .wraithcall-title-icon {
  margin-right: 0;
  vertical-align: baseline;
}

/* Overlap is positional (the second icon), independent of which of the two
   currently carries .twinwyrms-title-icon-mirror. */
.twinwyrms-title-icons .wraithcall-title-icon + .wraithcall-title-icon {
  margin-left: -10px;
}

.twinwyrms-title-icons .wyrm-pale {
  color: var(--wyrm-pale);
}

.twinwyrms-title-icons .wyrm-black {
  color: var(--wyrm-black);
}

.twinwyrms-title-icon-mirror {
  transform: scaleX(-1);
}

.wiki-help-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--faint);
  flex: none;
  /* Only matters where this sits inline in running text (e.g. a
     changelog entry's {{wiki:key}} token); ignored by flex parents
     (.title-help, .meter-label-group, .wiki-topbar-right). */
  vertical-align: -2px;
}

.wiki-help-link:hover {
  color: var(--a1);
}

.wiki-help-link svg {
  display: block;
}

.profile-title {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.profile-title-icon {
  width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tone-a, var(--a1));
}

.profile-title-icon svg {
  width: 100%;
  height: 100%;
}

.eyebrow {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: var(--a1);
  margin-bottom: 6px;
}

.page .sub {
  color: var(--muted);
  margin-bottom: 24px;
}

/* A plain horizontal divider between page sections, used sparingly (public
   profile below Profile Description, dashboard above the XP/Honor meters)
   where a thin rule reads clearer than pure whitespace. */
.section-divider {
  margin: 20px 0;
  border: none;
  border-top: 1px solid var(--line);
}

/* A .section immediately after the divider already gets its visual gap
   from the divider's own margin; without this, .section's own 25px
   top margin would stack on top of it, making the gap below the divider
   noticeably bigger than the gap above it. */
.section-divider + .section {
  margin-top: 0;
}

.own-profile-note {
  margin-top: 20px;
}

/* Public profile: guild name/rank, tight against its own block now that
   the surrounding spacing/divider is handled by .profile-block. Selector
   needs to match ".page .sub"'s specificity (two classes) to actually win. */
.page .sub.profile-guild-line {
  margin: 0;
}

/* Public profile (feature 89 follow-up): each identity block (Titles,
   Guild, Profile Description) gets a thin top divider so the stacked info
   under the name reads as separate groups on mobile. On desktop, Titles
   and Guild (grouped in .profile-info-row) sit side by side instead,
   separated by a vertical rule rather than stacking with a horizontal one. */
.profile-block {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.profile-block .title-help.is-sm,
.profile-block h3:first-child,
.profile-block #titles {
  margin-bottom: 8px;
}

.profile-block .sub:last-child {
  margin-bottom: 0;
}

.profile-info-row .profile-block {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.profile-info-row .profile-block + .profile-block {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

.profile-info-row {
  margin-top: 16px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

@media (min-width: 620px) {
  .profile-info-row {
    display: flex;
    align-items: flex-start;
    gap: 24px;
  }

  .profile-info-row .profile-block {
    flex: 1;
    min-width: 0;
  }

  .profile-info-row .profile-block + .profile-block {
    margin-top: 0;
    padding-top: 0;
    border-top: none;
    padding-left: 24px;
    border-left: 1px solid var(--line);
  }
}

/* Dashboard hero header: the player's own profile as a small cinematic
   block up top (race sigil, equipped title as an honorific, a large serif
   name, then the five hero stats as base/effective-with-gear pairs)
   instead of the compact inline .player-identity used everywhere else. */
.profile-hero {
  text-align: center;
  padding: 6px 0 22px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 24px;
}

/* Public profile: no .hero-stats row sits at the bottom (unlike the
   dashboard), so the same 22px bottom padding just pushed the actions row
   further away than needed. */
.profile-hero.is-compact {
  padding-bottom: 0;
  margin-bottom: 8px;
}

.profile-hero-icon {
  width: 40px;
  height: 40px;
  margin: 0 auto 8px;
  color: var(--a1);
}

.profile-hero-icon svg {
  width: 100%;
  height: 100%;
}

/* Guild public profile: the banner is its own shield graphic (not a race
   sigil svg), so it needs its own box size instead of the 40px race icon
   default, still centered the same way as .profile-hero-icon. */
.guild-profile-banner {
  width: auto;
  height: auto;
  margin: 0 auto 10px;
}

.profile-hero-honorific {
  /* No color here: the .q .q-* class carried on this element (same one
     the title chips use) is what colors it, so the honorific always
     matches its title's own quality color instead of a fixed race tint. */
  font: italic 600 20px var(--serif);
  letter-spacing: .3px;
}

.profile-hero-name {
  justify-content: center;
  margin-top: 2px;
}

.profile-hero h1 {
  font: 600 clamp(30px, 6vw, 42px)/1.05 var(--serif);
  letter-spacing: .3px;
}

.profile-hero h1 .outlaw-badge {
  vertical-align: middle;
  margin-left: 8px;
}

.profile-hero-subline {
  margin: 6px 0 0;
}

/* Wounded / In Dungeon: written out in full here (unlike the bare status
   dot next to the name in list rows, see .status-dot), right under the
   online/race/level line since this is the one place a visitor reads a
   profile in full rather than scanning a list. Colored via the same
   .status-infirmary/.status-dungeon classes the header badge uses;
   needs the two-class ".page .profile-hero-status" specificity to beat
   ".page .sub"'s own color rule (also two classes), since this element
   carries .sub too for its spacing/base size. */
.profile-hero-status {
  margin: 4px 0 0;
  font-weight: 600;
}

.page .profile-hero-status.status-infirmary {
  color: #b43a3a;
}

.page .profile-hero-status.status-dungeon {
  color: var(--q-legendary);
}

.page .profile-hero-status.status-shield {
  color: #4a7fb5;
}

.profile-hero-flavor {
  font: italic 500 14px var(--serif);
  color: var(--muted);
  margin-top: 8px;
}

.hero-stats {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 20px;
}

.hero-stat {
  padding: 0 22px;
  border-left: 1px solid var(--line);
  text-align: center;
  min-width: 92px;
}

.hero-stat:first-child {
  border-left: none;
}

.hero-stat-num {
  font: 600 21px/1.1 var(--serif);
}

.hero-stat-num small {
  font: 500 15px var(--serif);
  color: var(--faint);
}

.hero-stat-label {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--faint);
  margin-top: 2px;
}

@media (max-width: 620px) {
  .hero-stat {
    padding: 0 14px;
    min-width: 0;
    flex: 1 1 30%;
    border-left: none;
    margin-top: 10px;
  }
}

/* The two "who you are / what you hold" columns below the hero header and
   any active events, stacking to one column on mobile. */
.profile-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 48px;
}

@media (max-width: 620px) {
  .profile-cols {
    grid-template-columns: 1fr;
  }
}

/* Titles rendered as a row of chips (dashboard hero columns) rather than
   the middle-dot-separated sentence used on the public profile page; the
   equipped one gets a small trailing label instead of a separate tooltip. */
.title-chip-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
}

.title-chip-row .title-badge.is-equipped::after {
  content: "equipped";
  font: 400 9.5px var(--sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--faint);
  margin-left: 6px;
}

/* Per-hour resource ledger (dashboard right column): a label on the left,
   the real resource-chip value on the right, one row per resource. */
.ledger-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
  font-size: 12.5px;
}

.ledger-row:last-child {
  border-bottom: none;
}

.ledger-row-label {
  color: var(--muted);
}

/* Storage-fill bar on its own full-width line under a ledger row's
   label + per-hour chip: how full that resource's stockpile is against its
   cap. The bar is its own hover/tap tooltip (stockpile / cap + percentage);
   it turns red once the stockpile is full, since a full store means
   production is being wasted. */
.ledger-fill {
  flex: 1 1 100%;
  margin-top: 6px;
}

.ledger-fill .ledger-fill-track {
  flex: 1;
  display: flex;
  height: 4px;
  border-radius: 2px;
  background: color-mix(in srgb, var(--line) 55%, transparent);
  overflow: hidden;
}

.ledger-fill-bar {
  display: block;
  flex: none;
  height: 100%;
  border-radius: 2px;
  width: var(--fill, 0%);
  background: currentColor;
  opacity: 0.75;
}

.ledger-fill-bar.gold { color: #c8a13a; }
.ledger-fill-bar.wood { color: #7a4b2b; }
.ledger-fill-bar.stone { color: #8a919a; }
.ledger-fill-bar.food { color: #c9762f; }

.ledger-fill-bar.is-full {
  color: #b43a3a;
  opacity: 1;
}

/* The slice of the bar parked in Auction House escrow: red, sits directly
   after the stockpile segment (the track is a flex row). Escrow occupies
   storage until the listing sells, so it counts toward the bar's fill. */
.ledger-fill-bar.is-escrow {
  color: #b43a3a;
  opacity: 1;
}

/* Public profile redesign: reuses .profile-hero (icon/honorific/name/
   subline) from the dashboard hero header above, just without its
   .hero-stats row, plus a centered bio line and a centered action/record
   layout below it instead of the dashboard's two-column one. */
.profile-hero-quote {
  font: italic 500 19px var(--serif);
  color: var(--muted);
  margin-top: 12px;
  margin-bottom: 16px;
  max-width: 720px;
  margin-left: auto;
  margin-right: auto;
}

.profile-actions {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 12px;
  padding: 10px 0 22px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 24px;
}

/* Forces Send a Raven/Reinforce onto their own line on mobile only: a
   flex-basis: 100% item always starts a new flex line regardless of
   available space, so this must stay hidden/inert on desktop where all
   five actions share one row. */
.profile-actions-break {
  display: none;
}

@media (max-width: 620px) {
  .profile-actions-break {
    display: block;
    flex-basis: 100%;
    height: 0;
    /* .profile-actions' own 12px gap applies above AND below this zero-height
       line (once to the row before it, once to the row after), doubling the
       visual gap between the two button rows; this cancels one side back out. */
    margin-bottom: -12px;
  }

  /* The actual flex children of .profile-actions are the <form>/<a>/<button>/
     .upgrade-tooltip wrappers, not the .btn-action inside them, so equal
     widths have to be set here, with the button itself stretched to fill it. */
  .profile-actions > form,
  .profile-actions > a,
  .profile-actions > button,
  .profile-actions > .upgrade-tooltip {
    flex: 1 1 0;
    min-width: 0;
  }

  /* Spy/Duel/War (before the break) split the first line evenly; Raven/
     Reinforce (after it) split the second line evenly, so both rows read
     as the same total width even though they hold a different button count. */
  .profile-actions .btn-action {
    width: 100%;
    justify-content: center;
  }
}

/* The Spy action grew a mission-type and spy-count choice (espionage
   rework, whatsnext.md item 4). A temporary compact form until the full
   dossier's own "Send spies" controls (feature TBD) replace it. */
.spy-mission-form {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
}

.spy-mission-form label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin: 0;
}

.spy-mission-form select,
.spy-mission-form input[type="number"] {
  width: 84px;
  font-size: 12px;
}

/* Raven/Reinforce sit next to Spy/Duel/War as secondary actions: same pill
   shape and sizing as .btn-primary, just muted, so they read as lower
   priority without a completely different button language. */
.btn-primary.is-quiet {
  border-color: var(--line);
  color: var(--muted);
}

.btn-primary.is-quiet:not([disabled]):hover {
  background: none;
  border-color: var(--faint);
  color: var(--ink);
}

.profile-centered-section {
  text-align: center;
  margin-bottom: 30px;
}

.profile-centered-section h3 {
  text-align: center;
}

/* Honor row: the exact .compact-meters/.meter-row + <x-honor-bar> markup
   the dashboard uses, just centered as a block (text-align: left restores
   the label-left/value-right row .profile-centered-section's own centering
   would otherwise break) instead of full column width. */
.compact-meters.profile-honor-meter {
  max-width: 420px;
  margin-left: auto;
  margin-right: auto;
  text-align: left;
}

.profile-centered-section .battle-stats-block {
  max-width: 560px;
  margin-left: auto;
  margin-right: auto;
}

.profile-centered-section .battle-stats-tabs,
.profile-centered-section .battle-stats-block .grid-figures {
  justify-content: center;
}

.profile-centered-section .battle-stats-block .grid-figures {
  gap: 4px 28px;
}

/* The per-type breakdown (duels/wars/hunts/camps/dungeons), the row after
   .fig-break: small inline "23 duels" pairs, not the big stacked
   number-over-label blocks the headline record (battles/won/lost/win rate)
   uses. General sibling selector, since .fig-break only ever precedes this
   second group within the block. */
.profile-centered-section .battle-stats-block .fig-break ~ .fig {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
}

.profile-centered-section .battle-stats-block .fig-break ~ .fig b {
  display: inline;
  font: 600 12.5px var(--sans);
  color: var(--ink);
}

.profile-centered-section .battle-stats-block .fig-break ~ .fig span {
  text-transform: none;
  letter-spacing: normal;
  font-size: 12px;
  color: var(--muted);
}

.profile-guild-note {
  color: var(--muted);
  font-size: 12px;
  margin-top: 12px;
}

.profile-guild-note b {
  color: var(--gold);
  font-weight: 600;
}

/* Separates "Your Standing" from the Titles/Guild section right above it;
   .profile-centered-section's own margin-bottom on that previous block
   isn't enough on its own once the guild line shrinks its height. */
#standing {
  margin-top: 20px;
}

.profile-standing-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
  margin-top: 12px;
}

/* Empty state (no history yet): centered like the other empty placeholders
   on this page instead of left-aligned under a left-aligned heading, with
   the message itself reading as a muted aside (matches .bonds-empty). */
#player-history.is-empty h3 {
  text-align: center;
}

.history-empty {
  text-align: center;
  font-style: italic;
  font-size: 12px;
}

/* History with this lord: a thin single-line row (verdict, what, when)
   matching the dashboard's other lists, instead of a bordered-card
   .leaderboard-row. Only used here, so scoped to #player-history. */
#player-history .history-row {
  display: flex;
  align-items: baseline;
  gap: 14px;
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  color: inherit;
  text-decoration: none;
}

#player-history .history-row:last-child {
  border-bottom: none;
}

#player-history .history-row:hover {
  text-decoration: none;
}

#player-history .history-row:hover .history-what a {
  text-decoration: underline;
}

.history-verdict {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  width: 76px;
  flex: none;
}

.history-verdict.is-victory {
  color: #3a8f4f;
}

.history-verdict.is-defeat {
  color: #b43a3a;
}

.history-what {
  flex: 1;
  min-width: 0;
}

.history-what a {
  border-bottom: 1px dotted var(--faint);
}

.history-when {
  color: var(--faint);
  font-size: 11.5px;
  white-space: nowrap;
}

/* Spy dossier (espionage rework, whatsnext.md item 4): a two-panel spread
   reusing the same tokens the rest of the game already themes with (--serif
   headings, --muted/--faint text, --q-* quality colors, resource-chip). The
   form always renders every field it can reveal; unknown ones show a muted
   "?" in the value's own slot so nothing shifts once intel lands. */
#dossier {
  text-align: center;
}

.dossier-eyebrow {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: var(--faint);
}

.dossier-title {
  font-family: var(--serif);
  font-weight: 600;
  font-size: 26px;
  margin: 2px 0 0;
}

.dossier-lastword {
  font-size: 11.5px;
  color: var(--faint);
  margin: 4px 0 0;
}

.dossier-panels {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 40px;
  margin-top: 22px;
  text-align: left;
}

@media (max-width: 620px) {
  .dossier-panels {
    grid-template-columns: 1fr;
    gap: 24px 0;
  }
}

.dossier-panel h4 {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 1.6px;
  color: var(--faint);
  margin: 0 0 4px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

.dossier-panel h4 > span:first-child {
  flex: 1;
}

.dossier-row {
  padding: 7px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}

.dossier-row:last-child {
  border-bottom: none;
}

.dossier-row.is-stale {
  opacity: 0.62;
}

/* Label and age share the row's top line; the value (item name, range,
   "?") sits on its own line underneath, since a full item/resource line
   can run long and fighting the label for horizontal space read cramped. */
.dossier-row-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.dossier-row-label {
  color: var(--muted);
  font-size: 12px;
}

/* How long ago a known field was learned, always visible (not hover-only,
   a hidden tooltip turned out too easy to miss). */
.dossier-row-age {
  color: var(--faint);
  font-size: 11px;
  font-style: italic;
  white-space: nowrap;
}

.dossier-row-value {
  display: inline-block;
  margin-top: 2px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.dossier-row-to {
  font-weight: 400;
  color: var(--faint);
  margin: 0 4px;
  font-size: 12px;
}

.dossier-row-unknown {
  display: inline-block;
  margin-top: 2px;
  color: var(--faint);
}

.dossier-row-empty {
  display: inline-block;
  margin-top: 2px;
  color: var(--faint);
  font-style: italic;
}

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

small.hint {
  font-size: 12px;
}

.profile-rank {
  color: var(--muted);
  margin-bottom: 4px;
}

.profile-rank-description {
  color: var(--muted);
  font-style: italic;
  margin-bottom: 24px;
}

.compact-meters {
  margin: 10px 0 24px;
}

.meter-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 6px;
}

.meter-label {
  font-size: 11px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--faint);
}

/* Groups a .meter-label with a small <x-wiki-help-link> as one flex child,
   so .meter-row's space-between still pushes only the value to the right. */
.meter-label-group {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.meter-label-strong {
  color: var(--ink);
  letter-spacing: 0.4px;
}

.meter-value {
  font-size: 12px;
  color: var(--muted);
}

.bar {
  height: 3px;
  background: var(--line);
  border-radius: 2px;
  overflow: hidden;
  margin-bottom: 12px;
}

.bar i {
  display: block;
  height: 100%;
  background: linear-gradient(to right, var(--a1), var(--a2));
  transform-origin: left;
}

/* Each hero's HP bar in the duel/war arena picks up their own race colors
   (--tone-a/--tone-b, set by .race-{race} on the surrounding .duel-side,
   same source as that side's icon color and panel accent) instead of the
   viewer's own --a1/--a2, so the opponent's bar doesn't borrow your colors. */
.duel-side .bar i {
  background: linear-gradient(to right, var(--tone-a, var(--a1)), var(--tone-b, var(--a2)));
}

/* Two stacked bars instead of one: the plain <i> above is the real fill, the
   .bar-damage <i> underneath it (scoped to the arena only, not the base
   .bar used all over the site for queues/resource caps) shows a "damage
   flash": kow.js jumps it to the pre-hit width the instant a hit lands, then
   lets it animate back down to the new (lower) width over 500ms, so a chunk
   of dark red visibly drains away instead of the bar just snapping shorter. */
.duel-side .bar {
  position: relative;
}

.duel-side .bar i {
  position: absolute;
  top: 0;
  left: 0;
}

.duel-side .bar i.bar-damage {
  background: #7a1f1f;
  transition: width 500ms ease;
}

.table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 10px;
}

.table th,
.table td {
  text-align: left;
  padding: 8px 24px 8px 0;
  border-bottom: 1px solid var(--line);
}

.table th:last-child,
.table td:last-child {
  padding-right: 0;
}

/* Admin > Deeds' player table: 8-9 columns (name, Standing, Zeal,
   Goodwill, one per deed, last active) need to read as one row without
   wrapping, so headers shrink to short labels/icons and every cell stays
   on a single line; the wrap only scrolls if a screen is still too
   narrow. */
/* Generic horizontal-scroll wrapper for admin tables: on a phone .layout
   goes full-width but a table's columns (identity, dates, action links)
   still need their natural width, so the wrap scrolls instead of letting
   body's overflow-x: hidden silently clip the right-hand columns. */
.admin-table-wrap {
  overflow-x: auto;
}

.admin-deeds-table-wrap {
  overflow-x: auto;
}

.admin-deeds-table {
  font-size: 12px;
}

.admin-deeds-table th,
.admin-deeds-table td {
  padding: 6px 12px 6px 0;
  white-space: nowrap;
}

.admin-deeds-table th {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: var(--faint);
}

.admin-deeds-table th svg {
  vertical-align: -2px;
  color: var(--muted);
}

/* The Infirmary list reuses the Leaderboard's hairline row, but each row
   carries a second line (Hospital::reasonLabel()) inside the same row, so
   name/discharge/reason read as one record. */
.leaderboard-row.infirmary-row {
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
}

.infirmary-row-main {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 10px;
}

.infirmary-row-main .player-identity {
  flex: 1 1 auto;
  min-width: 0;
  /* Not overflow: hidden. The name's own ellipsis (.leaderboard-row b)
     already truncates it, and this parent clipping would also cut off
     the corner presence dot poking a few px past its icon (see
     .player-identity-icon .presence) and its hover tooltip. */
}

.infirmary-row-link {
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
  /* Not overflow: hidden, same reasoning as .infirmary-row-main
     .player-identity just below: it would clip the corner presence dot
     and its hover tooltip too. */
  color: inherit;
  text-decoration: none;
}

.infirmary-discharge {
  margin-left: auto;
  flex: none;
  white-space: nowrap;
  font-size: 12px;
  color: var(--muted);
}

/* The "help them recover" button sits after the discharge countdown,
   which already carries margin-left: auto (the presence dot now lives on
   the race icon's own corner, see .player-identity-icon .presence, so it
   no longer takes part in this row's alignment), so it lands flush right
   on the row without its own alignment rule. */
.infirmary-help-form {
  flex: none;
  margin-left: 6px;
}

.infirmary-reason {
  font-size: 11px;
  font-style: italic;
  color: var(--muted);
}

.infirmary-release-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.table th {
  font-size: 10.5px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
}

.table .num,
.num {
  text-align: right;
}

.resource-label {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.res-icon {
  flex: none;
  stroke: currentColor;
}

.resource-label.gold,
.resource-chip.gold {
  color: #c8a13a;
}

.resource-label.wood,
.resource-chip.wood {
  color: #7a4b2b;
}

.resource-label.stone,
.resource-chip.stone {
  color: #8a919a;
}

.resource-label.food,
.resource-chip.food {
  color: #c9762f;
}

/* A resource-chip showing a loss (Battle Reports: resources plundered from
   you) instead of a gain: same icon, but red like every other "lost X"
   value on the site, overriding whichever resource colour it'd normally
   carry. */
.resource-chip.is-negative {
  color: #b43a3a;
}

/* Favor chip: gold, the seal's colour. Used inside the Favor Hall's "Choose"
   buttons as the cost, the same way a gold cost sits inside a buy button. */
.resource-chip.favor {
  color: var(--gold);
}

/* Server clock chip: blue, the "time" resource colour (reuses the Superior
   quality blue token). Icon-only, sits to the right of the resource line. */
.resource-chip.clock {
  color: var(--q-superior);
}

/* The Wraithcall kill feed's inline "Wraith" chip (Wraithcall::
   highlightWraith()): the Legendary-quality purple, ghostly rather than
   tied to any real resource. */
.resource-chip.wraith {
  color: var(--q-legendary);
}

/* Header dungeon timer chip: only shown while a run is active, the Mythic
   red signals the run can time out. */
.resource-chip.dungeon {
  color: var(--q-mythic);
}

/* A gear item won from a PvE fight, shown inline as a chip with a "Found"
   label, matching how resource rewards read (see components/pve-loot-item). */
.loot-item-line {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.gem-pouch-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
  list-style: none;
  padding: 0;
  margin: 0;
}

.gem-pouch-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.rate-tag {
  margin-left: 2px;
  font-weight: 600;
}

/* Dashboard "Where to next" nudges: a compact, low-key card of one-line
   suggestions with inline links, meant to guide without nagging. */
.next-steps {
  margin-top: 14px;
  margin-bottom: 28px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--a1) 6%, transparent);
}

.next-steps-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--faint);
}

.next-steps ul {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.next-steps li {
  font-size: 13px;
  line-height: 1.4;
  color: var(--muted);
}

.next-steps a {
  color: var(--a1);
  text-decoration: none;
  font-weight: 600;
}

.next-steps a:hover {
  text-decoration: underline;
}

.next-steps-sep {
  color: var(--faint);
}

/* Dashboard browser-push opt-in banner (feature 57): same low-key card look
   as .next-steps, shown/hidden by web-push-settings.js once it knows whether
   this browser supports push and isn't already subscribed. */
.push-banner {
  margin-top: 14px;
  margin-bottom: 28px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: color-mix(in srgb, var(--a1) 6%, transparent);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}

.push-banner p {
  font-size: 13px;
  line-height: 1.4;
  color: var(--muted);
  margin: 0;
}

.push-banner a {
  color: var(--a1);
  text-decoration: none;
  font-weight: 600;
}

.push-banner a:hover {
  text-decoration: underline;
}

.push-banner-dismiss {
  background: none;
  border: none;
  color: var(--faint);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  padding: 0 2px;
}

/* On phones the "Totals per hour:" label crowds the resource chips out; drop
   the label there and let the chips speak for themselves. */
@media (max-width: 620px) {
  .totals-label {
    display: none;
  }
}

.rate-tag.is-neutral {
  color: var(--ink);
}

.rate-tag.is-negative {
  color: #b43a3a;
}

.rate-tag.is-positive {
  color: #3f8f5c;
}

.resource-cost {
  display: inline-flex;
  justify-content: flex-end;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 12px;
}

.resource-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-weight: 600;
  /* A chip sitting right after prose text (e.g. "Forge · <chip>") had its
     icon touching the preceding word with no breathing room. */
  margin-left: 3px;
  /* inline-flex defaults to baseline alignment against surrounding text,
     which an SVG icon's own baseline throws off, reading as sitting a few
     pixels too high; middle overcorrected (still slightly high), so this
     is a small manual nudge, the same idea as .player-identity's -2px. */
  vertical-align: -2px;
}

.ah-post-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 8px;
}

.ah-item-pills {
  flex-direction: column;
  align-items: flex-start;
}

.ah-filter-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 8px 0;
}

.ah-filter-pills a {
  padding: 4px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: 0.85em;
  color: var(--muted);
  text-decoration: none;
}

.ah-filter-pills a.is-active {
  border-color: var(--a1);
  color: var(--ink);
  font-weight: 600;
}

.ah-filter-group {
  border-top: 1px solid var(--line);
  margin-top: 8px;
  padding-top: 6px;
}

.ah-filter-group summary {
  cursor: pointer;
  font-size: 0.9em;
  font-weight: 600;
  list-style: none;
}

.ah-filter-group summary::-webkit-details-marker {
  display: none;
}

.ah-filter-group summary::before {
  content: '\25B8';
  display: inline-block;
  margin-right: 6px;
  color: var(--muted);
  transition: transform 0.15s ease;
}

.ah-filter-group[open] summary::before {
  transform: rotate(90deg);
}

.ah-filter-group summary .sub {
  margin-left: 8px;
  font-weight: 400;
}

.ah-filter-pills-sub {
  margin-top: 8px;
}

.ah-filter-pills-sub a {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.ah-scroll-sentinel {
  height: 1px;
}

.ah-empty-market {
  margin-top: 16px;
  text-align: center;
  font-style: italic;
}

.ah-fee-hint {
  margin-top: 8px;
  margin-bottom: 0;
}

.ah-fee-note {
  margin-top: 2px;
  font-size: 0.85em;
  font-style: italic;
}

/* The "listing sold" raven's buyer identity: the race icon sits visibly low
   against the bold name next to it in this specific running-sentence
   context. Scoped to just this raven (not the shared .player-identity-icon
   every other page uses) since there's no way to check whether nudging it
   globally would misalign it somewhere it currently looks fine. */
.ah-raven-buyer .player-identity-icon {
  position: relative;
  top: -3px;
}

/* A refused action (e.g. "not enough storage room to receive this") needs
   to read as more than a stray line of red text, but still subtle per the
   design brief: same tinted-background-plus-border treatment as .btn-danger
   and the low-condition gear warning, not a loud solid block. */
.ah-alert {
  margin: 10px 0;
  padding: 10px 12px;
  border: 1px solid color-mix(in srgb, #b43a3a 35%, var(--line));
  border-radius: 6px;
  background: color-mix(in srgb, #b43a3a 10%, transparent);
  color: color-mix(in srgb, #b43a3a 80%, black 20%);
}

.ah-alert.is-success {
  border-color: color-mix(in srgb, #3f8f5c 35%, var(--line));
  background: color-mix(in srgb, #3f8f5c 10%, transparent);
  color: color-mix(in srgb, #3f8f5c 80%, black 20%);
}

.ah-listings {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}

.ah-listing-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
}

.ah-listing-main {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.ah-listing-row.is-expired {
  border-color: var(--gold);
}

.ah-expired-badge {
  display: inline-block;
  align-self: flex-start;
  font: 700 10px/1 var(--sans);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--gold);
  border: 1px solid currentColor;
  border-radius: 2px;
  padding: 2px 5px;
}

.ah-listing-seller-line .player-identity {
  flex-wrap: nowrap;
}

.ah-listing-date {
  font-size: 0.85em;
  white-space: nowrap;
  vertical-align: 3px;
}

.ah-listing-line {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;
}

.ah-listing-arrow {
  color: var(--muted);
  font-size: 0.85em;
}

.ah-listing-action {
  margin-left: auto;
  flex-shrink: 0;
}

.ah-proceeds-collect {
  margin-top: 10px;
}

.ah-proceeds-collect .btn-primary {
  width: auto;
}

@media (max-width: 620px) {
  .ah-proceeds-collect form,
  .ah-proceeds-collect .btn-primary {
    width: 100%;
  }
}

.bank-space-bar {
  width: 100%;
  min-width: 0;
  margin-bottom: 0;
  height: 6px;
  background: color-mix(in srgb, var(--line) 70%, #9a7d2e 30%);
}

.bank-space-bar i {
  background: linear-gradient(to right, #d7b14a, #b88a2d);
}

.bank-space-wrap {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 6px;
}

.bank-figures {
  margin-top: 10px;
  margin-bottom: 18px;
}

.bank-vault-note {
  margin-bottom: 18px;
}

.bank-figures .safe {
  color: var(--q-fine);
  font-size: 11px;
  font-weight: 600;
  margin-left: 6px;
}

.stat {
  display: grid;
  grid-template-columns: max-content 44px minmax(0, 1fr);
  align-items: center;
  column-gap: 18px;
  margin-bottom: 8px;
  width: 100%;
}

.stat .num {
  font-weight: 600;
}

.bank-space-note {
  font-size: 10.5px;
  color: var(--muted);
  letter-spacing: 0.4px;
}

.bank-vault-section .table {
  max-width: 720px;
}

.bank-vault-section .table tr:first-child td {
  border-top: 1px solid var(--line);
}

.bank-capacity-section {
  margin-top: 30px;
}

.bank-teller-section {
  margin-top: 36px;
}

.teller {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 56px;
  max-width: 620px;
}

.till h4 {
  font: 600 20px/1.15 var(--serif);
  margin-bottom: 2px;
}

.till .note {
  font-size: 11.5px;
  color: var(--muted);
  margin-bottom: 16px;
}

.bank-teller-form {
  display: block;
}

.amount {
  position: relative;
  margin-bottom: 10px;
}

.amount input {
  width: 100%;
  font: 500 20px/1.3 var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 4px 44px 7px 0;
  border-radius: 0;
  appearance: textfield;
  -moz-appearance: textfield;
}

/* [data-max-warn] (kow.js): typing more than what's actually available
   turns the figure red instead of silently accepting it. */
.amount input.is-over-max {
  color: #b43a3a;
  border-bottom-color: #b43a3a;
}

/* Battle Reports' All/PVP/PVE filter (kow.js, [data-report-filter]).
   Selector is scoped to the two elements it's applied to (not the bare
   class) so its specificity beats .leaderboard-row/.battle-report-row's
   own display rules further down this file. */
.battle-report-row.is-hidden-by-filter,
.battle-report-group.is-hidden-by-filter,
.changelog-entry.is-hidden-by-filter,
.changelog-group.is-hidden-by-filter {
  display: none;
}

.amount input::-webkit-inner-spin-button,
.amount input::-webkit-outer-spin-button {
  -webkit-appearance: none;
}

.amount input::placeholder {
  color: var(--faint);
  font-weight: 400;
}

.amount input:focus {
  outline: none;
  border-bottom-color: var(--a2);
}

.amount .unit {
  position: absolute;
  right: 0;
  bottom: 9px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
}

.quick {
  font-size: 11.5px;
  color: var(--faint);
  margin-bottom: 18px;
}

.quick a {
  color: var(--muted);
  margin-right: 14px;
}

.till .primary,
.till .ghost {
  width: 100%;
}

.till .primary {
  color: var(--bg);
  background: var(--ink);
  border-color: var(--ink);
}

.till .primary:hover {
  background: var(--a1);
  border-color: var(--a1);
}

.till .ghost {
  color: var(--ink);
  background: transparent;
  border-color: var(--ink);
}

.till .ghost:hover {
  background: var(--a1);
  border-color: var(--a1);
  color: var(--bg);
}

.till .fee {
  margin-top: 10px;
  font-size: 11.5px;
  color: var(--faint);
}

.bank-deposit-section .inline-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  max-width: 480px;
  border-top: 1px solid var(--line);
  padding-top: 14px;
}

.bank-deposit-row {
  gap: 10px 16px;
}

.bank-deposit-row .field {
  flex: 1 1 auto;
  min-width: 0;
}

.bank-deposit-row .btn-primary,
.bank-deposit-row .upgrade-tooltip {
  flex: none;
  justify-self: end;
}

@media (max-width: 620px) {
  .teller {
    grid-template-columns: 1fr;
    gap: 36px;
  }

  .bank-ledger-heading {
    margin-top: 20px;
  }

  .stat {
    grid-template-columns: max-content 40px 1fr;
    column-gap: 12px;
  }

  .bank-deposit-section .inline-form {
    grid-template-columns: 1fr;
  }

  .bank-deposit-row .btn-primary,
  .bank-deposit-row .upgrade-tooltip {
    justify-self: start;
  }
}

/* Compact Menu (feature 55): small categories of small icon+label tiles, no
   sublines, so the whole hub fits in a glance like an idle-MMO menu. */
.menu-cat {
  margin-top: 18px;
}

.menu-cat h3 {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 8px;
}

.menu-items {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 8px;
  max-width: 760px;
}

.menu-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: color-mix(in srgb, var(--soft) 82%, transparent);
  color: var(--ink);
  text-decoration: none;
  font: 600 13px/1 var(--sans);
  text-align: left;
  cursor: pointer;
  transition: border-color 120ms ease;
}

.menu-item svg {
  flex: none;
  color: var(--a1);
}

.menu-item:hover {
  border-color: var(--a1);
}

/* Favor Hall's entry in the World menu: gold border in both themes, dark
   ink in light mode and gold text in dark mode, echoing the seal itself,
   to draw a bit more traffic to the link. */
.menu-item-favor {
  border-color: var(--gold);
  color: var(--ink);
}

.menu-item-favor:hover {
  border-color: var(--gold);
}

.menu-item-favor svg {
  color: var(--gold);
}

body.dark .menu-item-favor {
  color: var(--gold);
}

.menu-item-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--a1);
  margin-left: -2px;
}

/* Unseen changelog: gold border to draw the eye, plus a small NEW badge. */
.menu-item-new {
  border-color: var(--gold);
}

.menu-item-new:hover {
  border-color: var(--gold);
}

.menu-item-badge-new {
  margin-left: auto;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--gold);
  color: #1a1a1a;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.03em;
  line-height: 1.5;
}

/* Same gold-pill "NEW"-badge look as .menu-item-badge-new above, sized to
   sit inline in the desktop sidebar's small uppercase nav links instead of
   a grid-aligned menu card. */
.realm-nav-badge-new {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--gold);
  color: #1a1a1a;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: none;
  line-height: 1.5;
  vertical-align: middle;
}

/* Mission Board's "N/M completed today" badge: a plain count, not an
   alert, so it skips the gold pill treatment and just sits as small
   colored text, the same green used for the online-presence dot. */
.realm-nav-badge-progress {
  margin-left: 6px;
  font-size: 10px;
  font-weight: 600;
  color: #3f9b46;
}

/* Infirmary/Bounty Board realm-wide counts: a quiet number, not an alert,
   so it matches the admin footer stats' weight rather than the gold pill
   above. Infirmary stays the default faint tone; Bounty Board's active
   bounties read as --gold, same register as the resource/Favor gold. */
.realm-nav-badge-count {
  margin-left: 6px;
  font-size: 10px;
  font-weight: 600;
  color: var(--faint);
}

.realm-nav-badge-count-gold {
  color: var(--gold);
}

.realm-nav-badge-hungry {
  margin-left: 6px;
  color: #b43a3a;
  vertical-align: -1px;
}

/* So the logout form doesn't break the grid: its button becomes the grid item. */
.menu-item-form {
  display: contents;
}

/* Admin row on the World/Menu page, under Logout: the Admin button and the
   clickable live online headcount, sized like the other menu buttons,
   admin-only. */
.world-admin-row {
  margin-top: 10px;
}

/* World/Menu page redesign: two columns of plain list rows (icon, name,
   optional note, arrow-on-hover) instead of the compact tile grid above,
   which .menu-item/.menu-cat still serve elsewhere (events.blade.php). */
.menu-cols {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0 40px;
  margin-top: 18px;
  max-width: 760px;
}

@media (max-width: 640px) {
  .menu-cols {
    grid-template-columns: 1fr;
    gap: 0;
  }

  /* Stacked on mobile, so the second column's first heading ("Guide") needs
     its own top margin back, the h3:first-child reset below is only meant
     to zero the gap above each column's own top on desktop's side-by-side
     layout. */
  .menu-col + .menu-col {
    margin-top: 22px;
  }
}

.menu-col h3 {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--faint);
  margin: 22px 0 4px;
}

.menu-col h3:first-child {
  margin-top: 0;
}

.menu-rows {
  display: flex;
  flex-direction: column;
}

.menu-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 2px;
  border: none;
  border-bottom: 1px solid var(--line);
  background: none;
  color: var(--ink);
  text-decoration: none;
  font: 600 14px/1.2 var(--sans);
  text-align: left;
  cursor: pointer;
  width: 100%;
  transition: padding-left 120ms ease;
}

.menu-row:hover {
  padding-left: 6px;
  text-decoration: none;
}

.menu-row-icon {
  width: 17px;
  height: 17px;
  flex: none;
  color: var(--faint);
}

.menu-row-icon svg {
  width: 100%;
  height: 100%;
}

.menu-row:hover .menu-row-icon {
  color: var(--a1);
}

.menu-row:hover .menu-row-name {
  color: var(--a1);
}

.menu-row-note {
  margin-left: auto;
  font-size: 11px;
  font-weight: 500;
  color: var(--faint);
  white-space: nowrap;
}

.menu-row-note b {
  color: var(--muted);
  font-weight: 600;
}

/* Deeds index (owner concept mockup, 2026-08-12, deeds-overview-concept-2):
   the row name reads as a place name (serif), like everywhere else in the
   game, instead of the shared menu row's bold sans. */
.menu-row-deed .menu-row-name {
  font: 600 18px/1.2 var(--serif);
}

.menu-row-deed:hover .menu-row-name {
  color: var(--a1);
}

/* Zeal/Goodwill read as a plain sentence instead of resource chips, same
   mockup: value in the meter's own color, the rest faint. */
.deed-meter-line {
  font-size: 12.5px;
  color: var(--muted);
  margin-bottom: 4px;
}

.deed-meter-line b {
  font-weight: 600;
}

.deed-meter-line-last {
  margin-bottom: 22px;
}

.deed-meter-line b svg {
  vertical-align: -2px;
}

.deed-meter-line b.is-zeal {
  color: var(--zeal, #6a4fa0);
}

.deed-meter-line b.is-goodwill {
  color: #b0508a;
}

.deed-meter-line .next {
  color: var(--faint);
}

.deed-meter-line a {
  text-decoration: none;
}

.deed-meter-line a:hover {
  text-decoration: underline;
}

.menu-row-note + .menu-row-arrow {
  margin-left: 10px;
}

.menu-row-arrow {
  margin-left: auto;
  color: var(--faint);
  opacity: 0;
  font-size: 13px;
  transition: opacity 120ms ease;
}

.menu-row:hover .menu-row-arrow {
  opacity: 1;
}

/* Favor Hall's entry: gold, same treatment as the tile grid's .menu-item-favor. */
.menu-row-favor .menu-row-name,
.menu-row-favor .menu-row-icon {
  color: var(--gold);
}

.menu-row-favor:hover .menu-row-name,
.menu-row-favor:hover .menu-row-icon {
  color: var(--gold);
}

/* Unseen Guild chat / Changelog: gold NEW pill via the shared
   .menu-item-badge-new class, row just needs to push it before the arrow. */
.menu-row-new .menu-item-badge-new {
  margin-left: auto;
}

.menu-row-new .menu-item-badge-new + .menu-row-arrow {
  margin-left: 10px;
}

/* So the logout form doesn't break the column's row stack: the button
   becomes the row itself, matching every <a class="menu-row"> sibling. */
.menu-row-form {
  display: contents;
}

.menu-row-logout:hover .menu-row-name,
.menu-row-logout:hover .menu-row-icon {
  color: var(--a1);
}

.world-links {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  max-width: 760px;
}

/* Full-width hairline that spans both grid columns, setting the meta links
   (wiki/changelog/account/logout) apart from the game-facing links above. */
.world-links-divider {
  grid-column: 1 / -1;
  height: 1px;
  background: var(--line);
  margin: 12px 0;
}

.world-link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 14px;
  border: 1px solid var(--line);
  padding: 14px 46px 14px 16px;
  color: var(--ink);
  transition: border-color 140ms ease, background 140ms ease;
}

.world-link:hover {
  border-color: var(--a1);
  background: color-mix(in srgb, var(--a1) 8%, transparent);
  text-decoration: none;
}

.world-link-icon {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  flex: none;
}

/* Each destination gets its own logical color instead of one flat tone:
   gold for the vault, blue for rank/prestige, rust for combat, red for
   wounds/health (matching the HP chip), race accent for the wiki. */
.world-link-bank .world-link-icon {
  color: #c8a13a;
}

.world-link-leaderboard .world-link-icon {
  color: #5b7fc7;
}

.world-link-battle-reports .world-link-icon {
  color: #c2703a;
}

.world-link-wiki .world-link-icon {
  color: var(--a1);
}

/* Dungeons: same purple as the "In Dungeon" status badge (.status-dungeon). */
.world-link-dungeon .world-link-icon {
  color: var(--q-legendary);
}

.world-link-changelog .world-link-icon {
  color: #3f8f5c;
}

.world-link-account .world-link-icon {
  color: #7a5bc7;
}

.world-link-logout .world-link-icon {
  color: #b43a3a;
}

/* Favor Hall (feature 46): gold, the same tone as the Iron Vault. */
.world-link-favor-hall .world-link-icon {
  color: var(--gold);
}

/* Logout is a POST form, not a plain link; display:contents keeps the
   wrapping <form> invisible to .world-links' grid so the button inside
   still behaves like every other grid-item link card. */
.world-link-form {
  display: contents;
}

.world-link-logout {
  width: 100%;
  font: inherit;
  background: none;
  cursor: pointer;
  text-align: left;
}

.world-link-text {
  display: grid;
  gap: 3px;
}

.world-link-text b {
  font-size: 13px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

.world-link-text small {
  color: var(--muted);
  font-size: 11px;
}

/* City hub: a single-column list of place rows (serif name, live status
   note, italic serif description) instead of icon cards, matching the
   editorial tone of the rest of the realm's read-heavy pages. */
.city-places {
  width: 100%;
}

.city-place {
  display: block;
  padding: 18px 2px 20px;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  transition: padding-left 140ms ease;
}

.city-place:hover {
  padding-left: 8px;
  text-decoration: none;
}

.city-place-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}

.city-place-name {
  font: 600 21px/1.15 var(--serif);
  letter-spacing: .3px;
}

.city-place:hover .city-place-name {
  color: var(--a1);
}

.city-place-note {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .5px;
  white-space: nowrap;
}

.city-place-note-gold {
  color: var(--gold);
}

.city-place-note-hp {
  color: #b43a3a;
}

.city-place-desc {
  color: var(--muted);
  font: italic 400 12.5px/1.6 var(--sans);
  margin-top: 3px;
  width: 100%;
}

/* The Wilds hub: four destination "bands" stacked full width, each darker
   than the last as it goes deeper into the wilds, with a low-opacity
   watermark of the destination's own icon in the background. Scoped under
   .wilds-hub / .wilds-ground- so it never touches the generic .world-link
   grid other hubs (City, Wiki, admin) still use. */
.wilds-hub {
  display: grid;
  max-width: 720px;
}

.wilds-ground {
  position: relative;
  display: block;
  overflow: hidden;
  padding: 26px 60px 22px 22px;
  color: var(--ink);
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
}

.wilds-ground:first-child {
  border-top: 1px solid var(--line);
}

.wilds-ground:last-child {
  border-bottom: 1px solid var(--line);
}

/* A jagged treeline seam between bands: the previous band's color pokes
   down in triangles over the top of the next, darker one. --wilds-prev
   is the color the teeth are cut from, set per band below. */
.wilds-ground::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 14px;
  background:
    linear-gradient(135deg, var(--wilds-prev) 25%, transparent 25%) 0 0/16px 14px repeat-x,
    linear-gradient(225deg, var(--wilds-prev) 25%, transparent 25%) 0 0/16px 14px repeat-x;
  pointer-events: none;
}

.wilds-ground-1 {
  --wilds-prev: var(--bg);
  background: var(--bg);
}

.wilds-ground-2 {
  --wilds-prev: var(--bg);
  background: color-mix(in srgb, var(--bg), var(--ink) 3%);
}

.wilds-ground-3 {
  --wilds-prev: color-mix(in srgb, var(--bg), var(--ink) 3%);
  background: color-mix(in srgb, var(--bg), var(--ink) 6%);
}

.wilds-ground-4 {
  --wilds-prev: color-mix(in srgb, var(--bg), var(--ink) 6%);
  background: color-mix(in srgb, var(--bg), var(--ink) 9%);
}

.wilds-ground:hover {
  text-decoration: none;
}

.wilds-ground-1:hover .wilds-ground-title,
.wilds-ground-3:hover .wilds-ground-title {
  color: var(--a1);
}

.wilds-ground-2:hover .wilds-ground-title,
.wilds-ground-4:hover .wilds-ground-title {
  color: var(--a2);
}

.wilds-ground-1:hover .wilds-ground-enter,
.wilds-ground-3:hover .wilds-ground-enter {
  background: var(--a1);
  border-color: var(--a1);
  color: var(--bg);
}

.wilds-ground-2:hover .wilds-ground-enter,
.wilds-ground-4:hover .wilds-ground-enter {
  background: var(--a2);
  border-color: var(--a2);
  color: var(--bg);
}

.wilds-ground-icon {
  position: absolute;
  right: -12px;
  bottom: -12px;
  width: 130px;
  height: 130px;
  color: var(--ink);
  opacity: .06;
  pointer-events: none;
}

body.dark .wilds-ground-icon {
  opacity: .08;
}

.wilds-ground-icon svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: currentColor;
}

.wilds-ground-head {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}

.wilds-ground-title {
  font: 600 22px/1.1 var(--serif);
}

.wilds-ground-dist {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--faint);
}

.wilds-ground-desc {
  position: relative;
  display: block;
  color: var(--muted);
  font-size: 12.5px;
  max-width: 440px;
  margin-top: 4px;
}

.wilds-ground-meta {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 14px;
  font-size: 12px;
  color: var(--muted);
}

.wilds-ground-status {
  font-weight: 600;
}

.wilds-ground-status-ready {
  color: var(--q-fine);
}

.wilds-ground-status-wait {
  color: var(--gold);
}

.wilds-ground-enter {
  display: inline-block;
  flex: none;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  padding: 6px 14px;
  transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}

@media (max-width: 620px) {
  .wilds-ground {
    padding: 20px 16px 16px;
  }

  .wilds-ground-icon {
    width: 90px;
    height: 90px;
  }

  .wilds-ground-enter {
    padding: 5px 10px;
  }
}

/* Hunting Grounds: the same darkening-band treeline treatment as the Wilds
   hub, but with a variable number of grounds (a max-level player has all
   of them unlocked at once), so the shade per band comes from an inline
   custom property instead of fixed classes. Scoped under .hunt-ground- so
   it never touches the shared .building-card grid other pages still use. */
.hunt-grounds {
  display: grid;
  max-width: 720px;
}

.hunt-ground {
  position: relative;
  overflow: hidden;
  padding: 26px 26px 22px;
  color: var(--ink);
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  background: color-mix(in srgb, var(--bg), var(--ink) var(--hunt-shade, 0%));
}

.hunt-ground:first-child {
  border-top: 1px solid var(--line);
}

.hunt-ground:last-child {
  border-bottom: 1px solid var(--line);
}

.hunt-ground::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 14px;
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--bg), var(--ink) var(--hunt-prev-shade, 0%)) 25%, transparent 25%) 0 0/16px 14px repeat-x,
    linear-gradient(225deg, color-mix(in srgb, var(--bg), var(--ink) var(--hunt-prev-shade, 0%)) 25%, transparent 25%) 0 0/16px 14px repeat-x;
  pointer-events: none;
}

.hunt-ground:nth-child(odd):hover .hunt-ground-title {
  color: var(--a1);
}

.hunt-ground:nth-child(even):hover .hunt-ground-title {
  color: var(--a2);
}

.hunt-ground-icon {
  position: absolute;
  right: -12px;
  bottom: -12px;
  width: 130px;
  height: 130px;
  color: var(--ink);
  opacity: .06;
  pointer-events: none;
}

body.dark .hunt-ground-icon {
  opacity: .08;
}

.hunt-ground-icon svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: currentColor;
}

.hunt-ground-head {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}

.hunt-ground-title {
  font: 600 22px/1.1 var(--serif);
}

.hunt-ground-lvl {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--faint);
}

.hunt-ground-quarry {
  position: relative;
  color: var(--muted);
  font: italic 500 13px var(--serif);
  margin-top: 4px;
}

.hunt-ground-meta {
  position: relative;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 14px;
  font-size: 12px;
  color: var(--muted);
}

.hunt-ground-btn {
  display: inline-flex;
  align-items: center;
  background: none;
  border: 1px solid var(--ink);
  color: var(--ink);
  font: 600 11px var(--sans);
  letter-spacing: 2px;
  text-transform: uppercase;
  padding: 8px 18px;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease;
}

.hunt-ground-btn i {
  font-style: normal;
  color: var(--a1);
  margin-left: 8px;
  font-weight: 600;
}

.hunt-ground-btn:not(.btn-plus-locked):hover {
  background: var(--ink);
  color: var(--bg);
}

.hunt-ground-lock-hint {
  font-size: 11.5px;
  color: var(--faint);
  font-style: italic;
}

.hunt-ground-tally {
  font-size: 11.5px;
  color: var(--faint);
}

.hunt-ground.is-locked .hunt-ground-title,
.hunt-ground.is-locked .hunt-ground-quarry {
  color: var(--faint);
}

@media (max-width: 620px) {
  .hunt-ground {
    padding: 20px 16px 16px;
  }

  .hunt-ground-icon {
    width: 90px;
    height: 90px;
  }
}

/* ---------- Dungeons: halls behind wax-sealed or open doors ----------
   A short list (2-4 rows), so plain top-bordered rows read better than
   another .building-card grid: a door glyph (built from CSS, no new SVG)
   stands in for a room being enterable, with the shared "seal" icon laid
   over it, tinted with the player's own race colors, when it's not. */
.dungeon-halls {
  display: grid;
  max-width: 720px;
}

.dungeon-hall {
  display: grid;
  grid-template-columns: 72px 1fr auto;
  align-items: center;
  gap: 8px 22px;
  padding: 22px 0;
  border-top: 1px solid var(--line);
}

.dungeon-hall:last-child {
  border-bottom: 1px solid var(--line);
}

.dungeon-door {
  position: relative;
  justify-self: center;
  width: 58px;
  height: 74px;
  border: 1px solid var(--faint);
  border-radius: 29px 29px 0 0;
  background: radial-gradient(130% 95% at 50% 100%, color-mix(in srgb, var(--ink), transparent 55%) 0%, transparent 70%), var(--soft);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 3px;
  padding-bottom: 8px;
}

.dungeon-door::after {
  content: "";
  position: absolute;
  left: -7px;
  right: -7px;
  bottom: -1px;
  border-bottom: 1px solid var(--faint);
}

.dungeon-door-plank {
  width: 4px;
  height: 7px;
  border: 1px solid var(--faint);
  border-radius: 2px 2px 0 0;
  opacity: 0.55;
}

.dungeon-hall.is-sealed .dungeon-door {
  opacity: 0.85;
}

.dungeon-door-seal {
  position: absolute;
  top: 40%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(-8deg);
  width: 28px;
  height: 28px;
  color: var(--a1);
  opacity: 0.95;
}

.dungeon-door-seal svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: currentColor;
}

.dungeon-hall-title {
  display: block;
  font: 600 21px/1.15 var(--serif);
}

.dungeon-hall.is-sealed .dungeon-hall-title {
  color: var(--muted);
}

.dungeon-hall-lvl {
  display: block;
  margin-top: 4px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--faint);
}

.dungeon-hall-desc {
  color: var(--muted);
  font: italic 500 13px var(--serif);
  margin-top: 6px;
}

.dungeon-hall.is-sealed .dungeon-hall-desc {
  color: var(--faint);
}

.dungeon-hall-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 8px;
  font-size: 12px;
  color: var(--muted);
}

.dungeon-hall.is-sealed .dungeon-hall-meta {
  color: var(--faint);
}

.dungeon-hall-dot,
.dungeon-hall-rooms {
  color: var(--faint);
}

.dungeon-hall-loot-short {
  display: none;
}

.dungeon-hall-act {
  text-align: right;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
}

.dungeon-hall-btn {
  display: inline-flex;
  align-items: center;
  background: none;
  border: 1px solid var(--ink);
  color: var(--ink);
  font: 600 11px var(--sans);
  letter-spacing: 2px;
  text-transform: uppercase;
  padding: 9px 24px;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease;
  white-space: nowrap;
}

.dungeon-hall-btn .resource-chip {
  margin-left: 8px;
}

.dungeon-hall-btn:hover {
  background: var(--ink);
  color: var(--bg);
}

.dungeon-hall-btn:hover .resource-chip {
  color: inherit;
}

@media (max-width: 560px) {
  .dungeon-hall {
    grid-template-columns: 58px 1fr;
  }

  .dungeon-hall-loot-full {
    display: none;
  }

  .dungeon-hall-loot-short {
    display: inline;
  }

  .dungeon-hall-act {
    grid-column: 1 / -1;
    align-items: stretch;
    margin-top: 14px;
  }

  .dungeon-hall-act form,
  .dungeon-hall-act .upgrade-tooltip {
    display: flex;
  }

  .dungeon-hall-btn {
    width: 100%;
    justify-content: center;
  }

  .dungeon-hall-act .upgrade-hint {
    right: auto;
    left: 50%;
    top: calc(100% + 8px);
    transform: translateX(-50%);
  }
}

/* ---------- Bandit Camps: fallen chiefs / today's mark / rumors ---------- */
.camp-section-label {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 1.8px;
  color: var(--faint);
  margin: 0 0 10px;
}

.camp-fallen-row,
.camp-rumor-row {
  padding: 9px 2px;
  border-bottom: 1px solid var(--line);
}

.camp-fallen-row:first-child,
.camp-rumor-row:first-child {
  border-top: 1px solid var(--line);
}

.camp-fallen-line,
.camp-rumor-line {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}

.camp-fallen-name,
.camp-rumor-name {
  font: 600 16px/1.2 var(--serif);
  position: relative;
  color: var(--muted);
}

.camp-fallen-name.is-down::after {
  content: "";
  position: absolute;
  left: -2%;
  right: -2%;
  top: 54%;
  height: 1.5px;
  background: var(--a1);
  transform: rotate(-3deg);
}

.camp-fallen-meta,
.camp-rumor-type {
  color: var(--faint);
  font-size: 11.5px;
}

.camp-fallen-again,
.camp-rumor-lvl {
  margin-left: auto;
  font-size: 11.5px;
  white-space: nowrap;
  color: var(--faint);
}

.camp-fallen-plunder {
  display: flex;
  gap: 6px 14px;
  flex-wrap: wrap;
  margin-top: 6px;
  font-size: 11.5px;
}

@media (max-width: 480px) {
  /* Keep the chief's name and the Assault button together on the first
     line (the button needs the room), push the level/camp meta down to
     its own line instead of letting it fight the button for space. */
  .camp-fallen-line {
    row-gap: 2px;
  }

  .camp-fallen-name {
    order: 1;
  }

  .camp-fallen-again {
    order: 2;
  }

  .camp-fallen-meta {
    order: 3;
    flex-basis: 100%;
  }
}

.camp-mark {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: flex-start;
  gap: 18px;
  padding: 22px 24px;
  border: 1px solid var(--line);
}

.camp-mark-icon {
  position: absolute;
  right: -14px;
  bottom: -14px;
  width: 120px;
  height: 120px;
  color: var(--ink);
  opacity: .05;
  pointer-events: none;
}

body.dark .camp-mark-icon {
  opacity: .08;
}

.camp-mark-icon svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: currentColor;
}

.camp-mark-body {
  position: relative;
  width: 100%;
}

.camp-mark-type {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--a1);
}

.camp-mark-chief {
  position: relative;
  display: inline-block;
  font: 600 26px/1.15 var(--serif);
  margin: 2px 0 1px;
}

.camp-mark-chief.is-down::after {
  content: "";
  position: absolute;
  left: -1%;
  right: -1%;
  top: 54%;
  height: 2px;
  background: var(--a1);
  transform: rotate(-3deg);
}

.camp-mark-lvl {
  font-size: 12px;
  color: var(--muted);
}

.camp-mark-plunder {
  display: flex;
  gap: 8px 16px;
  flex-wrap: wrap;
  margin-top: 14px;
}

.camp-mark-act {
  margin-top: 16px;
}

/* The card clips overflow for its watermark icon; the default
   .upgrade-hint opens to the left of its trigger, which pushes it past
   the card's left edge and gets cut in half. The button sits near the
   card's left edge with plenty of room to its right, so open the hint
   that way instead. */
.camp-mark-act .upgrade-hint {
  right: auto;
  left: calc(100% + 10px);
}

@media (max-width: 620px) {
  .camp-mark {
    padding: 18px 16px;
  }

  .camp-mark-icon {
    width: 90px;
    height: 90px;
  }
}

.wiki-back-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--line);
  color: var(--muted);
  padding: 6px 10px;
  font-size: 10.5px;
  letter-spacing: 1px;
  text-transform: uppercase;
}

.raven-compose-back {
  display: block;
  margin-bottom: 10px;
  color: var(--muted);
  font-size: 12px;
}

.raven-compose-back:hover {
  color: var(--a1);
}

.wiki-topbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 8px;
}

.wiki-topbar h1 {
  margin-bottom: 0;
}

.wiki-back-link::before {
  content: "<";
  font-size: 12px;
  line-height: 1;
}

.wiki-back-link:hover {
  color: var(--a1);
  border-color: var(--a1);
  text-decoration: none;
}

/* Betting Dens: a plain crumb instead of the boxed back-button every other
   page uses, matching the concept mockup's understated "‹ The City" link.
   Scoped to this section only (.is-crumb), the rest of the site keeps the
   standard boxed .wiki-back-link. */
.wiki-back-link.is-crumb {
  border: 0;
  padding: 0;
  letter-spacing: 1.6px;
}

/* Sits as the last row of .den-list, below the Blackjack den, rather than
   as a topbar above the title. */
.den-list-back {
  margin-top: 20px;
  align-self: flex-start;
}

/* ---------- Wiki search (hub + results page) ---------- */

.wiki-search {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 18px;
}

.wiki-search input[type="search"] {
  flex: 1;
  min-width: 0;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  padding: 6px 0;
}

.wiki-search input[type="search"]:focus {
  outline: none;
  border-bottom-color: var(--a1);
}

/* The matched keyword inside a result snippet: an accent-colored emphasis,
   not the browser's default loud yellow highlight. */
.world-link-text small mark {
  background: none;
  color: var(--a1);
  font-weight: 600;
}

/* In-page highlights on a guide reached from a search result (?mark=...,
   kow.js): an accent wash plus a solid underline, like a highlighter pen,
   keeping the text color itself untouched. Clearly visible at a glance
   (owner request), but still the theme's own accent, never browser yellow. */
.page mark.wiki-search-mark {
  background: color-mix(in srgb, var(--a1) 30%, transparent);
  box-shadow: inset 0 -1px 0 var(--a1);
  color: inherit;
  border-radius: 2px;
  padding: 0 2px;
}

.race-showcase {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.race-card {
  border: 1px solid var(--line);
  padding: 14px 14px 12px;
  background: color-mix(in srgb, var(--soft) 82%, transparent);
  box-shadow: inset 0 2px 0 color-mix(in srgb, var(--tone-a, var(--a1)) 60%, var(--line));
}

.race-card-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}

.race-card-icon {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tone-a, var(--a1));
}

.race-card-icon svg {
  width: 100%;
  height: 100%;
}

.race-card h4 {
  font: 600 20px/1.05 var(--serif);
}

.race-card-words {
  font: italic 500 15px/1.35 var(--serif);
  color: color-mix(in srgb, var(--tone-a, var(--a1)) 75%, var(--ink));
  margin-bottom: 6px;
}

.race-card-theme {
  font-size: 10.5px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--tone-b, var(--a2)) 60%, var(--muted));
}

.race-human {
  --tone-a: var(--human1);
  --tone-b: var(--human2);
}

.race-orc {
  --tone-a: var(--orc1);
  --tone-b: var(--orc2);
}

.race-dwarf {
  --tone-a: var(--dwarf1);
  --tone-b: var(--dwarf2);
}

.race-elf {
  --tone-a: var(--elf1);
  --tone-b: var(--elf2);
}

.race-undead {
  --tone-a: var(--undead1);
  --tone-b: var(--undead2);
}

.drill-result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px 14px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--a1);
  background: color-mix(in srgb, var(--a1) 7%, var(--soft) 93%);
  padding: 12px 16px;
  margin-top: 8px;
  margin-bottom: 20px;
  animation: drill-result-in 220ms ease;
}

.drill-result-text {
  font: 500 13px/1.4 var(--sans);
  min-width: 0;
}

.drill-result-text strong {
  font-weight: 700;
}

.drill-result-gain {
  flex: none;
  font: 700 20px/1 var(--serif);
  color: var(--a1);
}

/* Feature 61: a Wraithcall result is good (a Wraith fell, nothing taken in
   return), bad (a miss), or a trade (a Wraith fell but so did a blow land on
   you) — three fixed colors, not the viewer's race accent, which says
   nothing about whether the roll went their way. */
.drill-result.is-positive {
  border-left-color: #3a8f4f;
  background: color-mix(in srgb, #3a8f4f 7%, var(--soft) 93%);
}

.drill-result.is-negative {
  border-left-color: #b43a3a;
  background: color-mix(in srgb, #b43a3a 7%, var(--soft) 93%);
}

.drill-result.is-mixed {
  border-left-color: #c8a13a;
  background: color-mix(in srgb, #c8a13a 7%, var(--soft) 93%);
}

/* .drill-result is a flex row built for a one-line result + a value badge;
   the run's outcome is a short story instead, so this switches it to a
   plain block with its own heading and paragraph, keeping only the
   colored border/background/animation from the base class. */
.wraithcall-outcome {
  display: block;
}

.wraithcall-outcome h3 {
  margin: 0 0 6px;
}

/* The "Victory!" verdict: the battle report's .battle-verdict treatment
   (big serif, win green / loss red), sitting in the centered event hero
   directly under the event title once the run resolves, not inside the
   boards box below. The hero's own bottom padding carries the spacing, so
   the verdict itself drops .battle-verdict's default bottom margin. */
.event-hero .wraithcall-verdict {
  margin: 2px 0 0;
}

/* .page .sub carries a 24px bottom margin for body copy; inside the
   outcome card the story line is the only content, so that margin just
   bloats the card. Scoped with .page so this wins on specificity. */
.page .wraithcall-outcome p {
  margin: 0;
}

/* Post-run boards dressed as the leaderboard's champion columns
   (.leaderboard-top-columns/.leaderboard-top-column carry the bordered
   boxes, soft background and serif headings): the two top 5 boards side by
   side, then Your Spoils and Gems won as full-width boxes below, all
   outside the green/red outcome card, which now holds only the story
   line. The container's own gap spaces the boxes, so the loot/spoils
   boxes no longer carry their own top margin. */
.wraithcall-boards {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Ranked rows (the two top 5 boards, not the loot/spoils boxes) get the
   champions' medallion number via a CSS counter instead of markup, so the
   server-rendered row HTML and the JS renderer stay untouched; the top
   three take the same gold/silver/bronze as .leaderboard-top-rank. The
   .sub empty-state row ("No blows struck.") stays unnumbered. */
.wraithcall-leaderboard-list.is-ranked {
  counter-reset: event-rank;
}

.wraithcall-leaderboard-list.is-ranked li:not(.sub) {
  counter-increment: event-rank;
}

.wraithcall-leaderboard-list.is-ranked li:not(.sub)::before {
  content: counter(event-rank);
  flex: none;
  width: 24px;
  text-align: center;
  font: 700 18px/1 var(--serif);
  color: var(--faint);
}

.wraithcall-leaderboard-list.is-ranked li:nth-child(1):not(.sub)::before {
  color: #b8860b;
}

.wraithcall-leaderboard-list.is-ranked li:nth-child(2):not(.sub)::before {
  color: #8a8a8f;
}

.wraithcall-leaderboard-list.is-ranked li:nth-child(3):not(.sub)::before {
  color: #a9743b;
}

.wraithcall-leaderboard-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.wraithcall-leaderboard-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 400 13px/1.4 var(--sans);
  border-bottom: 1px solid var(--line);
  padding: 6px 0;
}

.wraithcall-leaderboard-list li:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.wraithcall-leaderboard-list .identity-link {
  display: inline-flex;
  align-items: center;
}

.wraithcall-leaderboard-count {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 700 12px/1 var(--sans);
  color: var(--a1);
}

/* The Wraithcall board's own "Most attacks" icon (App\Http\Controllers\
   WraithcallController::attackerLeaderboardHtml()), same purple as the
   inline "Wraith" chip elsewhere, so both events' attack boards read the
   same way (a themed icon next to the count) instead of a bare number. */
.wraithcall-leaderboard-count .res-icon {
  color: var(--q-legendary);
}

/* The viewer's own take from the run: one row of the same battle-gain
   chips the battle reports use (XP/Honor/gem loot), sitting centered in
   the event hero directly under the verdict, exactly like a battle
   report's finale spoils. No extra chip styling of its own, just the
   row's spacing. */
.wraithcall-spoils-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 12px 20px;
  margin: 0;
}

/* Forces the Loot chip(s) onto their own line below XP/Honor instead of
   wrapping inline next to them once the row gets crowded (a zero-height,
   full-width flex item breaks the wrap here without splitting the row's
   markup into two separate elements). Only emitted by *Controller::
   yourSpoilsHtml() when there's loot to show. */
.wraithcall-spoils-break {
  flex-basis: 100%;
  height: 0;
}

.wraithcall-hero-spoils {
  margin-top: 12px;
}

/* The Twin Wyrms' per-wyrm attack split (e.g. "40 Pale" / "24 Black"): a
   wyrm icon tinted per side instead of spelling out "Pale"/"Black" in
   prose or showing a separate combined total. A direct flex child of
   .wraithcall-leaderboard-list li (align-items: center there), so its own
   font-size/line-height never need to match the identity text next to it;
   nesting an extra wrapper span here previously threw that baseline off,
   reading as sitting a couple pixels high. */
.twinwyrms-leaderboard-split {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font: 700 12px/1 var(--sans);
}

.twinwyrms-split-item {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}

.twinwyrms-split-item .res-icon {
  display: block;
}

.twinwyrms-split-item.wyrm-pale {
  color: var(--wyrm-pale);
}

.twinwyrms-split-item.wyrm-black {
  color: var(--wyrm-black);
}

@keyframes drill-result-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.attribute-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
}

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

/* Each attribute gets its own banner, alternating the two race-accent
   tones (var(--a1)/var(--a2), set per-viewer by body.theme-{race} in
   game-shell) the same way Concept B's mocked-up house banners alternated
   two fixed colors, so Train reads as themed to the player's own race
   without a separate .race-{name} wrapper. */
.attribute-banner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  background: color-mix(in srgb, var(--soft) 82%, transparent);
  border-top: 3px solid var(--a1);
  padding: 16px 12px 22px;
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - 14px), 50% 100%, 0 calc(100% - 14px));
}

.attribute-banner:nth-child(even) {
  border-top-color: var(--a2);
}

.attribute-banner-attr {
  font: 600 10px/1 var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
}

.attribute-banner-icon {
  width: 28px;
  height: 28px;
  margin: 10px 0 8px;
  color: var(--a1);
}

.attribute-banner:nth-child(even) .attribute-banner-icon {
  color: var(--a2);
}

.attribute-banner-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

.attribute-banner-value {
  font: 600 30px/1.05 var(--serif);
}

.attribute-banner-gear {
  font: 500 12px var(--serif);
  color: var(--faint);
  margin: 2px 0 10px;
}

.attribute-banner-gear b {
  color: var(--ink);
  font-weight: 600;
}

.attribute-banner-flavor {
  flex: 1;
  color: var(--muted);
  font: italic 500 14.5px/1.45 var(--serif);
  padding: 0 2px;
  margin-bottom: 16px;
}

.attribute-banner-cost {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-bottom: 12px;
}

.attribute-banner-actions {
  width: 100%;
}

.attribute-banner-actions .btn-primary,
.attribute-banner-actions .btn-plus-locked {
  display: block;
  width: 100%;
}

.attribute-banner-actions .upgrade-tooltip {
  display: block;
  width: 100%;
}

/* The default .upgrade-hint opens to the left (right: calc(100% + 10px)),
   which has nowhere to go in a narrow banner column; center it below
   instead, same idea as .attribute-card-actions before it. */
.attribute-banner-actions .upgrade-hint {
  right: auto;
  left: 50%;
  top: calc(100% + 8px);
  transform: translateX(-50%);
}

.building-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.building-card {
  border: 1px solid var(--line);
  padding: 16px;
  background: color-mix(in srgb, var(--soft) 82%, transparent);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Dungeon cards on the Barrows/Dungeons list stack several short lines
   (flavor text, level line, rewards, the action button); a tighter gap
   and padding keeps that from reading as excessively tall. */
.building-card-tight {
  padding: 12px 14px;
  gap: 5px;
}

/* Bot Profiles (admin/bots.blade.php): a <details>/<summary> version of
   .building-card so a long list of profiles reads as a stack of closed
   rows (name + key stats) by default, opening one at a time to edit its
   sliders, rather than every profile's full edit form sitting open and
   pushing the page's actual bot list far down the scroll. */
.bot-profile-card > summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: baseline;
}

.bot-profile-card > summary .building-card-head {
  flex: 1;
  min-width: 0;
}

/* The stats line ("33% aggr · 33% econ · 33% build · 0 bot(s) · Active")
   is long enough to overflow the viewport next to the name on a phone,
   since .building-card-head's default is a single flex row with the name
   and stats side by side. Stack them (name, then stats below) below a
   width where that row realistically has no room for both. */
@media (max-width: 560px) {
  .bot-profile-card > summary .building-card-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
  }
}

.bot-profile-card > summary::-webkit-details-marker {
  display: none;
}

/* A plain triangle glyph as the expand/collapse cue, since the browser's
   own marker is hidden above for a consistent look across browsers;
   rotates via [open] below rather than swapping character. */
.bot-profile-card > summary::before {
  content: "\25B8";
  display: inline-block;
  margin-right: 8px;
  color: var(--faint);
  transition: transform 0.15s ease;
}

.bot-profile-card[open] > summary::before {
  transform: rotate(90deg);
}

.bot-profile-card > summary .building-level {
  font-size: 11px;
}

.bot-profile-card > form,
.bot-profile-card > summary + form {
  margin-top: 10px;
}

/* .page .sub's own 24px margin-bottom is meant for prose between page
   sections; inside a card, the card's own flex gap already spaces
   stacked lines, so that margin only adds unwanted extra room. */
.building-card .sub {
  margin-bottom: 0;
}

/* A resource-chip leading a line (Expeditions' reward cards), instead of
   sitting mid-sentence: .resource-chip's own baseline vertical-align nudge
   reads high here, so center the chip against the line's text with flex
   instead of relying on baseline alignment. */
.expedition-reward-line {
  display: flex;
  align-items: center;
  gap: 4px;
}

.expedition-reward-line .resource-chip {
  margin-left: 0;
  vertical-align: baseline;
}

.building-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.building-card-head h4 {
  font: 600 18px/1.1 var(--serif);
}

/* ---------- The Mission Board (daily contracts + weekly writs) ----------
   A trial banner up top (the reward Daily Trial dots build toward), a
   Daily/Weekly tab pair (the shared .tabs/.tab-input pattern), and a card
   grid per tab. Each card's left edge fills upward as progress advances,
   a cheap always-visible progress cue that doesn't need its own legend. */
.mission-board-trial {
  margin-top: 20px;
  margin-bottom: 12px;
  border: 1px solid color-mix(in srgb, var(--gold) 45%, transparent);
  border-radius: 3px;
  background: color-mix(in srgb, var(--soft) 82%, transparent);
  padding: 16px 18px;
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 16px;
  align-items: center;
  position: relative;
  overflow: hidden;
}

.mission-board-trial::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--gold);
}

.mission-board-trial-seal {
  width: 40px;
  height: 40px;
  border: 1.5px solid var(--gold);
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--gold);
  flex-shrink: 0;
}

.mission-board-trial-seal svg {
  width: 19px;
  height: 19px;
}

.mission-board-trial-eyebrow {
  font: 600 9.5px/1 var(--sans);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--gold);
  margin-bottom: 3px;
}

.mission-board-trial h3 {
  font: 600 17px/1.2 var(--serif);
  /* .page h3 (kow.css above) styles section sub-headers as a small, faint,
     uppercase eyebrow; this h3 is the trial's actual reward line, so it
     needs its own full-contrast color, not that inherited one. */
  color: var(--ink);
  text-transform: none;
  letter-spacing: normal;
}

.mission-board-trial p {
  color: var(--muted);
  font-size: 12px;
  margin-top: 3px;
}

.mission-board-trial-dots {
  display: flex;
  gap: 7px;
  align-items: center;
  justify-self: end;
}

.mission-board-trial-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid var(--faint);
}

.mission-board-trial-dot.done {
  border-color: #3f9b46;
  background: #3f9b46;
}

@media (max-width: 640px) {
  .mission-board-trial {
    grid-template-columns: auto 1fr;
  }

  .mission-board-trial-dots {
    grid-column: 1 / -1;
    justify-self: start;
    margin-top: 4px;
  }
}

/* A count pill next to a tab label, e.g. "2/3". Tints with the tab's own
   color (inherited, including the active-tab gold/race color) rather than
   needing its own entry in the :has() color rules below. */
.tab-count {
  display: inline-block;
  margin-left: 6px;
  font-size: 10px;
  font-weight: 600;
  color: inherit;
  opacity: 0.75;
  background: color-mix(in srgb, currentColor 12%, transparent);
  border-radius: 20px;
  padding: 1.5px 7px;
}

.mission-board-note {
  margin-top: 18px;
  font-size: 11.5px;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 6px;
}

.mission-board-note svg {
  width: 13px;
  height: 13px;
  flex-shrink: 0;
}

.mission-grid {
  margin-top: 18px;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 14px;
}

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

.mission-card {
  position: relative;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 16px 16px 14px;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow: hidden;
}

.mission-card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--line);
}

.mission-card[data-state="progress"]::before {
  background: linear-gradient(to top, var(--a1) var(--fill, 0%), var(--line) var(--fill, 0%));
}

.mission-card[data-state="claimed"]::before {
  background: #3f9b46;
}

.mission-card[data-state="claimed"] {
  background: color-mix(in srgb, #3f9b46 5%, var(--bg));
}

.mission-card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 10px;
}

.mission-kind {
  font: 600 9px/1 var(--sans);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--faint);
  display: flex;
  align-items: center;
  gap: 5px;
  margin-bottom: 4px;
}

.mission-kind svg {
  width: 11px;
  height: 11px;
}

.mission-card h4 {
  font: 600 16px/1.25 var(--serif);
}

.mission-reward {
  flex-shrink: 0;
  white-space: nowrap;
}

.mission-progress-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.mission-progress-track {
  flex: 1;
  height: 5px;
  border-radius: 3px;
  background: color-mix(in srgb, var(--line) 55%, transparent);
  overflow: hidden;
}

.mission-progress-fill {
  height: 100%;
  border-radius: 3px;
  background: var(--a1);
  width: var(--fill, 0%);
}

.mission-card[data-state="claimed"] .mission-progress-fill {
  background: #3f9b46;
}

.mission-progress-num {
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
}

.mission-status {
  font-size: 11.5px;
  display: flex;
  align-items: center;
  gap: 6px;
}

.mission-status.claimed {
  color: #3f9b46;
  font-weight: 600;
}

.mission-status.claimed svg {
  width: 13px;
  height: 13px;
}

.mission-status.active a {
  color: var(--a2);
  text-decoration: none;
}

.mission-status.active a:hover {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.building-level {
  font-size: 12px;
  color: var(--muted);
  white-space: nowrap;
}

/* ---------- Forge tab (Camp page) ---------- */

/* The anvil: smith level (a thin --gold XP bar) plus whatever's currently
   at the fire, one line per active job (the smith queue can run more than
   one at once at higher Forge levels), or a flavor idle line when it's
   empty. Sits above the quality rows like the Camp cards' own headline
   figures, not boxed in a .compact-meters card. */
.anvil {
  padding: 2px 2px 18px;
  border-bottom: 1px solid var(--line);
}

.anvil-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 11px;
  color: var(--muted);
  margin-bottom: 6px;
}

.anvil-row b {
  color: var(--ink);
  font-weight: 600;
}

.anvil-lbl {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
}

.anvil-track {
  height: 2px;
  background: var(--line);
}

.anvil-track i {
  display: block;
  height: 100%;
  background: var(--gold);
}

.anvil-work {
  display: flex;
  align-items: baseline;
  gap: 12px;
  font-size: 13px;
  margin-top: 14px;
}

.anvil-work b {
  font: 600 15px var(--serif);
  color: var(--ink);
}

.anvil-work-verb {
  color: var(--muted);
}

.anvil-eta {
  margin-left: auto;
  color: var(--a2x, var(--a2));
  font-weight: 600;
  font-size: 12.5px;
  white-space: nowrap;
}

.anvil-idle {
  font: 500 13.5px var(--sans);
  color: var(--faint);
  margin-top: 14px;
}

/* The quality ladder itself: strongest unlocked tier on top (players
   almost always forge the best they can afford, so that's the row that
   belongs on top; see CampController's display reorder), the one locked
   next tier pinned above even that as a faint ghost row - literally the
   next rung, no cost or roll range since it can't be forged yet. */
.forge-rows {
  display: flex;
  flex-direction: column;
}

.forge-row {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 14px 2px;
  border-bottom: 1px solid var(--line);
}

.forge-row:last-child {
  border-bottom: none;
}

.forge-row-left {
  min-width: 0;
  flex: 1;
}

.forge-row-name {
  font: 600 20px/1.2 var(--serif);
  white-space: nowrap;
}

.forge-row-name small {
  font: 500 15px var(--serif);
  color: var(--muted);
}

.forge-row-meta {
  display: block;
  font-size: 11.5px;
  color: var(--muted);
  white-space: nowrap;
  margin-top: 2px;
}

.forge-row-roll {
  flex: none;
  text-align: right;
  white-space: nowrap;
}

.forge-row-roll .n {
  font: 600 19px/1 var(--serif);
}

.forge-row-roll .l {
  display: block;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
  margin-top: 3px;
}

.forge-row.q-fine .forge-row-roll .n { color: var(--q-fine); }
.forge-row.q-superior .forge-row-roll .n { color: var(--q-superior); }
.forge-row.q-excellent .forge-row-roll .n { color: var(--q-excellent); }
.forge-row.q-masterwork .forge-row-roll .n { color: var(--q-masterwork); }
.forge-row.q-flawless .forge-row-roll .n { color: var(--q-flawless); }
.forge-row.q-pristine .forge-row-roll .n { color: var(--q-pristine); }
.forge-row.q-exalted .forge-row-roll .n { color: var(--q-exalted); }
.forge-row.q-transcendent .forge-row-roll .n { color: var(--q-transcendent); }

.forge-row.is-locked .forge-row-name {
  color: var(--faint);
}

.forge-row-gate {
  display: block;
  font: 400 12.5px/1.5 var(--sans);
  color: var(--faint);
  margin-top: 2px;
}

@media (max-width: 560px) {
  .forge-row {
    gap: 12px;
  }

  .forge-row-name {
    font-size: 18px;
  }

  .forge-row-roll .n {
    font-size: 17px;
  }

  .forge-row-meta {
    white-space: normal;
  }
}

.forge-card-icon {
  display: inline-flex;
  vertical-align: -4px;
  margin-right: 6px;
  color: var(--a1x, var(--a1));
}

.forge-card-icon svg {
  display: block;
}

/* Small undecorated strip of gear icons on each Forge card, previewing
   which equipment types that quality can produce (feature 23: one card per
   quality now, not per slot, since the slot itself is chosen in the modal). */
.forge-card-icon-group {
  display: inline-flex;
  gap: 4px;
  vertical-align: -3px;
  margin-right: 6px;
  color: var(--muted);
}

/* ---------- Forge modal: equipment/weapon-type/attribute pickers ---------- */
.forge-modal-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 4px;
  font: 600 14px/1.3 var(--sans);
}

.forge-modal-title-icon {
  display: inline-flex;
  color: var(--a1x, var(--a1));
}

.forge-radio-group {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 10px 0;
}

/* Generic checkbox/radio pill: a visually-hidden input plus a styled label,
   active state driven purely by :has(), no JS. Reusable anywhere a native
   <select> would otherwise do (feature 23's Forge modal is the first use). */
.pill-radio {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font: 500 12.5px/1 var(--sans);
  color: var(--muted);
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}

.pill-radio input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.pill-radio:hover {
  border-color: var(--a1x, var(--a1));
}

.pill-radio:has(input:checked) {
  border-color: var(--a1);
  color: var(--ink);
  background: color-mix(in srgb, var(--a1) 12%, transparent);
}

/* Everything keyed to which "slot" pill is selected: the modal's own title
   icon and the stat/price preview in its footer, all sharing the same
   data-slot-option value so one :has() rule per slot toggles every one of
   them together, no JS. */
.forge-modal-title-icon [data-slot-option],
.forge-modal-stat [data-slot-option],
[data-choice-submit] [data-slot-option] {
  display: none;
}

/* Scoped to .confirm-box, not .forge-modal: kow.js moves the submit button
   (data-choice-submit) out of the form and next to Cancel once the dialog
   opens, so it and the form's own radios no longer share a common
   .forge-modal ancestor, only their shared dialog box. */
.confirm-box:has(input[name="slot"][value="weapon"]:checked) [data-slot-option="weapon"],
.confirm-box:has(input[name="slot"][value="armor"]:checked) [data-slot-option="armor"],
.confirm-box:has(input[name="slot"][value="offhand"]:checked) [data-slot-option="offhand"],
.confirm-box:has(input[name="slot"][value="gloves"]:checked) [data-slot-option="gloves"],
.confirm-box:has(input[name="slot"][value="boots"]:checked) [data-slot-option="boots"] {
  display: inline-flex;
}

/* Forge modal submit wraps the real button so it can gray out (and explain
   why on hover) when the picked slot costs more gold than the player holds.
   kow.js toggles .is-unaffordable per slot change; the price is per slot. */
.forge-submit {
  position: relative;
  display: inline-flex;
}

/* Lay the button out as a centered flex row so "Forge ·", the gold chip and
   the time chip line up on one baseline. The chips' own vertical-align nudge
   (meant for sitting inline after prose) doesn't apply to flex items and was
   leaving the gold chip riding high inside the button. */
.forge-submit .btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

.forge-submit.is-unaffordable .btn-primary {
  opacity: 0.5;
  cursor: not-allowed;
  /* Blocks the click/submit while leaving the wrapper hoverable, so the hint
     still opens (a plain disabled button would swallow the hover too). */
  pointer-events: none;
}

/* The "not enough gold" hint only exists while unaffordable, anchored above
   the button since the submit sits in the modal's bottom action row. */
.forge-submit:not(.is-unaffordable) .forge-unaffordable-hint {
  display: none;
}

.forge-unaffordable-hint {
  right: auto;
  left: 50%;
  top: auto;
  bottom: calc(100% + 10px);
  transform: translateX(-50%) translateX(var(--hint-shift, 0px));
  text-align: left;
}

.forge-submit.is-unaffordable:hover .forge-unaffordable-hint,
.forge-submit.is-unaffordable:focus-within .forge-unaffordable-hint {
  opacity: 1;
}

.forge-weapon-types {
  display: none;
}

.forge-modal:has(input[name="slot"][value="weapon"]:checked) .forge-weapon-types {
  display: block;
}

.btn-forge {
  font: 600 10px/1 var(--sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: 7px 12px;
  border: 1px solid var(--line);
  border-radius: 3px;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 120ms ease, color 120ms ease;
}

.btn-forge:hover {
  border-color: var(--a1x, var(--a1));
  color: var(--a1x, var(--a1));
}

.resource-chip.is-missing {
  color: #b43a3a;
}

.building-cost-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  margin-bottom: 6px;
}

.building-cost-row .resource-cost {
  justify-content: flex-start;
}

/* Camp building rows' cost + build-time line: plain inline resource chips
   (icon + amount, colored per resource, same .resource-chip idiom the
   header's own resource bar uses), not a bordered box. Costs never
   truncate, they wrap onto a second line as amounts grow past level 10
   (whatsnext.md item 3 made those routinely 6-7 digits) instead of being
   squeezed into a fixed-width cell. The shared upgrade-tooltip/chip-hint
   hover (same JS/CSS as item names and header resource chips) still
   reveals the exact amount, since App\Support\Building::abbreviateAmount()
   only shows a shortened "19.2k"/"7.8m" form once a cost passes 10,000. */
.cost-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 18px;
}

/* .building-action's own rule below (form/.upgrade-tooltip get width: 100%,
   meant for the single full-width Upgrade action) is a descendant selector,
   so it also reaches these chips nested two levels down inside .cost-strip.
   Undo it here, or each chip's flex-basis resolves from that 100% width and
   they stack one per line instead of running inline. Three classes deep so
   it outranks .building-action .upgrade-tooltip regardless of source order. */
.building-action .cost-strip .upgrade-tooltip {
  width: auto;
  flex: none;
}

.cost-strip .resource-chip {
  cursor: help;
  margin-left: 0;
}

.cost-strip .amt {
  font: 600 12.5px var(--sans);
  font-variant-numeric: tabular-nums;
}

/* The default .upgrade-hint opens to the left of its trigger (right: calc(100%
   + 10px)), sized for a right-aligned trigger like a locked upgrade button.
   Cost-strip chips run left to right across the row instead, so that
   default put every chip's hint (worst of all, the leftmost/gold one) off
   the left edge of the viewport on mobile before kow.js's on-load
   clampHintToViewport() pass ever got a chance to pull it back in, which is
   what made Camp's Buildings tab (the only page using .cost-strip) load
   wider than the viewport. Open below and centered instead. */
.cost-strip .upgrade-hint {
  right: auto;
  left: 50%;
  top: calc(100% + 8px);
  transform: translateX(calc(-50% + var(--hint-shift, 0px)));
}

/* Small "Rewards:" label above a resource-chip row (Wilds cards): a plain
   heading, not the page's usual bold h3, so it doesn't compete with the
   card's own title. */
.reward-label {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 600 10px/1 var(--sans);
  letter-spacing: 0.8px;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}

/* Each labeled group (Errand/Duration/Soldiers) on the Expeditions form
   gets its own breathing room and a thin divider, so the page reads as
   distinct steps instead of one crowded form. */
.expedition-field {
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}

.expedition-field:last-of-type {
  border-bottom: 0;
}

/* Duration and Soldiers sit side by side (not stacked like Errand above
   them), one shared divider for the pair instead of one each. */
.expedition-details {
  display: flex;
  gap: 40px;
  flex-wrap: wrap;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}

.expedition-details .expedition-field-inline {
  padding: 0;
  border-bottom: 0;
  flex: 1 1 220px;
}

/* A .building-card doubling as a radio option (Expeditions' Errand step):
   same hidden-input + :has(input:checked) trick as .pill-radio, just on
   the bigger card instead of a pill, so the reward info itself is the
   clickable choice instead of a separate row of pills below it. */
.card-radio {
  position: relative;
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease;
}

.card-radio input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.card-radio:hover {
  border-color: var(--a1x, var(--a1));
}

.card-radio:has(input:checked) {
  border-color: var(--a1);
  background: color-mix(in srgb, var(--a1) 12%, transparent);
}

/* Soldiers amount field on Expeditions: +/- buttons flank the input
   itself, the same idea as the Armory buy/sell modal's qty stepper, so a
   mobile player can tap instead of only having the quarter/half/all links. */
.amount-stepper {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.amount-stepper .amount {
  flex: 1;
  min-width: 0;
  margin-bottom: 0;
}

.amount-stepper-buttons {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.expedition-field > h4 {
  margin: 0 0 8px;
  font: 600 13px/1 var(--sans);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.6px;
}

/* Expeditions' Errand step: a band per errand (same "row, not card grid"
   idea as Hunting Grounds/Dungeons), so the reward rate reads as the
   headline instead of competing with three other cards for attention.
   Reuses .card-radio's hidden-input + :has(input:checked) trick, just laid
   out as a row instead of a bordered box. */
.expedition-errands {
  display: grid;
  gap: 10px;
}

.expedition-errand {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  cursor: pointer;
}

.expedition-errand:hover {
  border-color: var(--a1x, var(--a1));
}

.expedition-errand:has(input:checked) {
  border-color: var(--a1);
  background: color-mix(in srgb, var(--a1) 12%, transparent);
}

.expedition-errand-icon {
  position: absolute;
  right: 2px;
  bottom: -4px;
  height: 82%;
  width: auto;
  color: var(--ink);
  opacity: .05;
  pointer-events: none;
}

body.dark .expedition-errand-icon {
  opacity: .07;
}

.expedition-errand-icon svg {
  height: 100%;
  width: auto;
  display: block;
  fill: currentColor;
}

.expedition-errand-name {
  position: relative;
  font: 600 19px/1.2 var(--serif);
}

.expedition-errand:has(input:checked) .expedition-errand-name {
  color: var(--a1x, var(--a1));
}

.expedition-errand-rate {
  position: relative;
  margin-left: auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  text-align: right;
  white-space: nowrap;
}

.expedition-errand-rate span {
  font-size: 14px;
  font-weight: 600;
}

.expedition-errand-rate small {
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .3px;
  color: var(--faint);
  text-transform: none;
}

@media (max-width: 560px) {
  .expedition-errand-rate small {
    display: none;
  }

  .expedition-durations {
    flex-wrap: nowrap;
  }

  .expedition-duration-chip {
    flex: 1 1 0;
    justify-content: center;
    padding-left: 8px;
    padding-right: 8px;
  }

  .expedition-dispatchline {
    flex-direction: column;
    align-items: stretch;
  }

  .expedition-dispatchline > .upgrade-tooltip {
    display: flex;
  }

  .expedition-dispatchline .btn-primary {
    width: 100%;
    text-align: center;
  }
}

/* Expeditions' Duration step: plain rectangular chips (not the rounded
   .pill-radio used elsewhere), scoped to this page only so Forge's own
   pill-radios stay untouched. Same hidden-input + :has(input:checked)
   trick as .pill-radio/.card-radio. */
.expedition-durations {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.expedition-duration-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
  color: var(--muted);
  font: 600 11px var(--sans);
  letter-spacing: 1px;
  padding: 8px 18px;
  cursor: pointer;
  transition: border-color 120ms ease, color 120ms ease;
}

.expedition-duration-chip input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.expedition-duration-chip:hover {
  border-color: var(--faint);
}

.expedition-duration-chip:has(input:checked) {
  border-color: var(--a1);
  color: var(--a1x, var(--a1));
}

/* Expeditions' Soldiers step: +/- square buttons flanking a large serif
   figure, matching the Duration chips' plain-bordered look instead of the
   rounded .amount-stepper used on Bank/Armory. Still a real <input
   type="number"> underneath (data-max-warn and the quick-fill buttons
   both need one), just skinned to read as a big number, not a text field. */
.expedition-soldier-stepper {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.expedition-stepper-btn {
  background: none;
  border: 1px solid var(--line);
  color: var(--muted);
  width: 34px;
  height: 34px;
  font: 600 16px var(--sans);
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease;
}

.expedition-stepper-btn:hover {
  color: var(--ink);
  border-color: var(--faint);
}

.expedition-stepper-n {
  font: 600 24px/1 var(--serif);
  min-width: 64px;
  text-align: center;
  background: none;
  border: none;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  padding: 4px 0;
  appearance: textfield;
  -moz-appearance: textfield;
}

.expedition-stepper-n::-webkit-inner-spin-button,
.expedition-stepper-n::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.expedition-stepper-n.is-over-max {
  color: #b43a3a;
  border-bottom-color: #b43a3a;
}

.expedition-stepper-avail {
  font-size: 11.5px;
  color: var(--faint);
}

/* The Dispatch line: the submit button plus a live, client-computed
   preview (kow.js) of the yield and return time for whatever is currently
   selected, so a player sees the outcome before committing. */
.expedition-dispatchline {
  display: flex;
  align-items: center;
  gap: 18px;
  flex-wrap: wrap;
  margin-top: 6px;
}

.expedition-preview-wrap {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.expedition-dispatchline .expedition-preview {
  margin: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px;
}

.expedition-preview-warn {
  font-size: 11.5px;
  color: var(--faint);
  font-style: italic;
}

.expedition-kits-line {
  margin-top: 4px;
  font-size: 11.5px;
  color: var(--faint);
}

.expedition-preview-chip {
  display: inline-flex;
  align-items: center;
}

.expedition-preview-icon:not([hidden]) {
  display: inline-flex;
}

.building-action {
  margin-top: auto;
}

.building-action form,
.building-action .upgrade-tooltip {
  display: block;
  width: 100%;
}

.building-action .btn-primary,
.building-action .btn-forge {
  width: 100%;
}

.building-action .upgrade-hint {
  right: auto;
  left: 50%;
  top: calc(100% + 8px);
  transform: translateX(calc(-50% + var(--hint-shift, 0px)));
}

/* ---------- Camp's yard (build-progress strip above the building groups) ----------
   Only rendered while a single-slot build is actually running: the shared
   queue-progress meter, no label and no idle filler line (an idle yard says
   nothing a player needs, so it's simply absent rather than announcing
   "nothing is happening"). Replaces the old boxed "settlement overview"
   banner (tent icon + N-of-M headline + lit/unlit marks), which just
   duplicated what the rows below already show. */
.yard {
  margin-top: 20px;
  padding: 4px 2px 18px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 8px;
}

.yard .compact-meters {
  margin: 0;
}

/* ---------- Building groups (Foundation / Economy / Host) ---------- */
.group {
  margin-top: 32px;
}

.group-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}

.group-head h3 {
  font: 600 12px/1 var(--sans);
  letter-spacing: 1.6px;
  text-transform: uppercase;
  color: var(--muted);
}

.group-head .rule {
  flex: 1;
  height: 1px;
  background: var(--line);
}

/* ---------- Camp building cards (the two-column card redesign) ----------
   Every building is a full-width card split in two: the left column
   ("now") is what it does at its current level - icon/name/level, a bold
   state line, a short italic description - and the right column ("offer",
   on --soft) is what the next level costs and gives: the level label, the
   plain "+delta" gain (reusing .rate-tag.is-positive, the site's
   established gain-green rather than a one-off token), the existing
   .cost-strip chips, then whichever of the four action states applies
   (Upgrade / locked Upgrade with a hover reason / the camp-level gate
   sentence / Rising with time left), pinned to the bottom via
   .building-action's shared margin-top: auto. */
.stead-rows {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.stead-card {
  display: grid;
  grid-template-columns: 1fr 250px;
  border: 1px solid var(--line);
}

.stead-now {
  padding: 18px 20px;
}

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

.stead-icon {
  width: 15px;
  height: 15px;
  flex: none;
  color: var(--faint);
}

.stead-icon svg {
  width: 100%;
  height: 100%;
}

.stead-name {
  font: 600 19px/1.2 var(--serif);
  color: var(--ink);
}

.stead-lvl {
  margin-left: auto;
  font: 600 10px/1 var(--sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.stead-state {
  margin-top: 8px;
  font-size: 13px;
  color: var(--muted);
}

.stead-state b {
  color: var(--ink);
  font-weight: 600;
}

.stead-desc {
  font: 400 12.5px/1.6 var(--sans);
  color: var(--faint);
  margin-top: 8px;
  max-width: 400px;
}

.stead-risebar {
  height: 2px;
  margin: 10px 0 0;
}

.stead-risebar i {
  background: var(--a2x, var(--a2));
}

.stead-offer {
  border-left: 1px solid var(--line);
  background: var(--soft);
  padding: 16px 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.stead-offer-lvl {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
}

.stead-gain {
  font-size: 13px;
}

.stead-offer .cost-strip {
  margin-bottom: 0;
}

.stead-rising-tag {
  font: 600 10.5px/1 var(--sans);
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--a2);
  white-space: nowrap;
}

.b-lockline {
  font: 400 12.5px/1.5 var(--sans);
  color: var(--faint);
  text-align: center;
}

@media (max-width: 600px) {
  .stead-card {
    grid-template-columns: 1fr;
  }

  .stead-offer {
    border-left: none;
    border-top: 1px solid var(--line);
  }
}

/* ---------- Following: the hearth banner + calling rows ----------
   The village banner leads with the idle count itself: a large serif number is
   the page's one call to action (idle followers earn nothing), followed
   by the population count, a gate/next-arrival line, and the same
   resource-chip ledger the header's own tooltips use. */
.hearth {
  margin-top: 20px;
  margin-bottom: 8px;
}

.hearth-head {
  display: flex;
  align-items: center;
  gap: 14px;
}

.hearth-copy {
  flex: 1;
  min-width: 200px;
}

.hearth-idle-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

.hearth-idle-n {
  font: 600 30px/1 var(--serif);
  color: var(--a2);
}

.hearth-idle-n.is-empty {
  color: var(--faint);
}

.hearth-idle-t {
  font: 500 15px/1.4 var(--serif);
  color: var(--ink);
}

.hearth-pop {
  margin-top: 4px;
  font-size: 11.5px;
  color: var(--muted);
}

.hearth-gate {
  margin-top: 12px;
  font-size: 12.5px;
  color: var(--muted);
}

.hearth-gate a {
  color: var(--ink);
  border-bottom: 1px dotted var(--faint);
}

.hearth-gate a:hover {
  color: var(--a1);
}

.hearth-ledger {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 16px;
}

.hearth-ledger .lbl {
  font-size: 11.5px;
  color: var(--faint);
}

/* On a narrow phone the label plus four resource chips no longer fit one
   line; give the label its own line and let the chips wrap together
   below it, rather than breaking mid-chip. */
@media (max-width: 620px) {
  .hearth-ledger .lbl {
    flex-basis: 100%;
  }
}

/* ---------- Calling rows ----------
   One line per calling: outline icon, serif name + flavored count, the
   net hourly effect in resource-chip colors, and the Train/Retrain actions
   at the row's end. Replaces the earlier card-grid layout with the row
   style the rest of the site (World Menu) has moved to. */
.calling-rows {
  display: flex;
  flex-direction: column;
}

.calling-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  padding: 12px 2px;
  border-bottom: 1px solid var(--line);
}

.calling-row-icon {
  width: 17px;
  height: 17px;
  flex: none;
  color: var(--faint);
}

.calling-row-icon svg {
  width: 100%;
  height: 100%;
}

.calling-row-who {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex: none;
  white-space: nowrap;
}

.calling-row-name {
  font: 600 16px/1.2 var(--serif);
}

.calling-row-count {
  font-size: 11px;
  color: var(--faint);
}

/* Holds the effect + actions pair as one unit so it wraps to its own full
   line on mobile without splitting the icon away from the name/count. */
.calling-row-tail {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: 1;
  gap: 16px;
  min-width: 0;
}

.calling-row-effect {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 12.5px;
}

.effect-dot {
  color: var(--faint);
}

.effect-per-hour {
  color: var(--faint);
  font-size: 11px;
  margin-left: 2px;
}

.calling-row-actions {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex: none;
}

/* Train is a quiet chip (frequent, routine action) that only gains
   contrast on row hover, so it doesn't shout five times down the page. */
.calling-train-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: none;
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--muted);
  font: 600 9.5px var(--sans);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  padding: 5px 12px;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 150ms ease, color 150ms ease;
}

.calling-row:hover .calling-train-btn {
  color: var(--ink);
  border-color: var(--faint);
}

.calling-train-btn:hover {
  border-color: var(--ink);
}

.calling-train-btn.is-locked {
  border-color: var(--line);
  color: var(--faint);
  cursor: help;
}

.calling-row:hover .calling-train-btn.is-locked {
  color: var(--faint);
  border-color: var(--line);
}

.calling-retrain-link {
  background: none;
  border: none;
  padding: 0;
  color: var(--faint);
  font: 600 10px/1 var(--sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  cursor: pointer;
  white-space: nowrap;
}

.calling-retrain-link:hover {
  color: var(--ink);
}

.calling-retrain-link.is-locked {
  color: var(--faint);
  opacity: 0.6;
  cursor: help;
}

@media (max-width: 600px) {
  .calling-row-tail {
    flex: 1 1 100%;
  }
}

.outlaw-badge {
  display: inline-block;
  font: 700 10px/1 var(--sans);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: #b43a3a;
  border: 1px solid #b43a3a;
  border-radius: 2px;
  padding: 2px 5px;
  margin-left: 6px;
}

/* The header's own status badge (game-shell.blade.php, $headerStatus: the
   viewing player's own Wounded/In Dungeon, shown next to their HP in the
   resource bar). One color per status so each is recognizable at a
   glance. Add a new .status-{key} rule here whenever App\Support\
   PlayerStatus gains a new status. */
.status-badge {
  display: inline-block;
  font: 700 10px/1 var(--sans);
  letter-spacing: 0.6px;
  text-transform: uppercase;
  border: 1px solid currentColor;
  border-radius: 2px;
  padding: 2px 5px;
  /* Pushes itself (and nothing before it) to the far right when its
     .player-identity parent is a full-width flex row (see .is-lg below);
     a harmless no-op in every other, content-sized usage. */
  margin-left: auto;
}

.resource-bar-vitals .status-badge,
.resource-bar-vitals .upgrade-tooltip.status-badge-tooltip {
  margin-left: 0;
}

.status-infirmary {
  color: #b43a3a;
}

.status-dungeon {
  color: var(--q-legendary);
}

/* Feature 189: the post-defeat shield, steel blue. */
.status-shield {
  color: #4a7fb5;
}

/* Repair-needed color pair, shared by the header icon
   (.resource-chip.repair-alert-*) and the Gear page's intro line
   (.repair-alert-text.repair-alert-*): orange once any equipped hero item's
   condition drops to/below the warning threshold, red at/below the
   critical threshold. See HeroGear::headerRepairSeverity(). */
.resource-chip.repair-alert {
  margin-left: 3px;
}

.repair-alert-warning {
  color: #c47a1f;
}

.repair-alert-critical {
  color: #b43a3a;
}

.repair-alert-text {
  font-weight: 600;
}

/* Unlike every other header chip-hint (info only), the repair hint carries
   an actual link to Gear, so it needs pointer-events restored on hover/tap;
   the shared .chip-hint stays pointer-events:none everywhere else. */
.repair-tooltip .upgrade-hint {
  pointer-events: none;
}

.repair-tooltip:hover .upgrade-hint,
.repair-tooltip:focus-within .upgrade-hint,
.repair-tooltip.is-open .upgrade-hint {
  pointer-events: auto;
}

.repair-tooltip .upgrade-hint a {
  color: inherit;
  text-decoration: underline;
}

/* The In Dungeon badge gets a hover/tap tooltip with the live countdown
   (see game-shell.blade.php); the badge itself keeps pushing right in a
   full-width row, now via its tooltip wrapper instead. */
.upgrade-tooltip.status-badge-tooltip {
  margin-left: auto;
}

.status-badge-tooltip .status-badge {
  margin-left: 0;
}

/* <x-player-identity :status="true">'s own status indicator: a small
   colored icon next to the name (a cross for Wounded, the dungeon glyph
   for In Dungeon), clearer at a glance than a bare dot, not the header's
   bordered text badge either, there is no room for a text label in a
   compact row and no hover/tap needed (title attribute covers a mouse
   tooltip for free); the full text form lives on the public profile
   instead (.profile-hero-status). No color rule here on purpose: it
   always pairs with .status-infirmary/.status-dungeon on the same
   element, which set the actual color; adding `color: currentColor`
   here too was a bug (same specificity, this rule sits later in the
   file, so it silently won and erased the red/purple). */
.status-icon {
  display: inline-flex;
  align-items: center;
}

/* Feature 24: the NPC sibling of .player-identity, no race accent. */
.npc-identity {
  color: var(--muted);
}

.npc-identity b {
  color: var(--ink);
}

.honor-bar {
  margin: 6px 0 18px;
  width: 100%;
}

.honor-bar-track {
  position: relative;
  height: 2px;
  background: var(--line);
}

/* A thin center line at 0, the only fixed reference point on the track
   (matches the mockup's flat neutral bar instead of the old red-to-green
   gradient with a tick per honor title threshold). */
.honor-bar-zero {
  position: absolute;
  left: 50%;
  top: -3px;
  bottom: -3px;
  width: 1px;
  background: var(--faint);
}

.honor-bar-marker {
  position: absolute;
  top: -4px;
  width: 2px;
  height: 10px;
  transform: translateX(-1px);
}

.honor-bar-marker.is-positive {
  background: #3a8f4f;
}

.honor-bar-marker.is-negative {
  background: #b43a3a;
}

.honor-bar-scale {
  display: flex;
  justify-content: space-between;
  font-size: 10px;
  color: var(--faint);
  margin-top: 4px;
}

/* Two columns of champion lists so each row has room; stacks to one column
   on mobile (see the <=620px block). */
.leaderboard-top-columns {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
}

.leaderboard-top-column {
  border: 1px solid var(--line);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  background: color-mix(in srgb, var(--soft) 82%, transparent);
}

.leaderboard-top-column h4 {
  font: 600 14px/1.1 var(--serif);
  margin-bottom: 6px;
}

.leaderboard-top-column-placeholder {
  align-items: center;
  justify-content: center;
  text-align: center;
  color: var(--faint);
}

.leaderboard-top-empty {
  margin: 4px 0 0;
  font-size: 12px;
  font-style: italic;
}

.leaderboard-race-heading {
  margin: 18px 0 8px;
}

/* One clean line per champion: a fixed-width rank medallion, the compact
   identity (race icon + name, truncated), and the score pinned right, all
   vertically centred so the three read as one aligned row. */
.leaderboard-top-row {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  color: var(--ink);
  min-width: 0;
}

.leaderboard-top-row:last-child {
  border-bottom: none;
}

.leaderboard-top-row:hover {
  color: var(--a1);
  text-decoration: none;
}

.leaderboard-top-row:hover .player-identity {
  color: var(--a1);
}

.leaderboard-top-row .player-identity {
  flex: 1;
  min-width: 0;
  flex-wrap: nowrap;
  vertical-align: 0;
  font-size: 14px;
}

.leaderboard-top-row .player-identity b {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Rank number, big enough to read; top three get medal colours. */
.leaderboard-top-rank {
  flex: none;
  width: 24px;
  text-align: center;
  font: 700 18px/1 var(--serif);
  color: var(--faint);
}

.leaderboard-top-row.rank-1 .leaderboard-top-rank {
  color: #b8860b;
}

.leaderboard-top-row.rank-2 .leaderboard-top-rank {
  color: #8a8a8f;
}

.leaderboard-top-row.rank-3 .leaderboard-top-rank {
  color: #a9743b;
}

.leaderboard-top-score {
  flex: none;
  margin-left: auto;
  white-space: nowrap;
  font: 600 12px/1 var(--sans);
  color: var(--muted);
}

.champions-guest-cta {
  margin-top: 34px;
  padding-top: 22px;
  border-top: 1px solid var(--line);
  text-align: center;
}

.champions-guest-cta-note {
  margin-top: 14px;
  color: var(--muted);
}

.champions-guest-cta-note a {
  color: var(--ink);
  font-weight: 600;
}

/* Quiet text pager, no boxed buttons, Previous/Next on both ends of the
   list. Above the list it's just the two links, left and right; below the
   list a third, centered slot holds the "X-Y of Total" count between
   them. There is no "jump to your rank" link any more, the sticky
   .leaderboard-pinned row (see leaderboard.blade.php) covers that: the
   viewer's own row is always visible, on the page or pinned under it. */
.leaderboard-pagination {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  gap: 12px;
  font-size: 11px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--faint);
}

.leaderboard-pagination-nav {
  margin-bottom: 10px;
}

.leaderboard-pagination-bottom {
  margin-top: 14px;
}

.pagination-prev {
  grid-column: 1;
  justify-self: start;
}

.pagination-next {
  grid-column: 3;
  justify-self: end;
}

.leaderboard-page-info {
  grid-column: 2;
  justify-self: center;
  margin: 0;
}

.pagination-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--muted);
}

.pagination-btn:hover {
  color: var(--ink);
  text-decoration: none;
}

/* &larr;/&rarr; text glyphs render at visibly different apparent sizes in
   most fonts, so both directions are the one nav-chevron SVG (a down
   caret) instead, rotated per direction: guaranteed pixel-identical,
   just mirrored, no font rendering to trust. */
.pagination-arrow {
  display: inline-block;
  flex: none;
}

.pagination-prev .pagination-arrow {
  transform: rotate(90deg);
}

.pagination-next .pagination-arrow {
  transform: rotate(-90deg);
}

/* Prev/Next keep only their arrow on phones, there is no room for the
   word too next to the row's own content; the arrow itself grows a
   little there instead, a bare glyph is a small tap target otherwise. */
@media (max-width: 620px) {
  .pagination-btn-label {
    display: none;
  }

  .pagination-arrow {
    width: 15px;
    height: 15px;
  }

  .pagination-btn {
    padding: 4px;
  }
}

.leaderboard-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Column headers ("Lord" / "Standing") above a leaderboard-style list,
   aligned to roughly match the row content below (see .leaderboard-row). */
.leaderboard-cols {
  display: flex;
  gap: 14px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
  padding: 0 4px 6px;
}

.leaderboard-cols .leaderboard-cols-standing {
  margin-left: auto;
}

/* Redesigned as a plain hairline row (border-bottom only, no box, no
   hover fill), one line per lord: rank numeral, race icon (with an
   online-presence dot at its corner, see .player-identity-icon .presence),
   title, name, guild tag, then Standing pinned to the right via
   margin-left: auto on .leaderboard-standing. Never a second line:
   title/name truncate with ellipsis under pressure instead. */
.leaderboard-row {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 10px;
  border: none;
  border-bottom: 1px solid var(--line);
  padding: 11px 4px;
  color: var(--ink);
  /* Not overflow: hidden. The name's own ellipsis truncation lives on
     .leaderboard-row b (its own overflow/white-space/text-overflow), so
     the row doesn't need to clip; if it did, it would also clip (and on
     narrow rows, cut off entirely) the corner presence dot poking past
     its icon, and its hover "last seen" tooltip. */
  overflow: visible;
  transition: padding-left 150ms ease;
}

.leaderboard-list .leaderboard-row:last-child {
  border-bottom: none;
}

.leaderboard-row b {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 600 15px var(--sans);
  /* .q (the title, serif) and this (sans) sit side by side in a flex row
     centered by box height, not text baseline; the two fonts' differing
     metrics otherwise read as the name sitting visibly lower than the
     title next to it. Small manual nudge to line their bottoms up. */
  position: relative;
  top: -1px;
}

.leaderboard-row:hover {
  padding-left: 10px;
  text-decoration: none;
}

.leaderboard-row:hover b {
  color: var(--a1x);
}

.leaderboard-row.is-you {
  background: linear-gradient(90deg, color-mix(in srgb, var(--a2), transparent 92%), transparent 70%);
}

.leaderboard-row.is-you b {
  color: var(--a2x);
}

.leaderboard-rank {
  flex: none;
  min-width: 26px;
  text-align: right;
  font: 600 15px/1 var(--serif);
  color: var(--faint);
}

.leaderboard-row.rank-1 .leaderboard-rank {
  color: var(--gold);
  font-size: 18px;
}

.leaderboard-row.rank-2 .leaderboard-rank,
.leaderboard-row.rank-3 .leaderboard-rank {
  color: var(--muted);
  font-size: 16px;
}

/* Equipped title: smaller than the username (.leaderboard-row b), it's a
   secondary label in front of the name, not the row's main text. */
.leaderboard-row .q {
  font-size: 13.5px;
}

/* Guild tag: a plain small word, not the bordered badge used on the
   profile header/dashboard. */
.leaderboard-row .guild-tag-badge {
  border: none;
  border-radius: 0;
  padding: 0;
  background: none;
  color: var(--faint);
  font-size: 10px;
  letter-spacing: 1px;
  margin-left: -2px;
}

/* Pulled closer to the name than .player-identity's own 6px gap gives it,
   and nudged down 1px: the shield artwork sits visually top-heavy in its
   box, reading a touch too high otherwise. */
.leaderboard-row .player-identity .guild-banner {
  margin-left: 5px;
  position: relative;
  top: 1px;
}

/* The realm ranking word (Streetrat, Urchin, ...) pinned to the row's
   right edge; deliberately never a raw number, see LevelRank. */
.leaderboard-standing {
  margin-left: auto;
  flex: none;
  font: 600 13.5px var(--serif);
  color: var(--muted);
  white-space: nowrap;
  padding-left: 10px;
}

/* The viewer's own row, pinned as a sticky footer under the list when
   their rank sits on a different page (see leaderboard.blade.php). Always
   carries .is-you too, so this needs the same two-class specificity as
   .leaderboard-row.is-you (and to come after it) for its solid background
   to win over that rule's translucent gradient: a sticky footer needs to
   fully hide the rows scrolling past underneath it. */
.leaderboard-row.leaderboard-pinned {
  position: sticky;
  bottom: 0;
  background: var(--bg);
  border-top: 1px solid var(--faint);
  border-bottom: none;
}

@media (max-width: 560px) {
  .leaderboard-cols,
  .leaderboard-row .guild-tag-badge,
  .leaderboard-standing {
    display: none;
  }

  .leaderboard-row {
    gap: 8px;
  }

  .leaderboard-rank {
    min-width: 13px;
    font-size: 10px;
  }

  .leaderboard-row.rank-1 .leaderboard-rank {
    font-size: 11px;
  }

  .leaderboard-row.rank-2 .leaderboard-rank,
  .leaderboard-row.rank-3 .leaderboard-rank {
    font-size: 10.5px;
  }

  .leaderboard-row b {
    font-size: 13.5px;
  }

  .leaderboard-row .q {
    font-size: 12.5px;
  }
}

.leaderboard-race-icon {
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tone-a, var(--a1));
  flex: none;
}

.leaderboard-race-icon svg {
  width: 100%;
  height: 100%;
}

.leaderboard-rankname {
  color: var(--muted);
  font-size: 12px;
}

/* Bounty Board's rule/fineprint copy: smaller, italic, fainter than the
   page's normal .sub, the same quieter register the mockup used for the
   posting rules and the "no honor malus" note. */
.bounty-fineprint {
  font-size: 11.5px;
  font-style: italic;
  color: var(--faint);
  max-width: 480px;
}

#bounty-board-new .bounty-fineprint:first-child {
  margin-bottom: 18px;
}

#bounty-board-new .bounty-fineprint:last-child {
  margin-top: 12px;
}

/* Bounty Board's placement form: same inline .gate row every other
   wager-style form uses (coin-flip, horse-racing), just with the gold
   field narrower than the target-name field so it reads like a price, not
   another name field, and the hint forced onto its own full-width line
   since .gate is a wrapping flex row. Unlike a bet form's short field list,
   this row should use the page's full width rather than .gate's usual
   560px cap, so the Target field actually stretches instead of leaving the
   row looking stranded on the left. */
#bounty-place-form {
  max-width: none;
  width: 100%;
}

#bounty-amount-field {
  flex: 0 1 140px;
}

.bounty-gate-hint {
  flex-basis: 100%;
  margin: 0;
}

/* Bounty Board list: a bespoke row, not the generic .leaderboard-row, since
   the whole point of a bounty is the price on a head, name and gold amount
   both get the site's serif display type (--serif, the same Cormorant
   Garamond family the hero/profile headings already use) instead of the
   usual small sans identity row. */
.bounty-card-list {
  display: flex;
  flex-direction: column;
}

.bounty-card {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 16px;
  padding: 16px 4px;
  border-bottom: 1px solid var(--line);
}

.bounty-card:first-of-type {
  border-top: 1px solid var(--line);
}

.bounty-card-who {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  flex: 1 1 auto;
  color: inherit;
  text-decoration: none;
}

.bounty-card-who:hover {
  text-decoration: none;
}

.bounty-card-who .player-identity b {
  font: 600 22px/1.15 var(--serif);
  letter-spacing: 0.2px;
}

.bounty-card-who:hover .player-identity b {
  color: var(--a1);
}

.bounty-card-sub {
  font-size: 11.5px;
  color: var(--faint);
}

.bounty-card-price {
  margin-left: auto;
  text-align: right;
  white-space: nowrap;
  flex: none;
}

.bounty-card-amount .resource-chip {
  font: 600 26px/1 var(--serif);
  gap: 7px;
  margin-left: 0;
  vertical-align: -4px;
}

.bounty-card-amount .resource-chip svg {
  width: 20px;
  height: 20px;
}

.bounty-card-price-label {
  display: block;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
  margin-top: 3px;
}

.bounty-card-actions {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: stretch;
  width: 100px;
  flex: none;
}

/* Duel and War must land at the same width regardless of label/energy-cost
   digit length ("Duel 10" vs "War 25"), so both the plain <form> and the
   locked variant's .upgrade-tooltip wrapper are forced to fill the fixed
   100px actions column before the button itself is stretched to match. */
.bounty-card-actions form,
.bounty-card-actions .upgrade-tooltip {
  display: flex;
  width: 100%;
}

.bounty-card-actions .btn-action {
  width: 100%;
  justify-content: space-between;
  padding: 8px 12px;
}

.bounty-card-actions .btn-buyoff {
  width: 100%;
}

/* Highest active bounty leads the list, its name and price a size up from
   every other row, echoing how a leaderboard's #1 spot is the one worth
   noticing first. */
.bounty-card.is-top-bounty {
  padding: 20px 4px;
}

.bounty-card.is-top-bounty .player-identity b {
  font-size: 30px;
}

.bounty-card.is-top-bounty .bounty-card-amount .resource-chip {
  font-size: 34px;
}

.bounty-card.is-top-bounty .bounty-card-amount .resource-chip svg {
  width: 26px;
  height: 26px;
}

.bounty-top-flag {
  color: var(--gold);
  font-weight: 600;
}

/* Your own head on the board: a faint warning tint, same idea as
   .leaderboard-row.is-you elsewhere, scoped to this bespoke row instead. */
.bounty-card.is-you {
  background: linear-gradient(90deg, color-mix(in srgb, var(--a1) 8%, transparent), transparent 60%);
}

.bounty-card.is-you .bounty-card-sub {
  color: var(--a1);
}

/* Narrow screens: Duel/War drop below the who/price line onto their own
   full-width row, split evenly between the two, instead of staying a
   cramped fixed-width column squeezed next to the price. */
@media (max-width: 620px) {
  .bounty-card-actions {
    flex: 1 1 100%;
    flex-direction: row;
    width: 100%;
    order: 3;
  }

  .bounty-card-actions form,
  .bounty-card-actions .upgrade-tooltip {
    width: auto;
    flex: 1 1 0;
  }
}

/* Buying off your own bounty: a distinct gold action (not the usual
   ink-bordered .btn-primary) since it destroys gold rather than fighting. */
.btn-buyoff {
  display: inline-block;
  font: 600 11px/1 var(--sans);
  letter-spacing: 2px;
  text-transform: uppercase;
  text-align: center;
  color: var(--gold);
  background: transparent;
  border: 1px solid var(--gold);
  padding: 12px 22px;
  cursor: pointer;
}

.btn-buyoff:hover {
  background: var(--gold);
  color: var(--bg);
}

/* Dragon's Den (feature: pet dragons). Own row/panel language rather than
   reusing Bounty Board's card classes, styled after the owner's HTML
   concept mock: a quiet chip-driven list, serif dragon names, resource-
   colored output lines. */
.dragon-section-title {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 1.8px;
  color: var(--faint);
  margin: 30px 0 4px;
}

.dragon-section-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.dragon-species-count {
  font-weight: 600;
  letter-spacing: normal;
  color: var(--muted);
}

/* The Collection: a codex, not a grid of gray chips. One flowing line of
   names per brood (dominant resource); unfound names are plain faint
   text (absence is the signal, not a wall of identical gray icons),
   found names light up in the brood's own resource color with a small
   dragon sigil. */
.dragon-brood {
  margin: 18px 0 22px;
}

.dragon-brood-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 8px;
}

.dragon-brood-name {
  font: 600 14px var(--sans);
}

.dragon-brood-count {
  font-size: 11px;
  color: var(--faint);
}

.dragon-brood-specs {
  font-size: 13.5px;
  line-height: 1.9;
}

.dragon-spec {
  white-space: nowrap;
  color: var(--faint);
}

.dragon-spec-dot {
  color: var(--faint);
  opacity: 0.5;
  margin: 0 9px;
}

.dragon-spec.is-found {
  font-weight: 600;
}

.dragon-spec-icon {
  display: inline-block;
  width: 12px;
  height: 12px;
  vertical-align: -1px;
  margin-right: 5px;
}

.dragon-brood-stone .dragon-brood-name,
.dragon-brood-stone .dragon-spec.is-found {
  color: #8a919a;
}

.dragon-brood-wood .dragon-brood-name,
.dragon-brood-wood .dragon-spec.is-found {
  color: #7a4b2b;
}

.dragon-brood-food .dragon-brood-name,
.dragon-brood-food .dragon-spec.is-found {
  color: #c9762f;
}

.dragon-brood-mixed .dragon-brood-name,
.dragon-brood-mixed .dragon-spec.is-found {
  color: var(--a2);
}

/* Quiet outlined action button, shared by every Dragon's Den form; also
   usable on a plain <a> (the empty state's link to the Event Calendar). */
.chip {
  display: inline-block;
  background: none;
  border: 1px solid var(--line);
  color: var(--muted);
  font: 600 9.5px var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  padding: 6px 14px;
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s;
  white-space: nowrap;
}

.chip:hover {
  border-color: var(--ink);
  color: var(--ink);
}

/* No eggs and no dragons at all: one unified invitation instead of two
   separate "nothing here" sections stacked on top of each other. */
.dragon-empty-state {
  text-align: center;
  padding: 48px 16px;
  max-width: 380px;
  margin: 0 auto;
}

.dragon-empty-icon {
  display: inline-flex;
  color: var(--faint);
  margin-bottom: 14px;
}

.dragon-empty-title {
  font: 600 22px/1.2 var(--serif);
  margin-bottom: 8px;
}

.dragon-empty-text {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.6;
  margin-bottom: 18px;
}

/* The Clutch: eggs, incubating or waiting. */
.dragon-clutch-row {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 2px;
  border-bottom: 1px solid var(--line);
}

.dragon-clutch-row:first-of-type {
  border-top: 1px solid var(--line);
}

.dragon-clutch-icon {
  width: 18px;
  height: 18px;
  flex: none;
  color: var(--faint);
}

.dragon-clutch-who {
  min-width: 0;
  flex: 1 1 auto;
}

.dragon-clutch-name {
  font: 600 17px/1.2 var(--serif);
}

.dragon-clutch-state {
  text-align: right;
  white-space: nowrap;
  font-size: 12px;
  color: var(--muted);
  flex: none;
}

.dragon-clutch-state b {
  color: var(--ink);
  font-weight: 600;
}

.dragon-clutch-waiting {
  color: var(--faint);
}

.dragon-clutch-track {
  display: block;
  height: 2px;
  background: var(--line);
  margin-top: 6px;
  width: 120px;
}

.dragon-clutch-fill {
  display: block;
  height: 100%;
  background: var(--gold);
}

.dragon-incubate-form {
  flex: none;
}

/* Narrow screens: the Incubate button drops to its own full-width row
   instead of squeezing onto the same line as the egg's name. */
@media (max-width: 620px) {
  .dragon-clutch-row {
    flex-wrap: wrap;
  }

  .dragon-incubate-form {
    flex: 1 1 100%;
    order: 3;
  }

  .dragon-incubate-form .chip {
    display: flex;
    width: 100%;
    justify-content: center;
  }
}

/* The Roles: two panels, the only bordered boxes on the page, since they
   are slots and the container shape should read as such. */
.dragon-roles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  margin-top: 14px;
}

@media (max-width: 560px) {
  .dragon-roles {
    grid-template-columns: 1fr;
  }
}

.dragon-role {
  border: 1px solid var(--line);
  padding: 16px 18px;
}

.dragon-role-label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
}

/* A working/training dragon that's about to run out of stored food, right
   of the role label it's about to stop progressing on. */
.dragon-role-hungry {
  color: #b43a3a;
}

/* Dashboard widget: at a glance, which dragon holds each role and whether
   either is about to run dry, with a link to the Den to manage further. */
.dragon-widget-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-size: 13px;
  padding: 4px 0;
}

.dragon-widget-role {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
  flex: none;
  width: 90px;
}

.dragon-widget-name {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: 600 14px var(--serif);
}

.dragon-widget-icon {
  width: 14px;
  height: 14px;
  color: var(--faint);
}

.dragon-role-name {
  font: 600 20px/1.2 var(--serif);
  margin-top: 6px;
}

.dragon-role-meta {
  font-size: 12px;
  color: var(--muted);
  margin-top: 2px;
}

.dragon-role-output {
  font-size: 12.5px;
  color: var(--muted);
  margin-top: 10px;
}

.dragon-role-fed {
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 12px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.dragon-role-fed-label {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
}

.dragon-role-track {
  height: 2px;
  background: var(--line);
  margin-top: 5px;
}

.dragon-role-fill {
  height: 100%;
  background: #c9762f;
}

.dragon-role-xp {
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 12px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}

.dragon-role-xp b {
  color: var(--ink);
  font-weight: 600;
}

.dragon-role-fill-xp {
  background: var(--a1);
}

.dragon-feed-hint {
  font-size: 11.5px;
  color: var(--faint);
  margin-top: 8px;
}

.dragon-feed-hint .resource-chip {
  vertical-align: -5px;
}

.dragon-merge-result {
  font-size: 11.5px;
  color: var(--faint);
  margin-top: 12px;
}

.dragon-merge-warn {
  font: italic 500 12.5px/1.6 var(--serif);
  color: var(--a1x, var(--a1));
  margin-top: 8px;
}

.dragon-role-output b {
  font-weight: 600;
}

.dragon-role-empty {
  font: italic 500 13px/1.6 var(--sans);
  color: var(--faint);
  margin-top: 8px;
}

.dragon-den .empty-line,
.dragon-clutch .empty-line {
  font: italic 500 13px/1.6 var(--sans);
  color: var(--faint);
  padding: 14px 2px;
}

.dragon-role-actions {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  flex-wrap: wrap;
}

/* The Feed trigger and Stand down button split the row's full width evenly
   between them, rather than sitting compact and left-aligned. */
.dragon-role-actions > .chip,
.dragon-role-actions > form {
  flex: 1 1 0;
  min-width: 120px;
}

.dragon-role-actions form {
  display: flex;
  gap: 6px;
}

.dragon-role-actions form .chip {
  flex: 1 1 auto;
  width: 100%;
}

/* Resource/xp-colored output values, same palette the resource chips use
   elsewhere (never gold, Dragon's Den has no gold output by design). */
.c-food {
  color: #c9762f;
}

.c-xp {
  color: var(--a1);
}

/* The Den: every hatched dragon. */
/* Grid, not flex: the icon and the actions/note column both need to sit
   against the row's first line while the name+meta block is free to wrap
   to two lines, and on narrow screens the actions/note need to drop to
   their own full-width row under the meta line rather than truncating
   the name column. */
.dragon-den-row {
  display: grid;
  grid-template-columns: 1fr 220px;
  align-items: start;
  gap: 2px 12px;
  padding: 13px 2px;
  border-bottom: 1px solid var(--line);
}

.dragon-den-row:first-of-type {
  border-top: 1px solid var(--line);
}

/* Hovering/tapping either the icon or the name opens the same Gear-style
   stats popup (.item-tooltip/.item-hint, reused verbatim): both triggers
   share one [data-upgrade-lock] wrapper, kow.js's delegated click handler
   resolves either one back to the same wrapper. Overrides .item-tooltip's
   own inline-flex so it lays out like every other den row (icon left,
   name+meta block filling the rest) instead of shrink-wrapping to content. */
.dragon-den-row .dragon-den-tooltip {
  display: flex;
  gap: 12px;
  min-width: 0;
  width: 100%;
}

.dragon-den-icon-trigger {
  border: 0;
  background: transparent;
  padding: 0;
  cursor: pointer;
  color: inherit;
  flex: none;
  margin-top: 3px;
}

.dragon-den-icon {
  width: 18px;
  height: 18px;
  display: block;
  color: var(--faint);
}

/* Item-quality tier colors (q-fine/q-superior/...), same ladder gear uses. */
.dragon-den-icon.q-fine,
.item-hint-icon.q-fine,
.dragon-widget-icon.q-fine {
  color: #3f8f4f;
}

.dragon-den-icon.q-superior,
.item-hint-icon.q-superior,
.dragon-widget-icon.q-superior {
  color: #3f6fb0;
}

.dragon-den-icon.q-excellent,
.item-hint-icon.q-excellent,
.dragon-widget-icon.q-excellent {
  color: var(--gold);
}

.dragon-den-icon.q-legendary,
.item-hint-icon.q-legendary,
.dragon-widget-icon.q-legendary {
  color: #8f4fc9;
}

.dragon-den-icon.q-mythic,
.item-hint-icon.q-mythic,
.dragon-widget-icon.q-mythic {
  color: #a3262c;
}

.dragon-den-who {
  min-width: 0;
}

.dragon-den-name {
  font: 600 17px/1.2 var(--serif);
}

.dragon-den-name-trigger {
  border: 0;
  background: transparent;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: left;
}

.dragon-den-meta {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  font-size: 11.5px;
  color: var(--faint);
}

.dragon-role-tag {
  font-size: 10px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  font-weight: 600;
  white-space: nowrap;
  flex: none;
  margin-left: 8px;
}

.dragon-role-tag.is-working {
  color: var(--gold);
}

.dragon-role-tag.is-training {
  color: var(--a1);
}

.dragon-role-tag.is-resting {
  color: var(--faint);
}

.dragon-den-note {
  grid-column: 2;
  grid-row: 1;
  font-size: 11.5px;
  color: var(--faint);
  white-space: nowrap;
  justify-self: end;
}

.dragon-den-acts {
  grid-column: 2;
  grid-row: 1;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  width: 100%;
}

/* Two (or three, with Merge) action buttons split the column's width
   evenly between them instead of clustering right at their own content
   width. */
.dragon-den-acts form {
  display: flex;
  gap: 6px;
  flex: 1 1 0;
  min-width: 110px;
}

.dragon-den-acts form .chip,
.dragon-den-acts form select {
  flex: 1 1 auto;
  width: 100%;
}

/* Narrow screens: actions/note drop to their own full-width row under the
   name+meta block instead of squeezing into a third column. */
@media (max-width: 560px) {
  .dragon-den-row {
    grid-template-columns: 1fr;
  }

  .dragon-den-note,
  .dragon-den-acts {
    grid-column: 1;
    grid-row: 2;
    justify-content: flex-start;
    justify-self: start;
    margin-top: 6px;
  }
}

/* Naming is optional and can happen any time via a modal (choice dialog),
   not just once at hatch: a quiet inline link next to the name/species
   fallback, deliberately sized down from the serif name it sits beside. */
.dragon-rename-trigger {
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
  vertical-align: middle;
  color: var(--faint);
}

.dragon-rename-trigger:hover {
  color: var(--a2x, var(--a2));
}

.dragon-pin-form {
  display: inline-flex;
  vertical-align: middle;
  margin-left: 4px;
}

.dragon-pin-trigger {
  display: inline-flex;
  align-items: center;
  color: var(--faint);
}

.dragon-pin-trigger:hover {
  color: var(--gold);
}

.dragon-pin-trigger.is-pinned {
  color: var(--gold);
}

/* The public profile's pinned-dragon trophy: same Gear-style hover
   tooltip as everywhere else in the Den, laid out as a centered badge
   instead of a row. */
.profile-centered-section .profile-dragon-tooltip {
  display: inline-flex;
  justify-content: center;
}

.profile-dragon-trigger {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  border: 0;
  background: transparent;
  cursor: pointer;
  color: inherit;
  font: inherit;
}

.profile-dragon-icon {
  width: 28px;
  height: 28px;
}

.profile-dragon-name {
  font: 600 16px var(--serif);
}

.profile-dragon-species {
  font-size: 11px;
  color: var(--faint);
}

.profile-dragon-collection {
  font-size: 9.5px;
  letter-spacing: 0.5px;
  color: var(--faint);
  opacity: 0.8;
}

.player-identity {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  vertical-align: -2px;
}

.identity-link {
  color: var(--ink);
}

.identity-link:hover {
  color: var(--a1);
  text-decoration: none;
}

.identity-link:hover b {
  text-decoration: underline;
  text-underline-offset: 3px;
}

.player-identity-icon {
  position: relative;
  width: 18px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tone-a, var(--a1));
  flex: none;
}

.player-identity-icon svg {
  width: 100%;
  height: 100%;
}

/* <x-player-identity :presence="true"> nests a label-less <x-presence-dot>
   right after the race icon's svg; positioned as a small corner badge
   instead of its default inline dot+label, the familiar avatar-status
   pattern. Ringed in the page background so it reads against any row
   state (hover, is-you). */
.player-identity-icon .presence {
  position: absolute;
  right: -3px;
  bottom: -3px;
}

.player-identity-icon .presence-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  box-shadow: 0 0 0 2px var(--bg);
}

/* Member seal on the identity itself, right after the name (feature 46's
   .member-badge is the full "icon + Member" version shown on the profile
   status line; this is the bare icon, gold like that seal, for identities
   used inline all over the game e.g. the Tavern). */
.player-identity-member {
  display: inline-flex;
  align-items: center;
  width: 14px;
  height: 14px;
  color: var(--gold);
  flex: none;
}

.player-identity-member svg {
  width: 100%;
  height: 100%;
}

.player-identity.is-lg {
  display: flex;
  width: 100%;
}

.player-identity.is-lg .player-identity-icon {
  width: 30px;
  height: 30px;
}

.player-identity.is-lg .player-identity-member {
  width: 20px;
  height: 20px;
}

.battle-outcome-header {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 14px;
}

/* Generic reveal-after-duel utility: also used by a War report's Phase Two
   and Plunder sections, which stay hidden until Phase One's line-by-line
   playback finishes (see kow.js's [data-reveal-after-duel] hook). */
.is-hidden {
  /* Utility "hidden" toggle, so it must beat any component's own display rule
     regardless of source order. Without !important, a later same-specificity
     rule like `.battle-report-gains { display: flex }` overrode this, so the
     just-fought gains stayed visible all through the battle playback instead
     of being revealed only at the end. */
  display: none !important;
}

.battle-outcome-title-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}

.battle-outcome-title-row h1 {
  margin: 0;
}

.battle-outcome-links {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: none;
}

.battle-outcome-time {
  display: block;
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--faint);
}

.duel-vs-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
  margin-bottom: 16px;
  text-align: center;
}

.duel-vs-row.is-left {
  justify-content: flex-start;
  text-align: left;
}

.duel-vs-label {
  font: 700 20px/1 var(--serif);
  color: var(--faint);
  flex: none;
}

.battle-outcome-result {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}

/* The whole outcome (verdict word, loot/gains, action buttons) sits at the
   bottom of the report, below the fight log, so nothing spoils the result
   before the log has finished playing; kow.js scrolls this into view once
   it's revealed (see [data-reveal-after-duel] handling). */
.battle-finale {
  margin-top: 24px;
  text-align: center;
}

.battle-verdict {
  font: 700 26px/1.1 var(--serif);
  letter-spacing: 0.4px;
  margin-bottom: 14px;
}

.battle-verdict.is-win {
  color: #3a8f4f;
}

.battle-verdict.is-loss {
  color: #b43a3a;
}

/* One consistent "spoils row" for every battle report's finale, see
   <x-battle-spoils>: real resources (gold/wood/stone/food) always carry
   their SVG icon per the design rules, XP/Honor stay icon-less plain
   figures. The divider sits here (not on .battle-verdict) so it separates
   any bounty/revenge notes from the actual numbers below them, rather
   than always sitting directly under the verdict word regardless of what
   comes between. */
.battle-spoils {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px 28px;
  margin-top: 6px;
}

.battle-spoil {
  text-align: center;
}

.battle-spoil .n {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 700 20px/1 var(--serif);
  color: var(--ink);
}

.battle-spoil .n.gold {
  color: #c8a13a;
}

.battle-spoil .n.wood {
  color: #7a4b2b;
}

.battle-spoil .n.stone {
  color: #8a919a;
}

.battle-spoil .n.food {
  color: #c9762f;
}

.battle-spoil .n.is-positive {
  color: #3a8f4f;
}

.battle-spoil .n.is-negative {
  color: #b43a3a;
}

.battle-spoil .l {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
  margin-top: 4px;
}

/* Small-caps section label inside .battle-finale (e.g. "Found Items" above
   a Hunt/Dungeon item drop), same treatment as a .battle-spoil's own label
   but standalone rather than tied to a number above it. */
.battle-finale-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
  margin: 16px 0 6px;
}

.battle-finale .sub {
  text-align: center;
}

/* War report Phase Two: the "won the duel but the army clash struck their
   hero down too" flavor line reads as a standalone aside, not part of the
   left-aligned figures above it, so it's centered like a .battle-finale .sub. */
.army-clash-hero-note {
  text-align: center;
}

.battle-finale .hunt-again-row {
  display: flex;
  justify-content: center;
}

.battle-outcome-gains {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 20px;
}

.battle-gain {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
}

.battle-gain-value {
  font: 700 20px/1 var(--serif);
  color: var(--a1);
}

.battle-gain-value.is-positive {
  color: #3a8f4f;
}

.battle-gain-value.is-negative {
  color: #b43a3a;
}

.battle-gain-label {
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--faint);
}

.battle-report-group {
  margin-bottom: 22px;
}

.battle-report-group:last-child {
  margin-bottom: 0;
}

/* A small-caps label with a rule trailing off to the right, rather than a
   plain bold h3, so a run of day groups reads like a timeline instead of a
   stack of subheadings. */
.battle-report-group-title {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 8px;
  font: 600 10px var(--sans);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--faint);
}

.battle-report-group-title::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.battle-report-row {
  display: grid;
  grid-template-columns: 64px minmax(0, 1fr) auto 44px;
  grid-template-areas: "outcome main side when";
  align-items: center;
  gap: 8px 14px;
  overflow: visible;
}

.battle-report-row b {
  overflow: visible;
  white-space: normal;
}

.battle-report-outcome {
  grid-area: outcome;
  flex: none;
  font: 700 10px/1 var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  white-space: nowrap;
}

.battle-report-row.is-victory .battle-report-outcome {
  color: #3a8f4f;
}

.battle-report-row.is-defeat .battle-report-outcome {
  color: #b43a3a;
}

.battle-report-main {
  grid-area: main;
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.battle-report-foe {
  font: 600 16px/1.25 var(--serif);
  letter-spacing: 0.2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.battle-report-row:hover .battle-report-foe {
  color: var(--a1);
}

.battle-report-kind {
  font-size: 11px;
  color: var(--faint);
}

.battle-report-role {
  color: var(--muted);
}

/* Per-type icon (Duel/War/Hunt/Camp/Dungeon) on a Battle Reports row,
   so the list reads at a glance without the type text alone. */
.battle-report-type-icon {
  color: var(--muted);
  vertical-align: -1px;
  margin-right: 2px;
}

/* Desktop: resources, soldiers, XP and Honor all sit in one right-aligned
   row of small chips/values, wrapping onto a second line if a row carries
   a lot of spoils (a big war with plunder + soldiers lost + XP + Honor). */
.battle-report-gains {
  display: flex;
  align-items: baseline;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 4px 10px;
  max-width: 320px;
}

.battle-report-gains .resource-chip {
  font-size: 13px;
}

.battle-report-gains .battle-gain {
  flex-direction: row;
  align-items: baseline;
  gap: 5px;
}

.battle-report-gains .battle-gain-value {
  font: 700 16px/1 var(--serif);
}

.battle-report-gains .battle-gain-label {
  font-size: 11px;
  letter-spacing: 0.5px;
}

.hunt-again-row {
  margin: 10px 0 4px;
}

.battle-report-side {
  grid-area: side;
  display: flex;
  flex: none;
  justify-self: end;
  justify-content: flex-end;
  text-align: right;
}

.battle-report-side .sub {
  font-size: 11px;
  white-space: nowrap;
}

.battle-report-when {
  grid-area: when;
  flex: none;
  text-align: right;
  font-size: 11px;
  color: var(--faint);
  white-space: nowrap;
}

@media (max-width: 620px) {
  .leaderboard-top-columns {
    grid-template-columns: 1fr;
  }

  /* Mobile: outcome badge and the time share the top line, the foe's
     name/kind get their own full-width line, spoils wrap onto their own
     row below that. */
  .battle-report-row {
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "outcome when"
      "main main"
      "side side";
    row-gap: 6px;
  }

  .battle-report-gains {
    justify-content: flex-start;
    max-width: none;
  }
}

/* Pinned to the top of the viewport (below the site's own sticky header,
   .kow-top, whose measured height kow.js writes into --realm-nav-top)
   while the battle log below grows, so the HP bars stay in view however
   long the fight runs without needing to scroll back up to check them.
   Always the compact size (name/bar tightly stacked, small identity/icon):
   an earlier pass toggled a bigger size by default and shrank it once
   actually stuck, but the size change while playback was already under way
   read as a glitch, and the bigger size wasn't worth the toggle anyway. */
.duel-heads {
  position: sticky;
  /* --realm-nav-top carries a +16px allowance meant for the sidebar's own
     breathing room (kow.js: kowTop.offsetHeight + 16); subtracted back out
     here so .duel-heads sits flush against the sticky site header instead
     of leaving a 16px gap that the growing log could be glimpsed through
     as it scrolls past underneath. */
  top: calc(var(--realm-nav-top, 96px) - 16px);
  z-index: 5;
  background: var(--bg);
  padding: 4px 0;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--line);
}

.duel-heads .duel-side-head {
  margin-bottom: 2px;
}

.duel-heads .meter-row {
  margin-bottom: 2px;
}

.duel-heads .bar {
  margin-bottom: 0;
}

.duel-heads .player-identity,
.duel-heads .npc-identity {
  font-size: 12px;
}

.duel-heads .player-identity-icon,
.duel-heads .duel-side-icon {
  width: 15px;
  height: 15px;
}

/* Mobile-first: the two heads (name + HP bar each) stack small, one above
   the other, rather than sitting side by side, so the sticky bar stays
   compact on a narrow screen. Widened back out to a side-by-side pair on
   desktop, see the min-width block below. */
.duel-arena {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.duel-equipment-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 12px;
  margin-bottom: 16px;
}

.duel-side-equipment.is-right {
  justify-content: flex-end;
  text-align: right;
}

/* Only meaningful once the two sides sit next to each other (desktop). */
.duel-arena-vs {
  display: none;
  align-items: center;
  justify-content: center;
}

@media (min-width: 621px) {
  .duel-arena {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    gap: 12px;
  }

  .duel-arena-vs {
    display: flex;
  }
}

.duel-side {
  border: 1px solid var(--line);
  padding: 8px 12px;
  background: color-mix(in srgb, var(--soft) 82%, transparent);
  box-shadow: inset 0 2px 0 color-mix(in srgb, var(--tone-a, var(--a1)) 60%, var(--line));
}

/* The viewer's own side keeps the primary race color (above); whichever
   side is the opponent (the other player in a Duel/War, or always the NPC
   in a Hunt/Camp/Dungeon) gets the secondary color instead, so the two
   heads read as visually distinct at a glance even for same-race duels. */
.duel-side.is-opponent {
  box-shadow: inset 0 2px 0 color-mix(in srgb, var(--tone-b, var(--a2)) 60%, var(--line));
}

.duel-side-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 4px;
}

.duel-side-icon {
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--tone-a, var(--a1));
  flex: none;
}

.duel-side-icon svg {
  width: 100%;
  height: 100%;
}

.duel-side-equipment {
  margin-top: 10px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  font-size: 12.5px;
}

/* Grows with the fight instead of clipping to a fixed-height scroll box:
   rounds render top to bottom, oldest to newest, in both the live playback
   (kow.js appends each new line at the bottom and follows it into view,
   see keepLineInView()) and the static reload view. Once at rest, a log
   over 10 rounds gets its middle folded away (see kow.js's foldDuelLog()),
   so a long grinding fight still reads as a short page. */
.duel-log {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-height: 40px;
  padding: 12px 14px;
}

.duel-fold {
  text-align: center;
  font-style: italic;
  color: var(--faint);
  cursor: pointer;
  padding: 6px 0;
  margin: 0 0 6px;
}

.duel-fold:hover {
  color: var(--muted);
}

.duel-round {
  border-bottom: 1px solid var(--line);
  padding-bottom: 8px;
  margin: 0 0 6px;
  line-height: 22px;
}

/* Each round also carries the plain .sub class (for its muted/miss text
   color), whose .page .sub rule sets a 24px margin-bottom meant for
   ordinary paragraph spacing; that same-specificity-but-earlier rule would
   otherwise blow the log's rounds apart instead of the tight 6px stack
   above, so it's pinned back down here for anything inside the log. */
.duel-log .duel-round {
  margin-bottom: 6px;
}

.duel-log .duel-round:last-child {
  margin-bottom: 0;
}

.duel-round:last-child {
  border-bottom: none;
  padding-bottom: 0;
  margin-bottom: 0;
}

.duel-round.is-hit {
  color: var(--ink);
}

.duel-round.is-miss {
  color: var(--muted);
  font-style: italic;
}

.duel-round.is-outcome {
  font-weight: 600;
  font-style: normal;
}

/* The closing "X stands victorious..." line names the fight's actual
   winner, which may not be the viewer: .is-win (the viewer's own side won)
   is the same green as .battle-verdict.is-win, .is-loss (the opponent
   won) the same red as .battle-verdict.is-loss. No separate dark-mode
   value: neither hex is given one anywhere else on the site either. */
.duel-round.is-outcome.is-win {
  color: #3a8f4f;
}

.duel-round.is-outcome.is-loss {
  color: #b43a3a;
}

/* The round-limit line (Combat::resolveDuel()'s round cap reached without
   either hero hitting 0 HP): a system note, not a strike, so it reads
   distinctly from both a landed hit and a miss. */
.duel-round.is-limit {
  color: var(--faint);
  font-style: italic;
}

/* The battle-opening line (App\Support\BattleNarrator::openingLine()):
   scene-setting, deals no damage, so it reads distinctly from a landed hit
   or a miss, same treatment as the round-limit note above. */
.duel-round.is-opening {
  color: var(--faint);
  font-style: italic;
}

/* A critical hit's damage number, called out red mid-sentence so it reads
   at a glance without needing to parse the "Critical" category wording. */
.dmg-critical {
  color: #b43a3a;
  font-weight: 700;
}

/* The identity component's own vertical-align: -2px (tuned for header/
   leaderboard contexts, see .player-identity above) sits a couple px too
   high inside a running duel-log sentence's taller 22px line-height. */
.duel-round .player-identity {
  vertical-align: -4px;
}

.duel-line-in {
  animation: duel-line-in 260ms ease;
}

@keyframes duel-line-in {
  from {
    opacity: 0;
    transform: translateY(-6px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (max-width: 620px) {
  /* The two heads already stack by default (see .duel-arena above, which
     only widens into a side-by-side pair from 621px up); the equipment
     row below the sticky heads stacks the same way on a narrow screen. */
  .duel-equipment-row {
    grid-template-columns: 1fr;
  }

  .duel-side-equipment.is-right {
    justify-content: flex-start;
    text-align: left;
  }
}

.grid-figures {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 40px;
}

/* Forces a wrap inside a .grid-figures flex row: the battle-stats block uses
   it to put the headline figures (battles, won, win rate) on their own line
   and the per-type breakdown on the next, without splitting the container. */
.fig-break {
  flex-basis: 100%;
  height: 0;
}

/* Always 2 columns: War's Phase Two power/soldiers-lost figures read as
   two clear attacker/defender pairs instead of a loose wrapped list. */
.grid-figures-2col {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px 40px;
}

/* Spreads a short, fixed set of figures evenly across the full row width
   instead of hugging the left edge, for pages like admin presence where
   there is no long tail of figures to wrap onto a new line. */
.grid-figures-spread {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  gap: 14px 40px;
}

.fig b {
  display: block;
  font: 500 26px/1.1 var(--serif);
}

.fig b.is-negative {
  color: #b43a3a;
}

.sub .is-negative {
  color: #b43a3a;
  font-weight: 600;
}

/* Bounty Board's live amount hint carries the modifier on the .sub element
   itself (a whole-line warning, e.g. "already has an active bounty"),
   unlike .sub .is-negative above which colors just a nested span. */
.sub.is-negative {
  color: #b43a3a;
  font-weight: 600;
}

.sub.is-positive {
  color: #3a8a4c;
  font-weight: 600;
}

/* A generic tighter-spacing modifier for a form/table that follows other
   content (a table, another form) within the same section, instead of
   .gate's default 44px gap meant for a section's first/only form. */
.is-tight {
  margin-top: 18px;
}

/* Admin edit forms (user-edit.blade.php): .gate's flex-wrap row, capped at
   560px for a handful of small action-form fields, doesn't hold up once a
   section has 5-12 number fields (Buildings, Population, hero item grant)
   - they wrap unevenly instead of lining up in clean columns. This is the
   wide equivalent: a real responsive grid that still stacks to one column
   on a phone. */
.admin-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 14px 18px;
  margin-top: 18px;
}

.admin-form-grid > .btn-primary {
  justify-self: start;
  align-self: end;
}

/* Push notification on/off status (user-edit.blade.php's Progress tab):
   two icon+label chips, the off one dimmed instead of an inline opacity
   style. */
.admin-push-status {
  display: flex;
  align-items: center;
  gap: 14px;
}

.admin-push-status-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.admin-push-status-item.is-off {
  opacity: .4;
}

.fig span {
  font-size: 10.5px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
}

/* A full sentence caption ("Elron lost 12 soldiers.") instead of the
   uppercase label style above: sits directly under its own side's counter,
   not lumped together with the other side's after the whole grid. */
.fig span.fig-note {
  text-transform: none;
  letter-spacing: normal;
  font-size: 12px;
  color: var(--muted);
}

.fig span.fig-note.is-negative {
  color: #b43a3a;
  font-weight: 600;
}

/* War's Phase Two: whichever side is the viewer's opponent (depends on
   whether the viewer is the attacker, defender, or a reinforcement sender,
   see $attackerFigsAreOpponent/$defenderFigsAreOpponent in
   war-report.blade.php) right-aligns its power/soldiers figures instead of
   sharing the same left alignment as the viewer's own side. */
.fig.is-opponent {
  text-align: right;
}

.auth-card {
  margin-top: 54px;
  max-width: 460px;
  border-top: 1px solid var(--line);
  padding-top: 22px;
}

.auth-card h1 {
  font: 600 32px/1.05 var(--serif);
  margin-bottom: 8px;
}

.auth-card-watchtower {
  max-width: none;
  width: 100%;
}

.auth-card-watchtower h1 {
  display: flex;
  align-items: center;
  gap: 12px;
}

.auth-card-watchtower .auth-card-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: none;
  border-radius: 50%;
  color: #c22b2b;
  background: color-mix(in srgb, #c22b2b 10%, transparent);
}

.auth-card-watchtower .gate.stacked-form {
  max-width: none;
}

.auth-card-watchtower p.muted + p {
  margin-top: 4px;
}

.auth-card-watchtower p.watchtower-appeal-status {
  margin-top: 20px !important;
  font-style: italic;
}

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

.form-errors {
  margin-top: 12px;
  color: #c22b2b;
  font-size: 12px;
}

.form-errors ul {
  list-style: none;
}

.field-error {
  display: block;
  margin-top: 4px;
  color: #c22b2b;
  font-size: 11px;
}

/* Admin user-edit: a Save button that stays visible regardless of which
   tab is active, since the underlying <form> spans every tab-panel and
   saves them all together in one request. */
.tab-panel-save {
  margin-top: 20px;
  display: flex;
  align-items: center;
  gap: 12px;
}

.policy-page {
  margin-top: 54px;
  width: 100%;
}

.policy-hero {
  border-top: 1px solid var(--line);
  padding-top: 22px;
}

.policy-hero h1 {
  font: 600 40px/1.05 var(--serif);
  margin-bottom: 10px;
}

.policy-hero .sub {
  color: var(--muted);
  max-width: 760px;
}

.policy-grid {
  margin-top: 30px;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  width: 100%;
}

.policy-grid-rules {
  grid-template-columns: 1fr;
}

.policy-card {
  border: 1px solid var(--line);
  padding: 18px 18px 16px;
  background: color-mix(in srgb, var(--soft) 78%, transparent);
}

.policy-card h3 {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 8px;
}

.policy-card p {
  color: color-mix(in srgb, var(--ink) 88%, var(--muted));
}

.policy-note {
  margin-top: 18px;
  color: var(--muted);
}

/* Error / maintenance pages (errors/500.blade.php): the "back to profile"
   button sits below the hero copy with room to breathe. */
.error-actions {
  margin-top: 28px;
}

/* On mobile, the 500 page's "try again"/"go to..." buttons stack one per
   row at full width instead of wrapping side by side. */
@media (max-width: 620px) {
  .error-actions.action-row {
    flex-direction: column;
    align-items: stretch;
  }

  .error-actions .btn-action {
    width: 100%;
    justify-content: center;
  }
}

/* Admin-only technical detail on the 500 page: exception class/message and
   file:line, monospace so a long path doesn't try to read as prose. */
.error-debug {
  margin-top: 22px;
  max-width: 760px;
}

.error-debug-line {
  font: 12px/1.5 var(--mono, monospace);
  color: color-mix(in srgb, var(--ink) 88%, var(--muted));
  word-break: break-word;
  margin: 4px 0;
}

.q {
  font-weight: 600;
  letter-spacing: 0.2px;
}

.q-gold { color: var(--gold); }

/* The equipped title next to the big name header (Dashboard, public
   profile, .is-lg only, size="sm" identities like Leaderboard rows are
   compact enough already) eats too much width on a narrow screen next to
   the race icon and name; hide it there and rely on the Titles block
   further down the page instead. */
@media (max-width: 620px) {
  .player-identity.is-lg > .q {
    display: none;
  }
}
.q-worn { color: var(--q-worn); }
.q-fine { color: var(--q-fine); }
.q-superior { color: var(--q-superior); }
.q-excellent { color: var(--q-excellent); }
.q-masterwork { color: var(--q-masterwork); }
.q-flawless { color: var(--q-flawless); }
.q-pristine { color: var(--q-pristine); }
.q-exalted { color: var(--q-exalted); }
.q-transcendent { color: var(--q-transcendent); }
.q-legendary { color: var(--q-legendary); }
.q-mythic { color: var(--q-mythic); }
.q-silver { color: var(--q-silver); }
.q-bronze { color: var(--q-bronze); }

/* Ascendant title (feature 55): same green as the old standalone
   .race-laurel badge it replaces, so the color identity stays put. */
.q-ascendant { color: #6fbf5b; }

/* World Event victory titles (feature 89): tinted to match that event's
   own theme color instead of the item-quality ladder, with the wyrm/wraith
   icon inline and vertically centered against the title text. */
/* Wyrmslayer swaps to the other wyrm's color in dark mode, a small nod to
   the Twin Wyrms fight itself (light/dark flipping which wyrm reads
   "brighter"), rather than just re-using the same gold in both themes. */
.q.wyrm-pale { color: var(--wyrm-black); }
body.dark .q.wyrm-pale { color: var(--wyrm-pale); }
/* "Mirrorbreaker" (feature 190): the Mirrorfiend's own metallic dark-blue,
   not the item-quality Mythic red - a World Event title, same theme-color
   pattern as Wyrmslayer/Wraithcall Victor above, not the quality ladder. */
.q.q-mirrorfiend { color: var(--mirrorfiend); }
.title-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* Titles line (feature 89): flex row so the middle-dot separators center
   against the icon+text height of each .title-badge, instead of sitting
   on the surrounding text's baseline. */
.titles-line {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

.title-sep {
  color: var(--muted);
}

/* Titles get a hover/tap explainer of how each was earned; unlike the
   single-line chip hints (energy, HP, resources) this is a full sentence,
   so it wraps instead of the shared .chip-hint nowrap default. */
.title-tooltip .chip-hint {
  white-space: normal;
  width: min(220px, calc(100vw - 64px));
}

/* Member badge sits inside the Titles line too (feature 89); match the
   surrounding title text size there instead of the smaller 12px it uses
   on its original status-line spot. */
.titles-line .member-badge {
  font-size: inherit;
}

/* Changelog: each entry borrows a color off the item-quality ladder above
   (App\Support\Changelog), so it reads as a "quality tier" of update
   rather than inventing a new color language. */
.changelog-list {
  display: flex;
  flex-direction: column;
  gap: 28px;
  margin-top: 22px;
}

.changelog-group {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.changelog-group-title {
  position: sticky;
  top: 0;
  z-index: 1;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--faint);
  background: var(--bg);
  padding: 8px 0;
  margin: 0;
}

.changelog-entry {
  border: 1px solid var(--line);
  border-left: 4px solid var(--accent, var(--line));
  background: color-mix(in srgb, var(--soft) 82%, transparent);
  padding: 16px 18px;
}

.changelog-entry:target {
  border-color: var(--accent, var(--line));
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent, var(--line)) 40%, transparent);
}

.changelog-entry-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}

.changelog-entry-date {
  font-size: 11px;
  color: var(--faint);
  white-space: nowrap;
}

.changelog-entry h3 {
  margin: 2px 0 6px;
  position: relative;
}

.changelog-entry-permalink {
  margin-left: 6px;
  color: var(--faint);
  text-decoration: none;
  font-weight: 400;
  opacity: 0;
}

.changelog-entry h3:hover .changelog-entry-permalink {
  opacity: 1;
}

.changelog-entry.is-copied .changelog-entry-permalink {
  opacity: 1;
  color: var(--accent, var(--faint));
}

.changelog-entry-text {
  color: var(--muted);
  line-height: 1.5;
}

/* A {{wiki:key}} token's rendered link: the page's own name so it reads
   as running text, with the small help icon after it just marking it as
   a wiki link (see App\Support\Changelog::renderBody). */
.changelog-wiki-link {
  color: inherit;
  text-decoration: underline dotted;
  text-underline-offset: 2px;
}

.changelog-wiki-link svg {
  display: inline-block;
  vertical-align: -3px;
  margin-left: 2px;
  margin-right: 4px;
}

/* Small "see what's new" link, footer of every page (game shell included,
   which otherwise has no footer at all). */
.footer-links {
  margin-left: auto;
  display: flex;
  gap: 14px;
}

.footer-links a {
  color: var(--faint);
}

.footer-links a:hover {
  color: var(--muted);
}

footer {
  margin-top: 72px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: 11px;
  color: var(--faint);
  display: flex;
}

.footer-love {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--muted);
}

/* "Also on Android" nudge under Made with love, web only (body.is-native hides
   it inside the Capacitor app, where a store link would make no sense).
   display:flex (not inline) so it drops onto its own line below the love line. */
.world-online-count {
  display: flex;
  justify-content: center;
  margin-top: 24px;
}

.world-online-count .presence {
  text-decoration: none;
}

.world-online-count a.presence {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.footer-app {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 24px;
  color: var(--muted);
  font-size: 13px;
  text-decoration: none;
}

.footer-app:hover {
  color: var(--ink);
}

/* Desktop already has Discord in the left sidebar. On mobile, put the
   community link with the other footer destinations directly below the
   Google Play line instead of mixing it into Account actions. */
.footer-discord-mobile {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 10px;
  color: var(--muted);
  font-size: 13px;
  text-decoration: none;
}

.footer-discord-mobile svg {
  color: #5865f2;
}

.footer-discord-mobile:hover {
  color: var(--ink);
}

body.is-native .js-web-only {
  display: none !important;
}

.page > .footer-love {
  display: flex;
  justify-content: center;
  margin-top: 32px;
}

@media (max-width: 620px) {
  body::before {
    top: 0;
    right: 0;
    bottom: auto;
    width: auto;
    height: 4px;
    background: linear-gradient(to right, var(--a1) 0 50%, var(--a2) 50% 100%);
  }

  .app {
    /* Bottom padding clears the fixed .realm-nav-mobile bar (see below):
       without it, the last bit of page content, footer included, would
       always end up rendered underneath the bar instead of scrollable
       into view above it. */
    padding: 22px 18px calc(100px + env(safe-area-inset-bottom));
    padding-bottom: max(
      calc(100px + env(safe-area-inset-bottom)),
      var(--floating-widget-clearance, 0px)
    );
  }

  .footer-discord-mobile {
    display: flex;
  }

  .hero {
    margin-top: 56px;
  }

  .hero h1 {
    font-size: 40px;
  }

  .layout {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    /* On mobile the sticky header's resource bar already wraps to more
       lines (more chips than fit one row) than on desktop, so this margin
       stacked on top of that reads as too much empty space above the page's
       own content. */
    margin-top: 12px;
  }

  .policy-hero h1 {
    font-size: 34px;
  }

  .policy-grid {
    grid-template-columns: 1fr;
  }

  .realm-nav-desktop {
    display: none;
  }

  .realm-nav-mobile {
    display: flex;
    /* Fixed to the viewport, not the .layout flex column: this bar must
       stay put at all times, scroll position or page length aside, with
       the page's own content (footer included) scrolling normally
       underneath it instead of being sticky-released at the bottom of
       whatever .layout's own height happens to be. */
    position: fixed;
    top: auto;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    width: 100%;
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    border-top: 1px solid var(--line);
    padding: 0 8px calc(8px + env(safe-area-inset-bottom));
    overflow-x: auto;
  }

  .realm-nav a {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    text-align: center;
    font-size: 9.5px;
    white-space: nowrap;
    border-top: 2px solid transparent;
    padding: 8px 6px 7px;
  }

  .realm-nav-mobile .nav-icon {
    display: inline-flex;
    color: var(--muted);
  }

  .realm-nav-mobile .nav-label {
    font-size: 8.5px;
  }

  .realm-nav-mobile a.active .nav-icon {
    color: var(--a1);
  }

  .realm-nav-mobile a.active .nav-label {
    color: var(--a1);
  }

  .realm-nav a .nav-badge {
    position: absolute;
    top: 2px;
    right: 6px;
    font-size: 10px;
    margin-left: 0;
  }

  .world-links {
    grid-template-columns: 1fr;
  }

  .wiki-topbar {
    align-items: center;
  }

  .race-showcase {
    grid-template-columns: 1fr;
  }

  .attribute-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
  }

  .building-grid {
    grid-template-columns: 1fr;
  }

  .meter-row {
    flex-wrap: wrap;
    margin-bottom: 4px;
  }
}

/* Sticky top: header + resource bar stay visible while scrolling */
.kow-top {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--bg);
  border-bottom: 1px solid var(--line);
  padding: 4px 0 8px;
  transition: padding 150ms ease;
}

/* Set by kow.js the moment the page scrolls away from the very top: only
   the gap between the title and the top of the page grows a touch, since
   the bar's no longer flush against the viewport's own edge; the title
   itself stays the same size. */
.kow-top.is-scrolled {
  padding-top: 10px;
}

.resource-bar {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 12px;
  font-size: 13px;
}

.resource-bar .resource-chip {
  gap: 5px;
}

/* Two real rows now, not one flat line split only on mobile: vitals
   (energy/HP), left-aligned; resources (gold/wood/stone/food) left-aligned
   below. */
.resource-bar-vitals,
.resource-bar-resources {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 18px;
}

.resource-bar-vitals {
  justify-content: flex-start;
}

.resource-bar-resources {
  justify-content: flex-start;
}

.resource-chip.energy {
  color: var(--a1);
}

.resource-chip.hp {
  color: #b43a3a;
}

.resource-chip.zeal {
  color: #6a4fa0;
}

.resource-chip.goodwill {
  color: #b0508a;
}

.resource-chip.time {
  color: #4a78c4;
}

/* ---------- Deeds (design doc: deeds.md, owner concept mockup 2026-08-11) ----------
   A deed page is a ladder of the same action at rising stakes: a quiet
   result line under the title, a thin Craft strip, then rows best-work
   first. Locked work is never shown as a grey wall, only a closing
   whisper line once anything is still out of reach. */

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

.deed-title svg {
  flex: none;
  color: var(--a1);
}

.meter-label svg {
  vertical-align: -1px;
  margin-right: 2px;
  color: var(--gold);
}

.deed-scene {
  color: var(--muted);
  font: 400 14px/1.55 var(--sans);
  max-width: 100%;
  margin-bottom: 22px;
}

.bar.is-gold i {
  background: var(--gold);
}

.deed-work {
  display: flex;
  align-items: center;
  gap: 18px;
  padding: 17px 2px;
  border-bottom: 1px solid var(--line);
}

/* Miners' seam depth and Harvest's grow-timer bars reuse Dragon's Den's
   clutch track/fill, but full-width on their own line instead of that
   component's fixed 120px, since a deed row is much wider. */
.deed-work .dragon-clutch-track {
  width: 100%;
  margin-top: 6px;
}

.deed-work-left {
  min-width: 0;
  flex: 1;
}

.deed-work-name {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: 600 20px/1.2 var(--serif);
}

.deed-work-name svg {
  flex: none;
  color: var(--a1);
}

.deed-work-flavor {
  font: 400 12px/1.5 var(--sans);
  color: var(--faint);
  margin-top: 2px;
}

.deed-help-btn {
  flex: none;
  background: none;
  border: 1px solid var(--line);
  color: var(--muted);
  font: 600 9.5px var(--sans);
  letter-spacing: 1.8px;
  text-transform: uppercase;
  padding: 7px 16px;
  cursor: pointer;
  transition: border-color .15s, color .15s;
  white-space: nowrap;
}

.deed-help-btn i {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-style: normal;
  color: var(--zeal, #6a4fa0);
  margin-left: 6px;
  font-weight: 600;
}

.deed-help-btn i svg {
  width: 10px;
  height: 10px;
}

.deed-work:hover .deed-help-btn:not(:disabled) {
  color: var(--ink);
  border-color: var(--faint);
}

.deed-help-btn:not(:disabled):hover {
  border-color: var(--ink);
}

.deed-help-btn:disabled {
  color: var(--faint);
  cursor: default;
}

.deed-help-btn:disabled i {
  color: var(--faint);
}

/* A row's reward range/rate, between the flavor text and its action chip:
   without it there's no way to compare spots/seams before trying one
   (owner-reported: "niet logisch om een andere plek te kiezen"). */
.deed-work-yield {
  flex: none;
  font-size: 12px;
}

/* Tend the Sick's per-patient step progress: a row of small squares
   instead of a numeric bar, since the whole point is a handful of
   discrete steps, not a smooth quantity. */
.deed-patient-steps {
  display: flex;
  gap: 4px;
}

.deed-patient-step {
  width: 9px;
  height: 9px;
  border: 1px solid var(--faint);
  background: none;
}

.deed-patient-step.is-done {
  background: var(--gold);
  border-color: var(--gold);
}

/* Treat sits right next to the treatment count/stage word instead of
   pinned to the whole row's vertical center, so the name/flavor lines
   above keep the full row width for their own varying lengths (owner
   feedback, mobile). */
.deed-patient-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 7px;
}

.deed-patient-progress {
  display: flex;
  align-items: center;
  gap: 8px;
}

.deed-patient-stage {
  font-size: 11px;
  color: var(--muted);
}

/* Tend the Sick's bed, drawn as a small line-art strip instead of a
   generic icon (owner concept mockup, 2026-08-12): empty beds show the
   bare frame, occupied ones the patient under the blanket with faint
   fever-waver strokes above. */
.deed-bed {
  width: 56px;
  height: 35px;
  flex: none;
}

.deed-bed .frame {
  fill: none;
  stroke: var(--faint);
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.deed-bed.is-occupied .frame {
  stroke: var(--muted);
}

.deed-bed .fever {
  fill: none;
  stroke: #b43a3a;
  stroke-width: 1.3;
  stroke-linecap: round;
}

/* Two-line combine-option body for the Sow/Admit pickers: a title row
   (icon + name) and a meta row of resource-chip-style stats, instead of
   one long text line that had no icons and read cramped on mobile. */
.deed-option-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.deed-option-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font: 600 14px var(--sans);
}

.deed-option-title svg {
  flex: none;
  color: var(--a1);
}

.deed-option-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  font-size: 11px;
}

.deed-deeper {
  font: 400 12.5px/1.8 var(--sans);
  color: var(--faint);
  text-align: center;
  padding: 14px 0;
}

/* Ease the Miners' open-seam block (owner concept mockup, 2026-08-11): a
   push-your-luck dial instead of a ladder row, the growing haul number
   staged center as the one thing this deed is actually about. */
.deed-seam {
  text-align: center;
  padding: 34px 0 30px;
  border-bottom: 1px solid var(--line);
}

.deed-seam-name {
  font: 600 22px/1.2 var(--serif);
}

.deed-seam-flavor {
  font: 400 12.5px/1.5 var(--sans);
  color: var(--faint);
  margin-top: 2px;
}

.deed-seam-haul {
  font: 600 54px/1 var(--serif);
  margin-top: 22px;
  font-variant-numeric: tabular-nums;
}

.deed-seam-haul.stone {
  color: #8a919a;
}

.deed-seam-haul-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 2.5px;
  text-transform: uppercase;
  color: var(--faint);
  margin-top: 8px;
}

.deed-seam-meta {
  display: flex;
  justify-content: center;
  gap: 26px;
  font-size: 12.5px;
  color: var(--muted);
  margin-top: 20px;
  flex-wrap: wrap;
}

.deed-seam-meta b {
  font-weight: 600;
  color: var(--ink);
}

.deed-seam-risk b {
  transition: color .3s;
}

.deed-seam-risk.is-calm b {
  color: var(--ink);
}

.deed-seam-risk.is-uneasy b {
  color: var(--gold);
}

.deed-seam-risk.is-grim b {
  color: #b43a3a;
}

.deed-seam-riskline {
  font: 400 13px/1.6 var(--sans);
  color: var(--faint);
  margin-top: 14px;
  min-height: 20px;
}

.deed-seam-riskline.is-grim {
  color: #b43a3a;
}

.deed-collect-btn {
  margin-top: 22px;
  background: none;
  border: 1px solid var(--ink);
  color: var(--ink);
  font: 600 11.5px var(--sans);
  letter-spacing: 2.5px;
  text-transform: uppercase;
  padding: 11px 38px;
  cursor: pointer;
  transition: background .15s, color .15s;
}

.deed-collect-btn:hover {
  background: var(--ink);
  color: var(--bg);
}

/* Bring in the Harvest's field plots (owner concept mockup, 2026-08-11):
   the plot's own progress bar is drawn as a field strip instead of a
   numeric bar (see deeds/partials/field.blade.php). */
.deed-plot {
  padding: 18px 0 6px;
  border-bottom: 1px solid var(--line);
}

.deed-plot-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 0 2px;
  flex-wrap: wrap;
}

.deed-plot-name {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: 600 20px/1.2 var(--serif);
}

.deed-plot-name svg {
  flex: none;
  color: var(--a1);
}

.deed-plot-state {
  font-size: 12px;
  color: var(--muted);
}

.deed-plot-state b {
  color: var(--ink);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.deed-plot-state.is-ready {
  color: #3a8f4f;
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
}

.deed-plot-act {
  margin-left: auto;
}

.deed-plot-flavor {
  font: 400 12.5px/1.5 var(--sans);
  color: var(--faint);
  padding: 2px 2px 10px;
}

.deed-field {
  display: block;
  width: 100%;
  height: auto;
  margin-top: 6px;
}

.deed-field .soil {
  stroke: var(--faint);
  opacity: .45;
  fill: none;
  stroke-width: 1.2;
}

.deed-field .crop {
  stroke: var(--faint);
  fill: none;
  stroke-width: 1.3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.deed-field .crop.is-ripe {
  stroke: #c9762f;
}

/* The Ledger heading's link to the Boon shop (dashboard.blade.php). */
.ledger-boons-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .5px;
  text-transform: uppercase;
  color: #b0508a;
  margin-left: 8px;
  text-decoration: none;
}

.ledger-boons-link:hover {
  text-decoration: underline;
}

/* The Boon shop (App\Support\Boosts, owner concept mockup 2026-08-12,
   boon-shop-concept.html): active boons list. */
.boon-active-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 11px 2px;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}

.boon-active-name {
  font: 600 15px var(--serif);
}

.boon-active-flavor {
  color: var(--faint);
  font-size: 12px;
  font-style: italic;
}

.boon-active-time {
  margin-left: auto;
  color: #4a78c4;
  font-weight: 600;
  font-size: 12px;
  white-space: nowrap;
}

.boon-none {
  font: italic 500 13.5px/1.6 var(--serif);
  color: var(--faint);
  padding: 10px 2px;
}

.boon-cap-note {
  font: italic 500 12.5px/1.5 var(--serif);
  color: var(--faint);
  margin-top: 8px;
  padding: 0 2px;
}

/* Resource identity colors, reused from .resource-chip.* (gold/wood/stone/
   food) so a boon reads the same color everywhere it's named. */
.boon-active-name.r-wood, .boon-opt.is-on.r-wood { color: #7a4b2b; }
.boon-opt.is-on.r-wood { border-color: #7a4b2b; }
.boon-active-name.r-stone, .boon-opt.is-on.r-stone { color: #8a919a; }
.boon-opt.is-on.r-stone { border-color: #8a919a; }
.boon-active-name.r-food, .boon-opt.is-on.r-food { color: #c9762f; }
.boon-opt.is-on.r-food { border-color: #c9762f; }
.boon-active-name.r-gold, .boon-opt.is-on.r-gold { color: #c8a13a; }
.boon-opt.is-on.r-gold { border-color: #c8a13a; }

/* The composer: three chip rows (Stores/Lift/For) instead of a 60-button
   price grid, one live offer sentence + price + Buy (deeds-boons.js). */
.boon-pickrow {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}

.boon-picklbl {
  width: 74px;
  flex: none;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
}

.boon-opt {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: none;
  border: 1px solid var(--line);
  color: var(--muted);
  font: 600 11px var(--sans);
  letter-spacing: 1px;
  padding: 6px 14px;
  cursor: pointer;
  transition: border-color .15s, color .15s;
}

.boon-opt:hover {
  border-color: var(--faint);
}

.boon-opt.is-on {
  border-color: var(--ink);
  color: var(--ink);
}

.boon-offer {
  display: flex;
  align-items: baseline;
  gap: 18px;
  flex-wrap: wrap;
  border-top: 1px solid var(--line);
  padding-top: 18px;
  margin-top: 6px;
}

.boon-sentence {
  font: 500 16px/1.5 var(--serif);
  color: var(--ink);
}

.boon-sentence b {
  font-weight: 700;
}

.boon-price {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  font: 600 20px var(--serif);
  color: #b0508a;
  white-space: nowrap;
}

.boon-price small {
  font: 500 11px var(--sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--faint);
  margin-left: 6px;
}

.boon-buy {
  background: none;
  border: 1px solid var(--ink);
  color: var(--ink);
  font: 600 11px var(--sans);
  letter-spacing: 2.2px;
  text-transform: uppercase;
  padding: 10px 30px;
  cursor: pointer;
  transition: background .15s, color .15s;
}

.boon-buy:hover {
  background: var(--ink);
  color: var(--bg);
}

.boon-buy:disabled {
  border-color: var(--line);
  color: var(--faint);
  cursor: default;
}

.boon-shortline {
  font: italic 500 12px var(--serif);
  color: var(--faint);
  flex-basis: 100%;
}

@media (max-width: 560px) {
  .boon-sentence {
    flex-basis: 100%;
  }
}

.deed-help-btn.is-primary {
  border-color: var(--ink);
  color: var(--ink);
}

.deed-help-btn.is-primary:hover {
  background: var(--ink);
  color: var(--bg);
}

@media (max-width: 560px) {
  .deed-work {
    gap: 12px;
  }

  .deed-work-name {
    font-size: 18px;
  }

  .deed-bed {
    width: 40px;
    height: 25px;
  }
}

/* The active training job's bar sits directly above the "up next" list, so it
   drops the tall default compact-meters/bar bottom spacing (meant to separate
   a lone bar from a following section) and lets the queue sit close under the
   progress bar. Scoped to #training-queue so every other bar keeps its rhythm. */
#training-queue .compact-meters {
  margin-bottom: 6px;
}

#training-queue .bar {
  margin-bottom: 0;
}

.train-upnext {
  list-style: none;
  margin: 4px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
}

.train-upnext li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.train-cancel-row {
  margin-top: 10px;
}

/* Active training job: the countdown and its small Cancel sit together at the
   right end of the meter-row, so the running job uses the same compact row as
   the queued ones instead of an oversized button below the progress bar. */
.train-row-end {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

/* "Manage" link under a queue progress bar (Dashboard events reuse the shared
   <x-queue-progress>, which the Camp/Following/Gear queues also use). */
.queue-manage {
  display: block;
  margin-top: 0;
  text-align: right;
  font-size: 12px;
  color: var(--muted);
}

/* The "Manage" link sits close under the bar. The gap is the bar's own
   bottom margin (a plain .bar carries 12px); a negative margin-top on the
   inline-block link is ignored, so tighten it at the source instead. */
.queue-progress .bar {
  margin-bottom: 4px;
}

/* Tighter bottom margin for the shared queue block than a plain .compact-meters,
   so stacked Dashboard queues sit closer together. */
.compact-meters.queue-progress {
  margin-bottom: 12px;
}

/* The Cancel hover tooltip carries a gold refund chip (project.md's resource
   display rule), so its label and the chip lay out on one line; the plain
   .upgrade-hint is text-only. */
.cancel-tooltip .upgrade-hint {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.btn-cancel {
  font-size: 10px;
  padding: 8px 14px;
}

/* ---------- Tabs (radio + CSS :has(), no JS) ----------
   Markup: a .tabs wrapper holds the (visually hidden) radio inputs, a
   .tab-bar of <label for="..."> triggers, then one .tab-panel per tab
   carrying data-tab-panel="<input id>". Add one pair of :has() rules below
   per new tab id; there are only a handful of tab groups in the whole app,
   so this stays easier to read than a fully generic selector. */
.tab-input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.tab-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 26px;
}

.tab-btn {
  display: inline-block;
  padding: 10px 20px;
  font: 600 11px/1 var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 120ms ease, border-color 120ms ease;
}

/* Unread count on a tab (e.g. Rookery Messages/Notifications). */
.tab-badge {
  display: inline-block;
  min-width: 16px;
  height: 16px;
  margin-left: 6px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--a1);
  color: var(--bg);
  font: 700 10px/16px var(--sans);
  letter-spacing: 0;
  text-align: center;
  vertical-align: middle;
}

.tab-btn:hover {
  color: var(--ink);
}

/* Plain-link tab bars (e.g. guild XP log's today/week/month filters) render
   the active state server-side via this class instead of the :has()/radio
   trick above, since each filter is its own page load, not a client toggle. */
.tab-btn.is-active {
  color: var(--ink);
}

.tab-panel {
  display: none;
}

.tabs:has(#gear-tab-hero:checked) label[for="gear-tab-hero"],
.tabs:has(#gear-tab-soldiers:checked) label[for="gear-tab-soldiers"],
.tabs:has(#camp-tab-buildings:checked) label[for="camp-tab-buildings"],
.tabs:has(#camp-tab-forge:checked) label[for="camp-tab-forge"],
.tabs:has(#camp-tab-bank:checked) label[for="camp-tab-bank"],
.tabs:has(#wilds-tab-grounds:checked) label[for="wilds-tab-grounds"],
.tabs:has(#wilds-tab-camps:checked) label[for="wilds-tab-camps"],
.tabs:has(#wilds-tab-contracts:checked) label[for="wilds-tab-contracts"],
.tabs:has(#board-tab-daily:checked) label[for="board-tab-daily"],
.tabs:has(#board-tab-weekly:checked) label[for="board-tab-weekly"],
.tabs:has(#user-tab-identity:checked) label[for="user-tab-identity"],
.tabs:has(#user-tab-hero:checked) label[for="user-tab-hero"],
.tabs:has(#user-tab-buildings:checked) label[for="user-tab-buildings"],
.tabs:has(#user-tab-population:checked) label[for="user-tab-population"],
.tabs:has(#user-tab-resources:checked) label[for="user-tab-resources"],
.tabs:has(#user-tab-gear:checked) label[for="user-tab-gear"],
.tabs:has(#user-tab-progress:checked) label[for="user-tab-progress"],
.tabs:has(#lb-tab-realm:checked) label[for="lb-tab-realm"],
.tabs:has(#lb-tab-champions:checked) label[for="lb-tab-champions"],
.tabs:has(#lb-tab-race:checked) label[for="lb-tab-race"],
.tabs:has(#lb-tab-guilds:checked) label[for="lb-tab-guilds"],
.tabs:has(#account-tab-profile:checked) label[for="account-tab-profile"],
.tabs:has(#account-tab-notifications:checked) label[for="account-tab-notifications"],
.tabs:has(#account-tab-security:checked) label[for="account-tab-security"],
.tabs:has(#horse-tab-horses:checked) label[for="horse-tab-horses"],
.tabs:has(#horse-tab-history:checked) label[for="horse-tab-history"],
.tabs:has(#events-tab-calendar:checked) label[for="events-tab-calendar"],
.tabs:has(#events-tab-history:checked) label[for="events-tab-history"] {
  color: var(--a1x, var(--a1));
}

.tabs:has(#gear-tab-hero:checked) [data-tab-panel="gear-tab-hero"],
.tabs:has(#gear-tab-soldiers:checked) [data-tab-panel="gear-tab-soldiers"],
.tabs:has(#camp-tab-buildings:checked) [data-tab-panel="camp-tab-buildings"],
.tabs:has(#camp-tab-forge:checked) [data-tab-panel="camp-tab-forge"],
.tabs:has(#camp-tab-bank:checked) [data-tab-panel="camp-tab-bank"],
.tabs:has(#wilds-tab-grounds:checked) [data-tab-panel="wilds-tab-grounds"],
.tabs:has(#wilds-tab-camps:checked) [data-tab-panel="wilds-tab-camps"],
.tabs:has(#wilds-tab-contracts:checked) [data-tab-panel="wilds-tab-contracts"],
.tabs:has(#board-tab-daily:checked) [data-tab-panel="board-tab-daily"],
.tabs:has(#board-tab-weekly:checked) [data-tab-panel="board-tab-weekly"],
.tabs:has(#user-tab-identity:checked) [data-tab-panel="user-tab-identity"],
.tabs:has(#user-tab-hero:checked) [data-tab-panel="user-tab-hero"],
.tabs:has(#user-tab-buildings:checked) [data-tab-panel="user-tab-buildings"],
.tabs:has(#user-tab-population:checked) [data-tab-panel="user-tab-population"],
.tabs:has(#user-tab-resources:checked) [data-tab-panel="user-tab-resources"],
.tabs:has(#user-tab-gear:checked) [data-tab-panel="user-tab-gear"],
.tabs:has(#user-tab-progress:checked) [data-tab-panel="user-tab-progress"],
.tabs:has(#lb-tab-realm:checked) [data-tab-panel="lb-tab-realm"],
.tabs:has(#lb-tab-champions:checked) [data-tab-panel="lb-tab-champions"],
.tabs:has(#lb-tab-race:checked) [data-tab-panel="lb-tab-race"],
.tabs:has(#lb-tab-guilds:checked) [data-tab-panel="lb-tab-guilds"],
.tabs:has(#account-tab-profile:checked) [data-tab-panel="account-tab-profile"],
.tabs:has(#account-tab-notifications:checked) [data-tab-panel="account-tab-notifications"],
.tabs:has(#account-tab-security:checked) [data-tab-panel="account-tab-security"],
.tabs:has(#account-tab-danger:checked) [data-tab-panel="account-tab-danger"],
.tabs:has(#horse-tab-horses:checked) [data-tab-panel="horse-tab-horses"],
.tabs:has(#horse-tab-history:checked) [data-tab-panel="horse-tab-history"],
.tabs:has(#events-tab-calendar:checked) [data-tab-panel="events-tab-calendar"],
.tabs:has(#events-tab-history:checked) [data-tab-panel="events-tab-history"] {
  display: block;
}

@media (max-width: 620px) {
  .tab-btn {
    flex: 1 1 0;
    text-align: center;
    padding: 10px 8px;
  }
}

/* Feature 56: Notifications filter chips (All / Following / Building / War). */
.raven-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 18px;
}

.raven-filter {
  appearance: none;
  padding: 5px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font: 600 11px/1 var(--sans);
  letter-spacing: 0.5px;
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
}

.raven-filter:hover {
  color: var(--ink);
  border-color: var(--a1x, var(--a1));
}

.raven-filter.active {
  color: var(--bg);
  background: var(--a1x, var(--a1));
  border-color: var(--a1x, var(--a1));
}

@media (max-width: 620px) {
  .raven-filter {
    flex: 1 1 0;
    text-align: center;
  }
}

/* ---------- Gear page: hero slots, satchel, armory rows ---------- */

.hero-slots {
  margin-bottom: 8px;
}

.hero-slot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 16px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
}

.hero-slot:first-child {
  border-top: 1px solid var(--line);
}

.hero-slot-name {
  width: 64px;
  flex: none;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
}

.hero-slot-item {
  flex: 1;
}

.hero-slot-item.hint {
  color: var(--faint);
}

.hero-slot-act {
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

/* Unlike .act (the satchel/armory table's action cells), .hero-slot-act
   isn't a <td>, so its <form>s never picked up the table's own
   `.act form { display: inline }` reset and defaulted to block, stacking
   each button on its own line instead of sitting beside it. */
.hero-slot-act form {
  display: inline-flex;
}

.btn-icon-sm {
  font: 600 14px/1 var(--sans);
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  width: 26px;
  height: 26px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease, background-color 120ms ease;
}

/* A combine job in progress (feature 55): the icon reads green (the theme's
   positive tone) with its "In progress" hint on hover/tap. The live countdown
   and bar live in the Gear page's Combine Queue, not inline behind the item. */
.btn-icon-sm.is-combining {
  color: #3f8f5c;
  border-color: color-mix(in srgb, #3f8f5c 45%, var(--line));
  cursor: default;
}

.btn-icon-sm:hover {
  color: var(--a1x, var(--a1));
  border-color: var(--a1x, var(--a1));
  background: color-mix(in srgb, var(--a1x, var(--a1)) 10%, transparent);
}

/* Guild member row action icons: promote green, demote orange, kick red,
   transfer blue, each keeping its own color on hover instead of switching
   to the page's accent color like the default .btn-icon-sm:hover does. */
.btn-icon-sm.icon-promote {
  color: #2f8f4f;
  border-color: color-mix(in srgb, #2f8f4f 35%, var(--line));
}

.btn-icon-sm.icon-promote:hover {
  color: #2f8f4f;
  border-color: #2f8f4f;
  background: color-mix(in srgb, #2f8f4f 12%, transparent);
}

.btn-icon-sm.icon-kick {
  color: #b43a3a;
  border-color: color-mix(in srgb, #b43a3a 35%, var(--line));
}

.btn-icon-sm.icon-kick:hover {
  color: #b43a3a;
  border-color: #b43a3a;
  background: color-mix(in srgb, #b43a3a 12%, transparent);
}

.btn-icon-sm.icon-transfer {
  color: #2f6fb4;
  border-color: color-mix(in srgb, #2f6fb4 35%, var(--line));
}

.btn-icon-sm.icon-transfer:hover {
  color: #2f6fb4;
  border-color: #2f6fb4;
  background: color-mix(in srgb, #2f6fb4 12%, transparent);
}

.btn-icon-sm.icon-manage-rank {
  color: #7a4fb4;
  border-color: color-mix(in srgb, #7a4fb4 35%, var(--line));
}

.btn-icon-sm.icon-manage-rank:hover {
  color: #7a4fb4;
  border-color: #7a4fb4;
  background: color-mix(in srgb, #7a4fb4 12%, transparent);
}

.btn-icon-sm:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  color: var(--muted);
  border-color: var(--line);
}

/* Hero item name + hover/tap popup with its slot icon, stat and condition.
   Reuses the existing upgrade-tooltip open/close JS via [data-upgrade-lock]
   /[data-upgrade-lock-trigger], just anchored below-left instead of to the
   right, since item names sit at the left edge of a row, not a header chip. */
.item-tooltip {
  position: relative;
  display: inline-flex;
}

.item-name-trigger {
  border: 0;
  background: transparent;
  font: inherit;
  color: inherit;
  padding: 0;
  cursor: help;
  text-decoration: underline dotted;
  text-underline-offset: 3px;
  text-decoration-color: var(--faint);
  /* A <button> defaults to centered text in every browser's UA stylesheet;
     harmless while the name fits on one line (shrink-to-fit width equals
     the text width, so there's no slack to center within), but once a long
     name wraps in a narrow cell the button's box widens to the available
     space and the wrapped lines visibly drift to center without this. */
  text-align: left;
}

.item-bonus {
  color: var(--a2x, var(--a2));
  font-weight: 600;
}

.item-hint {
  position: absolute;
  left: 0;
  top: calc(100% + 8px);
  z-index: 100;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 10px;
  background: color-mix(in srgb, var(--bg) 92%, #000 8%);
  border: 1px solid var(--line);
  color: var(--ink);
  padding: 8px 12px;
  border-radius: 3px;
  font-size: 11px;
  width: max-content;
  max-width: min(240px, calc(100vw - 16px));
  white-space: normal;
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
  transform: translateX(var(--hint-shift, 0px));
}

.item-hint-icon {
  flex: none;
  display: inline-flex;
}

.item-hint-icon svg {
  display: block;
}

.item-hint-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.item-hint-title {
  font-weight: 600;
}

.item-tooltip:hover .item-hint,
.item-tooltip:focus-within .item-hint,
.item-tooltip.is-open .item-hint {
  opacity: 1;
}

/* Item-link chips inside a chat message (Guild Chat / World Event chat,
   both use .wraithcall-chat for their message list): the default
   position: absolute hint (a) gets clipped by the chat's own
   overflow-y: auto scroll box instead of floating over it, and (b) still
   expands that box's scrollable content while invisible (opacity: 0 isn't
   display: none), showing as dead whitespace under the newest message.
   Switched to position: fixed, placed in viewport coordinates by
   assets/js/kow.js's clampHintToViewport() (which also opens it upward
   from the trigger, the natural direction for a popup near a chat's
   newest/bottom message) instead of the CSS top/bottom every other
   .item-hint on the site still uses. */
.wraithcall-chat .item-hint {
  position: fixed;
  top: 0;
  left: 0;
  bottom: auto;
}

.sub2 {
  display: block;
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 1px;
}

/* Combine dialog (feature 51): a list of spare items to sacrifice, each a
   selectable row with a radio and the quality-coloured item name (its stats
   popup opens on hover/tap). Confirm lives in the dialog's bottom row. */
.combine-radio-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 2px 0 12px;
  text-align: left;
}

/* Socket modal (feature 12/23): a .combine-radio-group per field (which
   socket, which pouch gem), same choice-dialog shell as Combine.
   Selector beats .choice-options form's own display:block on specificity. */
.choice-options form.socket-form {
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-align: left;
}

.combine-option {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease;
}

.combine-option:hover {
  border-color: var(--a1x, var(--a1));
}

.combine-option:has(input:checked) {
  border-color: var(--a1);
  background: color-mix(in srgb, var(--a1) 12%, transparent);
}

/* Radio drives selection but is visually hidden; the row itself highlights
   when picked (see :has(input:checked) above), no radio dot. */
.combine-option input[type="radio"] {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

/* Repair All sits between the hero-slots grid and the Satchel table,
   right-aligned so it reads as an action on everything above it, not a
   heading for what follows. */
.repair-all-row {
  display: flex;
  justify-content: flex-end;
  margin: 4px 0 16px;
}

/* Your Stats: a true grid (not .grid-figures' flex-wrap, whose column count
   depends on content width) so the four counters always split the full row
   width evenly, 4-up on desktop and 2-up on mobile. */
.hero-stats-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 14px 20px;
  margin-bottom: 20px;
}

@media (max-width: 620px) {
  .hero-stats-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* The trigger spans the whole figure (label and number both), so hovering
   or tapping anywhere on either line opens the trained/gear breakdown,
   not just the number. */
.hero-stat-fig .upgrade-tooltip {
  display: flex;
  width: 100%;
}

.hero-stat-trigger {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100%;
  text-align: center;
  border: 0;
  background: transparent;
  font: inherit;
  color: inherit;
  margin: 0;
  padding: 0;
}

/* Only the interactive figures (Attack/Defense power, with a breakdown
   hint) are an actual <button>; the plain counts (Equipped, Not Equipped)
   reuse the same layout as a <span>, which needs no pointer cursor. */
button.hero-stat-trigger {
  cursor: pointer;
}

/* Label above the number (markup order), lighter than it: --muted rather
   than the default .fig span --faint, which read too washed-out in light
   mode and too low-contrast in dark mode for a primary label like this.
   Two classes, not one, so this beats .fig span's own (class + type)
   specificity instead of silently losing to it. */
.hero-stat-fig .hero-stat-label {
  color: var(--muted);
}

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

.cond.low {
  color: var(--warn, #b8860b);
  font-weight: 600;
}

/* Buttons made to read as plain inline text actions, same as the anchor
   links they sit next to in an .act cell. */
.act,
td.act,
.table th.act {
  text-align: right;
  white-space: nowrap;
}

/* The Satchel's action column can now hold five-plus icon buttons per row
   (equip, repair, sell, re-forge, socket, combine), which no longer
   reliably fits on one line next to a long gem-suffixed item name. Flex +
   wrap (instead of the shared .act's inline/nowrap + margin-left spacing)
   keeps the name column from being squeezed narrow and gives the wrapped
   second line the same gap as the first, both directions from one `gap`. */
.satchel-table td.act {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 8px 10px;
  white-space: normal;
}

.satchel-table td.act > * + * {
  margin-left: 0;
}

/* Sits directly under the tight figures grid, so it needs a little air
   above it when the Smithy is not built yet. */
.armory-smithy-hint {
  margin-top: 16px;
}

/* Sub-headers splitting the armory into its own "Armory Stock" (buy/sell)
   and "Equip Your Army" (equip/unequip) grids, so buying gear and building
   an army composition read as two distinct steps instead of one crowded
   card per tier/role. */
.armory-section-title {
  margin-top: 20px;
  font: 600 15px/1.2 var(--serif);
}

/* Army equipment cards (reuses .building-grid/.building-card's own 2-col
   desktop / 1-col mobile layout, same 620px breakpoint) instead of the old
   dense table: a row per tier/role packed a quantity field plus four
   buttons into one table cell, which never fit a table's fixed columns on
   any viewport. Name+role sit on the card head next to the stock/equipped
   count and the equip/unequip buttons; price, quantity and buy/sell move
   to their own row below, wrapping naturally on narrow screens. */
.armory-card {
  gap: 10px;
}

.armory-name {
  font: 600 17px/1.2 var(--serif);
}

/* Slightly larger than the shared .sub2 default (11.5px), which reads too
   small under the bigger .armory-name it sits beneath. */
.armory-name .sub2 {
  font-size: 13px;
}

.armory-name-block {
  min-width: 0;
}

.armory-equip-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

/* Armory cards top-align the name against the action buttons instead of
   .building-card-head's default baseline, which drifted odd once the
   Equip/Unequip buttons stacked into two rows on the right. */
.armory-card .building-card-head {
  align-items: flex-start;
}

/* Stock/equipped figures sit right under the name/role, not as their own
   block after the action buttons: that used to add a whole extra row of
   height to every card. */
.armory-stock-figures {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin-top: 3px;
  color: var(--muted);
  font-size: 12px;
}

/* Equip/Unequip stack as two text buttons instead of sitting side by side
   as +/- icons, so the words stay readable at a glance on the "Equip Your
   Army" cards. */
.armory-equip-buttons {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.btn-armory-toggle {
  padding: 3px 10px;
  font-size: 12px;
  line-height: 1.3;
  width: 76px;
  text-align: center;
}

/* The Sell trigger sits right next to Equip/Unequip now, so it needs the
   same icon-button footprint; the gold tint marks it as the one action
   that pays out instead of just reassigning stock. Only the rest state is
   overridden here: .btn-icon-sm:hover's own (higher-specificity, since it
   pairs a class with :hover) accent color/background already wins on
   hover, same as every other icon button. */
.armory-sell-btn {
  color: #c8a13a;
  border-color: color-mix(in srgb, #c8a13a 45%, var(--line));
}

.armory-buy-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.armory-buy-row .hint {
  margin-right: auto;
}

/* Buy/Sell modal quantity field: label above a plain number input, same
   shape as the Forge modal's own fields above it. */
.choice-qty-field {
  display: block;
  margin: 10px 0;
}

.choice-qty-field span {
  display: block;
  font: 600 11px/1 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
  margin-bottom: 4px;
}

.choice-qty-field input {
  width: 100%;
  font: 500 15px/1.3 var(--sans);
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 8px 10px;
}

.choice-qty-field input:disabled {
  opacity: 0.4;
  color: var(--muted);
  border-color: var(--line);
  cursor: not-allowed;
}

/* Groups the quantity input with its own +/- stepper buttons, so a mobile
   player can tap to nudge the amount instead of only having the keyboard
   or the quarter/half/all quick-fill links below it. */
.choice-qty-input-row {
  display: flex;
  align-items: center;
  gap: 6px;
}

.choice-qty-input-row input {
  flex: 1;
  min-width: 0;
}

/* .choice-options button (the option-picker rule further down) forces
   display:block/width:100%/text-align:left on every plain button inside a
   choice dialog, which would stretch these two square steppers to the full
   row width; override with a selector that beats it on class count alone,
   restating .btn-icon-sm's own square-icon shape. Fixed px (not
   width:auto/aspect-ratio) because content-based shrink-to-fit width made
   these collapse to the glyph's width while the row's height stretched them
   tall, producing a narrow rectangle instead of a square. */
.choice-qty-input-row .btn-icon-sm {
  display: inline-flex;
  width: 38px;
  height: 38px;
  text-align: center;
  flex-shrink: 0;
}

/* Reinforcements' Send modal: a compact key/value summary (kits sent,
   defense power, travel time) instead of loose sentences, same minimalist
   label/value shape as .choice-qty-field's uppercase micro-label. */
.choice-summary {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 10px 0;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 3px;
}

.choice-summary > div {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.choice-summary dt {
  font: 600 11px/1 var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--muted);
}

.choice-summary dd {
  margin: 0;
  font: 500 14px/1.3 var(--sans);
  color: var(--ink);
  text-align: right;
}

/* Dashboard's Reinforcements panel: a single-line row (icon, name, meta
   text, action pushed right) matching the dashboard hero redesign's other
   thin-divider lists (.bonds-row, .ledger-row), not the bordered-card
   .leaderboard-row look used everywhere else. */
/* "Stationed elsewhere" / "Garrisoning here" group labels: the default
   .sub bottom margin (24px, meant to separate prose from what follows)
   reads as a big gap sitting right above a tight .reinforcement-row list. */
.sub.reinforcement-group-label {
  margin-bottom: 4px;
  font-size: 11.5px;
}

.reinforcement-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}

.reinforcement-row:last-child {
  border-bottom: none;
}

.reinforcement-row-icon {
  width: 14px;
  height: 14px;
  flex: none;
  color: var(--a1);
}

.reinforcement-row-icon svg {
  width: 100%;
  height: 100%;
  display: block;
}

.reinforcement-row-who {
  font-weight: 600;
  color: inherit;
  text-decoration: none;
  flex: none;
}

.reinforcement-row-who:hover {
  text-decoration: underline;
}

.reinforcement-row-meta {
  color: var(--muted);
  font-size: 12px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.reinforcement-row-meta .link-btn {
  color: inherit;
  text-decoration: underline dotted;
}

.reinforcement-row-act {
  margin-left: auto;
  flex: none;
}

.reinforcement-row-act .btn-ghost:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.act form {
  display: inline;
}

.act a,
.link-btn {
  color: var(--muted);
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

.act a:hover,
.link-btn:hover {
  color: var(--a2x, var(--a2));
  text-decoration: underline;
  text-underline-offset: 3px;
}

.sell-btn {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 4px 8px;
  height: 26px;
  cursor: pointer;
  transition: color 120ms ease, border-color 120ms ease;
}

.sell-btn:hover {
  color: var(--a1x, var(--a1));
  border-color: var(--a1x, var(--a1));
}

.act > * + * {
  margin-left: 10px;
}

/* Satchel/hero-slot action cells mix plain forms, icon buttons and a
   native <details> trigger; each aligns to its own baseline by default,
   which reads as a jagged row. Middle-aligning every direct child keeps
   them on one visual line regardless of what's inside. */
.act > * {
  vertical-align: middle;
}

.forge-form {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.forge-form select {
  border: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  padding: 2px 0 4px;
}

.qty {
  width: 52px;
  font: 500 13.5px/1.3 var(--sans);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-align: right;
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 2px 0 4px;
  margin-right: 4px;
  appearance: textfield;
  -moz-appearance: textfield;
}

.qty::-webkit-inner-spin-button,
.qty::-webkit-outer-spin-button {
  -webkit-appearance: none;
}

.qty::placeholder {
  color: var(--faint);
  font-weight: 400;
}

.qty:focus {
  outline: none;
  border-bottom-color: var(--a2x, var(--a2));
}

tr.locked td {
  opacity: 0.45;
}

tr.locked:hover td {
  background: transparent;
}

tr.locked .act {
  opacity: 1;
}

tr.locked .lock {
  font-size: 11.5px;
  color: var(--faint);
  font-style: italic;
}

@media (max-width: 620px) {
  .col-price,
  .col-stat,
  .col-condition {
    display: none;
  }

  .hero-slot {
    gap: 4px 12px;
  }
}

.btn-cancel-sm {
  font-size: 9px;
  padding: 4px 8px;
  letter-spacing: 1px;
  text-transform: uppercase;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

/* ---------- ravens ---------- */
.nav-badge {
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0;
  color: var(--a1);
  margin-left: 7px;
}

.msg {
  margin-top: -6px;
}

.msg .body {
  color: var(--muted);
  white-space: pre-line;
}

/* Same class of bug as .duel-round .player-identity below: the identity
   component's own vertical-align: -2px (tuned for header/leaderboard rows)
   sits too high inside a raven body's running sentence line-height. */
.msg .body .player-identity {
  vertical-align: -4px;
}

/* The NPC identity carries no icon (only a bold name), so the -2px/-4px
   nudges above, which exist solely to seat the removed identity icon, would
   drop the word below the surrounding sentence's baseline. Reset it to the
   text baseline in every context it appears (plain, duel arena log, raven
   body). Listed after the context rules so it wins at equal specificity. */
.npc-identity,
.duel-round .npc-identity,
.msg .body .npc-identity {
  vertical-align: baseline;
}

/* One line per raven: a bare outline icon (no circle badge, unlike
   leaderboard/battle-report rows), subject text that truncates rather than
   wraps, then a short time column and a hover-reveal arrow. Deliberately
   quieter/denser than the rest of the site's list rows, matching the
   Rookery concept mockup. */
.raven-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 4px;
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  transition: padding-left 120ms ease;
}

.raven-row:first-child {
  border-top: 1px solid var(--line);
}

.raven-row:hover {
  padding-left: 8px;
  text-decoration: none;
}

/* Day-bucket header above a run of rows, same "label + trailing rule"
   language as .battle-report-group-title so both list pages read as a
   timeline. */
.raven-day {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 22px 0 4px;
  font: 600 10px var(--sans);
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--faint);
}

.raven-day::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.raven-list .raven-day:first-child {
  margin-top: 0;
}

/* Quiet affordance that the whole row is the action, fading in on hover so
   the row itself stays uncluttered otherwise. */
.raven-row-arrow {
  flex: none;
  color: var(--faint);
  opacity: 0;
  transition: opacity 120ms ease;
}

.raven-row:hover .raven-row-arrow {
  opacity: 1;
}

.raven-row-icon {
  flex: none;
  width: 16px;
  height: 16px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--faint);
}

.raven-row-icon svg {
  width: 100%;
  height: 100%;
}

.raven-row.unread .raven-row-icon {
  color: var(--a2x, var(--a2));
}

.raven-row-txt {
  min-width: 0;
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13.5px;
}

.raven-row.unread .raven-row-txt {
  color: var(--ink);
}

.raven-row.unread .raven-subject {
  font-weight: 600;
}

.raven-row-from {
  color: var(--muted);
}

.raven-row-when {
  margin-left: auto;
  flex: none;
  text-align: right;
  font-size: 11px;
  color: var(--faint);
  white-space: nowrap;
}

@media (max-width: 560px) {
  .raven-row-txt {
    white-space: normal;
  }
}

.raven-mark-read {
  margin: 40px 0 0;
}

.raven-history {
  margin: 22px 0 0;
  border-top: 1px solid var(--line);
  padding-top: 14px;
}

/* A tinted background per message, rather than a divider line, makes the
   own/theirs split immediately readable, in the same subtle-tint language
   as .field's own soft backgrounds elsewhere. */
.raven-history-msg {
  padding: 10px 12px;
  margin-bottom: 10px;
  border-radius: 6px;
  background: var(--soft);
}

.raven-history-msg:last-child {
  margin-bottom: 0;
}

.raven-history-msg.is-mine {
  background: color-mix(in srgb, var(--a1) 9%, var(--soft));
}

.raven-history-meta {
  margin: 0;
  font-size: 12px;
  color: var(--muted);
}

.raven-history-msg.is-mine .raven-history-meta {
  color: var(--a1);
}

.raven-history-body {
  margin: 0;
  color: var(--muted);
  white-space: pre-line;
}

/* Compose's To/Subject row: wider than .gate's generic 560px cap so the
   pair fills the page on desktop, still one full-width field per row on
   narrow screens since each basis alone exceeds a phone's viewport. */
.raven-send-form .gate {
  max-width: none;
}

.raven-send-form .gate .field {
  flex: 1 1 320px;
}

.raven-body-field {
  margin-top: 14px;
  margin-bottom: 18px;
}

.raven-body-field textarea {
  width: 100%;
  min-height: 220px;
  resize: vertical;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  padding: 8px;
  font: inherit;
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--ink) 45%, transparent) transparent;
}

.raven-body-field textarea::-webkit-scrollbar {
  width: 8px;
}

.raven-body-field textarea::-webkit-scrollbar-track {
  background: transparent;
}

.raven-body-field textarea::-webkit-scrollbar-thumb {
  background-color: color-mix(in srgb, var(--ink) 45%, transparent);
  border-radius: 4px;
  border: 2px solid transparent;
  background-clip: padding-box;
}

.raven-body-field textarea::-webkit-scrollbar-thumb:hover {
  background-color: color-mix(in srgb, var(--ink) 70%, transparent);
}

.raven-body-count {
  margin: 4px 0 0;
  text-align: right;
  font-size: 12px;
}

.admin-battle-line-field {
  max-width: 720px;
  margin: 14px 0 18px;
}

.admin-battle-line-field textarea {
  width: 100%;
  resize: vertical;
  border: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  padding: 8px;
  font: inherit;
}

.admin-placeholder-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 8px;
}

.admin-placeholder-buttons .btn-ghost {
  font-size: 11px;
  padding: 4px 8px;
}

/* Reusable themed confirm dialog (kow.js's window.kowConfirm / <form
   data-confirm>), standing in for the browser's own window.confirm() so
   destructive actions look like the rest of the game instead of an
   unstyled system alert. Sits above literally everything else, including
   the .upgrade-hint/.item-hint/.combine-form popups (100) and the sticky
   header (20). */
.confirm-overlay[hidden] {
  display: none;
}

.confirm-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: color-mix(in srgb, #000 55%, transparent);
}

.confirm-box {
  max-width: 360px;
  width: 100%;
  max-height: calc(100vh - 80px);
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--line);
  padding: 20px;
  box-shadow: inset 0 2px 0 var(--a1);
}

/* The choice dialog can hold a full form (Forge's slot/weapon-type/
   attribute pickers), not just a list of plain option buttons, so it gets
   more room than the short-text confirm dialog. */
.choice-overlay .confirm-box {
  max-width: 480px;
}

/* Some multi-step choice dialogs contain typed values that are costly to
   reconstruct if a fast touch gesture escapes the scroll box. Their trigger
   opts into explicit-close mode in kow.js: Cancel (or a successful submit)
   is the only way out, and scroll chaining is contained inside the dialog. */
.choice-overlay.is-explicit-close .confirm-box {
  overscroll-behavior: contain;
}

/* Combine dialog: let each spare's stats popup (absolutely positioned below
   its row) escape the box instead of expanding its scroll area, which showed
   an unwanted scrollbar. Scoped so the taller Forge form still scrolls. */
.choice-overlay .confirm-box:has(.combine-radio-group) {
  overflow: visible;
}

/* Account Clusters "View matched details"/"View trades" tables: wider than
   the default 480px choice-box so the fingerprint-comparison table (7
   columns) and the trade table fit without a horizontal scrollbar; cells
   wrap instead of forcing a fixed width. */
.choice-overlay .confirm-box:has(.admin-cluster-modal) {
  max-width: min(90vw, 800px);
}

/* Auction House withdraw confirm: the message embeds <x-hero-item-name>,
   whose own hover/tap popup is absolutely positioned below the item name
   the same way the combine dialog's stats popup is above, so it gets the
   same overflow-escaping fix instead of an unwanted scrollbar. Excludes
   .choice-overlay: the "offer equipment" satchel picker also embeds
   <x-hero-item-name> per row, and with a large satchel this same rule made
   its box unscrollable, hiding the "Post Listing" button below the fold. */
.confirm-overlay:not(.choice-overlay) .confirm-box:has(.item-tooltip) {
  overflow: visible;
}

.confirm-message {
  margin: 0 0 18px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--ink);
}

.confirm-actions {
  display: flex;
  justify-content: space-between;
  gap: 10px;
}

.confirm-actions.is-single-action {
  justify-content: center;
}

/* Options list inside the reusable "choice" dialog (kow.js's
   data-choice-trigger), e.g. Following's Retrain picker. Each option is a
   real <form><button> cloned straight out of a <template>, so this just
   lays them out as a full-width vertical stack. */
.choice-options {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}

.choice-options form {
  display: block;
}

/* The dialog lives outside .page (kow.js appends it straight to <body>),
   so a plain <h3> inside it (e.g. Forge's "Equipment"/"Weapon Type"
   section labels) never picks up .page h3's styling; restated here. */
.choice-options h3 {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 2px;
  color: var(--faint);
  margin-top: 14px;
  margin-bottom: 8px;
}

.choice-options h3:first-child {
  margin-top: 0;
}

.choice-options button {
  display: block;
  width: 100%;
  text-align: left;
}

/* The Forge modal's own submit button (data-choice-submit) isn't one of
   the stacked "pick one of these options" buttons above (those are
   Following's Retrain-style option list): kow.js moves it out of
   .choice-options into .confirm-actions, next to Cancel, before the
   .choice-options button rule above ever gets a chance to apply to it. */

/* Following's Retrain picker: the calling icon for each option, and its
   own heading (the dialog's default title is plain text set via
   textContent, so it can't carry a resource-chip) with the gold cost
   inline right after the text instead of dropping to its own line. */
.choice-options .calling-choice-option {
  display: flex;
  align-items: center;
  gap: 8px;
}

.choice-dialog-heading-inline {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

/* Chat item-link "+" picker (assets/js/chat-item-picker.js): the trigger
   button next to each chat composer's input, and the overlay it opens
   (reuses .confirm-overlay/.choice-overlay/.choice-options for the shell
   and list layout - only the search input and the "item gone" fallback
   need their own rules). */
.chat-item-picker-trigger {
  flex: 0 0 auto;
  padding-left: 10px;
  padding-right: 10px;
}

.chat-item-picker-search {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  padding: 7px 4px;
  margin-bottom: 12px;
}

.chat-item-picker-search:focus {
  outline: none;
  border-bottom-color: var(--a1);
}

/* .choice-options button already sets display:block, which outranks the
   plain [hidden] UA rule on specificity, so a filtered-out option would
   otherwise stay visible. */
.chat-item-picker-list button[hidden] {
  display: none;
}

.chat-item-missing {
  color: var(--faint);
  font-style: italic;
}

/* Picker's "what do you want to share?" first step: Equipment, Dragon or
   Battle report. Thumb-friendly choices rather than the compact single-line
   options .choice-options normally holds. */
.chat-picker-kind-choice {
  display: flex;
  flex-direction: row;
  gap: 8px;
  margin-bottom: 4px;
}

.chat-picker-kind-choice .chat-picker-kind-option {
  flex: 1 1 0;
  width: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 16px 8px;
  text-align: center;
  color: var(--muted);
  border: 0;
  border-radius: 3px;
  transition: color 120ms ease, background 120ms ease;
}

.chat-picker-kind-choice .chat-picker-kind-option svg {
  color: var(--faint);
  transition: color 120ms ease;
}

.chat-picker-kind-choice .chat-picker-kind-option:hover,
.chat-picker-kind-choice .chat-picker-kind-option:focus-visible {
  color: var(--ink);
  background: color-mix(in srgb, var(--a1) 6%, transparent);
}

.chat-picker-kind-choice .chat-picker-kind-option:hover svg,
.chat-picker-kind-choice .chat-picker-kind-option:focus-visible svg {
  color: var(--a1);
}

/* Battle report chip shared into chat (components/battle-report-chip.
   blade.php): reuses .item-tooltip/.item-hint's hover/tap popup shell
   wholesale, just adding the win/loss badge and war's two-phase summary
   lines on top. */
.dragon-chat-name-trigger {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.dragon-chat-rates {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 2px 4px;
}

.dragon-chat-rates .resource-chip {
  margin-left: 0;
  font-size: 11px;
}

.report-name-trigger {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.report-outcome {
  font-weight: 600;
}

.report-outcome.is-win {
  color: #3a8f4f;
}

.report-outcome.is-loss {
  color: #b43a3a;
}

.report-hint-phase {
  color: var(--muted);
}

.report-hint-label {
  font-weight: 600;
  margin-top: 2px;
}

.report-hint-stat-row {
  line-height: 1.5;
}

.report-hint-gains {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  margin-left: 0;
}

.report-hint-gains .resource-chip {
  margin-left: 0;
  font-size: 11px;
}

/* ==========================================================================
   Tavern (feature 41): a flat forum. Topic overview with search + pagination,
   and a per-topic thread with its own pagination. Minimalist, race-accented,
   reusing --line/--soft/--a1 like the rest of the game pages.
   ========================================================================== */

/* Page header: title on the left, "New topic" button pinned top-right. */
.tavern-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.tavern-head .title-help {
  margin: 0;
}

.tavern-new-btn {
  flex: none;
}

.tavern-head-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

.tavern-admin-only-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* A touch more air under the header/button row than the default .sub. */
.tavern-intro {
  margin-top: 14px;
}

/* Compact house rules block above the topic list. */
.tavern-rules {
  margin-bottom: 18px;
  padding: 10px 14px;
  border-left: 3px solid var(--line);
  background: var(--soft);
}

.tavern-rules h3 {
  margin: 0 0 6px;
  font-size: 14px;
}

.tavern-rules ul {
  margin: 0;
  padding-left: 18px;
  font-size: 13px;
  color: var(--muted);
}

.tavern-rules li {
  margin-bottom: 2px;
}

.tavern-rules .tavern-rules-note {
  margin: 10px 0 0;
  font-size: 12px;
}

body.dark .tavern-rules {
  background: color-mix(in srgb, var(--bg) 78%, #000 22%);
}

/* Success flash, shared by the overview and the thread. */
.tavern-flash {
  margin: 0 0 18px;
  padding: 10px 14px;
  border-left: 3px solid var(--a1);
  background: var(--soft);
  color: var(--ink);
  font-size: 14px;
}

body.dark .tavern-flash {
  background: color-mix(in srgb, var(--bg) 78%, #000 22%);
}

/* Shared form styling (new topic + reply). */
.tavern-form .tavern-field {
  margin-bottom: 14px;
}

.tavern-field label {
  display: block;
  margin-bottom: 6px;
  font: 600 11px/1 var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
}

.tavern-field input,
.tavern-field select,
.tavern-field textarea {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--soft);
  color: var(--ink);
  padding: 11px 13px;
  font: inherit;
}

.tavern-field textarea {
  min-height: 120px;
  resize: vertical;
}

.tavern-field input:focus,
.tavern-field select:focus,
.tavern-field textarea:focus {
  outline: none;
  border-color: var(--a1);
}

body.dark .tavern-field input,
body.dark .tavern-field select,
body.dark .tavern-field textarea {
  background: color-mix(in srgb, var(--bg) 72%, #000 28%);
}

.tavern-field .tavern-field-note {
  margin: 6px 0 0;
  font-size: 12px;
}

.tavern-field-checkbox label.checkbox-row {
  display: inline-flex;
  align-items: flex-start;
  text-transform: none;
  letter-spacing: normal;
  font: 400 14px/1.4 var(--sans);
  color: var(--ink);
}

.tavern-field-checkbox label.checkbox-row input {
  margin-top: 2px;
}

.tavern-field-checkbox small {
  display: block;
  margin-top: 2px;
  font: 400 12px/1.4 var(--sans);
  color: var(--faint);
}

/* Topics head: heading on the left, search on the right, stacking on mobile. */
.tavern-topics-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.tavern-topics-head h3 {
  margin: 0;
}

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

.tavern-search input {
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--soft);
  color: var(--ink);
  padding: 8px 11px;
  font: inherit;
  min-width: 180px;
}

.tavern-search input:focus {
  outline: none;
  border-color: var(--a1);
}

body.dark .tavern-search input {
  background: color-mix(in srgb, var(--bg) 72%, #000 28%);
}

.tavern-search-btn {
  font: 600 11px/1 var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 9px 14px;
  cursor: pointer;
  transition: border-color 140ms ease;
}

.tavern-search-btn:hover {
  border-color: var(--ink);
}

.tavern-search-clear {
  font-size: 12px;
  color: var(--muted);
  text-decoration: none;
}

.tavern-search-clear:hover {
  color: var(--a1);
}

.tavern-search-note {
  margin-top: 12px;
}

.tavern-empty {
  margin-top: 14px;
}

/* Topic list. */
.tavern-list {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  border-top: 1px solid var(--line);
}

.tavern-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 4px;
  border-bottom: 1px solid var(--line);
}

.tavern-row-main {
  display: flex;
  flex-direction: column;
  gap: 3px;
  text-decoration: none;
  color: var(--ink);
  min-width: 0;
}

.tavern-row-title {
  font-family: var(--serif);
  font-size: 19px;
  line-height: 1.2;
}

/* Only the title reacts on hover (colour shift); the global a:hover underline
   would otherwise underline the whole row (title + identity + meta). */
.tavern-row-main:hover {
  text-decoration: none;
}

.tavern-row-main:hover .tavern-row-title {
  color: var(--a1);
}

.tavern-post-author:hover {
  text-decoration: none;
}

/* Flex row so the inline player-identity (race icon + titles + name) sits
   flush on the same baseline-ish line as the "started ..." text, instead of
   riding high on the identity component's own vertical-align. */
.tavern-row-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 8px;
  font-size: 12px;
  color: var(--muted);
}

.tavern-row-started {
  color: var(--faint);
}

.tavern-row-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 3px;
  flex: none;
  text-align: right;
}

.tavern-row-count {
  font: 600 12px/1 var(--sans);
  color: var(--a1);
}

.tavern-row-when {
  font-size: 11px;
  color: var(--faint);
}

/* Back link on the thread page. */
.tavern-back {
  display: inline-block;
  margin-bottom: 12px;
  font-size: 13px;
  color: var(--muted);
  text-decoration: none;
}

.tavern-back:hover {
  color: var(--a1);
}

.tavern-topic-title {
  overflow-wrap: anywhere;
}

.tavern-topic-title-icon {
  width: 0.7em;
  height: 0.7em;
  margin-right: 6px;
  vertical-align: 2px;
  color: var(--a1);
}

/* A single post: opening topic body and each reply share this shell. The
   thick left edge is coloured by the POSTER's race (race-* modifier below),
   not the viewer's theme, so a thread shows who is who at a glance. */
.tavern-post {
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: 4px;
  padding: 14px 16px;
  margin-bottom: 14px;
}

.tavern-post.race-human {
  border-left-color: var(--human1);
}

.tavern-post.race-orc {
  border-left-color: var(--orc1);
}

.tavern-post.race-dwarf {
  border-left-color: var(--dwarf1);
}

.tavern-post.race-elf {
  border-left-color: var(--elf1);
}

.tavern-post.race-undead {
  border-left-color: var(--undead1);
}

.tavern-post-opening {
  background: var(--soft);
}

body.dark .tavern-post-opening {
  background: color-mix(in srgb, var(--bg) 82%, #000 18%);
}

.tavern-replies {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* align-items: center so the inline player-identity (race icon + titles +
   name) lines up with the timestamp and delete control on one clean row. */
.tavern-post-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  margin-bottom: 8px;
  font-size: 12px;
}

/* inline-flex + align-items:center so this link centers on its own flex axis
   instead of relying on inline vertical-align, which put the .player-identity
   icon's -2px nudge at odds with .tavern-post-when's plain text baseline and
   made the timestamp read a couple px lower than the username. */
.tavern-post-author {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: var(--ink);
}

/* Keep the identity (race icon + titles + name) on one line so the timestamp
   sits beside it on the same row instead of dropping below the wrapped
   labels. vertical-align reset is harmless inside the flex head. */
.tavern-post-author .player-identity,
.tavern-row-meta .player-identity {
  flex-wrap: nowrap;
  vertical-align: 0;
}

.tavern-post-when {
  color: var(--faint);
}

/* Moderation / owner controls: Edit, Pin, Lock, Delete. Pushed to the right
   of the post header; each is a small ghost button or link. */
.tavern-post-actions {
  margin-left: auto;
  margin-bottom: 12px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.tavern-post-actions form {
  display: inline;
}

.tavern-action {
  display: inline-block;
  font: 600 10px/1 var(--sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 5px 9px;
  cursor: pointer;
  text-decoration: none;
  transition: color 140ms ease, border-color 140ms ease;
}

.tavern-action:hover {
  color: var(--ink);
  border-color: var(--ink);
}

.tavern-action:disabled {
  color: var(--muted);
  border-color: var(--line);
  opacity: 0.5;
  cursor: default;
}

.tavern-action:disabled:hover {
  color: var(--muted);
  border-color: var(--line);
}

.tavern-action-danger:hover {
  color: #c22b2b;
  border-color: #c22b2b;
}

/* Report button (feature 49): shown on ravens and Tavern posts. Muted until
   hovered, then warns in hazard amber. Matches the .tavern-action sizing so it
   sits cleanly next to the other post actions. */
.report-form {
  display: inline-block;
  margin: 0;
}

.report-btn {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: 600 10px/1 var(--sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 5px 9px;
  cursor: pointer;
  transition: color 140ms ease, border-color 140ms ease;
}

.report-btn:hover {
  color: #c2871f;
  border-color: #c2871f;
}

/* Icon-only variant (Wraithcall chat): no border/label, just the hazard glyph,
   pushed to the far right of its row. Wraithcall chat/feed rows are flex
   containers (see .wraithcall-chat li above), where float has no effect on
   a flex item, so push right with margin-left: auto instead. */
.report-form-icon-only {
  margin-left: auto;
}

.report-btn-icon-only {
  border: none;
  background: transparent;
  padding: 2px;
}

/* Raven view (feature 49 tweak): back link stacked above the title, and the
   Report button pushed to the far right of the reply action row. */
.raven-topbar {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: 8px;
}

.raven-topbar .wiki-back-link {
  align-self: flex-end;
}

.raven-topbar h1 {
  margin-bottom: 0;
}

.raven-reply .report-form {
  margin-left: auto;
}

/* Admin report queue (feature 49). */
.admin-report-count {
  margin-left: 6px;
  font-weight: 600;
  color: #c2871f;
}

/* Bot lords (App\Support\Bots): stands in for the "Via" column's globe/
   Google Play icon on the admin Online Players list, since a bot isn't
   reached via either. Admin-only, never shown to players. Muted rather
   than accent-colored, a quiet aside, not a badge competing for attention. */
.admin-bot-marker {
  color: var(--faint);
}

.report-resolved {
  opacity: 0.55;
}

/* Admin account clusters (anti multi-accounting). */
.admin-cluster {
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 14px;
  margin-bottom: 10px;
}

.admin-cluster-players {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.admin-cluster-players li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
}

.admin-cluster-player-info {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
}

p.sub.is-negative {
  color: #c43c3c;
  margin-bottom: 6px;
}

.admin-cluster-player-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.admin-cluster-player-actions form {
  display: inline;
}

.admin-cluster-parties {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}

.admin-cluster-arrow {
  color: var(--muted);
  font-weight: 600;
}

.admin-cluster-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 8px;
}

.admin-cluster-modal {
  max-width: 100%;
}

.admin-cluster-modal h3 {
  margin-top: 0;
}

.admin-cluster-modal table {
  font-size: 13px;
  width: 100%;
  table-layout: fixed;
}

.admin-cluster-modal table td,
.admin-cluster-modal table th {
  overflow-wrap: break-word;
}

.admin-cluster-modal tr.is-negative {
  background: rgba(196, 60, 60, 0.1);
}

.report-handled-tag {
  margin-left: 6px;
  font-size: 10px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
}

.report-snippet {
  margin-top: 3px;
  color: var(--muted);
  font-size: 12px;
}

/* Pinned / Locked badges, on the overview rows and the topic header. */
.tavern-topic-badges {
  display: flex;
  gap: 6px;
  margin-bottom: 14px;
}

.tavern-topic-badges:empty {
  display: none;
}

.tavern-badge {
  display: inline-block;
  font: 600 9px/1.4 var(--sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  border-radius: 3px;
  padding: 3px 7px;
  vertical-align: 1px;
}

.tavern-badge-pinned {
  color: #8a5a12;
  background: color-mix(in srgb, #b8860b 18%, transparent);
}

.tavern-badge-locked {
  color: #8a4141;
  background: color-mix(in srgb, #c22b2b 14%, transparent);
}

.tavern-badge-poll {
  color: #2a6f8a;
  background: color-mix(in srgb, #2a9fd6 16%, transparent);
}

.tavern-badge-admin-only {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--a1);
  background: color-mix(in srgb, var(--a1) 14%, transparent);
}

.tavern-badge-icon {
  width: 9px;
  height: 9px;
}

/* Guild Board rank-gated topic badge (min_rank): rank crest + "Marshal+". */
.tavern-badge-ranked {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: #5f4d8a;
  background: color-mix(in srgb, #6a5acd 14%, transparent);
}

body.dark .tavern-badge-ranked {
  color: #a99ad4;
}

/* Guild Board category badge/tab label. */
.tavern-badge-category {
  color: var(--muted);
  background: color-mix(in srgb, var(--line) 60%, transparent);
}

/* Board Categories management page: quiet hairline rows instead of boxed
   cards, in keeping with the Board's own list styling but reserved for
   this one page (.board-cat-* namespace, not shared with .tavern-*). */
.board-cat-scene {
  font: italic 500 14px/1.5 var(--serif);
}

/* h3.board-cat-heading (element+class) beats the generic ".page h3" reset
   (also class+element specificity) only because it's declared later; the
   plain ".board-cat-heading" class alone would lose to ".page h3" outright
   since "type+class" outranks "class" regardless of source order. */
h3.board-cat-heading {
  display: flex;
  align-items: center;
  gap: 14px;
  font: 600 10.5px/1 var(--sans);
  text-transform: uppercase;
  letter-spacing: 1.8px;
  color: var(--faint);
  margin: 28px 0 4px;
}

h3.board-cat-heading.board-cat-heading-spaced {
  margin-top: 40px;
}

.board-cat-heading::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--line);
}

.board-cat-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 13px 2px;
  border-bottom: 1px solid var(--line);
}

.board-cat-name {
  font: 600 17px/1.2 var(--serif);
}

.board-cat-row-sub {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
}

.board-cat-meta {
  font-size: 11.5px;
  color: var(--faint);
}

.board-cat-meta b {
  color: var(--muted);
  font-weight: 600;
}

.board-cat-links {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
  flex: none;
}

/* Remove's trigger is a <button> wrapped in its own <form> (for the
   POST/DELETE); Rename's is a bare <button>. Zero the form out so both
   sit at the exact same baseline instead of the form's box nudging
   Remove down. */
.board-cat-links form {
  margin: 0;
  display: inline-flex;
}

/* Quiet uppercase text links, no border/box, matching Rename/Remove's
   low-emphasis role next to a category's name (unlike .tavern-action's
   bordered-button treatment used for topic-level moderation actions).
   Both are plain inline buttons now (Rename opens the shared themed
   choice modal, same as Donate/Invite elsewhere), so they sit flush on
   one line instead of a <details> disclosure pushing Remove down. */
.board-cat-link {
  font: 600 10px/1 var(--sans);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--faint);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

.board-cat-link:hover {
  color: var(--ink);
}

.board-cat-link-danger:hover {
  color: #c22b2b;
}

/* Inside the choice modal (.confirm-box, max-width 360px): stack fields,
   no need for the row's own inline layout. */
.board-cat-edit-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  text-align: left;
}

.board-cat-input {
  background: none;
  border: none;
  border-bottom: 1px solid var(--faint);
  color: var(--ink);
  font: 600 15px var(--serif);
  padding: 3px 1px;
  outline: none;
}

.board-cat-input:focus {
  border-color: var(--ink);
}

.board-cat-input::placeholder {
  color: var(--faint);
  font-weight: 500;
}

.board-cat-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
}

.board-cat-hint {
  font: italic 500 11.5px/1.4 var(--serif);
  color: var(--faint);
}

.board-cat-new-form {
  margin-top: 14px;
}

.board-cat-field {
  margin: 0 0 18px;
}

.board-cat-label {
  display: block;
  font: 600 10px/1 var(--sans);
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 8px;
}

.board-cat-edit-form .board-cat-input,
.board-cat-new-form .board-cat-input {
  width: 100%;
  font-size: 17px;
}

.tavern-badge-awaiting-reply {
  color: #8a5a12;
  background: color-mix(in srgb, #b8860b 18%, transparent);
}

.tavern-badge-answered {
  color: var(--faint);
  background: color-mix(in srgb, var(--line) 60%, transparent);
}

.tavern-badge-status-open {
  color: #2a6f8a;
  background: color-mix(in srgb, #2a9fd6 16%, transparent);
}

.tavern-badge-status-planned {
  color: #8a5a12;
  background: color-mix(in srgb, #b8860b 18%, transparent);
}

.tavern-badge-status-closed {
  color: var(--faint);
  background: color-mix(in srgb, var(--line) 60%, transparent);
}

.tavern-badge-status-completed {
  color: #2f7d4f;
  background: color-mix(in srgb, #2fa35f 16%, transparent);
}

.tavern-row-title .tavern-badge {
  margin-right: 4px;
}

.tavern-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 10px 0 14px;
}

.tavern-filter {
  display: inline-block;
  font: 600 10px/1 var(--sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 5px 9px;
  text-decoration: none;
  transition: color 140ms ease, border-color 140ms ease;
}

.tavern-filter:hover {
  color: var(--text);
  border-color: var(--muted);
}

.tavern-filter-active {
  color: var(--text);
  border-color: var(--muted);
  background: color-mix(in srgb, var(--line) 40%, transparent);
}

.tavern-locked-note {
  margin-top: 16px;
  font-style: italic;
}

.tavern-post-body {
  color: var(--ink);
  font-size: 15px;
  line-height: 1.55;
  overflow-wrap: anywhere;
}

/* Guild Board poll: builder (new topic form) + results (topic view). */
.tavern-poll-builder {
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 12px 14px;
  margin-bottom: 14px;
}

.tavern-poll-option-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.tavern-poll-option-row input {
  flex: 1;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--soft);
  color: var(--ink);
  padding: 9px 11px;
  font: inherit;
}

body.dark .tavern-poll-option-row input {
  background: color-mix(in srgb, var(--bg) 72%, #000 28%);
}

.tavern-poll-options {
  list-style: none;
  margin: 0 0 14px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.tavern-poll-option {
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 10px 12px;
}

.tavern-poll-option.is-voted {
  border-color: var(--a1);
}

.tavern-poll-option .checkbox-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 400 14px/1.4 var(--sans);
  color: var(--ink);
  margin-bottom: 6px;
}

.tavern-poll-bar-track {
  height: 6px;
  border-radius: 3px;
  background: var(--soft);
  overflow: hidden;
}

body.dark .tavern-poll-bar-track {
  background: color-mix(in srgb, var(--bg) 72%, #000 28%);
}

.tavern-poll-bar-fill {
  height: 100%;
  background: var(--a1);
  border-radius: 3px;
  transition: width 200ms ease;
}

.tavern-poll-pct {
  display: block;
  margin-top: 4px;
  font-size: 11px;
  color: var(--faint);
}

/* Pagination, shared by overview and thread. */
.tavern-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 20px;
}

.tavern-pager-step,
.tavern-pager-page {
  font: 600 12px/1 var(--sans);
  color: var(--ink);
  text-decoration: none;
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 8px 11px;
  min-width: 36px;
  text-align: center;
  transition: border-color 140ms ease, color 140ms ease;
}

.tavern-pager-step:hover,
.tavern-pager-page:hover {
  border-color: var(--ink);
}

.tavern-pager-page.is-current {
  border-color: var(--a1);
  color: var(--a1);
}

.tavern-pager-step.is-disabled {
  color: var(--faint);
  border-color: var(--line);
  cursor: default;
}

.tavern-pager-gap {
  color: var(--faint);
  padding: 0 2px;
}

@media (max-width: 620px) {
  .tavern-search {
    width: 100%;
  }

  .tavern-search input {
    flex: 1;
    min-width: 0;
  }

  .tavern-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
  }

  .tavern-row-side {
    flex-direction: row;
    align-items: baseline;
    gap: 12px;
    text-align: left;
  }

  .tavern-post-actions {
    margin-left: 0;
  }
}

/* ==========================================================================
   Guilds (feature 83, Phase 1, admin-only). Reuses .section/.table/.sub/
   .btn-primary/.btn-ghost/.btn-danger/.resource-chip throughout; these
   rules only cover the shapes those shared classes don't already handle.
   ========================================================================== */
/* A section's <h3> with a bit of right-aligned meta text beside it, e.g.
   Members' open/closed + member-count line. flex-wrap: nowrap (the flex
   default, restated explicitly) plus white-space: nowrap on the meta text
   keep it on the heading's own line instead of dropping underneath it. */
.guild-section-head {
  display: flex;
  flex-wrap: nowrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.guild-section-head h3 {
  margin: 0;
}

.guild-section-head .sub {
  margin: 0;
  font-size: 12px;
  white-space: nowrap;
  flex: none;
}

.guild-intro {
  margin-top: 14px;
  margin-bottom: 16px;
}

/* Guild identity header: sigil/banner on its own line above the name,
   name+tag centered beneath it, then a level/member/applications subline,
   like a heraldic title block rather than the usual left-aligned page
   heading. Scoped to this wrapper only, so every other page's plain
   left-aligned .title-help is untouched. */
.guild-identity-head {
  text-align: center;
  padding: 6px 0 4px;
  margin-bottom: 8px;
}

.guild-identity-sigil {
  margin-bottom: 12px;
}

.guild-identity-title.title-help {
  justify-content: center;
  margin-bottom: 6px;
}

.guild-identity-sub {
  color: var(--muted);
  font-size: 12.5px;
  margin: 0;
}

.guild-identity-sep {
  color: var(--faint);
  margin: 0 7px;
}

/* Announcement, read-first: a centered italic serif line under a small
   caps label, the same read-then-quietly-edit pattern as the rest of the
   page (see .guild-form). "Change the word" opens the existing textarea
   via the shared data-choice-trigger dialog instead of showing an
   always-open textarea to every member. */
.guild-proclamation {
  text-align: center;
}

.guild-proclamation-label {
  font: 600 10px/1 var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 8px;
}

.guild-proclamation-text {
  font: 400 15px/1.6 var(--sans);
  color: var(--ink);
  max-width: 480px;
  margin: 0 auto;
}

.guild-proclamation-edit {
  display: inline-block;
  background: none;
  border: none;
  padding: 0;
  margin-top: 10px;
  font: 600 10px/1 var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
  cursor: pointer;
}

.guild-proclamation-edit:hover {
  color: var(--muted);
}

/* Guild nav tabs (The Hall / Treasury / Board / Chat): centered, wider
   gaps, like the mockup's tab row. Scoped to .guild-nav-tabs (shared
   partial rendered on all four guild pages) so every other tabbed page
   (Gear, Camp, Wilds, User settings) keeps its own left-aligned .tab-bar. */
.guild-nav-tabs {
  justify-content: center;
  gap: 26px;
}

/* Condensed guild identity header used above .guild-nav-tabs on Treasury,
   Board and Chat: same centered name/tag + level/members subline as The
   Hall's full header, minus the banner sigil, so those pages start higher. */
.guild-identity-condensed {
  padding-bottom: 4px;
}

/* Guild level/XP: a thin 2px gold bar with a plain label/value row above
   and a quiet right-aligned "View XP log" link below, replacing the
   heavier shared .compact-meters/.bar treatment used for queues/Honor. */
.guild-level-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  font-size: 11px;
  color: var(--muted);
  margin-bottom: 6px;
}

.guild-level-value b {
  color: var(--ink);
  font-weight: 600;
}

.guild-level-track {
  position: relative;
  height: 2px;
  background: var(--line);
}

.guild-level-fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--gold);
}

.guild-level-loglink {
  display: block;
  text-align: right;
  font-size: 11px;
  color: var(--faint);
  margin-top: 6px;
}

.guild-level-loglink:hover {
  color: var(--muted);
}

/* Stewardship buttons: same full-width-between-them fill and mobile
   full-width stacking as .guild-button-row's other users (Treasury's
   Donate/upgrade), just with pill-style typography instead of
   .btn-ghost's plain ink-colored, non-uppercase default look. */
.guild-button-row.guild-steward-row .btn-ghost {
  color: var(--muted);
  font: 600 10px var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  padding: 9px 15px;
}

.guild-button-row.guild-steward-row .btn-ghost:hover {
  color: var(--ink);
  border-color: var(--faint);
}

/* Section headings on the guild page: a small caps label followed by a
   hairline rule filling the rest of the row (mockup's h3::after), instead
   of the plain label the shared .page h3 rule renders elsewhere. */
.guild-page h3 {
  display: flex;
  align-items: center;
  gap: 14px;
}

.guild-page h3::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.guild-heading-cap {
  color: var(--faint);
  letter-spacing: .5px;
  text-transform: none;
  font-weight: 400;
}

/* .leaderboard-row is the hairline row style everywhere now, nothing
   guild-specific to override here any more; the one thing left is the
   promote/demote/kick icons fading in on row hover instead of sitting
   there permanently. Touch devices (no hover) keep them always visible
   so they stay reachable. */

/* Guild Hall member rows are flex-aligned (.leaderboard-row's own
   align-items: center already centers the identity block against the
   rank text and the manage icons), so the identity component's shared
   vertical-align: -2px (tuned for header/leaderboard text contexts, see
   .player-identity and .duel-round .player-identity above) just pushes
   the name up here instead of helping; cancel it. */
.guild-member-list .player-identity {
  vertical-align: -3px;
}

/* The name/rank text sits right at this point, but the 18px race icon
   (a flex item centered inside its own .player-identity-icon box) still
   reads a couple px lower than that text; nudge the icon up to meet it. */
.guild-member-list .player-identity-icon {
  position: relative;
  top: -2px;
}

@media (hover: hover) {
  .guild-member-list .guild-member-actions {
    opacity: 0;
    transition: opacity .15s ease;
  }

  .guild-member-list .leaderboard-row:hover .guild-member-actions,
  .guild-member-list .leaderboard-row:focus-within .guild-member-actions {
    opacity: 1;
  }
}

/* Membership rules + disband, merged into one quiet line at the bottom of
   the tab (mockup's "fineprint"). The leave/disband buttons submit a
   hidden CSRF-only <form> that lives outside this <p> via form="...",
   since a <form> nested inside a <p> isn't valid phrasing content and
   gets auto-closed by the browser, breaking the sentence onto two lines. */
.guild-fineprint {
  font: 400 13px/1.7 var(--sans);
  color: var(--faint);
}

.guild-fineprint .btn-text-danger {
  font: inherit;
  font-weight: 600;
  text-decoration: underline dotted;
}

.guild-flash {
  margin: 0 0 18px;
  padding: 10px 14px;
  border-left: 3px solid var(--a1);
  background: var(--soft);
  color: var(--ink);
  font-size: 14px;
}

body.dark .guild-flash {
  background: color-mix(in srgb, var(--bg) 78%, #000 22%);
}

.guild-form .guild-field {
  margin-bottom: 14px;
}

.guild-field label {
  display: block;
  margin-bottom: 6px;
  font: 600 11px/1 var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
}

.guild-field input,
.guild-field textarea {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--soft);
  color: var(--ink);
  padding: 11px 13px;
  font: inherit;
}

body.dark .guild-field input,
body.dark .guild-field textarea {
  background: color-mix(in srgb, var(--bg) 72%, #000 28%);
}

.guild-list {
  list-style: none;
  margin: 16px 0 0;
  padding: 0;
  border-top: 1px solid var(--line);
}

.guild-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 14px 4px;
  border-bottom: 1px solid var(--line);
}

.guild-invite-actions {
  display: flex;
  gap: 6px;
  flex: none;
}

/* Guild XP log rows: identity + timestamp share one line, the "earned XP
   by ..." sentence sits left-aligned on its own line below, rather than
   competing with them as a third .guild-row flex child (which centered it
   between the other two and let it wrap the row onto multiple lines). */
.guild-xplog-row {
  flex-direction: column;
  align-items: stretch;
  gap: 4px;
}

.guild-xplog-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
}

/* Two classes to out-specificity ".page .sub"'s margin-bottom:24px, which
   otherwise wins on specificity regardless of source order. */
.guild-xplog-row .guild-xplog-text {
  text-align: left;
  margin: 0;
}

.guild-empty {
  margin-top: 14px;
}

.guild-donation-row {
  flex-wrap: wrap;
}

.guild-donation-amounts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* Top-contributor blocks on the donation history page: champion-style rows,
   but the score side is a set of resource chips (one per resource donated).
   The chips always take a full second line, indented past the rank
   medallion (24px + 10px gap) so they align under the name, otherwise up
   to four chips squeeze the username to nothing in the narrow columns. */
.guild-donation-top-columns .leaderboard-top-row {
  flex-wrap: wrap;
}

.guild-donation-top-columns .guild-donation-amounts {
  flex-basis: 100%;
  padding-left: 34px;
}

/* All Members list on the donation history page: a <details> disclosure
   (collapsed by default unless a member filter is active, see the Blade)
   so the full roster doesn't push the transaction log below the fold. */
.guild-all-donors summary {
  cursor: pointer;
  font-weight: 600;
}

.guild-all-donors-list {
  margin-top: 10px;
}

.guild-all-donors-list .leaderboard-row.is-active {
  background: color-mix(in srgb, var(--a1) 8%, transparent);
  border-radius: 8px;
}

.guild-donation-filter {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}

/* The vault: one thin 2px bar per resource in its resource colour, amount
   left in the resource colour, cap right in faint, so "how close are we"
   reads at a glance. */
.guild-vault {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin: 12px 0 20px;
}

.guild-vault-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  font-size: 12px;
  margin-bottom: 5px;
}

.guild-vault-amount {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-weight: 600;
}

.guild-vault-cap {
  color: var(--faint);
}

.guild-vault-track {
  height: 2px;
  background: var(--line);
}

.guild-vault-fill {
  height: 100%;
}

.guild-vault-gold .guild-vault-amount { color: #c8a13a; }
.guild-vault-gold .guild-vault-fill { background: #c8a13a; }
.guild-vault-wood .guild-vault-amount { color: #7a4b2b; }
.guild-vault-wood .guild-vault-fill { background: #7a4b2b; }
.guild-vault-stone .guild-vault-amount { color: #8a919a; }
.guild-vault-stone .guild-vault-fill { background: #8a919a; }
.guild-vault-food .guild-vault-amount { color: #c9762f; }
.guild-vault-food .guild-vault-fill { background: #c9762f; }

.guild-vault-acts {
  display: flex;
  align-items: baseline;
  gap: 16px;
}

.guild-vault-histlink {
  font-size: 12px;
  color: var(--muted);
}

.guild-vault-histlink:hover {
  color: var(--ink);
}

/* Donate modal fields: a resource-chip (icon + coloured name) plus the
   on-hand/stockpile-room hint, replacing .choice-qty-field's plain text
   label. .choice-qty-field span is a broad descendant selector (shared with
   Gear's Buy/Sell modal fields) that would otherwise blockify and
   de-colour the chip's own nested <span>; these two rules win on
   specificity to restore the chip's normal look inside it. */
.guild-donate-label {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin-bottom: 4px;
}

.choice-qty-field .resource-chip {
  display: inline-flex;
}

.choice-qty-field .resource-chip span {
  display: inline;
  text-transform: none;
  letter-spacing: normal;
  color: inherit;
  margin-bottom: 0;
  font: inherit;
  font-weight: 600;
}

.guild-donate-max {
  font-size: 11px;
  color: var(--faint);
}

.guild-rank-badge {
  font: 600 10.5px/1 var(--sans);
  letter-spacing: 1px;
  text-transform: uppercase;
  padding: 2px 8px;
  border-radius: 10px;
  background: var(--soft);
  color: var(--muted);
}

.guild-rank-badge.rank-lord_commander {
  background: color-mix(in srgb, #c8a13a 20%, transparent);
  color: #a17a1f;
}

.guild-rank-badge.rank-high_marshal {
  background: color-mix(in srgb, #c8a13a 12%, transparent);
  color: color-mix(in srgb, #a17a1f 75%, var(--a1));
}

.guild-rank-badge.rank-marshal {
  background: color-mix(in srgb, var(--a1) 16%, transparent);
  color: var(--a1);
}

.guild-rank-badge.rank-veteran {
  background: color-mix(in srgb, var(--a2) 16%, transparent);
  color: var(--a2);
}

.guild-rank-badge.rank-initiate {
  opacity: 0.75;
}

/* Guild Hall member rows (mockup): plain rank text after the identity,
   not the pill-shaped .guild-rank-badge the public guild profile and the
   guild XP log still use. */
.guild-member-rank {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 600 10px/1 var(--sans);
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--faint);
}

.guild-rank-icon {
  flex: 0 0 auto;
  vertical-align: -2px;
}

.guild-member-rank.rank-lord_commander {
  color: var(--gold);
}

.guild-member-rank.rank-high_marshal {
  color: color-mix(in srgb, var(--gold) 70%, var(--a1));
}

.guild-member-rank.rank-marshal {
  color: var(--a2);
}

.guild-member-rank.rank-veteran {
  color: var(--a1);
}

.guild-member-rank.rank-initiate {
  opacity: 0.75;
}

.guild-member-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  /* Forces the action cluster onto its own line below the rank/identity/
     presence line (a plain flex-wrap row would otherwise tuck it right
     after the presence dot, undoing that dot's own right-aligned margin). */
  flex-basis: 100%;
  margin-top: 8px;
}

/* Members list, styled like the Leaderboard's own row (.leaderboard-row/
   .leaderboard-list), but the rank number slot holds the guild rank badge
   instead. The Standing word and the action icons (kick/promote/etc, Lord
   Commander and Marshal+ views only) are wrapped together in
   .guild-member-right so a single margin-left:auto on the wrapper pushes
   both flush right, on the same line as the rank badge and player
   identity: two separate auto margins would each claim their own share of
   the free space and land the actions in the middle of the row instead. */
.guild-member-right {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-left: auto;
}

.guild-member-right .leaderboard-standing {
  margin-left: 0;
  padding-left: 0;
}

.guild-member-row .guild-member-actions {
  flex-basis: auto;
  flex-wrap: nowrap;
  margin-top: 0;
}

.guild-member-row .guild-rank-badge {
  flex: none;
  width: 96px;
  text-align: center;
}

/* Pending applications have no rank-badge column to steal room from, so
   .player-identity's own flex-wrap:wrap (needed elsewhere) never kicks in
   for the member row, but can here once the rank title is shown alongside
   a longer name; force everything onto one line instead. */
.guild-pending-list .player-identity {
  flex-wrap: nowrap;
}

/* Upgrades as single-line rows (feature 83 follow-up): name on its own
   line, description on the next, resources + Buy on a third, rather than a
   card grid. */
.guild-upgrade-list {
  display: flex;
  flex-direction: column;
  margin-top: 12px;
}

.guild-upgrade-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}

.guild-upgrade-row:first-child {
  padding-top: 0;
}

.guild-upgrade-row:last-child {
  border-bottom: none;
}

.guild-upgrade-main {
  min-width: 0;
}

.guild-upgrade-main b {
  display: block;
}

.guild-upgrade-main .sub {
  /* .page .sub's 24px margin-bottom is meant for the space below a page
     intro paragraph, not between this description and the cost row right
     under it. */
  margin: 2px 0 0;
}

.guild-upgrade-cost {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 6px;
}

/* The parts of an upgrade's cost the Treasury can't yet cover: dimmed so
   the affordable parts (full opacity) and the bottleneck read apart at a
   glance, same idea as .forge-submit's affordability styling. */
.resource-chip.is-short {
  opacity: 0.4;
}

.guild-upgrade-hint {
  font: italic 500 11px var(--sans);
  color: var(--faint);
  margin-top: 6px;
}

.guild-upgrade-act {
  flex: none;
}

.guild-upgrade-maxed {
  flex: none;
}

/* The permission sentence above the Upgrades list, once, instead of
   repeated per row. */
.guild-steward-line {
  font: italic 500 13px/1.5 var(--serif);
  color: var(--faint);
  margin: -6px 0 18px;
}

/* Unlocked one-time upgrades (Board/Chat/Banner/Tag) collapse to a single
   quiet line of names under "Already held", instead of spending most of
   the Upgrades list saying "Unlocked." four times. */
.guild-held-line {
  font-size: 13px;
  color: var(--muted);
}

.guild-held-line b {
  font: 600 15px var(--serif);
  color: var(--muted);
}

.guild-held-sep {
  color: var(--faint);
  margin: 0 8px;
}

@media (max-width: 620px) {
  .guild-upgrade-row {
    flex-direction: column;
    align-items: stretch;
  }

  .guild-upgrade-act {
    width: 100%;
  }

  .guild-upgrade-act form,
  .guild-upgrade-act button {
    width: 100%;
  }
}

/* Toggle-open + Invite-by-name: 50/50 side by side on desktop, each a full
   row on mobile. A lone button (e.g. a Marshal, who can't toggle open/closed)
   still fills the row since flex: 1 1 0 on a single child spans it whole. */
.guild-button-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 12px;
  margin-bottom: 12px;
}

.guild-button-row form,
.guild-button-row > button,
.guild-button-row > a {
  flex: 1 1 0;
  min-width: 0;
}

.guild-button-row form button,
.guild-button-row > a {
  width: 100%;
}

@media (max-width: 620px) {
  .guild-button-row {
    flex-direction: column;
  }
}

/* Disband: plain red text, not a button, since it's the single most
   destructive action on the page and shouldn't read as just another
   equally-weighted button choice. Still a real <button> for the
   data-confirm submit to intercept. */
.guild-disband-form {
  margin-top: 14px;
}

.btn-text-danger {
  background: none;
  border: none;
  padding: 0;
  font: 600 13px/1 var(--sans);
  color: #b43a3a;
  cursor: pointer;
  text-decoration: underline;
}

.btn-text-danger:hover {
  color: color-mix(in srgb, #b43a3a 80%, black 20%);
}

/* Guild Banner: a base shield (fill = --banner-primary) with a smaller
   emblem overlay (fill = --banner-secondary) centered on top. Both SVGs are
   embedded raw (no inline fill attributes of their own), so these two CSS
   custom properties recolor them per-guild without needing a colored copy
   of each asset on disk. */
.guild-banner {
  position: relative;
  display: inline-block;
  flex: none;
}

.guild-banner-base,
.guild-banner-emblem {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The emblem's size and vertical position are player-controlled (two
   sliders in the customizer, --banner-emblem-scale/--banner-emblem-offset),
   not fixed, since any one emblem's own proportions may sit differently on
   any one base; translateY on the wrapper (not the svg) so it moves as a
   percentage of the shield's own height regardless of scale. */
.guild-banner-emblem {
  transform: translateY(var(--banner-emblem-offset, 0%));
}

/* The base renders larger than its own box (overflowing slightly, .guild-
   banner itself doesn't clip) while the emblem stays at its own share of
   the box, so the emblem reads as proportionally smaller sitting on top of
   a fuller shield, instead of both scaling together. */
.guild-banner-base svg {
  width: 130%;
  height: 130%;
  fill: var(--banner-primary);
}

/* Placeholder flag shown in the Guild section when a lord isn't in a
   guild: a small, muted outline instead of a full-size colored/emblemed
   banner, so "no guild" doesn't visually compete with an actual one. */
.guild-banner-empty .guild-banner-base {
  align-items: center;
  justify-content: center;
}

.guild-banner-empty .guild-banner-base svg {
  width: 55%;
  height: 55%;
  fill: var(--faint);
}

.guild-banner-emblem svg {
  width: var(--banner-emblem-scale, 42%);
  height: var(--banner-emblem-scale, 42%);
  fill: var(--banner-secondary);
}

.guild-banner-xs {
  width: 18px;
  height: 18px;
  vertical-align: middle;
}

.guild-banner-sm {
  width: 44px;
  height: 44px;
}

.guild-banner-md {
  width: 72px;
  height: 72px;
}

.guild-banner-lg {
  width: 140px;
  height: 140px;
}

/* The monthly Guild XP champion's crown (feature: Champion categories
   expansion), perched on top of the banner it decorates. Wraps whichever
   banner markup (real or the empty placeholder) so a champion guild without
   a designed banner still shows the crown. Golden/Silver/Bronze reuse the
   same q-excellent/q-silver/q-bronze colors as every other rank badge. */
.guild-banner-crowned {
  position: relative;
  display: inline-flex;
  flex: none;
}

.guild-crown {
  position: absolute;
  top: 2px;
  left: 50%;
  transform: translateX(-50%);
  width: 13px;
  height: 13px;
  line-height: 0;
}

.guild-crown svg {
  display: block;
  width: 100%;
  height: 100%;
  fill: currentColor;
}

/* Banner/Tag customizer: a grid of clickable thumbnail radios (base shield
   or emblem, shown in a neutral color; the actual color choice is a
   separate swatch step below). The radio itself is visually hidden, its
   label is the clickable thumbnail, :checked styles the label via the
   sibling selector, same "hidden input + styled label" trick .tab-input
   uses elsewhere. */
.guild-banner-picker {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(44px, 1fr));
  gap: 5px;
  max-height: 220px;
  overflow-y: auto;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: 4px;
  /* Firefox */
  scrollbar-width: thin;
  scrollbar-color: var(--muted) transparent;
}

.guild-banner-picker::-webkit-scrollbar {
  width: 8px;
}

.guild-banner-picker::-webkit-scrollbar-track {
  background: transparent;
}

.guild-banner-picker::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 4px;
}

.guild-banner-picker::-webkit-scrollbar-thumb:hover {
  background: var(--muted);
}

.guild-banner-option {
  display: flex;
  cursor: pointer;
}

.guild-banner-option input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.guild-banner-option-swatch {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--muted);
  transition: border-color 120ms ease, color 120ms ease, background-color 120ms ease;
}

.guild-banner-option-swatch svg {
  width: 70%;
  height: 70%;
  fill: currentColor;
}

.guild-banner-option input:checked + .guild-banner-option-swatch {
  color: var(--a1x, var(--a1));
  border-color: var(--a1x, var(--a1));
  background: color-mix(in srgb, var(--a1x, var(--a1)) 10%, transparent);
}

.guild-banner-preview-wrap {
  display: flex;
  justify-content: center;
  padding: 8px 0 16px;
}

/* The public profile's banner is a plain view-only trigger for the "see it
   bigger" modal (data-choice-trigger, no submit button in the template),
   so it must not look like a normal button. */
.guild-banner-trigger {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  line-height: 0;
}

/* The "see it bigger" modal on the public profile: banner centered, guild
   name below it as a real <h1> in the same serif style as the page's own
   title (.page h1), which this markup sits outside of (the dialog is
   appended to <body>, not .page), so it needs its own copy of that rule. */
.guild-banner-modal {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
}

.guild-banner-modal h1 {
  font: 600 32px/1.05 var(--serif);
  margin: 0;
}

.guild-form input[type="range"] {
  width: 100%;
  accent-color: var(--a1);
}

.guild-banner-color-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.guild-banner-color-option {
  cursor: pointer;
}

.guild-banner-color-option input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.guild-banner-color-swatch {
  display: block;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid var(--line);
}

.guild-banner-color-option input:checked + .guild-banner-color-swatch {
  border-color: var(--a1x, var(--a1));
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--a1x, var(--a1)) 30%, transparent);
}

.guild-tag-badge {
  display: inline-block;
  vertical-align: middle;
  padding: 1px 5px;
  border-radius: 3px;
  font: 700 10px/1.4 var(--sans);
  letter-spacing: 0.5px;
  /* Dark in light mode, light in dark mode (--ink flips with the theme),
     not the page's race accent color: a tag is a guild identity marker,
     not tied to whichever race is viewing it. */
  color: var(--ink);
  border: 1px solid var(--line);
}

/* ==========================================================================
   Presence (feature 44): a small coloured dot (+ optional label) for
   Online / Away / Offline, used on the dashboard bonds, the leaderboard rows
   and public profiles.
   ========================================================================== */
.presence {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
}

/* The dot (+ optional label) moved one level deeper into the shared
   .upgrade-tooltip/.chip-hint hover wrapper (feature 44 follow-up: a
   "since" tooltip), so the flex layout that used to live on .presence
   itself now lives on this inner trigger span. */
.presence-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: help;
}

.presence-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--faint);
  flex: none;
}

.presence-online .presence-dot {
  background: #3f9b46;
}

.presence-away .presence-dot {
  background: #c8912e;
}

.presence-offline .presence-dot {
  background: #b3b3b8;
}

.presence-online .presence-label {
  color: #3f9b46;
}

/* Race/rank/level text, the Ascendant laurel and the online indicator now
   share one line (feature 89), so this only needs a tight bottom margin
   before the guild-info line beneath it, not the old negative-margin hack
   that pulled a separate presence paragraph up under a sibling .sub.
   Selector needs to match ".page .sub"'s specificity (two classes) to win. */
.page .sub.profile-presence {
  margin-top: 8px;
  margin-bottom: 2px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* Admin Online Players list (feature 44): on phones there is no room for
   5 columns, so each row becomes a compact 2-line card: name, status and
   via on one line, last page and last seen on the line below, with a
   divider between players. */
@media (max-width: 620px) {
  [data-presence-table] thead {
    display: none;
  }

  [data-presence-table],
  [data-presence-table] tbody {
    display: block;
    width: 100%;
  }

  [data-presence-table] tr {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 12px;
    padding: 8px 0;
    border-bottom: 1px solid var(--line);
  }

  [data-presence-table] td {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 0;
    border-bottom: 0;
    text-align: left;
  }

  /* Forcing this cell to the full row width wraps it (and everything
     after it) onto a second line, while name/status/via stay together
     on the first. */
  [data-presence-table] td[data-label="Last page"] {
    flex-basis: 100%;
    font-size: 13px;
    color: var(--muted);
  }

  [data-presence-table] td[data-label="Last page"]::before,
  [data-presence-table] td[data-label="Last seen"]::before {
    content: attr(data-label) ": ";
    flex: none;
    font-size: 10.5px;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--faint);
  }
}

/* ==========================================================================
   Friends & Enemies (feature 43): the profile "Your Standing" controls and
   the two-column dashboard lists.
   ========================================================================== */
.relationship-friend {
  color: #2e8b3d;
}

.relationship-enemy {
  color: #c22b2b;
}

.bonds-columns {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.bonds-heading {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0 0 6px;
  font: 600 11px/1 var(--sans);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--faint);
}

.bonds-count {
  font-size: 11px;
  letter-spacing: 0;
  color: var(--muted);
}

.bonds-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}

.bonds-row:last-child {
  border-bottom: none;
}

.bonds-identity {
  flex: 1;
  min-width: 0;
  text-decoration: none;
  color: var(--ink);
}

.bonds-identity .player-identity {
  flex-wrap: nowrap;
  vertical-align: 0;
}

.bonds-identity .player-identity b {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bonds-identity:hover {
  text-decoration: none;
}

.bonds-remove {
  flex: none;
}

.bonds-empty {
  margin: 4px 0 0;
  font-size: 12px;
  font-style: italic;
}

/* ==========================================================================
   Battle-stats period tabs (feature 45): This week / month / year / all time.
   ========================================================================== */
.battle-stats-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 14px;
}

.battle-stats-tab {
  font: 600 11px/1 var(--sans);
  letter-spacing: 0.5px;
  color: var(--muted);
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 6px 12px;
  cursor: pointer;
  transition: border-color 140ms ease, color 140ms ease;
}

.battle-stats-tab:hover {
  color: var(--ink);
  border-color: var(--ink);
}

.battle-stats-tab.is-active {
  color: var(--a1);
  border-color: var(--a1);
}

/* The battle-stats figures fill the page width instead of packing to the
   left: each wrapped line (headline record on one, per-type breakdown on the
   next, split by .fig-break) spreads edge to edge so the counters read as
   evenly-spaced columns on desktop and mobile alike. */
.battle-stats-block .grid-figures {
  justify-content: space-between;
  /* A small minimum column gap (not the site-wide 40px) so the 4- and
     5-figure rows stay on one line even on a narrow phone; space-between
     then opens the real spacing out to the full width. */
  gap: 14px 10px;
}

/* ==========================================================================
   Admin nav headcount (feature 44): total lords, online, away, under the
   desktop Admin link.
   ========================================================================== */
/* `.realm-nav a { display: block }` outranks a bare `.realm-nav-admin-stats`
   on specificity, so the flex column below needs the `.realm-nav` ancestor
   in its own selector to actually win. */
.realm-nav .realm-nav-admin-stats {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: baseline;
  gap: 5px;
  margin-top: 6px;
  padding-left: 14px;
  font-size: 11px;
  color: var(--faint);
  text-decoration: none;
}

.realm-nav-admin-stat-count {
  font-weight: 600;
}

.realm-nav-admin-stat-count.hot {
  color: var(--a1);
}

/* Planned/Completed ticket counts stack directly under Support with no
   gap between them; Lords below keeps its own margin-top separation. */
.realm-nav .realm-nav-admin-stats.realm-nav-admin-stats-tight {
  margin-top: 0;
  padding-top: 0;
}

.realm-nav .realm-nav-admin-stats-tight,
.realm-nav .realm-nav-admin-stats:has(+ .realm-nav-admin-stats-tight) {
  padding-bottom: 0;
}

.realm-nav-admin-stats:hover {
  color: var(--ink);
}

/* Registered-lords total: set apart from the Support/Planned/Completed
   count rows above it as a quiet footer figure, echoing the small-caps
   "N online" line rather than reading as another actionable stat row. */
.realm-nav .realm-nav-lords {
  display: block;
  margin-top: 12px;
  padding-left: 14px;
  font-size: 11px;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--faint);
  text-decoration: none;
}

.realm-nav .realm-nav-lords:hover {
  color: var(--ink);
}

.realm-nav .realm-nav-discord {
  display: flex;
  align-items: center;
  gap: 6px;
}

.realm-nav-discord-icon {
  flex: none;
  color: #5865f2;
}

/* Today-activity figures on the public Online Now page (realm/online.blade.php),
   shown as small stacked stat tiles: value on top, label underneath. */
.realm-today-counts {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 28px;
}

.realm-today-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 64px;
}

.realm-today-value {
  font-size: 20px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1;
}

.realm-today-label {
  font-size: 12px;
  color: var(--muted);
}

/* Membership and the Favor Hall (feature 46). Favor reads in the same gold
   register as the Iron Vault's balance; Membership is comfort/standing, so
   nothing here shouts. */
/* The Favor Hall centrepiece: a Member's balance framed as one quiet gold
   panel, stacked and centred ("counted seal by seal"), subtle gold wash rather
   than a loud block, in keeping with the white-first, accent-only palette. */
.favor-balance {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 4px;
  padding: 30px 20px 26px;
  border: 1px solid color-mix(in srgb, var(--gold) 38%, var(--line));
  border-radius: 14px;
  background:
    radial-gradient(120% 120% at 50% 0%, color-mix(in srgb, var(--gold) 12%, transparent), transparent 70%),
    var(--bg);
}

.favor-balance-icon {
  color: var(--gold);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  margin-bottom: 6px;
}

/* One-off thank-you after an in-app Membership purchase (/membership?welcome=1),
   same quiet gold framing as the Favor Hall balance. */
.membership-thankyou {
  text-align: center;
  padding: 26px 20px;
  border: 1px solid color-mix(in srgb, var(--gold) 38%, var(--line));
  border-radius: 14px;
  background:
    radial-gradient(120% 120% at 50% 0%, color-mix(in srgb, var(--gold) 12%, transparent), transparent 70%),
    var(--bg);
}

.membership-thankyou-icon {
  color: var(--gold);
  display: inline-flex;
  margin-bottom: 6px;
}

.membership-thankyou h2 {
  margin: 0 0 6px;
}

/* Centre the "Visit the Favor Hall" button under the centred thank-you text
   (.action-row is flex and left-aligns by default). */
.membership-thankyou .action-row {
  justify-content: center;
}

/* ---------- Welcome wizard (feature 54) ---------- */
.wizard-wrap {
  max-width: 520px;
  margin: 24px auto;
}

.wizard {
  border: 1px solid var(--line);
  border-radius: 16px;
  background: var(--bg);
  padding: 28px 24px 20px;
  text-align: center;
}

.wizard-dots {
  display: flex;
  justify-content: center;
  gap: 8px;
  margin-bottom: 20px;
}

.wizard-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--line);
  transition: background 0.2s ease, transform 0.2s ease;
}

.wizard-dot.is-current {
  background: var(--a1);
  transform: scale(1.25);
}

/* Only the current step shows; the rest stay in the DOM for instant nav. */
.wizard-step {
  display: none;
}

.wizard-step.is-current {
  display: block;
}

.wizard-icon {
  display: inline-flex;
  color: var(--a1);
  margin-bottom: 10px;
}

.wizard-step h2 {
  margin: 0 0 10px;
  font-family: var(--serif);
}

.wizard-body {
  color: var(--muted);
  line-height: 1.6;
  max-width: 42ch;
  margin: 0 auto;
}

.wizard-resources {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin: 14px auto 0;
  max-width: 42ch;
}

.resource-chip-inline {
  vertical-align: middle;
  margin: 0 1px;
}

.wizard-tip {
  margin: 14px auto 0;
  max-width: 42ch;
  padding: 10px 12px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--a1) 10%, transparent);
  color: var(--ink);
  font-size: 13px;
  line-height: 1.5;
}

.wizard-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  margin-top: 24px;
  border-top: 1px solid var(--line);
  padding-top: 16px;
}

.wizard-nav {
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.favor-balance-figure {
  font: 600 clamp(48px, 12vw, 64px)/1 var(--serif);
  color: var(--gold);
  letter-spacing: 0.01em;
}

.favor-balance-label {
  font-size: 12px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

.favor-balance-note {
  margin-top: 12px;
  max-width: 34ch;
  font-size: 13px;
  color: var(--muted);
  line-height: 1.5;
}

/* Shared "what Membership grants" list (subscribe page, Favor Hall, status
   page). One benefit per row: bold headline, muted line under it. */
.membership-grants {
  list-style: none;
  padding: 0;
  margin: 10px 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.membership-grants li {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-left: 14px;
  border-left: 3px solid var(--gold);
}

.membership-grants li .sub {
  margin: 0;
}

.membership-fair-note {
  margin-top: 8px;
}

.membership-price {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 12px;
}

.membership-price-figure {
  font: 600 34px/1 var(--serif);
  color: var(--gold);
}

.membership-price-period {
  font-size: 13px;
  color: var(--muted);
}

.membership-price-vat {
  font-size: 11px;
  opacity: 0.75;
}

.membership-checkout-note {
  margin-top: 14px;
  font-size: 12px;
}

.member-word {
  color: var(--gold);
}

/* Feature 161: the gift form's message box. The generic .field input/select
   rule doesn't cover textarea (see the .policy-page .field textarea comment
   above), so without this it falls back to the browser's unstyled white
   box, unreadable in dark mode; same boxed styling + dark fill as that one. */
#membership-gift-form-el textarea {
  width: 100%;
  min-height: 80px;
  resize: vertical;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: var(--soft);
  color: var(--ink);
  padding: 11px 13px;
  font: inherit;
}

#membership-gift-form-el textarea:focus {
  outline: none;
  border-color: var(--a1);
}

body.dark #membership-gift-form-el textarea {
  background: color-mix(in srgb, var(--bg) 72%, #000 28%);
}

/* Favor Hall: what Favor buys, as a row of cards (feature 55). auto-fill keeps
   the row sized for three, so a single-use realm still reads as a menu with
   room to grow rather than one lonely stretched card. */
.favor-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 12px;
  margin-top: 12px;
}

.favor-card {
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 14px;
  background: color-mix(in srgb, var(--soft) 82%, transparent);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.favor-card-icon {
  display: inline-flex;
  color: var(--gold);
}

.favor-card h4 {
  margin: 0;
  font-family: var(--serif);
}

.favor-card-desc {
  margin: 0;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.5;
  flex: 1;
}

.favor-card-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.favor-card-action {
  margin-top: 2px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.favor-card-action form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.favor-card-note {
  margin: 0;
  color: var(--muted);
  font-size: 12px;
}

/* The "Choose" action button: label plus the Favor cost chip, centred, so a
   card's cost reads off the button itself rather than a separate line. */
.favor-choose {
  justify-content: center;
}

/* Race options in the change-blood modal wear their faction colours (set by
   the .race-{name} classes as --tone-a/--tone-b): the race icon takes the
   primary tone (matching how the race reads everywhere else on the site, e.g.
   Human red), the row border the secondary, and a picked row deepens into its
   own colour rather than the generic amber. */
.combine-option.favor-race {
  border-color: color-mix(in srgb, var(--tone-b) 45%, var(--line));
}

.combine-option.favor-race svg {
  color: var(--tone-a);
}

.combine-option.favor-race:hover {
  border-color: var(--tone-b);
}

.combine-option.favor-race:has(input:checked) {
  border-color: var(--tone-b);
  background: color-mix(in srgb, var(--tone-a) 14%, transparent);
}

/* Member seal (feature 46): a small gold line icon + "Member", shown on the
   public profile's online-status line, right before the presence indicator.
   Inline, gold, never a filled block, so it reads as standing beside the
   status rather than competing with it. */
.member-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--gold);
}

.member-badge svg {
  flex: none;
}

/* Ascendant laurel (feature 55): a light-green victory wreath worn by every
   lord of the race that led the realm's battles last month. Sits beside the
   Member seal on a profile, and after the race name on the leaderboard. */
.race-laurel {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #6fbf5b;
}

.race-laurel svg {
  flex: none;
}

/* Guild badge (feature 83): name + rank on the public profile status line,
   admin-viewer only while Guilds itself stays admin-only. Same shape as
   .member-badge/.race-laurel beside it. */
.guild-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--a1);
}

.guild-badge svg {
  flex: none;
}

/* On the public profile, the guild-info line reads as a plain info line
   next to "Elf Streetrat. Level 7." above it (feature 89), not a small
   pill badge, so it matches that line's text size instead of the 12px
   badge size used elsewhere. Name and rank stack on their own lines
   beside the banner, instead of running together on one line. */
.profile-guild-line.guild-badge {
  align-items: center;
  font-size: inherit;
  font-weight: inherit;
  letter-spacing: inherit;
}

.guild-badge-lines {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-left: 6px;
}

/* City: Betting Dens hub. A typographic den list (serif names, italic
   serif descriptions) instead of the site's usual boxed .world-link
   tiles: the Dens are meant to read with a bit more swagger than the rest
   of the game, per the concept brief. Built entirely on the site's own
   theme vars (--bg/--ink/--line/--muted/--gold/--serif), so it tracks the
   existing light/dark toggle automatically, no bespoke dark palette. */
.den-scene {
  font: italic 500 15px/1.6 var(--serif);
  max-width: 520px;
}

.den-list {
  display: flex;
  flex-direction: column;
}

/* .den is a plain container, not a link: it holds a nested real link
   (.identity-link, in .den-last) and nested anchors are invalid HTML, the
   browser silently closes the outer one right before the inner one opens,
   which broke both the layout (the last-outcome line rendered detached
   from its row) and hover (the whole multi-line block got underlined by
   the default a:hover rule). Instead the den name is the one real link,
   stretched over the full row via ::after (the standard "stretched link"
   trick) so the whole row still opens the den on click/tap; .identity-link
   sits above that overlay (position + z-index) so it stays independently
   clickable. */
.den {
  position: relative;
  display: block;
  padding: 22px 2px;
  border-bottom: 1px solid var(--line);
  transition: padding-left 150ms ease;
}

.den:last-child {
  border-bottom: 0;
}

.den:hover {
  padding-left: 8px;
}

.den-head {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
}

.den-name {
  font: 600 22px/1.15 var(--serif);
  color: var(--ink);
}

.den-name:hover {
  text-decoration: none;
}

.den-name::after {
  content: "";
  position: absolute;
  inset: 0;
}

.den:hover .den-name {
  color: var(--a1);
}

.den-last .identity-link {
  position: relative;
  z-index: 1;
}

.den-note {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  white-space: nowrap;
  color: var(--gold);
}

.den-note.is-live {
  color: #3a8a4c;
}

.den-desc {
  font: italic 500 13.5px/1.6 var(--serif);
  color: var(--muted);
  margin-top: 4px;
  max-width: 460px;
}

.den-last {
  font-size: 12px;
  line-height: 1.7;
  color: var(--faint);
  margin-top: 10px;
}

/* <x-player-identity>'s default vertical-align (-2px) is calibrated for
   the site's normal 13.5px body text; at .den-last's smaller 12px it reads
   as sitting too high. Also shrink its 18px icon a touch so it doesn't
   tower over this smaller flavor line. */
.den-last .player-identity {
  vertical-align: -3px;
}

.den-last .player-identity-icon {
  width: 15px;
  height: 15px;
}

/* Coin Flip: a two-faced coin that spins on rotateY (see coin-flip.js),
   landing on whichever face the server actually decided. */
.coin-stage {
  display: flex;
  justify-content: center;
  padding: 20px 0;
  perspective: 600px;
}

.coin {
  position: relative;
  width: 72px;
  height: 72px;
  transform-style: preserve-3d;
}

.coin-face {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 22px;
  backface-visibility: hidden;
  border: 2px solid color-mix(in srgb, var(--gold) 60%, transparent);
  background: color-mix(in srgb, var(--gold) 16%, var(--bg));
  color: var(--gold);
}

.coin-face.coin-tails {
  transform: rotateY(180deg);
}

.coin-result {
  text-align: center;
  font-weight: 600;
}

/* Blackjack: dealer/player hands as two rows of small card tiles. Kept to
   the site's subtle-accent rule (no felt-green table block): plain bordered
   tiles on the page background, red only on the suit glyph itself. Cards
   wrap onto a second line on narrow screens instead of overflowing. */
.bj-table {
  display: flex;
  flex-direction: column;
  gap: 20px;
  margin-top: 8px;
}

.bj-hand-label {
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-bottom: 10px;
}

.bj-hand-value {
  color: var(--ink);
  font-weight: 700;
  font-size: 16px;
}

.bj-cards {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.bj-card {
  width: 70px;
  height: 98px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  font-weight: 700;
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.bj-card-rank {
  font-size: 24px;
  line-height: 1;
}

.bj-card-suit {
  font-size: 21px;
  line-height: 1;
}

.bj-card-red {
  color: #b43a3a;
}

.bj-card-back {
  width: 70px;
  height: 98px;
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, var(--gold) 60%, transparent);
  background: repeating-linear-gradient(
    135deg,
    color-mix(in srgb, var(--gold) 16%, var(--bg)),
    color-mix(in srgb, var(--gold) 16%, var(--bg)) 6px,
    color-mix(in srgb, var(--gold) 28%, var(--bg)) 6px,
    color-mix(in srgb, var(--gold) 28%, var(--bg)) 12px
  );
}

.bj-actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
}

.bj-actions form {
  margin: 0;
}

.bj-result {
  margin-top: 16px;
  font-weight: 600;
}

.bj-result-win,
.bj-result-blackjack {
  color: #2f8a4e;
}

.bj-result-lose {
  color: #b43a3a;
}

/* Blackjack table history, styled after .training-log-* on /train: muted,
   compact rows, newest hand first. */
.bj-history-section h3 {
  margin-bottom: 8px;
}

.bj-history-list {
  display: flex;
  flex-direction: column;
}

.bj-history-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 3px 0;
  font-size: 12.5px;
  border-bottom: 1px solid var(--line);
}

.bj-history-row:last-child {
  border-bottom: none;
}

.bj-history-outcome {
  min-width: 120px;
}

.bj-history-win,
.bj-history-blackjack {
  color: #2f8a4e;
}

.bj-history-lose {
  color: #b43a3a;
}

.bj-history-wager {
  color: var(--muted);
}

.bj-history-time {
  margin-left: auto;
  color: var(--muted);
}

/* Betting: shared spacing for a wager form so its option picker, submit
   button and the "you have X gold" line beneath it never crowd together. */
.wager-form {
  margin-top: 18px;
}

.wager-form .btn-primary {
  margin-top: 22px;
}

.wager-gold-note {
  margin-top: 14px;
}

/* Coin Flip's heads/tails picker: two clearly separate cards, same
   hidden-radio + :has(input:checked) technique as .combine-option, sized
   and bordered like .horse-choice-option so both pickers read as one
   family instead of a flat, low-contrast segmented control. */
.pill-toggle {
  display: flex;
  gap: 10px;
  margin-top: 6px;
}

.pill-toggle-option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 96px;
  padding: 11px 22px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
  transition: border-color 140ms ease, background 140ms ease, color 140ms ease;
}

.pill-toggle-option input {
  display: none;
}

.pill-toggle-option:hover {
  border-color: var(--a1);
}

/* Same subtle border + tinted-background treatment as the Forge modal's
   .pill-radio (see /camp), not a solid fill: a checked pill should read as
   highlighted, not as a different-colored button. */
.pill-toggle-option:has(input:checked) {
  border-color: var(--a1);
  color: var(--ink);
  background: color-mix(in srgb, var(--a1) 12%, transparent);
}

.pill-toggle-option.is-disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

/* .field label (and .choice-qty-field's own label rule) style ANY label
   inside them, uppercase/10px/block, meant for a plain field caption; that
   beats .pill-toggle-option's single-class rule on specificity alone, which
   would otherwise squash a pill nested inside one of those wrappers (e.g. a
   resource-type picker inside a modal's .field) into that tiny caption look
   instead of a button. Restate the pill look here, one class higher, so it
   always wins regardless of what wraps it. */
.field .pill-toggle-option,
.choice-qty-field .pill-toggle-option {
  display: inline-flex;
  margin-bottom: 0;
  font-size: 13px;
  font-weight: 600;
  text-transform: none;
  letter-spacing: normal;
  color: var(--ink);
}

/* Auction House modals: a pill group with more than two or three options
   (resource type, calling, weapon) needs to line up in a tidy, even grid
   instead of Coin Flip's two fixed-width cards. Two problems ruled out
   flex-wrap and grid-auto-fit in turn: flex-wrap with a flex-basis fallback
   distributed leftover row space unevenly (uneven label lengths meant rows
   didn't align), and auto-fit stretched a lone leftover item (5 callings in
   a 4-column fit) into one ugly full-width row by itself. A fixed 2-column
   grid sidesteps both: every pill is always exactly half the row, and an
   odd last item just sits in its own half-width cell instead of stretching. */
.ah-pill-toggle {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 6px;
}

.ah-pill-toggle .pill-toggle-option {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-width: 0;
  padding: 8px 6px;
  font-size: 12.5px;
  line-height: 1.2;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ah-pill-toggle .pill-toggle-option svg {
  flex-shrink: 0;
}

.ah-pill-toggle .pill-toggle-option .resource-chip {
  margin-left: 0;
  gap: 3px;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The weapon item picker is a single column, one item per row (a name can
   run long), not a grid of equal squares like the other three pill groups. */
.ah-item-pills {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ah-item-pills .pill-toggle-option {
  justify-content: flex-start;
  text-align: left;
  white-space: normal;
}

/* Equip-title modal: same one-per-row pill list as .ah-item-pills, but a
   title label is short (unlike a weapon name), so the auction house's
   generous 11px/22px padding reads as oversized empty space around a
   two-word pill; tighten to match .ah-pill-toggle's compact padding
   instead, and widen the icon-to-text gap inside each .title-badge (its
   own 4px, tuned for the dashboard's dense titles-line, reads cramped at
   this larger pill size). */
.title-pills {
  align-items: stretch;
}

.title-pills .pill-toggle-option {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  line-height: 1;
}

.title-pills .pill-toggle-option .title-badge {
  gap: 7px;
}

/* Aligns the "Equip title" link to the Titles <h3> baseline, and pushes it
   to the right edge of the Titles block instead of sitting squeezed
   directly against the heading. */
.title-equip-link {
  font-size: 12px;
  margin-left: auto;
}

/* The choice dialog's shared .confirm-message is plain body text (most
   dialogs phrase it as a question/instruction, not a title), so the Equip
   title modal renders its own real heading inside the template instead of
   relying on data-choice-title. */
.choice-options h3.choice-dialog-heading {
  margin: 0 0 20px;
  font-size: 15px;
  font-weight: 700;
  text-transform: none;
  letter-spacing: normal;
  color: var(--ink);
}

/* Vertical rhythm inside a modal form: .field blocks stacked with no gap
   read as one dense wall of controls, and a field's own label/input/hint
   sit flush against each other with only .choice-qty-field's own margin
   doing any work. */
.choice-options .field {
  margin-bottom: 16px;
}

.choice-options .field:last-child {
  margin-bottom: 0;
}

.choice-options .field > * + * {
  margin-top: 8px;
}

/* The "Max N per listing" hint next to an amount input, red once the typed
   value exceeds that max (checked live by assets/js/auction-house.js), so a
   player sees the problem before submitting instead of only from the
   server's rejection. */
.ah-max-hint.is-over {
  color: #b43a3a;
  font-weight: 600;
}

/* Horse Racing's horse picker: a grid of chips (colored swatch + name)
   instead of a plain <select>, same hidden-radio pattern as the pill toggle. */
.horse-choice-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 8px;
  margin-top: 6px;
}

.horse-choice-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 140ms ease, background 140ms ease;
}

.horse-choice-option input {
  display: none;
}

.horse-choice-option:hover {
  border-color: var(--a1);
}

.horse-choice-option:has(input:checked) {
  border-color: var(--a1);
  background: color-mix(in srgb, var(--a1) 8%, transparent);
}

.horse-swatch {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  flex: none;
}

/* Horse Racing: a straight lane per horse, the token's left% is driven by
   horse-race.js from the race's precomputed finish time. */
.horse-track {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

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

.horse-lane-label {
  width: 90px;
  flex: none;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.horse-lane-track {
  position: relative;
  flex: 1;
  height: 14px;
  border-radius: 7px;
  background: color-mix(in srgb, var(--ink) 6%, transparent);
}

.horse-token {
  position: absolute;
  top: 50%;
  left: 0;
  width: 12px;
  height: 12px;
  margin-top: -6px;
  border-radius: 50%;
  transition: left 250ms linear;
}

.horse-finish-line {
  position: absolute;
  top: 0;
  right: 2px;
  bottom: 0;
  width: 2px;
  background: color-mix(in srgb, var(--ink) 25%, transparent);
}

.horse-odds-grid {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.horse-odds-row {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
}

.horse-odds-name {
  font-weight: 600;
}

/* Horse Racing: a small pulsing dot signals "this is live", both while a
   race is actually running and while waiting for the next one to start
   (data-horse-next-start's countdown auto-reloads once it hits zero). */
.live-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #3a8a4c;
  vertical-align: middle;
  margin-right: 2px;
  animation: live-dot-pulse 1.6s ease-in-out infinite;
}

@keyframes live-dot-pulse {
  0%, 100% {
    opacity: 1;
    box-shadow: 0 0 0 0 color-mix(in srgb, #3a8a4c 45%, transparent);
  }
  50% {
    opacity: 0.6;
    box-shadow: 0 0 0 4px color-mix(in srgb, #3a8a4c 20%, transparent);
  }
}

.horse-race-winner-banner {
  margin-top: 10px;
  padding: 12px 16px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--gold) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--gold) 40%, transparent);
  font-size: 15px;
  text-align: center;
}

/* Horse race result raven (ravens/horse-race-result.blade.php). A row is a
   flex container with a gap instead of relying on whitespace text nodes
   between spans, since .msg .body's white-space: pre-line can make plain
   inline spacing look inconsistent between inline-flex chips and text. */
.horse-race-result-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.horse-race-result-bets {
  list-style: none;
  margin: 8px 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.horse-race-result-bets li {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* Horse name and its wagered chip are one unit that never splits across
   lines; only the outcome (won/no payout) wraps to its own line if the
   row is too narrow. */
.horse-race-result-bet-horse,
.horse-race-result-bet-outcome {
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

/* Feature 61: The Wraithcall. A scrolling ticker under the header, on every
   page, while a run is underway (layouts/game-shell.blade.php). Single-pass
   loop: the track starts fully off-screen right and scrolls to fully
   off-screen left, then repeats; a lazy but adequate approximation of a
   seamless marquee. */
/* Fixed dread-red, not the viewer's own race accent (var(--a1)): a
   realm-wide horde alert should read the same for every race, not tint
   itself to whichever lord happens to be looking at it. */
.wraithcall-ticker {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  background: color-mix(in srgb, #b43a3a 12%, var(--soft) 88%);
  border-bottom: 1px solid var(--line);
  padding: 7px 0;
  text-decoration: none;
}

/* The assembly window (a run gathering heroes, not yet underway) is a
   heads-up, not an alarm, so it reads in the viewer's own race accent
   instead of the "Wraiths at the gates" red used once combat is live. */
.wraithcall-ticker.is-pending {
  background: color-mix(in srgb, var(--a1) 12%, var(--soft) 88%);
}

.wraithcall-ticker.is-pending .wraithcall-ticker-track {
  color: var(--a1);
}

/* Live-state tint: each event's own color, not a shared "danger" red -
   red stays reserved for actual danger to the viewer (hospitalized, low
   energy) elsewhere on these pages. :not(.is-pending) keeps the assembly
   window's race-accent tint above winning regardless of source order. */
.wraithcall-ticker.is-wraithcall:not(.is-pending) {
  background: color-mix(in srgb, var(--q-legendary) 12%, var(--soft) 88%);
}

.wraithcall-ticker.is-wraithcall:not(.is-pending) .wraithcall-ticker-track {
  color: var(--q-legendary);
}

.wraithcall-ticker.is-twinwyrms:not(.is-pending) {
  background: color-mix(in srgb, var(--wyrm-pale) 12%, var(--soft) 88%);
}

.wraithcall-ticker.is-twinwyrms:not(.is-pending) .wraithcall-ticker-track {
  color: var(--wyrm-pale);
}

.wraithcall-ticker.is-mirrorfiend:not(.is-pending) {
  background: color-mix(in srgb, var(--mirrorfiend) 12%, var(--soft) 88%);
}

.wraithcall-ticker.is-mirrorfiend:not(.is-pending) .wraithcall-ticker-track {
  color: var(--mirrorfiend);
}

.wraithcall-ticker.is-guild-raid:not(.is-pending) {
  background: color-mix(in srgb, var(--q-excellent) 12%, var(--soft) 88%);
}

.wraithcall-ticker.is-guild-raid:not(.is-pending) .wraithcall-ticker-track {
  color: var(--q-excellent);
}

.wraithcall-ticker-track {
  display: inline-block;
  padding-left: 100%;
  font: 600 13px/1.4 var(--sans);
  color: #b43a3a;
  animation: wraithcall-ticker-scroll 20s linear infinite;
}

.wraithcall-ticker:hover .wraithcall-ticker-track {
  animation-play-state: paused;
}

@keyframes wraithcall-ticker-scroll {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-100%);
  }
}

.wraithcall-horde-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 8px;
}

/* The event's own color, not the viewer's race accent: the horde/fiend's
   threat level has nothing to do with which race is looking at it (Twin
   Wyrms keeps its own per-wyrm override below, more specific so it still
   wins; Mirrorfiend's .mirrorfiend modifier below overrides this too). */
.wraithcall-percent {
  font: 700 22px/1 var(--serif);
  color: var(--q-legendary);
}

.wraithcall-percent.mirrorfiend {
  color: var(--mirrorfiend);
}

.wraithcall-bar {
  height: 10px;
  border-radius: 5px;
  margin-bottom: 6px;
}

/* Solid event color, not the race-accent gradient .bar i uses everywhere
   else: a gradient here implied a meaning (progress? two-tone stat?) that
   was never actually there, it was always just one number. */
.wraithcall-bar i {
  background: var(--q-legendary);
}

.wraithcall-bar.mirrorfiend i {
  background: var(--mirrorfiend);
}

/* Guild Raids' bar carries the foe's HP pool as a centered label ("104k /
   130k"), so it's tall enough to hold text; a double text-shadow in the
   page background acts as a halo, keeping the label legible whether it
   sits over the filled or the empty part of the bar, in either theme. */
.wraithcall-bar.has-hp-label {
  height: 20px;
  position: relative;
}

.wraithcall-hp-label {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font: 700 11px/1 inherit;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-shadow: 0 0 3px var(--bg), 0 0 3px var(--bg), 0 0 3px var(--bg);
  pointer-events: none;
}

/* The strength bar's own caption ("The horde's/Mirrorfiend's strength
   remaining."): a tight caption directly under the bar it describes, not a
   page-level intro paragraph, so it doesn't want .sub's 24px bottom margin
   meant for that other job. */
.event-bar-caption {
  font-size: 11.5px;
  color: var(--faint);
  margin: 0;
}

/* Feature 190: the Mirrorfiend's inline "Mirrorfiend" kill-feed chip
   (Mirrorfiend::highlightMirrorfiend()) and its own page title icon, its
   own metallic dark-blue color, distinct from the Wraithcall's Legendary
   purple and from the (unrelated) Mythic item-quality red. */
.resource-chip.mirrorfiend {
  color: var(--mirrorfiend);
}

.wraithcall-title-icon.mirrorfiend {
  color: var(--mirrorfiend);
}

/* The phase is the hero of this page: one centered block between
   hairlines, not a small clock competing with the strength bar. The word
   itself (not color-as-danger) carries the open/closed meaning: Shattered
   reads in --energy green, tightly tracked - open, go; Mirrored widens
   out and fades to --faint - sealed, hold. Red stays reserved for danger
   to the viewer (hospitalized, low energy) elsewhere on this page, not for
   "don't attack right now". */
/* The stance section sits directly under .event-hero, which already ends in
   its own border-bottom + margin-bottom - the default .section margin-top
   (25px) on top of that stacked into a lot of empty space before the phase
   block even starts. */
#mirrorfiend-stance {
  margin-top: 0;
}

.mirrorfiend-phase {
  text-align: center;
  padding: 4px 0 14px;
  margin-bottom: 14px;
  /* Only a bottom rule: .event-hero above already ends in its own
     border-bottom, so a border-top here doubled up into two adjacent
     lines. */
  border-bottom: 1px solid var(--line);
}

.mirrorfiend-phase-word {
  font: 700 24px/1 var(--serif);
  letter-spacing: 4px;
  text-transform: uppercase;
  /* The site's established "positive/open" green (.drill-result.is-positive,
     the online presence dot), not a new one-off token. */
  color: #3a8a4c;
  transition: color 0.3s ease, letter-spacing 0.3s ease, padding-left 0.3s ease;
  /* letter-spacing adds trailing space after the last character that
     text-align: center still counts toward the line's width, so a
     letter-spaced word centers visually a bit left of true center without
     this - nudge it back right by the same amount. */
  padding-left: 4px;
}

.mirrorfiend-phase[data-stance="mirrored"] .mirrorfiend-phase-word {
  color: var(--faint);
  letter-spacing: 7px;
  padding-left: 7px;
}

.mirrorfiend-phase-meaning {
  font: italic 500 13px/1.5 var(--serif);
  color: var(--ink);
  margin-top: 6px;
}

.mirrorfiend-phase-shift {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 1.2px;
  text-transform: uppercase;
  color: var(--faint);
  margin-top: 6px;
  padding-left: 1.2px;
}

/* Tabular figures so the digits don't shuffle the line's width as they
   count down. */
.mirrorfiend-phase-shift b {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.wraithcall-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
}

/* Attack and Help the Wounded split the row evenly on desktop instead of
   hugging their own text width, so the two actions read as one deliberate
   choice rather than a button sitting lost in empty space. */
.wraithcall-actions > .upgrade-tooltip {
  flex: 1 1 0;
}

.wraithcall-actions .btn-action {
  width: 100%;
  justify-content: center;
}

@media (max-width: 620px) {
  .wraithcall-actions {
    flex-direction: column;
  }

  .wraithcall-actions button {
    width: 100%;
  }

  .wraithcall-leaderboards {
    grid-template-columns: 1fr;
    gap: 18px;
  }
}

/* Same reasoning as .action-row .upgrade-hint: the default .upgrade-hint
   opens to the left, which would overlap the neighboring button in this
   horizontal row. Drop it below instead. */
.wraithcall-actions .upgrade-hint {
  right: auto;
  left: auto;
  top: calc(100% + 8px);
  transform: translateX(var(--hint-shift, 0px));
}

/* The blocked-action status line (hospitalized/no energy/on cooldown -
   "Catch your breath.") sits directly under the two full-width, centered
   Attack/Help buttons; left-aligned text there reads as orphaned, so it
   centers to match. */
[data-wraithcall-status],
[data-twinwyrms-status],
[data-mirrorfiend-status] {
  text-align: center;
}

[data-wraithcall-status] {
  margin-top: 10px;
}

/* No scrollbar on the feed: it's capped server-side to a handful of rows
   (feed_display_lines), so a fixed max-height would only ever clip, never
   actually need to scroll. Guild Chat instead supports loading further
   back (infinite scroll), so it gets its own scrollable wrapper below. */
.wraithcall-feed,
.wraithcall-chat {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Wraps the chat <ul> + its infinite-scroll sentinel: scrolls internally
   instead of the whole page. Its actual max-height is set inline by
   guild-chat.js, measured off the box's real position and (on mobile) the
   fixed bottom nav bar's real rendered height, since a static vh-minus-
   pixels guess here can't account for the sticky header's height, the
   mobile browser's collapsing URL bar, or notch safe-area, all of which
   vary by device. min-height is only a fallback floor for the moment
   before that JS runs (or if it doesn't). Scrollbar is themed via the
   existing --muted/--soft variables, which body.dark already redefines, so
   this reads correctly in both light and dark without a separate
   dark-mode rule. */
.guild-chat-scroll {
  min-height: 220px;
  overflow-y: auto;
  margin-bottom: 24px;
  scrollbar-width: thin;
  scrollbar-color: var(--muted) var(--soft);
}

.guild-chat-scroll::-webkit-scrollbar {
  width: 8px;
}

.guild-chat-scroll::-webkit-scrollbar-track {
  background: var(--soft);
  border-radius: 4px;
}

.guild-chat-scroll::-webkit-scrollbar-thumb {
  background: var(--muted);
  border-radius: 4px;
}

.guild-chat-scroll::-webkit-scrollbar-thumb:hover {
  background: var(--ink);
}

/* Flex row, not inline text, on the feed list: the <x-player-identity> icon
   block and the plain sentence text that follows it don't share a baseline,
   so relying on inline layout put the name visibly higher than the rest of
   the line. Chat rows instead stack author+time above the message body, so
   they get their own column layout below. */
.wraithcall-feed li,
.wraithcall-chat li {
  font: 400 13px/1.4 var(--sans);
  border-bottom: 1px solid var(--line);
  padding-bottom: 6px;
}

.wraithcall-feed li {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
  transition: opacity 220ms ease, transform 220ms ease;
}

/* A freshly arrived line (assets/js/wraithcall.js's renderFeed()) starts
   from this state and has it removed a frame later, so the transition above
   plays it sliding down into place instead of just popping in; a line
   pushed off the bottom by new arrivals gets .is-leaving instead, fading in
   place before JS removes it a moment later. */
.wraithcall-feed li.is-entering {
  opacity: 0;
  transform: translateY(-6px);
}

.wraithcall-feed li.is-leaving {
  opacity: 0;
}

.wraithcall-chat li {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.guild-chat-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

/* Not .sub: that class carries .page .sub's 24px margin-bottom, meant for
   page-level subtext paragraphs, which broke this inline label's flex
   alignment next to the identity block. */
.guild-chat-time {
  color: var(--muted);
  font-size: 12px;
}

/* Plain inline flow, not flex: with flex-wrap a long (multi-line) message
   fills the row's full width and pushes the time stamp onto its own line
   below the text. Inline flow lets the time ride right after the last word
   of the message, however many lines it wraps to. */
.guild-chat-body-row {
  display: block;
}

.guild-chat-body-row .guild-chat-time,
.guild-chat-body-row .guild-chat-edited {
  margin-left: 2px;
  white-space: nowrap;
}

/* An item-link chip's own .item-name-trigger sets its dotted underline
   directly (kow.css line ~8591); this reset just makes sure nothing about
   the message row/text itself carries or extends that line - only the
   linked item name should ever show it. */
.guild-chat-body-row,
.guild-chat-body {
  text-decoration: none;
}

/* The flex row above centers flex items by box height, but the <a> wrapping
   the identity was still a plain inline box: its height came from the
   surrounding font's line box (ascent+descent, ~18px), a couple px taller
   than the icon's own 18px content, so it never actually matched the
   sentence text's box and rode a few px high even with align-items:
   center. Making it a flex container itself sizes its box to its content
   exactly, so it matches the text sibling and centers flush. */
.wraithcall-feed .identity-link,
.wraithcall-chat .identity-link {
  display: inline-flex;
  align-items: center;
}

.wraithcall-feed .player-identity,
.wraithcall-chat .player-identity {
  vertical-align: 0;
}

/* One group per burst of consecutive same-author messages within 5 minutes
   (App\Support\EventChat::renderGroups()): one byline (icon, name, guild
   tag, presence dot, time), each message in the burst as its own line
   beneath it, indented to sit under the name rather than the icon. */
.chat-grp {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.chat-byline {
  display: inline-flex;
  align-items: center;
}

.chat-byline .player-identity b {
  font: 600 14px/1.2 var(--sans);
}

/* Each byline's own moment, formatted from UTC to the viewer's local time
   client-side (assets/js/*.js's formatChatTimes()); empty text server-side
   until that runs, per the "server hands out UTC, client converts"
   convention used everywhere else in the game. */
.chat-when {
  font-size: 10.5px;
  color: var(--faint);
  margin-left: 6px;
}

.chat-grp p {
  color: var(--muted);
  font: 400 13.5px/1.5 var(--sans);
  /* Same reasoning as .guild-chat-body above: only an item-link chip's own
     .item-name-trigger should ever show its dotted underline, never the
     message text around it. */
  text-decoration: none;
  /* Icon width (18px) + .player-identity's own gap (6px): lines up under
     the name instead of the icon. */
  padding-left: 24px;
  margin: 0;
  overflow-wrap: anywhere;
}

/* The viewer's own messages: name reads in their race's secondary accent,
   same token the rest of the page already uses to mark "you" (e.g. Twin
   Wyrms' own target pills), not a color tied to the message content. */
.chat-grp.is-self .chat-byline .player-identity b {
  color: var(--a2);
}

/* Text-only guild tag in chat, no banner glyph: a dense scrolling list
   reads cleaner without a second graphic per line, the banner still shows
   everywhere else identities render (profile, leaderboard, guild page). */
.wraithcall-chat .guild-banner {
  display: none;
}

.wraithcall-chat .guild-tag-badge {
  border: none;
  padding: 0;
  background: none;
  color: var(--faint);
}

/* Chat's own system/herald lines (a stance shift, a regen tick, the
   pre-start countdown, ...): the event itself speaking, set apart from
   every lord's own message with a centered line rather than another chat
   row, so it can't be mistaken for a player line at a glance. Normal sans,
   not italic serif - the serif italic read poorly at this small a size. */
.chat-herald {
  text-align: center;
  font: 500 12.5px/1.5 var(--sans);
  color: var(--faint);
}

/* Feed action colors: a miss (lost that swing to a Wraith) orange, a wound
   (the Wraith actually landed a blow) red. Scoped here since .is-negative
   elsewhere is always paired with its own parent selector, no bare rule
   exists to fall back on. */
.wraithcall-feed .is-negative {
  color: #b43a3a;
  font-weight: 600;
}

.wraithcall-feed .is-warning {
  color: #c8763a;
  font-weight: 600;
}

/* A wasted swing (Mirrorfiend's "fed" action: a landed hit during Mirrored
   that healed the fiend instead of hurting it) - a mistake, not a danger
   to the player, so it reads italic and faint rather than alarm-red. */
.wraithcall-feed .is-wasted {
  color: var(--faint);
  font-style: italic;
  font-weight: 400;
}

/* The acting hero's own blows, in their own race accent: same "is this me"
   tint the chat already gives its own messages (.chat-grp.is-self). */
.wraithcall-feed .identity-link.is-self .player-identity b {
  color: var(--a2);
}

/* One row, comma-separated identities, not a list of rows: keeps the
   Infirmary roster compact even when several heroes have fallen back. */
.wraithcall-wounded {
  margin: 8px 0 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
  font: 400 13px/1.4 var(--sans);
}

.wraithcall-wounded-label {
  color: #b43a3a;
  font-weight: 700;
  margin-right: 2px;
}

.wraithcall-wounded span[data-wounded-id] {
  display: inline-flex;
  align-items: center;
}

/* Same fix as .wraithcall-feed .identity-link: sizes the anchor's box to
   its own icon content instead of the surrounding font's line-box slop, so
   it lines up with the countdown text next to it. */
.wraithcall-wounded .identity-link {
  display: inline-flex;
  align-items: center;
}

/* Per-hero countdown to their Infirmary discharge, ticked every second by
   wraithcall.js's tickWoundedTimers(). Tabular figures so the digits don't
   shift the row width as they count down. */
.wraithcall-wounded-timer {
  color: var(--faint);
  font-variant-numeric: tabular-nums;
  margin-left: 4px;
}

.wraithcall-chat-form {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  margin-bottom: 16px;
}

.wraithcall-chat-form input[type="text"] {
  flex: 1;
  border: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  padding: 7px 4px;
}

.wraithcall-chat-form input[type="text"]:focus {
  outline: none;
  border-bottom-color: var(--a1);
}

/* Own messages within the edit window get a pointer + subtle hover cue on
   the body text so "you can click this" reads without extra chrome. */
.wraithcall-chat [data-editable] .guild-chat-body {
  cursor: pointer;
  border-bottom: 1px dashed transparent;
}

.wraithcall-chat [data-editable] .guild-chat-body:hover {
  border-bottom-color: var(--faint);
}

.guild-chat-edited {
  color: var(--faint);
  font-size: 12px;
}

.guild-chat-edit-form {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1;
  min-width: 160px;
}

.guild-chat-edit-form input[type="text"] {
  flex: 1;
  min-width: 0;
  border: 0;
  border-bottom: 1px solid var(--a1);
  background: transparent;
  color: var(--ink);
  padding: 4px 2px;
  font: inherit;
}

.guild-chat-edit-form input[type="text"]:focus {
  outline: none;
}

/* Small screens: drop the identity icon+name while a message is open for
   edit, so the input keeps the width instead of squeezing next to it. */
@media (max-width: 620px) {
  .wraithcall-chat li:has(.guild-chat-edit-form) .identity-link {
    display: none;
  }
}

.guild-chat-edit-error {
  color: #b43a3a;
  font-size: 12px;
}

/* No flex here on purpose: a flex item's box is only as tall as its own
   content, but a plain inline icon inside normal flow gets the same
   line-height "strut" text does, so this ends up exactly as tall as the
   text Send button next to it without hardcoding either button's height. */
.guild-chat-save {
  padding: 6px 10px;
  flex-shrink: 0;
}

.guild-chat-save svg {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.2em;
  fill: currentcolor;
}

/* The Twin Wyrms' own neutral tints (never the viewer's own race accent,
   --a1/--a2), so the Pale/Black split reads the same for every lord.
   Black Wyrm's dark-mode values are lifted much lighter than its light-mode
   ink tone: the light-mode "dark" color read as barely visible text/bar
   fill against an already-dark page background, and the selected target
   button (text color var(--bg), i.e. dark in dark mode) needs a fill light
   enough to actually contrast against that dark text. */
:root {
  --wyrm-pale: #a8925f;
  --wyrm-pale-bar-a: #cdbfa0;
  --wyrm-pale-bar-b: #e8dcc0;
  --wyrm-black: #3a3f47;
  --wyrm-black-bar-a: #2b2f36;
  --wyrm-black-bar-b: #4a5058;
}

/* Two bars side by side. Stacks to one column on mobile, same 620px break
   as every other two-column layout on this page (.wraithcall-leaderboards). */
.twinwyrms-bars {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

@media (max-width: 620px) {
  .twinwyrms-bars {
    grid-template-columns: 1fr;
    gap: 10px;
  }
}

.twinwyrms-bar-block h4 {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font: 600 14px/1.2 var(--sans);
}

.twinwyrms-bar-pale i {
  background: linear-gradient(to right, var(--wyrm-pale-bar-a), var(--wyrm-pale-bar-b));
}

.twinwyrms-bar-black i {
  background: linear-gradient(to right, var(--wyrm-black-bar-a), var(--wyrm-black-bar-b));
}

.wyrm-pale .wraithcall-percent,
.wyrm-pale h4 {
  color: var(--wyrm-pale);
}

.wyrm-black .wraithcall-percent,
.wyrm-black h4 {
  color: var(--wyrm-black);
}

/* Inline "Pale Wyrm"/"Black Wyrm" chip (TwinWyrms::highlightWyrmNames()),
   same idea as the Wraithcall's own "Wraith" chip but tinted per wyrm
   instead of a single ghostly purple. */
.resource-chip.wyrm-chip.wyrm-pale {
  color: var(--wyrm-pale);
}

.resource-chip.wyrm-chip.wyrm-black {
  color: var(--wyrm-black);
}

/* The wyrm icon's own artwork reads visually a couple pixels low next to
   its text everywhere it's used (the bar headers' h4, and the inline
   "Pale Wyrm"/"Black Wyrm" chip above), the same kind of empirical nudge as
   .resource-chip's own manual vertical-align: -2px correction. */
.twinwyrms-bar-block h4 .res-icon,
.resource-chip.wyrm-chip .res-icon {
  transform: translateY(-1.5px);
}

/* The two-way target selector above the Attack button: each pill takes
   half the row (so the pair always fills 100% width on desktop too, not
   just content width), the selected one filled with its own wyrm tint
   instead of the usual --a1 active state, for the same race-neutral reason
   as the bars above. */
.twinwyrms-target-select {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
}

.twinwyrms-target-btn {
  flex: 1 1 50%;
  text-align: center;
}

.twinwyrms-target-btn[aria-pressed="true"] {
  color: var(--bg);
  border-color: transparent;
}

.twinwyrms-target-btn[data-twinwyrms-target-btn="pale"][aria-pressed="true"] {
  background: var(--wyrm-pale);
}

.twinwyrms-target-btn[data-twinwyrms-target-btn="black"][aria-pressed="true"] {
  background: var(--wyrm-black);
}

/* Kill-feed system lines (a regen tick, a bond breaking, victory, time
   running out, App\Support\Events). Muted and italic, no player identity,
   so they read as the world reacting rather than another hero's blow. Chat's
   own system/herald lines use .chat-herald instead, see below. */
.wraithcall-feed .is-system {
  color: var(--faint);
  font-style: italic;
}

/* Admin > World Events: the 7-day schedule editor. Each day contributes
   exactly two form controls (the .checkbox-row, then its time input), so a
   plain 2-column grid lines every day's row up without touching the markup. */
.admin-schedule-grid {
  display: grid;
  grid-template-columns: 130px auto;
  align-items: center;
  gap: 10px 16px;
  max-width: 320px;
  margin: 4px 0 10px;
}

.admin-schedule-grid input[type="time"] {
  border: 0;
  border-bottom: 1px solid var(--line);
  background: transparent;
  color: var(--ink);
  padding: 4px 0;
}

/* Reinforcements: the compact Recall/Send back buttons in the Dashboard's
   Reinforcements panel and lists, a smaller variant of .btn-ghost rather
   than a whole new style. */
.btn-sm {
  font-size: 12px;
  padding: 4px 10px;
}

/* gear2 preview: inline-stepper "Your Soldiers" rows, replacing the
   equip/unequip modals with one row per tier/role and a quantity stepper,
   so mobile players don't open a dialog per change (see gear2.blade.php). */
.strip2 {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px 16px;
  padding: 8px 12px;
  background: var(--panel-muted, rgba(0, 0, 0, 0.03));
  border-radius: 6px;
  margin-bottom: 10px;
  font-size: 12px;
}

.strip2-figures {
  display: flex;
  gap: 16px;
  align-items: baseline;
}

.strip2 b {
  font-size: 15px;
  margin-right: 4px;
}

.strip2 .lab {
  color: var(--muted);
  text-transform: uppercase;
  font-size: 9.5px;
  letter-spacing: 0.8px;
}

.strip2.over2 {
  background: color-mix(in srgb, #b43a3a 12%, transparent);
  color: #b43a3a;
}

.strip2.over2 b {
  color: #b43a3a;
}

.growrow2 {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "ident stepper"
    "ofcap quickset";
  align-items: center;
  row-gap: 4px;
  column-gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
  min-width: 0;
}

.growrow2 .ident2 {
  grid-area: ident;
  min-width: 0;
}

.growrow2 .stepper2 {
  grid-area: stepper;
  justify-self: end;
}

.growrow2 .quickset2 {
  grid-area: quickset;
  justify-content: flex-end;
  justify-self: end;
}

.growrow2 .ofcap2 {
  grid-area: ofcap;
  justify-self: start;
}

.growrow2:first-of-type {
  border-top: 1px solid var(--line);
}

.ident2 {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.dot2 {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--muted);
}

.dot2.attack {
  background: #a32626;
}

.dot2.defense {
  background: #26518f;
}

.ident2 .nm2 {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12.5px;
  font-weight: 500;
}

.ident2 .nm2 i {
  font-style: normal;
  color: var(--muted);
  font-weight: 400;
}

.ident2 .meta2 {
  color: var(--muted);
  font-size: 11.5px;
  white-space: nowrap;
}

/* Lines below the item name (Owned: N, price/stock) start where the name's
   own text starts, past the role dot + its gap (6px + 6px). */
.indent2 {
  display: block;
  margin-left: 12px;
}

.stepper2 {
  display: flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}

.stepper2 input.qty {
  width: 4.5ch;
  text-align: center;
}

.ofcap2 {
  color: var(--muted);
  font-size: 11px;
}

.quickset2 {
  display: flex;
  gap: 14px;
  font-size: 11px;
  white-space: nowrap;
}

.quickset2 a {
  padding: 4px 0;
  color: var(--muted);
  cursor: pointer;
}

.quickset2 a:hover {
  color: var(--accent-2, var(--muted));
  text-decoration: underline;
  text-underline-offset: 2px;
}

.btnrow2 {
  display: flex;
  margin-top: 10px;
}

.btnrow2 .btn-primary {
  width: 100%;
}

.strip2 .note2 {
  font-size: 11px;
  color: var(--muted);
  margin-left: auto;
}

.tag2 {
  display: block;
  margin-top: 12px;
  font-size: 11px;
  color: var(--muted);
}

.tag2 b {
  color: var(--ink, inherit);
}

.stockrow2 {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    "ident stepper"
    "meta acts";
  align-items: center;
  row-gap: 4px;
  column-gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
  min-width: 0;
}

.stockrow2:first-of-type {
  border-top: 1px solid var(--line);
}

.stockrow2 .ident2 {
  grid-area: ident;
  min-width: 0;
}

.stockrow2 .stepper2 {
  grid-area: stepper;
  justify-self: end;
}

.stockrow2 .meta2 {
  grid-area: meta;
  justify-self: start;
  font-size: 11px;
}

.stockrow2 .meta2 .resource-chip {
  font-size: 11px;
  font-weight: 400;
}

.stockrow2 .acts2 {
  grid-area: acts;
  display: flex;
  align-items: stretch;
  gap: 8px;
  justify-self: stretch;
  white-space: nowrap;
}

.stockrow2 .stepper2 input.qty {
  width: 4.5ch;
  text-align: center;
}

.stockrow2 .acts2 form {
  display: flex;
  flex: 1;
}

.stockrow2 .acts2 form button {
  width: 100%;
}

@media (max-width: 620px) {
  .strip2 {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }

  .strip2-figures {
    justify-content: center;
  }

  .strip2 .note2 {
    margin-left: 0;
    text-align: center;
  }
}

@media (max-width: 400px) {
  .growrow2,
  .stockrow2 {
    gap: 6px;
  }

  .ident2 .nm2 {
    font-size: 12px;
  }
}

/* Infirmary: the Maester's welcome, read like the opening of a page in a
   book rather than a plain status line. Race-accented border/wash, same
   restrained "subtle accent, not a loud block" treatment as everywhere else. */
.maester-welcome {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--a1);
  background: color-mix(in srgb, var(--a1) 5%, var(--soft) 95%);
  padding: 16px 18px;
}

.maester-portrait {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--a1);
}

.maester-welcome-text {
  min-width: 0;
}

.maester-name {
  font: 700 11px/1.2 var(--sans);
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--faint);
  margin: 4px 0 6px;
}

.maester-quote {
  font: 500 italic 16px/1.5 var(--serif);
  margin: 0;
}

@media (max-width: 480px) {
  .maester-welcome {
    gap: 12px;
    padding: 14px;
  }

  .maester-portrait {
    width: 40px;
    height: 40px;
  }

  .maester-quote {
    font-size: 15px;
  }
}

/* Feature 176: Hero Training Log, /train. Muted, compact rows, newest drill
   first; scrolling near the sentinel loads older rows (train-log.js). */
.training-log-section h3 {
  margin-bottom: 8px;
}

.training-log-list {
  display: flex;
  flex-direction: column;
}

.training-log-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 3px 0;
  font-size: 12.5px;
  border-bottom: 1px solid var(--line);
}

.training-log-row:last-child {
  border-bottom: none;
}

.training-log-attr {
  min-width: 80px;
}

.training-log-gain {
  color: var(--muted);
}

.training-log-value {
  color: var(--muted);
}

.training-log-time {
  margin-left: auto;
  color: var(--muted);
}

.training-log-sentinel {
  height: 1px;
}

/* Event Calendar: --event-color is set inline per section/row from each
   event's own established color var (e.g. --q-legendary, --wyrm-pale), so
   this file only ever styles the mechanism, never a specific event's hue. */
.event-cal-next {
  border: 1px solid var(--line);
  border-left: 3px solid var(--event-color);
  border-radius: 6px;
  padding: 18px 20px;
  margin-bottom: 20px;
}

.event-cal-label {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--event-color);
}

.event-cal-name {
  font-size: 22px;
  font-weight: 600;
  margin-top: 8px;
  color: var(--event-color);
}

.event-cal-flavor {
  font-style: italic;
  color: var(--muted);
  max-width: 480px;
  margin-top: 4px;
  font-size: 13.5px;
}

.event-cal-whenrow {
  display: flex;
  align-items: baseline;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 14px;
}

.event-cal-count {
  font-size: 19px;
  font-weight: 600;
}

.event-cal-count small {
  font-size: 11px;
  font-weight: 500;
  color: var(--faint);
  letter-spacing: .5px;
  text-transform: uppercase;
  margin-left: 6px;
}

.event-cal-at {
  font-size: 12.5px;
  color: var(--muted);
}

.event-cal-join {
  display: inline-block;
  margin-top: 14px;
}

.event-cal-heading {
  font-size: 10.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 1.5px;
  color: var(--faint);
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 22px 0 4px;
}

.event-cal-heading::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--line);
}

.event-cal-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 11px 2px;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}

.event-cal-row-name {
  font-weight: 600;
  color: var(--event-color);
  white-space: nowrap;
}

.event-cal-row-when {
  margin-left: auto;
  text-align: right;
  white-space: nowrap;
  font-size: 12.5px;
  color: var(--muted);
}

.event-cal-row-server {
  display: block;
  font-size: 10.5px;
  color: var(--faint);
}

.event-cal-tznote {
  font-size: 11.5px;
  color: var(--faint);
  font-style: italic;
  margin-top: 14px;
}

@media (max-width: 560px) {
  .event-cal-row {
    row-gap: 2px;
  }

  .event-cal-row-when {
    margin-left: auto;
  }
}

/* Event Calendar's History tab: one card per resolved GameEventRun, reusing
   the same .leaderboard-top-columns/.wraithcall-leaderboard-list boards the
   live event pages already carry (App\Http\Controllers\
   EventCalendarController::historyRow()), so a past run reads the same way
   a live one does, just stacked and dated instead of live-polled. */
.event-history-rows {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-top: 8px;
}

.event-history-row {
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 14px 16px;
}

.event-history-row-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}

.event-history-row-name {
  font-weight: 600;
  color: var(--event-color);
}

.event-history-row-outcome {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .5px;
}

.event-history-row-outcome.is-victory {
  color: #3a8f4f;
}

.event-history-row-outcome.is-expired {
  color: var(--muted);
}

.event-history-row-date {
  margin-left: auto;
  font-size: 12.5px;
  color: var(--faint);
}

.event-history-expired-note {
  margin: 0;
}

/* Guild Raids' summon composer (guild/raids.blade.php + guild-raids.js,
   owner concept mockup guild-raid-concept.html): the Boon Shop composer
   pattern grown a step: selectable foe rows, a strength stepper, a
   local-clock datetime with the server conversion beneath, then one serif
   offer sentence + the summons cost + one button. Resource identity colors
   shared with the Boon Shop's own r-* classes above. */
.raid-foe {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 11px 2px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
}

.raid-foe-name {
  font: 600 18px/1.2 var(--serif);
  color: var(--muted);
}

.raid-foe:hover .raid-foe-name {
  color: var(--ink);
}

.raid-foe-icon {
  vertical-align: -4px;
  margin-right: 2px;
}

.raid-foe-boon {
  font-size: 11.5px;
  color: var(--faint);
}

.raid-foe-mark {
  margin-left: auto;
  font-size: 12px;
  color: var(--ink);
  visibility: hidden;
}

.raid-foe.is-on .raid-foe-mark {
  visibility: visible;
}

.raid-foe.is-on.r-wood .raid-foe-name { color: #7a4b2b; }
.raid-foe.is-on.r-stone .raid-foe-name { color: #8a919a; }
.raid-foe.is-on.r-food .raid-foe-name { color: #c9762f; }
.raid-foe.is-on.r-gold .raid-foe-name { color: #c8a13a; }

.raid-stepper {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--line);
}

.raid-stepper button {
  background: none;
  border: none;
  color: var(--muted);
  font: 600 15px var(--sans);
  width: 34px;
  height: 32px;
  cursor: pointer;
}

.raid-stepper button:hover {
  color: var(--ink);
}

.raid-stepper input {
  width: 56px;
  height: 32px;
  background: none;
  border: none;
  border-left: 1px solid var(--line);
  border-right: 1px solid var(--line);
  color: var(--ink);
  font: 600 15px var(--serif);
  text-align: center;
  outline: none;
  -moz-appearance: textfield;
  appearance: textfield;
}

.raid-stepper input::-webkit-outer-spin-button,
.raid-stepper input::-webkit-inner-spin-button {
  -webkit-appearance: none;
}

.raid-locknote {
  font: italic 500 12px var(--serif);
  color: var(--faint);
}

.raid-when {
  background: none;
  border: none;
  border-bottom: 1px solid var(--faint);
  color: var(--ink);
  font: 600 15px var(--serif);
  padding: 4px 2px;
  outline: none;
}

.raid-when:focus {
  border-color: var(--ink);
}

.raid-whennote {
  font-size: 11.5px;
  color: var(--faint);
  margin-top: 6px;
}

.raid-whennote b {
  color: var(--muted);
  font-weight: 600;
}

/* Inline validation, never a popup (concept note): a serif italic line in
   the announce accent. */
.raid-error {
  font: italic 500 12.5px var(--serif);
  color: var(--a1);
  margin-top: 6px;
}

/* "Called by <identity> for <time>": a flex row instead of prose, so the
   identity's icon/banner/tag sit centered on one line with the text. */
.raid-called-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 5px;
}

.raid-offer {
  border-top: 1px solid var(--line);
  padding-top: 18px;
  margin-top: 20px;
}

.raid-sentence {
  font: 400 14px/1.7 var(--sans);
  color: var(--ink);
}

.raid-sentence b {
  font-weight: 600;
}

.raid-costline {
  display: flex;
  align-items: baseline;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 14px;
  font-size: 12px;
}

.raid-costlbl {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 1.8px;
  text-transform: uppercase;
  color: var(--faint);
}

/* Treasury can't cover this part (cost/holding chip): dimmed, same
   affordability language as the Treasury tab's own Buy buttons. */
.raid-costline .resource-chip.is-short {
  opacity: .4;
}

.raid-holdline {
  font-size: 11.5px;
  color: var(--faint);
  margin-top: 6px;
}

.raid-summon {
  margin-top: 18px;
}

/* Raid report's two follow-on actions (guild/raid-report.blade.php), each
   taking half the row, together spanning it edge to edge. */
.raid-report-actions {
  display: flex;
  gap: 10px;
}

.raid-report-actions .btn-primary,
.raid-report-actions .btn-ghost {
  flex: 1;
  text-align: center;
}

/* Floating chat widgets: Guild Chat (components/guild-chat-widget.blade.php)
   and World Event chat (components/event-chat-widget.blade.php, one per
   currently-active event). Both carry [data-floating-chat-widget] for
   shared positioning - assets/js/floating-widgets.js lays out however many
   are present in a single row, bottom-right, Guild Chat anchored at the
   right edge and World Event widgets stacking to its left (or the first
   event widget taking the anchor spot if there's no Guild Chat widget on
   the page), keeping the whole row clear of .realm-nav-mobile using its
   real measured height on mobile (same "measure it, don't guess" approach
   the chat scroll box's own sizing already used). right/bottom here are
   only the static single-widget fallback before that JS runs. z-index
   above the sticky header/nav bar (20) but below the confirm dialog (200),
   so a destructive confirm still wins if one happens to be open. */
[data-floating-chat-widget] {
  position: fixed;
  right: 16px;
  bottom: calc(20px + env(safe-area-inset-bottom));
  z-index: 50;
}

.floating-chat-widget-btn {
  width: 48px;
  height: 48px;
  border-radius: 999px;
  /* .icon-btn itself sets no border/background/color/appearance (only
     .header-actions .icon-btn does, scoped to the header); outside the
     header a plain <button> falls back to the browser/OS's native chrome
     for all four, which ignores --ink/--bg entirely and read wrong (and
     the SVG's fill="currentColor" inherited that wrong native color too)
     in dark mode. All four reset explicitly here rather than relying on
     the header-scoped rule. */
  appearance: none;
  -webkit-appearance: none;
  border: 1px solid var(--line);
  background: var(--bg);
  color: var(--ink);
  box-shadow: 0 2px 10px color-mix(in srgb, #000 20%, transparent);
}

/* A count (Guild Chat, from its own unread-count endpoint) or, for World
   Event chat which has no persisted per-player "seen" state, just this
   border highlight on its own with no number (event-chat.js's client-side
   new-message check). */
/* Admin-only Guild Raids overview (components/admin-raids-widget.blade.php):
   a small always-visible card pinned bottom-left on desktop listing every
   guild's planned/live raid, each row linking to that raid's page for
   read-only spectating. Desktop-only by design (owner request): on narrow
   screens it would fight the mobile nav for the same corner. */
.admin-raids-widget {
  position: fixed;
  left: 16px;
  bottom: calc(20px + env(safe-area-inset-bottom));
  z-index: 50;
  max-width: 280px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  box-shadow: 0 2px 10px color-mix(in srgb, #000 20%, transparent);
  font: 400 12px/1.5 var(--sans);
}

.admin-raids-widget-title {
  font-weight: 600;
  margin: 0 0 4px;
}

.admin-raids-widget ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.admin-raids-widget li a {
  color: var(--ink);
  text-decoration: none;
  display: block;
  padding: 2px 0;
}

.admin-raids-widget li a:hover {
  color: var(--a1);
}

.admin-raids-widget .is-live {
  color: var(--q-excellent);
  font-weight: 600;
}

@media (max-width: 900px) {
  .admin-raids-widget {
    display: none;
  }
}

.floating-chat-widget-btn.has-unread {
  border-color: var(--a1);
}

.floating-chat-widget-panel[hidden] {
  display: none;
}

/* Fixed, not relative to its own (possibly stacked-left) toggle button:
   every widget's panel opens from the exact same bottom-right screen
   position regardless of which button triggered it or where that button
   sits in the row, set by assets/js/floating-widgets.js via
   --floating-chat-panel-bottom. This also means a panel's vertical range
   never overlaps the button row it grew out of, or any other widget's
   button further along that row - no z-index tug-of-war needed between an
   open panel and a sibling widget's collapsed button. */
.floating-chat-widget-panel {
  position: fixed;
  right: 16px;
  bottom: var(--floating-chat-panel-bottom, 80px);
  z-index: 51;
  width: min(340px, calc(100vw - 32px));
  background: var(--bg);
  border: 1px solid var(--line);
  box-shadow: 0 6px 24px color-mix(in srgb, #000 25%, transparent);
  padding: 10px;
}

.floating-chat-widget-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font: 600 13px/1.3 var(--sans);
  margin-bottom: 8px;
}

.floating-chat-widget-title {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.floating-chat-widget-close {
  width: 26px;
  height: 26px;
  font-size: 18px;
  line-height: 1;
  appearance: none;
  -webkit-appearance: none;
  border: none;
  background: transparent;
  color: var(--ink);
}

.floating-chat-widget-close:hover {
  color: var(--a1);
}

/* Fixed height instead of assets/js/guild-chat.js's viewport-remaining-
   space sizing: that measurement assumes a box filling the rest of a full
   page, wrong for this small overlay panel (guild-chat.js skips its own
   resize logic for any box carrying data-fixed-height; event-chat.js never
   runs that logic at all). */
.floating-chat-widget-panel .guild-chat-scroll {
  max-height: min(50vh, 360px);
  margin-bottom: 10px;
  padding-right: 10px;
}

/* Both floating chat panels read chronologically now (list, then composer
   below it, last element in the panel) - no bottom margin needed. */
.floating-chat-widget-panel .wraithcall-chat-form {
  margin-top: 0;
  margin-bottom: 0;
}

/* Feature 209: the admin dashboard's per-day charts, drawn by Chart.js
   (assets/js/admin-dashboard.js) on a canvas; this wrapper gives the
   responsive canvas its fixed height on every viewport. */
.admin-chart {
  position: relative;
  height: 200px;
  margin-top: 14px;
}

@media (max-width: 600px) {
  .admin-chart {
    height: 160px;
  }
}


.admin-income {
  margin-top: 18px;
}

/* Income split line under each income figure: Mollie (exact) vs Google
   Play (estimated), quieter than the figure itself. */
.admin-income-split {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  color: var(--faint);
}

.admin-income-note {
  margin-top: 10px;
  font-size: 12px;
}

/* The dashboard's glance row holds five figures; on a phone the spread
   grid squeezes them into five unreadable slivers with oversized numbers.
   Wrap into three columns and step the type down one size. */
@media (max-width: 600px) {
  .admin-glance {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 12px 16px;
  }

  .admin-glance .fig b {
    font-size: 19px;
  }

  .admin-glance .fig span {
    font-size: 9px;
    letter-spacing: 1px;
  }
}

/* Admin dashboard redesign: the two Chart.js graphs + income figures used
   to sit above the tool links, forcing a long scroll past charts an admin
   rarely needs before reaching the actual actions. Folded into a closed
   <details> below the tools instead, same chevron/rotate pattern as
   .ah-filter-group's collapsible filters. */
.admin-analytics {
  border-top: 1px solid var(--line);
  padding-top: 14px;
}

.admin-analytics > summary {
  cursor: pointer;
  list-style: none;
}

.admin-analytics > summary::-webkit-details-marker {
  display: none;
}

.admin-analytics > summary h3 {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 0;
}

.admin-analytics > summary h3::before {
  content: '\25B8';
  display: inline-block;
  color: var(--muted);
  transition: transform 0.15s ease;
}

.admin-analytics[open] > summary h3::before {
  transform: rotate(90deg);
}

.admin-analytics > .section:first-of-type {
  margin-top: 18px;
}
