/* Formidable sample theme — "xfunc Soft".
   The library ships no styling; this stylesheet is the consumer side of the
   story: it targets the formidable-* hooks the kit emits. Tokens first, one
   dark-mode override block, then chrome, forms, and the formidable surface. */

:root {
    /* palette — light */
    --bg: #ffffff;
    --field-bg: #f8f9fa;
    --text: #1a1a2e;
    --muted: #4a5565;
    --border: #e5e7eb;
    --accent: #0774b8;
    --ring: rgba(7, 116, 184, .16);
    --explain: #6d28d9;
    --error: #b91c1c;
    --error-text: #b91c1c;
    --warn: #a16207;
    --info: #516fca;
    --valid: #15803d;
    --summary-bg: #fdf1ef;
    --cta: #f26a2b;
    --cta-text: #ffffff;
    /* the custom select chevron, stroked in the muted colour — a data URI cannot read a
       token, so each scheme carries its own */
    --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%234a5565' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    /* chrome (same in both schemes) */
    --nav-bg: #1a1a2e;
    --nav-active: #f26a2b;
    --nav-brand-sub: #81d2f8;

    /* shape */
    --radius-input: 12px;
    --radius-button: 10px;
    --radius-card: 10px;
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #1a1a2e;
        --field-bg: #252547;
        --text: #f3f4f6;
        --muted: #99a1af;
        --border: #3b3b63;
        --accent: #81d2f8;
        --ring: rgba(129, 210, 248, .2);
        --explain: #c4b5fd;
        --error: #f87171;
        --error-text: #fca5a5;
        --warn: #fbbf24;
        --info: #a5b4fc;
        --valid: #4ade80;
        --summary-bg: rgba(248, 113, 113, .09);
        --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5' fill='none' stroke='%2399a1af' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    }

    /* Native picker/spinner chrome (calendar glyphs, the number input's up/down arrows) follows
       color-scheme, not the theme tokens - the tokens still paint the field's own background and
       text. */
    form input[type="date"],
    form input[type="number"] {
        color-scheme: dark;
    }
}

/* ---------- base ---------- */

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: 'Segoe UI', system-ui, sans-serif;
    font-size: 1rem;
    line-height: 1.5;
}

/* How the page moves when something scrolls it into view - a summary click, a blocked submit's
   focus move - is the consumer's to set, so the library asks for no behaviour of its own and
   this is where the answer lives. It has to be on the root element: scroll-behavior propagates
   to the viewport from html and, unlike overflow, not from body. Chromium does not read
   prefers-reduced-motion for scrolling on its own, so the guard below is what makes the
   preference reach it. */
html {
    scroll-behavior: smooth;
}

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

/* The Bootstrap fitting page adds Bootstrap's stylesheet straight after this one, and
   Bootstrap 5.3 without data-bs-theme carries its own light-only `body` rule;
   being later, it wins the equal-specificity tie in dark mode and flips the whole shell light.
   `html body` out-specifies it while leaving the demo area to theme itself through its own
   data-bs-theme attribute. */
@media (prefers-color-scheme: dark) {
    html body {
        background: var(--bg);
        color: var(--text);
    }
}

/* Pre-boot splash — replaced by Blazor's first render. This stylesheet loads in <head>,
   so the tokens theme the splash before the runtime arrives. */
.boot {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .4rem;
    color: var(--muted);
}

/* One full turn, repeated by the loading splash's spinner and by a pending field's spinner. */
@keyframes formidable-spin {
    to {
        transform: rotate(360deg);
    }
}

.boot-spinner {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: 3px solid var(--border);
    border-top-color: var(--accent);
    animation: formidable-spin .8s linear infinite;
}

h1 {
    font-size: 1.75rem;
    margin: 0 0 .5rem;
}

h2 {
    font-size: 1.25rem;
    margin: 1.5rem 0 .5rem;
}

p {
    margin: 0 0 .9rem;
}

small {
    color: var(--muted);
}

/* white-space: pre-wrap keeps a code span's literal spacing on screen — inline HTML collapses
   runs of whitespace by default, and the Normalize walkthrough types spacing the reader needs
   to see exactly as written. Site-wide is deliberate: every other code span in the sample is
   single-spaced already, and pre-wrap still wraps a long span instead of overflowing it. */
