/* Minimal server-rendered styling. No JavaScript, no CSS framework:
   constitution Technology Constraints keep the frontend plain. */

/* Palette sampled from static/banner.jpg rather than eyeballed, so the page and
   the artwork are literally the same colours:

     --bg       the banner's own averaged edge colour
     --panel    its dim aurora haze
     --accent   its brightest mint
     --accent-2 its brightest blue                                            */
:root {
  --bg: #000307;
  --panel: #061019;
  --ink: #e6f7f8;
  --muted: #7e9bab;
  --accent: #4af9e7;
  --accent-2: #369ee8;
  --line: #103048;

  /* Surfaces are translucent so the banner reads through them. How far these
     can be opened up is not a matter of taste -- it is bounded by contrast, and
     the values below are the loosest that keep every pairing above WCAG AA
     against the BRIGHTEST pixel in the artwork (which is pure white):

       under a panel   body 13.7:1   muted 5.2:1
       bare on the page  body  5.1:1   (the roster heading is the only such text)

     Muted text is never placed bare over the page -- .tagline and .fineprint
     live inside panels, .battletag and the footer have their own veil -- which
     is what allows the panel veil to be this open. Putting muted text directly
     on the background would break that, at roughly 2:1. */
  --panel-veil: rgba(6, 16, 25, 0.72);
  --bar-veil: rgba(0, 3, 7, 0.7);
}

* { box-sizing: border-box; }

/* The page colour lives on the root, leaving body's own background transparent
   so the fixed banner layer below can sit behind the content and in front of
   the page colour. Painting --bg on body instead would bury it. */
html { background-color: var(--bg); }

body {
  margin: 0;
  background: transparent;
  color: var(--ink);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* Hold the footer at the bottom of the page on short pages -- the landing
     page is one screen of content in a tall viewport, and without this the
     footer sat halfway up with the banner showing below it.

     A column flex body with a growing main, rather than position: fixed: the
     footer belongs at the end of the PAGE, not stuck to the viewport, so on the
     roster it scrolls away under the cards like an ordinary footer instead of
     hovering over them. */
  min-height: 100vh;
  min-height: 100dvh; /* honours mobile browser chrome; ignored where unsupported */
  display: flex;
  flex-direction: column;
}

