/* ───────────── MythosLoom theme: dark parchment-and-ember ───────────── */
/*
   Taken from the prototype's app.css so the two look like one product. Only what Phase 1 renders is
   here — the prototype's stat blocks, spell cards, initiative rows, plot clocks, meters and table-view
   styles are left behind rather than carried over as dead rules. They come back with the screens that
   need them, in Phase 2 and Phase 3.

   The custom properties are the whole theme. Anything new should use them rather than a literal colour;
   a hard-coded hex is how a palette stops being a palette.

   #101 tried the mockup's light parchment across the whole product and it was rejected on sight: the
   argument for it was that the character sheet prints on paper and a dark screen makes it two documents,
   which is true and is not worth the product being a colour nobody wanted to look at. The typography from
   that experiment stayed. So did the four bugs it exposed, all of which were only ever invisible rather
   than absent — the comments that mention a light ground are describing how each was found.

   Darkest where you read, lighter where something is raised. The roles matter as much as the values: the
   light version of this had to invert them as well, and putting the sidebar and the form fields on the
   wrong side of that was the first thing that went wrong.
*/
:root {
    --bg: #0f0e13;          /* the page, and the recessed ground of a form field */
    --bg-2: #17161d;        /* the sidebar */
    --bg-3: #1f1d27;        /* an inset or a card within a panel */
    --panel: #1b1a22;       /* a raised panel */
    --border: #2d2b38;
    --border-strong: #3f3c4e;
    --text: #e8e3d8;
    --muted: #9c97a8;
    --accent: #d9822b;      /* ember */
    --accent-hover: #f0a050;
    --accent-soft: rgba(217, 130, 43, .15);
    --on-accent: #1a0f05;   /* text on an accent fill */
    --accent-2: #8b5cf6;    /* arcane violet */
    --danger: #d64545;
    --ok: #3fae6a;
    --warn: #e0b341;
    --radius: 10px;

    /*
       Cinzel is inscriptional: all capitals, no italic, cut for carving rather than reading. It is a
       nameplate face and is used as one — headings only, never a sentence. It was named here from the
       start and never loaded, so every heading in the product rendered in Palatino until #101; the
       @font-face block below is what made the declaration true.
    */
    --font-display: "Cinzel", "Palatino Linotype", "Book Antiqua", Georgia, serif;
    --font-body: "EB Garamond", Georgia, "Times New Roman", serif;

    /*
       A sans for the small print: field labels, table headings, the letterspaced metadata under a card.
       Garamond has a small x-height and turns to mush below about 13px, which is exactly the size those
       are. Two faces doing two jobs rather than one doing both badly.
    */
    --font-ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/*
   Self-hosted, latin subset only — see wwwroot/fonts/README.md for why, and for the OFL licences that
   have to travel with the files. All three are variable fonts, so one file covers the whole weight range.

   They are declared here rather than in sheet.css because App.razor loads this file on every page, the
   printed sheet included: PrintLayout strips the chrome, not the head. A browser downloads a face only
   where something uses it, so the pages that never set one pay nothing for the declaration.
*/
@font-face {
    font-family: "Cinzel";
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("fonts/cinzel-latin.woff2") format("woff2");
}

/*
   The sheet's label face (#162): section headings, the small letterspaced capitals over a number, the
   character's name. A condensed grotesque against the serif everything else is read in — it is what makes
   the sheet scannable without a single box or rule being added, and it is the one thing #152 was set in
   that this could not have until the file was here.
*/
@font-face {
    font-family: "Archivo Narrow";
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("fonts/archivonarrow-latin.woff2") format("woff2");
}

@font-face {
    font-family: "EB Garamond";
    font-style: normal;
    font-weight: 400 600;
    font-display: swap;
    src: url("fonts/ebgaramond-latin.woff2") format("woff2");
}

@font-face {
    font-family: "EB Garamond";
    font-style: italic;
    font-weight: 400 600;
    font-display: swap;
    src: url("fonts/ebgaramond-italic-latin.woff2") format("woff2");
}

html, body { background: var(--bg); color: var(--text); font-family: var(--font-body); }
/* Garamond runs small for its point size, so the base is nudged up rather than every rule being resized. */
body { min-height: 100vh; font-size: 1.05rem; line-height: 1.55; }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); }
h1, h2, h3, h4, .brand { font-family: var(--font-display); font-weight: 600; letter-spacing: .03em; color: var(--text); }
h1 { font-size: 1.9rem; } h2 { font-size: 1.4rem; } h3 { font-size: 1.15rem; }
h1:focus { outline: none; }
::selection { background: var(--accent-2); color: white; }