code {
    font-family: Consolas, monospace;
    font-size: .85em;
    background: var(--field-bg);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: .05em .3em;
    white-space: pre-wrap;
}

/* ---------- app chrome ---------- */

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

.topbar {
    background: var(--nav-bg);
    color: #ffffff;
    padding: .65rem 1.25rem;
    display: flex;
    align-items: center;
}

.brand {
    font-weight: 700;
}

.brand b {
    color: var(--nav-active);
    margin-right: .35rem;
}

.brand em {
    font-style: normal;
    font-weight: 500;
    font-size: .75rem;
    color: var(--nav-brand-sub);
    margin-left: .4rem;
}

.shell-body {
    display: flex;
    flex: 1;
}

.sidebar {
    width: 15rem;
    flex-shrink: 0;
    padding: 1.1rem .9rem;
    border-right: 1px solid var(--border);
}

.sidebar ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

.sidebar h2 {
    font-size: .75rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: var(--muted);
    margin: 1rem 0 .3rem .6rem;
}

.sidebar a {
    display: block;
    padding: .38rem .6rem;
    border-radius: 8px;
    color: var(--muted);
    text-decoration: none;
    font-size: .875rem;
}

.sidebar a:hover {
    color: var(--text);
    background: var(--field-bg);
}

.sidebar a.active {
    color: var(--nav-active);
    font-weight: 600;
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 4px;
}

main {
    flex: 1;
    min-width: 0;
    padding: 1.5rem 2rem 3rem;
    max-width: 52rem;
}

@media (max-width: 760px) {
    .shell-body {
        flex-direction: column;
    }

    .sidebar {
        width: auto;
        border-right: none;
        border-bottom: 1px solid var(--border);
    }
}

/* ---------- fields and inputs ---------- */

.field {
    position: relative;
    margin: 0 0 1rem;
}

.field > label {
    position: relative;
    display: block;
    font-size: .875rem;
    font-weight: 600;
    margin-bottom: .1rem;
}

/* The required marker the kit emits: content and a class, no styling of its own. The error
   colour is the convention readers already know an asterisk in, and the marker is aria-hidden,
   so nothing here has to keep a screen reader in mind - the input's own aria-required carries
   the fact. Left margin only: it trails the label's text and precedes the input below it. */
.formidable-required {
    color: var(--error);
    margin-left: .15rem;
}

/* Padding does not margin-collapse the way the field's own bottom margin does, so a field
   showing a message keeps this room on top of its normal trailing margin. The 4.8px gap above
   the message (.formidable-message-list's own margin-top) already groups it with its input; this is
   the field's own share of that same grouping, keeping the message reading as owned by the
   field above it rather than floating up against the next label. The list itself renders
   always (empty when the field has none), so this keys on an actual message rather than the
   list's own presence — an empty list must not claim the padding. */
.field:has(.formidable-message) {
    padding-bottom: .35rem;
}

/* The rule above only reaches messages rendered inside the field. Where a renderless
   FormidableField wraps the pair, the message list is the field's SIBLING instead, and the
   default margins land backwards: the field's 1rem bottom margin opens a full field-gap above
   the message while the list's own zero bottom margin leaves it flush against the next label,
   so the message reads as a heading for the field below rather than a verdict on the one
   above. These two restore the nested shape's proportions - the field snugs down to the same
   grouping gap the list's own top margin gives it inside a field, and the list carries the
   trailing room the field's own margin supplies in the nested shape - sized to 1.35rem (1rem +
   the nested shape's own .35rem trailing padding) so the two shapes end in exactly the same
   trailing gap. Both key on an actual message for the same reason as above — the sibling list
   is always there too, and an empty one must fall through to the plain field/list defaults. */
.field:has(+ .formidable-message-list .formidable-message) {
    margin-bottom: .3rem;
}

.field + .formidable-message-list:has(.formidable-message) {
    margin: 0 0 1.35rem;
}

/* One sibling message list must keep the default proportions: a collection's messages
   introduce the item list below them, and on the roster pages that list follows a team's name
   field, so the pair above would glue a team-level message to the name field it does not
   belong to. Matching on the item list that follows restores both defaults for that shape.
   Two separate :has() clauses (rather than nesting one inside the other, which the selector
   grammar forbids) express the same "has a message AND is followed by the roster" condition. */