/* The banner, held in the centre of the VIEWPORT -- position: fixed, so it
   stays put while the page scrolls -- and kept behind everything.

   Its edges are feathered into --bg with a radial mask. Matching the page
   colour to the artwork's edge does most of the blending; the mask removes the
   last visible seam, so the image appears to hover rather than to be pasted on.
   pointer-events: none so it can never swallow a click. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: url("/static/banner.jpg");
  background-repeat: no-repeat;
  background-position: center center;
  /* Sized to hover in the middle rather than fill: never taller than the
     viewport, never wider than the page's comfortable measure. */
  background-size: min(1100px, 92vw) auto;
  opacity: 0.4;
  -webkit-mask-image: radial-gradient(ellipse 62% 58% at center, #000 30%, transparent 74%);
  mask-image: radial-gradient(ellipse 62% 58% at center, #000 30%, transparent 74%);
}

.site-header {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
  background: var(--bar-veil);
}

.brand {
  font-weight: 700;
  letter-spacing: 0.18em;
  color: var(--accent);
  text-decoration: none;
  font-size: 1.1rem;
}

.site-header nav ul {
  display: flex;
  gap: 1rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.site-header nav a { color: var(--ink); text-decoration: none; }
.site-header nav a:hover { color: var(--accent-2); }

.logout {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.battletag { color: var(--muted); font-size: 0.9rem; }

/* Next up: what the calendar has coming, in the header, so nobody has to go
   looking for raid night. It takes the spare room between the nav and the
   sign-out; the sign-out gives up its own auto margin when the ticker is
   there, or the two would split the space and the ticker would float in the
   middle. Each line is a pill, so two events read as two things and not one
   run-on sentence. */
.ticker {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
  font-size: 0.85rem;
}

.ticker + .logout { margin-left: 0; }

.ticker-label {
  color: var(--accent);
  font-size: 0.68rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}

.ticker-items {
  display: flex;
  gap: 0.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
  min-width: 0;
}

.ticker-items li { min-width: 0; }

.ticker-items a {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  max-width: 18rem;
  padding: 0.25rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--panel-veil);
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.ticker-items a:hover { border-color: var(--accent-2); color: var(--accent-2); }

.ticker-when {
  color: var(--muted);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
  flex: none;
}

.ticker-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Happening now: the time is lit, with a dot that breathes. */
.is-live .ticker-when { color: var(--accent); }

.is-live .ticker-when::before {
  content: "";
  display: inline-block;
  width: 0.45em;
  height: 0.45em;
  margin-right: 0.4em;
  border-radius: 50%;
  background: var(--accent);
  vertical-align: 0.1em;
  animation: ticker-pulse 2s ease-in-out infinite;
}

@keyframes ticker-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.3; }
}

@media (prefers-reduced-motion: reduce) {
  .is-live .ticker-when::before { animation: none; }
}

/* On a narrow screen the ticker takes a row of its own beneath the rest of
   the header, and the sign-out gets its margin back. */
@media (max-width: 48rem) {
  .ticker { margin-left: 0; flex-basis: 100%; order: 1; }
  .ticker + .logout { margin-left: auto; }
  .ticker-items { flex-wrap: wrap; }
}

/* flex: 1 is what pushes the footer down; width still comes from the max-width
   and the auto margins, which keep working inside a column flex container.
   min-height: 0 lets it shrink below its content rather than overflowing. */
main {
  flex: 1;
  min-height: 0;
  width: 100%;
  max-width: 46rem;
  margin: 0 auto;
  padding: 2rem 1.5rem;
}

h1 { margin-top: 0; font-size: 1.6rem; }

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

.hero, .panel, .character-card {
  background: var(--panel-veil);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 1.75rem;
}

button, .primary-link {
  font: inherit;
  cursor: pointer;
  border: 1px solid var(--line);
  background: rgba(16, 48, 72, 0.55);
  color: var(--ink);
  padding: 0.6rem 1rem;
  border-radius: 6px;
  text-decoration: none;
  display: inline-block;
}

button.primary, .primary-link {
  background: var(--accent);
  color: #002b26;
  border-color: transparent;
  font-weight: 600;
}

button.primary:hover, .primary-link:hover { background: #6ffcec; }

button:hover { border-color: var(--accent); }

.notice {
  background: rgba(54, 158, 232, 0.12);
  border: 1px solid rgba(54, 158, 232, 0.45);
  border-radius: 6px;
  padding: 0.75rem 1rem;
  color: #cfe8fb;
}

.fineprint { color: var(--muted); font-size: 0.85rem; }
.fineprint a { color: var(--accent); }

/* Roster: a left-hand list of characters with the detail card revealed on
   hover, in the shape of a console side-nav.

   Entirely CSS. The site ships no JavaScript and the CSP has no script-src at
   all, so a scripted popover is not an option here -- which is fine, because
   :hover and :focus-within do the whole job. */
.roster-heading { margin: 0 0 1rem; font-size: 1.5rem; }

/* The dashboard is the one page that wants the whole width: its character list
   is a page-level rail, not part of the reading column.

   :has() rather than a class on <main>, because the core owns the layout and an
   app supplies only its own content -- an app cannot put a class on <main>
   without the platform growing an API for it, and one app is not a reason to
   grow one (Principle II, Principle VII). Where :has() is unsupported the page
   simply keeps the centred column it had before, which is a working layout
   rather than a broken one. */
main:has(.dashboard) { max-width: none; }

.dashboard {
  display: grid;
  grid-template-columns: minmax(13rem, 17rem) minmax(0, 1fr);
  gap: 2rem;
  align-items: start;
}

/* No characters, no rail, no empty first column. */
.dashboard.is-empty { grid-template-columns: minmax(0, 1fr); }


/* Everything that is prose stays at a readable measure even though main no
   longer constrains it. */
.dashboard-main { max-width: 46rem; }

/* Sticky, so the roster stays to hand while the roadmap scrolls past it --
   which is the whole point of a navigation pane. */
.character-nav { position: sticky; top: 1.5rem; }

/* A pinned rail whose LIST scrolls, with the heading and the note above it
   and the total below it staying put.

   Only once tooltip.js has marked it: a scrolling box clips anything
   positioned outside it, and the hover cards sit beside their rows, outside
   the rail. The script places them in viewport coordinates, where the rail
   cannot clip them, and only then adds this class. Without the script the
   rail is the tall sticky column it always was -- reachable, just not pinned.

   dvh, so the height follows mobile browser chrome; the 100vh line is the
   fallback where dvh is unsupported. */
.character-nav.is-scrollable {
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 3rem);
  max-height: calc(100dvh - 3rem);
}

.character-nav.is-scrollable > .character-list,
.character-nav.is-scrollable > .roster-groups {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  /* Reaching the end of the list must not start scrolling the page. */
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

/* Scrollbars come inside the box, so the last row's right edge and the
   frame's rounded corners are already accounted for by the border radius on
   the list; nothing to add. */

.character-list {
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--panel-veil);
  border: 1px solid var(--line);
  border-radius: 10px;
  /* The popover has to escape this box; never set overflow here. */
}

.character-row {
  position: relative;
  border-left: 3px solid transparent;
}

/* The row's whole face is one link to the character's panel, so a click
   anywhere on it -- not only on the name inside the card -- opens the
   character. The padding is on the link rather than the row for the same
   reason: no dead margin around the target. */
.row-link {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.7rem 0.9rem;
  color: inherit;
  text-decoration: none;
  outline: none;
}

.character-row + .character-row { border-top: 1px solid var(--line); }
.character-row:first-child { border-radius: 9px 9px 0 0; }
.character-row:last-child { border-radius: 0 0 9px 9px; }

/* is-current is on the row so the roster still says which character is the
   most recently played without needing the card open. */
.character-row.is-current { border-left-color: var(--accent-2); }

/* Which character the Armory panel is showing. Distinct from is-current: that
   one never moves, this one follows whichever name was last clicked. */
.character-row.is-selected {
  background: rgba(74, 249, 231, 0.1);
  border-left-color: var(--accent);
}
.character-row.is-selected .row-name { color: var(--cls-text, var(--accent)); }

.character-row:hover,
.character-row:focus,
.character-row:focus-within {
  background: rgba(54, 158, 232, 0.12);
  border-left-color: var(--accent);
  outline: none;
}

.row-name { font-weight: 600; }
.row-meta { color: var(--muted); font-size: 0.85rem; }

/* The card, floated to the right of the row it belongs to.

   Hidden with visibility and opacity rather than display:none so it can fade,
   and pointer-events:none so a card that is invisible can never intercept a
   click meant for the row underneath it. visibility is transitioned with a
   delay on the way out only, which is what stops it being focusable while
   invisible. */
.character-card.popover {
  position: absolute;
  top: -1px;
  left: calc(100% + 1.5rem);
  z-index: 5;
  /* Wide enough that a date and a raid's progress each sit on one line
     beside a capped label column; see the dl rule below. */
  width: 24rem;
  max-width: min(24rem, 70vw);
  /* More opaque than a normal panel: this one overlays other content, so the
     artwork behind it must not compete with the text on it. */
  background: rgba(6, 16, 25, 0.94);
  box-shadow: 0 18px 40px -16px #000;
  opacity: 0;
  visibility: hidden;
  transform: translateX(-0.4rem);
  pointer-events: none;
  transition: opacity 120ms ease, transform 120ms ease, visibility 0s linear 120ms;
}

.character-row:hover .popover,
.character-row:focus .popover,
.character-row:focus-within .popover {
  opacity: 1;
  visibility: visible;
  transform: translateX(0);
  transition-delay: 0s;
  /* Back on, or the link inside an open card could not be clicked -- the card
     would be visible and inert, which looks like a broken link rather than a
     disabled one. */
  pointer-events: auto;
}

/* A transparent bridge across the gap between row and card.

   Without it the pointer leaves the row on its way to the card, the card
   closes, and the link inside is unreachable by mouse -- the classic hover-menu
   failure. The card is a child of the row, so anything over this bridge still
   counts as hovering the row. */
.character-card.popover::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -1.5rem;
  width: 1.5rem;
}

@media (prefers-reduced-motion: reduce) {
  .character-card.popover { transition: none; transform: none; }
}

.roster-hint { color: var(--muted); font-size: 0.9rem; margin: 0.25rem 0 0; }

/* Guild roster: the same rail, grouped by rank.

   The heading is what makes the order legible -- a roster sorted by rank with
   no headings reads as an arbitrary order to anyone who does not already know
   the ranks. */
/* The roster is ONE panel with rank sections inside it, not a panel per rank.

   Five stacked boxes read as five lists; one box with headings in it reads as
   one roster, which is what it is -- and matches the character rail exactly. */
.roster-groups {
  background: var(--panel-veil);
  border: 1px solid var(--line);
  border-radius: 10px;
  /* The hover card has to escape; never set overflow here. */
}

/* Inside the panel the lists carry no chrome of their own. */
.roster-groups .character-list {
  background: none;
  border: 0;
  border-radius: 0;
}

/* ...and the rows stop rounding their corners, since they are no longer at the
   panel's edges -- only the first and last row in the whole panel are. */
.roster-groups .character-row:first-child,
.roster-groups .character-row:last-child { border-radius: 0; }

.rank-heading {
  margin: 0;
  padding: 0.75rem 0.9rem 0.4rem;
  border-top: 1px solid var(--line);
  font-size: 0.72rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--accent);
}

/* The first section heads the panel, so it needs no rule above it. */
.roster-groups > .rank-heading:first-child {
  border-top: 0;
  border-radius: 10px 10px 0 0;
}

.roster-note {
  margin: 0 0 0.75rem;
  font-size: 0.8rem;
  color: var(--muted);
}

/* The roster search: an input and a small go button, above the list. */
.roster-search {
  display: flex;
  gap: 0.4rem;
  margin: 0 0 0.75rem;
}

.roster-search input {
  flex: 1 1 auto;
  min-width: 0;
  font: inherit;
  font-size: 0.9rem;
  color: var(--ink);
  background: rgba(16, 48, 72, 0.35);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.45rem 0.65rem;
}

