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;
}
/*
   Matthew (AI), 2026-09-20 09:10. The hand-rolled chrome that used to live here - .app-header,
   .app-brand, .app-main and the organisation switcher's list layout - is gone. Tabler's navbar,
   page-wrapper and dropdown do all of it, and CLAUDE.md section 7 says not to hand-roll what the
   framework already provides. What is left below is the error UI, which is Blazor's own and belongs
   to neither framework.
*/

/*
   Matthew (AI), 2026-09-20 09:40. Spacing for Font Awesome icons inside Tabler controls.

   Tabler ships `.btn .icon { margin: ... }` and expects its own `<svg class="icon">`. We use Font
   Awesome Pro (CLAUDE.md section 7), whose `<i class="fad fa-...">` carries no `icon` class, so every
   button rendered as "[icon]Sign out" with the glyph jammed against the word. One rule here rather
   than an `icon` class added by hand to a hundred elements - and it lives in app.css because it is a
   style used on more than one page (section 7).

   `.btn-icon` is excluded on purpose: those buttons hold an icon and nothing else, so a trailing
   margin would push the glyph off centre.
*/
.btn:not(.btn-icon) > i[class*="fa-"],
.dropdown-item > i[class*="fa-"],
.alert > i[class*="fa-"],
.nav-link > i[class*="fa-"],
.page-title > i[class*="fa-"] {
    margin-inline-end: 0.5rem;
}

/* An icon-only button centres its glyph and needs no text spacing at all. */
.btn-icon > i[class*="fa-"] {
    margin: 0;
}

/* ---------------------------------------------------------------------------
   Matthew (AI), 2026-09-23 11:10, owner's request: dark mode, reported broken
   in three places.

   Bootstrap 5.3's colour modes recolour everything that reads --bs-* tokens,
   which is most of Tabler. Two families do not follow, and both were reported:
   the contextual table variants, and anything Telerik draws.
   --------------------------------------------------------------------------- */

/* Tabler's .table-success / -warning / -danger / -info carry FIXED colours: a
   pale tint and, explicitly, `--tblr-table-color: #000`. They were inherited
   from Bootstrap before colour modes existed and do not respond to one, so in
   dark mode a journal's balanced footer and the asset register's reconciliation
   row came out as pale bands of near-white with black text in the middle of a
   dark page.

   The semantics are worth keeping — green means balanced, amber means look at
   this, red means wrong — so the palette is restated rather than the classes
   being taken off the markup. A translucent tint of the framework's own semantic
   colour lets the surface underneath show through, which is what makes it read
   as part of a dark table rather than as a sticker on one.

   **The prefix is `--tblr-`, not `--bs-`.** Tabler renames Bootstrap's whole
   custom-property namespace, so `var(--bs-success-rgb)` resolves to nothing,
   which makes the declaration invalid, which silently drops it and leaves the
   pale original in place. The first version of this block did exactly that and
   changed nothing at all; the browser said so when asked. */
:root[data-bs-theme="dark"] .table-success {
    --matrix-table-tint: var(--tblr-success-rgb);
}

:root[data-bs-theme="dark"] .table-warning {
    --matrix-table-tint: var(--tblr-warning-rgb);
}

:root[data-bs-theme="dark"] .table-danger {
    --matrix-table-tint: var(--tblr-danger-rgb);
}

:root[data-bs-theme="dark"] .table-info {
    --matrix-table-tint: var(--tblr-info-rgb);
}

:root[data-bs-theme="dark"] .table-success,
:root[data-bs-theme="dark"] .table-warning,
:root[data-bs-theme="dark"] .table-danger,
:root[data-bs-theme="dark"] .table-info {
    --tblr-table-color: var(--tblr-body-color);
    --tblr-table-bg: rgba(var(--matrix-table-tint), 0.18);
    --tblr-table-border-color: rgba(var(--matrix-table-tint), 0.3);

    /* The striped, hover and active variants carry their own fixed pair each.
       Left alone they would put black text back the moment somebody moved a
       mouse over the row. */
    --tblr-table-striped-color: var(--tblr-body-color);
    --tblr-table-striped-bg: rgba(var(--matrix-table-tint), 0.24);
    --tblr-table-active-color: var(--tblr-body-color);
    --tblr-table-active-bg: rgba(var(--matrix-table-tint), 0.28);
    --tblr-table-hover-color: var(--tblr-body-color);
    --tblr-table-hover-bg: rgba(var(--matrix-table-tint), 0.28);
}

/* .table-light is a section heading row on the profit and loss and the balance
   sheet (SectionRows), and it is fixed light for the same reason. In dark mode a
   heading should be a slightly raised surface, not a white stripe.

   --tblr-secondary-bg and --tblr-body-color are stated with CSS light-dark(),
   so they already carry the right value for the mode in force. */
:root[data-bs-theme="dark"] .table-light {
    --tblr-table-color: var(--tblr-body-color);
    --tblr-table-bg: var(--tblr-secondary-bg);
    --tblr-table-border-color: var(--tblr-border-color);
    --tblr-table-striped-color: var(--tblr-body-color);
    --tblr-table-active-color: var(--tblr-body-color);
    --tblr-table-hover-color: var(--tblr-body-color);
}

