/* magnoon.nl — shared page chrome: the site rail, page header, page footer.
   Used by every app so a visitor reads them as one site rather than four
   independently styled tools. Depends on the custom properties declared in
   site/tokens.css (--panel, --edge, --edge-soft, --ink, --faint, --muted,
   --accent, --accent-ink, --hit), so it must be linked after that stylesheet.
   The self-contained loot-filter workbench has no external stylesheet, so it
   inlines this file verbatim at assemble time (apps/loot-filter/assemble.mjs)
   the same way it inlines the tokens.css palette — edit only here. */

/* Screen-reader-only text: present in the accessibility tree, absent visually.
   Used for labels whose control already reads as obvious on screen. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- site rail ----
   Every tool, in the bar, on every page. This replaced a breadcrumb
   ("magnoon.nl / Prism Crafting") that told a visitor where they already were
   and nothing about what else exists: reaching a sibling tool meant scrolling to
   the footer cross-links, which a visitor who arrived from a search result has
   no reason to suspect are there. Those cross-links stay — they are what a
   scriptless phone falls back to, and the loot-filter page is long enough that
   the footer is a real second chance.

   The tab labels are the widest thing in the bar, so below 860px they stop
   fitting a row and the rail becomes a push-down drawer: one line, and the list
   opens *below* it, shoving the page down rather than floating over it. Nothing
   is ever covered — which is what a fixed or absolutely positioned menu would
   cost here, since the first control on most pages sits directly under this bar.
   860px is the width the whirlwind calculator already folds its columns at;
   reuse it rather than introducing a second nearby number.

   The row carried four tabs when 860px was chosen and now carries five ("Builds",
   the shortest label of the set). That is the number to re-check if a sixth is
   ever added — the labels wrap before the breakpoint fires, so the failure is a
   two-line bar on a desktop rather than anything a test catches.

   Progressive enhancement, and in this direction deliberately: with no script
   the tab list is simply visible — a row on a desktop, a stack on a phone — and
   apps/shared/topnav.js marks the nav `data-nav="ready"`, which is what reveals
   the toggle and permits the collapsed state at all. Collapsing by default and
   relying on script to undo it would leave a scriptless phone with no site
   navigation above the fold. */
/* The lit top edge and the shadow under the bar are what make the rail read as a
   strip laid on the page rather than a band cut out of it — the same reason the
   ground now sits well below --panel rather than beside it. Literal rgb() rather
   than tokens: these are light and shade on whatever surface they fall on, not
   two more colours the palette has to name. */
.topnav {
  border-bottom: 1px solid var(--edge);
  background: var(--panel);
  box-shadow:
    inset 0 1px 0 rgb(220 231 243 / 6%),
    0 1px 0 rgb(0 0 0 / 40%);
}
/* The bar's background is full-bleed (.topnav above), but its contents sit on
   the same 1680px measure and the same 20px gutter as the page below, so the
   wordmark lines up with the page heading and the tabs with the content. Do not
   widen this to the window: it was tried, and the tabs stranded on the left of a
   wide monitor read as unaligned with everything under them.

   The rail states its own font and line-height rather than inheriting the
   page's. The four apps set four different body typographies (14px/1.35 on the
   workbench, 16px/1.5 on the other three), and every child here already fixes
   its font-size — so the only thing that inheritance still carried in was the
   line-height, which moved the brand's glyphs 0.13px between pages and made the
   wordmark appear to twitch when a visitor moved from one tool to the next.
   Shared chrome must not depend on which app it happens to be sitting on. */
.rail {
  max-width: var(--page-width);
  margin: 0 auto;
  padding: 0 20px;
  display: flex;
  align-items: stretch;
  gap: 0 22px;
  min-height: 46px;
  font-family: "Segoe UI", system-ui, sans-serif;
  line-height: 1.4;
}
/* The brand is the logo image, not a wordmark set in text, so nothing here
   describes type any more — what sizes it is the image's own height. 30px in a
   46px rail is the ratio the logo set states (art-sources/README.md), and both
   attributes are set in the markup so the rail does not reflow once the SVG
   decodes. */