.field:has(+ .formidable-message-list .formidable-message):has(+ .formidable-message-list + .member-list) {
    margin-bottom: 1rem;
}

.field + .formidable-message-list:has(.formidable-message):has(+ .member-list) {
    margin: .3rem 0 0;
}

/* .culture is the localization page's language picker: it sits outside the form, so it has to
   ask for the field chrome the form selectors give every other select. */
form input:where(:not([type=radio]):not([type=checkbox])),
form select,
form textarea,
.culture select {
    display: block;
    width: 100%;
    margin-top: .3rem;
    font: inherit;
    font-size: .875rem;
    color: var(--text);
    background: var(--field-bg);
    border: 1px solid var(--formidable-state-border);
    border-radius: var(--radius-input);
    padding: .6rem .85rem;

    --formidable-state-border: var(--border);
}

form input:where(:not([type=radio]):not([type=checkbox])):focus,
form select:focus,
form textarea:focus,
.culture select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--ring);
    /* background-color, not the shorthand: the shorthand would reset the select chevron's
       background-image below */
    background-color: var(--bg);
}

/* A native select paints its arrow hard against the right border, ignoring the padding
   inset the date icon and number spinners respect. Drawing the chevron ourselves puts it
   on the same .85rem line; the pending state hides it so the spinner owns that corner.
   (The Bootstrap page's .form-select out-specifies this and keeps Bootstrap's own arrow.) */
form select,
.culture select {
    appearance: none;
    background-image: var(--select-chevron);
    background-repeat: no-repeat;
    background-position: right .85rem center;
    background-size: 12px 8px;
    padding-right: 2.3rem;
}

/* ---------- validation states (FormidableCssClasses defaults) ---------- */

form .formidable-invalid {
    --formidable-state-border: var(--error);
    border-color: var(--formidable-state-border);
}

/* The confirmation border is a verdict like the three around it, so it carries a colour of its
   own: a field has to read as confirmed on its own, with no neutral neighbour to compare it
   against. */
form .formidable-valid {
    --formidable-state-border: var(--valid);
    border-color: var(--formidable-state-border);
}

form .formidable-warning {
    --formidable-state-border: var(--warn);
    border-color: var(--formidable-state-border);
}

form .formidable-info {
    --formidable-state-border: var(--info);
    border-color: var(--formidable-state-border);
}

/* The generic :focus rule above always draws the accent ring, so a field carrying any of the
   four state colours would otherwise read as neutral accent while it's being edited. These
   override just the ring's tint, not the border-color the generic rule also sets while focused,
   so the two stay consistent with every other field until blur reveals the state border
   underneath. :focus-visible matches pointer and keyboard focus alike on a text-like input, so
   the coverage matches the generic rule's plain :focus in practice. */
form .formidable-invalid:focus-visible {
    /* the focus ring and the state's own weight, composited — box-shadow is one
       property, so a focus rule that named only the ring would drop the state. */
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 16%, transparent),
                inset 0 0 0 1px var(--formidable-state-border);
}

form .formidable-warning:focus-visible {
    /* the focus ring and the state's own weight, composited — box-shadow is one
       property, so a focus rule that named only the ring would drop the state. */
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--warn) 16%, transparent),
                inset 0 0 0 1px var(--formidable-state-border);
}

form .formidable-info:focus-visible {
    /* the focus ring and the state's own weight, composited — box-shadow is one
       property, so a focus rule that named only the ring would drop the state. */
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--info) 16%, transparent),
                inset 0 0 0 1px var(--formidable-state-border);
}

/* A decided state reads heavier as well as differently coloured, so nothing rests on hue
   alone. An inset ring rather than a thicker border: the box is unchanged, so a field that
   gains or loses a state never nudges its own text by a pixel. */
form .formidable-invalid,
form .formidable-warning,
form .formidable-info,
form .formidable-valid {
    box-shadow: inset 0 0 0 1px var(--formidable-state-border);
}

