/* ═══════════════════════════════════════════════════════════════════════════════════════════════
   ORPHEUS — THE COMPONENT LAYER
   Everything this app has that Bootstrap does not: the shell, the navigation rail, the ambient
   backdrop, the meander rule, the inscriptional label, the hero and its constellation.

   Loaded after bridge.css (which has already put Bootstrap in our colours) and before motion.css
   (which owns every @keyframes and every transition that can be switched off). Nothing here
   should set an animation — put it in motion.css, where prefers-reduced-motion can reach it.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */

/* ── Base ──────────────────────────────────────────────────────────────────────────────────── */

html {
    -webkit-text-size-adjust: 100%;
}

/* No overflow-x here. It was set to hidden to contain the backdrop's oversized drifting layers,
   which was both unnecessary and harmful: .o-backdrop already clips them with its own
   overflow:hidden and contain:strict, and `overflow-x: hidden` computes overflow-y to `auto`,
   which turns <body> into a scroll container. That changes what position:sticky measures against
   and leaves the document with two nested scrollers for no reason. Measured: body reported
   overflowY "auto" and its own scrollHeight of 1469px. */
body {
    background: var(--o-bg);
    color: var(--o-ink);
    font-family: var(--o-font-ui);
    font-size: var(--o-text-md);
    line-height: 1.6;
    min-height: 100vh;
}

/* Numbers get the telemetry face wherever they are the point of the sentence. Applied by class
   rather than automatically: a year inside a paragraph is prose, not a readout. */
.o-num {
    font-family: var(--o-font-data);
    font-variant-numeric: tabular-nums;
    letter-spacing: .01em;
}

/* ── The ambient backdrop ──────────────────────────────────────────────────────────────────────
   Fixed behind everything, never scrolls, never takes a click. Three layers:
     mesh   — coloured light, the thing that stops the page being flat black
     stars  — two tiles at different speeds, which is what makes it feel deep rather than printed
     grain  — a little noise, so the gradients do not band on cheap panels
   motion.css moves the star layers and stops them when the tab is hidden. */

.o-backdrop {
    position: fixed;
    inset: 0;
    z-index: var(--o-z-backdrop);
    pointer-events: none;
    overflow: hidden;
    contain: strict;       /* it can never affect layout, so let the browser skip it entirely */
}

.o-backdrop > * {
    position: absolute;
    inset: -12%;           /* oversized: the drift must never expose an edge */
}

.o-backdrop-mesh {
    background:
        radial-gradient(48rem 38rem at 12% -6%, rgba(var(--o-gold-rgb), .16), transparent 62%),
        radial-gradient(40rem 34rem at 96% 8%, rgba(var(--o-accent-rgb), .09), transparent 60%),
        radial-gradient(52rem 42rem at 72% 108%, rgba(var(--o-gold-rgb), .09), transparent 64%);
}

[data-bs-theme='light'] .o-backdrop-mesh {
    background:
        radial-gradient(48rem 38rem at 12% -6%, rgba(var(--o-gold-rgb), .22), transparent 62%),
        radial-gradient(40rem 34rem at 96% 8%, rgba(var(--o-accent-rgb), .08), transparent 60%),
        radial-gradient(52rem 42rem at 72% 108%, rgba(184, 138, 42, .14), transparent 64%);
}

/* The star tile is a MASK, not an image, so one asset takes whatever colour the theme puts in
   --o-star. An <img> would need a second copy in bronze for Marble. */
.o-backdrop-stars {
    background-color: var(--o-star);
    opacity: var(--o-bloom);
    -webkit-mask-image: var(--o-star-tile);
    mask-image: var(--o-star-tile);
    -webkit-mask-repeat: repeat;
    mask-repeat: repeat;
    -webkit-mask-size: 420px 420px;
    mask-size: 420px 420px;
    will-change: transform;
}

.o-backdrop-stars.is-far {
    opacity: calc(var(--o-bloom) * .5);
    -webkit-mask-size: 260px 260px;
    mask-size: 260px 260px;
}