/* layout */
.shell { display: grid; grid-template-columns: 240px 1fr; min-height: 100vh; }
/*
   A column, so the footer can be pushed to the bottom of it rather than sitting under the last link
   (#406). The nav grows to fill whatever the links do not use, and the footer inside it takes the slack.
*/
.sidebar { background: var(--bg-2); border-right: 1px solid var(--border); padding: 1rem .75rem; position: sticky; top: 0; height: 100vh; overflow-y: auto; display: flex; flex-direction: column; }
.sidebar .nav.flex-column { flex: 1 1 auto; }
.nav-footer { margin-top: auto; padding-top: .9rem; border-top: 1px solid var(--border); }
/*
   Quiet on purpose. It is there to be found when somebody asks "which version are you on", not to be
   read on the way past.
*/
.nav-version { font-family: var(--font-ui); font-size: .7rem; letter-spacing: .08em; color: var(--muted); padding: .5rem .6rem 0; }
.content { padding: 1.5rem 2rem 4rem; max-width: 1400px; }
/*
   Narrow: the sidebar collapses to a bar with the brand and a menu button (#475). The button discloses
   the full navigation as a panel instead of laying it out in a row.

   The previous phone layout kept every link in a single horizontally-scrolling row. That put the
   wordmark, every navigation link, the account link, Log out, Terms, Privacy and the version side by
   side — squished and hard to tap one-handed, which is how players use the site (BRD NFR-4). A button
   that opens the same links as a panel keeps every destination one tap away without cramming them into
   the bar.

   The panel is hidden with `display: none` rather than moved off-screen, so when it is closed its links
   leave the tab order as well as the screen. `data-open` is driven by the component state, which clears
   on navigation, so choosing a link closes the panel.
*/
@media (max-width: 900px) {
    .shell { grid-template-columns: 1fr; }

    .sidebar {
        position: sticky; top: 0; z-index: 10; height: auto;
        border-right: none; border-bottom: 1px solid var(--border);
        padding: .4rem .75rem;
        display: block;
    }

    .sidebar-header { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }

    .sidebar .brand { padding: 0 .4rem 0 0; font-size: 1rem; white-space: nowrap; }
    .sidebar .brand small { display: none; }

    .sidebar .nav-toggle {
        display: inline-flex; align-items: center; justify-content: center;
        width: 2.5rem; height: 2.5rem; flex: none;
        background: transparent; border: 1px solid var(--border); border-radius: 8px;
        color: var(--text); cursor: pointer;
    }
    .sidebar .nav-toggle:hover { background: var(--bg-3); }
    .sidebar .nav-toggle .ico { width: 1.4rem; height: 1.4rem; }

    .sidebar .nav.flex-column { display: none; }
    .sidebar .nav.flex-column[data-open="true"] {
        display: flex; flex-direction: column; align-items: stretch;
        margin-top: .5rem; padding-top: .5rem; border-top: 1px solid var(--border);
    }

    .sidebar .nav-section { display: block; }
    .sidebar .nav-footer { display: block; margin-top: .5rem; padding-top: .5rem; border-top: 1px solid var(--border); }
    .sidebar .nav-version { padding: .5rem .6rem 0; white-space: nowrap; }
    .sidebar .nav a { padding: .6rem .6rem; font-size: .95rem; }

    .sidebar form { margin: 0; }
    .sidebar form .btn { width: 100% !important; margin-top: .5rem !important; padding: .5rem .6rem; }

    .content { padding: 1rem; }
}

.brand { font-size: 1.25rem; color: var(--accent); display: block; padding: .25rem .5rem 1rem; }
.brand small { display: block; font-family: var(--font-ui); font-size: .7rem; color: var(--muted); letter-spacing: .1em; text-transform: uppercase; }
/*
   The menu button exists in the markup at every width so the component does not need to know the
   viewport; above 900px it is simply not shown. See the phone block for its appearance.
*/
.nav-toggle { display: none; }
/* The laptop's counterpart: the sidebar folds to a rail holding only this button, for pages that want the width. */
.nav-collapse { display: none; }
@media (min-width: 901px) {
    .nav-collapse {
        display: inline-flex; align-items: center; justify-content: center;
        position: absolute; top: .6rem; right: .5rem;
        width: 1.75rem; height: 1.75rem;
        background: transparent; border: 1px solid var(--border); border-radius: 8px;
        color: var(--muted); cursor: pointer;
    }
    .nav-collapse:hover { background: var(--bg-3); color: var(--text); }
    .nav-collapse .ico { width: 1.1rem; height: 1.1rem; }

    html[data-nav-collapsed] .shell { grid-template-columns: 3rem 1fr; }
    html[data-nav-collapsed] .sidebar > :not(.nav-collapse) { display: none; }
}
.nav-section { font-family: var(--font-ui); font-size: .7rem; text-transform: uppercase; letter-spacing: .12em; color: var(--muted); padding: .9rem .5rem .3rem; }
.nav a { display: flex; align-items: center; gap: .55rem; padding: .45rem .6rem; border-radius: 8px; color: var(--text); font-size: .93rem; }
.nav a:hover { background: var(--bg-3); }
.nav a.active { background: var(--accent-soft); color: var(--accent); }
/*
   Icons are SVG since #106, sized in em so they scale with whatever text they sit beside, and stroked in
   currentColor so an active navigation item's ember reaches them. They were emoji, which could do
   neither.
*/
.ico { width: 1.15em; height: 1.15em; flex: none; vertical-align: -0.18em; }
.nav a .ico { opacity: .85; }
.nav a.active .ico, .nav a:hover .ico { opacity: 1; }
.ico-lg { width: 1.7rem; height: 1.7rem; color: var(--accent); }

/* Bootstrap's .text-muted is its own grey, chosen for its own palette rather than this one. Kept pointed
   at the theme's muted colour so there is one answer to what muted means. It mattered more when the
   surfaces were dark — Bootstrap's near-black muted was invisible on them, which is what #93 found — and
   it still matters now for the same reason in reverse: two greys is one grey too many. */
.text-muted { color: var(--muted) !important; }

/* The breadcrumb's current page and dividers, which Bootstrap colours with that same near-black grey
   and which vanished against the page for the reason above (#523). */
.breadcrumb { --bs-breadcrumb-divider-color: var(--muted); --bs-breadcrumb-item-active-color: var(--muted); }

/* panels & cards */
.panel { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); padding: 1rem 1.15rem; }
.panel + .panel { margin-top: 1rem; }
.panel-title { display: flex; justify-content: space-between; align-items: center; margin-bottom: .75rem; }
.panel-title h2, .panel-title h3 { margin: 0; }
.grid { display: grid; gap: 1rem; }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.card { color: var(--text); --bs-card-bg: var(--bg-3); background: var(--bg-3); border: 1px solid var(--border); border-radius: var(--radius); padding: .9rem 1rem; transition: border-color .15s; }
.card:hover { border-color: var(--border-strong); }
.muted { color: var(--muted); }
.small { font-size: .85rem; }
.tiny { font-size: .75rem; }
.mono { font-family: ui-monospace, Menlo, Consolas, monospace; }
.stack { display: flex; flex-direction: column; gap: .5rem; }
/* A horizontal group of controls. Deliberately NOT called .row.
   The prototype redefines Bootstrap's .row as a flex row and neutralises its gutters. That is fine in a
   project whose own markup uses it that way, and wrong here: the Identity account pages are laid out with
   Bootstrap's grid, and overriding .row flattened their columns so content ran off the side of the page.
   A separate name leaves the grid alone. */