form .formidable-valid:focus-visible {
    /* the focus ring and the state's own weight, composited — box-shadow is one
       property, so a focus rule that named only the ring would drop the state. */
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--valid) 16%, transparent),
                inset 0 0 0 1px var(--formidable-state-border);
}

/* The spinner sits inside the control, over whatever chrome that control carries. A field
   being checked cannot be operated while the check runs — a date picker or a chevron is no use
   mid-flight — so the seconds it is covered cost nothing, which is not true of a state that
   persists. It is the only mark drawn on a control for that reason. */
.field > label:has(> .formidable-pending)::after,
.field:has(> .formidable-pending)::after {
    content: "";
    position: absolute;
    right: 12px;
    bottom: 14px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 2px solid var(--accent);
    border-top-color: transparent;
    animation: formidable-spin .8s linear infinite;
}

form .formidable-pending {
    padding-right: 2.3rem;
}

form select.formidable-pending {
    background-image: none;
}

/* ---------- messages ---------- */

/* The list itself renders always - empty when the field has no issues - so it is a persistent
   element the same way .summary-slot's wrapper is, and the same technique applies directly: no
   @starting-style is needed because there is no insertion to supply a pre-insertion frame for,
   just an ordinary property change on an element that was already there. :has() keys the open
   row off an actual message (the ITEM, not the list) so the grid-row track grows from zero
   without knowing the content's height up front and, because the list never leaves the DOM,
   collapses back to zero the same way on the way out. */
.formidable-message-list {
    display: grid;
    grid-template-rows: 0fr;
    list-style: none;
    margin: 0;
    padding: 0;
    transition: grid-template-rows .22s ease, margin-top .22s ease;
}

.formidable-message-list:has(.formidable-message) {
    grid-template-rows: 1fr;
    margin-top: .3rem;
}

.formidable-message-list > li {
    overflow: hidden;
    min-height: 0;
}

@media (prefers-reduced-motion: reduce) {
    .formidable-message-list {
        transition: none;
    }
}

.formidable-message {
    font-size: .875rem;
}

/* Only the gap BETWEEN messages needs its own margin - the list's own margin-top above already
   groups the first message with its field, and a grid item's margin never collapses into its
   container's the way a plain block child's would, so giving every message the same top margin
   would double that opening gap on the first one. */
.formidable-message + .formidable-message {
    margin-top: .15rem;
}

/* Severity's colour. It is the ONLY thing telling a warning from an info inline, which the
   summary's own band headings say in words wherever one is rendered — see the backlog. */
.formidable-message--error {
    color: var(--error-text);
}

.formidable-message--warning {
    color: var(--warn);
}

.formidable-message--info {
    color: var(--info);
}

/* native Blazor ValidationMessage on the vanilla-interop page */
.validation-message {
    display: block;
    font-size: .875rem;
    margin-top: .3rem;
    color: var(--error-text);
}

/* ---------- buttons ---------- */

button {
    font: inherit;
    font-size: .875rem;
    font-weight: 600;
    border-radius: var(--radius-button);
    padding: .55rem 1.3rem;
    cursor: pointer;
}

/* These two rules leave a Bootstrap button (.btn) and a MudBlazor button (.mud-button-root) to
   their own library's look. :where() gives the exclusions no weight, so for every other button
   the rules weigh what button[type=submit] and button.primary weigh. */
button[type=submit]:where(:not(.btn):not(.mud-button-root)),
button.primary:where(:not(.btn):not(.mud-button-root)) {
    background: var(--cta);
    color: var(--cta-text);
    border: 1px solid transparent;
}

button[type=submit]:where(:not(.btn):not(.mud-button-root)):hover,
button.primary:where(:not(.btn):not(.mud-button-root)):hover {
    filter: brightness(1.06);
}

button[type=button]:not(.primary):not(.formidable-summary__link) {
    background: transparent;
    color: var(--accent);
    border: 1px solid var(--border);
}

button[type=button]:not(.primary):not(.formidable-summary__link):hover {
    border-color: var(--accent);
}

/* A disabled button reads as unavailable: a grey fill, muted text and a not-allowed pointer,
   with no change on hover. The :not() pair gives it the weight of the secondary rules above, so
   it wins their tie by coming later, and it leaves a Bootstrap or MudBlazor button to its own
   library's disabled look. */