.rail-brand {
  flex: none;
  display: flex;
  align-items: center;
  text-decoration: none;
  transition: opacity 0.15s ease;
}
.rail-brand img {
  display: block;
  height: 30px;
  width: auto;
}
.rail-brand:hover {
  opacity: 0.82;
}
.rail-brand:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.rail-tabs {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: stretch;
  gap: 2px;
}
/* The <li> has to be a flex container in its own right. As a stretched flex item
   it is already full-height, but a block-level <a> inside it is only as tall as
   its line box, so every tab's label sat at the top of the rail while the brand
   beside it was centred — a misalignment that reads as sloppy at a glance and is
   invisible in the markup. */
.rail-tabs li {
  display: flex;
}
.rail-tabs a {
  display: flex;
  align-items: center;
  gap: 7px;
  white-space: nowrap;
  padding: 0 12px;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px; /* the active underline covers the nav's own bottom border */
  color: var(--muted);
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
}
.rail-tabs a:hover {
  color: var(--ink);
  background: var(--hit);
}
.rail-tabs a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.rail-tabs a[aria-current="page"] {
  color: var(--accent-ink);
  border-bottom-color: var(--accent);
  background: linear-gradient(to bottom, transparent 40%, rgb(29 58 82 / 55%));
}
/* Full names on the phone, where the list is vertical and has the room; the
   desktop row keeps the short form, which is what lets four tabs fit one line.
   One label per app either way — apps/shared/site-links.js holds both halves.
   The whole label is wrapped in .rail-label so it is a single flex item: the
   tail's leading space is inline text inside that item, and would be dropped as
   flex-item leading whitespace if the tail sat directly in the <a>. */
.rail-tail {
  display: none;
}
/* ---- the alpha marker ----
   Three places draw it — the Whirlwind tab in the rail, that page's own <h1>,
   and the homepage's tool card — so the pill itself is here and each place
   states only its size. It was a rail-only rule (`.rail-tabs .alpha-tag`) with
   a second full copy in the calculator's stylesheet, which meant the homepage
   card, styled by neither, rendered the word "Alpha" as bare serif text beside
   the card title. Nobody chose that; it is what a shared object with no shared
   rule looks like.

   The red is --bad from site/tokens.css. It used to be #ef4444 written out
   here, in site/index.html and in the calculator's own :root — three
   definitions of one colour, and the homepage's copy had already been edited
   on its own.

   The calculator's own rule wins where both load, because it comes later at the
   same specificity, and it needs to: its marker sits beside a clamp()ed heading
   and must size against it (0.42em) rather than take a fixed step. */
.alpha-tag {
  display: inline-block;
  margin: 0;
  padding: 0.1em 0.5em;
  border-radius: var(--radius-pill);
  background: var(--bad);
  color: var(--on-bad);
  font-family: "Segoe UI", system-ui, sans-serif;
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.7;
  text-transform: uppercase;
  vertical-align: baseline;
}
/* Smaller in the rail, where it rides a 12px tab rather than a card title.
   More specific than the calculator's, so the tab keeps this size on that page
   too — the marker must read the same on every page's rail. */
.rail-tabs .alpha-tag {
  font-size: var(--fs-3xs);
}

/* The drawer's control. Hidden until topnav.js says the collapse it opens is
   actually available, and never shown above the breakpoint. */
.rail-toggle {
  display: none;
  margin-left: auto;
  align-items: center;
  gap: 9px;
  padding: 5px 10px;
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-sm);
  background: var(--ground);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-2xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
}
.rail-toggle:hover {
  border-color: var(--accent);
  color: var(--accent-ink);
}
.rail-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.rail-bars {
  display: grid;
  gap: 3px;
  flex: none;
}
.rail-bars i {
  display: block;
  width: 13px;
  height: 1.5px;
  border-radius: 1px;
  background: var(--accent);
}