:root {
    --o-star-tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='420' height='420'%3E%3Cg fill='%23fff'%3E%3Ccircle cx='34' cy='61' r='1.3'/%3E%3Ccircle cx='128' cy='22' r='.8'/%3E%3Ccircle cx='241' cy='87' r='1.5'/%3E%3Ccircle cx='356' cy='40' r='.9'/%3E%3Ccircle cx='77' cy='149' r='1'/%3E%3Ccircle cx='198' cy='176' r='1.7'/%3E%3Ccircle cx='309' cy='131' r='.8'/%3E%3Ccircle cx='399' cy='198' r='1.2'/%3E%3Ccircle cx='19' cy='243' r='1.4'/%3E%3Ccircle cx='151' cy='268' r='.9'/%3E%3Ccircle cx='268' cy='232' r='1.1'/%3E%3Ccircle cx='372' cy='299' r='1.6'/%3E%3Ccircle cx='58' cy='331' r='1'/%3E%3Ccircle cx='174' cy='366' r='1.3'/%3E%3Ccircle cx='291' cy='343' r='.85'/%3E%3Ccircle cx='412' cy='388' r='1'/%3E%3Ccircle cx='104' cy='406' r='1.15'/%3E%3Ccircle cx='226' cy='412' r='.8'/%3E%3C/g%3E%3C/svg%3E");

    /* feTurbulence, rendered once by the browser and tiled. Cheaper than a PNG and it scales. */
    --o-grain-tile: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='.5'/%3E%3C/svg%3E");
}

.o-backdrop-grain {
    background-image: var(--o-grain-tile);
    opacity: .035;
    mix-blend-mode: overlay;
}

[data-bs-theme='light'] .o-backdrop-grain {
    opacity: .05;
    mix-blend-mode: multiply;
}

/* ── The shell ─────────────────────────────────────────────────────────────────────────────── */

.o-shell {
    position: relative;
    z-index: var(--o-z-content);
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.o-main {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;          /* without this a wide table inside stretches the whole layout */
}

.o-content {
    flex: 1;
    padding: var(--o-s-6) var(--o-s-5) var(--o-s-7);
    width: 100%;
    max-width: 82rem;
}

@media (min-width: 641px) {
    .o-shell {
        flex-direction: row;
    }

    .o-content {
        padding: var(--o-s-7) var(--o-s-6) var(--o-s-8);
    }
}

/* ── The navigation rail ───────────────────────────────────────────────────────────────────────
   The single biggest carrier of the identity: it is on every screen of the signed-in app. Glass
   over the backdrop, a gold hairline down its trailing edge, and a meander band under the brand. */

.o-rail {
    position: relative;
    z-index: var(--o-z-rail);
    display: flex;
    flex-direction: column;
    background: linear-gradient(180deg,
        color-mix(in srgb, var(--o-bg-raised) 92%, transparent),
        color-mix(in srgb, var(--o-void) 88%, transparent));
    backdrop-filter: blur(var(--o-glass-blur));
    border-right: 1px solid var(--o-line);
}

@media (min-width: 641px) {
    .o-rail {
        width: var(--o-rail);
        flex: 0 0 var(--o-rail);
        height: 100vh;
        position: sticky;
        top: 0;
    }
}

/* There was a full-height meander running down the rail's trailing edge here. It was removed after
   looking at it: at 264px from the content it is not an edge treatment, it is a gold ladder down
   the middle of the screen, and it won every glance in the app. A meander is an ornament — it
   belongs where a reader's eye is already stopping. So it sits under the brand instead, once, and
   the rail's edge is the hairline border it always had. */

.o-brand {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--o-s-3);
    padding: var(--o-s-5) var(--o-s-5) var(--o-s-5);
    color: var(--o-gold);
    text-decoration: none;
}

/* The meander's one appearance in the chrome: a band under the wordmark, where the eye already
   stops. Faded out at both ends so it reads as inlay rather than as a border with a pattern. */
.o-brand::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 20px;
    background: var(--o-meander);
    opacity: .5;
    pointer-events: none;
    -webkit-mask-image: var(--o-meander-h), linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
    mask-image: var(--o-meander-h), linear-gradient(90deg, transparent, #000 18%, #000 82%, transparent);
    -webkit-mask-repeat: repeat-x, no-repeat;
    mask-repeat: repeat-x, no-repeat;
    -webkit-mask-size: var(--o-meander-tile), 100% 100%;
    mask-size: var(--o-meander-tile), 100% 100%;
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
}

