/* ── Tokens ──────────────────────────────────────────
   One place for the palette. The site had five accent hues added where each was needed — gold,
   blue, red, orange, violet — with nothing saying which meant what. These are the whole set now,
   and every one of them has a job.

   Dark only, by design: there is no light theme and nothing here is arranged to allow one. The
   greys are warm rather than neutral, because the game's blacks are torchlit and a pure #111 reads
   as an admin panel rather than a dungeon. */
:root {
    --ink: #121011;
    --surface: #1A1719;
    --surface-lift: #221E21;
    --line: #2E292C;

    --text: #D9D5D2;
    --text-dim: #8C8783;
    --text-faint: #6A6663;

    /* The identity accent, and the game's own. */
    --gold: #C8A84B;
    --gold-lit: #E6D190;
    --gold-shade: #261F00;

    /* Tier semantics. These three are the only other hues on the site, and they mean exactly one
       thing each — the difficulty band a piece of content belongs to. */
    --heroic: #C8A84B;
    --epic: #7FA9D0;
    --legendary: #B08BD8;

    --display: Georgia, "Iowan Old Style", "Times New Roman", serif;
}

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--ink); }
body {
    font-family: Verdana, Geneva, Tahoma, sans-serif;
    font-size: 0.95rem;
    color: var(--text);
    padding: 20px;
    padding-top: 0;
    padding-bottom: 24px;
}

/* Two type roles across the whole site: a serif for the things that name something, the sans for
   everything that measures something. Headings were all Verdana bold at three sizes, which is what
   made every page read as a form rather than a reference. */
h1, h2, h3, h4 {
    font-family: var(--display);
    font-weight: normal;
    letter-spacing: 0.01em;
    color: var(--gold-lit);
}
/* The house link, so a new one is never left on browser blue against a dark page. Every stylesheet
   that dresses links by their surroundings loads after this one and overrides it. */
a {
    color: var(--gold);
    text-decoration: none;
}
a:hover { text-decoration: underline; }

/* The browser's default focus ring is a thin dark outline, which on a near-black page is invisible.
   Keyboard users get a gold one instead; mouse users get nothing, as before. */
a:focus-visible,
button:focus-visible,
select:focus-visible,
input:focus-visible,
summary:focus-visible {
    outline: 2px solid #c8a84b;
    outline-offset: 2px;
    border-radius: 2px;
}

/* The one inverted strip on the site: black on gold, above everything, so a reader meets it
   before they meet any number they might trust. Full bleed like the header below it. */
.site-beta {
    margin: 0 -20px;
    background: var(--gold);
    color: var(--ink);
    padding: 7px 20px;
    font-size: 0.78rem;
    letter-spacing: 0.01em;
    text-align: center;
}
.site-beta strong { font-weight: bold; }

