﻿/* ---- Typography ---------------------------------------------------------------------------
   No font is named in this file, and nothing here branches on language. culture.js resolves the
   UI font for the active language and publishes it as --fjq-ui-font on <html>, so adding a
   language never touches this stylesheet. To change a face, edit the FONTS table at the top of
   culture.js.

   Selectors are html[dir] rather than :root deliberately. MudThemeProvider injects its own :root
   block into the DOM at runtime, which lands after this stylesheet in document order — at equal
   specificity it would win. html[dir] outranks :root, so these hold regardless of injection
   order. culture.js always sets dir, so html[dir] always matches. */

html[dir] body {
    font-family: var(--fjq-ui-font, 'Segoe UI', Tahoma, Helvetica, Arial, sans-serif);
}

/* MudBlazor renders its typography from these custom properties, so redefining them reaches
   every MudText, button and input at once instead of chasing individual components. */
html[dir] {
    --mud-typography-default-family: var(--fjq-ui-font);
    --mud-typography-h1-family: var(--fjq-ui-font);
    --mud-typography-h2-family: var(--fjq-ui-font);
    --mud-typography-h3-family: var(--fjq-ui-font);
    --mud-typography-h4-family: var(--fjq-ui-font);
    --mud-typography-h5-family: var(--fjq-ui-font);
    --mud-typography-h6-family: var(--fjq-ui-font);
    --mud-typography-subtitle1-family: var(--fjq-ui-font);
    --mud-typography-subtitle2-family: var(--fjq-ui-font);
    --mud-typography-body1-family: var(--fjq-ui-font);
    --mud-typography-body2-family: var(--fjq-ui-font);
    --mud-typography-button-family: var(--fjq-ui-font);
    --mud-typography-caption-family: var(--fjq-ui-font);
    --mud-typography-overline-family: var(--fjq-ui-font);
}

/* Code keeps the monospace face in both directions. Under RTL it also stays left-to-right:
   identifiers and code are not Arabic text, and letting them mirror mangles them. */
code,
pre,
.markdown-body pre {
    font-family: var(--fjq-mono-font, Consolas, 'Courier New', monospace);
}

html[dir="rtl"] code,
html[dir="rtl"] pre,
html[dir="rtl"] .markdown-body pre {
    direction: ltr;
    text-align: left;
}

/* Live numeric readouts — countdowns, timers, anything that reticks in place.
   font-variant-numeric: tabular-nums alone is not enough: it is a request for the font's tnum
   OpenType feature, and a face that does not ship tnum makes it a silent no-op, leaving
   proportional digits that change width as the value ticks. Roboto has tnum, Tajawal does not,
   so the same markup was steady in English and jittered in Arabic. The monospace stack is
   tabular by construction, so this holds for every language including ones not yet added —
   which keying off the language or direction would not. Same reasoning as the code rule above:
   a clock readout is a machine value, not prose.

   Prefixed html[dir] for the same reason the typography block at the top of this file is, and it
   is not optional here: index.html loads this stylesheet BEFORE MudBlazor.min.css, and MudBlazor
   sets both font-family and letter-spacing on .mud-typography-caption. A bare .fjq-numeric ties
   that on specificity (0,1,0) and loses on document order — silently, with the element simply
   keeping the UI font. html[dir] .fjq-numeric is 0,2,1 and wins regardless of link order. */
html[dir] .fjq-numeric {
    font-family: var(--fjq-mono-font, Consolas, 'Courier New', monospace);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.5px;
}

/* Keeps a negative (expired) readout as "-04:32" rather than letting bidi reorder the sign
   onto the other side of the digits. */
html[dir="rtl"] .fjq-numeric {
    direction: ltr;
}

/* ---- Right-to-left ---------------------------------------------------------------------
   MudRTLProvider (see MainLayout) flips MudBlazor's own components. The rules below cover what
   it does not: the hand-written CSS in this file that would otherwise hardcode a side. Keyed
   off the dir attribute culture.js sets on <html>, so none of it needs a C# flag. */