.inline-row { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.inline-row > .spacer { flex: 1 1 auto; }
.inline-row.between { justify-content: space-between; }
.spacer { flex: 1; }
hr { border-color: var(--border); opacity: 1; }

/* forms */
/*
   Tick boxes and radios are excluded, and the exclusion is the point. This rule uses the `background`
   shorthand with !important, which sets background-image to none as well as the colour — and a checked
   box is drawn by Bootstrap as a tick in a background image. Every checkbox in the application rendered
   as an empty box whether or not it was ticked: the equipment a character was wearing, the sections of
   the sheet to fill in, "remember me" on the login page. It was wrong on the dark theme too and simply
   did not look wrong, because an unstyled box on a dark ground looks like a box.
*/
input:not([type="checkbox"]):not([type="radio"]), select, textarea, .form-control, .form-select {
    background: var(--bg) !important; color: var(--text) !important; border: 1px solid var(--border) !important; border-radius: 8px !important;
}
/* A field with a problem keeps its red edge against the rule above (docs/HOMEBREW-EDITOR-CONTROLS.md E7). */
input.is-invalid:not([type="checkbox"]):not([type="radio"]), select.is-invalid, textarea.is-invalid { border-color: var(--danger) !important; }
input:focus, select:focus, textarea:focus, .form-control:focus, .form-select:focus { outline: none; border-color: var(--accent) !important; box-shadow: 0 0 0 3px var(--accent-soft) !important; }
/* A hint in an empty field, readable on the dark ground (the review of October 2026) but quieter than a value. */
input::placeholder, textarea::placeholder, .form-control::placeholder { color: var(--muted); opacity: 1; }
label { font-family: var(--font-ui); font-size: .78rem; color: var(--muted); text-transform: uppercase; letter-spacing: .08em; margin-bottom: .2rem; display: block; }
.field { margin-bottom: .7rem; }
.form-check-input { background-color: var(--bg); border-color: var(--border-strong); }
.form-check-input:checked { background-color: var(--accent); border-color: var(--accent); }
.form-check-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.form-check-label { text-transform: none; letter-spacing: 0; font-size: .9rem; color: var(--text); display: inline; }

/*
   A <label> is not always a field caption. Bootstrap's btn-check pattern makes an entire card a label —
   which is how the builder draws a species or a class — and the rule above was uppercasing everything
   inside one, the ruleset's own words included: "Choose 1 of 18" arrived as "CHOOSE 1 OF 18" and a size
   as "MEDIUM, SMALL". Uppercase is for the small print, not for content.
*/
label.card, label.btn, label.nav-link {
    font-family: inherit; text-transform: none; letter-spacing: normal; font-size: inherit;
    color: inherit; margin-bottom: 0;
}

/*
   And the focus ring has to go on the label too, for the other half of the same arrangement (#381).

   btn-check hides the input — it is clipped to nothing, which is how an entire card becomes a radio
   button — so the element that holds the focus is the one element that cannot show it. Every rule above
   draws focus on the input, so the builder's largest and most-used control drew none at all: tab onto a
   class and the browser agrees it is focused, `:focus-visible` matches, and nothing on the screen moves.
   A keyboard player could reach the twelve classes and not see which one they were on.

   An outline rather than this file's usual border-plus-halo. The halo is a 15% wash that reads as a glow
   beside a field's solid accent border; on its own against a dark card it is not a focus indicator, and
   WCAG 2.4.7 wants one that can be seen. The ember at full strength, offset clear of the card's own
   border, is unambiguous and matches the colour that already means "this one".

   :focus-visible and not :focus, so clicking a card with a mouse does not leave a ring behind it.
*/
.btn-check:focus-visible + label {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/*
   Bootstrap's .border-primary is Bootstrap's blue, which belongs to neither this palette nor the one
   before it. The builder marks a chosen option with it, so the one card on the screen that is supposed
   to say "this one" said it in a colour from another product.
*/
.border-primary { border-color: var(--accent) !important; }
.text-primary { color: var(--accent) !important; }
.form-text { color: var(--muted); font-size: .85rem; }
textarea { min-height: 90px; }

/* The floating-label forms from the Identity scaffold sit badly against uppercase muted labels, so the
   label reverts to ordinary text inside them rather than every account page being rewritten. */
.form-floating > label { text-transform: none; letter-spacing: 0; color: var(--muted); }

/* buttons */
/* Buttons carry the sans: a button is a label, not prose, and Garamond's small caps read as decoration
   at button size. */
.btn { font-family: var(--font-ui); border-radius: 8px; font-weight: 500; border: 1px solid transparent; }
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: var(--on-accent); }
.btn-outline-primary { background: transparent; border-color: var(--accent); color: var(--accent); }
.btn-outline-primary:hover { background: var(--accent); color: var(--on-accent); }
.btn-outline-secondary, .btn-ghost { background: transparent; border-color: var(--border); color: var(--text); }
.btn-outline-secondary:hover, .btn-ghost:hover { background: var(--bg-3); border-color: var(--border-strong); color: var(--text); }
.btn-danger, .btn-outline-danger { background: transparent; border-color: var(--danger); color: var(--danger); }
.btn-danger:hover, .btn-outline-danger:hover { background: var(--danger); color: var(--on-accent); }
.btn-outline-success { background: transparent; border-color: var(--ok); color: var(--ok); }
.btn-outline-success:hover { background: var(--ok); color: var(--on-accent); }
.btn-link { color: var(--accent); }
.btn-sm { font-size: .8rem; padding: .25rem .6rem; }

/* The account pages use nav-pills, whose active state is a Bootstrap colour that belongs to neither the
   light theme it assumes nor this one. Matched to the sidebar so the two navigations agree. */
.nav-pills .nav-link { color: var(--text); border-radius: 8px; }
.nav-pills .nav-link:hover { background: var(--bg-3); }
.nav-pills .nav-link.active { background: var(--accent-soft); color: var(--accent); }

/* badges & tags */
.badge { font-family: var(--font-ui); border-radius: 6px; font-weight: 500; padding: .25em .5em; }

/*
   Bootstrap's semantic backgrounds are Bootstrap's colours. The sheet marks a proficient save with one
   and the builder colours its findings by severity with the rest, so leaving them alone put a grey from
   another palette next to every ability on the sheet. Mapped, not fought: the severity names still come
   from the engine and this is still the only place they become colours.
*/
.badge.bg-secondary { background-color: var(--muted) !important; color: var(--on-accent) !important; }
.badge.bg-danger { background-color: var(--danger) !important; color: var(--on-accent) !important; }
.badge.bg-warning { background-color: var(--warn) !important; color: var(--on-accent) !important; }
.badge.bg-info { background-color: var(--accent-2) !important; color: var(--on-accent) !important; }

/*
   Bootstrap sets <code> to a pink of its own. The sheet uses it for the name of a derived value, which
   is a label rather than a snippet — but the colour was the part that came from somewhere else.
*/
code { color: var(--accent); }
.tag { display: inline-block; font-family: var(--font-ui); background: var(--bg); border: 1px solid var(--border); border-radius: 999px; padding: .1rem .55rem; font-size: .72rem; color: var(--muted); margin: .1rem .15rem .1rem 0; }
.tag.ok { border-color: var(--ok); color: var(--ok); }
.tag.danger { border-color: var(--danger); color: var(--danger); }

/* tables — Bootstrap's defaults assume a light background, so they are overridden rather than fought */
.table { --bs-table-color: var(--text); --bs-table-bg: transparent; --bs-table-border-color: var(--border); color: var(--text); font-size: .92rem; }
.table > thead th { font-family: var(--font-ui); text-align: left; font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); border-bottom: 1px solid var(--border); }
.table > tbody td { border-bottom: 1px solid var(--border); vertical-align: middle; }
.table-striped > tbody > tr:nth-of-type(odd) > * { --bs-table-accent-bg: rgba(255, 255, 255, .02); color: var(--text); }
.table-warning, .table-warning > * { --bs-table-accent-bg: rgba(224, 179, 65, .12); color: var(--text); }
.table > :not(caption) > * > * { background-color: transparent; }

