html, body {
    /* Segoe UI first: the shell is styled after Dynamics 365, and the type is half of that look.
       The fallbacks keep macOS/Linux on their native grotesques rather than a faux-Segoe. */
    font-family: 'Segoe UI Variable Text', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

/* ---- Colour tokens (work item #1526) --------------------------------------------------------
   Every colour this stylesheet uses for text, for a state outline or for a filled surface is
   declared here once and referenced with var(). Two reasons, and the second is the load-bearing
   one:

     1. One place to change a colour, so the error red in the validation message and the error red
        in the crash banner cannot drift apart.
     2. AccessibilityContrastTests measures these tokens. It resolves each var() reference out of
        this block, computes the WCAG 2.x relative-luminance contrast ratio for the foreground and
        background pairs the portal actually renders, and fails the build below 4.5:1 for text or
        3:1 for a UI boundary. A colour that is not a token is a colour nothing measures, so any
        new text or state colour belongs here rather than inline in the rule.

   The palette is Fluent 2 (the modern refresh): a light app bar and a light sitemap with icons,
   Fluent Communication Blue for links, buttons, the selected row and its accent bar, and neutral
   grays for borders. The content area stays white on purpose: Bootstrap 5.1's .text-muted is
   #6c757d, which is only 4.69:1 even on white, so a tinted canvas behind the content would push
   every muted caption in the portal under AA. Depth comes from borders and cards instead.

   Measured ratios, recorded so a reviewer can check the arithmetic without running the suite:
     against --odin-surface (white):
       --odin-link             5.38:1     --odin-code-text       5.41:1
       --odin-focus-ring       5.84:1     --odin-error           6.65:1
       --odin-valid-outline    4.53:1     --odin-primary-surface 5.38:1 (against --odin-on-primary)
       --odin-muted-text       7.00:1     --odin-heading-text   15.52:1
       --odin-input-accent     6.19:1 (the field underline, a UI boundary)
     the shell chrome:
       --odin-on-header       15.52:1     and --odin-header-muted 6.19:1, on --odin-header-surface
       --odin-nav-text        14.87:1     on --odin-nav-surface (13.62:1 on the hover surface)
       --odin-nav-heading      5.93:1     on --odin-nav-surface
       --odin-nav-accent       5.16:1     on --odin-nav-surface (4.81:1 on the selected surface,
                                          where it is also the selected row's text colour)
       --odin-focus-ring       5.59:1     on --odin-nav-surface */
:root {
    /* The page itself. Bootstrap paints the body white and its body text #212529. */
    --odin-surface: #ffffff;
    --odin-surface-text: #212529;

    /* Headings, table header text and form labels: Fluent's neutral foreground 1. */
    --odin-heading-text: #242424;
    /* Secondary text. Replaces Bootstrap's .text-muted (#6c757d, 4.69:1) with a darker gray that
       clears AA with margin, since captions and helper text are the most common small text here. */
    --odin-muted-text: #595959;

    /* Neutral strokes and fills for structure: card and table outlines, the table header band and
       panel footers. Decorative separators, not indicators, so they carry no contrast obligation;
       the control outline that does is --odin-input-accent below. */
    --odin-border: #e0e0e0;
    --odin-subtle-surface: #fafafa;

    /* Form fields, Fluent 2 style: a light outline on three sides and a dark underline on the
       fourth. The underline is what makes the field identifiable (WCAG 1.4.11, 3:1). */
    --odin-input-border: #d1d1d1;
    --odin-input-accent: #616161;

    /* Shell chrome: a light app bar over a light sitemap. */
    --odin-header-height: 3.25rem;
    --odin-header-surface: #ffffff;
    --odin-on-header: #242424;
    --odin-header-muted: #616161;

    --odin-nav-surface: #fafafa;
    --odin-nav-text: #242424;
    --odin-nav-heading: #616161;
    --odin-nav-hover-surface: #f0f0f0;
    --odin-nav-selected-surface: #ebf3fc;
    --odin-nav-accent: #0f6cbd;
    --odin-nav-border: #e0e0e0;

    --odin-link: #0f6cbd;

    /* One focus indicator for the whole app. A UI boundary, so the 3:1 threshold applies. */
    --odin-focus-ring: #0b5ed7;

    /* Filled primary button: white text on this fill (Fluent Communication Blue). */
    --odin-primary-surface: #0f6cbd;
    --odin-primary-border: #115ea3;
    --odin-on-primary: #ffffff;

    /* One error red, used three ways: message text on the surface, the invalid-field outline, and
       the fill of the crash banner (with --odin-on-error as its text). It was plain `red`
       (#ff0000), which is only 4.00:1 on white and failed WCAG AA for text. */
    --odin-error: #b32121;
    --odin-on-error: #ffffff;

    /* The "valid and modified" outline. It was #26b050 at 2.84:1, under the 3:1 that WCAG 1.4.11
       requires of a non-text UI indicator. */
    --odin-valid-outline: #198754;

    /* The dismissable "unhandled error" strip along the bottom, which carries body text. */
    --odin-notice-surface: #ffffe0;

    --odin-code-text: #c02d76;
}

/* ---- Shell chrome (Dynamics 365 restyle) ----------------------------------------------------
   The header and sitemap colours live HERE, not in the scoped Layout/*.razor.css files, precisely
   so the contrast suite measures them: the scoped sheets keep geometry only. */

.app-header {
    background-color: var(--odin-header-surface);
    color: var(--odin-on-header);
    height: var(--odin-header-height);
    border-bottom: 1px solid var(--odin-border);
}

    .app-header a,
    .app-header .app-header-brand {
        color: var(--odin-on-header);
    }

    /* LoginDisplay's "Entra not configured" note and greeting sit on the navy header, where
       Bootstrap's gray .text-muted would fall below AA. Bootstrap declares its colour with
       !important, so matching weight is the only way to win. */
    .app-header .text-muted {
        color: var(--odin-header-muted) !important;
    }

    /* The session buttons (log in / log out). Bootstrap's outline-secondary is gray-on-gray here,
       so repaint both outline variants with the header's own ink. */
    .app-header .btn-outline-primary,
    .app-header .btn-outline-secondary {
        color: var(--odin-on-header);
        border-color: var(--odin-header-muted);
        background-color: transparent;
    }

        .app-header .btn-outline-primary:hover,
        .app-header .btn-outline-secondary:hover {
            color: var(--odin-on-header);
            border-color: var(--odin-on-header);
            background-color: rgba(0, 0, 0, 0.05);
        }

.sidebar {
    background-color: var(--odin-nav-surface);
    border-right: 1px solid var(--odin-nav-border);
}

/* Sitemap areas (Common / Administration / Other): a small, quiet heading over each run of links,
   the way the D365 sitemap labels its groups. */
.nav-section {
    padding-bottom: 0.4rem;
}

    .nav-section:first-of-type {
        padding-top: 0.6rem;
    }

.nav-section-heading {
    color: var(--odin-nav-heading);
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    padding: 0.65rem 1rem 0.3rem 1rem;
}

.odin-nav .nav-link {
    color: var(--odin-nav-text);
    font-size: 0.875rem;
    height: 2.25rem;
    display: flex;
    align-items: center;
    gap: 0.7rem;
    border-radius: 6px;
    padding: 0 0.65rem;
}

    .odin-nav .nav-link:hover {
        background-color: var(--odin-nav-hover-surface);
        color: var(--odin-nav-text);
    }

    /* Work item #1526: the current page was signalled by a background shade and nothing else, which
       is a colour-only cue (WCAG 1.4.1). The inset accent bar gives the state a shape too, and
       PageNavLink adds the aria-current="page" that carries it to a screen reader. box-shadow rather
       than border-left on purpose: it paints inside the box, so the active link does not shift
       against its neighbours. */
    /* The selected row takes the accent as its text colour too, so its icon (drawn in currentColor,
       see css/icons.css) turns blue with it. */
    .odin-nav .nav-link.active {
        background-color: var(--odin-nav-selected-surface);
        color: var(--odin-nav-accent);
        font-weight: 600;
        box-shadow: inset 0.1875rem 0 0 var(--odin-nav-accent);
    }

    /* The sitemap is light, so the app-wide ring works here; lift the row as well so the focused
       link reads at a glance (work item #1526). */
    .odin-nav .nav-link:focus-visible {
        background-color: var(--odin-nav-hover-surface);
        color: var(--odin-nav-text);
        outline: 3px solid var(--odin-focus-ring);
        outline-offset: -3px;
    }

/* ---- Accessibility (work item #1526) --------------------------------------------------------
   Focus visibility (WCAG 2.4.7) and the skip link. The rules below are deliberately global: a
   single visible focus ring for every focusable thing beats per-component styling that new
   markup silently misses. AccessibilityContractTests pins the parts a redesign must not drop. */

/* App.razor's <FocusOnNavigate Selector="h1" /> moves focus to the page heading after every route
   change. Suppress the ring ONLY when the browser says this was not a keyboard interaction, so a
   mouse-driven navigation stays quiet while a keyboard user still sees where focus landed. */
h1:focus:not(:focus-visible) {
    outline: none;
}

/* The one focus indicator. High contrast against both the white content area and the light
   sitemap, drawn outside the element so it never reflows or is clipped by a tight container. */
:focus-visible {
    outline: 3px solid var(--odin-focus-ring);
    outline-offset: 2px;
    border-radius: 2px;
}

/* Skip link (WCAG 2.4.1): first thing in the tab order, off-screen until focused, then pinned to
   the top-left of the viewport above the sticky header. Bootstrap's .visually-hidden-focusable
   handles the hiding; this is only where it lands and what it looks like once revealed. */
.skip-link:focus {
    position: fixed;
    top: 0.5rem;
    left: 0.5rem;
    z-index: 2000;
    padding: 0.5rem 0.9rem;
    background-color: var(--odin-surface);
    color: var(--odin-link);
    border: 1px solid var(--odin-focus-ring);
    border-radius: 4px;
    text-decoration: underline;
}

/* The skip link's target. It only ever receives focus programmatically (from the skip link, which
   is a keyboard interaction), so :focus-visible carries the ring and a stray click does not. */
main:focus:not(:focus-visible) {
    outline: none;
}

a, .btn-link {
    color: var(--odin-link);
}

.btn-primary {
    color: var(--odin-on-primary);
    background-color: var(--odin-primary-surface);
    border-color: var(--odin-primary-border);
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.5rem;
    padding-bottom: 3rem;
}

/* ---- Modern refresh: type, page header, tables, cards, forms, buttons ------------------------
   Global on purpose. The portal has ~35 pages built from plain Bootstrap markup; restyling the
   Bootstrap classes they already use modernises every one of them at once, and a page added later
   picks the look up for free. Components that need new markup (PageHeader, SidePanel) live in
   Components/ and are documented there. */

body {
    color: var(--odin-surface-text);
    background-color: var(--odin-surface);
    -webkit-font-smoothing: antialiased;
}

h1, .h1 {
    font-size: 1.5rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--odin-heading-text);
    margin-bottom: 0.25rem;
}