/* Mirrors a directional glyph — a back/next arrow or chevron — for the reading direction.
   MudRTLProvider moves a StartIcon to the trailing side but does not touch the glyph itself, so
   an unmarked back arrow still points left under RTL, where left is forwards. Opt in per icon:
   only glyphs meaning "the way you came" or "the way ahead" should turn — a folder or a search
   icon must not.

   Put the class on a MudIcon (first selector) or on the MudButton carrying it (the other two,
   which reach the icon without flipping the label). A MudIconButton renders neither the class
   on the glyph nor a mud-button-icon-start, so it needs the last selector — there is no label
   beside it to protect. Deliberately not a bare .fjq-mirror-icon:
   that would also match the button root and mirror its text. Prefixed html[dir] for the
   specificity reason described at the top of this file. */
html[dir="rtl"] .mud-icon-root.fjq-mirror-icon,
html[dir="rtl"] .fjq-mirror-icon .mud-button-icon-start,
html[dir="rtl"] .fjq-mirror-icon .mud-button-icon-end,
html[dir="rtl"] .fjq-mirror-icon .mud-icon-root {
    transform: scaleX(-1);
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

#blazor-error-ui {
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        inset-inline-end: 0.75rem;
        top: 0.5rem;
    }

    /* The bar is wordless (see index.html), so the two glyphs need the gap the sentence used to
       provide. Logical property: the reload control follows the warning mark in both directions. */
    #blazor-error-ui .reload {
        margin-inline-start: 0.75rem;
        font-size: 1.1rem;
        text-decoration: none;
    }

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

code {
    color: #c02d76;
}

/* Rendered Markdown page bodies (see PageContentView). Keeps headings, lists, code and
   tables sensibly sized inside MudBlazor cards rather than using raw browser defaults. */