/*
   Bootstrap's list group is white with near-black text, which is its own palette again. The builder puts
   every outstanding finding in one and the sheet puts every rule that stayed prose in one, so the two
   places a character tells you something about itself were a page from another product.
*/
.list-group-item { background-color: var(--panel); color: var(--text); border-color: var(--border); }

/* alerts */
.alert { border-radius: var(--radius); border: 1px solid var(--border); background: var(--panel); color: var(--text); }
.alert-info { border-color: var(--accent-2); background: rgba(139, 92, 246, .12); }
.alert-warning { border-color: var(--warn); background: rgba(224, 179, 65, .10); }
.alert-danger { border-color: var(--danger); background: rgba(214, 69, 69, .12); }
/* Bootstrap's alert links are a darker shade of the alert's colour, which on this dark ground is unreadable. */
.alert .alert-link { color: inherit; text-decoration: underline; }
.alert-success { border-color: var(--ok); background: rgba(63, 174, 106, .12); }

/* landing — its own layout, with no sidebar */
.landing { display: flex; flex-direction: column; min-height: 100vh; }
.landing-content { flex: 1 0 auto; padding: 2rem 2rem 3rem; max-width: 1000px; margin: 0 auto; width: 100%; }
.landing-footer { border-top: 1px solid var(--border); padding: 1rem 2rem; max-width: 1000px; margin: 0 auto; width: 100%; }
.landing-footer a { color: var(--muted); }
.landing-footer a:hover { color: var(--accent); }

.masthead { display: flex; align-items: center; gap: 1rem; padding-top: 1rem; }
.masthead-logo { width: 96px; height: 96px; object-fit: contain; }
.masthead-title { font-size: 2.6rem; margin: 0; color: var(--accent); letter-spacing: .04em; }
@media (max-width: 600px) {
    .masthead-logo { width: 64px; height: 64px; }
    .masthead-title { font-size: 2rem; }
    .landing-content, .landing-footer { padding-left: 1rem; padding-right: 1rem; }
}

.hero { padding: 2rem 0; max-width: 800px; }
/* The masthead carries the h1, so the hero's own line is an h2 and is sized up to read as the headline. */
.hero h2 { font-size: 2.6rem; line-height: 1.1; }
.hero p.lead { color: var(--muted); font-size: 1.15rem; }
.feature { padding: 1.2rem; border: 1px solid var(--border); border-radius: var(--radius); background: var(--panel); }
.feature .ico { margin-bottom: .1rem; }
.feature h3 { margin: .4rem 0 .2rem; font-size: 1.05rem; }