.site-header {
    margin: 0 -20px 0 -20px;
    background: #171416;
    border-bottom: 1px solid var(--line);
    padding: 0 20px;
    display: flex;
    align-items: stretch;
    gap: 24px;
}
.site-title {
    font-size: 1.1rem;
    font-weight: bold;
    color: #c8a84b;
    text-decoration: none;
    padding: 12px 0;
    white-space: nowrap;
    flex-shrink: 0;
}
.site-title:hover { color: var(--gold-lit); }
.site-nav {
    flex: 1;
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: 0;
}
.site-nav a {
    display: flex;
    align-items: center;
    padding: 0 14px;
    color: #999;
    text-decoration: none;
    font-size: 0.82rem;
    white-space: nowrap;
    border-bottom: 2px solid transparent;
}
/* Before hover and active, which it ties with on specificity, so both still win over it. */
.site-nav a.site-nav-about { margin-left: auto; color: var(--text-faint); }
.site-nav a:hover { color: #ddd; background: #1e1e1e; }
.site-nav a.active { color: #fff; border-bottom-color: #c8a84b; }
.effect-desc { display: inline; color: #999; font-size: 0.85em; }
.set-bonus-line { color: #999; font-size: 0.85em; padding-bottom: 2px; }
/* Two places only: the item page, and the examination panel wherever it appears — beside the
   table on that page, or as the hover tip over a list. The class pages and the browse lists use
   .effect-desc for their own prose, and that prose is the whole reason a reader is on them. */
body.hide-descs .item-detail .effect-desc,
body.hide-descs .item-detail .set-bonus-line,
body.hide-descs .panel-container .effect-desc,
body.hide-descs .panel-container .panel-main-set-bonus-line { display: none; }
/* Controls tucked into a corner rather than a bar across the foot of the page: they set how the
   page is read, they are not part of what it says. */
footer.site-footer {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 1000;
    display: flex;
    gap: 8px;
}

footer.site-footer button {
    background: #1a1a1a;
    border: 1px solid #444;
    border-radius: 4px;
    color: #999;
    font-family: inherit;
    font-size: 0.76rem;
    padding: 7px 12px;
    cursor: pointer;
    opacity: 0.5;
}

footer.site-footer button:hover {
    opacity: 1;
    color: #ddd;
    border-color: #6a6a6a;
}


/* An effect the item might roll, rather than one it has.
   Set in from the certainties and headed with the count, because "1 of these" changes what the
   list underneath means and a reader who misses that reads twenty effects the item does not have. */
.effect-choice { margin: 8px 0 4px; }

.effect-choice-head {
    font-size: 0.78rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #8ab4d0;
    margin-bottom: 2px;
}

.effect-choice-list {
    border-left: 2px solid #2f3a44;
    padding-left: 10px;
    margin-left: 2px;
}

body.hide-debug .debug-only { display: none; }

/* The right-hand column exists only to carry a debug panel, so hiding the panel has to hand its
   width back — otherwise the preview shows a gap that no visitor will ever see. */
body.hide-debug .quest-content .quest-left-column { flex: 1 1 100%; }
.page-wrapper { display: flex; flex-direction: column; gap: 20px; }
.top-row { display: flex; gap: 30px; align-items: flex-start; }
.item-table-wrapper { flex: 1; min-width: 300px; max-width: 50%; }
.mock-side { flex-shrink: 0; }

/* The 3D preview sits above the examination panel and matches its width, so the column keeps one
   edge. The stage stays dark whatever the page theme: the model is lit for a dark backdrop, and a
   light one would change how every colour on it reads. */
.item-preview3d {
    width: 473px;
    max-width: 100%;
    margin-bottom: 14px;
    border: 1px solid #2f3a44;
    background: #0f1114;
}
.item-preview3d-stage {
    position: relative;
    height: 360px;
    cursor: grab;
    touch-action: none;
}
.item-preview3d-stage:active { cursor: grabbing; }
.item-preview3d-stage canvas { display: block; width: 100%; height: 100%; }
.item-preview3d-stage canvas:focus-visible { outline: 2px solid #c8a84b; outline-offset: -2px; }
.item-preview3d-full {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 1;
    display: flex;
    padding: 5px;
    background: rgba(27, 33, 40, 0.8);
    color: #d6d8d8;
    border: 1px solid #3a4652;
    border-radius: 3px;
    cursor: pointer;
}
.item-preview3d-full:hover,
.item-preview3d-full:focus-visible { border-color: #c8a84b; color: #c8a84b; }

/* Directly under full screen, same corner and same shape: the two of them act on the view rather
   than on what is drawn, so they read as a pair. 8px top + 28px button + 6px gap puts it clear of
   the one above without measuring anything at runtime. */
.item-preview3d-reset {
    position: absolute;
    top: 42px;
    right: 8px;
    z-index: 1;
    display: flex;
    padding: 5px;
    background: rgba(27, 33, 40, 0.8);
    color: #d6d8d8;
    border: 1px solid #3a4652;
    border-radius: 3px;
    cursor: pointer;
}
.item-preview3d-reset:hover,
.item-preview3d-reset:focus-visible { border-color: #c8a84b; color: #c8a84b; }
/* The appearance panel, shared with the Studio: the character-creation controls for a body whose
   face is composited. Everything here is the panel's own look; where it sits is set by whoever
   holds it - floating over the stage on an item page, in the column on the Studio. */
.appearance-button {
    background: #1b2128;
    color: #d6d8d8;
    border: 1px solid #3a4652;
    border-radius: 3px;
    padding: 3px 10px;
    font-size: 0.8rem;
    cursor: pointer;
}
.appearance-button:hover,
.appearance-button[aria-expanded="true"] { border-color: #c8a84b; }
.appearance-panel {
    padding: 8px;
    background: rgba(15, 17, 20, 0.92);
    border: 1px solid #3a4652;
    border-radius: 3px;
    color: #d6d8d8;
    font-size: 0.8rem;
    cursor: default;
}
.appearance-panel[hidden] { display: none; }
/* On an item page it floats over the stage's right side, scrolling when the frame is short. */
.item-preview3d .appearance-panel {
    position: absolute;
    top: 40px;
    right: 8px;
    bottom: 8px;
    z-index: 1;
    width: 206px;
    overflow-y: auto;
}
.appearance-actions { display: flex; gap: 6px; margin-bottom: 8px; }
.appearance-actions button,
.appearance-panel select {
    background: #1b2128;
    color: #d6d8d8;
    border: 1px solid #3a4652;
    border-radius: 3px;
    padding: 2px 8px;
    font-size: 0.8rem;
}
.appearance-actions button { flex: 1; cursor: pointer; }
.appearance-actions button:hover { border-color: #c8a84b; }
/* Label, then the control. The label column is minmax(0, 1fr) rather than auto so it gives way -
   ellipsised - instead of pushing the control about; a long label would otherwise move the arrows
   on its own row and nobody else's. */
.appearance-row {
    display: grid;
    grid-template-columns: minmax(48px, 1fr) auto;
    align-items: center;
    gap: 8px;
    margin-bottom: 6px;
}
.appearance-row > :first-child {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* The class sets a display, so it has to take hidden back itself. */
.appearance-row[hidden] { display: none; }
/* Three fixed columns, so both arrows sit on the same two vertical lines on every row of the
   sidebar - the Character section's and the Appearance section's alike - however wide the reading
   between them happens to be.

   Flex with a min-width on the reading did not do this. The row right-aligned the stepper, so the
   RIGHT arrow lined up and the left one drifted by however much the reading exceeded the minimum:
   measured at 41px across a typical race ("1 / 5" against "21 / 21") and 143px once the Character
   section's name-bearing rows were counted.

   The width is the widest reading that can occur. That is not a number of digits: the Body row
   reads "Composite Plating (1/3)", which measures 183px, against 64px for the widest "200 / 200".
   The other two body feats - Adamantine Body, Mithral Body - are shorter, so 190px covers every
   reading with a little slack and nothing is ever truncated.

   It is a maximum rather than a fixed size because the same panel is drawn in two places. The
   item page's is narrower than the Studio's, and at a flat 190px the stepper hung 62px past the
   right edge of its own row. Letting the track shrink costs no alignment: every row in a panel has
   the same width to divide, so they all land on the same two lines whatever that width is. */
.appearance-stepper {
    --reading: 190px;
    display: grid;
    grid-template-columns: 22px minmax(0, var(--reading)) 22px;
    align-items: center;
    gap: 4px;
}
/* The item page's panel is a 188px column beside the model rather than the Studio's 358px sidebar,
   and it never shows a name-bearing reading - it has no Gender or Body row, only "n / m", whose
   widest is 57px. Sized at the Studio's 190px the labels lost the argument and came out as
   "Base ...", "Crani...".

   At 188px it cannot hold every label whole - "Facial Detail 2" alone is 91px - so the tighter
   gaps here are to clear the common ones rather than all of them: they buy 6px, which is what
   takes "Base Color" from one pixel short to fitting. The rest ellipsis, which is the right answer
   in a column this narrow and is what the label column is set up to do. */
.item-preview3d .appearance-row { gap: 6px; }
.item-preview3d .appearance-stepper { --reading: 58px; gap: 3px; }
.appearance-stepper button {
    width: 22px;
    height: 20px;
    padding: 0;
    background: #1b2128;
    color: #d6d8d8;
    border: 1px solid #3a4652;
    border-radius: 3px;
    line-height: 1;
    cursor: pointer;
}
.appearance-stepper button:hover { border-color: #c8a84b; }
.appearance-stepper output {
    text-align: center;
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.appearance-colours {
    margin: 0 0 8px;
    padding: 0;
    border: 0;
}
.appearance-colours legend { padding: 0 0 4px; }
/* The swatches scroll under the legend rather than with it, but only when there is really more
   than fits. Every palette in the game lays out in ten rows or fewer once its own column count is
   used, so the cap is ten rows and nothing scrolls: at five, a Dwarf's forty-nine skin tones grew
   a scrollbar with empty panel below it. */
.appearance-swatches {
    display: grid;
    grid-template-columns: repeat(10, 1fr);
    /* No gap: the swatches sit against each other and their own 1px border is the only thing
       between two colours. A gap here was two hundred cells' worth of panel spent on nothing, and
       these palettes are colour fields to pick a point in rather than buttons to aim at. */
    gap: 0;
    /* Room for the selected swatch's ring, so the scroll container does not clip it at the edges. */
    padding: 2px;
    max-height: 240px;
    overflow-y: auto;
    overscroll-behavior: contain;
}
.appearance-colours button {
    aspect-ratio: 1;
    min-width: 0;
    padding: 0;
    /* Only on two sides, and shared with the neighbour's, so the line between two cells stays one
       pixel wide instead of doubling. The grid's own edge loses its outer line, which is what the
       fieldset is for. */
    border: 0;
    border-right: 1px solid #0f1114;
    border-bottom: 1px solid #0f1114;
    cursor: pointer;
}
/* Raised, because with the cells touching an outline would otherwise be half covered by whichever
   neighbour the grid paints next. A cell owns only its right and bottom borders, so the ring goes
   outside to cover the neighbours' lines and the cell drops its own two. */
.appearance-colours button[aria-pressed="true"] {
    position: relative;
    z-index: 1;
    border-color: transparent;
    outline: 2px solid #c8a84b;
    outline-offset: 0;
}
.item-preview3d-help {
    display: none;
    position: absolute;
    top: 8px;
    left: 10px;
    right: 48px;
    margin: 0;
    color: #888;
    font-size: 0.8rem;
    pointer-events: none;
}

/* Full screen fills the browser window rather than the display: the page stays underneath, and Esc
   or the corner button puts the frame back where it was. */
.item-preview3d.is-full {
    position: fixed;
    inset: 0;
    /* Over everything on the page, the fixed site footer (1000) included. */
    z-index: 1100;
    width: auto;
    max-width: none;
    margin: 0;
    display: flex;
    flex-direction: column;
}
.item-preview3d.is-full .item-preview3d-stage { flex: 1; height: auto; min-height: 0; }
.item-preview3d.is-full .item-preview3d-help { display: block; }
body.item-preview3d-open { overflow: hidden; }
.item-preview3d-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 6px 10px;
    border-top: 1px solid #2f3a44;
    font-size: 0.8rem;
}
.item-preview3d-spin {
    background: #1b2128;
    color: #d6d8d8;
    border: 1px solid #3a4652;
    border-radius: 3px;
    padding: 3px 10px;
    font-size: 0.8rem;
    cursor: pointer;
}
.item-preview3d-spin:hover { border-color: #c8a84b; }
.item-preview3d-hint { color: #888; }
.item-preview3d-race {
    background: #1b2128;
    color: #d6d8d8;
    border: 1px solid #3a4652;
    border-radius: 3px;
    padding: 2px 6px;
    font-size: 0.8rem;
    max-width: 200px;
}
.item-preview3d .visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}
/* The second row: the studio links, then the STL download at the far end, with no border between
   the rows. A link for a hand the weapon is already in is marked rather than repeated. */
.item-preview3d-links { border-top: 0; padding-top: 0; gap: 10px; }
.item-preview3d-studio.is-worn { color: #8fae6a; }
.item-preview3d-stl { margin-left: auto; }
.bottom-section { width: 100%; }
.item-table { border-collapse: collapse; width: 100%; margin-bottom: 12px; }
.item-table th {
    text-align: left; color: #fff; padding: 4px 8px;
    border-bottom: 1px solid #555; font-size: 0.85rem;
}
.item-table td { padding: 3px 8px; vertical-align: top; border-bottom: 1px solid #222; font-size: 0.88rem; }
.item-name-header { margin: 0; padding: 20px 0; font-size: 1.5rem; color: #e0c97a; font-weight: bold; text-align: center; }
.fn { color: #888; width: 160px; white-space: nowrap; }
.fv { color: #d6d8d8; }
.sec-hdr { font-weight: bold; color: #c8a84b; margin-top: 10px; margin-bottom: 4px; font-size: 0.9rem; }
.elist { list-style: none; margin: 0 0 8px 0; padding: 0; }
.elist li { padding: 2px 4px; font-size: 0.9rem; }
.elist li::before { content: '\25B6'; color: #c8a84b; margin-right: 6px; font-size: 0.6em; vertical-align: middle; }

/* ── Ingredient page ─────────────────────────────────── */
.ingredient-page { max-width: 900px; }
.ingredient-header { display: flex; align-items: center; gap: 14px; padding-top: 16px; }
.ingredient-icon { width: 48px; height: 48px; flex-shrink: 0; }
.ingredient-name { margin: 0; font-size: 1.4rem; color: #e0c97a; font-weight: bold; }
.ingredient-kind { color: #888; font-size: 0.85rem; margin-top: 2px; }

/* ── Home ────────────────────────────────────────────
   The site's index. Cards rather than a list because the note under each name is the point: a
   visitor who has not used this before needs to know what "Enhancements" will show them. */
.home { max-width: 1040px; margin: 0 auto; padding: 12px 0 40px; }

.home-masthead {
    padding: 34px 0 26px;
    border-bottom: 1px solid var(--line);
    margin-bottom: 30px;
}

.home-masthead h1 {
    margin: 0;
    font-size: clamp(2rem, 4vw, 2.9rem);
    color: var(--gold-lit);
}

.home-lede {
    margin: 14px 0 0;
    max-width: 60ch;
    color: var(--text-dim);
    line-height: 1.6;
}

.home-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 12px;
}

.home-card {
    display: block;
    padding: 16px 18px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 4px;
    text-decoration: none;
    /* The left edge lights up on hover rather than the whole card: a large block changing colour
       under the pointer is a lot of movement for a link. */
    border-left: 2px solid var(--line);
}

.home-card:hover {
    background: var(--surface-lift);
    border-left-color: var(--gold);
    text-decoration: none;
}

.home-card-name {
    display: block;
    font-family: var(--display);
    font-size: 1.15rem;
    color: var(--gold-lit);
    margin-bottom: 5px;
}

.home-card-note {
    display: block;
    font-size: 0.8rem;
    line-height: 1.5;
    color: var(--text-faint);
}

.home-card:hover .home-card-note { color: var(--text-dim); }
.ingredient-empty { color: var(--text-faint); font-style: italic; font-size: 0.88rem; }

/* ── Found In ────────────────────────────────────────
   Where an item has been seen to drop. Deliberately quiet: it is a short table on most items and
   absent on nearly all of them, because it only ever reports collected observations. */
.source-table { border-collapse: collapse; font-size: 0.85rem; font-variant-numeric: tabular-nums; margin-bottom: 8px; }
.source-table th {
    text-align: right;
    padding: 3px 12px 5px 0;
    font-size: 0.66rem;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    font-weight: normal;
    color: var(--text-dim);
    border-bottom: 1px solid var(--line);
}
.source-table th:first-child, .source-table th:nth-child(2) { text-align: left; }
.source-table td { padding: 4px 12px 4px 0; border-bottom: 1px solid #1C1A1C; text-align: right; }
.source-table td:first-child, .source-table td:nth-child(2) { text-align: left; }
/* Quest and chest names are both prose and both left-aligned, so the 12px that separates the
   numeric columns is not enough to read them as two columns. */
.source-table th:first-child, .source-table td:first-child { padding-right: 28px; }
.source-track { color: var(--text-faint); font-size: 0.78rem; }
.source-note { margin: 6px 0 0; font-size: 0.76rem; line-height: 1.5; color: var(--text-faint); font-style: italic; max-width: 62ch; }
.source-note-inline { color: var(--text-faint); font-style: italic; }

/* ── Recipes ─────────────────────────────────────────────
 *
 * A recipe is a direction: these things become that one. It was drawn as a two-column table with a
 * hairline between the columns, which is the right structure and the wrong picture — a 1px rule the
 * colour of the border did not read as anything, so the result looked like a sixth ingredient that
 * had wandered off to the right.
 *
 * So the row is a card, the direction is a chevron rather than a rule, and the result is framed in
 * the accent. Nothing else on the item page uses gold for an object, so the framed tile is the one
 * thing on the row the eye lands on, which is also the answer to the question that brought you.
 */

/* The device, with its own icon. A player recognises an altar by its picture before its name, and
   the icon is also what makes the heading read as a place rather than as a label on the card. */
.recipe-device {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    font-size: 0.9rem;
    font-weight: 600;
    margin-bottom: 8px;
    text-decoration: none;
}
.recipe-device-icon {
    width: 30px;
    height: 30px;
    background: var(--ink);
    border: 1px solid var(--line);
    border-radius: 5px;
}
.recipe-device:hover .recipe-device-icon { border-color: var(--text-faint); }

/* The same pairing one size up, for the device's own page. */
.device-heading { display: flex; align-items: center; gap: 14px; margin: 0 0 18px; }
.device-heading h2 { margin: 0; }
.device-icon {
    width: 52px;
    height: 52px;
    background: var(--ink);
    border: 1px solid var(--line);
    border-radius: 6px;
}
.device-count {
    font-size: 0.7rem;
    letter-spacing: 0.13em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-top: 3px;
}

/* Two content columns with the chevron between them, and a label row above that shares their
   tracks — so the labels sit over what they name however wide the ingredients run. */
.recipe-card {
    display: grid;
    /* Every track sized to its content, and justify-content: start so the slack collects at the
       right instead of being handed to the ingredients.
       The ingredients column was minmax(0, 1fr), which took all of it — on an item page with no
       description that stretched the column the width of the page and left the result pinned
       against the far edge, a screen away from the things it is made of. */
    grid-template-columns: auto auto auto;
    grid-template-rows: auto auto;
    justify-content: start;
    /* Hung from the top rather than centred. The two sides of a recipe rarely have the same height —
       a two-line ingredient caption beside three one-line results — and centring them floated the
       icons to different heights on either side of the chevron, so nothing in the row shared a line.
       From the top, every icon starts where every other icon starts. */
    align-items: start;
    /* As wide as what it holds, not as wide as the column it sits in. A grid is a block, so a trade
       of one thing for one thing drew its border from the left margin to the contents list with the
       whole right half of the box empty — a card the size of the page containing two icons. */
    width: fit-content;
    max-width: 100%;
    gap: 4px 14px;
    padding: 9px 12px;
    margin-bottom: 8px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 7px;
}
.recipe-card:last-child { margin-bottom: 0; }

/* The item page's recipes, flowed across the width instead of down it. Cards are as wide as their
   contents, so a page of one-for-one trades fits four or five to a row and a card with a product
   description takes a row to itself — the wrapping decides, not a rule about kinds of card.
 *
 * Not on a device's own page: there the cards are held to one width so the Created column reads
 * straight down, and a wrapping row has no column to read.
 *
 * The gap replaces the cards' own bottom margin; two spacings would show as a wider gutter between
 * rows than within them. */
.recipe-cards {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 8px;
}
.recipe-cards .recipe-card { margin-bottom: 0; }

.recipe-card-label {
    grid-row: 1;
    font-size: 0.6rem;
    line-height: 1.1;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text-faint);
}
.recipe-card-label-in { grid-column: 1; }
.recipe-card-label-out { grid-column: 3; }
.recipe-card-in { grid-row: 2; grid-column: 1; }
/* Level with the icons it points between, not with the middle of the tallest column: the tiles are
   an icon above a caption that may run to three lines, and a chevron centred on all of that ends up
   under the pictures rather than between them. */
.recipe-card-arrow { grid-row: 2; grid-column: 2; display: flex; align-items: center; margin-top: 13px; }
.recipe-card-out { grid-row: 2; grid-column: 3; }

/* A fourth column for what the item says about itself. On a device with a hundred and fifty recipes
   the description is often the only thing that tells two of them apart, and there is room for it —
   the ingredients rarely fill their track. Held to a readable measure rather than allowed to run the
   width of the window, and separated by a rule so it reads as commentary on the result rather than
   as another column of the recipe. */
/* A page of recipes reads down the Created column, so that column has to start in the same place on
   every row. Each card is its own grid and cannot share track sizes with its neighbours, so the
   ingredients track is given the width of the largest possible one instead: five tiles and four
   gaps, five being the most any recipe in the data has.
 *
 * Only where cards are listed together. A single card on an item page has nothing to line up with,
 * and holding the track open there would strand the chevron a long way from a lone ingredient. */
/* A fallback only. Each section sets this inline from the widest ingredient row it actually
   renders — see _RecipeUses.cshtml — because the widest a recipe could be is five tiles and most
   sections are nowhere near that. */
.recipe-list { --recipe-in-w: calc(5 * 96px + 4 * 14px); }
.recipe-list .recipe-card { grid-template-columns: var(--recipe-in-w) auto auto; }
.recipe-list .recipe-card-described {
    grid-template-columns: var(--recipe-in-w) auto auto minmax(0, 1fr);
}

/* With a description there is something that wants the slack, so it takes the fourth track and the
   grid goes back to filling its width. */
.recipe-card-described {
    grid-template-columns: auto auto auto minmax(0, 1fr);
    justify-content: normal;
}
.recipe-card-desc {
    grid-row: 2;
    grid-column: 4;
    max-width: 54ch;
    padding-left: 18px;
    border-left: 1px solid var(--line);
    font-size: 0.78rem;
    line-height: 1.5;
    color: var(--text-dim);
    font-style: italic;
}

/* Two borders rotated into a chevron: crisper at this size than a glyph, and it takes the accent
   without depending on a font having the character. */
.recipe-card-arrow::after {
    content: "";
    width: 10px;
    height: 10px;
    border-top: 2px solid var(--gold);
    border-right: 2px solid var(--gold);
    transform: rotate(45deg);
    opacity: 0.5;
}

/* Wraps rather than scrolls: a recipe is a handful of ingredients, and one that needs a dozen reads
   better as two rows than as a strip running off the side. */
/* One tile, one width, wherever the tile appears. An item page shows the same materials twice —
 * spent in the recipe that makes this item, returned by the recipe that breaks it down — and a
 * result tile twice the width of an ingredient tile made those two rows read as different kinds of
 * thing. Every card is now in + in + in > out at one size, and the chevron is what says which side
 * you are on. */
.recipe-ingredients { --tile-w: 96px; display: flex; flex-wrap: wrap; gap: 14px; margin: 0; padding: 0; }
.recipe-ingredient { position: relative; width: var(--tile-w); }

/* Except down a device's own page, where there is no ingredient row to compare a result against and
 * the results carry the longest names on the site — "Legendary Green Steel Augment: Protection from
 * Elements, Mass" wrapped to six lines at the ingredient width and dragged the whole card down. */
.recipe-list .recipe-card-out .recipe-ingredients { --tile-w: 176px; }

/* A plus between the ingredients, so the row reads as the equation it is.
 *
 * Not decoration: the chevron says the right-hand side is a consequence, and nothing else says the
 * left-hand side is a conjunction. Without it a row of icons could as easily mean "one of these",
 * which is a thing recipes elsewhere do mean.
 *
 * Sits against the icon rather than the middle of the tile, because the captions run to three lines
 * and a plus centred on the whole tile would drift below the icons it sits between. Safe at any
 * length: no recipe in the data has more than five ingredients, and two thirds have one or two. */
.recipe-ingredient + .recipe-ingredient::before {
    content: "+";
    position: absolute;
    left: -13px;
    top: 9px;
    width: 16px;
    text-align: center;
    font-size: 0.85rem;
    line-height: 1;
    color: var(--text-faint);
}
.recipe-ingredient-link { display: block; text-decoration: none; }
.recipe-ingredient-icon {
    display: block;
    width: 36px;
    height: 36px;
    margin: 0 auto 3px;
    background: var(--ink);
    border: 1px solid var(--line);
    border-radius: 5px;
}
/* The caption is the slot as the recipe writes it, so it carries the count. Centred under the icon
   and allowed to wrap, because "Shavarath Signet Stone" does not fit on one line at this width. */
.recipe-ingredient-name {
    display: block;
    text-align: center;
    font-size: 0.7rem;
    line-height: 1.25;
    color: var(--text-dim);
    /* Balanced rather than ragged: these are two and three line names in a narrow measure, where
       the default leaves a single trailing word on its own line more often than not. */
    text-wrap: balance;
}
.recipe-ingredient-link:hover .recipe-ingredient-name { color: var(--text); }
.recipe-ingredient-link:hover .recipe-ingredient-icon { border-color: var(--text-faint); }

/* The result, framed and lit so a page of recipes has somewhere for each row to end. Down a device's
 * page only, for the same reason as the width above: on an item page a material would be grey where
 * the item is made and gold where it is broken down — one thing painted two ways a screen apart. The
 * item you are actually looking at is what carries gold there; see .recipe-ingredient-here. */
.recipe-list .recipe-card-out .recipe-ingredient-icon {
    border-color: rgba(200, 168, 75, 0.55);
    background: linear-gradient(var(--gold-shade), var(--ink));
    box-shadow: 0 0 0 3px rgba(200, 168, 75, 0.07);
}
.recipe-list .recipe-card-out .recipe-ingredient-name { color: var(--gold-lit); }
.recipe-list .recipe-card-out .recipe-ingredient-link:hover .recipe-ingredient-icon { border-color: var(--gold); }

/* An upgrade has no product to draw, so the column carries a tag and the recipe's own name. */
/* An upgrade's two answers, side by side. What it adds and what it takes off are different
   questions, and running them together as two lines of prose under a tag made the second read as an
   afterthought — when it is the half a reader cannot find anywhere else, the effects being stripped
   are not on the item going in. */
.recipe-upgrade-cols {
    display: flex;
    align-items: flex-start;
    gap: 20px;
}

.recipe-upgrade-col { min-width: 0; }

.recipe-upgrade-col-label {
    font-size: 0.6rem;
    line-height: 1.1;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-bottom: 3px;
}

.recipe-upgrade-list {
    list-style: none;
    margin: 0;
    padding: 0;
    font-size: 0.76rem;
    line-height: 1.4;
    max-width: 26ch;
}

.recipe-upgrade-list li { white-space: normal; }

/* Only where there is something to read. A dotted underline under every name would promise a
   description on the ones that have none. */
.recipe-mutation-known {
    text-decoration: underline dotted rgba(255, 255, 255, 0.22);
    text-underline-offset: 2px;
    cursor: help;
}

/* The same green and red the delta lines use, so a reader who has seen one card reads the next
   without re-learning it. */
.recipe-upgrade-added li { color: #7FB08B; }
.recipe-upgrade-removed li { color: #B08B8B; }

.recipe-upgrade { max-width: 22ch; }
.recipe-upgrade-tag {
    display: inline-block;
    font-size: 0.6rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--gold);
    border: 1px solid rgba(200, 168, 75, 0.4);
    border-radius: 3px;
    padding: 1px 6px;
    margin-bottom: 4px;
}
.recipe-upgrade-name { display: block; font-size: 0.76rem; line-height: 1.35; color: var(--text-dim); }

/* Narrow: the columns stack and the chevron turns to point down the page instead of across it. */
@media (max-width: 1100px) {
    /* The description drops below the row before the row itself stacks: it is the widest thing on
       the card and the first that stops fitting. */
    .recipe-card-described { grid-template-columns: auto auto auto; justify-content: start; }
    .recipe-list .recipe-card-described { grid-template-columns: var(--recipe-in-w) auto auto; }
    .recipe-card-desc {
        grid-row: 3;
        grid-column: 1 / -1;
        max-width: none;
        margin-top: 12px;
        padding: 12px 0 0;
        border-left: 0;
        border-top: 1px solid var(--line);
    }
}

@media (max-width: 720px) {
    /* Tiles wrap here, and a plus at a line break either dangles off the end of one row or opens
       the next. The label still says Ingredients, so the row survives losing them. */
    .recipe-ingredient + .recipe-ingredient::before { content: none; }
    /* Full width again once the card is a stack, and one card to a line: there is no second column
       to flow into at this size. Shrinking to content is what keeps a two-icon trade from drawing a
       box the width of a desktop column; on a phone the column is the card's width anyway, and
       hugging the contents only makes a ragged left-hand edge down the page. */
    .recipe-cards { display: block; }
    .recipe-cards .recipe-card { margin-bottom: 8px; }
    .recipe-card { width: auto; grid-template-columns: minmax(0, 1fr); grid-template-rows: auto auto auto auto; }
    .recipe-card-described { grid-template-columns: minmax(0, 1fr); justify-content: normal; }
    /* Stacked, so there is no column to line up and the fixed track would only force a scrollbar. */
    .recipe-list .recipe-card,
    .recipe-list .recipe-card-described { grid-template-columns: minmax(0, 1fr); }
    .recipe-card-desc { grid-row: 6; }
    .recipe-card-label-in  { grid-row: 1; grid-column: 1; }
    .recipe-card-in        { grid-row: 2; grid-column: 1; }
    .recipe-card-arrow     { grid-row: 3; grid-column: 1; justify-content: center; padding: 4px 0; }
    .recipe-card-label-out { grid-row: 4; grid-column: 1; }
    .recipe-card-out       { grid-row: 5; grid-column: 1; }
    .recipe-card-arrow::after { transform: rotate(135deg); }
}


/* ── Used In ─────────────────────────────────────────────
 * The same cards as everywhere else. What tells this section apart is not a different shape but a
 * marked tile — the item whose page this is, lit where it sits among the ingredients, so the answer
 * to "where does this go" is visible without reading a single name. */
/* One device and its cards. Both halves of the item page draw themselves out of this now, so the
   spacing is settled in one place rather than in two that drifted apart — the heading used to carry
   another 14px on top of the section header's own margin, which set Used In's first device lower
   under its heading than Found In's, on a page that shows both. */
.uses-device { margin-bottom: 18px; }

/* How many recipes this device runs with the item, said beside its name so the size of what follows
   is known before scrolling into it. */
.uses-device-count {
    font-size: 0.68rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-faint);
    font-weight: normal;
}

/* The filter and the compact pager share a row, so a page can be turned without scrolling to the
   bottom to find out how. Sitting next to the filter, not pushed to the far side of the section:
   space-between put it a table's width away from everything else and it read as page furniture
   rather than as a control. */
.uses-controls {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 14px;
    margin: 0 0 12px;
}
.uses-filter { display: flex; align-items: center; gap: 8px; }

/* "Show 10" — how many cards a device puts on a page. On the controls row rather than beside the
   heading, because it is the same kind of work as the filter and the pager it sits between: not
   what this section is, but how much of it you are looking at. */
.uses-size { display: flex; align-items: center; gap: 6px; }
.uses-size-label { font-size: 0.72rem; color: var(--text-faint); }
.uses-size-select {
    font: inherit;
    font-size: 0.76rem;
    padding: 3px 6px;
    color: var(--text-dim);
    background: var(--surface-lift);
    border: 1px solid var(--line);
    border-radius: 4px;
    cursor: pointer;
}
.uses-size-select:hover { color: var(--text); border-color: var(--text-faint); }

/* Sized to what is typed in it, not to the table it sits above. .search-input is width:100% for the
   browse panels, where the box is the whole column — here it is one control on a row. */
.uses-controls .uses-search { width: 260px; max-width: 100%; }

.uses-count { font-size: 0.72rem; color: var(--text-faint); font-variant-numeric: tabular-nums; }
.uses-empty { font-size: 0.8rem; color: var(--text-faint); font-style: italic; margin: 0 0 12px; }

.uses-pager { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.uses-pager a, .uses-pager-off, .uses-button, .uses-clear {
    font: inherit;
    font-size: 0.76rem;
    padding: 4px 12px;
    color: var(--text-dim);
    background: var(--surface-lift);
    border: 1px solid var(--line);
    border-radius: 4px;
    text-decoration: none;
    cursor: pointer;
}
.uses-pager a:hover, .uses-button:hover, .uses-clear:hover { color: var(--text); border-color: var(--text-faint); }
/* An edge of the range, drawn the same as a link so the row does not reflow when it stops being one. */
.uses-pager-off { opacity: 0.35; cursor: default; }
.uses-pages { font-size: 0.72rem; color: var(--text-faint); font-variant-numeric: tabular-nums; }

/* The one on the controls row is a second control, not the row itself: chevrons rather than words,
   and no top margin to fight the row it is sitting in. */
.uses-pager-compact { margin-top: 0; gap: 5px; }
.uses-pager-compact a, .uses-pager-compact .uses-pager-off {
    padding: 1px 8px;
    font-size: 0.9rem;
    line-height: 1.35;
}
.uses-pager-compact .uses-pages { font-size: 0.7rem; }

.recipe-ingredient-here .recipe-ingredient-icon {
    border-color: var(--gold);
    box-shadow: 0 0 0 3px rgba(200, 168, 75, 0.16);
}
.recipe-ingredient-here .recipe-ingredient-name { color: var(--gold-lit); }

/* What a recipe puts on and takes off, under what it makes. Two directions, so two signs and two
   colours — a change that only says what was added is half an answer. */
.uses-delta { font-size: 0.74rem; line-height: 1.4; margin-top: 4px; max-width: 22ch; }
.uses-added { color: #7FB08B; }
.uses-removed { color: #B08B8B; }

/* ── Floating contents list ──────────────────────────────
   The panel beside a long page that names its sections and jumps to them. Shared: the quest, feat
   and enhancement pages all want one, and the first two of those had grown near-identical copies.

   Lives here rather than in a page stylesheet because three families use it and item.css is the one
   sheet _Layout always loads.

   Sized to its own longest entry between bounds rather than given a fixed width — "Tier 3" on a
   tree page and "Undead Turning Feats" on the heroic feats page are the same component and should
   not need two rules to sit properly. */
.page-with-toc {
    display: flex;
    gap: 20px;
    align-items: flex-start;
}

.page-with-toc-main {
    flex: 1;
    /* Without this a wide table forces the flex item past its share and shoves the contents list
       off the edge instead of scrolling inside its own column. */
    min-width: 0;
}

.page-toc {
    order: 2;
    flex-shrink: 0;
    width: max-content;
    min-width: 130px;
    max-width: 240px;
    position: sticky;
    top: 16px;
    /* A long list scrolls within itself rather than growing past the viewport, which would defeat
       the point of it staying put. */
    max-height: calc(100vh - 32px);
    overflow-y: auto;
    padding: 12px 14px;
    background: #1a1a1a;
    border: 1px solid #333;
    border-radius: 4px;
    font-size: 0.85rem;
}

.page-toc-heading {
    font-size: 0.66rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--text-dim);
    margin-bottom: 10px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--line);
}

.page-toc ul { list-style: none; margin: 0; padding: 0; }
.page-toc li { padding: 2px 0; }

/* Nesting shown by indent rather than bullets, which would compete with the lists on the page. */
.page-toc ul ul { padding-left: 14px; margin: 2px 0 4px; }

.page-toc a {
    display: block;
    width: fit-content;
    color: var(--gold);
    text-decoration: none;
    line-height: 1.4;
}
.page-toc a:hover { color: var(--gold-lit); text-decoration: underline; }
.page-toc a:focus-visible {
    outline: 1px solid var(--gold);
    outline-offset: 2px;
    border-radius: 2px;
}

/* A nested entry is a subdivision of the one above it, so it is quieter than its parent. */
.page-toc ul ul a { color: var(--text-dim); font-size: 0.8rem; }
.page-toc ul ul a:hover { color: var(--text); }

/* Side by side needs width for both. Narrow, the list goes back above the content, where sticking
   it to the viewport would only eat the screen it is meant to help navigate. */
@media (max-width: 900px) {
    .page-with-toc { display: block; }

    .page-toc {
        width: auto;
        max-width: none;
        position: static;
        max-height: none;
        margin-bottom: 20px;
    }
}

/* ── Binding badge ───────────────────────────────────────
   The BtA / BtC tag beside an item's name, on a chest's loot list and on the item tables. Lives
   here rather than in quest.css because three page families draw it and this is the one sheet
   _Layout always loads.

   --text-dim, not --text-faint: at the size this is set it was quiet enough to skip past, and
   whether something binds is the kind of thing you scan a list for. The full wording stays in the
   title, so the abbreviation is never the only thing on offer. */
.bind-badge {
    margin-left: 6px;
    padding: 1px 5px;
    border: 1px solid var(--line);
    border-radius: 3px;
    font-size: 0.68rem;
    letter-spacing: 0.04em;
    color: var(--text-dim);
    white-space: nowrap;
    cursor: help;
}

/* ── Sortable table headers ──────────────────────────────
   For every table /js/table-sort.js binds, which marks each header it has taken over with
   .is-sortable — so only the columns that actually sort advertise themselves as clickable, and a
   data-type="none" column stays inert.

   Here rather than in quest.css for the same reason .bind-badge is: the quest list, the item browse
   tables and the search results all sort, and item.css is the one sheet _Layout loads for all of
   them. Left in quest.css the arrows were invisible on every item page, which is most of them. */
th.is-sortable {
    cursor: pointer;
    user-select: none;
}

th.is-sortable.sort-asc::after {
    content: ' ▲';
    color: #c8a84b;
}

th.is-sortable.sort-desc::after {
    content: ' ▼';
    color: #c8a84b;
}

/* ── Raid badge ──────────────────────────────────────────
   The R beside a quest that is a raid, and in the pack index's Quests heading. Here rather
   than in quest.css for the same reason .bind-badge is: three page families draw it and this
   is the sheet _Layout always loads. */
.raid-badge {
    display: inline-block;
    font-size: 0.6em;
    font-weight: 700;
    line-height: 1;
    padding: 3px 3px;
    border: 1px solid #000;
    border-radius: 2px;
    background: #c8a84b;
    color: #000;
    vertical-align: middle;
    margin-right: 4px;
    letter-spacing: 0;
    cursor: default;
}