.markdown-body {
    overflow-wrap: anywhere;
}

    .markdown-body > :first-child {
        margin-top: 0;
    }

    .markdown-body > :last-child {
        margin-bottom: 0;
    }

    .markdown-body h1 { font-size: 1.6rem; margin: 1.2rem 0 0.6rem; }
    .markdown-body h2 { font-size: 1.35rem; margin: 1.1rem 0 0.5rem; }
    .markdown-body h3 { font-size: 1.15rem; margin: 1rem 0 0.5rem; }
    .markdown-body h4, .markdown-body h5, .markdown-body h6 { font-size: 1rem; margin: 1rem 0 0.5rem; }

    .markdown-body p,
    .markdown-body ul,
    .markdown-body ol,
    .markdown-body blockquote,
    .markdown-body table {
        margin: 0 0 0.85rem;
    }

    /* Logical properties throughout so rendered Markdown mirrors with the document direction
       instead of needing a parallel [dir="rtl"] rule for every side. */
    .markdown-body ul, .markdown-body ol {
        padding-inline-start: 1.5rem;
    }

    .markdown-body blockquote {
        border-inline-start: 3px solid var(--mud-palette-lines-default, #e0e0e0);
        padding-inline-start: 1rem;
        color: var(--mud-palette-text-secondary, #616161);
    }

    /* Light themes: a translucent dark overlay so the block sits a touch DARKER than the card
       surface (background-gray reads lighter than the surface in some light themes). Dark themes
       keep the solid background-gray panel, which already contrasts nicely. */
    .markdown-body pre {
        background: rgba(0, 0, 0, 0.06);
        padding: 0.75rem 1rem;
        border-radius: 8px;
        overflow-x: auto;
    }

    .fjq-theme-dark .markdown-body pre {
        background: var(--mud-palette-background-gray, rgba(255, 255, 255, 0.06));
    }

    .markdown-body pre code {
        color: inherit;
    }

/* Syntax highlighting tokens (ColorCode class-based output), themed to match GitHub.
   Role colors are defined as variables here (GitHub light palette) and overridden for dark
   mode under .fjq-theme-dark (GitHub dark palette, applied to MudLayout by MainLayout); each
   token class just references a role variable. Untokenised text inherits the code text color. */
    .markdown-body {
        --code-comment: #6e7781;
        --code-keyword: #cf222e;
        --code-string: #0a3069;
        --code-constant: #0550ae;
        --code-entity: #8250df;
        --code-tag: #116329;
    }

    .fjq-theme-dark .markdown-body {
        --code-comment: #8b949e;
        --code-keyword: #ff7b72;
        --code-string: #a5d6ff;
        --code-constant: #79c0ff;
        --code-entity: #d2a8ff;
        --code-tag: #7ee787;
    }

    .markdown-body pre .keyword,
    .markdown-body pre .preprocessorKeyword,
    .markdown-body pre .controlKeyword,
    .markdown-body pre .pseudoKeyword {
        color: var(--code-keyword);
    }

    .markdown-body pre .string,
    .markdown-body pre .stringCSharpVerbatim,
    .markdown-body pre .stringEscape,
    .markdown-body pre .htmlAttributeValue,
    .markdown-body pre .xmlAttributeValue,
    .markdown-body pre .cssPropertyValue,
    .markdown-body pre .jsonString {
        color: var(--code-string);
    }

    .markdown-body pre .comment,
    .markdown-body pre .xmlComment,
    .markdown-body pre .xmlDocComment,
    .markdown-body pre .htmlComment,
    .markdown-body pre .xmlDocTag {
        color: var(--code-comment);
        font-style: italic;
    }

    .markdown-body pre .type,
    .markdown-body pre .className,
    .markdown-body pre .namespace,
    .markdown-body pre .cssSelector {
        color: var(--code-entity);
    }

    .markdown-body pre .htmlElementName,
    .markdown-body pre .xmlName {
        color: var(--code-tag);
    }

    .markdown-body pre .number,
    .markdown-body pre .jsonNumber,
    .markdown-body pre .htmlAttributeName,
    .markdown-body pre .xmlAttribute,
    .markdown-body pre .cssPropertyName,
    .markdown-body pre .attribute,
    .markdown-body pre .jsonKey {
        color: var(--code-constant);
    }

    .markdown-body pre .markdownHeader {
        color: var(--code-constant);
    }

    .markdown-body pre .bold,
    .markdown-body pre .markdownHeader {
        font-weight: 600;
    }

    .markdown-body pre .italic {
        font-style: italic;
    }

    .markdown-body img {
        max-width: 100%;
        height: auto;
    }

    /* A wide table scrolls sideways inside its own box instead of crushing its columns — the same
       deal <pre> gets. The wrapper div is emitted by ScrollableTableRenderer; the margin moves onto
       it so the gap below the block is unchanged. */
    .md-table-scroll {
        overflow-x: auto;
        margin: 0 0 0.85rem;
    }

    .markdown-body table {
        width: 100%;
        border-collapse: collapse;
    }

    .md-table-scroll > table {
        margin: 0;
    }

    /* .markdown-body sets overflow-wrap:anywhere so a long URL in a paragraph can't blow out the
       card. Inside a table that same rule is what does the crushing: `anywhere` counts toward a
       column's minimum width, so auto layout is told every column can shrink to one character and
       happily fits twelve of them into the card. `break-word` still breaks a word too long to fit
       but leaves the minimum at the longest word, so the table asks for its natural width and
       overflows into the scroller instead. */
    .markdown-body th,
    .markdown-body td {
        border: 1px solid var(--mud-palette-lines-default, #e0e0e0);
        padding: 8px 12px;
        text-align: start;
        overflow-wrap: break-word;
    }

    /* table-hover is a theme-relative overlay (subtle dark in light themes, subtle light in
       dark themes), so the header sits just off the card surface in both — unlike the absolute
       background-gray, which is near-white in light and heavy-dark in dark. */
    .markdown-body th {
        background: var(--mud-palette-table-hover, rgba(128, 128, 128, 0.08));
        font-weight: 600;
    }

    .markdown-body tbody tr:hover {
        background: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.04));
    }

/* ---- Admin lists that fold to a list on a phone -------------------------------------------
   Below md the users and roles tables stop being tables: each row becomes one tappable list
   item — tinted circle, name, kebab — and the table head goes with it, since column labels have
   nothing left to label once the row is a single cell. Shared here rather than repeated in a
   <style> block per page: the second page to want it is what makes it a pattern. The breakpoint
   is MudBlazor's own md (960px), so these rules and the d-md-* classes on the cells switch on
   the same width. */

@media (max-width: 959.98px) {
    .fjq-list-table thead {
        display: none;
    }
}

.fjq-list-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    min-height: 64px;
    cursor: pointer;
}