/* legal documents rendered from markdown */
.legal { max-width: 70ch; }
.legal h1 { margin-bottom: .25rem; }
.legal h2 { margin-top: 2rem; }
.legal blockquote { border-left: 3px solid var(--warn); padding-left: 1rem; color: var(--muted); }
.legal table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
.legal th, .legal td { text-align: left; padding: .45rem .5rem; border-bottom: 1px solid var(--border); vertical-align: top; }
.legal th { font-family: var(--font-ui); font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
.legal code { color: var(--accent); }

/* equipment (#445): what is owned, as a table, and the catalogue in a dialog drawn by Blazor, since
   Bootstrap's JavaScript is not shipped. Full screen on a phone, a centred panel otherwise. */
.equip-table td:first-child { min-width: 0; }
.equip-badge { background: var(--accent-soft); color: var(--accent); }
.equip-on { color: var(--ok); font-weight: 600; }
.equip-step { padding: 0 .45rem; line-height: 1.4; }
.equip-backdrop { position: fixed; inset: 0; background: rgba(0, 0, 0, .6); z-index: 1040; }
.equip-dialog { position: fixed; z-index: 1050; background: var(--panel); border: 1px solid var(--border-strong);
                inset: 0; padding: 1rem; display: flex; flex-direction: column; }
.equip-catalogue { overflow-y: auto; flex: 1 1 auto; min-height: 0; }
.equip-offer { border-color: var(--border) !important; }
@media (min-width: 576px) {
    .equip-dialog { inset: 5vh auto auto 50%; transform: translateX(-50%); width: min(560px, 92vw);
                    max-height: 90vh; border-radius: var(--radius); padding: 1.25rem; }
}

.validation-message { color: var(--danger); font-size: .8rem; }
.valid.modified:not([type=checkbox]) { outline: 1px solid var(--ok); }
.invalid { outline: 1px solid var(--danger); }

#blazor-error-ui { color-scheme: light only; background: lightyellow; bottom: 0; box-shadow: 0 -1px 2px rgba(0,0,0,.2); box-sizing: border-box; display: none; left: 0; padding: .6rem 1.25rem .7rem 1.25rem; position: fixed; width: 100%; z-index: 1000; color: black; }
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: .75rem; top: .5rem; }
.blazor-error-boundary { background: #b32121; padding: 1rem 1rem 1rem 3.7rem; color: white; }
.darker-border-checkbox.form-check-input { border-color: #929292; }

/* ruleset chooser (#476): the card grid on wide screens, a single dropdown on a phone. The switch is
   the viewport, not a preference — at 480px and under, only the selected system's guidance and its
   coverage gap fit under one control, and comparing every card at once is no longer what a phone is
   for. The dropdown and the cards carry the same name and value, so the form posts the same system
   whichever one the viewport shows. */
.ruleset-mobile { display: none; }

@media (max-width: 480px) {
    .ruleset-desktop { display: none; }
    .ruleset-mobile { display: block; }
}

/* The sign-up trap field (#490): off screen rather than display:none, which a bot would skip. */
.signup-trap {
    position: absolute;
    left: -10000px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* Admin Overview (#501): tiles, funnel bars and the retention heatmap, in CSS with no chart library. */
.overview-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
    gap: 0.75rem;
}

.overview-tile {
    border: 1px solid var(--bs-border-color);
    border-radius: 0.5rem;
    padding: 0.75rem;
}

.overview-tile__value {
    font-size: 1.75rem;
    font-weight: 600;
}

.overview-funnel__row {
    display: grid;
    grid-template-columns: minmax(8rem, 12rem) 1fr auto;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.25rem;
}

.overview-funnel__bar {
    display: block;
    height: 0.9rem;
    border-radius: 0.25rem;
    background: var(--bs-primary);
}

.overview-cohorts__cell {
    background: rgba(var(--bs-primary-rgb), calc(var(--share) * 0.85));
}

/* The homebrew form (#529): each rule's kind in a column of its own, so the rows line up. */
/* A rule: its kind, then its fields wrapping among themselves, with the remove button held at the row's end. */
.hb-rule {
    display: grid;
    grid-template-columns: minmax(6rem, 10rem) minmax(0, 1fr) auto;
    align-items: start;
    gap: .5rem;
    margin-bottom: .35rem;
}

/* Level with the first line of fields when they wrap onto more. */
.hb-rule > .hb-rule-kind,
.hb-rule > .btn { margin-top: .3rem; }

.hb-rule-fields { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; min-width: 0; }

.hb-rule-fields > .form-select { max-width: 100%; }

/* A distance and its unit, read as one: "60 feet" (docs/HOMEBREW-EDITOR-CONTROLS.md E2). */
.hb-distance { width: 6rem; }

.hb-unit { color: var(--muted); }

/* A number or dice by level, one step a line (E4). */
.hb-steps { display: flex; flex-direction: column; gap: .35rem; flex-basis: 100%; }

.hb-step { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }

/* A problem said beside its field, on a line of its own below the row's fields (E7). */
.hb-field-error { flex-basis: 100%; margin: 0; font-size: .9rem; color: var(--danger); }

/*
   The homebrew editor in sections (the homebrew editor prototype), for every kind. A side list of
   sections, one panel shown at a time, and the last save as a player reads it beside them. A section a
   kind does not have simply has no radio, so its rules never match.

   The side list is a row of radio buttons placed before the list and the panels, so the general sibling
   combinator can show the one that is checked: no script, and every panel stays in the page, so the one
   form behind them still posts all of it. The radios are hidden but focusable, and arrow keys move
   between them as in any radio group; the label of the focused one carries the focus ring.
*/
.hb-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
    padding-bottom: 1rem;
    margin-bottom: 1.25rem;
    border-bottom: 1px solid var(--border);
}

.hb-header h1 { margin: .25rem 0 .35rem; }

.hb-eyebrow {
    font-family: var(--font-ui);
    font-size: .75rem;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--muted);
    margin: 0;
}

.hb-status {
    display: flex;
    flex-wrap: wrap;
    gap: .75rem;
    margin: 0;
    color: var(--muted);
}

.hb-saved { color: var(--ok); font-weight: 600; }

.hb-unsaved { font-family: var(--font-ui); font-size: .9rem; font-weight: 600; color: var(--accent); }

.hb-stale {
    font-size: .9rem;
    padding: .4rem .6rem;
    border-left: 3px solid var(--accent);
    background: var(--accent-soft);
}

.hb-leave {
    max-width: 28rem;
    padding: 1.25rem 1.5rem;
    color: var(--text);
    background: var(--bg-2);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
}

.hb-leave::backdrop { background: rgba(0, 0, 0, .6); }

.hb-leave h2 { font-size: 1.3rem; }

.hb-leave-actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: 1rem; }