button:disabled:not(.btn):not(.mud-button-root),
button:disabled:not(.btn):not(.mud-button-root):hover {
    background: var(--border);
    color: var(--muted);
    border-color: var(--border);
    cursor: not-allowed;
    filter: none;
}

button:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--ring);
}

/* ---------- summary (links excluded from the secondary-button rules above) ---------- */

/* The slide: FormidableSummary's severity bands exist exactly while it has something to show —
   the wrapper and its fixed-role regions persist either way — so :has() toggles the open row
   off the bands, not off the always-present wrapper, and no separate open/closed class needs
   keeping in sync. overflow: hidden and min-height: 0 on the wrapper keep it from resisting the
   collapsed 0fr row. */
.summary-slot {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows .22s ease;
}

.summary-slot > .formidable-summary {
    overflow: hidden;
    min-height: 0;
}

.summary-slot:has(.formidable-summary__band) {
    grid-template-rows: 1fr;
}

@media (prefers-reduced-motion: reduce) {
    .summary-slot {
        transition: none;
    }
}

/* Neutral: FormidableSummary.Show can render a summary carrying only one severity band, so no
   single colour lives on the wrapper or its regions — each band below carries its own. The
   bands are peers, not a card-in-a-card: no outer background, border or padding, just the
   stack. And because the wrapper and regions persist while empty, everything that would give
   an empty summary a footprint — the bottom margin, the gaps — is keyed on the bands. */
.formidable-summary:has(.formidable-summary__band) {
    margin-bottom: 1.1rem;
}

.formidable-summary__band + .formidable-summary__band {
    margin-top: .5rem;
}

/* Show="All" renders both fixed-role regions in one summary; when each holds a band, this is
   where the last error band and the first advisory band meet. */
.formidable-summary__region--errors:has(.formidable-summary__band) + .formidable-summary__region--advisories:has(.formidable-summary__band) {
    margin-top: .5rem;
}

/* The panel treatment — tint, left rule, radius, padding — lives on the band, not the list, so
   every band starts at the same left edge regardless of severity. */
.formidable-summary__band {
    border-radius: var(--radius-card);
    padding: .55rem 1rem;
}

.formidable-summary__band--error {
    background: var(--summary-bg);
    border-left: 3px solid var(--error);
}

.formidable-summary__band--warning {
    background: color-mix(in srgb, var(--warn) 10%, var(--bg));
    border-left: 3px solid var(--warn);
}

.formidable-summary__band--info {
    background: color-mix(in srgb, var(--info) 10%, var(--bg));
    border-left: 3px solid var(--info);
}

.formidable-summary__heading {
    margin: 0 0 .3rem;
    font-size: .75rem;
    font-weight: 700;
}

.formidable-summary__band--error .formidable-summary__heading {
    color: var(--error-text);
}

.formidable-summary__band--warning .formidable-summary__heading {
    color: var(--warn);
}

.formidable-summary__band--info .formidable-summary__heading {
    color: var(--info);
}

.formidable-summary__group {
    list-style: none;
    margin: 0;
    padding: 0;
}

.formidable-summary__item {
    margin: .15rem 0;
}

/* width: fit-content hugs the pill to the message text - the hit target is the text itself,
   not the full row; the hover/focus-visible tint below is what carries the "this is clickable"
   affordance, and the focus ring (drawn from the button's own box) hugs the same shape. The
   negative left margin plus a matching left padding extends that tint past the text's left edge
   without moving the text itself, mirroring the existing right-side padding so the tint reads as
   symmetric around the flush-aligned message. */
button.formidable-summary__link {
    display: block;
    width: fit-content;
    background: none;
    border: none;
    border-radius: var(--radius-button);
    margin: 0 0 0 -.55rem;
    padding: .35rem .55rem;
    font: inherit;
    font-size: .875rem;
    font-weight: 500;
    color: var(--error-text);
    cursor: pointer;
    text-align: left;
}

button.formidable-summary__link:hover,
button.formidable-summary__link:focus-visible {
    background: color-mix(in srgb, currentColor 12%, transparent);
}

.formidable-summary__group--warning button.formidable-summary__link {
    color: var(--warn);
}

.formidable-summary__group--info button.formidable-summary__link {
    color: var(--info);
}