h2, .h2 {
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--odin-heading-text);
}

h3, .h3, .h4, .h5 {
    font-weight: 600;
    color: var(--odin-heading-text);
}

/* Bootstrap's .text-muted is #6c757d, 4.69:1: legal, but the thinnest margin in the portal on its
   most common small text. Bootstrap declares it !important, so matching weight is the only way in. */
.text-muted {
    color: var(--odin-muted-text) !important;
}

/* The page header (Components/PageHeader.razor): title and one line of context on the left, the
   page's commands (New, Refresh, Export) on the right, a hairline under both. */
.page-header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.75rem 1.5rem;
    padding-bottom: 0.9rem;
    margin-bottom: 1.25rem;
    border-bottom: 1px solid var(--odin-border);
}

    .page-header h1 {
        margin-bottom: 0;
    }

.page-header-description {
    margin: 0.3rem 0 0 0;
    max-width: 75ch;
    font-size: 0.875rem;
    color: var(--odin-muted-text);
}

.page-header-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
}

/* Buttons: Fluent proportions, room for a leading icon. */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    border-radius: 6px;
    font-weight: 600;
}

.btn-sm {
    border-radius: 4px;
}

/* Tables: a contained, banded grid. The outline and header band are drawn with the neutral tokens;
   the row hover is a translucent overlay (Bootstrap 5.1 paints cells with box-shadow over
   --bs-table-accent-bg) so it darkens a highlighted .table-warning row instead of replacing it. */
