/* Hallmark · macrostructure: Split Studio · H2 hero knobs: ratio=6/6, right=code figure, divider=negative space
 * theme: custom · vibe: "compile-time, cool, precise, .NET" · paper: oklch(99% 0.006 277) · accent: oklch(47% 0.21 277)
 * display: Geist · body: Geist · mono: JetBrains Mono · axes: light / geometric-sans / cool
 * nav: N9 edge-aligned minimal · footer: Ft2 inline single line · enrichment: none · studied: no
 * contrast: pass (40-41) · slop: pass (42-45) · honest: pass (46) · chrome: pass (47)
 * tokens: pass (48) · responsive: pass (49) · mobile: pass (34, 49, 50-57)
 */

/* Every value here is a var() from css/tokens.css. A literal colour, font stack, or duration in
   this file is a defect: it is how a design system erodes into a freestyle over three edits. */

/* --- Base ---------------------------------------------------------------- */

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* `clip`, not `hidden`: hidden creates a scroll container, which breaks the sticky documentation
   rail and can trap focus on an overflowing element. */
html {
    overflow-x: clip;
    scroll-behavior: smooth;
    /* Anchored headings must not land under the header when it is sticky at wide widths. */
    scroll-padding-top: var(--space-3xl);
    /* `color-scheme` used to be declared here, for the things this stylesheet does not paint:
       scrollbars, the canvas behind the page, and a form control's default rendering. It now lives
       on :root in css/tokens.css, where it also resolves every light-dark() pair and carries the
       reader's override. Declaring it in both places would let the two drift. */
}

body {
    margin: 0;
    overflow-x: clip;
    background: var(--color-paper);
    color: var(--color-ink);
    font-family: var(--font-body);
    font-size: var(--text-base);
    font-weight: 400;
    line-height: var(--leading-body);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

h1,
h2,
h3,
h4 {
    font-family: var(--font-display);
    font-style: normal;
    font-weight: 600;
    line-height: var(--leading-heading);
    letter-spacing: var(--tracking-display);
    /* Long identifiers ("RenderTreeBuilder", "BlazorCodeFirst") are longer than a phone is wide. */
    overflow-wrap: anywhere;
    min-width: 0;
}

a {
    color: var(--color-accent);
    text-underline-offset: 0.18em;
    text-decoration-thickness: from-font;
}

a:hover {
    color: var(--color-accent-strong);
}

code,
pre,
kbd {
    font-family: var(--font-mono);
    font-variant-ligatures: none;
}

/* The ring is never transitioned: a ring that fades in leaves a keyboard user with no indicator
   for the length of the transition. */
:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
    border-radius: var(--radius-xs);
}

::selection {
    background: var(--color-accent-soft);
    color: var(--color-accent-strong);
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.skip-link {
    position: absolute;
    top: var(--space-xs);
    left: var(--space-xs);
    z-index: var(--z-modal);
    padding: var(--space-xs) var(--space-md);
    border-radius: var(--radius-sm);
    background: var(--color-accent);
    color: var(--color-accent-ink);
    font-size: var(--text-sm);
    text-decoration: none;
    transform: translateY(-200%);
}

.skip-link:focus-visible {
    transform: none;
}

/* --- Shell --------------------------------------------------------------- */

.site-shell {
    display: flex;
    flex-direction: column;
    min-height: 100dvh;
}

.site-main {
    flex: 1;
    min-width: 0;
}

.shell {
    width: 100%;
    max-width: var(--shell-max);
    margin-inline: auto;
    padding-inline: clamp(var(--space-md), 4vw, var(--space-xl));
}

/* --- N9 Edge-aligned nav -------------------------------------------------
   Wordmark hard-left, utilities hard-right, the space between left empty. Deliberately not the
   wordmark-left / centred-link-cluster / filled-CTA-right bar: that shape is genre-blind and lands
   identically on a bakery and a compiler. The documentation's real navigation is the rail. */

.site-header {
    border-bottom: var(--rule-hairline) solid var(--color-rule);
    background: var(--color-paper);
}

.site-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-lg);
    width: 100%;
    max-width: var(--shell-max);
    margin-inline: auto;
    padding: var(--space-md) clamp(var(--space-md), 4vw, var(--space-xl));
}

