h1:focus {
    outline: none;
}

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

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

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

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

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}
/* ------------------------------------------------------------------------
   THE TfLens PALETTE — the few tokens whose value differs from the library's.

   TR-001 is CLOSED as of TrBlazeUI 2.1.0-ci.10: the shipped trblazeui.css now
   declares every token it references — `--alert-*` (colour, `-bg` and
   `-foreground`), all nine `--sidebar*`, `--chart-1..5`, `--font-sans` and
   `--font-mono` — in zero-specificity `:where(:root)` / `:where(.dark)` rules.
   The copies that used to be restated in AuthLayout, Profile and here (because
   an Alert otherwise rendered colourless and the sidebar painted nothing) are
   gone; a plain `:root` rule wins over `:where(:root)` with no !important, so
   what remains below is an ordinary theme override, not a workaround.

   ONE thing is still stated here: `--chart-1..5`, the chart series ramp, which
   the mockups fix and the library's own five do not match.

   The values below are `docs/mockups/*.html`'s five, converted to OKLCH — the
   same five, to the same precision, that GateOutcomes.razor.css and
   Routing.razor.css were each holding a private copy of. Until now this block
   held a THIRD, unrelated ramp (`--chart-2` violet against the mockups' green),
   and because a plain `:root` beats the library's `:where(:root)` it won
   app-wide, which is exactly why those two pages had to shadow it back. Aligned
   here, the page-level copies become exact duplicates and can go.

   `--alert-*` is NO LONGER declared here. It used to be, with the mockups' four
   hues — but only the hues, not the `-bg` and `-foreground` companions the
   library pairs with each. That left an Alert taking its BORDER from one palette
   and its SURFACE and INK from another: the sign-in failure alert drew a hue
   25.3 border around a hue 22.216 ground. Half a palette in front of a whole one
   is the shape of the bug TR-001 was, so the whole family now comes from the
   shipped sheet, which is theme-aware and contrast-validated across every
   foreground/surface pair. The hues move by a few degrees against the mockups'
   hexes and the alerts are internally consistent for the first time.
   ------------------------------------------------------------------------ */
:root {
    --chart-1: oklch(71.4% 0.143 254.6); /* #60a5fa */
    --chart-2: oklch(77.3% 0.153 163.2); /* #34d399 */
    --chart-3: oklch(72.5% 0.175 349.8); /* #f472b6 */
    --chart-4: oklch(83.7% 0.164 84.4); /* #fbbf24 */
    --chart-5: oklch(70.9% 0.159 293.5); /* #a78bfa */
}

/* ---------------- shell: the sidebar differences against the mockup -------- */

/* REQ-UI-006 — the two sidebar tokens the mockups set darker than the library does.

   Every mockup draws the dark sidebar as `--sidebar:#0f0f0f` (a step UP from the `#0a0a0a` page
   ground) and its hover/active surface as `--accent:#1f1f1f`. TrBlazeUI's dark defaults are lighter
   than both, so the panel read as a lighter slab than the design. The light palette and the other
   seven `--sidebar*` tokens now match the shipped defaults exactly and are no longer restated.

   These live in app.css, not in MainLayout.razor.css, because the phone menu renders inside the
   PortalHost — outside every element a scoped stylesheet here could reach. */
:root.dark {
    --sidebar: oklch(0.169 0 0);
    --sidebar-accent: var(--muted);
}

/* TR-035 is CLOSED. The phone sheet reads `--sidebar-width-mobile` (18rem) itself now, so the
   `@media (max-width: 47.999rem) { :root { --sidebar-width: var(--sidebar-width-mobile) } }` rule
   that used to stand here has been deleted: left in place it would compound with the library's own
   fix and leave the DESKTOP token wrong below 768px. `SidebarProvider` also takes
   `Width` / `MobileWidth` / `IconWidth` for a shell that wants to differ from the tokens; TfLens
   wants exactly the tokens, so it passes none of them. */

/* The sidebar's parts measured to the mockups' `.sb-head` / `.sb-body` / `.grp-label` / `.sb-foot`.
   Hooks are set in MainLayout.razor and match in the desktop sidebar and the phone menu alike. */
.tflens-sb-head {
    /* `.sb-head{height:64px}` — level with the header's 64px rule, which the border-b continues. */
    height: 4rem;
    justify-content: center;
    padding-top: 0;
    padding-bottom: 0;
}

/* REQ-UI-075 / BRD-209 — the horizontal lockup (`.mark` + `.brand .word` in misses.html). The tile is the
   mockup's `.mark`: a 32 x 32 / 8px box filled with the brand's #0284C7 (in both themes, never a theme
   token — ADR-033) holding the approved white glyph, TfLens-appiconfg.svg, at full size. The wordmark is
   58% of the tile's height (18.56px), TfLens-horizontal.svg's own proportion, 589:139 wide. The text title
   and the subtitle are retired, so these replace `.tflens-sb-title` / `.tflens-sb-subtitle`. */