.roster-search input::placeholder { color: var(--muted); }

.roster-search input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(74, 249, 231, 0.2);
}

.roster-search button {
  flex: none;
  padding: 0.45rem 0.7rem;
  font-size: 0.9rem;
}

/* Off-screen, not display:none, so a screen reader still gets the label. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* Several members matched a search: a short list to pick from. */
.search-matches h2 { margin: 0; font-size: 1.25rem; }
.search-matches .fineprint { margin: 0.25rem 0 0; }
.search-matches code { color: var(--accent-2); }

.match-list {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

.match-list li { display: flex; flex-direction: column; }
.match-list a { font-weight: 600; text-decoration: none; }
.match-list a:hover,
.match-list a:focus { text-decoration: underline; }
.roster-note code {
  color: var(--accent-2);
  font-size: 0.95em;
}

/* Guild summary: what the roster adds up to, beside the roster itself. */
.guild-summary h2 { margin: 0; font-size: 1.5rem; }
.guild-summary .realm { color: var(--muted); margin-top: 0.25rem; }

.guild-stats {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.4rem 1.5rem;
  margin: 1.25rem 0 0;
}
.guild-stats dt { color: var(--muted); }
.guild-stats dd { margin: 0; font-variant-numeric: tabular-nums; }

.summary-heading {
  margin: 1.5rem 0 0.6rem;
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.count-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.3rem;
}

.count-list li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 3rem;
  gap: 0.75rem;
  font-size: 0.9rem;
}