.brand {
    display: flex;
    align-items: baseline;
    gap: var(--space-xs);
    min-width: 0;
}

/* The wordmark is a C# namespace, so it is set in the machine face rather than the display face.
   It carries the active class on "/" because the CI guard needs exactly that value somewhere on
   every route, but it draws no active rule: a wordmark does not need to tell you it is the page
   you are on. */
.brand .nav-link::after {
    content: none;
}

.brand .nav-link {
    padding: 0;
    font-family: var(--font-mono);
    font-size: var(--text-md);
    font-weight: 600;
    letter-spacing: -0.02em;
    color: var(--color-ink);
    text-decoration: none;
}

.brand .nav-link:hover,
.brand .nav-link.active {
    color: var(--color-ink);
    background: none;
}

.nav-utilities {
    display: flex;
    align-items: center;
    gap: var(--space-lg);
}

/* A clickable label in a horizontal row is a single-line object at every viewport: wrapping one
   reads as a styling error, not as intent. The rail is exempt -- it is a vertical list, where a
   two-line document title is part of the column's rhythm rather than a broken affordance. */
.site-nav .nav-link,
.chip,
.theme-toggle,
.foot-link {
    white-space: nowrap;
}

.nav-link {
    position: relative;
    padding-block: var(--space-2xs);
    color: var(--color-ink-2);
    font-size: var(--text-sm);
    font-weight: 500;
    text-decoration: none;
}

.nav-link::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: -2px;
    height: 1px;
    background: currentColor;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform var(--dur-micro) var(--ease-out);
}

.nav-link:hover {
    color: var(--color-ink);
}

.nav-link:hover::after {
    transform: scaleX(1);
}

.nav-link.active {
    color: var(--color-accent);
}

.nav-link.active::after {
    background: var(--color-accent);
    transform: scaleX(1);
}

/* --- Buttons and chips ---------------------------------------------------- */

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--space-md);
    border: var(--rule-hairline) solid var(--color-rule);
    border-radius: var(--radius-sm);
    background: none;
    color: var(--color-ink);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    font-weight: 500;
    text-decoration: none;
    cursor: pointer;
    transition:
        border-color var(--dur-micro) var(--ease-out),
        background-color var(--dur-micro) var(--ease-out),
        transform var(--dur-micro) var(--ease-out);
}

.chip:hover {
    border-color: var(--color-ink-2);
    color: var(--color-ink);
}

.chip:active {
    transform: translateY(1px);
}

/* Three channels, not opacity alone: a faded control that still takes a pointer reads as a bug. */
.chip:disabled,
.chip[aria-disabled="true"],
.install-copy:disabled {
    opacity: 0.55;
    cursor: not-allowed;
    transform: none;
}

.chip--primary {
    border-color: var(--color-accent);
    background: var(--color-accent);
    color: var(--color-accent-ink);
}

.chip--primary:hover {
    border-color: var(--color-accent-strong);
    background: var(--color-accent-strong);
    color: var(--color-accent-ink);
}

/* --- Theme toggle ---------------------------------------------------------
   The chip's hairline and radius, with no fill: this is a setting, not a destination, and the one
   filled control in the row is already spoken for. It carries no icon because the nav carries none
   anywhere -- a glyph here would be a word borrowed from a language this design does not speak.

   The element holds no state. All three words are always in the DOM and the rules below choose
   which one is visible from the attribute on :root, so Blazor re-rendering the header cannot
   disturb the theme, and the button is correct in the prerendered HTML before the runtime starts.
   wwwroot/index.html owns the click. */
.theme-toggle {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--space-md);
    border: var(--rule-hairline) solid var(--color-rule);
    border-radius: var(--radius-sm);
    background: none;
    color: var(--color-ink);
    font-family: var(--font-body);
    font-size: var(--text-sm);
    font-weight: 500;
    cursor: pointer;
    transition:
        border-color var(--dur-micro) var(--ease-out),
        transform var(--dur-micro) var(--ease-out);
}

.theme-toggle:hover {
    border-color: var(--color-ink-2);
}

.theme-toggle:active {
    transform: translateY(1px);
}