.table {
    font-size: 0.875rem;
    border-collapse: separate;
    border-spacing: 0;
    border: 1px solid var(--odin-border);
    border-radius: 8px;
}

    .table > :not(caption) > * > * {
        padding: 0.6rem 0.75rem;
        border-bottom-color: var(--odin-border);
    }

    .table > thead > tr > th {
        background-color: var(--odin-subtle-surface);
        color: var(--odin-heading-text);
        font-weight: 600;
        white-space: nowrap;
    }

    .table > thead > tr > th:first-child {
        border-top-left-radius: 8px;
    }

    .table > thead > tr > th:last-child {
        border-top-right-radius: 8px;
    }

    .table > tbody > tr:last-child > * {
        border-bottom-width: 0;
    }

    /* A row's commands stay on one line, and a record's name (the first column's link) does not break
       at its hyphens: "contoso-prod" split over two lines reads as two things. */
    .table > tbody > tr > td:has(> .btn) {
        white-space: nowrap;
    }

    .table > tbody > tr > td:first-child > a {
        white-space: nowrap;
    }

    .table > tbody > tr:hover > * {
        --bs-table-accent-bg: rgba(0, 0, 0, 0.035);
    }

    .table > :not(:first-child) {
        border-top: 0;
    }

/* Cards: the dashboard tiles and detail panels. A hairline outline plus a soft resting shadow. */
.card {
    border: 1px solid var(--odin-border);
    border-radius: 8px;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
}

    .card > .card-header {
        background-color: var(--odin-subtle-surface);
        border-bottom-color: var(--odin-border);
        font-weight: 600;
    }

    .card > .card-header:first-child {
        border-radius: 8px 8px 0 0;
    }