.o-brand:hover,
.o-brand:focus {
    color: var(--o-gold-soft);
}

.o-brand-name {
    font-family: var(--o-font-display);
    font-weight: 700;
    font-size: 1.32rem;
    letter-spacing: .1em;
    line-height: 1;
    /* The wordmark is a gradient of the metal, not a flat fill. background-clip:text is the only
       way to get that on live text rather than as an image. */
    background: var(--o-gilt);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.o-brand-sub {
    display: block;
    font-family: var(--o-font-ui);
    font-size: .56rem;
    font-weight: 500;
    letter-spacing: var(--o-tracking-label);
    text-transform: uppercase;
    color: var(--o-ink-3);
    margin-top: .28em;
    -webkit-text-fill-color: var(--o-ink-3);   /* escape the parent's transparent text fill */
}

.o-nav {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--o-s-4) var(--o-s-3);
    flex: 1;
    overflow-y: auto;
    min-height: 0;
}

.o-nav-group {
    font-size: var(--o-text-2xs);
    font-weight: 600;
    letter-spacing: var(--o-tracking-label);
    text-transform: uppercase;
    color: var(--o-ink-3);
    padding: var(--o-s-4) var(--o-s-3) var(--o-s-2);
}

.o-nav-group:first-child {
    padding-top: var(--o-s-1);
}