/* The standing word, quieter than the value beside it so the eye lands on the half that changes. */
.theme-toggle__label {
    color: var(--color-muted);
    font-weight: 400;
}

/* One grid cell for all three words. The two that are not current stay in flow and keep
   contributing their width, so the button is always as wide as the longest of them and the row does
   not shift when the reader clicks -- which a min-width would only approximate, and would have to
   be retuned by hand for any change of face or wording.

   visibility, not display, is what keeps that width. Both are excluded from the accessible name, so
   the button still reads as its current state alone. */
.theme-toggle__states {
    display: grid;
}

.theme-toggle__states > span {
    grid-area: 1 / 1;
    visibility: hidden;
    text-align: left;
}

:root:not([data-theme]) .theme-toggle__states > .is-system,
:root[data-theme="light"] .theme-toggle__states > .is-light,
:root[data-theme="dark"] .theme-toggle__states > .is-dark {
    visibility: visible;
}

/* --- Landing ------------------------------------------------------------- */

.hero {
    display: grid;
    /* minmax(0, …) so a wide code figure cannot push the grid past the viewport. */
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-2xl);
    /* Heavier at the bottom than the top. Symmetric padding leaves a hero floating above the page
       instead of leading into the section under it. */
    padding-block: var(--space-2xl) var(--space-3xl);
}

.hero-title {
    margin: 0;
    font-size: var(--text-display);
    font-weight: 700;
    line-height: var(--leading-display);
}

.hero-lede {
    max-width: 46ch;
    margin: var(--space-lg) 0 0;
    color: var(--color-ink-2);
    font-size: var(--text-md);
}

.hero-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-sm);
    margin-top: var(--space-xl);
}

.install {
    display: flex;
    align-items: center;
    gap: var(--space-sm);
    padding: var(--space-xs) var(--space-xs) var(--space-xs) var(--space-md);
    border: var(--rule-hairline) solid var(--color-rule);
    border-radius: var(--radius-sm);
    background: var(--color-paper-2);
    min-width: 0;
}

.install code {
    overflow-x: auto;
    font-size: var(--text-sm);
    color: var(--color-ink);
    white-space: nowrap;
}

.install-copy {
    flex: none;
    padding: var(--space-2xs) var(--space-xs);
    border: var(--rule-hairline) solid var(--color-rule);
    border-radius: var(--radius-xs);
    background: var(--color-paper);
    color: var(--color-muted);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    cursor: pointer;
    white-space: nowrap;
    transition: color var(--dur-micro) var(--ease-out);
}

.install-copy:hover {
    color: var(--color-ink);
}

/* --- Split Studio diptych -------------------------------------------------
   The page's repeating unit. Direction alternates down the page via .split--flip, which swaps the
   two halves at wide widths only; below the layout breakpoint every split is one column in DOM
   order, so the claim always precedes its proof. */

.split {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-xl);
    align-items: start;
    padding-block: var(--space-3xl);
    border-top: var(--rule-hairline) solid var(--color-rule);
}

.split-title {
    margin: 0;
    font-size: var(--text-xl);
}

.split-body {
    max-width: var(--measure);
    margin: var(--space-md) 0 0;
    color: var(--color-ink-2);
}

.split-body + .split-body {
    margin-top: var(--space-md);
}

.pair {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-lg);
}

/* --- Code figures ---------------------------------------------------------
   A typographic frame -- caption, rule, code -- rather than a drawn window with a fake title bar
   and traffic-light dots. The reader already has a real editor; a redrawn one is a picture of a
   picture frame. */

.figure {
    margin: 0;
    min-width: 0;
}

.figure figcaption {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    gap: var(--space-sm);
    padding-bottom: var(--space-xs);
    border-bottom: var(--rule-hairline) solid var(--color-rule);
    margin-bottom: var(--space-sm);
    color: var(--color-muted);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
}

.figure figcaption em {
    font-style: normal;
    color: var(--color-accent);
}