/* min-width: 0 is what lets the ellipsis below happen: a flex item's default min-width is auto,
   meaning "never shrink below the content", so a long address would push the row wider than the
   screen instead of truncating inside it. */
.fjq-list-lines {
    flex: 1;
    min-width: 0;
}

.fjq-list-primary,
.fjq-list-secondary {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.fjq-list-primary {
    font-weight: 600;
    line-height: 1.35;
}

.fjq-list-secondary {
    font-size: 0.8125rem;
    line-height: 1.35;
    color: var(--mud-palette-text-secondary);
}

/* One line, clipped with an ellipsis when it will not fit. For values with no useful wrap point —
   a file name, a path — where wrapping makes a ragged two- or three-line block that costs more
   than the tail it saved. Pair it with a title= carrying the full value, since this hides the end
   of the text from a reader who may need it.

   min-width: 0 is part of the utility, not an extra: as a flex item the default min-width is auto,
   meaning "never shrink below the content", and without it the element never narrows far enough to
   trigger the ellipsis at all. Its flex ANCESTORS need the same, which is a per-layout concern the
   utility cannot reach — see the note on the link in EntityAttachments for how that bites. */
.fjq-truncate {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The ancestor half of the rule above, for a disclosure widget whose title carries a file name.

   MudExpansionPanel puts TitleContent inside .mud-expand-panel-text, which MudBlazor declares as
   `flex: 1 1 auto` with no min-width. A flex item's automatic minimum size is its content, so that
   wrapper refuses to go narrower than the whole title — the header grows past the panel, the name
   spills out of it and the chevron is pushed off the end. No amount of .fjq-truncate inside can
   help: it shrinks within the box it is given, and this box never shrinks.

   Scoped to panels that opt in with .fjq-file-panel rather than applied to every expansion panel
   in the app: min-width: 0 only permits shrinking, but a title elsewhere may be relying today on
   forcing its panel wider, and that is not a change to make underneath someone.

   Prefixed html[dir] for the reason the top of this file gives — index.html loads this stylesheet
   BEFORE MudBlazor.min.css, so ties are lost on document order. */
html[dir] .fjq-file-panel .mud-expand-panel-text {
    min-width: 0;
}

/* The chevron is the other thing in that header, and it must not be what gives. */
html[dir] .fjq-file-panel .mud-expand-panel-icon {
    flex-shrink: 0;
}

/* The outcome at the trailing edge of a row — a score beside its grade, or a status. One line:
   stacked, "0%" over "F" reads as a wrapped line that ran out of room rather than as two facts,
   and the desktop row shows the pair side by side too.

   A flex row rather than one text run, which is what keeps the two apart under RTL: "0% F" in a
   single run puts a Latin grade and a number either side of a neutral space, and bidi is free to
   reorder them. Two elements cannot be reordered against each other — the row itself mirrors, the
   way every other row in the app does. */
.fjq-list-trailing {
    flex-shrink: 0;
    display: flex;
    align-items: baseline;
    gap: 6px;
    white-space: nowrap;
    line-height: 1.3;
}

/* One hue per record (TintedAvatar picks it), carried in as --fjq-avatar-h and turned into a
   pair here: a soft wash for the circle and a deep version of the SAME hue for the initials.
   Same-hue rather than white-on-saturated because ten saturated circles on one screen read as a
   toy, and because white text would have to hold contrast against every hue in the set at one
   lightness — generating each pair from its own hue lands them all at the same contrast.

   Light first, then re-stated for dark off the .fjq-theme-dark class MainLayout puts on
   MudLayout: the app has fourteen themes and no hardcoded colour could serve them all, but a hue
   rotated to the right lightness can.

   html[dir] for the reason the top of this file gives — index.html loads this stylesheet BEFORE
   MudBlazor.min.css, so a tie on specificity with MudBlazor's own .mud-avatar-* colour classes
   would be lost on document order. */
html[dir] .mud-avatar.fjq-avatar {
    background-color: hsl(var(--fjq-avatar-h, 210), 52%, 87%);
    color: hsl(var(--fjq-avatar-h, 210), 45%, 30%);
    font-weight: 600;
    font-size: 0.9rem;
}

html[dir] .fjq-theme-dark .mud-avatar.fjq-avatar {
    background-color: hsl(var(--fjq-avatar-h, 210), 32%, 27%);
    color: hsl(var(--fjq-avatar-h, 210), 60%, 78%);
}

/* The leading element for a row that stands for a thing rather than a person — a role, and
   whatever else follows it. Deliberately NOT the circle above: a circle of coloured initials is
   the person convention wherever it appears, so on a role it reads as a user account. Square-ish
   corners and one monochrome glyph say "an entity" instead, and carry no false identity.

   40px to match the avatar it sits in line with, so a phone moving between the two admin lists
   keeps the same rhythm. primary-hover is MudBlazor's own low-alpha primary, so the tile follows
   whichever of the fourteen themes is on without naming a colour. */
.fjq-list-tile {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 10px;
    background-color: var(--mud-palette-primary-hover);
}

/* ---- Article reader (the hub's Read tab) ----------------------------------------------------
   Two costs stand between a long article and a smooth scroll: fetching bodies nobody is looking
   at, and rendering the ones that have been fetched. The slice endpoint handles the first; this
   handles the second.

   content-visibility: auto lets the browser skip style, layout and paint for a page that is
   scrolled out of view, and pick it up again on approach. The component stays mounted and its
   Markdown stays converted — only the work the screen cannot show is skipped — so nothing has to
   be re-fetched or re-parsed when it scrolls back.

   contain-intrinsic-size is the placeholder height used while a page is being skipped, and is
   what stops the scrollbar lurching as pages drop in and out. The `auto` keyword makes the
   browser remember each page's real height once measured, so the estimate only applies to pages
   that have never been on screen. Browsers without support simply render every page as before. */
.fjq-reader-page {
    content-visibility: auto;
    contain-intrinsic-size: auto 900px;
}

/* The reader scrolls with the document rather than inside a fixed-height pane — no guess at the
   surrounding chrome to get wrong, and a phone keeps its native scrolling. The position readout
   sticks to the top so it survives that. */
/* The reader's own bar: page count, Generate questions, Back to top. Sticky, so those two stay
   reachable at any depth in a long article — which is the whole point of it and is exactly what
   `top: 0` broke. The app bar is fixed and occupies the top of the *viewport*, so a sticky element
   pinned to 0 parks underneath it and never comes back: the bar was there, unclickable, from the
   first scroll onwards. It has to stop where the app bar ends.

   --mud-appbar-height is what MudBlazor's own fixed chrome measures itself against, with the
   fallback matching MudMainContent's mt-16. The two overrides mirror MudBlazor's toolbar rules —
   below sm the bar is 7/8 of that height in portrait and 3/4 in landscape — so the reader bar
   lands flush against it rather than leaving a strip of moving text above itself. */
.fjq-reader-bar {
    position: sticky;
    top: var(--mud-appbar-height, 64px);
    z-index: 3;
    background-color: var(--mud-palette-surface);
}

@media (max-width: 599px) and (orientation: portrait) {
    .fjq-reader-bar {
        top: calc(var(--mud-appbar-height, 64px) - var(--mud-appbar-height, 64px) / 8);
    }
}

@media (max-width: 599px) and (orientation: landscape) {
    .fjq-reader-bar {
        top: calc(var(--mud-appbar-height, 64px) - var(--mud-appbar-height, 64px) / 4);
    }
}

/* The reader's selection toolbar. Fixed, because it is positioned against a viewport rect the
   browser measured — the page scrolling under it is exactly when it gets withdrawn, so it never
   has to track the document.

   z-index sits in the gap MudBlazor leaves between its popover (1200) and its app bar (1300): above
   every bit of page content, below the app bar so a passage near the top of the window does not put
   a button over the navigation, and well below dialogs (1400) so opening one covers the toolbar
   rather than leaving it floating over the passage that launched it. */
.fjq-passage-toolbar {
    position: fixed;
    z-index: 1250;
    border-radius: 10px;
    box-shadow: var(--mud-elevation-8);
}

/* ---- Passage questions dialog ---------------------------------------------------------------
   The dialog is fullscreen because it holds a passage, a config bar and a stack of cards with
   four- and six-line text fields. That is room to work in, not permission to set text the width of
   a monitor: a question field 200 characters across is worse to read and worse to edit than one at
   a column width, so the content is capped and centred inside the larger box. */
.fjq-qgen-dialog {
    max-width: 920px;
    margin-inline: auto;
}

/* The dialog drops to the page background tone so the cards inside it, which are surface, read as
   raised sections against it — the same three-tone nesting StudyTopic gets for free by being a page
   on the page background. A dialog is surface by default, which made a surface card inside one
   invisible but for its shadow.

   :has() rather than a class on the dialog, because MudDialog's own Class parameter is not
   documented to land on .mud-dialog and the three regions inside it (title, content, actions) carry
   no background of their own — colouring the root is what reaches all three. It also takes the
   selector to (0,2,0), which beats MudBlazor's .mud-dialog on specificity rather than on link
   order, and app.css loses that order (see the note at the top of this file).

   **Descendant, not a child combinator.** .mud-dialog-content is NOT a direct child of .mud-dialog:
   MudBlazor wraps it in an unclassed element after the title, which its own
   `.mud-dialog .mud-dialog-title+*>.mud-dialog-content` rule gives away. `:has(> …)` matches nothing
   here and fails silently, leaving the dialog its default surface colour.

   The same nesting is why ContentClass="pa-4" is inert, incidentally: MudBlazor sets the content's
   padding from `.mud-dialog .mud-dialog-content`, at (0,2,0), which a utility class cannot outrank.
   The 24px sides and 8px top/bottom on this dialog are MudBlazor's, not ours.

   Palette variables, not a dark literal: on a dark theme Background is genuinely darker than
   Surface, which is the effect asked for, and on a light one the pair stays light. Three of the
   light themes define Background and Surface as the same colour, and there the separation falls
   back to the cards' elevation shadow — correct, if quieter. */
.mud-dialog:has(.fjq-qgen-dialog-content) {
    background-color: var(--mud-palette-background);
}

/* Source material set as a pull quote, not as a fourth grey box: with the config bar and every
   draft card already rounded rectangles, only the accent edge says which block is the text the
   questions came from. Logical border and radii, so an Arabic passage is accented on its right
   without a dir rule.

   No max-height and no scroller, and nothing in this dialog is sticky (decided 2026-09-01, see the
   header comment in GenerateQuestionsDialog.razor). The whole thing is one document that scrolls
   once: the passage grows to whatever it holds, the cards follow it, and there is never a region
   that holds still while another moves under it. */
.fjq-qgen-dialog__quote {
    padding: 12px 16px;
    background-color: var(--mud-palette-surface);
    border-inline-start: 3px solid var(--mud-palette-primary);
    border-radius: 12px;
    border-start-start-radius: 3px;
    border-end-start-radius: 3px;
}

/* The generator's own card. It matches the draft cards deliberately — same elevation, same radius,
   same surface tone against the dialog's darker ground — because the dialog does two things and
   these are the two kinds of card it does them with: one that makes questions, then the ones that
   review them. Margins only here; MudCard brings its own paper. */
.fjq-qgen-dialog__toolbar {
    margin-top: 16px;
    margin-bottom: 16px;
}

/* Everything the generator reports lands here, and the band holds its height whether occupied or
   not: it fills during a generation and empties after a save, and if it collapsed when empty the
   row above would resize under a pointer that is on Generate. */
.fjq-qgen-dialog__status {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 20px;
    min-height: 28px;
    margin-top: 10px;
}

.fjq-qgen-dialog__status-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/* An empty review list is most of the dialog, so it is composed rather than left as a sentence in
   the top-left corner of a large blank area. */
.fjq-qgen-dialog__empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 56px 16px;
    text-align: center;
}

.fjq-qgen-dialog__empty .mud-icon-root {
    margin-bottom: 6px;
    opacity: 0.28;
}

/* ---- Generated question draft card ----------------------------------------------------------
   Used by GenerateQuestionsDialog, from a passage or from a whole article. It was shared with the
   routed QuestionsFromArticle screen, whose cards sat in a SplitPane pane that dragged down to
   320px; nothing below needs horizontal room to lay out, which is a habit worth keeping.

   The arrangement follows StudyTopic's /questions route on purpose — see the component's header
   comment. Three tones nested outermost to innermost: the page or dialog ground, the card's
   surface, and the insets the answer and the source row sit in.

   Note the rules that carry .mud-paper. app.css is linked BEFORE MudBlazor.min.css (index.html line
   44 against line 72), so a bare .fjq-draft-card--saved ties .mud-paper on specificity and loses on
   document order — silently, with the card simply staying the surface colour. */
.fjq-draft-card__head {
    display: flex;
    align-items: center;
    gap: 8px;
    /* Held whether or not the discard button is in it, so a card does not change height at the
       moment it is saved and the button goes away. */
    min-height: 34px;
    margin-bottom: 4px;
}

/* The question is the headline of the card, as it is on StudyTopic, which sets its markdown to
   1.15rem/500. A shade smaller here because this one is being edited rather than read.

   Two class names deep: MudBlazor's own .mud-input-root sets a font-size on the same element, and a
   single class would tie it and lose on link order. */
.fjq-draft-card .fjq-draft-card__question textarea {
    font-size: 1.1rem;
    font-weight: 500;
}

/* The recessed tone inside a card: the answer panel and the source-page row both sit in it, so the
   card reads as one light band — the question, its headline — over two darker ones that support it.

   Background rather than background-gray, so the insets match the dialog's own ground exactly and
   the card is the only thing in the box at the surface tone. On the three light themes whose
   palettes give Background and Surface the same value this tone vanishes and the insets read flat;
   background-gray is the value to come back to if that matters more than the match.

   .mud-paper is not decoration on this selector. These are MudPapers, and .mud-paper sets
   background-color: surface at (0,1,0) — a bare .fjq-draft-card__inset ties it and loses on link
   order, leaving the panel at the card's own colour with no error anywhere. The fields inside need
   nothing: a Variant.Text input is `background: none`, so it shows whatever the panel is. */
.mud-paper.fjq-draft-card__inset {
    border-radius: 12px;
    background-color: var(--mud-palette-background);
}

.fjq-draft-card__badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/* Discard was the only coloured thing on every card, above content nobody had read yet. It keeps
   its place and its tooltip and takes the error colour under the pointer, where the intent is no
   longer in doubt. The descendant selector is what beats .mud-icon-button-default on order. */
.fjq-draft-card .fjq-draft-card__discard {
    color: var(--mud-palette-text-secondary);
}

.fjq-draft-card .fjq-draft-card__discard:hover {
    color: var(--mud-palette-error);
}

/* A saved card is settled, not merely disabled. The fields grey out on their own, which says "you
   cannot type here" rather than "this one is in the database".

   The border is declared in full: the card is elevated rather than outlined, so it has no border
   style to take a colour, and border-color alone would do nothing.

   color-mix against the surface rather than a fixed rgba: the tint is then computed from whatever
   success is in the active palette, and stays a tint on a dark one instead of becoming a wash. */
.mud-paper.fjq-draft-card--saved {
    border: 1px solid var(--mud-palette-success);
    background-color: color-mix(in srgb, var(--mud-palette-success) 7%, var(--mud-palette-surface));
}

.mud-paper.fjq-draft-card--error {
    border: 1px solid var(--mud-palette-error);
}

.fjq-draft-card__error {
    display: flex;
    align-items: flex-start;
    gap: 6px;
    margin-top: 8px;
}

/* ---- Dual-tone nav icons --------------------------------------------------------------------
   The drawer's fourteen icons are drawn from the palette rather than from fixed colours, the same
   way BrandMark is: the container shape takes the theme's primary, the part that says what the
   item *is* takes its secondary. Switching theme re-inks all of them with no C# involved, because
   MudIcon injects the icon string as the children of its own <svg> and these classes resolve
   against the custom properties MudThemeProvider publishes.

   Class rules rather than fill="" attributes on the elements themselves: MudBlazor sets
   fill:currentColor on the <svg> (.mud-icon-root.mud-svg-icon) and the children inherit it, so a
   rule on the child is what overrides it. It also means an element that was given no fjq-ic-*
   class does not fail — it quietly comes out in the nav link's text colour, which is the fastest
   way to spot the one that was missed.

   The soft pair is the same variable at reduced opacity rather than --mud-palette-*-lighten, for
   the reason BrandMark records: most palettes in ThemeDefinitions set Primary/Secondary and leave
   the lighten/darken shades at MudBlazor's defaults, which still carry the *default* theme's hue.

   A theme whose Primary and Secondary sit close together — Aurora, #88C0D0 against #81A1C1 —
   renders these closer to mono than to two tones. That is a property of that palette, not of the
   icons: every glyph reads from its silhouette alone, so a collapsed pair costs the emphasis and
   nothing else. It is also no worse than what those links looked like as single-colour icons. */
.fjq-ic-primary {
    fill: var(--mud-palette-primary);
}

.fjq-ic-primary-soft {
    fill: var(--mud-palette-primary);
    opacity: 0.38;
}

.fjq-ic-accent {
    fill: var(--mud-palette-secondary);
}

.fjq-ic-accent-soft {
    fill: var(--mud-palette-secondary);
    opacity: 0.45;
}

/* The admin block keeps the separation it had when those links were Color.Tertiary, but takes it
   from the two colours the theme actually defines: the tones are swapped, so an admin icon reads
   as the inverse of a learner one. Tertiary is set by five of the fifteen palettes and falls back
   to MudBlazor's default teal in the other ten, where it belongs to no theme on the list — which
   is why the swap is used instead of a third colour. */
.fjq-nav-admin .fjq-ic-primary,
.fjq-nav-admin .fjq-ic-primary-soft {
    fill: var(--mud-palette-secondary);
}

.fjq-nav-admin .fjq-ic-accent,
.fjq-nav-admin .fjq-ic-accent-soft {
    fill: var(--mud-palette-primary);
}


/* ---- Tab headers on the three content hubs ---------------------------------------------------

   The hubs label their tabs from sm up and show the icon alone below that. The switch has to be a
   media query rather than MudHidden, because MudTabs measures each tab exactly once — on its first
   render — and then positions the active-tab slider as a *percentage* of the summed widths
   (SetSliderState). MudHidden resolves its breakpoint through JS interop, so the labels arrived a
   render later: MudTabs had already measured four equal, label-less tabs and read tab 4 of 4 as
   75%–100% of a strip whose tabs are in truth uneven. The slider sat left of every tab but the
   first, where 0%–25% happens to land about right, and below sm — where the tabs really are equal
   — nothing was ever wrong. A media query applies before the first paint, so what MudTabs measures
   is final.

   fjq-tab-icon carries what MudBlazor's mud-tab-icon-text carries, the 8px gap towards the label,
   but only at the widths where there is a label for it to sit beside. It is qualified by .mud-tab
   to outrank MudBlazor's own .mud-icon-root, which sets a 4px gap at the same specificity and is
   loaded after this file — unqualified, the gap silently halved. */
.fjq-tab-label {
    display: none;
}

@media (min-width: 600px) {
    .fjq-tab-label {
        display: inline;
    }

    .mud-tab .fjq-tab-icon {
        margin-inline-end: 8px;
    }
}