/* Every rail item, whether it is a link or the sign-out form's button. */
.o-nav a,
.o-nav button {
    position: relative;
    display: flex;
    align-items: center;
    gap: var(--o-s-3);
    width: 100%;
    padding: .62rem var(--o-s-3);
    border: 0;
    border-radius: var(--o-r-sm);
    background: none;
    color: var(--o-ink-2);
    font-family: inherit;
    font-size: var(--o-text-sm);
    font-weight: 500;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.o-nav a:hover,
.o-nav button:hover {
    background: var(--o-surface-2);
    color: var(--o-ink);
}

/* The gold bar that marks where you are. It is always present and scales from nothing, so moving
   between pages reads as the light travelling rather than as a block appearing. */
.o-nav a::before {
    content: '';
    position: absolute;
    left: 0;
    top: 50%;
    width: 3px;
    height: 0;
    border-radius: var(--o-r-pill);
    background: var(--o-gold);
    transform: translateY(-50%);
}

.o-nav a.active {
    background: var(--o-gold-wash);
    color: var(--o-gold-ink);
    font-weight: 600;
}

.o-nav a.active::before {
    height: 62%;
    box-shadow: var(--o-glow-gold);
}

.o-nav a.active .o-icon {
    color: var(--o-gold);
    opacity: 1;
}

.o-icon {
    flex: none;
    width: 18px;
    height: 18px;
    opacity: .75;
}

.o-nav-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.o-rail-foot {
    padding: var(--o-s-3) var(--o-s-4) var(--o-s-4);
    border-top: 1px solid var(--o-line);
    display: flex;
    flex-direction: column;
    gap: var(--o-s-3);
}

/* ── The theme toggle ──────────────────────────────────────────────────────────────────────────
   A two-state segmented control rather than a single icon that swaps: with one icon nobody can
   tell whether it shows the current theme or the one it would switch to, which is the oldest
   complaint about theme toggles. Both options are visible and one is lit. */

.o-theme {
    display: flex;
    gap: 2px;
    padding: 3px;
    border: 1px solid var(--o-line);
    border-radius: var(--o-r-pill);
    background: var(--o-inset);
}

.o-theme button {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .4em;
    padding: .32rem .5rem;
    border: 0;
    border-radius: var(--o-r-pill);
    background: none;
    color: var(--o-ink-3);
    font-family: inherit;
    font-size: var(--o-text-2xs);
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    cursor: pointer;
}

.o-theme button:hover {
    color: var(--o-ink);
}

.o-theme button[aria-pressed='true'] {
    background: var(--o-gold-wash-2);
    color: var(--o-gold-ink);
    box-shadow: var(--o-glow-inset);
}

.o-theme svg {
    width: 13px;
    height: 13px;
}

/* ── The mobile rail ───────────────────────────────────────────────────────────────────────────
   Under 641px the rail becomes a bar with a disclosure. The checkbox is the state, so it works
   with no script at all — which matters on the sign-in page, where a script failure would
   otherwise lock someone out of the menu. */

.o-rail-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.o-rail-toggle {
    appearance: none;
    position: absolute;
    top: var(--o-s-4);
    right: var(--o-s-4);
    width: 2.75rem;
    height: 2.25rem;
    margin: 0;
    border: 1px solid var(--o-line);
    border-radius: var(--o-r-sm);
    background: var(--o-surface-2) var(--o-burger) no-repeat center / 1.35rem;
    cursor: pointer;
    z-index: 1;
}

:root {
    --o-burger: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23E8B44A' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 7h16M4 12h16M4 17h16'/%3E%3C/svg%3E");
}

[data-bs-theme='light'] {
    --o-burger: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238A6512' stroke-width='2' stroke-linecap='round'%3E%3Cpath d='M4 7h16M4 12h16M4 17h16'/%3E%3C/svg%3E");
}

.o-rail-body {
    display: none;
}

.o-rail-toggle:checked ~ .o-rail-body {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
}

@media (min-width: 641px) {
    .o-rail-toggle {
        display: none;
    }

    .o-rail-body {
        display: flex;
        flex-direction: column;
        flex: 1;
        min-height: 0;
    }
}

/* ── The meander rule ──────────────────────────────────────────────────────────────────────────
   A proper Greek key: a continuous bottom line that carries from tile to tile, with a square
   spiral rising out of it. Used as a section divider and down the rail's edge. Both are masks,
   so the colour comes from --o-meander and one asset serves both themes.

   In Marble a second, offset copy in --o-meander-emboss sits under it, which makes the rule look
   ENGRAVED rather than drawn — the light-theme substitute for the glow it has in Obsidian. */

:root {
    /* One and a half turns per tile, not three. The first version packed a full square spiral into
       a 40×16 tile at 1.6 stroke and, rendered at its real size, read as a row of little boxes
       with no line joining them — the connecting bottom rail was there but too fine to see against
       everything else going on. Fewer turns, a heavier stroke and a taller tile, and it reads as a
       Greek key at the size it is actually used.

       The bottom line spans the FULL tile width so consecutive tiles join into one continuous
       rail; the hook rises out of it. That is what makes a meander a meander rather than a
       repeated glyph. */
    --o-meander-h: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='20' viewBox='0 0 40 20'%3E%3Cg fill='none' stroke='%23fff' stroke-width='2.8'%3E%3Cpath d='M0 18.6h40'/%3E%3Cpath d='M32 18.6V3H8v10h15'/%3E%3C/g%3E%3C/svg%3E");

    --o-meander-tile: 40px 20px;
}

/* Twenty pixels tall, not fourteen. The drawing was always right — rendered at 4× it is a
   textbook Greek key — but at 14px with a 2.2 stroke the counters between the arms close up under
   anti-aliasing and the whole thing reads as a row of little boxes. The proportions are unchanged;
   only the size is, because below about 18px this motif has no legible version. */
.o-rule {
    height: 20px;
    margin: var(--o-s-5) 0;
    background: var(--o-meander);
    -webkit-mask-image: var(--o-meander-h);
    mask-image: var(--o-meander-h);
    -webkit-mask-repeat: repeat-x;
    mask-repeat: repeat-x;
    -webkit-mask-size: var(--o-meander-tile);
    mask-size: var(--o-meander-tile);
    position: relative;
}

.o-rule::after {
    content: '';
    position: absolute;
    inset: 1px 0 -1px;
    background: var(--o-meander-emboss);
    -webkit-mask-image: var(--o-meander-h);
    mask-image: var(--o-meander-h);
    -webkit-mask-repeat: repeat-x;
    mask-repeat: repeat-x;
    -webkit-mask-size: var(--o-meander-tile);
    mask-size: var(--o-meander-tile);
    z-index: -1;
}

/* A short meander used as an ornament under a page title rather than as a full-width divider. */
.o-rule-short {
    width: 120px;
    margin: var(--o-s-3) 0 var(--o-s-5);
}

/* ── The inscriptional label ───────────────────────────────────────────────────────────────────
   Gold, small, letterspaced, uppercase. Used for section headings and table captions — the voice
   that makes the interface feel carved rather than typed. */

.o-label {
    display: flex;
    align-items: center;
    gap: var(--o-s-3);
    font-size: var(--o-text-2xs);
    font-weight: 600;
    letter-spacing: var(--o-tracking-label);
    text-transform: uppercase;
    color: var(--o-gold-ink);
    margin: 0 0 var(--o-s-3);
}

/* The hairline that runs off to the right of a label, stopping at the container's edge. */
.o-label::after {
    content: '';
    flex: 1;
    height: 1px;
    background: linear-gradient(90deg, var(--o-line-strong), transparent);
}

/* ── The account pages ─────────────────────────────────────────────────────────────────────────
   Sign in, sign up, and the password and confirmation flows. Each is one short form with one
   thing to do, and full-width they read as a form stranded at the top-left of an empty screen.
   As a centred card they read as a door.

   Keyed off data-section from MainLayout, which is how fourteen files with four different internal
   shapes get the same treatment without fourteen edits. /Account/Manage is deliberately NOT in
   this family: those are real settings pages and belong at full width.

   The col-* reset is the one concession to those internal shapes — Login and Register put their
   form in a col-lg-6, which inside a 34rem card would be half of not very much. */

[data-section='account'] {
    max-width: 34rem;
    margin-inline: auto;
    margin-block: var(--o-s-7);
    padding: var(--o-s-6);
    background: var(--o-surface);
    border: 1px solid var(--o-line);
    border-radius: var(--o-r-lg);
    backdrop-filter: blur(var(--o-glass-blur));
    box-shadow: var(--o-shadow-lg);
}

[data-section='account'] .row > [class*='col-'] {
    flex: 0 0 100%;
    max-width: 100%;
    margin-left: 0;     /* offset-lg-1 on Register's second column */
}

[data-section='account'] h1 {
    font-size: var(--o-text-2xl);
    margin-bottom: var(--o-s-2);
}

/* A hairline meander across the top of the card, so the door has a lintel. */
[data-section='account']::before {
    content: '';
    display: block;
    height: 20px;
    margin: calc(var(--o-s-2) * -1) 0 var(--o-s-4);
    background: var(--o-meander);
    opacity: .4;
    -webkit-mask-image: var(--o-meander-h), linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
    mask-image: var(--o-meander-h), linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
    -webkit-mask-repeat: repeat-x, no-repeat;
    mask-repeat: repeat-x, no-repeat;
    -webkit-mask-size: var(--o-meander-tile), 100% 100%;
    mask-size: var(--o-meander-tile), 100% 100%;
    -webkit-mask-composite: source-in;
    mask-composite: intersect;
}

/* ── Panels and empty states ───────────────────────────────────────────────────────────────── */

.empty-state {
    text-align: center;
    padding: var(--o-s-7) var(--o-s-5);
    border: 1px dashed var(--o-line-strong);
    border-radius: var(--o-r);
    background: var(--o-surface);
    color: var(--o-ink-2);
}

/* ── The landing hero ──────────────────────────────────────────────────────────────────────────
   Two columns on a wide screen: the argument on the left, Lyra on the right. Zeus set Orpheus's
   lyre in the sky as that constellation, which is why the star field is not decoration — it is
   the same object as the logo. */

.o-hero {
    display: grid;
    gap: var(--o-s-6);
    align-items: center;
    padding: var(--o-s-5) 0 var(--o-s-6);
}

@media (min-width: 900px) {
    .o-hero {
        grid-template-columns: minmax(0, 1.15fr) minmax(0, .85fr);
        gap: var(--o-s-8);
        padding: var(--o-s-7) 0 var(--o-s-8);
    }
}

.o-hero h1 {
    font-size: var(--o-text-3xl);
    line-height: 1.06;
    margin: 0 0 var(--o-s-4);
    max-width: 16ch;
}

@media (min-width: 1200px) {
    .o-hero h1 {
        font-size: var(--o-text-4xl);
    }
}

.o-hero .lead {
    max-width: 46ch;
}

/* The gold word in the headline. Kept as a <span> in the markup so the emphasis is editable
   copy rather than a CSS rule guessing at a word. */
.o-gilt {
    background: var(--o-gilt);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

/* ── The Lyra panel ────────────────────────────────────────────────────────────────────────────
   ART SLOT. This is the replaceable region: today it holds the procedural constellation below;
   swapping in a raster hero means putting an <img> inside .o-lyra and adding .has-art to it,
   which hides the SVG and keeps every size, radius and shadow exactly as they are. Nothing else
   has to change. */

.o-lyra {
    position: relative;
    aspect-ratio: 4 / 5;
    max-height: 30rem;
    border-radius: var(--o-r-lg);
    border: 1px solid var(--o-line);
    background:
        radial-gradient(60% 50% at 50% 34%, rgba(var(--o-gold-rgb), .14), transparent 70%),
        linear-gradient(160deg, color-mix(in srgb, var(--o-bg-raised) 90%, transparent), var(--o-void));
    overflow: hidden;
    box-shadow: var(--o-shadow-lg);
}

.o-lyra > img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: none;
}

.o-lyra.has-art > img { display: block; }
.o-lyra.has-art > svg { display: none; }

.o-lyra > svg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

/* Its own colour rather than --o-star-dim, which is the backdrop's dust and is tuned to be barely
   there. At the panel's real size (238px for a 200-unit viewBox) a 1px line at dust opacity
   disappeared and Lyra read as six unrelated dots — the lines ARE the constellation.

   --o-star is itself rgba(...,.85), so stroke-opacity MULTIPLIES against that: .5 here measured as
   an effective .425, which is why the first correction barely moved it. .75 gives ~.64. */
.o-lyra-line {
    stroke: var(--o-star);
    stroke-opacity: .75;
    stroke-width: 1.4;
    fill: none;
}

.o-lyra-star {
    fill: var(--o-star);
}

.o-lyra-dust {
    fill: var(--o-star-dim);
}

/* Vega gets a real halo. A drop-shadow filter on a 5px circle is cheap, and it is the only way
   to make one star look brighter than another rather than just bigger. */
.o-lyra-vega {
    fill: var(--o-vega);
    filter: drop-shadow(0 0 7px rgba(var(--o-gold-rgb), .85));
}

.o-lyra-caption {
    position: absolute;
    left: var(--o-s-5);
    right: var(--o-s-5);
    bottom: var(--o-s-5);
    font-size: var(--o-text-2xs);
    letter-spacing: var(--o-tracking-label);
    text-transform: uppercase;
    color: var(--o-ink-3);
}

.o-lyra-caption strong {
    color: var(--o-gold-ink);
    font-weight: 600;
}

/* ── Live indicators ───────────────────────────────────────────────────────────────────────────
   Cyan is reserved for this: something that is happening right now. If it is not live, it is not
   cyan. The bars are real elements rather than a GIF so they take the theme and stop under
   reduced-motion. */

.o-eq {
    display: inline-flex;
    align-items: flex-end;
    gap: 2px;
    height: 1em;
    vertical-align: -.12em;
}

.o-eq span {
    width: 2.5px;
    border-radius: 1px;
    background: var(--o-accent);
    height: 35%;
}

.o-live {
    display: inline-flex;
    align-items: center;
    gap: .45em;
    color: var(--o-accent-ink);
    font-family: var(--o-font-data);
    font-size: var(--o-text-2xs);
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
}

/* ── Prose (the legal pages) ───────────────────────────────────────────────────────────────────
   Moved here from app.css, which is gone. Long-form reading, so the measure is capped and the
   line height is looser than the rest of the interface. */

.legal,
.o-prose {
    max-width: var(--o-measure);
    line-height: 1.7;
    color: var(--o-ink-2);
}

.legal h2,
.o-prose h2 {
    font-size: var(--o-text-lg);
    margin-top: var(--o-s-6);
    scroll-margin-top: var(--o-s-5);
    color: var(--o-ink);
}

.legal p,
.o-prose p {
    margin-bottom: var(--o-s-4);
}

.legal-meta {
    color: var(--o-ink-3);
    font-size: var(--o-text-sm);
}

.legal-summary {
    background: var(--o-gold-wash);
    border: 1px solid var(--o-line-strong);
    border-radius: var(--o-r);
    padding: var(--o-s-4) var(--o-s-5) var(--o-s-1);
    margin: var(--o-s-5) 0 var(--o-s-6);
}

.legal-table td {
    font-size: var(--o-text-sm);
    vertical-align: top;
}

/* The two clauses US courts expect to be conspicuous. */
.legal-caps {
    font-weight: 600;
    color: var(--o-ink);
}

/* ── The footer ────────────────────────────────────────────────────────────────────────────── */

.site-footer {
    border-top: 1px solid var(--o-line);
    padding: var(--o-s-5) var(--o-s-5) var(--o-s-6);
    color: var(--o-ink-3);
    font-size: var(--o-text-xs);
    line-height: 1.6;
}

@media (min-width: 641px) {
    .site-footer {
        padding-left: var(--o-s-6);
        padding-right: var(--o-s-6);
    }
}

.site-footer nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--o-s-2) var(--o-s-5);
    margin-bottom: var(--o-s-3);
}