.hb-actions { display: flex; align-items: center; gap: .5rem; }

.hb-editor {
    display: grid;
    grid-template-columns: 12rem minmax(0, 1fr) 20rem;
    gap: 1.75rem;
    align-items: start;
}

.hb-section-radio {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    pointer-events: none;
}

.hb-sections { display: flex; flex-direction: column; gap: .25rem; }

.hb-section-label {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: .5rem;
    min-height: 2.75rem;
    padding: 0 .85rem;
    border-radius: 8px;
    border: 1px solid transparent;
    cursor: pointer;
    /* A label, so it would otherwise be the small capitals over a field. */
    margin: 0;
    font-family: var(--font-body);
    font-size: 1.05rem;
    color: var(--text);
    text-transform: none;
    letter-spacing: 0;
}

.hb-section-label:hover { border-color: var(--border-strong); }

.hb-count { font-family: var(--font-ui); font-size: .8rem; font-weight: 600; color: var(--accent); }

.hb-panel { display: none; }

.hb-panel h2 { font-size: 1.4rem; margin-bottom: .75rem; }

#hb-s-basics:checked ~ .hb-panels .hb-panel-basics,
#hb-s-traits:checked ~ .hb-panels .hb-panel-traits,
#hb-s-features:checked ~ .hb-panels .hb-panel-features,
#hb-s-spellcasting:checked ~ .hb-panels .hb-panel-spellcasting,
#hb-s-spells:checked ~ .hb-panels .hb-panel-spells,
#hb-s-choices:checked ~ .hb-panels .hb-panel-choices,
#hb-s-offered:checked ~ .hb-panels .hb-panel-offered,
#hb-s-branches:checked ~ .hb-panels .hb-panel-branches,
#hb-s-description:checked ~ .hb-panels .hb-panel-description,
#hb-s-sharing:checked ~ .hb-panels .hb-panel-sharing,
#hb-s-advanced:checked ~ .hb-panels .hb-panel-advanced { display: block; }

#hb-s-basics:checked ~ .hb-sections .hb-section-label-basics,
#hb-s-traits:checked ~ .hb-sections .hb-section-label-traits,
#hb-s-features:checked ~ .hb-sections .hb-section-label-features,
#hb-s-spellcasting:checked ~ .hb-sections .hb-section-label-spellcasting,
#hb-s-spells:checked ~ .hb-sections .hb-section-label-spells,
#hb-s-choices:checked ~ .hb-sections .hb-section-label-choices,
#hb-s-offered:checked ~ .hb-sections .hb-section-label-offered,
#hb-s-branches:checked ~ .hb-sections .hb-section-label-branches,
#hb-s-description:checked ~ .hb-sections .hb-section-label-description,
#hb-s-sharing:checked ~ .hb-sections .hb-section-label-sharing,
#hb-s-advanced:checked ~ .hb-sections .hb-section-label-advanced {
    background: var(--accent-soft);
    color: var(--accent);
}

#hb-s-basics:focus-visible ~ .hb-sections .hb-section-label-basics,
#hb-s-traits:focus-visible ~ .hb-sections .hb-section-label-traits,
#hb-s-features:focus-visible ~ .hb-sections .hb-section-label-features,
#hb-s-spellcasting:focus-visible ~ .hb-sections .hb-section-label-spellcasting,
#hb-s-spells:focus-visible ~ .hb-sections .hb-section-label-spells,
#hb-s-choices:focus-visible ~ .hb-sections .hb-section-label-choices,
#hb-s-offered:focus-visible ~ .hb-sections .hb-section-label-offered,
#hb-s-branches:focus-visible ~ .hb-sections .hb-section-label-branches,
#hb-s-description:focus-visible ~ .hb-sections .hb-section-label-description,
#hb-s-sharing:focus-visible ~ .hb-sections .hb-section-label-sharing,
#hb-s-advanced:focus-visible ~ .hb-sections .hb-section-label-advanced {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* A trait folds to its name and what the app applies, and opens to be edited. */
.hb-trait {
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: .75rem;
}

.hb-trait > summary {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: .25rem 1rem;
    min-height: 3.25rem;
    padding: .5rem 1.1rem;
    cursor: pointer;
}

.hb-trait-name { font-weight: 600; font-size: 1.1rem; }

.hb-trait-summary { font-family: var(--font-ui); font-size: .85rem; color: var(--muted); }

.hb-trait-body { padding: 1rem 1.1rem 1.1rem; border-top: 1px solid var(--border); }

/* What else a choice could offer, folded away until asked for. */
.hb-options > summary { cursor: pointer; color: var(--accent); }

.hb-options { padding-left: .25rem; }

.hb-add { border-style: dashed; min-height: 2.75rem; }