@media (max-width: 860px) {
  .rail {
    flex-wrap: wrap;
    align-items: center;
    gap: 0 12px;
    padding: 0 14px;
  }
  /* The bar's own row is a flex line here, and a wrapped line is only as tall as
     its tallest item — so with the drawer open, the line shrank to the toggle's
     28px and the wordmark jumped up by 9px at the moment of opening. Pinning the
     brand to the bar's height fixes the line at 46px either way. */
  .rail-brand {
    min-height: 46px;
  }
  .topnav[data-nav="ready"] .rail-toggle {
    display: flex;
  }
  /* The one rule the whole drawer turns on. It depends on the toggle being the
     immediate previous sibling of the list in the markup — keep them adjacent. */
  .topnav[data-nav="ready"] .rail-toggle[aria-expanded="false"] + .rail-tabs {
    display: none;
  }
  .rail-tabs {
    width: 100%; /* forces the wrap, so the list opens on its own row */
    flex-direction: column;
    gap: 0;
    padding: 5px 0 7px;
    border-top: 1px solid var(--edge-soft);
  }
  .rail-tabs li {
    display: block;
  }
  .rail-tabs li + li {
    border-top: 1px solid var(--edge-soft);
  }
  .rail-tabs a {
    min-height: 46px; /* a touch target, not a 19px line of text */
    padding: 0 10px;
    border-bottom: 0;
    margin-bottom: 0;
    font-size: var(--fs-sm);
    letter-spacing: 0.04em;
    text-transform: none;
    white-space: normal;
  }
  .rail-tabs a[aria-current="page"] {
    background: var(--hit);
    box-shadow: inset 3px 0 0 var(--accent);
  }
  .rail-tabs .alpha-tag {
    font-size: var(--fs-3xs);
  }
  .rail-tail {
    display: inline;
  }
}
@media (prefers-reduced-motion: reduce) {
  .rail-brand {
    transition: none;
  }
}

/* ---- page header ---- */
/* The title row. The Ko-fi button sits at the far right, centred on the title
   rather than pinned to the top of the header: pinned to the top it clears the
   heading's tall line box and reads as a separate line above it.
   nowrap plus a shrinkable h1 is what guarantees it stays on the title's row at
   every width — the heading wraps within itself instead of pushing the button
   onto a line of its own. (The whirlwind calculator replaces this whole block
   in its own stylesheet; it has a third header child to place.) */
.pagehead {
  padding: 24px 0 14px;
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 20px;
}
.pagehead .kofi {
  flex: none;
  margin-left: auto;
}
/* Outfit at 600, not Cinzel at 500: the inscriptional face read as costume next
   to what these pages actually are. A geometric sans needs its own tracking —
   0.035em was opening Cinzel's capitals, and on Outfit's lowercase it reads as a
   gap, so the step down to 0.01em is part of the same choice, not a stray value. */
.pagehead h1 {
  font-family: Outfit, "Segoe UI", system-ui, sans-serif;
  font-weight: 600;
  font-size: var(--fs-hero);
  letter-spacing: 0.01em;
  margin: 0;
  color: var(--ink);
  min-width: 0; /* wrap the heading rather than widen the row past the button */
}
/* The masthead rule the loot-filter workbench already draws under its own
   header.top, for the pages whose class picker follows the title directly.
   Opt-in rather than on .pagehead itself: a page whose title is followed by
   prose rather than a control row gets a rule cutting that block in half. */
.pagehead-ruled {
  border-bottom: 1px solid var(--edge);
}

/* ---- page footer / build stamp ---- */
.pagefoot {
  margin-top: 32px;
  padding-top: 14px;
  border-top: 1px solid var(--edge);
}
.buildstamp {
  margin: 0;
  color: var(--faint);
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.buildstamp code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: var(--fs-xs);
  padding: 1px 5px;
  border: 1px solid var(--edge-soft);
  border-radius: var(--radius-sm);
  background: rgb(9 13 19 / 50%);
  color: var(--muted);
}

/* ---- Ko-fi support button, top-right of the page header ----
   Ko-fi ships the button at a brighter blue than this dark palette carries
   (--accent is #5c9fd4); the filter knocks it back so it reads as part of the
   page rather than as pasted-in third-party artwork. Hover restores the
   original artwork, which doubles as the affordance.

   contrast() is doing real work here, not decoration: brightness() alone dims
   the white label along with the blue, and by the time the background is dark
   enough to sit in this palette the text has gone grey. Raising contrast pushes
   the label back to white while the blue keeps falling. Re-tune the two
   together — dropping brightness on its own costs legibility. */
.kofi {
  display: inline-block;
  border-radius: var(--radius-md);
  line-height: 0; /* no descender gap under the image */
  filter: brightness(0.72) saturate(0.8) contrast(1.3);
  transition: filter 120ms ease-out;
}
.kofi img {
  display: block;
  height: 36px;
  width: auto;
}
.kofi:hover,
.kofi:focus-visible {
  filter: none;
}
.kofi:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .kofi {
    transition: none;
  }
}
/* On a phone the full-size button takes too much of the title's row; the
   heading keeps priority and the button steps down rather than wrapping. */