/* The slab is the visual box. DocGen emits <div class="csharp"><pre> inside it, and Diagnostic
   writes a bare <pre>, so the inner block carries none of the box's own painting.

   color-scheme is what lets one generated stylesheet serve both surfaces. css/highlight.css is
   written as light-dark() pairs, and those resolve against the ELEMENT's computed color-scheme
   rather than the system's, so declaring it here picks the right half whatever the page theme is.
   Without it a reader on the light theme would meet the paper palette on the dark slab. Same
   technique as :root[data-theme] in css/tokens.css, reached for the same reason. */
.slab {
    overflow-x: auto;
    margin: 0;
    padding: var(--space-lg);
    border-radius: var(--radius-md);
    background: var(--color-slab);
    color: var(--color-slab-ink);
    color-scheme: dark;
    font-size: var(--text-sm);
    line-height: var(--leading-code);
    tab-size: 4;
}

.slab--light {
    border: var(--rule-hairline) solid var(--color-rule);
    background: var(--color-paper-2);
    color: var(--color-ink);
    color-scheme: light;
}

/* The slab paints the box, so the block inside it carries only the margin reset -- a UA `pre` has
   no padding, border or background of its own, and its colour already inherits.

   Nothing here may touch the font. `code, pre, kbd` above sets the mono family at specificity
   0-0-1, and a `font: inherit` here (0-1-1) beats it and resolves to the body sans face, because
   .slab sets a size and a line-height but never a family. That rendered every figure on the site
   in Geist. The browser suite reads overflow and contrast, not font-family, so it stayed green.

   One selector per fence language, because the wrapper carries the language as its class. Adding a
   language to SnippetConverter's map without adding it here leaves that figure with the UA `pre`
   margin: measured, `.html` stood 26px taller than the `.csharp` figure beside it in the same pair,
   which is what caught it. StylesheetTests now reads the language list off that map and holds this
   selector list to it, so the next language is caught by a test instead of by eye. */
.slab > pre,
.slab > .csharp > pre,
.slab > .html > pre {
    margin: 0;
}

/* The build error is not code, so it does not borrow the code vocabulary. Two classes: where the
   error is, and which error it is. */
.diag-loc {
    color: var(--color-diag-loc);
}

.diag-id {
    color: var(--color-diag-id);
}

/* --- F4 Step sequence -----------------------------------------------------
   The only numbered thing on the page, and the numbers are load-bearing: this is a pipeline whose
   stages happen in this order. Nothing else on the site carries an eyebrow. */

.steps {
    display: grid;
    gap: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

/* Single column: the index sits above its heading, never beside it. A label in a narrow left
   column with the heading to its right is the most recognisable templated-editorial tell there is,
   and it is the reason this is the only numbered thing on the page. */
.step {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-2xs);
    padding-block: var(--space-lg);
    border-top: var(--rule-hairline) solid var(--color-rule-2);
}

.step:first-child {
    border-top: 0;
}

.step-index {
    font-family: var(--font-mono);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    color: var(--color-accent);
}

.step-title {
    margin: 0;
    font-size: var(--text-md);
    font-weight: 600;
}

.step-note {
    max-width: var(--measure);
    margin: var(--space-2xs) 0 0;
    color: var(--color-ink-2);
    font-size: var(--text-sm);
}

/* The closing section runs tighter than the ones above it. Identical padding on every section is
   a flat rhythm, and the last one is introducing a list rather than making an argument. */
.split--close {
    padding-block: var(--space-2xl) var(--space-3xl);
}

/* --- Index strip ---------------------------------------------------------- */

.index-list {
    margin: 0;
    padding: 0;
    list-style: none;
    border-top: var(--rule-hairline) solid var(--color-rule-2);
}

.index-list li {
    border-bottom: var(--rule-hairline) solid var(--color-rule-2);
}

/* Wraps because the row cannot always hold both halves. The route beside each title is machine text
   and carries `white-space: nowrap`, so at 320 px the longest of them ran 18 px past the viewport,
   where `overflow-x: clip` on html and body swallowed the end of the path instead of scrolling to
   it. Wrapping drops the route to its own line at that width and loses nothing; shortening or
   hiding it would. */
.index-link {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-md);
    padding-block: var(--space-md);
    color: var(--color-ink);
    font-weight: 500;
    text-decoration: none;
}

.index-link span {
    color: var(--color-muted);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    white-space: nowrap;
}

.index-link:hover {
    color: var(--color-accent);
}