.hb-branch {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    padding: .75rem 1rem;
    margin-bottom: .5rem;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.hb-danger {
    margin-top: 2rem;
    padding: 1rem 1.1rem;
    border: 1px solid rgba(214, 69, 69, .5);
    border-radius: var(--radius);
}

.hb-preview {
    background: var(--bg-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 1.1rem;
}

.hb-preview h2 { font-size: 1.4rem; margin: .4rem 0 .1rem; }

/* The explorer's entry, in a column a third its width: each fact's name above its value, and less padding. */
.hb-preview dl.row > dt,
.hb-preview dl.row > dd { flex: 0 0 100%; max-width: 100%; }
.hb-preview dl.row > dd { margin-bottom: .5rem; }
.hb-preview .card-body { padding: .85rem; }
.hb-preview .card-title { font-size: 1.1rem; }

/*
   Laid out by the room the page has, not the window's width, since the site's sidebar takes its share
   first. The form comes first: with less than about 72rem, the preview drops under it rather than squeeze
   it; with less than 48rem, the sections become a wrapping row above it. Inside the form, a row of fields
   stacks once it is narrower than its fields can be read at, whatever Bootstrap's window breakpoints say.
*/
.hb-page { container: hb-page / inline-size; }

.hb-panels { container: hb-form / inline-size; }

@container hb-page (max-width: 72rem) {
    .hb-editor { grid-template-columns: 11rem minmax(0, 1fr); }
    .hb-preview { grid-column: 2; }
}

@container hb-page (max-width: 48rem) {
    .hb-editor { grid-template-columns: minmax(0, 1fr); }
    .hb-preview { grid-column: auto; }
    .hb-sections { flex-direction: row; flex-wrap: wrap; }
}

@container hb-form (max-width: 36rem) {
    .hb-panel .row > [class*="col-md-"] { flex: 0 0 100%; max-width: 100%; }
    .hb-rule { grid-template-columns: minmax(0, 1fr) auto; }
    .hb-rule-kind { grid-column: 1 / -1; }
}

/* The homebrew list's search: words, a kind and a ruleset, in a row that wraps. */
.hb-library-search { display: flex; flex-wrap: wrap; align-items: flex-end; gap: .75rem 1rem; margin-bottom: 1rem; }

.hb-library-search label { display: block; margin-bottom: .2rem; }

/*
   A spell's casting time, range, components and duration, each named, in a row that wraps (the second
   review of October 2026); and an entry's text keeping the line breaks its author typed.
*/
.entry-stats { display: flex; flex-wrap: wrap; gap: .5rem 2rem; margin: 0 0 1rem; }

.entry-stats dt { font-family: var(--font-ui); font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); font-weight: 400; }

.entry-stats dd { margin: 0; }

.entry-text { white-space: pre-line; }

/*
   The Rule Explorer as a library: the ruleset switch, one search, a tab per section, and rows that say
   enough to choose by. The search is a plain GET form with one button, which sends the words and the
   filters together; filters do not send themselves on change, which would move a keyboard reader on
   at every arrow key.
*/
.lib-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: .75rem 1.5rem;
    margin-bottom: 1rem;
}

.lib-header h1 { margin: 0; }

.lib-rules { display: flex; flex-wrap: wrap; align-items: center; gap: .35rem; font-family: var(--font-ui); font-size: .9rem; }

.lib-rules-label { color: var(--muted); margin-right: .25rem; }

.lib-rule {
    padding: .35rem .75rem;
    border: 1px solid var(--border-strong);
    border-radius: 999px;
    color: var(--text);
    text-decoration: none;
}

.lib-rule:hover { border-color: var(--accent); color: var(--text); }

.lib-rule.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent-hover); font-weight: 600; }

.lib-search { margin-bottom: 1rem; }

.lib-query-label { display: block; margin-bottom: .3rem; }

.lib-query { display: flex; gap: .5rem; }

.lib-query input { flex: 1; min-width: 0; }

.lib-filters {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: .75rem 1rem;
    margin-top: .75rem;
    font-family: var(--font-ui);
}

.lib-filter label { display: block; margin-bottom: .2rem; }

.lib-filter select { min-width: 9rem; }

.lib-toggle { display: flex; align-items: center; gap: .4rem; min-height: 2rem; padding-left: 0; }

.lib-toggle .form-check-input { margin: 0; float: none; }

.lib-toggle label { margin: 0; text-transform: none; letter-spacing: 0; font-size: .95rem; color: var(--text); }

.lib-clear { font-size: .9rem; align-self: center; }

.lib-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: .25rem;
    border-bottom: 1px solid var(--border);
    margin-bottom: 1rem;
}

.lib-tab {
    padding: .55rem .9rem;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
    color: var(--muted);
    text-decoration: none;
}

.lib-tab:hover { color: var(--text); }

.lib-tab.active { color: var(--text); border-bottom-color: var(--accent); }

.lib-count { font-family: var(--font-ui); font-size: .8rem; font-weight: 600; color: var(--accent); margin-left: .25rem; }

.lib-browse {
    list-style: none;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
    gap: .75rem;
}

.lib-browse a {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: 1rem 1.1rem;
    background: var(--panel);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text);
    text-decoration: none;
}

.lib-browse a:hover { border-color: var(--accent); }

.lib-browse-name { font-family: var(--font-display); font-size: 1.15rem; }

.lib-summary { color: var(--muted); font-family: var(--font-ui); font-size: .9rem; }

.lib-group { font-size: 1.15rem; margin: 1.5rem 0 .5rem; }

.lib-results { list-style: none; padding: 0; margin: 0; }

.lib-results li { padding: .6rem 0; border-bottom: 1px solid var(--border); }

.lib-result-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: .4rem .6rem; }

.lib-result-name { font-size: 1.1rem; font-weight: 600; }

.lib-badge, .lib-tag {
    font-family: var(--font-ui);
    font-size: .72rem;
    letter-spacing: .04em;
    padding: .1rem .45rem;
    border-radius: 4px;
}

.lib-badge { border: 1px solid var(--border-strong); color: var(--muted); }

.lib-tag { border: 1px solid var(--accent-2); color: var(--text); }

.lib-detail { color: var(--muted); font-family: var(--font-ui); font-size: .88rem; margin-top: .15rem; }