.tflens-sb-mark {
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    border-radius: 8px;
    background: #0284C7;
    overflow: hidden;
    flex: none;
}

    .tflens-sb-mark svg {
        display: block;
        width: 32px;
        height: 32px;
    }

/* Light: TfLens-horizontal.svg (976 x 320) scaled by 78.65/589 so its wordmark region (x 340, y 46,
   589 x 139) fills the box exactly — 130.33 x 42.73 px, offset -45.40 / -6.14 px. Dark: the white PNG. */
.tflens-sb-wordmark {
    display: block;
    flex: none;
    width: 78.65px;
    height: 18.56px;
    background-image: var(--tflens-wm-light);
    background-repeat: no-repeat;
    background-size: 130.33px 42.73px;
    background-position: -45.40px -6.14px;
}

/* The dark theme swaps in the white wordmark, keyed off the `dark` class on <html> — a second image, never
   a filter (ADR-033). The tile is the same in both themes. */
.dark .tflens-sb-wordmark {
    background-image: var(--tflens-wm-dark);
    background-size: contain;
    background-position: left center;
}

/* `.sb-body{padding:12px 8px;gap:16px}` with the groups themselves unpadded, so the items start 8px in
   from the edge and the active bar (below) can sit on the edge itself. */
.tflens-sb-body {
    padding: 12px 8px;
    gap: 16px;
}

    .tflens-sb-body > [data-sidebar="group"] {
        padding: 0;
    }

    /* `.nav a{height:36px;padding:0 10px;gap:10px}`, stacked with no gap between them. The icon rail
       keeps the library's own sizing: it sets `!size-8 !p-2` there, which these do not touch. */
    .tflens-sb-body ul {
        gap: 0;
    }

    .tflens-sb-body a[data-testid^="nav-"] {
        height: 36px;
        padding: 0 10px;
        gap: 10px;
    }

    [data-collapsible="icon"] .tflens-sb-body ul {
        gap: 4px;
    }

/* In the 48px icon rail the footer's switch and version line cannot fit: they spilled out of the rail
   as a clipped "v0.1 · rea" under the page. The mockup hides both there
   (`body.sidebar-collapsed .sb-foot span, .sb-foot .switch{display:none}`) and keeps the ruled strip. */
[data-collapsible="icon"] .tflens-sb-foot > * {
    display: none;
}

/* The phone menu is TrBlazeUI's Sheet, which spaces its children with `gap-4`; the mockup's panel runs
   the head straight into the body (`.sb-head` then `.sb-body{padding-top:12px}`), so the 16px gap only
   pushed every item down. */
[role="dialog"]:has(> .tflens-sb-head) {
    gap: 0;
}

/* `.grp-label` — an 11px uppercase caption, one line, 6px above its items. The library's label is a
   32px-tall `h-8` box, which stood each caption 10px taller than the mockup's and put an empty half-line
   under "WORKSPACE" and "REPORTS". */
[data-sidebar="group-label"],
.tflens-sidebar-group-label {
    height: auto;
    margin-bottom: 6px;
    padding: 0 8px;
    text-transform: uppercase;
    letter-spacing: .06em;
    font-size: 11px;
    line-height: 1.5;
    font-weight: 500;
    white-space: nowrap;
    color: var(--muted-foreground);
}

/* The current page — `.nav a.active`: the muted surface, weight 500, and a 3px bar in the primary ink
   on the sidebar's own edge (`left:-8px` from an item that starts 8px in).
   TrBlazeUI marks the current item with an EMPTY data-active attribute (data-active="") plus
   aria-current="page" — not data-active="true". Match on presence. The bar hangs off the item's <li>
   because the <a> clips its own overflow. Scoped to the sidebar body so no other current-page link
   (a pager, a breadcrumb) picks up the bar. */
.tflens-sb-body a[data-active],
.tflens-sb-body a[aria-current="page"] {
    background: var(--sidebar-accent);
    border-radius: 8px;
    font-weight: 500;
}

.tflens-sb-body li:has(> a[data-active])::before,
.tflens-sb-body li:has(> a[aria-current="page"])::before {
    content: "";
    position: absolute;
    left: -8px;
    top: 8px;
    bottom: 8px;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--sidebar-primary);
}

/* The theme switch's glyph follows the theme, as the mockups' `.switch .sun` / `.switch .moon` do:
   a sun on the light palette, a moon on the dark one. Both are rendered; one is hidden. */
.tflens-theme-moon,
.dark .tflens-theme-sun {
    display: none;
}