.site-footer a {
    color: var(--o-ink-2);
    text-decoration: none;
}

.site-footer a:hover {
    color: var(--o-gold-ink);
    text-decoration: underline;
    text-underline-offset: .2em;
}

.site-footer p {
    margin: 0 0 var(--o-s-2);
    max-width: 64rem;
}

/* ── The Blazor error strip ────────────────────────────────────────────────────────────────── */

#blazor-error-ui {
    color-scheme: dark;
    background: #2A1014;
    color: #FFD9DD;
    border-top: 2px solid var(--o-danger);
    bottom: 0;
    box-shadow: 0 -8px 24px rgba(0, 0, 0, .5);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: .7rem 1.25rem .8rem;
    position: fixed;
    width: 100%;
    z-index: var(--o-z-toast);
    font-size: var(--o-text-sm);
}

#blazor-error-ui .reload {
    color: var(--o-gold);
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    right: 1rem;
    top: .6rem;
}

/* ── The reporter ──────────────────────────────────────────────────────────────────────────────
   The capture button and the overlay it opens. Markup in MainLayout, behaviour in feedback.js.

   STACKING, which is the part that breaks silently: the overlay has to sit above everything else
   in the app, because a large share of what people want to report happens INSIDE a dialog, and a
   reporter that cannot open over one cannot be used from exactly the states most worth reporting.
   The scale, in one place so a new overlay can be slotted into it deliberately:

       rotation editor's "rebuild now?" dialog   1070 / 1071
       help tooltips                             1060
       the capture button                        9990
       the reporter overlay                      10000

   Nothing in this app may be given a z-index above 10000. */