.lib-snippet { color: var(--muted); font-size: .95rem; font-style: italic; margin-top: .15rem; }

.lib-empty { padding: 1.5rem; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); }

.lib-back { font-family: var(--font-ui); font-size: .9rem; margin-bottom: .25rem; }

@media (max-width: 700px) {
    .lib-query .btn { padding-left: .9rem; padding-right: .9rem; }
    .lib-filter { flex: 1 1 45%; }
    .lib-filter select { width: 100%; min-width: 0; }
    .lib-tab { padding: .45rem .6rem; }
}

/*
   The guided build's setup line and stepper (#605). Every stage in one row that scrolls sideways on a
   phone rather than wrapping into a second row, so the order reads left to right. A stage's state is in
   its words and its mark; the colours only repeat them.
*/
.build-setup { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem 1rem; margin-top: 1rem; }
.build-setup label { margin: 0; }
.build-level { width: auto; min-width: 5rem; }

.build-stepper { display: flex; gap: .25rem; list-style: none; margin: 1rem 0 0; padding: 0;
    border-bottom: 1px solid var(--border); overflow-x: auto; }
.build-stepper li { flex: 1 0 9rem; }
.build-step { width: 100%; display: flex; align-items: center; gap: .6rem; text-align: left; background: none;
    border: 0; border-bottom: 3px solid transparent; color: var(--text); padding: .5rem .4rem .7rem;
    min-height: 3.25rem; cursor: pointer; }
.build-step:hover { background: var(--accent-soft); }
.build-step:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.build-step.is-current { border-bottom-color: var(--accent); }
.build-step-mark { flex: 0 0 1.9rem; height: 1.9rem; border-radius: 50%; display: inline-flex;
    align-items: center; justify-content: center; border: 1.5px solid var(--border-strong); color: var(--muted);
    font-size: .9rem; }
.build-step.is-current .build-step-mark { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.build-step.is-done .build-step-mark { border-color: var(--ok); color: var(--ok); }
.build-step.needs-attention .build-step-mark { border-color: var(--warn); color: var(--warn); }
.build-step-label { display: block; font-weight: 600; }
.build-step-state { display: block; font-size: .85rem; color: var(--muted); }
.build-step.needs-attention .build-step-state { color: var(--warn); }
.build-part + .build-part { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--border); }

/* The review (#606): where the character stands, what is left, and a card per stage. */
.build-status { border-radius: var(--radius); padding: .75rem 1rem; margin-bottom: 1rem;
    background: rgba(224, 179, 65, .12); border: 1px solid rgba(224, 179, 65, .4); }
.build-status.is-ready { background: rgba(63, 174, 106, .12); border-color: rgba(63, 174, 106, .4); }
.build-tasks { list-style: none; padding: 0; margin: 0 0 1rem; }
.build-tasks li { display: flex; flex-wrap: wrap; gap: .25rem .75rem; align-items: baseline;
    padding: .4rem 0; border-top: 1px solid var(--border); }
.build-tasks li:first-child { border-top: 0; }
.build-card { border-top: 1px solid var(--border); padding: .9rem 0; }
.build-scores { display: grid; grid-template-columns: repeat(auto-fill, minmax(7.5rem, 1fr)); gap: .5rem;
    margin: .5rem 0 0; }
.build-scores div { background: var(--bg-3); border-radius: var(--radius); padding: .4rem .6rem; }
.build-scores dt { font-weight: 400; color: var(--muted); font-size: .85rem; }
.build-scores dd { margin: 0; font-size: 1.15rem; }
.build-return { display: flex; flex-wrap: wrap; gap: .5rem 1rem; align-items: center; justify-content: space-between;
    background: var(--accent-soft); border-radius: var(--radius); padding: .5rem 1rem; margin-bottom: 1rem; }
.build-print summary { cursor: pointer; color: var(--muted); }

/* A chosen spell or feat, described where it was chosen (#607). */
.build-described { background: var(--bg-3); border-radius: var(--radius); padding: .5rem .75rem; }
.build-described summary { cursor: pointer; font-weight: 600; }
.build-described-stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); gap: .25rem .75rem;
    margin: .5rem 0; }
.build-described-stats dt { font-weight: 400; color: var(--muted); font-size: .85rem; }
.build-described-stats dd { margin: 0; }

/*
   The guided build beside its summary (#608), and the ability breakdown. The summary sits beside the form
   only where there is room for both, and under it otherwise, so it never squeezes the questions.
*/
.build-layout { display: flex; flex-wrap: wrap; gap: 1.5rem 2rem; align-items: flex-start; margin-top: 1rem; }
.build-main { flex: 999 1 34rem; min-width: 0; }
.build-summary { flex: 1 1 16rem; background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 1rem 1.25rem; }
.build-summary-list { display: grid; gap: .4rem; margin: 0 0 .75rem; }
.build-summary-list div { display: flex; justify-content: space-between; gap: 1rem; }
.build-summary-list dt { font-weight: 400; color: var(--muted); }
.build-summary-list dd { margin: 0; font-weight: 600; text-align: right; }
.build-abilities { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: .6rem; }
.build-ability { background: var(--bg-3); border-radius: var(--radius); padding: .6rem .8rem; }
.build-ability-name { font-weight: 600; }
.build-ability-total { font-family: var(--font-display); font-size: 1.6rem; line-height: 1.2; }

/* The story fields (#609): two to a row where there is room, the backstory across the whole width. */
.build-story { display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); gap: 1rem 1.25rem; }
.build-story > div:last-child { grid-column: 1 / -1; }

/* Story suggestions (#617): the steer and fill panel, and each field's Suggest and Undo beside its label. */
.build-suggest { border: 1px dashed var(--bs-border-color); border-radius: .5rem; padding: .75rem 1rem; }
.build-suggest-actions { display: flex; gap: .75rem; margin: -.25rem 0 .35rem; }