.index-link:hover span {
    color: var(--color-accent);
}

/* The index carries the same three groups the rail does, so a reader who scans one and then the
   other meets one shape twice rather than two organizations of the same eleven documents. */
.index-group + .index-group {
    margin-top: var(--space-xl);
}

/* --- Ft2 footer ----------------------------------------------------------- */

/* No margin-top: every section and the documentation shell already carry their own bottom padding,
   and stacking a margin on top of that opened a band of dead space above the rule. */
.site-footer {
    border-top: var(--rule-hairline) solid var(--color-rule);
}

.site-footer .shell {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-md) var(--space-lg);
    padding-block: var(--space-xl);
    color: var(--color-muted);
    font-size: var(--text-sm);
}

.foot-links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-lg);
}

.foot-link {
    color: var(--color-muted);
    text-decoration: none;
}

.foot-link:hover {
    color: var(--color-ink);
    text-decoration: underline;
}

/* --- Documentation shell --------------------------------------------------
   The rail is the documentation's real navigation, and it renders only on the documentation
   routes. On every other route the header nav is the whole of it.

   The rail comes AFTER the document in the DOM. Below the layout breakpoint the shell is one
   column, and a rail placed first would put the whole table of contents between the reader and the
   page they asked for: they would land on a document URL, see a list of other documents, and scroll
   past all of them to reach their own. It is repositioned into the first column at wide widths,
   where it costs nothing. */

.docs-shell {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-xl);
    align-items: start;
    padding-block: var(--space-2xl);
}

.docs-rail {
    min-width: 0;
    padding-top: var(--space-lg);
    border-top: var(--rule-hairline) solid var(--color-rule);
}

/* The language switch sits at the top of the rail, before the groups: a reader who wants
   another edition of this page wants it before reading, and someone who does not want it should
   meet it once and never again. It renders only where a counterpart exists, so its absence is
   information too. */
.lang-switch {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
    margin: 0 0 var(--space-md);
    padding: 0;
    list-style: none;
}

/* Not .nav-link: the CI guard asserts that exactly one element per route carries the whole value
   "nav-link active", and a switch link is never the active document. */
.lang-link {
    color: var(--color-muted);
    font-size: var(--text-xs);
    letter-spacing: var(--tracking-label);
    text-decoration: none;
    text-transform: uppercase;
    transition: color var(--dur-micro) var(--ease-out);
}

.lang-link:hover {
    color: var(--color-accent);
}

/* The rail is three groups rather than one list, because a reader arrives with one of three
   questions: how do I start, how do I write this, what is the exact rule. A flat list answers none
   of them -- it asks the reader to guess which of eleven documents takes them from nothing to a
   rendered page.

   The group heading is body face at full ink rather than an uppercase micro-label. Three labels in
   that voice would read as three eyebrows, which is the tic the label treatment earns only when
   there is one of it. */
.rail-groups {
    margin: 0;
    padding: 0;
    list-style: none;
}

.rail-group + .rail-group {
    margin-top: var(--space-lg);
}

.rail-group-heading {
    margin: 0 0 var(--space-xs);
    color: var(--color-ink);
    font-size: var(--text-sm);
    font-weight: 600;
}

.rail-list {
    margin: 0;
    padding: 0;
    list-style: none;
    border-left: var(--rule-hairline) solid var(--color-rule);
}

/* The rail's links carry the same class value as the header's, because the CI guard matches that
   value whole and a rail-specific third class would make the assertion match nothing. The look is
   separated by the ancestor instead. */
.rail-list .nav-link {
    display: block;
    padding: var(--space-xs) var(--space-md);
    margin-left: -1px;
    border-left: 2px solid transparent;
    color: var(--color-ink-2);
    font-size: var(--text-sm);
    text-decoration: none;
    transition: color var(--dur-micro) var(--ease-out);
}

.rail-list .nav-link::after {
    content: none;
}

.rail-list .nav-link:hover {
    color: var(--color-ink);
}

.rail-list .nav-link.active {
    border-left-color: var(--color-accent);
    color: var(--color-accent);
    font-weight: 500;
}