.o-report-fab {
    position: fixed;
    right: var(--o-s-5);
    bottom: var(--o-s-5);
    z-index: 9990;
    display: inline-flex;
    align-items: center;
    gap: var(--o-s-2);
    padding: .6rem 1.05rem;
    border: 1px solid var(--o-line-strong);
    border-radius: var(--o-r-pill);
    background: color-mix(in srgb, var(--o-bg-raised) 92%, transparent);
    backdrop-filter: blur(var(--o-glass-blur));
    color: var(--o-gold-ink);
    font-family: inherit;
    font-size: var(--o-text-sm);
    font-weight: 600;
    cursor: pointer;
    box-shadow: var(--o-shadow);
    transition: border-color var(--o-t-fast) var(--o-ease),
                color var(--o-t-fast) var(--o-ease),
                box-shadow var(--o-t-base) var(--o-ease);
}

.o-report-fab:hover {
    border-color: var(--o-gold);
    color: var(--o-gold-soft);
    box-shadow: var(--o-glow-gold);
}

[data-bs-theme='light'] .o-report-fab:hover {
    color: var(--o-gold-deep);
}

/* On a phone the label costs more room than it earns; the icon and the shape carry it. */
@media (max-width: 560px) {
    .o-report-fab {
        right: var(--o-s-4);
        bottom: var(--o-s-4);
        padding: .7rem;
    }

    .o-report-fab span {
        display: none;
    }
}