/* Forms: labels above, in the heading ink; Fluent 2 fields with the underline that identifies them. */
.form-label {
    font-weight: 600;
    color: var(--odin-heading-text);
}

.form-control,
.form-select {
    border-color: var(--odin-input-border);
    border-bottom-color: var(--odin-input-accent);
    border-radius: 4px;
}

    .form-control:focus,
    .form-select:focus {
        border-color: var(--odin-input-border);
        border-bottom-color: var(--odin-primary-surface);
    }

.form-text {
    color: var(--odin-muted-text);
}

.alert {
    border-radius: 8px;
}

.badge {
    border-radius: 999px;
    font-weight: 600;
}

/* A table inside a card is flush with it: the card already draws the outline and the corners. */
.card > .table,
.card > .card-body > .table:only-child {
    border: 0;
    border-radius: 0;
    margin-bottom: 0;
}

/* Figure tiles (record pages): a small label over one large number, in a responsive grid. */
.odin-stats {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
    gap: 0.75rem;
}

.odin-stat {
    border: 1px solid var(--odin-border);
    border-radius: 8px;
    padding: 0.7rem 0.9rem;
}

.odin-stat-label {
    font-size: 0.75rem;
    color: var(--odin-muted-text);
}

.odin-stat-value {
    font-size: 1.35rem;
    font-weight: 600;
    line-height: 1.25;
    color: var(--odin-heading-text);
}

/* A card header that also carries controls (a filter, a sort) on its right. */
.card-header.odin-card-header-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
}