/* ---------------------------------------------------------------------------
   Telerik.

   §7.1 self-hosts the Bootstrap swatch from the package, and the package ships
   exactly one: the light one. There is no dark swatch to point at, and Telerik's
   CDN cannot be used because §7.1 requires an SRI hash they do not publish.

   It does not need one. The whole theme is built on CSS custom properties, and
   nearly all of them are DERIVED from a handful of base colours using relative
   oklch(). The text colour carries a clamp — clamp(0.2621, (0.6 - l) * 99999,
   0.95) — which flips it to near-white the instant the surface it sits on drops
   below 0.6 lightness. So redefining four colours recolours the grids, the
   dropdowns, the comboboxes, the popups AND the charts.

   Measured in the browser rather than assumed, because the first version of this
   block also restated every --kendo-chart-* variable and that was dead code:
   Telerik defines those ON the .k-chart element, which shadows anything
   inherited from :root. With only the four below in place, a real .k-chart
   resolves its text to oklch(0.95 ...) and its gridlines to a light grey at 8%
   in dark mode, and to oklch(0.26 ...) and a dark grey at 8% in light mode.
   --------------------------------------------------------------------------- */
:root[data-bs-theme="dark"] {
    --kendo-color-app-surface: oklch(21% 0.012 248deg);
    --kendo-color-surface: oklch(25% 0.012 248deg);
    --kendo-color-base: oklch(30% 0.012 248deg);
    --kendo-color-border: oklch(95% 0.005 248deg / 0.15);
    --kendo-color-border-alt: oklch(95% 0.005 248deg / 0.25);
    --kendo-color-subtle: oklch(72% 0.014 240deg);
}

/* The one chart variable worth restating, and it has to be set on the chart
   element to beat Telerik's own rule there. Left alone it derives to a dark of
   its own, a shade off the Tabler card the chart sits in — which is how a chart
   ends up looking like a screenshot pasted into the page. Transparent lets the
   card's background through. Text and gridlines are deliberately absent: they
   derive correctly from the four colours above, and stating them here would be
   two places to change one decision. */
:root[data-bs-theme="dark"] .k-chart,
:root[data-bs-theme="dark"] .k-sparkline,
:root[data-bs-theme="dark"] .k-stockchart {
    --kendo-chart-bg: transparent;
    background-color: transparent;
}

/* Matthew (AI), 2026-09-24 04:54, owner's request: the boards sub-system's columns.
   In the shared stylesheet per CLAUDE.md §7 — a style used on more than one page belongs here, and the
   board view and the card modal to come both need these. Columns scroll sideways rather than wrapping,
   because a board that reflows into rows stops reading as a board. */
.board-columns {
    display: flex;
    gap: 1rem;
    overflow-x: auto;
    align-items: flex-start;
    padding-bottom: 0.5rem;
}

.board-column {
    flex: 0 0 20rem;
    max-width: 20rem;
}

/* Matthew (AI), 2026-09-24 15:38, owner's request: the columns follow docs/boards/board-view.png - a grey
   column with white cards inside it, rather than a white card containing a white list. The contrast is what
   makes a board read as columns of things rather than as one long form. */
.board-column > .card {
    background-color: var(--tblr-bg-surface-secondary);
}

.board-column-header {
    background-color: transparent;
    border-bottom: 0;
}

/* The cards keep the page background, so they stand out from the column behind them. A margin rather than the
   list-group's own borders, which would draw a line between cards that are meant to read as separate. */
.board-column .board-card {
    background-color: var(--tblr-bg-surface);
    border: 1px solid var(--tblr-border-color);
    border-radius: var(--tblr-border-radius);
    margin: 0 0.5rem 0.5rem;
}

.board-column .list-group {
    background-color: transparent;
}

/* The chosen faces in the member filter, which are otherwise only told apart by the dimming on the others. */
.board-filter-on {
    outline: 2px solid var(--tblr-primary);
    outline-offset: 1px;
    border-radius: 50%;
}

/* A grab cursor only where dragging is actually possible: the attribute is false for anybody without
   planning.use_board, so the cursor tells the truth. */
.board-card[draggable="true"] {
    cursor: grab;
}

.board-card-dragging {
    opacity: 0.45;
}

/* Matthew (AI), 2026-09-24 14:50, owner's request: the card cover, the column drag handle and the file
   drop zone. All three are board styles, so they belong here beside the ones above rather than inline. */

/* Matthew (AI), 2026-09-26 09:50, owner's request: the file viewer dialog. Wide and tall, because the point
   of it is to see a document rather than a thumbnail of one - a preview in a default modal is a slot somebody
   has to scroll a page through. */
@media (min-width: 576px) {
    .file-viewer-dialog {
        max-width: 90vw;
    }
}

.file-viewer-body {
    min-height: 70vh;
    padding: 0;
}

/* The frame fills the body. A PDF or an embedded viewer with its own scrollbar inside a scrolling dialog is
   two scrollbars for one document, which nobody can use. */