/* ---------- structure and status ---------- */

fieldset {
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: .9rem 1rem;
    margin: 0 0 1rem;
}

legend {
    font-weight: 600;
    font-size: .875rem;
    padding: 0 .4rem;
}

fieldset > label:has(> select) {
    display: block;
    margin: 0 0 1rem;
}

/* Its own scroller, so its own answer: a click on a summary entry for a row inside the panel
   scrolls the panel as well as the page, and both should move the same way. The FocusFallback
   beside it deliberately does not - it asks for an instant jump, because Virtualize has to have
   rendered the row by the time the retried focus lands. */
.scroll-panel {
    height: 20rem;
    overflow-y: auto;
    scroll-behavior: smooth;
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: .5rem .75rem;
    margin-bottom: 1rem;
}

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

/* Virtualize takes one ItemSize for the whole collection, so every row has to occupy exactly
   that much space regardless of content — a row that only grows once its message renders
   throws off the scrollbar's math and makes a FocusFallback scroll (index * ItemSize) land
   short past the first grown row. Fixing the height, always tall enough for one message line,
   keeps a clean row and a messaged row identical. */
.scroll-panel .field {
    height: 102px;
    box-sizing: border-box;
}

p[role=status] {
    color: var(--muted);
    font-size: .875rem;
}

em[role=status] {
    font-style: normal;
    font-size: .75rem;
    color: var(--accent);
}

.member-list {
    list-style: none;
    margin: 0 0 .6rem;
    padding: 0;
}

.member-list li.field {
    margin-bottom: .8rem;
}

/* ---------- teaching panel and action rows ---------- */

.teaching {
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: 1rem 1.15rem;
    margin: 1.1rem 0 1.4rem;
    font-size: 1rem;
}

.teaching h2 {
    font-size: .75rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--muted);
    margin: 0 0 .35rem;
}

.teaching section + section,
.teaching .teaching__code {
    margin-top: .9rem;
}

.teaching ul,
.teaching ol {
    margin: 0;
    padding-left: 1.2rem;
}

.teaching li {
    margin: .2rem 0;
}

.teaching__steps li + li {
    margin-top: .5rem;
}

/* A long tour groups its steps under small headings, and How it works can group its
   paragraphs the same way. */
.teaching__steps h3,
.teaching__more > h3 {
    font-size: .875rem;
    margin: .9rem 0 .3rem;
}

/* A Try it step's result sits under its action, indented, behind an accent "You see:" label.
   A step whose result a reader would not expect adds a Why line under it, its label violet.
   A link to the page that teaches the step sits last, indented the same. */
.teaching__result,
.teaching__why,
.teaching__page {
    margin: .1rem 0 0 .9rem;
}

.teaching__label {
    font-weight: 600;
    color: var(--accent);
}

/* Violet marks an explanation, kept apart from the accent and the validation-state colours. */
.teaching__why .teaching__label {
    color: var(--explain);
}

/* The two collapsed sections share one look, so How it works reads as the same kind of
   control as Show the code. */
.teaching__code summary,
.teaching__more summary {
    cursor: pointer;
    font-weight: 600;
    color: var(--accent);
}

.teaching__code h3 {
    font-size: .875rem;
    font-family: Consolas, monospace;
    color: var(--muted);
    margin: .8rem 0 .3rem;
}