/* An empty list's placeholder: centred, quiet, with room for one call to action. */
.odin-empty {
    padding: 2.5rem 1rem;
    text-align: center;
    color: var(--odin-muted-text);
    border: 1px dashed var(--odin-border);
    border-radius: 8px;
}

/* ---- Side panel (Components/SidePanel.razor) --------------------------------------------------
   Create and edit forms open in a panel that slides in from the right, the way Dynamics 365's
   quick-create does, instead of appearing below a long list where nobody sees them. It is a native
   <dialog> opened with showModal(), so the browser supplies the modality: the page behind is inert,
   focus stays inside, and focus returns to the opener on close. */
dialog.odin-panel {
    margin: 0 0 0 auto;
    padding: 0;
    border: 0;
    width: min(38rem, 100vw);
    max-width: 100vw;
    height: 100%;
    max-height: 100%;
    background-color: var(--odin-surface);
    color: var(--odin-surface-text);
    box-shadow: -8px 0 32px rgba(0, 0, 0, 0.18);
}

    dialog.odin-panel[open] {
        display: flex;
        flex-direction: column;
        animation: odin-panel-in 0.18s ease-out;
    }

    dialog.odin-panel::backdrop {
        background: rgba(0, 0, 0, 0.32);
    }

    dialog.odin-panel.odin-panel-wide {
        width: min(56rem, 100vw);
    }

.odin-panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid var(--odin-border);
}

    .odin-panel-header h2 {
        margin: 0;
        font-size: 1.15rem;
    }

    /* The heading receives focus when the panel opens (so it is announced); like the page h1, the
       ring is suppressed only when that was not a keyboard interaction. */
    .odin-panel-header h2:focus:not(:focus-visible) {
        outline: none;
    }

.odin-panel-close {
    padding: 0.25rem;
}

.odin-panel-body {
    flex: 1;
    overflow-y: auto;
    padding: 1.25rem;
}

.odin-panel-footer {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: center;
    justify-content: flex-start;
    padding: 0.9rem 1.25rem;
    border-top: 1px solid var(--odin-border);
    background-color: var(--odin-subtle-surface);
}

@keyframes odin-panel-in {
    from {
        transform: translateX(2rem);
        opacity: 0;
    }

    to {
        transform: none;
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    dialog.odin-panel[open] {
        animation: none;
    }
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid var(--odin-valid-outline);
}

.invalid {
    outline: 1px solid var(--odin-error);
}

/* The field outline is a colour-only signal, so it is never the whole story: EditForm renders this
   message beside the field, which is the text cue that carries the same meaning (WCAG 1.4.1). */
.validation-message {
    color: var(--odin-error);
}

#blazor-error-ui {
    background: var(--odin-notice-surface);
    color: var(--odin-surface-text);
    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;
}

    /* A <button> (see index.html) so it is keyboard-reachable; stripped back to the plain glyph the
       error banner has always shown, so nothing about the look changes. */
    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
        padding: 0;
        background: none;
        border: 0;
        color: inherit;
        font: inherit;
        line-height: 1;
    }

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, var(--odin-error);
    padding: 1rem 1rem 1rem 3.7rem;
    color: var(--odin-on-error);
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.loading-progress {
    position: relative;
    display: block;
    width: 8rem;
    height: 8rem;
    margin: 20vh auto 1rem auto;
}

    .loading-progress circle {
        fill: none;
        stroke: #e0e0e0;
        stroke-width: 0.6rem;
        transform-origin: 50% 50%;
        transform: rotate(-90deg);
    }

        .loading-progress circle:last-child {
            stroke: var(--odin-primary-surface);
            stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
            transition: stroke-dasharray 0.05s ease-in-out;
        }

.loading-progress-text {
    position: absolute;
    text-align: center;
    font-weight: bold;
    inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}

    .loading-progress-text:after {
        content: var(--blazor-load-percentage-text, "Loading");
    }

code {
    color: var(--odin-code-text);
}