.o-report {
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: flex;
    align-items: flex-end;
    justify-content: center;
    padding: var(--o-s-4);
}

@media (min-width: 640px) {
    .o-report {
        align-items: center;
    }
}

.o-report-scrim {
    position: absolute;
    inset: 0;
    background: rgba(4, 3, 8, .68);
    backdrop-filter: blur(3px);
}

.o-report-panel {
    position: relative;
    width: min(38rem, 100%);
    max-height: calc(100vh - var(--o-s-6));
    overflow-y: auto;
    padding: var(--o-s-5) var(--o-s-5) var(--o-s-4);
    border: 1px solid var(--o-line-strong);
    border-radius: var(--o-r-lg);
    background: var(--o-surface-solid);
    box-shadow: var(--o-shadow-lg);
}

.o-report-panel h2 {
    font-size: var(--o-text-xl);
    margin: 0 0 var(--o-s-4);
}

/* Type: three real radios, styled as a segmented control. Radios rather than a select so all
   three options are readable at once — this is the first thing the form asks and it should not
   need opening. */
.o-report-types {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: var(--o-s-2);
    margin-bottom: var(--o-s-4);
}

.o-report-type input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.o-report-type span {
    display: block;
    padding: .55rem .75rem;
    border: 1px solid var(--o-line);
    border-radius: var(--o-r-sm);
    background: var(--o-surface);
    color: var(--o-ink-2);
    font-size: var(--o-text-sm);
    text-align: center;
    cursor: pointer;
    transition: border-color var(--o-t-fast) var(--o-ease),
                background-color var(--o-t-fast) var(--o-ease),
                color var(--o-t-fast) var(--o-ease);
}