.dark .tflens-theme-moon {
    display: block;
}

/* The repo count rendered on its own line beneath the Repos link instead of
   inline on the row, which looked like a layout bug rather than a badge. */
/* The badge is already absolutely positioned by the library, but its <li> carries no class or
   attribute and so establishes no positioning context — the badge resolved against a distant
   ancestor and landed on the line BELOW the Repos link, looking like a layout bug. Giving the
   containing item `position: relative` is what actually anchors it to its own row. */
nav li:has([data-testid="nav-repo-count"]),
li:has(> [data-testid="nav-repo-count"]) {
    position: relative;
}

[data-testid="nav-repo-count"] {
    top: 50%;
    right: 10px;
    bottom: auto;
    transform: translateY(-50%);
}

/* ------------------------------------------------------------------------
   THE ANONYMOUS SPLIT LAYOUT (REQ-UI-001..004, REQ-NFR-015)

   These rules used to live in Components/Layout/AuthLayout.razor.css, i.e. in
   the Blazor SCOPED-CSS BUNDLE (TfLens.styles.css). That made every anonymous
   screen depend on one optional file: `trblazeui.css` and `app.css` alone are
   not enough to lay this page out, so when the bundle did not arrive the whole
   sign-in screen collapsed into one full-width column starting at x=0 — with no
   console error, no Blazor error and no log line. It reached the owner in UAT
   on 2026-08-28 (docs/uatissuessc/Login.png) and was reproduced pixel-for-pixel.

   app.css is served straight out of wwwroot by UseStaticFiles and does not go
   through the static-web-assets manifest, so it has strictly fewer ways to go
   missing. The class names are unique to this layout, so dropping the scoping
   attribute changes nothing about what they match.

   TR-001 is closed and the token block that used to head `.auth-split` is gone
   — the shipped sheet defines `--sidebar*` and every `--alert-*` itself, and
   the four status hues the mockups draw are declared once at the top of this
   file instead of a second time here.

   TR-002 is closed for the sheet as a whole (`sm:`/`md:`/`lg:`/`xl:`/`2xl:` all
   ship, at 40/48/64/80/96rem) but NOT for this breakpoint: the two declarations
   that define it — the brand panel's `width: 45%` and the swap of its rule from
   the bottom edge to the right edge — have no utility in the pre-built sheet
   (`md:w-[45%]` is an arbitrary value, and there is no responsive `border-*`
   variant at all). Splitting one breakpoint between this file and the markup to
   move the two declarations that DO have utilities would leave the layout
   harder to read than the rule it replaced, so the query stays whole.
   ------------------------------------------------------------------------ */

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

.auth-brand {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 2rem;
    padding: 2rem 1.5rem;
    border-bottom: 1px solid var(--border);
}

.auth-word {
    display: flex;
    align-items: center;
    gap: 0.75rem;
}

/* REQ-UI-074 / BRD-208 — the mockup's `.mark` box: 34 x 34 with a 9px radius on a phone (`.brandp .word
   .mark` in login.html's 767px block), 44 x 44 with 12px from 768px up, filled with the brand's #0284C7 in
   both themes — never a theme token (ADR-033) — and holding the approved white glyph
   (TfLens-appiconfg.svg) at the box's full size, as login.html's `.mark svg` does. */
.auth-mark {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: 9px;
    background: #0284C7;
    overflow: hidden;
    flex: none;
}

    .auth-mark svg {
        display: block;
        width: 100%;
        height: 100%;
    }

.auth-wordmark {
    font-size: 2rem;
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: 1.1;
}

.auth-tagline {
    margin: 1rem 0 0;
    max-width: 26rem;
    font-size: 1.125rem;
    line-height: 1.5;
}

.auth-bullets {
    list-style: none;
    margin: 1.5rem 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
}

.auth-bullets li {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    font-size: 0.9375rem;
}

/* The mockup's ticks are green (docs/mockups/login.html); the build rendered them in the
   inherited foreground because --alert-success was declared on .auth-split AFTER the icon
   inherited its colour. Naming the token directly on the tick is what makes it green. */
.auth-tick {
    color: var(--alert-success);
    flex: none;
}

.auth-right {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    padding: 2rem 1.5rem;
}