/* --- Anchor filter --------------------------------------------------------
   Above the diagnostics reference: 42 sections on one page, previously reachable only by the
   browser's own find. The chips are ordinary links to ids on the page, so with WebAssembly not yet
   started -- or blocked -- the block is a table of contents rather than a dead control. */

.anchor-filter {
    margin-block: var(--space-lg) var(--space-xl);
    padding: var(--space-md);
    border: var(--rule-hairline) solid var(--color-rule);
    border-radius: var(--radius-sm);
}

.anchor-filter-label {
    display: block;
    margin-bottom: var(--space-xs);
    color: var(--color-muted);
    font-size: var(--text-xs);
    font-weight: 500;
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
}

/* The only text input on the site, so it declares its own face rather than inheriting the UA's.
   `font: inherit` alone would leave the size to the browser's form defaults, which on Safari is two
   steps below the surrounding prose. */
.anchor-filter-input {
    width: 100%;
    padding: var(--space-sm) var(--space-md);
    border: var(--rule-hairline) solid var(--color-rule);
    border-radius: var(--radius-sm);
    background: none;
    color: var(--color-ink);
    font-family: var(--font-mono);
    font-size: var(--text-sm);
}

/* The ring stays. The rule at the top of this file puts a 2px --color-focus outline on every
   focusable element and says why; replacing it here with a 1px border-colour swap would leave the
   site's only text input with its weakest focus indicator. The accent border is additive. */
.anchor-filter-input:focus-visible {
    border-color: var(--color-accent);
}

.anchor-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    margin: var(--space-md) 0 0;
    padding: 0;
    list-style: none;
}

/* Mono, because a diagnostic id is machine text: it is what the build printed and what the reader
   is matching character for character. */
.anchor-chip {
    display: inline-block;
    padding: var(--space-2xs) var(--space-sm);
    border: var(--rule-hairline) solid var(--color-rule);
    border-radius: var(--radius-sm);
    color: var(--color-ink-2);
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    text-decoration: none;
    transition:
        border-color var(--dur-micro) var(--ease-out),
        color var(--dur-micro) var(--ease-out);
}

.anchor-chip:hover {
    border-color: var(--color-accent);
    color: var(--color-accent);
}

/* --- Prose (rendered Markdown) -------------------------------------------- */

.prose {
    max-width: var(--measure);
    min-width: 0;
}

/* A translation that has fallen behind says so above its own title, because a reader deciding
   whether to trust the page needs that before reading it rather than after. The accent rule marks
   it as the site talking about the document, not part of the document. */
.stale-note {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs) var(--space-sm);
    margin: 0 0 var(--space-lg);
    padding: var(--space-sm) var(--space-md);
    border-left: 2px solid var(--color-accent);
    background: var(--color-paper-2);
    color: var(--color-ink-2);
    font-size: var(--text-sm);
    line-height: var(--leading-body);
}

.stale-note a {
    color: var(--color-accent);
    text-decoration: underline;
    text-underline-offset: 0.2em;
}

.prose > h1 {
    margin: 0 0 var(--space-lg);
    font-size: var(--text-2xl);
}

.prose h2 {
    margin: var(--space-2xl) 0 var(--space-md);
    padding-bottom: var(--space-xs);
    border-bottom: var(--rule-hairline) solid var(--color-rule);
    font-size: var(--text-lg);
}

.prose h3 {
    margin: var(--space-xl) 0 var(--space-sm);
    font-size: var(--text-md);
}

.prose p,
.prose li {
    color: var(--color-ink-2);
}

.prose pre {
    overflow-x: auto;
    padding: var(--space-md);
    border: var(--rule-hairline) solid var(--color-rule);
    border-radius: var(--radius-sm);
    background: var(--color-paper-2);
    font-size: var(--text-sm);
    line-height: var(--leading-code);
    tab-size: 4;
}

/* An output figure is a pair: an expression, then the markup it renders. Markdown gives two adjacent
   fences no relationship at all, and measured on /docs/elements-and-decorations they read as two
   unrelated blocks 13px apart -- the reader is left to infer that the second is the first's output,
   which is the inference this whole page exists to remove.

   Drawn as one stacked box instead, sharing a single rule where they meet. No label: a word here
   would be English in a stylesheet both editions load, and CONTRIBUTING.md keeps reader-facing
   strings out of everything but the content tree. The direction needs none -- C# above, HTML below,
   in that order every time.

   The selector is the language wrapper DocGen emits, so it applies to a pair and to nothing else: a
   csharp fence followed by prose keeps its own box. */