@media (max-width: 560px) {
  .pagehead .kofi img {
    height: 30px;
  }
}

/* ---- picker rows ----
   One labelled row of choices: <div class="btnrow"><span class="rowlabel">Class</span>
   …buttons…</div>. Prism Crafting is the reference layout — the label sits in a
   fixed 52px column so Class, Item and Quality line up down the left edge and
   the buttons start on one vertical rule.

   Shared rather than per-app because all three picker pages draw the same row:
   prism-crafting and transfigure-checklist had byte-identical copies of these
   two rules in their own stylesheets, and the loot filter's class bar had a
   .rowlabel with no width at all, so its label ran into the "All" button while
   the other two pages aligned. */
.btnrow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.btnrow[hidden] {
  display: none;
}
.rowlabel {
  flex: none;
  width: 52px;
  color: var(--faint);
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
/* Below this the label column costs more than it aligns: the buttons wrap onto
   their own lines anyway, so the label takes a line of its own and the row
   reads as a heading over its choices. */
@media (max-width: 480px) {
  .rowlabel {
    width: 100%;
  }
}

/* ---- class pickers ----
   Shared class identity treatment. Apps add .class-bar to the .btnrow containing
   their class buttons; other button rows keep the neutral .cbtn appearance. */
.class-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.class-bar[hidden] {
  display: none;
}
/* --fs-base, not --fs-sm. These buttons were 13.5px before the scale landed,
   and --fs-sm (13px) rounded them down — the one place the migration's
   round-to-nearest was visible enough to notice. The picker is a primary
   control on three pages, so it rounds up. */
.cbtn {
  font: inherit;
  font-size: var(--fs-base);
  /* Stated, not inherited. `font: inherit` also takes the page's line-height,
     and the loot-filter workbench sets a tighter body (14px/1.35) than the two
     reference pages (16px/1.5) — which made its class buttons 30.9px tall
     against their 33px, so one row of the same buttons sat 2px off the other
     two. The box is the picker's, not the host page's prose. */
  line-height: 1.5;
  padding: 5px 11px;
  cursor: pointer;
  border-radius: var(--radius-sm);
  background: var(--panel);
  color: var(--muted);
  border: 1px solid var(--edge-soft);
}
.cbtn:hover {
  color: var(--ink);
}
.cbtn.active {
  background: var(--hit);
  color: var(--accent-ink);
  border-color: var(--accent);
}
.cbtn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}
.cbtn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* Class accents follow the dominant identity colours in Diablo IV class
   artwork. Idle tint stays restrained; selection brings the colour forward
   without relying on it alone. */
.class-bar .cbtn[data-class] {
  color: color-mix(in srgb, var(--class-color) 72%, var(--ink));
  border-color: color-mix(in srgb, var(--class-color) 48%, var(--edge-soft));
  background: color-mix(in srgb, var(--class-color) 8%, var(--panel));
}
.class-bar .cbtn[data-class]:hover {
  color: var(--class-color);
  border-color: color-mix(in srgb, var(--class-color) 72%, var(--edge));
  background: color-mix(in srgb, var(--class-color) 14%, var(--panel));
}
.class-bar .cbtn[data-class].active {
  color: #111820;
  border-color: var(--class-color);
  background: var(--class-color);
  box-shadow:
    inset 0 0 0 1px rgb(0 0 0 / 28%),
    0 0 0 2px color-mix(in srgb, var(--class-color) 55%, transparent),
    0 3px 8px color-mix(in srgb, var(--class-color) 30%, transparent);
}
.class-bar .cbtn[data-class]:focus-visible {
  outline-color: var(--class-color);
}
.class-bar [data-class="barbarian"] {
  --class-color: #dc6b59;
}
.class-bar [data-class="druid"] {
  --class-color: #c3944d;
}
.class-bar [data-class="necromancer"] {
  --class-color: #6fb5a5;
}
.class-bar [data-class="paladin"] {
  --class-color: #dfb957;
}
.class-bar [data-class="rogue"] {
  --class-color: #d66b78;
}
.class-bar [data-class="sorcerer"] {
  --class-color: #70abe0;
}
.class-bar [data-class="spiritborn"] {
  --class-color: #87b961;
}
.class-bar [data-class="warlock"] {
  --class-color: #b67bc0;
}