.o-report-type input:checked + span {
    border-color: var(--o-gold);
    background: var(--o-gold-wash);
    color: var(--o-gold-ink);
    font-weight: 600;
}

/* The radio is visually hidden, so the focus ring has to be put back on what is visible —
   otherwise the control looks unreachable to anyone using a keyboard. */
.o-report-type input:focus-visible + span {
    outline: 2px solid var(--o-gold);
    outline-offset: 2px;
}

.o-report-form .form-label {
    margin-top: var(--o-s-3);
}

.o-report-count {
    margin: var(--o-s-1) 0 0;
    font-family: var(--o-font-data);
    font-size: var(--o-text-2xs);
    color: var(--o-ink-3);
    text-align: right;
}

.o-report-context {
    margin: var(--o-s-4) 0 0;
    border: 1px solid var(--o-line);
    border-radius: var(--o-r-sm);
    background: var(--o-surface);
    padding: var(--o-s-3) var(--o-s-4);
}

.o-report-context > summary {
    cursor: pointer;
    font-size: var(--o-text-sm);
    color: var(--o-gold-ink);
}

.o-report-context dl {
    display: grid;
    grid-template-columns: minmax(6rem, auto) 1fr;
    gap: .3rem var(--o-s-3);
    margin: var(--o-s-3) 0 0;
    font-size: var(--o-text-xs);
}

.o-report-context dt {
    color: var(--o-ink-3);
    font-weight: 500;
}

.o-report-context dd {
    margin: 0;
    color: var(--o-ink-2);
    font-family: var(--o-font-data);
    overflow-wrap: anywhere;
}

.o-report-note {
    margin: var(--o-s-3) 0 0;
    font-size: var(--o-text-xs);
    color: var(--o-ink-3);
}

.o-report-error {
    margin: var(--o-s-4) 0 0;
    padding: var(--o-s-2) var(--o-s-3);
    border-left: 3px solid var(--o-danger);
    border-radius: var(--o-r-xs);
    background: rgba(var(--o-danger-rgb), .10);
    color: var(--o-danger-ink);
    font-size: var(--o-text-sm);
}

.o-report-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--o-s-3);
    margin: var(--o-s-5) 0 0;
}

.o-report-done {
    text-align: center;
    padding: var(--o-s-4) 0 var(--o-s-2);
}

.o-report-done .o-report-actions {
    justify-content: center;
}

.o-report-tick {
    width: 2.6rem;
    height: 2.6rem;
    color: var(--o-success);
    opacity: 1;
    margin-bottom: var(--o-s-3);
}

/* The page behind an open reporter must not scroll — on a phone, scrolling the page under a
   fixed overlay is the commonest way a modal feels broken. */
body.o-report-locked {
    overflow: hidden;
}