.count-label { overflow-wrap: anywhere; }
.count-n {
  text-align: right;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.roster-total {
  margin: 0.9rem 0 0;
  font-size: 0.8rem;
  color: var(--muted);
}

/* Narrow screens have no room beside the list, and no hover either. The card
   drops inline beneath its row and opens on tap, which focus-within gives us
   for free. display, not opacity, so a closed card takes up no space. */
@media (max-width: 48rem) {
  .dashboard { grid-template-columns: 1fr; }
  .character-nav { position: static; }
  .roster-hint { display: none; }

  .character-card.popover {
    position: static;
    display: none;
    width: auto;
    max-width: none;
    margin-top: 0.6rem;
    opacity: 1;
    visibility: visible;
    transform: none;
    box-shadow: none;
  }

  .character-row:hover .popover,
  .character-row:focus .popover,
  .character-row:focus-within .popover { display: block; }
}

.character-card.is-current {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(74, 249, 231, 0.18), 0 0 28px -12px var(--accent);
}

.character-card .badge {
  margin: 0 0 0.5rem;
  color: var(--accent);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.character-card h2 { margin: 0; font-size: 1.25rem; }

.character-card h2 a {
  color: var(--cls-text, var(--ink));
  text-decoration: none;
  border-bottom: 1px solid transparent;
}
.character-card h2 a:hover,
.character-card h2 a:focus {
  color: var(--accent);
  border-bottom-color: var(--accent);
  outline: none;
}

.character-card .realm { color: var(--muted); margin-top: 0.25rem; }

.character-card dl,
.armory-detail dl {
  display: grid;
  /* The label column takes what it needs up to a cap, then wraps. It used to
     be max-content, which let one long raid name -- "Aberrus, the Shadowed
     Crucible" -- widen the column until the values beside it were squeezed
     into a sliver four lines tall, while a card with only short labels looked
     fine. A capped column keeps every card the same shape whatever it has to
     say; the cap is generous enough that only raid names ever reach it. */
  grid-template-columns: fit-content(10rem) minmax(0, 1fr);
  gap: 0.4rem 1rem;
  margin: 1rem 0 0;
}

.character-card dt,
.armory-detail dt { overflow-wrap: anywhere; }

.character-card dt,
.armory-detail dt { color: var(--muted); }

.character-card dd,
.armory-detail dd { margin: 0; font-variant-numeric: tabular-nums; }

/* The way back out of a member's Armory panel on the guild page.

   Only the guild roster renders this: on My Characters every row is already a
   way back, because the rail is the same list you came from. On the guild page
   the panel has replaced the guild summary, so without this there is no link
   that returns to it. */
.armory-back {
  margin: 0 0 1rem;
  font-size: 0.9rem;
}

.armory-back a {
  color: var(--muted);
  text-decoration: none;
}

.armory-back a:hover,
.armory-back a:focus {
  color: var(--accent);
  text-decoration: underline;
}

/* The Armory panel: one character, shown the way the Armory shows one.

   The image is Blizzard's own render of that character in its current gear,
   served from render.worldofwarcraft.com. No extracted game assets, no WebGL
   and no JavaScript -- the trade being that it is a still, not a model you can
   turn. */
.armory {
  display: grid;
  grid-template-columns: minmax(0, 17rem) minmax(0, 1fr);
  gap: 1.75rem;
  align-items: start;
  background: var(--panel-veil);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 1.75rem;
}

.armory-figure img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 8px;
}

.armory-norender {
  margin: 0;
  padding: 2rem 1rem;
  text-align: center;
  color: var(--muted);
  font-size: 0.9rem;
  border: 1px dashed var(--line);
  border-radius: 8px;
}

.armory-detail h2 { margin: 0; font-size: 1.7rem; }
.armory-detail .realm { color: var(--muted); margin-top: 0.3rem; }

.armory-detail .badge {
  margin: 0 0 0.5rem;
  color: var(--accent);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* One character stacks on a narrow screen; the render leads. */
@media (max-width: 48rem) {
  .armory { grid-template-columns: minmax(0, 1fr); }
  .armory-figure img { max-width: 16rem; margin: 0 auto; }
}

/* Equipped gear, in the game's own slot order. */
.gear-heading {
  margin: 1.5rem 0 0.6rem;
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.gear-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.3rem;
}

.gear-list li { font-size: 0.9rem; }

.gear-slot { color: var(--muted); }
.gear-name { overflow-wrap: anywhere; }
.gear-ilvl {
  text-align: right;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Item quality, the colours players read gear by.

   Six are the game's own values unchanged. Rare and Epic are lightened:
   at #0070dd and #a335ee they land near 3:1 on this background, which is
   below AA and exactly the two qualities most gear is. The hue and
   saturation are kept, so they still read as the blue one and the purple
   one -- only the lightness moves, and only as far as it must. */
.q-poor { color: #9d9d9d; }
.q-common { color: #ffffff; }
.q-uncommon { color: #1eff00; }
.q-rare { color: #3a9eff; } /* game #0070dd, lightened to 5.3:1 */
.q-epic { color: #c37cf4; } /* game #a335ee, lightened to 5.3:1 */
.q-legendary { color: #ff8000; }
.q-artifact { color: #e6cc80; }
.q-heirloom { color: #00ccff; }

/* Item tooltips.

   Opened by CSS alone -- :hover and :focus-within -- so they work with the
   script blocked. tooltip.js only improves WHERE the box lands and lights up
   the other pieces of a set; it adds .tip-open and inline coordinates, which
   the rules below defer to when present. */
.gear-item { position: relative; }

.gear-row {
  display: grid;
  /* icon, slot, name, gems, item level. The gem column is auto so it takes no
     space at all on the many items that have no sockets. */
  grid-template-columns: 1.75rem 6rem minmax(0, 1fr) auto 3rem;
  gap: 0.7rem;
  align-items: center;
  width: 100%;
  padding: 0.25rem 0.35rem;
  font: inherit;
  text-align: left;
  color: inherit;
  background: none;
  border: 1px solid transparent;
  border-radius: 6px;
  cursor: default;
}

.gear-row:hover,
.gear-row:focus-visible {
  background: rgba(54, 158, 232, 0.12);
  border-color: var(--line);
  outline: none;
}

.gear-icon {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 4px;
  display: block;
}
.gear-icon-blank { border: 1px solid var(--line); }

/* Gem sockets on the row. */
.gear-gems {
  display: flex;
  gap: 0.2rem;
  align-items: center;
}

.gem {
  width: 1rem;
  height: 1rem;
  border-radius: 3px;
  display: block;
}

/* An empty socket is drawn rather than left out: an unfilled socket is
   information, and on jewellery it is usually the thing worth noticing. */
.gem-empty {
  border: 1px dashed var(--muted);
  opacity: 0.7;
  background: transparent;
}

.tt-socket .gem {
  display: inline-block;
  vertical-align: -2px;
  margin-right: 0.3rem;
  width: 0.875rem;
  height: 0.875rem;
}

.tt-socket-type {
  color: var(--muted);
  font-size: 0.78rem;
  margin-left: 0.35rem;
}

/* The other pieces of the set currently being hovered. */
.gear-item.set-related .gear-row {
  background: rgba(74, 249, 231, 0.09);
  border-color: rgba(74, 249, 231, 0.35);
}

.item-tooltip {
  position: absolute;
  left: calc(100% + 0.75rem);
  top: 0;
  z-index: 20;
  width: 21rem;
  max-width: min(21rem, 80vw);
  padding: 0.85rem 1rem;
  /* Near-opaque: a tooltip overlays other text, and this one is dense. */
  background: rgba(4, 11, 18, 0.97);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 20px 44px -18px #000;
  font-size: 0.85rem;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 90ms ease, visibility 0s linear 90ms;
}

.gear-item:hover .item-tooltip,
.gear-item:focus-within .item-tooltip,
.gear-item.tip-open .item-tooltip {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

@media (prefers-reduced-motion: reduce) {
  .item-tooltip { transition: none; }
}

.item-tooltip p { margin: 0 0 0.15rem; }

.tt-name { font-weight: 600; font-size: 0.95rem; }
.tt-ilvl { color: var(--accent); }
.tt-transmog { color: var(--accent-2); }
.tt-stat { color: #6ee7a8; }
.tt-enchant { color: #6ee7a8; }
.tt-socket { color: var(--muted); }
.tt-socket.is-empty { font-style: italic; }
.tt-line { color: var(--ink); }
.tt-dim { color: var(--muted); }

.tt-split { display: flex; justify-content: space-between; gap: 1rem; }

.tt-set-name { margin-top: 0.6rem; color: var(--accent); }
.tt-set-piece { color: var(--ink); padding-left: 0.6rem; }
.tt-set-piece.is-missing { color: var(--muted); }
.tt-set-effect { margin-top: 0.4rem; color: #6ee7a8; }

@media (max-width: 48rem) {
  /* No room beside a row on a phone; drop it underneath instead. */
  .item-tooltip {
    position: static;
    width: auto;
    max-width: none;
    margin: 0.4rem 0 0.6rem;
    box-shadow: none;
    display: none;
    opacity: 1;
    visibility: visible;
  }
  .gear-item:hover .item-tooltip,
  .gear-item:focus-within .item-tooltip,
  .gear-item.tip-open .item-tooltip { display: block; }

  .gear-row { grid-template-columns: 1.75rem 4.5rem minmax(0, 1fr) auto 2.5rem; }
}

/* Roadmap: placeholder copy for things that do not exist yet. Styled plainly
   on purpose -- it should read as a list of intentions, not as navigation
   somebody will try to click. */
.roadmap { margin-top: 2rem; }
.roadmap h2 { margin-top: 0; font-size: 1.3rem; }

.roadmap-list {
  list-style: none;
  margin: 1.25rem 0 0;
  padding: 0;
  display: grid;
  gap: 1.1rem;
}

.roadmap-list li {
  border-left: 2px solid var(--line);
  padding-left: 1rem;
}

.roadmap-list h3 {
  margin: 0 0 0.2rem;
  font-size: 1rem;
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.roadmap-list p { margin: 0; color: var(--muted); }

.tag-ai {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #002b26;
  background: var(--accent);
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
}

.site-footer {
  background: var(--bar-veil);
  border-top: 1px solid var(--line);
  padding: 1.25rem 1.5rem;
  color: var(--muted);
  font-size: 0.8rem;
  text-align: center;
}


/* ---------------------------------------------------------------------------
   The guild overview: stat tiles, bar charts and leaderboards.

   Everything here is CSS over SVG attributes. The Content-Security-Policy
   allows no inline styles, and the site ships no charting script, so a bar's
   length is a rect's width attribute, the ring is a dasharray over a
   pathLength of 100, and the growth on load is a keyframe. Nothing below is
   load-bearing: with animation off the charts are simply already drawn.

   Marks follow the house rules for charts: thin bars capped well under 24px,
   4px rounded ends, a single hue for a single series, labels in ink rather
   than in the data colour, the value at the tip of each bar.
   ------------------------------------------------------------------------ */

/* Two columns -- the summary and the leaderboards -- which is why the main
   column drops its reading measure on this page. */
.dashboard-main--wide { max-width: none; }

.guild-overview {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(16rem, 0.85fr);
  gap: 1.5rem;
  align-items: start;
}

@media (max-width: 68rem) {
  .guild-overview { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Stat tiles ---------------------------------------------------------- */

.stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 2.25rem;
  margin: 1.25rem 0 0;
}

.stat { display: flex; flex-direction: column; }

.stat-label {
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Proportional figures, on purpose: tabular digits make a display-size number
   look loose. Tabular is reserved for the columns of numbers further down. */
.stat-value {
  font-size: 2.25rem;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.01em;
}

.stat-value small {
  font-size: 0.95rem;
  font-weight: 500;
  color: var(--accent);
  margin-left: 0.15rem;
}

/* The roles bar: the guild as tank, healer and DPS, in the stat row's
   remaining space. Pushed to the right of the row where there is room; wraps
   under the tiles where there is not. */
.stat-roles {
  margin-left: auto;
  min-width: 15rem;
  flex: 0 1 20rem;
  gap: 0.35rem;
}

.stat-roles .stat-label small {
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.72rem;
  margin-left: 0.3rem;
}

/* An svg is a replaced element, so border-radius with overflow hidden clips
   its content -- which is what rounds the ends of the stacked bar without a
   clip-path per slice. */
.role-bar {
  display: block;
  width: 100%;
  height: 12px;
  border-radius: 6px;
  overflow: hidden;
  background: rgba(16, 48, 72, 0.55);
}

.role-gap { fill: var(--panel); }

/* Role colours, as the game's group finder paints them: tank blue, healer
   green, DPS red. On marks and swatches only; the labels beside them are ink. */
.role-tank   { fill: #4f8fe6; background-color: #4f8fe6; }
.role-healer { fill: #4fd18a; background-color: #4fd18a; }
.role-dps    { fill: #e0575f; background-color: #e0575f; }

.role-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  font-size: 0.85rem;
}

.role-legend li { display: flex; align-items: center; gap: 0.35rem; white-space: nowrap; }
.role-legend b { font-variant-numeric: tabular-nums; }
.role-legend small { color: var(--muted); font-variant-numeric: tabular-nums; }

.role-swatch {
  display: inline-block;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 2px;
}

/* The ring meter beside the at-cap tile. */
.stat-ring {
  flex-direction: row;
  align-items: center;
  gap: 0.85rem;
}

.ring { width: 3.5rem; height: 3.5rem; flex: none; }

.ring-track,
.ring-fill {
  fill: none;
  stroke-width: 3.2;
}

.ring-track { stroke: var(--line); }

/* The arc starts at twelve o'clock -- the rotation -- and draws itself in.
   pathLength="100" on the circle is what lets the dasharray be a percentage. */
.ring-fill {
  stroke: var(--accent);
  stroke-linecap: round;
  transform: rotate(-90deg);
  transform-origin: 50% 50%;
  animation: ring-draw 900ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

@keyframes ring-draw {
  from { stroke-dasharray: 0 100; }
}

/* ---- Bar charts ---------------------------------------------------------- */

.bars {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.45rem;
}

.bar-row {
  display: grid;
  grid-template-columns: minmax(5.5rem, 8.5rem) minmax(0, 1fr) 2.5rem;
  /* A second row under the bar for the role breakdown, when there is one. */
  grid-template-areas:
    "label bar n"
    ".     roles roles";
  gap: 0.1rem 0.75rem;
  align-items: center;
  font-size: 0.9rem;
}

.bar-row .bar-label { grid-area: label; }
.bar-row .bar       { grid-area: bar; }
.bar-row .bar-n     { grid-area: n; }

/* Tank · Healer · DPS under a class bar: small, muted, the numbers in ink so
   they read as numbers and the role names recede to labels. */
.bar-roles {
  grid-area: roles;
  font-size: 0.75rem;
  color: var(--muted);
  line-height: 1.4;
  margin-bottom: 0.25rem;
}

.bar-role { white-space: nowrap; }
.bar-role-name { letter-spacing: 0.04em; text-transform: uppercase; font-size: 0.68rem; }
.bar-role { color: var(--ink); }
.bar-role .bar-role-name { color: var(--muted); }

.bar-label { overflow-wrap: anywhere; }

/* The svg is the track: full width, the rect inside it a percentage of it. */
.bar {
  display: block;
  width: 100%;
  height: 10px;
  border-radius: 5px;
  background: rgba(16, 48, 72, 0.55);
}

.bar-fill {
  fill: var(--cls, var(--accent));
  transform-origin: 0 50%;
  animation: bar-grow 700ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

@keyframes bar-grow {
  from { transform: scaleX(0); }
}

.bar-n {
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* Class colours, as the game paints them, in two roles.

   --cls is the canonical hue, for marks: bar fills and swatches, where
   contrast against the surface is not at stake.

   --cls-text is what a NAME is written in. Every character name on the site
   wears its class colour, the way the game does it, so the rail, the cards,
   the leaderboards and the Armory panel all agree with the class chart. It is
   the same hue except where the canonical one cannot clear 4.5:1 on the
   panel -- Death Knight, Demon Hunter and Shaman -- which are lightened just
   far enough to pass, exactly as the item quality colours were. Every
   --cls-text is held to that by TestClassTextColoursClearAA; do not add or
   change one without running it. */
.cls-death-knight { --cls: #c41e3a; --cls-text: #e14b62; }
.cls-demon-hunter { --cls: #a330c9; --cls-text: #c06adf; }
.cls-druid        { --cls: #ff7c0a; --cls-text: #ff7c0a; }
.cls-evoker       { --cls: #33937f; --cls-text: #33937f; }
.cls-hunter       { --cls: #aad372; --cls-text: #aad372; }
.cls-mage         { --cls: #3fc7eb; --cls-text: #3fc7eb; }
.cls-monk         { --cls: #00ff98; --cls-text: #00ff98; }
.cls-paladin      { --cls: #f48cba; --cls-text: #f48cba; }
.cls-priest       { --cls: #ffffff; --cls-text: #ffffff; }
.cls-rogue        { --cls: #fff468; --cls-text: #fff468; }
.cls-shaman       { --cls: #0070dd; --cls-text: #2f8cff; }
.cls-warlock      { --cls: #8788ee; --cls-text: #8788ee; }
.cls-warrior      { --cls: #c69b3a; --cls-text: #c69b3a; }

/* A name in its class's colour. Falls back to whatever the element would
   have been, so an unknown class reads as ordinary text rather than vanishing. */
.class-name { color: var(--cls-text, inherit); }

/* The authority mark before a guild master's or an officer's name: a gold
   crown, a silver shield. Sized to the text it sits beside, so it scales with
   a rail row and a card heading alike. Gold and silver are their own tokens,
   not class colours: the mark says standing, and the name beside it still
   says class. */
.rank-mark {
  width: 0.95em;
  height: 0.95em;
  margin-right: 0.3em;
  vertical-align: -0.1em;
  flex: none;
}

.rank-mark--gm      { fill: #f2c14e; }
.rank-mark--officer { fill: #b8c7d3; }

/* ---- Leaderboards -------------------------------------------------------- */

.leaderboards {
  display: grid;
  gap: 1rem;
}

.board { padding: 1rem 1.25rem; }
.board .summary-heading { margin-top: 0; }

.board-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.2rem;
}

.board-list li {
  position: relative;
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr) auto;
  gap: 0.55rem;
  align-items: center;
  padding: 0 0.5rem;
  font-size: 0.9rem;
  line-height: 1.7;
}

/* The bar behind each row, the same look as the class chart's bars but
   under the text rather than beside it. It is the row's whole height and
   faint, so a white Priest bar or a yellow Rogue one never swallows the
   name; the text is positioned so it paints over the bar. */
.board-bar {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.board-bar .bar-fill { opacity: 0.22; }

.board-list li > :not(.board-bar) { position: relative; }

.board-rank {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* The first place gets the accent; the rest read as the list they are. */
.board-list li:first-child .board-rank { color: var(--accent); font-weight: 600; }

.board-list a {
  color: var(--cls-text, var(--ink));
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.board-list a:hover,
.board-list a:focus { color: var(--accent); text-decoration: underline; }

.board-n {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .bar-fill,
  .ring-fill { animation: none; }
}

/* ---------------------------------------------------------------------------
   The Logs page: the audit trail as a table, for officers.
   ------------------------------------------------------------------------ */

.log-kinds {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 1rem 0;
}

.log-kind {
  font-size: 0.85rem;
  padding: 0.3rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--ink);
  text-decoration: none;
}

a.log-kind:hover,
a.log-kind:focus { border-color: var(--accent); }

.log-kind.is-selected {
  background: var(--accent);
  border-color: transparent;
  color: #002b26;
  font-weight: 600;
}

/* Tables may be wider than the page; the wrapper scrolls, the page does not. */
.log-scroll { overflow-x: auto; }

.log-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.88rem;
}

.log-table th,
.log-table td {
  text-align: left;
  vertical-align: top;
  padding: 0.45rem 0.6rem;
  border-bottom: 1px solid var(--line);
}

.log-table th {
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}

.log-when   { white-space: nowrap; color: var(--muted); font-variant-numeric: tabular-nums; }
.log-who    { white-space: nowrap; }
.log-action code { color: var(--accent-2); font-size: 0.85em; }
.log-detail { color: var(--muted); }

.log-older { margin: 1rem 0 0; }
.log-older a { color: var(--accent); }

/* ---------------------------------------------------------------------------
   The Calendar: the schedule by day, and the officers' event form.
   ------------------------------------------------------------------------ */

.event-form {
  margin: 1.25rem 0 1.5rem;
  padding: 1rem 1.25rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: rgba(16, 48, 72, 0.25);
  display: grid;
  gap: 0.75rem;
}

.form-row { display: grid; gap: 0.3rem; }
.form-row--split { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 0.75rem; }
.form-row--split > div { display: grid; gap: 0.3rem; }

.form-row label,
.form-label {
  font-size: 0.78rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.event-form input,
.event-form select,
.event-form textarea {
  font: inherit;
  color: var(--ink);
  background: rgba(16, 48, 72, 0.35);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0.5rem 0.65rem;
  min-width: 0;
}

.event-form input:focus,
.event-form select:focus,
.event-form textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px rgba(74, 249, 231, 0.2);
}

/* datetime-local's picker icon is drawn by the browser in its own colour;
   invert it so it shows on the dark field. */
.event-form input[type="datetime-local"]::-webkit-calendar-picker-indicator,
.event-form input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(0.8); }
.event-form select option { background: var(--panel); color: var(--ink); }

/* The days a series falls on: a row of ticks, in plain case unlike the
   field labels above them. */
.weekdays { display: flex; flex-wrap: wrap; gap: 0.4rem 1rem; }
.weekdays .weekday {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.9rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
}
.weekdays input[type="checkbox"] { accent-color: var(--accent); margin: 0; }

.form-actions { display: flex; align-items: center; gap: 1rem; }
.form-cancel { color: var(--muted); }
.form-error { margin: 0; }

.cal-day { margin-top: 1.5rem; }
.cal-day-label {
  margin: 0 0 0.5rem;
  font-size: 0.85rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

.cal-events { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.5rem; }

.cal-event {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr) auto;
  gap: 0.75rem;
  align-items: start;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: rgba(16, 48, 72, 0.2);
}

.cal-time { font-variant-numeric: tabular-nums; color: var(--ink); white-space: nowrap; }
.cal-title { margin: 0; font-size: 1.05rem; }
.cal-where { margin: 0.15rem 0 0; color: var(--muted); font-size: 0.9rem; }
.cal-repeats { margin: 0.15rem 0 0; color: var(--accent-2); font-size: 0.85rem; }
.cal-notes { margin: 0.35rem 0 0; font-size: 0.9rem; white-space: pre-line; }

.cal-actions { display: flex; align-items: center; gap: 0.5rem; }
.cal-edit { color: var(--accent); font-size: 0.9rem; }
.cal-delete, .cal-skip { margin: 0; }
.cal-delete button, .cal-skip button { padding: 0.3rem 0.6rem; font-size: 0.85rem; }

@media (max-width: 40rem) {
  .cal-event { grid-template-columns: minmax(0, 1fr); }
}

.calendar-empty { margin-top: 1rem; }

/* ---------------------------------------------------------------------------
   Combat logs (spec 003): the upload page, an upload's fights, and the two
   panels the comparison adds to the character card. */

.combatlogs h1 { margin-top: 0; }

.upload-form {
  display: grid;
  gap: 0.75rem;
  margin: 1rem 0 1.5rem;
  padding: 1rem;
  border: 1px solid var(--line);
  border-radius: 0.5rem;
  background: var(--panel-veil);
}

.upload-form input[type="file"] { color: var(--ink); max-width: 100%; }

.upload-form progress {
  width: 100%;
  height: 0.6rem;
  accent-color: var(--accent);
}

.upload-status { margin: 0; color: var(--muted); font-size: 0.9rem; }
.upload-status.is-error { color: #f08a8a; }

.uploads {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}

.upload-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  gap: 0.75rem;
  align-items: center;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 0.4rem;
  background: var(--panel-veil);
}

.upload-row a { color: var(--ink); text-decoration: none; }
.upload-row a:hover { color: var(--accent-2); }
.upload-meta { color: var(--muted); font-size: 0.85rem; }

.upload-state {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.upload-state.is-parsed { color: var(--accent); }
.upload-state.is-failed { color: #f08a8a; }
.upload-state.is-busy { color: var(--accent-2); }

.upload-remove button {
  background: none;
  border: 1px solid var(--line);
  color: var(--muted);
  border-radius: 999px;
  padding: 0.2rem 0.7rem;
  cursor: pointer;
  font: inherit;
  font-size: 0.8rem;
}
.upload-remove button:hover { border-color: #f08a8a; color: #f08a8a; }

.fight { margin-top: 1.25rem; }

.fight-head {
  display: flex;
  gap: 0.75rem;
  align-items: baseline;
  flex-wrap: wrap;
}
.fight-head h2 { margin: 0; font-size: 1.05rem; }
.fight-result {
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.fight-result.is-kill { color: var(--accent); }

.fight-summary {
  margin: 0.5rem 0 0;
  padding: 0.6rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 0.4rem;
  background: var(--panel-veil);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
  gap: 0.4rem 1rem;
  font-size: 0.9rem;
}
.fight-summary .stat-label {
  display: block;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.fight-summary .stat-value { font-variant-numeric: tabular-nums; }
.fight-casts { grid-column: 1 / -1; color: var(--muted); font-size: 0.85rem; }

/* The comparison (spec 003, fifth amendment): the controls under the
   render inside the Armory panel; the result under the card at the full
   width -- the table and the talent difference on the left, the write-up on
   the right. */
.armory-aside { margin-top: 1.25rem; }
.card-analysis { padding: 1rem 0 0; border-top: 1px solid var(--line); min-width: 0; }
.analysis-results { margin-top: 1.25rem; padding: 1.25rem 1.5rem; min-width: 0; }
.analysis-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 1.5rem;
}
.analysis-left, .analysis-right { min-width: 0; }
.talent-diff-heading { margin: 1rem 0 0.25rem; font-size: 0.9rem; color: var(--accent); }
@media (max-width: 48rem) {
  .analysis-grid { grid-template-columns: minmax(0, 1fr); }
}
.card-analysis h3,
.analysis-results h3 {
  margin: 0 0 0.6rem;
  font-size: 0.8rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.analysis-meta { margin: 0 0 0.6rem; color: var(--muted); font-size: 0.85rem; }

.analyse-form { display: grid; gap: 0.5rem; margin-bottom: 1rem; }
.analyse-form select,
.analyse-form input[type="url"] {
  width: 100%;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 0.3rem;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
}

.upgrade-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}
.upgrade-table th,
.upgrade-table td {
  padding: 0.35rem 0.5rem;
  text-align: left;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.upgrade-table th {
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.upgrade-table .lvl { font-variant-numeric: tabular-nums; color: var(--muted); white-space: nowrap; }
.verdict-same { color: var(--muted); }
.verdict-holds { color: var(--accent); }
.verdict-chase { color: #f5c26b; }

.talent-diff { font-size: 0.85rem; }
.talent-diff dt { color: var(--muted); font-size: 0.7rem; letter-spacing: 0.06em; text-transform: uppercase; margin-top: 0.5rem; }
.talent-diff dd { margin: 0.2rem 0 0; }

/* The review: the model's Markdown as the card renders it (writeup.go).
   A document, so headings have weight, tables have rules, and lists have
   their marks. */
.writeup { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--line); max-width: 72rem; }
.writeup p { margin: 0 0 0.8rem; line-height: 1.6; max-width: 72ch; }
.writeup h4 { margin: 1.6rem 0 0.5rem; padding-bottom: 0.3rem; font-size: 1.05rem; color: var(--accent); border-bottom: 1px solid var(--line); }
.writeup h4:first-child { margin-top: 0; }
.writeup h5 { margin: 1rem 0 0.4rem; font-size: 0.9rem; color: var(--ink); }
.writeup ul, .writeup ol { margin: 0 0 0.9rem; padding-left: 1.4rem; max-width: 72ch; }
.writeup li { margin: 0.25rem 0; line-height: 1.5; }
.writeup code { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 0.85em; background: rgba(16, 48, 72, 0.55); padding: 0.1em 0.35em; border-radius: 4px; overflow-wrap: anywhere; }
.writeup-scroll { overflow-x: auto; margin: 0.5rem 0 1rem; }
.writeup-table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
.writeup-table td:first-child, .writeup-table th:first-child { white-space: nowrap; }
.writeup-table th, .writeup-table td { padding: 0.35rem 0.6rem; text-align: left; border-bottom: 1px solid var(--line); vertical-align: top; }
.writeup-table th { font-size: 0.7rem; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.do-first li { margin: 0.4rem 0; font-weight: 600; }
.verify-data { color: var(--accent); }
.verify-inferred { color: #f5c26b; }

@media (max-width: 48rem) {
  .upload-row { grid-template-columns: minmax(0, 1fr); }
}

/* Talents on the character card (spec 003, FR-033): the build, grouped the
   way the game groups it, with a line saying where it came from. */
.talents { margin-top: 1.25rem; }
.talents-source { margin: 0 0 0.5rem; color: var(--muted); font-size: 0.85rem; }
.talent-groups {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
  gap: 0.75rem;
  margin: 0;
}
.talent-groups dt {
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.talent-groups dd { margin: 0.25rem 0 0; font-size: 0.85rem; line-height: 1.5; }
.talent-groups .rank { color: var(--muted); font-size: 0.75rem; }


/* ---------------------------------------------------------------------------
   The front door (spec 004): what an anonymous visitor sees at "/".

   Wider than the reading column, like the dashboard, and for the same reason:
   the top lists sit beside the officers, not under them. Everything in a
   panel, because muted text never sits bare on the artwork (see the palette
   note at the top of this file).
   ------------------------------------------------------------------------ */

main:has(.welcome) { max-width: 64rem; }

.welcome { display: grid; gap: 1.25rem; }

.welcome-hero {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(15rem, 2fr);
  gap: 1.75rem;
  align-items: start;
}

.welcome-hero h1 { font-size: 2rem; }

.hero-actions {
  display: grid;
  gap: 0.75rem;
  align-content: start;
}

.hero-actions form { margin: 0; }
.hero-actions button.primary, .hero-actions .primary-link { width: 100%; text-align: center; }

/* The Discord link is the second door, and reads as one: the same shape as
   the sign-in button, on the panel's own surface rather than the accent. */
.discord-link {
  background: rgba(16, 48, 72, 0.55);
  color: var(--ink);
  border-color: var(--line);
}
.discord-link:hover { background: rgba(16, 48, 72, 0.85); border-color: var(--accent); }

.welcome-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
  gap: 1.25rem;
  align-items: start;
}

.welcome .summary-heading { margin-top: 0; }

.officer-list {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: grid;
  gap: 0.6rem;
}

.officer-list li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.1rem 0.75rem;
  align-items: baseline;
}

.officer-list .class-name { font-weight: 600; }

.officer-rank {
  color: var(--accent);
  font-size: 0.85rem;
  white-space: nowrap;
}

.officer-meta {
  grid-column: 1 / -1;
  color: var(--muted);
  font-size: 0.85rem;
}

.welcome-stat { margin: 0.5rem 0 0; }
.welcome-stat b { color: var(--accent); font-size: 1.4rem; }
.welcome-stat small { color: var(--muted); }

/* A board row's name when it is not a link: the same colour and clipping the
   guild page gives the linked one. */
.board-name {
  color: var(--cls-text, var(--ink));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.welcome .leaderboards .board { padding: 0.5rem 0 0; }

.cares h3 { font-size: 1rem; margin-bottom: 0.4rem; }
.cares ul { margin: 0 0 1rem; padding-left: 1.2rem; }
.cares li { margin: 0.2rem 0; }

.cares-columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
  gap: 1.5rem;
}

.cares-motto {
  color: var(--accent);
  font-size: 1.35rem;
  font-weight: 600;
  margin: 0.25rem 0;
}

.cares .notice a { color: #cfe8fb; }

@media (max-width: 44rem) {
  .welcome-hero { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------------------
   Analysing (spec 003, sixth amendment): the card shows work in progress.

   A pulsing mark and one line of patter at a time, cycling in CSS: every
   phrase sits in the same place and takes its turn through the keyframes,
   staggered by animation-delay. Twelve phrases, three seconds each. Under
   reduced motion the first phrase stands still with the mark.
   ------------------------------------------------------------------------ */

.analysing {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.75rem 0 0;
  padding: 0.6rem 0.8rem;
  background: rgba(54, 158, 232, 0.12);
  border: 1px solid rgba(54, 158, 232, 0.45);
  border-radius: 6px;
  color: #cfe8fb;
  font-size: 0.9rem;
}

.analysing-orb {
  flex: none;
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 0 rgba(74, 249, 231, 0.6);
  animation: analysing-pulse 1.6s ease-out infinite;
}

@keyframes analysing-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(74, 249, 231, 0.6); }
  70%  { box-shadow: 0 0 0 0.6rem rgba(74, 249, 231, 0); }
  100% { box-shadow: 0 0 0 0 rgba(74, 249, 231, 0); }
}

.analysing-words {
  position: relative;
  display: block;
  flex: 1;
  min-width: 0;
  height: 1.5em;
  overflow: hidden;
}

.analysing-phrase {
  position: absolute;
  inset: 0 0 auto 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  transform: translateY(0.5em);
  animation: analysing-cycle 36s linear infinite;
}

.analysing-phrase:nth-child(1)  { animation-delay: 0s; }
.analysing-phrase:nth-child(2)  { animation-delay: 3s; }
.analysing-phrase:nth-child(3)  { animation-delay: 6s; }
.analysing-phrase:nth-child(4)  { animation-delay: 9s; }
.analysing-phrase:nth-child(5)  { animation-delay: 12s; }
.analysing-phrase:nth-child(6)  { animation-delay: 15s; }
.analysing-phrase:nth-child(7)  { animation-delay: 18s; }
.analysing-phrase:nth-child(8)  { animation-delay: 21s; }
.analysing-phrase:nth-child(9)  { animation-delay: 24s; }
.analysing-phrase:nth-child(10) { animation-delay: 27s; }
.analysing-phrase:nth-child(11) { animation-delay: 30s; }
.analysing-phrase:nth-child(12) { animation-delay: 33s; }

/* One twelfth of the cycle each: in, hold, out, then wait for the rest. */
@keyframes analysing-cycle {
  0%    { opacity: 0; transform: translateY(0.5em); }
  1.5%  { opacity: 1; transform: translateY(0); }
  7%    { opacity: 1; transform: translateY(0); }
  8.3%  { opacity: 0; transform: translateY(-0.5em); }
  100%  { opacity: 0; transform: translateY(-0.5em); }
}

@media (prefers-reduced-motion: reduce) {
  .analysing-orb { animation: none; }
  .analysing-phrase { animation: none; }
  .analysing-phrase:first-child { opacity: 1; transform: none; }
}

/* The assistant (spec 006): a launcher pinned to the corner of every page,
   a panel over the page when open, a sheet from the bottom on a phone. The
   details element is laid out column-reverse so the panel opens above the
   launcher, where the eye already is. */
.assistant {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 20;
  max-width: calc(100vw - 2.5rem);
  display: flex;
  flex-direction: column-reverse;
  align-items: flex-end;
}
.assistant-launcher {
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 1rem;
  border: 1px solid var(--accent);
  border-radius: 999px;
  background: rgba(6, 16, 25, 0.94);
  color: var(--accent);
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 18px 40px -16px #000;
}
.assistant-launcher::-webkit-details-marker { display: none; }
.assistant-launcher::marker { content: ""; }
.assistant-launcher:hover,
.assistant-launcher:focus-visible { background: rgba(54, 158, 232, 0.18); outline: none; }
.assistant-launcher-mark {
  display: inline-grid;
  place-items: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 50%;
  background: var(--accent);
  color: var(--bg);
  font-weight: 700;
}
.assistant-panel {
  width: 26rem;
  max-width: 100%;
  max-height: min(70vh, 40rem);
  display: flex;
  flex-direction: column;
  margin-bottom: 0.75rem;
  background: rgba(6, 16, 25, 0.96);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 18px 40px -16px #000;
}
.assistant-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 0.8rem 1rem;
  border-bottom: 1px solid var(--line);
}
.assistant-head h2 { margin: 0; font-size: 1rem; color: var(--accent); }
.assistant-head a { font-size: 0.8rem; color: var(--accent-2); }
.assistant-body { overflow-y: auto; padding: 0.5rem 1rem; flex: 1 1 auto; min-height: 8rem; }
.assistant-thread { list-style: none; margin: 0; padding: 0; }
.exchange { padding: 0.6rem 0; border-bottom: 1px solid var(--line); }
.exchange-q { margin: 0 0 0.4rem; font-weight: 600; }
.exchange-a { font-size: 0.92rem; }
.exchange-a p { margin: 0 0 0.6rem; line-height: 1.5; }
.exchange-a h4, .exchange-a h5 { margin: 0.8rem 0 0.3rem; font-size: 0.95rem; color: var(--accent); }
.exchange-a ul, .exchange-a ol { margin: 0 0 0.6rem; padding-left: 1.2rem; }
.exchange-a li { margin: 0.2rem 0; }
.exchange-a .writeup-scroll { margin: 0.4rem 0 0.8rem; }
.exchange-sources {
  margin: 0.2rem 0 0;
  font-size: 0.78rem;
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}
.exchange-sources a { color: var(--accent-2); }
.assistant-empty { color: var(--muted); font-size: 0.9rem; }
.assistant-empty p { margin: 0.4rem 0; }
.assistant-empty ul { margin: 0.3rem 0; padding-left: 1.2rem; }
.assistant-ask { display: flex; flex-direction: column; gap: 0.5rem; padding: 0.6rem 1rem 0; }
.assistant-ask textarea {
  width: 100%;
  resize: vertical;
  min-height: 3rem;
  background: rgba(0, 3, 7, 0.6);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.5rem 0.7rem;
  font: inherit;
}
.assistant-ask textarea:focus { border-color: var(--accent); outline: none; }
.assistant-actions { display: flex; align-items: center; gap: 0.75rem; }
.assistant-wait { margin: 0; color: var(--muted); font-size: 0.85rem; }
.assistant-notice { margin: 0.5rem 1rem 0; }
.assistant-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 0.5rem 1rem 0.8rem;
}
.assistant-foot .fineprint { margin: 0; font-size: 0.72rem; flex: 1 1 14rem; }
.assistant-new button { font-size: 0.8rem; }
.assistant-page { max-width: 48rem; }
.assistant-page .assistant-body { padding: 0; min-height: 0; }
.assistant-page .assistant-ask, .assistant-page .assistant-notice, .assistant-page .assistant-foot { padding-left: 0; padding-right: 0; margin-left: 0; margin-right: 0; }
@media (max-width: 48rem) {
  .assistant { right: 0; bottom: 0; left: 0; max-width: none; align-items: stretch; padding: 0 0.75rem 0.75rem; }
  .assistant-launcher { align-self: flex-end; }
  .assistant-panel { width: auto; max-height: 75vh; }
}

/* The War Room (spec 007): the front page's tables and the review, boss by
   boss, in the review card's dress. */
.warroom .panel + .panel { margin-top: 1.25rem; }
.warroom-reports form { margin: 0; }
.warroom-reports button { font-size: 0.85rem; }
.warroom .muted { color: var(--muted); }
.warroom-code { margin-top: 1rem; }
.warroom-code label { display: block; margin-bottom: 0.4rem; color: var(--muted); font-size: 0.85rem; }
.warroom-code-row { display: flex; gap: 0.6rem; flex-wrap: wrap; }
.warroom-code-row input {
  flex: 1 1 20rem;
  min-width: 0;
  background: rgba(0, 3, 7, 0.6);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.5rem 0.7rem;
  font: inherit;
}
.warroom-code-row input:focus { border-color: var(--accent); outline: none; }
.warroom-review .writeup { max-width: none; }
.warroom-boss { margin-top: 2rem; padding-top: 1rem; border-top: 2px solid var(--line); }
.warroom-boss-name { margin: 0 0 0.6rem; font-size: 1.3rem; color: var(--accent); }
.warroom-boss-name small { display: block; margin-top: 0.2rem; font-size: 0.8rem; font-weight: 400; color: var(--muted); letter-spacing: 0.04em; }
.warroom-sides { display: grid; grid-template-columns: max-content 1fr; gap: 0.25rem 1rem; margin: 0 0 0.8rem; font-size: 0.9rem; }
.warroom-sides dt { color: var(--muted); font-size: 0.72rem; letter-spacing: 0.06em; text-transform: uppercase; align-self: baseline; }
.warroom-sides dd { margin: 0; }
.warroom-facts { margin: 0 0 1rem; padding-left: 1.2rem; font-size: 0.9rem; color: var(--ink); }
.warroom-facts li { margin: 0.25rem 0; }