.prose .csharp:has(+ .html) pre {
    margin-bottom: 0;
    border-bottom: 0;
    border-bottom-right-radius: 0;
    border-bottom-left-radius: 0;
}

.prose .csharp + .html pre {
    margin-top: 0;
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}

/* Same reasoning as the headings above, reached by a different route. A run of inline code separated
   by ideographic commas ("`Html.Div`、`.Class(...)`、`.OnClick(...)`") gets no break opportunity from
   Chromium at the commas, so the three spans stay on one line: at 320 px the third ran 40 px past a
   288 px content box. html and body are overflow-x: clip, so the overflow is cut off rather than
   scrolled to, and the reader loses the text outright. Breaking an identifier mid-token is ugly;
   silently deleting the end of it is worse. */
.prose :not(pre) > code {
    padding: 0.1em 0.32em;
    border-radius: var(--radius-xs);
    background: var(--color-paper-3);
    color: var(--color-ink);
    font-size: 0.875em;
    overflow-wrap: anywhere;
}

.prose table {
    display: block;
    overflow-x: auto;
    border-collapse: collapse;
    font-size: var(--text-sm);
}

.prose th,
.prose td {
    padding: var(--space-xs) var(--space-sm);
    border: var(--rule-hairline) solid var(--color-rule);
    text-align: left;
}

.prose blockquote {
    margin-inline: 0;
    padding-left: var(--space-md);
    border-left: 2px solid var(--color-rule);
    color: var(--color-muted);
}

/* ':::warning' from a document. The one block that outranks the prose around it, and the reason it
   exists rather than a bold sentence: bold is inline emphasis inside an ordinary paragraph, so a
   reader skimming headings and code passes over it. That reader is the one the warning is for --
   the one copying a `Raw` call into their own page.

   Drawn as `.prose pre` is -- hairline all round, --radius-sm, tinted ground -- because that is
   already this page's shape for "a block, not a paragraph". Only the hue differs, and the hue is
   what the block is for. A thick coloured left stripe was the first draft and is the wrong shape
   twice over: it is a named tell, and it would have made this block a heavier .stale-note rather
   than a different kind of thing.

   The first and last margins collapse so the block's own padding sets its inset, whatever the author
   opened it with. */
.prose .warning {
    margin: var(--space-lg) 0;
    padding: var(--space-md);
    border: var(--rule-hairline) solid var(--color-warn);
    border-radius: var(--radius-sm);
    background: var(--color-warn-soft);
    color: var(--color-ink);
}

.prose .warning > :first-child {
    margin-top: 0;
}

.prose .warning > :last-child {
    margin-bottom: 0;
}

.prose .warning :not(pre) > code {
    background: var(--color-warn-chip);
}

/* The "#" is real link text so assistive technology can announce it; it is only visually
   de-emphasised until the heading is hovered or the link itself is focused. */
.headlink {
    margin-left: var(--space-xs);
    color: var(--color-muted);
    text-decoration: none;
    opacity: 0;
}

.prose h2:hover .headlink,
.prose h3:hover .headlink,
.prose h4:hover .headlink,
.prose h5:hover .headlink,
.prose h6:hover .headlink,
.headlink:focus-visible {
    opacity: 1;
}

/* A device with no hover has no way to reveal it, and deep-linking to a section is a primary action
   on a documentation site. Keyed on the interaction capability rather than on a width, because that
   is the thing actually being tested. */
@media (hover: none) {
    .headlink {
        opacity: 1;
    }
}

/* --- Counter demo --------------------------------------------------------- */

.demo {
    padding-block: var(--space-2xl);
}

.demo-readout {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-md);
    margin-top: var(--space-lg);
}

.demo-count {
    font-family: var(--font-mono);
    font-size: var(--text-2xl);
    font-variant-numeric: tabular-nums;
}

.demo-milestone {
    color: var(--color-accent);
    font-size: var(--text-sm);
}