.teaching__code pre {
    background: var(--field-bg);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: .75rem .9rem;
    overflow-x: auto;
    font-size: .875rem;
    line-height: 1.45;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* The global inline-code rule above gives every <code> its own padding/border/background —
   right for prose, wrong inside a <pre>, where CSS fragmentation applies that left padding to
   only the first line's text fragment and nudges it out of alignment with the rest. */
.teaching__code pre code {
    padding: 0;
    border: none;
    background: none;
    border-radius: 0;
}

/* How it works holds its heading inside the summary, so a list of the page's headings reads
   Try it, The rules, How it works. On screen the heading takes the summary's look: it sits
   inline beside the marker and drops the section headings' small capitals. */
.teaching .teaching__more {
    margin-top: .9rem;
}

.teaching__more summary h2 {
    display: inline;
    margin: 0;
    font: inherit;
    letter-spacing: normal;
    text-transform: none;
    color: inherit;
}

.teaching__more > p,
.teaching__more > ul {
    margin: .5rem 0 0;
}

/* ---------- lesson layout ---------- */

/* A lesson page hands its form to the teaching panel, which wraps the two in .lesson, panel
   first. Below the breakpoint the lesson is one column: the panel keeps its card above the
   form, its sections in markup order, so the order on screen is the order Tab moves through.

   From 72rem up the form sits left and the panel right, three parts to two. At 72rem the
   content box is 72rem less the sidebar (15rem) and main's padding (4rem), so 53rem; less the
   2rem gap, the form gets 30.6rem and the panel 20.4rem, above their 28rem and 18rem floors (the
   page's scrollbar takes about 1rem, leaving 30rem and 20rem). main widens to 84rem, where the
   form column reaches 46.8rem, inside the 48rem every other page reads at. The heading and the
   intro above the lesson keep that 48rem measure. */
@media (min-width: 72rem) {
    main:has(.lesson) {
        max-width: 84rem;
    }

    main:has(.lesson) > :not(.lesson) {
        max-width: 48rem;
    }

    .lesson {
        display: grid;
        grid-template-columns: minmax(28rem, 3fr) minmax(18rem, 2fr);
        grid-template-areas: "demo panel";
        column-gap: 2rem;
        align-items: start;
        margin-top: 1.1rem;
    }

    .lesson > .lesson__demo {
        grid-area: demo;
        min-width: 0;
    }

    /* The panel keeps its own column, so it never covers the form, and scrolls with the page,
       so it has no scrollbar of its own: a long panel or a long form makes the page longer. */
    .lesson > .teaching {
        grid-area: panel;
        margin: 0;
    }

    /* Code needs width more than a place beside the form: with Show the code open the lesson
       is one column, the panel above the form at the lesson's full width. */
    .lesson:has(.teaching__code[open]) {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas: "panel" "demo";
        row-gap: 1.4rem;
    }
}

.actions {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin-top: 1.25rem;
}

.actions button {
    margin-right: 0;
}

p[role=status] {
    margin-top: 1rem;
}

/* A line under an actions row (a raw-values line, a note) keeps the gap a status line keeps,
   whether it follows the row inside the form or follows the form the row ends. */
.actions + p,
form:has(> .actions:last-child) + p {
    margin-top: 1rem;
}

/* ---------- field-state table ---------- */

.state-table {
    width: 100%;
    border-collapse: collapse;
    font-size: .75rem;
    margin: 1rem 0 1.4rem;
}

.state-table th,
.state-table td {
    border: 1px solid var(--border);
    padding: .4rem .6rem;
    text-align: left;
}

.state-table thead {
    color: var(--muted);
    background: var(--field-bg);
}

/* ---------- summary focus landing ---------- */

/* A field with no input of its own - a collection, the model-level gate - takes the summary's
   click on the container carrying its id, and a container shows nothing when focused. The mark is
   scoped to :focus-visible on purpose: these containers are focusable by mouse too (tabindex="-1"
   only removes them from the tab order), so plain :focus would paint the whole form or fieldset
   whenever a click landed on padding or a caption. Activating a summary entry from the keyboard
   carries focus-visible into the programmatic focus, so the keyboard path keeps its mark while a
   mouse click gets the scroll alone. The accent token covers both colour schemes. */
form:focus-visible,
fieldset:focus-visible,
.team-list:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* The router moves focus to the page heading on every navigation, so assistive technology
   announces the page that was opened. A heading is not a control and never enters the tab order,
   so a ring painted on that programmatic focus marks nothing a visitor can act on. */
h1:focus {
    outline: none;
}

/* ---------- capped summary lists ---------- */

/* The line a capped band renders in place of what it held back. Whatever a page puts on it is
   about the LIST — a count of what is not listed, or an expander that names it — where an entry
   is a route into the form. Styling it as quieter running text is what keeps
   those two kinds apart without a second read, so a control that does live on this line reads
   as acting on the list rather than as one more field to go and fix.

   The BOX starts where an entry's TEXT starts, which is the list's own left edge: an entry's
   pill reaches further left than that only because the entry button pays its negative margin
   back as padding, so a left padding here would set this line in from every name above it.
   Whatever the page puts inside then sits where its own content puts it — a bare count starts at
   that edge, while a <summary> renders its disclosure marker inside the box and indents its label
   by the marker. The rule and the space around it carry the separation, so the line reads as the
   end of the list rather than as one more thing after it. */
.formidable-summary__overflow {
    margin: .5rem 0 0;
    padding-top: .45rem;
    border-top: 1px solid color-mix(in srgb, currentColor 25%, transparent);
    font-size: .75rem;
    color: var(--muted);
}

/* Shaping the summary puts an expander on that line, and its trigger has to look like a control
   while the line around it stays quieter than the entries. The pointer is scoped to the trigger
   rather than to the line, so an overflow line that is only a count — Dialog-first submit's —
   still offers nothing to click and takes no pointer. */
.formidable-summary__overflow > .overflow-reveal > summary {
    width: fit-content;
    cursor: pointer;
}

/* The revealed names are a note about the list, not more entries: markers and an indent rather
   than the pill treatment the entries above them carry. */
.formidable-summary__overflow .overflow-reveal ul {
    list-style: disc;
    margin: .2rem 0 0;
    padding-left: 1.5rem;
}

.formidable-summary__overflow .overflow-reveal li {
    margin: .1rem 0;
}

/* ---------- the sample's own dialog ---------- */

/* The overlay stays in the DOM so one class flip drives the fade in both directions, and so the
   summary inside it is a live region that was already on the page when its contents arrived.
   visibility does the hiding: it keeps the panel out of the tab order and out of the
   accessibility tree while the dialog is closed, and it is transitionable, so the fade plays out
   before the panel goes. AnnouncementDialog waits out these .2s transitions with a FadeDuration
   of its own, and nothing binds the two numbers: change them together. */
.announcement {
    position: fixed;
    inset: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: rgba(26, 26, 46, .55);
    opacity: 0;
    visibility: hidden;
    transition: opacity .2s ease, visibility 0s linear .2s;
}

.announcement--open {
    opacity: 1;
    visibility: visible;
    transition: opacity .2s ease, visibility 0s linear 0s;
}

.announcement__panel {
    width: min(30rem, 100%);
    max-height: 100%;
    overflow-y: auto;
    padding: 1.25rem 1.5rem 1.5rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    background: var(--bg);
    color: var(--text);
    box-shadow: 0 1.2rem 2.5rem rgba(0, 0, 0, .35);
    transform: translateY(.75rem);
    transition: transform .2s ease;
}

.announcement--open .announcement__panel {
    transform: none;
}

.announcement__panel > h2 {
    margin-top: 0;
    font-size: 1.25rem;
}

/* The panel takes focus when it opens, which is a programmatic move: the ring marks it for a
   keyboard visitor and stays off a pointer one, the same rule the summary's focus landings use. */
.announcement__panel:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
}