.auth-panel {
    width: 100%;
    max-width: 25rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

@media (min-width: 768px) {
    .auth-split {
        flex-direction: row;
    }

    .auth-mark {
        width: 44px;
        height: 44px;
        border-radius: 12px;
    }

    .auth-brand {
        width: 45%;
        flex: none;
        padding: 3rem;
        border-bottom: 0;
        border-right: 1px solid var(--border);
    }

    .auth-bullets {
        margin-top: 2rem;
    }

    .auth-right {
        padding: 3rem 1.5rem;
    }
}

/* ------------------------------------------------------------------------
   .tflens-stack — the standard page rhythm (REQ-NFR-015).

   The mockups set sections 24px apart. This was written by hand ELEVEN times —
   once in each page's scoped stylesheet — which meant a new page got no spacing
   at all until someone remembered to copy it, and it made the page's rhythm
   depend on the scoped-CSS bundle arriving. That is the REQ-UI-001 failure
   shape in miniature. Declared once here, globally, from app.css (served
   straight from wwwroot).

   2.1.0-ci.10 ships the full spacing scale, so this is now exactly
   `flex flex-col gap-6` and the class is a NAME for the page rhythm rather than
   a stand-in for a missing utility. It is kept because eleven pages spell it,
   and because one named rule is still the thing that stops the rhythm drifting;
   it is no longer a TR-002 workaround.
   ------------------------------------------------------------------------ */
.tflens-stack {
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
}

/* TR-016 is CLOSED and the `.tflens-badge-info` / `-success` / `-warning` block that used to stand
   here is deleted. `BadgeVariant` gained `Success`, `Info` and `Warning` in 2.1.0-ci.10, so the three
   tones the mockups draw on every status column are component parameters now instead of classes the
   caller has to remember. Every call site was converted in this pass — Effort, Misses, Prices, Repos
   and the Playbook surfaces — and `grep -rn "tflens-badge"` over `src/`, `tests/verify/` and
   `docs/mockups/` returns nothing, which is what made the deletion safe.

   Why the classes existed: `BadgeVariant` offered exactly four members — Default, Destructive,
   Outline, Secondary — so `info`, `success` and `warning` had no expression at all and every status
   pill rendered monochrome. That is why the Repos grid read flatter than its mockup and why a synced
   row and a stale row looked alike (REQ-UI-011). The `--alert-*` tokens they rode on are still
   declared at the top of this file, and the library's own variants read the same four hues, so a
   badge and its matching Alert remain the same colour.
   ------------------------------------------------------------------------ */

/* ============================================================================
   REQ-UI-001 .. REQ-UI-004, REQ-UI-009 — mockup parity for the anonymous screens
   (owner UAT, 2026-08-29).

   These rules live here and NOT in a scoped `.razor.css` for the same reason the rest of the `.auth-*`
   set does: app.css is served straight from wwwroot, so the anonymous layout never depends on the
   optional `TfLens.styles.css` bundle again (REQ-NFR-015, the 2026-08-28 UAT failure).

   The field icons are keyed off the input's `name`, which is stable, semantic, and identical across all
   four screens — so one rule dresses Login, Register, Forgot and Reset without touching four files or
   inventing a wrapper component. The icons are inline `data:` SVGs (Lucide `mail` and `lock`) because a
   CSS background cannot reference a Blazor component, and `currentColor` is not available to a data URI:
   the stroke is the muted foreground of the dark surface these screens always render on.
   ============================================================================ */

.auth-panel input[name="Email"],
.auth-panel input[name="Password"],
.auth-panel input[name="Confirm"],
.auth-panel input[name="CurrentPassword"] {
    background-repeat: no-repeat;
    background-position: 0.75rem center;
    background-size: 1rem 1rem;
    padding-left: 2.25rem;
}

/* Lucide `mail`, stroke #a1a1aa */
.auth-panel input[name="Email"] {
    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="%23a1a1aa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>');
}

/* Lucide `lock`, stroke #a1a1aa */
.auth-panel input[name="Password"],
.auth-panel input[name="Confirm"],
.auth-panel input[name="CurrentPassword"] {
    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="%23a1a1aa" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>');
}

/* The reveal control belongs INSIDE the field, as the mockup draws it — it sat outside, which both
   shortened the input and broke the card's alignment down the right edge. */
.auth-input-wrap {
    position: relative;
    display: block;
}

    .auth-input-wrap input {
        width: 100%;
        padding-right: 2.5rem;
    }

    .auth-input-wrap .auth-input-action {
        position: absolute;
        top: 50%;
        right: 0.25rem;
        transform: translateY(-50%);
    }

/* The mockups rule off the card footer and underline the links inside it. Without the rule the
   "New here? Create an account" row read as part of the form rather than as a way out of it. */
.auth-panel .auth-card-footer {
    border-top: 1px solid var(--border);
    margin-top: 0.25rem;
    padding-top: 1rem;
}

.auth-panel .auth-card-footer a {
    text-decoration: underline;
    text-underline-offset: 2px;
}

/* The brand panel's footer carries the theme toggle beside the credit line, as every auth mockup
   shows. It was absent from all four anonymous screens. */
.auth-brand-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}