.file-viewer-frame {
    width: 100%;
    height: 70vh;
    border: 0;
}

/* Scaled down to fit and never up. A small screenshot blown up to fill the dialog is a blurred image
   somebody then has to download to read anyway. */
.file-viewer-image {
    max-width: 100%;
    max-height: 70vh;
    width: auto;
    height: auto;
    object-fit: contain;
}

/* Matthew (AI), 2026-09-24 15:44, owner's request: the rule between the card dialog's two columns, as
   docs/boards/card-view.png has it. Only once the columns are side by side - stacked on a narrow screen it
   would be a line across the middle of nothing. */
@media (min-width: 992px) {
    .board-card-talk {
        border-left: 1px solid var(--tblr-border-color);
    }
}

/* Matthew (AI), 2026-09-24 15:22, owner's request: the board tiles follow docs/boards/board-listing.png. The
   description holds two lines whether or not there is one, so the counts and the faces line up across a row
   instead of stepping up and down with the length of the text. line-clamp truncates the long ones rather than
   letting one tile grow taller than its neighbours. */
.board-tile-description {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    min-height: 2.5rem;
}

/* Matthew (AI), 2026-09-24 15:05, owner's request: the card dialog is 80% of the screen and two columns, so
   more of a card is visible at once. Bootstrap's modal-xl is a fixed 1140px, which on a wide screen leaves
   most of it empty - 80vw tracks the screen instead. The cap at the small breakpoint is deliberate: Tabler's
   modal is already full-width on a phone, and 80vw there would be a dialog with margins nobody asked for. */
@media (min-width: 576px) {
    .board-card-modal {
        max-width: 80vw;
    }
}

/* Matthew (AI), 2026-09-24 15:00, owner's report: label text was grey and hard to read. Tabler's .badge sets
   its own colour from its variables, and a badge given only a background-color keeps that default - which
   against ten saturated label colours is low-contrast grey on every one of them. White, in one place, because
   the label palette and the custom-label colours are all dark enough to carry it: the lightest is #fbc02d,
   and white on that is the one to watch if the palette ever gains a pale colour. !important because Tabler's
   own .badge colour rule is more specific than a bare class. */
.board-label,
.board-label:hover,
.board-label:focus {
    color: #fff !important;
}

/* A strip above the title rather than a tint behind it. A card tinted dark enough to read as coloured is a
   card whose text no longer has the contrast Tabler chose for it. */
.board-card-cover {
    height: 0.375rem;
    border-radius: 0.25rem;
}

/* The column header is the drag handle, so it says so - and only where dragging is possible, for the same
   reason .board-card[draggable="true"] does. */
.card-header[draggable="true"] {
    cursor: grab;
}

.board-column-dragging {
    opacity: 0.45;
}

/* The drop zone is outlined all the time rather than only while something is over it: a target that is
   invisible until you are already dragging onto it is a target nobody discovers. */
.board-drop-zone {
    border: 1px dashed var(--tblr-border-color);
    border-radius: var(--tblr-border-radius);
    padding: 0.75rem;
}

.board-drop-zone-over {
    border-color: var(--tblr-primary);
    border-style: solid;
    background-color: var(--tblr-primary-lt);
}

/* Tabler handles the rest. On a phone the columns are narrower and still scroll, which is the behaviour
   every kanban board on a small screen settles on. */
@media (max-width: 575.98px) {
    .board-column {
        flex-basis: 16rem;
        max-width: 16rem;
    }
}

/* Matthew (AI), 2026-09-25 13:20, owner's request: the dashed frame of a marketing image placeholder, so a page that
   still needs its photograph looks deliberate rather than broken. */
.border-dashed {
    border-style: dashed !important;
}

/* Matthew (AI), 2026-09-26 08:49, owner's request: the home page's hero carousel.
   Bootstrap's controls are 15% of the width and would sit over the text and buttons at every size, so they are
   narrowed to a gutter and the panels are padded to clear it. Their colour follows the page's text colour rather than
   Bootstrap's fixed white, which is invisible on this light band. */
.home-hero .carousel-control-prev,
.home-hero .carousel-control-next {
    width: 3rem;
    color: var(--tblr-body-color);
}

.home-hero .carousel-item > .container {
    padding-inline: 3rem;
}

/* The band is as tall as its tallest panel, at every width, so the page below does not jump every ten seconds when a
   shorter panel comes round — on a phone the four differed by 62px. Bootstrap already stacks the panels in one place
   (float, and margin-right: -100%) and hides the idle ones with display: none; kept in the layout but invisible
   instead, they hold the band open. visibility: hidden also keeps them out of the tab order and away from screen
   readers, as display: none did. */
.home-hero .carousel-item:not(.active):not(.carousel-item-next):not(.carousel-item-prev) {
    display: block;
    visibility: hidden;
}

/* On a wide screen each panel also has a floor and centres its content in it, so a short panel sits in the middle of
   the band rather than at its top. */
@media (min-width: 992px) {
    .home-hero .carousel-item > .container {
        min-height: 26rem;
        display: flex;
        align-items: center;
    }

    .home-hero .carousel-item > .container > .row {
        flex: 1;
    }
}