/* The dialog is the whole announcement, so its band needs no bottom margin of its own. */
.announcement .formidable-summary:has(.formidable-summary__band) {
    margin-bottom: 0;
}

@media (prefers-reduced-motion: reduce) {
    .announcement,
    .announcement--open,
    .announcement__panel {
        transition: none;
    }
}

/* ---------- toggle rows ---------- */

.toggles {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    margin: 1rem 0 1.25rem;
}

/* ---------- a second form ---------- */

/* A page's second form sits in a card of its own, with a gap above it and its heading inside,
   so it reads as a separate form and its button is not taken for the first form's. */
.form-card {
    border: 1px solid var(--border);
    border-radius: var(--radius-card);
    padding: 1rem 1.15rem;
    margin: 2rem 0 0;
}

.form-card > h2 {
    margin-top: 0;
}

/* The form-level message list opens the card's form, right above a button: once it holds a
   message it keeps a gap below as well as above, and the gap opens and closes with the list. */
.form-card form > .formidable-message-list {
    transition: grid-template-rows .22s ease, margin-top .22s ease, margin-bottom .22s ease;
}

.form-card form > .formidable-message-list:has(.formidable-message) {
    margin-bottom: 1rem;
}

@media (prefers-reduced-motion: reduce) {
    .form-card form > .formidable-message-list {
        transition: none;
    }
}