.demo-buttons {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    margin-top: var(--space-lg);
}

/* --- Blazor's own error surfaces ------------------------------------------
   Kept from the framework template. These render only when the app has already failed, so they
   deliberately do not depend on the token file resolving. */

#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgb(0 0 0 / 0.2);
    box-sizing: border-box;
    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;
    right: 0.75rem;
    top: 0.5rem;
}

.blazor-error-boundary {
    background: #b32121;
    padding: 1rem;
    color: white;
}

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

/* --- Layout breakpoint (60rem) -------------------------------------------- */

@media (min-width: 60rem) {
    .pair {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: var(--space-xl);
    }

    .split {
        grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
        gap: var(--space-3xl);
    }

    /* The alternation. Order is swapped visually only; the DOM keeps claim-before-proof, so the
       reading order on a screen reader and at narrow widths is unchanged. The ratio inverts with
       it, so the wide column follows the figure rather than staying under the prose. */
    .split--flip {
        grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    }

    .split--flip > :first-child {
        order: 2;
    }

    /* A section whose content needs the full measure: the head stacks above it, and the gap
       between the two is the section's internal rhythm rather than its column gutter. */
    .split--wide {
        grid-template-columns: minmax(0, 1fr);
        gap: var(--space-xl);
    }

    .docs-shell {
        grid-template-columns: var(--rail-width) minmax(0, 1fr);
        gap: var(--space-3xl);
        padding-block: var(--space-3xl);
    }

    /* Explicit placement rather than `order`, so the rail lands in the first column without
       depending on how many siblings the shell has. */
    .docs-rail {
        grid-column: 1;
        grid-row: 1;
        position: sticky;
        /* Docked beneath the banner, not at the viewport top: two elements stuck to 0 overlap, and
           the one deeper in the DOM paints over the bar. */
        top: calc(var(--nav-height) + var(--space-lg));
        z-index: var(--z-sticky);
        padding-top: 0;
        border-top: 0;
    }

    .docs-content {
        grid-column: 2;
        grid-row: 1;
    }

    .site-header {
        position: sticky;
        top: 0;
        z-index: var(--z-sticky-nav);
    }
}

/* --- Typography breakpoint (40rem) ---------------------------------------- */

@media (max-width: 40rem) {
    .split,
    .hero {
        padding-block: var(--space-2xl);
    }

    /* Three destinations, a wordmark and a setting have to share 320 px. Every label here is
       already as short as it can be, so the row is fitted by stepping the type down rather than by
       hiding a destination -- the demo is the one a reader on a phone is most likely to want, and
       it is the one a "hide the last item" rule would take away. */
    .site-nav {
        gap: var(--space-sm);
        padding-inline: var(--space-md);
    }

    .brand .nav-link {
        font-size: var(--text-sm);
    }

    .site-nav .nav-link {
        font-size: var(--text-xs);
    }

    .nav-utilities {
        gap: var(--space-sm);
    }

    .nav-utilities .chip,
    .nav-utilities .theme-toggle {
        padding: var(--space-xs) var(--space-sm);
    }

    .theme-toggle {
        font-size: var(--text-xs);
    }

    /* The one label in this row that can go without taking a control away: the state word alone
       still says what the button does once it is the only setting here.

       display:none rather than the .visually-hidden clip, because this word is decorative
       (aria-hidden) and the accessible name is carried by a separate span that is hidden at every
       width. Taking it out of the box tree is what actually returns its space to a 320px row, which
       the clip would not; it also leaves the button with one text rect, which is what the wrapping
       check in tests/browser/layout.spec.ts counts. */
    .theme-toggle__label {
        display: none;
    }

    .install code {
        font-size: var(--text-xs);
    }
}

/* Coarse pointers need a real hit target regardless of how small the label is. */
@media (pointer: coarse) {
    .chip,
    .theme-toggle,
    .install-copy,
    .site-nav .nav-link {
        min-height: 44px;
        display: inline-flex;
        align-items: center;
    }

    .rail-list .nav-link,
    .index-link {
        min-height: 44px;
        display: flex;
        align-items: center;
    }
}

@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    *,
    *::before,
    *::after {
        animation-duration: 150ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 150ms !important;
    }
}
