/* The network is isolated: no web fonts — MudBlazor's default typography falls back to system fonts. */
:root {
    --mud-typography-default-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --mud-typography-h4-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --mud-typography-h5-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --mud-typography-h6-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --mud-typography-body1-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --mud-typography-body2-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --mud-typography-button-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    --mud-typography-overline-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

html, body {
    font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    background-color: #FBFAF9;
}

/* Reserve the scrollbar's width even when nothing scrolls. Opening a dialog makes MudBlazor lock the body,
   the scrollbar disappears, and everything underneath shifts by its width — the table you were reading moves
   out from under the pointer. Reserving the gutter costs a few pixels once and removes the jump entirely. */
html {
    scrollbar-gutter: stable;
}

/* The error strip is deliberately light in both themes, so it carries its own ink: "color-scheme" governs
   only the colors the browser paints (scrollbars, form controls) and not the inherited text color, so without
   this the dark theme's near-white body text would stand on light yellow. Every background in this file states
   its text color or has a dark counterpart for the same reason. */
#blazor-error-ui {
    color-scheme: light only;
    background: lightyellow;
    color: #252B37;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss,
    #blazor-error-ui .reload {
        color: #252B37;
    }

    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }

.loading-progress {
    position: absolute;
    display: block;
    width: 8rem;
    height: 8rem;
    inset: 20vh 0 auto 0;
    margin: 0 auto 0 auto;
}

    .loading-progress circle {
        fill: none;
        stroke: #E8E2D9;
        stroke-width: 0.6rem;
        transform-origin: 50% 50%;
        transform: rotate(-90deg);
    }

        .loading-progress circle:last-child {
            stroke: #F7BF18;
            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;
    color: #676F7E;
    inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}

    .loading-progress-text:after {
        content: var(--blazor-load-percentage-text, "Lädt …");
    }

/* ── Flow-app look ─────────────────────────────────────────────────────────
   Tokens taken from gotomaxx-flow-app (index.css :root):
   background #FBFAF9, background-soft #F6F3EE, border #E8E2D9, accent #FFF9E6,
   text-primary #252B37, text-strong #555D6D, text-secondary #676F7E, primary gold #F7BF18.
   The dark theme of the flow-app is a NEUTRAL grey since it dropped its warm browns (index.css .dark):
   background #1A1A1A, card #1F1F1F, soft #262626, accent #2E2E2E, border #404040,
   text #F2F2F2 / #BABABA / #A6A6A6, and a softer gold #E9BC63 that reads on dark. The dark section at the
   end of this file carries exactly those values. */

/* Scrollbars, as the flow-app draws them everywhere: thin, the thumb in the hairline colour, no arrow buttons.
   A stock scrollbar is a wide grey slab on a page whose every other edge is a hairline. */
* {
    scrollbar-width: thin;
    scrollbar-color: #E8E2D9 transparent;
}

*::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

*::-webkit-scrollbar-track {
    background: transparent;
}

*::-webkit-scrollbar-thumb {
    background-color: #E8E2D9;
    border-radius: 8px;
    border: 2px solid transparent;
    background-clip: padding-box;
}

*::-webkit-scrollbar-thumb:hover {
    background-color: rgba(103, 111, 126, 0.55);
}

*::-webkit-scrollbar-corner {
    background: transparent;
}

*::-webkit-scrollbar-button {
    display: none;
}

/* Header: page-colored, 64px, logo left — no elevated white bar. */
.bw-appbar {
    padding-left: 12px !important;
    padding-right: 16px !important;
}

.bw-logo {
    height: 34px;
    width: 34px;
    object-fit: contain;
    margin-right: 10px;
}

.bw-apptitle {
    font-weight: 600 !important;
    font-size: 1.05rem !important;
    color: #252B37;
}

/* Head of the account menu: the signed-in user, as a reading line and not as a menu item. The padding
   matches a menu row so the name stands over the entries instead of beside them. */
.bw-account-head {
    padding: 8px 16px;
    min-width: 180px;
}

.bw-account-name {
    color: #252B37;
    font-size: 0.875rem;
    font-weight: 600;
}

.bw-account-role {
    color: #676F7E;
    font-size: 0.75rem;
}

/* Tenant selection in the bar: a fixed 240px that neither grows into the spacer nor shrinks, and 24px after the
   title. It stands left of the spacer, right behind the logo and the title, so the spacer still pushes the chat
   button and the account menu to the right edge; the distance to the title is this element's own left margin,
   and nothing stands between it and the spacer. The class sits on the component's own wrapping div, which is
   the flex item of the bar; the library's div.mud-select inside it is a block and fills it (see
   BwTenantSelect.razor for the measurement that moved it there). The field takes the input tone of the
   flow-app template — its hairline border colour, the body text colour and its rounded corners; the bar's
   height is not touched. */
.bw-tenant-select {
    flex: 0 0 240px;
    width: 240px;
    margin-left: 24px;
}

.bw-tenant-select .mud-input-outlined-border {
    border-color: #E8E2D9;
    border-radius: 8px;
}

.bw-tenant-select .mud-input-slot {
    color: #252B37;
    font-size: 0.875rem;
}

body.bw-dark .bw-tenant-select .mud-input-outlined-border {
    border-color: #333333;
}

body.bw-dark .bw-tenant-select .mud-input-slot {
    color: #F2F2F2;
}

/* Sidebar: page-colored with a hairline border, like the flow-app aside. */
.bw-drawer {
    border-right: 1px solid #E8E2D9;
}

    /* Column layout so the collapse control sits at the bottom of the sidebar. */
    .bw-drawer .mud-drawer-content {
        display: flex;
        flex-direction: column;
    }

.bw-nav {
    padding: 10px 8px;
    flex: 1 1 auto;
    overflow-y: auto;
}

/* Collapse control pinned to the sidebar's bottom edge; centered in the mini state. */
.bw-drawer-foot {
    flex: 0 0 auto;
    border-top: 1px solid #E8E2D9;
    padding: 6px 10px;
    display: flex;
    justify-content: flex-end;
}

.bw-drawer.mud-drawer--closed .bw-drawer-foot {
    justify-content: center;
    padding: 6px 4px;
}

/* Nav rows: rounded, medium-weight, soft hover; active row gets the gold
   edge bar plus the warm accent surface (flow-app SidebarRow). */
.bw-nav .mud-nav-link {
    position: relative;
    border-radius: 8px;
    padding: 9px 10px;
    margin: 1px 0;
    font-size: 0.875rem;
    font-weight: 500;
    color: #555D6D;
    transition: background-color 0.15s ease, color 0.15s ease;
}

    .bw-nav .mud-nav-link .mud-icon-root {
        font-size: 20px;
        margin-right: 10px;
        color: #555D6D;
    }

    .bw-nav .mud-nav-link:hover {
        background-color: #F6F3EE;
        color: #252B37;
    }

    /* The heading of a group is a section label, as the flow-app sidebar labels its sections: small
       capitals, quiet ink, no icon of its own — the entries below it carry the icons. The chevron stays,
       because the group still folds. Three selectors deep so it beats the row rules above. */
    .bw-nav .mud-nav-group > .mud-nav-link {
        padding: 14px 10px 4px 10px;
        margin: 0;
        font-size: 0.6875rem;
        font-weight: 600;
        letter-spacing: 0.1em;
        text-transform: uppercase;
        color: #676F7E;
    }

        .bw-nav .mud-nav-group > .mud-nav-link:hover {
            background-color: transparent;
            color: #252B37;
        }

        .bw-nav .mud-nav-group > .mud-nav-link > .mud-nav-link-icon {
            display: none;
        }

    /* The surface belongs to the LINK and to nothing inside it. A text span painting the same color
       over its own, narrower box would give the active row two color boxes of different extent — visible as a
       lighter frame around the label. The link is the row, so the row is what is tinted.

       The selector is stated at MudBlazor's own reach, and that is measured rather than chosen: its rule
       ".mud-navmenu.mud-navmenu-default .mud-nav-link.active:not(.mud-nav-link-disabled)" carries FIVE classes,
       so a three-class ".bw-nav .mud-nav-link.active" loses and this tint never appeared at all — the active
       row showed MudBlazor's own rgba(85, 93, 109, 0.06) instead. Adding the element type beats it outright
       instead of relying on load order. Same trap as the sticky table header, which MudBlazor repaints from a
       five-class selector too. */
    body .bw-nav.mud-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled) {
        background-color: #FFF9E6;
    }

    /* MudBlazor's own active rule paints the label in the primary color (gold on the
       gold-tinted surface — unreadable), so the dark ink needs !important here. */
    .bw-nav .mud-nav-link.active,
    .bw-nav .mud-nav-link.active .mud-nav-link-text {
        color: #2E2A24 !important;
        font-weight: 600;
    }

        .bw-nav .mud-nav-link.active .mud-icon-root {
            color: #2E2A24 !important;
        }

        .bw-nav .mud-nav-link.active::before {
            content: "";
            position: absolute;
            left: 0;
            top: 6px;
            bottom: 6px;
            width: 3px;
            border-radius: 0 3px 3px 0;
            background-color: #F7BF18;
        }

/* ── The heights a full-viewport calc() is built from ──────────────────────────
   One place for the three numbers every "fill the rest of the viewport" height has to subtract, so a page
   height and the padding it compensates cannot drift apart. --bw-appbar-height is the MudBlazor AppBar,
   --bw-content-top and --bw-content-bottom are the .bw-content paddings right below, which read these
   variables instead of repeating the numbers. Whoever needs such a height writes
   calc(100vh - var(--bw-appbar-height) - var(--bw-content-top) - var(--bw-content-bottom))
   and nothing else; a page that ends flush at the bottom (.bw-content-flush) subtracts
   --bw-content-flush-bottom in place of the last term.

   --bw-dialog-furniture is the fourth case and a different one: a dialog does not stand in .bw-content at all,
   it is centred in the window by MudBlazor's provider. What it has to give up is its own furniture — the
   heading, the inner padding, the button row, and the margin the dialog keeps from the window edges. Whoever
   sizes something inside a dialog against the window writes
   calc(100vh - var(--bw-dialog-furniture)) and nothing else. It is one number and not three, because those
   four parts belong to one component and move together. */
:root {
    --bw-appbar-height: 64px;
    --bw-content-top: 28px;
    --bw-content-bottom: 48px;
    --bw-content-flush-bottom: 12px;
    --bw-dialog-furniture: 210px;
}

/* Content column: bounded width with airy padding, like the flow-app container. */
/* The content follows the drawer state and always uses the full remaining width; wide widgets
   (tables, charts) scroll inside their own containers instead of the page scrolling sideways. */
/* It is also the COLUMN that owns the height below the app bar, and that is what lets a page whose content is
   a table give room back when something appears above it. Measured before this rule: with two notices over the
   page title — 92 px and 112 px — the window was 768 px tall and the document 1004 px, so the reader had to
   scroll 236 px to reach the pager line of a table that had been made to fit exactly. ".bw-page-fill" computed
   its height from the viewport alone and subtracted nothing for what stood above it, because a calc cannot
   know that.
   "min-height" and not "height": a page that legitimately grows past the window — a long form, the dashboard —
   still grows and still scrolls. What changes is only that a child asking for "the rest" gets the rest of THIS
   box instead of the rest of the window. */
.bw-content {
    padding: var(--bw-content-top) 32px var(--bw-content-bottom) 32px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    box-sizing: border-box;
    min-height: calc(100vh - var(--bw-appbar-height));
}

/* The exception to that bottom padding, for a page whose own content reaches the bottom edge: a negative
   margin is the only way a child can take back an ancestor's padding, and it is stated once here so no page
   writes its own number. What remains below the page is --bw-content-flush-bottom. */
.bw-content-flush {
    margin-bottom: calc(var(--bw-content-flush-bottom) - var(--bw-content-bottom));
}

/* ── Analyses tree ─────────────────────────────────────────────────────────
   The navigation of the analyses page: a fixed search field over a scrolling tree, one line per node, and a
   width that follows the navigation drawer. Same palette as the sidebar — the two are the same kind of thing
   and a reader should not have to learn a second visual language for the second list of links. */

/* The panel: a flex column, so only .bw-tree-body scrolls and the search field above it stays put. The width
   is the drawer's mirror image, with the drawer's own transition (225ms, cubic-bezier(0, 0, 0.2, 1)) so both
   move together instead of one snapping while the other slides.
   Both vertical numbers come from the constants at the top of this file, like every other full-viewport
   height in the project. The sticky offset is where the panel already starts — the app bar plus the top
   padding of .bw-content, whose first flex child this panel is — so it stays put instead of jumping upwards
   the moment the page scrolls; and the height ends on the same line as .bw-page-fill, the panel's flex
   sibling, which subtracts exactly these three terms. The literals that stood here (80px and
   100vh - 112px) started 12px above that line and reached 28px past the bottom padding, and they were the
   last two viewport numbers in the project not built from the constants. */
.bw-tree {
    width: 260px;
    flex-shrink: 0;
    position: sticky;
    top: calc(var(--bw-appbar-height) + var(--bw-content-top));
    max-height: calc(100vh - var(--bw-appbar-height) - var(--bw-content-top) - var(--bw-content-bottom));
    display: flex;
    flex-direction: column;
    overflow: hidden;
    padding: 8px;
    transition: width 225ms cubic-bezier(0, 0, 0.2, 1);
}

/* The menu gives up its width; the tree takes it. The marker sits on .bw-content and comes from MainLayout,
   not from measuring anything in the browser. */
.bw-menu-collapsed .bw-tree {
    width: 420px;
}

.bw-tree-head {
    flex: 0 0 auto;
    padding-bottom: 8px;
}

.bw-tree-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overflow-x: hidden;
}

    /* MudBlazor gives the tree itself "overflow: auto". Left standing, the body and the tree would be two
       nested scrollports and the search field would scroll away inside one of them. */
    .bw-tree-body .mud-treeview {
        overflow: visible;
    }

/* One line per node. The label is a flex item, so it needs min-width: 0 before an ellipsis can happen at all,
   and the rule reaches the text element inside it because that is what carries the characters. */
.bw-tree .mud-treeview-item-label,
.bw-tree .mud-treeview-item-label * {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Only the label itself, not the text element inside it: the 4px MudBlazor puts on each side of a label are
   room the ellipsis takes instead. */
.bw-tree .mud-treeview-item-label {
    padding-left: 2px;
    padding-right: 2px;
}

/* The row's own padding. Three classes, because MudBlazor's dense rule for this box already carries two and a
   two-class rule of ours would lose to it and stand here without effect. */
.bw-tree .mud-treeview-dense .mud-treeview-item-content {
    padding: 1px 2px;
}

/* Group rows: the top level of the tree, in the body size and the weight of a heading. */
.bw-tree .mud-treeview > .mud-treeview-item > .mud-treeview-item-content .mud-treeview-item-label,
.bw-tree .mud-treeview > .mud-treeview-item > .mud-treeview-item-content .mud-treeview-item-label * {
    font-size: 0.875rem;
    font-weight: 600;
    color: #252B37;
}

/* Analyses: one level in and a step quieter than the group they belong to. */
.bw-tree .mud-treeview-group .mud-treeview-item-label,
.bw-tree .mud-treeview-group .mud-treeview-item-label * {
    font-size: 0.8125rem;
    font-weight: 400;
    color: #252B37;
}

/* ── What a tree row spends before its first letter ───────────────────────────
   MudBlazor's own measures are made for a tree that is the whole page: the expand arrow claims 2rem plus 4px
   of margin on each side, the node symbol 32px plus 4px, the label another 4px on each side, and a group
   indents its children by 17px. That is 40 + 36 + 8 = 84px of chrome, plus 27px of indent, before a leaf's
   title starts — in a panel 260px wide, two thirds of which are the titles this tree exists to show. The
   hierarchy is drawn by the arrow, the guide line and the indent, and none of the three needs that much room.
   The four rules below halve it: 20 + 22 + 4 = 46px of chrome and 15px of indent, 61px instead of 111px. The
   arrow's own symbol comes down with its box, or it would stick out of it. */

/* The guide line: a hairline down the children of a group, so a long list of analyses stays visibly attached
   to its heading. The indent lines it up under the group's folder symbol. */
.bw-tree .mud-treeview-group {
    margin-left: 10px;
    padding-left: 5px;
    border-left: 1px solid #E8E2D9;
}

/* A small gap between groups; inside a group the rows stay dense. */
.bw-tree .mud-treeview > .mud-treeview-item + .mud-treeview-item {
    margin-top: 3px;
}

.bw-tree .mud-treeview-item-content {
    border-radius: 6px;
}

/* The expand arrow: half the width and no margin at all. Two classes, to reach past MudBlazor's own
   one-class rule for the same box. */
.bw-tree .mud-treeview-item-arrow {
    width: 1.25rem;
    margin: 0;
    min-height: 0;
}

.bw-tree .mud-treeview-item-arrow .mud-icon-root {
    font-size: 18px;
}

/* The node symbol. margin-inline-end, not margin-right: MudBlazor sets both on this box, the logical one
   last, and a physical declaration of the same reach would be overwritten by it. */
.bw-tree .mud-treeview-item-icon {
    width: 20px;
    margin-inline-end: 2px;
}

.bw-tree .mud-treeview-item-icon .mud-icon-root {
    font-size: 18px;
    color: #676F7E;
}

.bw-tree .mud-treeview-hover .mud-treeview-item-content:hover {
    background-color: #F6F3EE;
}

/* The selected analysis carries the sidebar's warm surface and its gold edge — the one loud thing in the tree,
   and the same signal the active menu entry gives. Four class selectors, to reach MudBlazor's own selected
   rule instead of losing to it; the inset shadow draws the edge without a positioned pseudo-element. */
.bw-tree .mud-treeview .mud-treeview-item-content.mud-treeview-item-selected {
    background-color: #FFF9E6;
    box-shadow: inset 3px 0 0 #F7BF18;
}

    .bw-tree .mud-treeview .mud-treeview-item-content.mud-treeview-item-selected .mud-treeview-item-label,
    .bw-tree .mud-treeview .mud-treeview-item-content.mud-treeview-item-selected .mud-treeview-item-label * {
        color: #2E2A24;
        font-weight: 600;
    }

    .bw-tree .mud-treeview .mud-treeview-item-content.mud-treeview-item-selected .mud-icon-root {
        color: #2E2A24;
    }

/* clip, not hidden — the difference decides whether "position: sticky" works anywhere on a page.
   "hidden" makes this box a scroll container: with overflow-y left at visible, CSS computes it to auto, and
   every sticky descendant then resolves against this box instead of the viewport. This box has no height
   limit, so it never scrolls, and a sticky element inside it never moves. "clip" cuts the same sideways
   overflow without creating a scroll container, so the viewport stays the scrollport it always was. */
.bw-main {
    overflow-x: clip;
}

/* The row area of every table. It carries the rounding, because ".bw-table .mud-table" must not clip: a
   clipping table box cuts the sticky header off the moment it leaves its starting position. The container is
   the scrollport anyway (MudBlazor gives it "overflow-y: auto"), so a non-visible overflow is already there
   and the rounding takes effect without adding one. "inherit" keeps the radius at the table's own value.
   ".bw-table" is the class BwTable puts on the table element itself (see BwTable.razor.cs, LOOK_CLASS), so one
   selector reaches the table on the page and the same table magnified into a dialog alike; a dialog's content
   stands outside ".bw-main", which is why the old page-scoped selector missed it there. */
.bw-table .mud-table-container {
    overflow-x: auto;
    border-radius: inherit;
}

/* Cards: white, hairline border, whisper of a shadow — instead of Material elevation. */
.bw-table-large,
.bw-main .mud-paper,
.bw-login-card {
    border: 1px solid #E8E2D9;
    box-shadow: 0 1px 2px rgba(35, 30, 20, 0.05) !important;
}

/* KPI cards on the dashboard. */
.bw-kpi-label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #676F7E;
    margin-bottom: 6px;
}

.bw-kpi-value {
    font-size: 1.9rem;
    font-weight: 600;
    color: #252B37;
    line-height: 1.2;
}

/* The head of a figure tile: the caption at the left, the star of the analyses page at the right, on one line.
   The negative margin keeps the small icon button's own padding from making the head taller than the caption,
   so a tile with a star stands exactly as high as it stood without one. */
.bw-kpi-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 6px;
}

    .bw-kpi-head .bw-kpi-label {
        margin-bottom: 0;
    }

    .bw-kpi-head .mud-icon-button {
        margin: -8px -8px -8px 0;
    }

    /* The element around the star only keeps its click from reaching the tile. As a block holding an inline
       button it took the line height of the text as its own height — measured 20 px against the caption's
       15.7 px, every tile 4.3 px taller —, as a flex box it is exactly as high as the button's margin box. */
    .bw-kpi-head > div {
        display: flex;
    }

/* Section heading above a table. */
.bw-section-title {
    font-size: 1rem !important;
    font-weight: 600 !important;
    color: #252B37;
}

/* Secondary explanatory text. */
.bw-muted {
    color: #676F7E;
}

/* ── One shape for every page ──────────────────────────────────────────────
   A page is a heading row, optionally a filter bar, then its content. These classes carry
   that shape so it is not written inline once per page and drifts apart afterwards. */

/* Heading row: the page title on the left, the page's own actions on the right. */
.bw-page-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 24px;
}

.bw-page-title {
    font-weight: 600 !important;
}

/* Filter bar: one quiet row of equal-height fields directly above the table they steer. No paper
   around it — a filter belongs to its table and is not a section of its own. */
.bw-filterbar {
    display: flex;
    align-items: flex-start;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 16px;
}

/* A switch has no input frame, so it would otherwise hang at the top edge of the row. */
.bw-filterbar .mud-switch {
    align-self: center;
    margin: 0;
}

/* A filter keeps the width its content needs instead of the width of the screen. */
.bw-filterbar-field {
    width: 220px;
    flex: 0 0 auto;
}

.bw-filterbar-wide {
    width: 380px;
    flex: 0 0 auto;
}

/* Numeric and date cells: the figures end at the same edge and their digits line up. */
.bw-num {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* Free text in a table cell: an event description of the security log, the detail of a running operation, an
   ERP hash. Such a text carries no break opportunity the line breaker would accept — "CODE|p1|p2" and a
   semicolon-joined list of fetches are one single word to it —, so the cell keeps growing, the table becomes
   wider than its container, and ".bw-table .mud-table-container { overflow-x: auto }" answers with a horizontal
   bar under every table. "anywhere" is the value that also lowers the cell's MINIMUM width (unlike
   "break-word", which leaves the minimum at the longest run and therefore keeps the bar); the max-width caps
   what the column claims of the remaining space, so the dated and numeric columns beside it keep their width. */
.bw-wrap {
    max-width: 34rem;
    overflow-wrap: anywhere;
}

    /* The same class on a HEADING cell needs a floor, and the reason was measured rather than imagined.
       "anywhere" lowers the minimum width to ONE character, and a heading is the one cell whose text the
       layout cannot compress: in Kunde 360 the column "Mandant · Adressnr." was given 52 px beside eight
       numeric columns that keep their intrinsic width, so its heading broke after every letter, the heading
       row grew to 364 px, and the table STILL burst its container (971 px against 954). Both of the things
       "anywhere" is here to prevent. So the heading breaks between words only and keeps room for its longest
       one, while the data cells below it keep "anywhere" — they are the ones carrying texts with no break
       opportunity at all. */
    th.bw-wrap {
        min-width: 9rem;
        overflow-wrap: break-word;
    }

/* A table with more columns than the container can pay for: the padding is halved and the sorting buttons in
   the heading stop claiming a button's worth of space.

   Measured on Kunde 360 at 1280 px, which is the width the acceptance is taken at: eight columns, content as
   short as "0" and "1 / 2", and the table still wanted 971 px of the 954 available — because 40 px of every
   column is padding (16 left, 24 right from MudBlazor's dense rule) and six of the eight headings are
   MudButtons with their own 16 px on each side. That is 320 px plus the buttons, against 954. No amount of
   wrapping fixes that; only the padding does.

   Stated at MudBlazor's own reach on purpose: its winning rule is
   ".mud-table-dense * .mud-table-row .mud-table-cell" with four classes, and a shorter selector of ours is
   written and never seen — the same trap that cost the navigation its surface and the sticky heading its
   band. */
.bw-table-tight.mud-table-dense * .mud-table-row .mud-table-cell {
    padding-left: 8px;
    padding-right: 8px;
}

    .bw-table-tight .mud-table-cell .mud-button-root {
        min-width: 0;
        padding-left: 2px;
        padding-right: 2px;
    }

/* Action column: as narrow as its icons and pinned to the right edge of the table. */
.bw-actions {
    text-align: right;
    white-space: nowrap;
    width: 1%;
}

/* Explained empty state: what would stand here, and how it gets here. */
.bw-empty {
    padding: 28px 16px;
    text-align: center;
    font-size: 0.875rem;
    color: #676F7E;
}

/* A block that falls apart when it is pulled across a wide screen — a form, a two-column value
   list — keeps a readable measure instead of the full width. */
.bw-narrow {
    max-width: 560px;
}

/* Content area of an editing dialog: the content scrolls, the title and the footer do not, so
   "Übernehmen" stays reachable in a long field list.

   The measure is what the window has left over the dialog's own furniture — heading, inner padding and the
   button row, about 200px together — rather than a share of the window: a share leaves a margin that grows
   with the screen, so the taller the monitor the more of it the dialog would leave unused. A floor keeps the
   content area usable on a short window instead of letting it collapse to a few lines. */
.bw-dialog-content {
    max-height: calc(100vh - var(--bw-dialog-furniture));
    min-height: 120px;
    overflow-y: auto;
    padding-right: 4px;
}

/* Content that brings its own scrollport — a table, which gives its row area a definite height and scrolls the
   rows in it — gets none from the dialog. Two scrollports on one box show no more than one does: the outer bar
   moves the sticky header and the pager line out of sight, and the inner one, which is the one that actually
   holds the rows, stays exactly where it was. See BwEditDialog.ContentFills.

   It is a flex column of the same height the held dialog below takes, and not a box that follows its content:
   a table can only fill a height that is stated, and stated nowhere it grew with its rows and the DIALOG
   scrolled, header and pager included. A child that is not a table — the picture or the chart of a chat
   attachment — is centred in the column instead. The magnified table's dialog is the one exception and gets
   its measure from the dialog's own chain further down. */
.bw-dialog-content-fill {
    height: calc(100vh - var(--bw-dialog-furniture));
    max-height: none;
    display: flex;
    flex-direction: column;
    justify-content: center;
    overflow: hidden;
    padding-right: 0;
}

/* A dialog with tabs, held at one height instead of following its content.

   Sized by its content, such a dialog changes height with every tab the reader picks: the window jumps, the
   buttons move out from under the pointer, and a short tab makes the dialog small again right after a long one
   made it big. The height is the same measure the scrolling default uses as its ceiling, so a held dialog is
   exactly as tall as one that had grown into it — it just gets there when it opens and stays there.

   The flex column is what makes the tab content, and not the dialog, do the scrolling: the panel area is the
   one item allowed to give height up, and "min-height: 0" is what actually permits it — a flex item's floor is
   its content, so without it a long table would push the box open again and the dialog would grow after all.
   The panel area is a flex column itself, so a table standing in the open tab is handed the remaining height
   the way a page hands it down (see ".bw-page-fill .bw-table-frame" above) and scrolls its rows, with the
   pager line at the bottom edge of the dialog. A tab holding several things — a figure grid over a table, a
   value list over a table — keeps everything but the table at its own height. See
   BwEditDialog.ContentHoldsHeight. */
.bw-dialog-content-held {
    height: calc(100vh - var(--bw-dialog-furniture));
    display: flex;
    flex-direction: column;
    overflow-y: hidden;
}

    .bw-dialog-content-held > .bw-tabs {
        flex: 1 1 auto;
        min-height: 0;
        display: flex;
        flex-direction: column;
    }

        .bw-dialog-content-held > .bw-tabs .mud-tabs-panels {
            flex: 1 1 auto;
            min-height: 0;
            display: flex;
            flex-direction: column;
            overflow-y: auto;
        }

/* Two tables side by side in one tab of a held dialog — the year and the month series of a customer's
   revenue. Stacked, the first would have taken the height and the second would have stood below the dialog's
   edge; beside each other both take the tab's whole height and each scrolls its own rows. Every column is a
   flex column of its own, so the table frame inside it grows exactly as it does in a tab of its own. */
.bw-dialog-columns {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    gap: 24px;
}

.bw-dialog-column {
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* A column is half a dialog wide, and the whole footer line does not fit into a half: measured at 602px, the
   filter cluster (335px) and the pager (443px) broke into two rows, with the arrows standing under the filter
   and the second row costing every table of the column two rows of data. In a column the rows-per-page choice
   is withheld — the default stays — so filter, count and arrows keep the ONE row, arrows at the right end. */
.bw-dialog-column .mud-table-pagination-caption,
.bw-dialog-column .mud-table-pagination-select {
    display: none;
}

/* A single control inside an editing dialog — the search field above a list, the target picker of a drilldown
   — keeps a readable width instead of stretching to the dialog's. A field as wide as the dialog reads as the
   dialog's own heading row rather than as something that acts on the table under it.

   Stated once because four dialogs carried the same inline style, and with "!important" deliberately: this is
   one class, while MudBlazor sizes its input control through selectors carrying several of its own, so the
   plain declaration would stand in the stylesheet without ever winning. */
.bw-dialog-field {
    max-width: 320px !important;
}

/* Two-column value list: label and value belong side by side, and pulling them across a wide screen is
   what tears them apart. The label column is quiet, the measure is bounded. */
.bw-values {
    max-width: 560px;
}

    .bw-values th[scope="row"] {
        width: 45%;
        font-weight: 500;
        color: #676F7E;
        white-space: nowrap;
    }

    .bw-values td {
        color: #252B37;
    }

/* A chart on the page is a button in disguise: it opens the magnified view. Say so with the cursor, since
   the tooltip only appears after a pause. */
.bw-chart-click {
    cursor: zoom-in;
}

/* The bars and the points of a chart whose analysis has a drilldown lead to it; the rest of the chart keeps the
   cursor of its surroundings (the zoom cursor on the page). */
.bw-chart-drill path.mud-chart-bar,
.bw-chart-drill circle.mud-chart-point {
    cursor: pointer;
}

/* Keeps the chart's place on the page while the magnified view holds the one chart element. Without it the
   page would show a gap, which reads as a drawing that failed rather than one that moved. It takes the measure
   the chart takes (.bw-chart-fill below: the rest of the panel), so the page does not jump when the magnified
   view opens or closes. */
.bw-chart-placeholder {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px dashed #E8E2D9;
    border-radius: 10px;
    color: #676F7E;
    font-size: 0.875rem;
}

    body.bw-dark .bw-chart-placeholder {
        border-color: #404040;
        color: #A6A6A6;
    }

/* ── Analyses page: one window, and two escapes out of it ──────────────────────
   The page is a box as high as what is left below the app bar (.bw-page-fill above) and nothing in it grows
   the document. Two of the four presentations could outgrow a window, and both answer with a dialog rather
   than a taller page: the chart is drawn into exactly the room the panel has left and opens magnified for a
   bigger box, the table takes that room and scrolls in its row area — and opens magnified as well, for the
   width. */

/* The row below the head: the tree and the panel share what the box has left. min-height: 0 is the load-
   bearing declaration — without it a flex item refuses to shrink below its content, and the panel's content
   is a table of up to five thousand rows. "stretch" rather than "flex-start", because both children want the
   height of the row and neither may be as tall as its own content. */
.bw-analysis-body {
    flex: 1 1 auto;
    min-height: 0;
    gap: 24px;
    align-items: stretch;
}

/* The panel beside the tree: a flex column, so the table is the one child that grows and everything above it
   — title row, filter bar, leading figures — keeps its own height. It clips rather than scrolls: a panel that
   scrolled would be the page scrolling under another name, which is exactly what the box is here to prevent,
   and the two things inside it that can be too big both have a dialog to open. */
.bw-analysis-panel {
    flex-grow: 1;
    min-width: 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Title, currency and the buttons of the shown analysis in one row. The downloads belong to the heading; a row
   of their own would cost the presentation the height of a button. */
.bw-analysis-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 8px;
}

/* The explanation under the presentation: cut to two lines, and a click opens it in full. Cut and not hidden —
   the first two lines say which explanation it is. The cursor and the hover state are the whole announcement;
   there is deliberately no keyboard path, because this interface promises none anywhere and a lone one here
   would be a promise it does not keep. */
.bw-footnote {
    cursor: pointer;
}

/* The clamp sits on the element that carries the characters, not on the box around it: -webkit-line-clamp
   counts the lines of the element it is declared on, and declared on the wrapper it would count the one block
   inside as one line and cut nothing. */
.bw-footnote-text {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
}

    /* Two classes, to reach past ".bw-muted" on the same element — a hover state that loses its colour
       declaration announces nothing. */
    .bw-footnote:hover .bw-footnote-text {
        color: #252B37;
        text-decoration: underline;
    }

    body.bw-dark .bw-footnote:hover .bw-footnote-text {
        color: #F2F2F2;
    }

/* Keeps the table's place in the panel while the magnified view holds the one table element — the stand-in
   the chart leaves behind, in the measure a table has: it grows with the panel instead of standing at a
   height of its own, because that is what the table it replaces does. */
.bw-table-placeholder {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px dashed #E8E2D9;
    border-radius: 10px;
    color: #676F7E;
    font-size: 0.875rem;
}

    body.bw-dark .bw-table-placeholder {
        border-color: #404040;
        color: #A6A6A6;
    }

/* ── The magnified table in its dialog ────────────────────────────────────────
   MudBlazor shows an inline dialog through MudDialogProvider, which stands in MainLayout ABOVE the main
   content: the dialog's content is therefore NOT inside .bw-main, so the table conventions below cannot
   rely on that ancestor where the magnified table stands. BwTable puts ".bw-table" on the table
   element itself, on the page and in the dialog alike, so the conventions reach it there too without being
   restated here. What belongs here is only the measure.

   A definite height, because that is what makes the row area a scrollport and the header stick to its top
   edge; without it the table grows with its rows and the DIALOG scrolls, header and all.

   The measure is the window minus the dialog's furniture — heading, inner padding and the button row — and not
   a share of the window, for the reason stated at ".bw-dialog-content": a share left a margin that grew with
   the screen, and the magnified view of a table is opened precisely to see more rows at once. The pager line
   counts as part of the table and is inside this height. */
/* The magnified table takes what the dialog has left. The dialog itself is as large as the window allows (see
   the rule below), so a height computed from the viewport would either fall short of it or overshoot it; the
   flex item takes the remainder instead, and min-height: 0 is what lets it shrink below the height of its rows
   rather than pushing the pager line out of the box. */
.bw-table-large {
    flex: 1 1 auto;
    min-height: 0;
    border-radius: 10px;
}

/* The whole analysis behind a favorite card is a table of its own in the same view (FavoriteTableDialog): its
   wrapper takes the place the magnified table takes, and the same chain below hands the height down to it. */
.bw-zoom-table {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
}

/* A dialog holding the magnified table is as large as the window allows, and hands that size down: the content
   column, the frame and the table are one flex chain, so the rows get every pixel the furniture leaves.
   ":has" picks exactly this dialog and no other — a form dialog stays as small as its fields. */
.mud-dialog:has(.bw-table-large),
.mud-dialog:has(.bw-zoom-table) {
    display: flex;
    flex-direction: column;
    width: 96vw;
    max-width: 96vw;
    height: 94vh;
}

    /* The chain has to be unbroken from the dialog down to the row area: MudBlazor's own content box is the
       link between the dialog and ours, and left as a block it hands the table an unbounded height — measured
       2345 px of table in a 764 px dialog, scrolled by the dialog instead of by the rows. */
    .mud-dialog:has(.bw-table-large) .mud-dialog-content,
    .mud-dialog:has(.bw-zoom-table) .mud-dialog-content {
        display: flex;
        flex: 1 1 auto;
        flex-direction: column;
        min-height: 0;
        overflow: hidden;
    }

    .mud-dialog:has(.bw-table-large) .bw-dialog-content-fill,
    .mud-dialog:has(.bw-zoom-table) .bw-dialog-content-fill {
        display: flex;
        flex: 1 1 auto;
        flex-direction: column;
        justify-content: flex-start;
        height: auto;
        min-height: 0;
    }

    .mud-dialog:has(.bw-table-large) .bw-table-box,
    .mud-dialog:has(.bw-zoom-table) .bw-table-box {
        flex: 1 1 auto;
        min-height: 0;
    }

/* The magnify button stands in the heading cell of the LAST DATA COLUMN, at that cell's right end, whatever the
   caption's alignment. It is drawn by a heading cell of no width appended behind the last data column
   (BwTable.razor): the cell takes no room, so the table gains no column a reader could see, and it scrolls with
   the table, so the button stays at the right end of the last column wherever a wide table is scrolled — a
   button over the frame stayed in the frame's corner and covered whichever caption passed beneath it.

   The cell is sticky like every heading cell of a fixed header, and that makes it the positioned parent of the
   button: the strip reaches left from the cell's right edge into the last data column, as high as the heading
   row, and centres the icon in it. Painted after the last data column's cell, it stands over that cell's
   background and never under it. The selectors name the dense-table cell rule they have to win against. */
.bw-table .mud-table-head .mud-table-row > th.mud-table-cell.bw-magnify-cell {
    position: sticky;
    top: 0;
    width: 0;
    min-width: 0;
    max-width: 0;
    padding: 0;
    border: 0;
    overflow: visible;
}

.bw-table-magnify {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    display: flex;
    align-items: center;
    padding-right: 4px;
    z-index: 3;
}

/* A table's caption stands above the frame and not in a toolbar inside it: inside, it pushed the heading row
   — and the magnify button in it — one row down on exactly the tables that had a caption, which put the one
   button every table shares in two different places. */
.bw-table-title {
    margin: 0 0 8px;
}

/* A chart takes exactly the room it is given, in BOTH directions: the rest of the analyses panel on the page,
   the whole content column in the magnified view. MudBlazor draws its charts into a fixed 650×350 view box and
   scales that with the aspect ratio kept, which left a chart either letterboxed — a third of a wide dialog,
   empty margins left and right — or, drawn at full width, taller than the dialog and scrolling. Every chart
   therefore sets MatchBoundsToSize, which makes MudBlazor draw into the size of its box, and this class is
   that box: the one flex item of its column that grows, never asking for its content's height, and itself a
   flex column so the chart's own wrapper and the drawing inside it take the same measure. The selectors reach
   down through MudBlazor's wrapper without naming its classes, so a renamed wrapper cannot silently undo the
   chain: the wrapper is the only element child, the drawing the only graphic that is not an icon. */
.bw-chart-fill {
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

    .bw-chart-fill > div {
        flex: 1 1 auto;
        min-height: 0;
        min-width: 0;
        display: flex;
        flex-direction: column;
    }

    .bw-chart-fill svg:not(.mud-icon-root) {
        flex: 1 1 0;
        min-height: 0;
        width: 100%;
    }

    /* MatchBoundsToSize takes the box's height minus THIRTY pixels for the legend and draws into that; the
       legend MudBlazor then renders is 63 pixels (10px padding above and below, 10px margin, one line), so the
       drawing came out 32 pixels taller than the room left for it and was scaled down, with empty margins left
       and right again — measured: view box 880×418 in an 880×386 graphic. The legend is held at exactly the
       thirty pixels the measure assumes, one line, items in a row. */
    .bw-chart-fill .mud-chart-legend {
        flex: 0 0 30px;
        height: 30px;
        padding: 0;
        margin: 0;
        align-items: center;
        flex-wrap: nowrap;
        overflow: hidden;
    }

    /* A legend item is 28 pixels high; MudBlazor's two-pixel margin above and below would make the line 32
       and clip it inside the thirty pixels. */
    .bw-chart-fill .mud-chart-legend .mud-chart-legend-item {
        margin-top: 0;
        margin-bottom: 0;
    }

/* The row area always reserves its scrollbar, at a width of its own: a table that scrolls and one that does
   not then end at the same place, and the button above finds the same edge in both. */
.bw-table .mud-table-container {
    scrollbar-gutter: stable;
}

    .bw-table .mud-table-container::-webkit-scrollbar {
        width: 10px;
    }

/* The last data column's caption gives up the room the button needs, so the button never covers it — a left-
   aligned caption ends before the button, a right-aligned one ends there too. Only the HEADING cell steps
   aside, and only where the button actually is (":has"); the data cells keep the padding every other column
   has, and a table in the magnified view, which carries no button, keeps its full width. 26 px of button,
   4 px to the right edge, 6 px gap. */
.bw-table .mud-table-head .mud-table-row:has(> .bw-magnify-cell) > th.mud-table-cell:nth-last-child(2) {
    padding-right: 36px;
}

/* The live ERP records as cards, side by side and wrapping. They are alternatives the reader compares — which
   of these is the customer I meant — and comparing means having them beside each other rather than one below
   the next. The basis is the width a postal address needs without breaking a street line; below two of them
   the row wraps by itself, so the same markup serves a narrow window. */
.bw-live-cards {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
}

.bw-live-card {
    flex: 1 1 280px;
    min-width: 260px;
    border: 1px solid #E8E2D9;
    border-radius: 10px;
    padding: 12px 16px;
}

    body.bw-dark .bw-live-card {
        border-color: #404040;
    }

    /* The label column takes what it needs and no more, so the values line up against the same edge on every
       card. Without it each card measures its own widest label and the cards stop being comparable. */
    .bw-live-card .bw-live-field {
        white-space: nowrap;
        width: 1%;
        padding-right: 12px;
        color: #676F7E;
    }

    body.bw-dark .bw-live-card .bw-live-field {
        color: #A6A6A6;
    }

/* Differing row of the live-ERP comparison. A tint, not a fill: the value has to stay the readable
   thing on the row. Both halves are set together — a background alone inherits whatever text color
   the theme happens to carry, which is how this row once ended up near-white on light amber. */
.bw-deviation {
    background-color: #FBEED2;
}

    .bw-deviation td,
    .bw-deviation th {
        color: #4A3B12;
    }

/* Tables, as the flow-app draws its data tables (components/ui/table.tsx): a quiet header in the SURFACE
   colour with small sentence-case captions — no capitals, no letter-spacing, no tinted band —, hairlines
   between the rows, and a faint cool hover. No "overflow: hidden" here — it would clip the header into the
   rounded corners, and it is exactly what makes a sticky header invisible: the table box becomes a clip box
   the header cannot leave, so a scrolled-away header is cut instead of staying put. The rounding is done by
   ".mud-table-container" above, which clips anyway.
   ".bw-table" is BwTable's own class (see BwTable.razor.cs, LOOK_CLASS) on the table element itself, so one
   rule reaches a table on the page and the same table magnified into a dialog alike. */
.bw-table {
    border-radius: 10px;
}

.bw-table .mud-table-cell {
    border-bottom: 1px solid #E8E2D9 !important;
    font-size: 0.875rem;
    color: #252B37;
}

.bw-table th.mud-table-cell {
    background-color: #FFFFFF;
    font-size: 0.75rem;
    font-weight: 500;
    letter-spacing: normal;
    text-transform: none;
    color: #676F7E;
    height: 40px;
}

/* A fixed header is repainted by MudBlazor in the surface colour, out of a selector of five classes that
   outreaches the rule above. The header belongs to the table conventions, so it is restated at that reach —
   and it has to be opaque, because with a sticky header the rows pass underneath it.
   ".bw-table" and "mud-table-sticky-header" sit on the SAME element, so the selector is compounded rather
   than nested, and it counts five classes still. */
.bw-table.mud-table-sticky-header .mud-table-root .mud-table-head .mud-table-cell {
    background-color: #FFFFFF;
}

.bw-table tr:last-child .mud-table-cell {
    border-bottom: none !important;
}

.bw-table.mud-table-hover .mud-table-body .mud-table-row:hover {
    background-color: #F7F8FA;
}

/* ── A page whose long content is its table ────────────────────────────────────
   Head, filter bar and search field stand still and only the rows move. That needs two things, and neither
   of them is a number written twice: the page becomes a box exactly as high as what is left below the app
   bar — the calc from the :root constants at the top of this file, the same one .bw-chat-page uses — and the
   table becomes the one child of that box that grows. The row area then has a definite height, MudBlazor's
   FixedHeader makes it the scrollport, and the header cells stick to its top edge. Without the box the
   document would scroll by the height of the head above the table, which is what this whole rule prevents. */
/* "flex: 1 1 0" and not "1 1 auto": the basis has to be zero, or the box would start from the height of its
   own content — a table of five thousand rows — and grow the column instead of fitting into it. The floor
   stays, so a window covered in notices ends in a small table and a scrollbar rather than in a table too low
   to read. */
.bw-page-fill {
    display: flex;
    flex-direction: column;
    flex: 1 1 0;
    min-height: 320px;
}

/* The chain that hands the remaining height down to the table: the tab strip, its panel area, the paper a
   page wraps its table in, and the table itself. Nothing else needs a rule — a flex item does not grow
   unless it is told to, so head, filter bar, search field and pagination line keep their own height by
   default. Inside a tab strip the classes still reach the table, because MudBlazor draws the open panel with
   "display: contents": its children, not the panel, are the items of the panel area's flex column. */
.bw-page-fill > .mud-tabs,
.bw-page-fill .mud-tabs .mud-tabs-panels {
    flex: 1 1 auto;
    min-height: 0;
}

/* An administration rendered INSIDE a tab panel of another page — the three under "Einstellungen", the three
   under "Zugänge". Those pages are routable in their own right and then wrap themselves in ".bw-page-fill";
   embedded they must not, because that class is "the height of the window minus the bar" and inside a panel
   that is already that box it would be a second full window and push the table out of the bottom.

   Removing the wrapper is not an option either: Razor cannot open an element conditionally, and the div sits
   in the middle of the chain that hands the height down — panel area, wrapper, table frame, row area. A plain
   block there breaks the chain, and a broken chain does not look broken: the table renders every one of its
   rows and the PANEL scrolls instead of the row area, which is the same defect twice removed.

   "display: contents" takes the wrapper out of the layout without taking it out of the markup, so the page's
   own children become items of the panel's flex column. The height rules below keep reaching them, because
   they are descendant selectors rooted at the HOST page's ".bw-page-fill". It is the same device MudBlazor
   uses for the open panel itself. */
.bw-page-embedded {
    display: contents;
}

/* The table and the paper around it grow the same way, and keep a floor: a window covered in notices has to
   take the height from somewhere, and a table squeezed to a sliver is worse than a box that scrolls.
   The floor is what keeps the table USABLE, and 180 px is not (measured on the status page, tab "Datenbestand",
   with the rebuild notice over the page): header 40 px and pager line 63 px leave 77 px for the rows — two rows
   of 33 px, the rest behind a scrollbar, the pager at the bottom edge of a panel the reader first has to scroll.
   300 px leaves 197 px for the rows: six dense rows, four of the taller kind, and the panel scrolls for whatever
   the window cannot hold.
   The same two rules hold inside a dialog whose content is held at one height (.bw-dialog-content-held) or
   handed to its content (.bw-dialog-content-fill): there the table is the child that grows and scrolls its
   rows, so the pager line stays at the bottom edge of the dialog instead of standing below what the dialog
   shows — which is what made the customer-360 tabs look as though they could neither page nor filter. */
.bw-page-fill .bw-table-frame,
.bw-page-fill .bw-table-fill,
.bw-dialog-content-held .bw-table-frame,
.bw-dialog-content-held .bw-table-fill,
.bw-dialog-content-fill .bw-table-frame,
.bw-dialog-content-fill .bw-table-fill {
    flex: 1 1 auto;
    min-height: 300px;
}

/* The panel area is the flex column that hands the height on to the table of the open tab. It may scroll, as a
   safety for a window too low for the floor below; with every tab carrying one table it does not. */
.bw-page-fill .mud-tabs .mud-tabs-panels {
    display: flex;
    flex-direction: column;
    overflow-y: auto;
}

/* The paper around a table passes the height on instead of shrinking to the table's natural height. */
.bw-table-frame {
    display: flex;
    flex-direction: column;
}

/* The table that takes the rest of the page; its height comes from the rule above. */
.bw-table-fill {
    display: flex;
    flex-direction: column;
}

/* The row area is the one item of that column that grows and may be squeezed, so it is what scrolls. There
   is deliberately no second height class beside this one any more: a table capped at a measure of its own
   (the dashboard and the status page had one at 480 px) left the PAGE to scroll for what did not fit, which
   is the one thing every page of this interface promises not to do. A page with several tables divides them
   into tabs or columns instead. */
.bw-table-fill .mud-table-container {
    flex: 1 1 auto;
    min-height: 0;
}

/* The pager box has to BE a line before anything can share it.
   MudBlazor gives ".mud-table-pagination" "display: block", so its two children — the leading cluster and
   the pager toolbar — would stack as block boxes and "margin-right: auto" would do nothing at all: measured
   45 px of filter above 52 px of toolbar, 105 px of furniture for one row's worth of controls. Laying the box
   out as a flex row is what puts the two clusters on one line, which is the whole point — the line exists
   anyway, so the filter costs no height.
   The arrangement is the flow-app's pagination footer (components/ui/pagination-footer.tsx): what is SHOWN at
   the left end — there the row range, here the filter and the range beside it —, the controls that move the
   list at the right end. Scoped with :has() to the lines BwTablePager draws; a bare MudBlazor pager, should
   one ever stand somewhere, keeps the layout its library ships. */
/* The gap above is not decoration. Measured on the user table: the row area ended at 614 and the filter box
   began at 614 — the two touched, so the filter's frame and its label sat ON the table's last rule and read as
   part of the table rather than as a control under it. The same edge is what made the pager look pasted onto
   the rows. The row gap does the same where the line wraps on a narrow table: two rows of controls with
   nothing between them are one block of furniture. */
.mud-table-pagination:has(> .bw-pager-lead) {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    padding-top: 10px;
    column-gap: 16px;
    row-gap: 6px;
    border-top: 1px solid var(--mud-palette-table-lines);
}

/* The rule above the line belongs to the LINE, not to the pager toolbar inside it.
   Measured on the analyses table: the only top border in the whole row was ".mud-toolbar"'s own, and a
   toolbar is only as wide as its buttons — so the rule ran from "Zeilen pro Seite" to the last arrow and
   stopped. Left of it, under the filter, and right of it, under „1–10 von 10", there was no rule at all, which
   reads as a line the two controls are covering. They were not covering anything; it was never drawn there.
   Moving it to the pagination box draws it once, across the full width, above everything the row carries. */
.mud-table-pagination:has(> .bw-pager-lead) .mud-table-pagination-toolbar {
    border-top: none;
}

/* The leading cluster: the filter and, beside it, how many rows the table holds and which of them are on
   screen. "margin-right: auto" is what moves it to the left end and leaves the pager at the right. It may
   shrink, and that is deliberate: on a table too narrow for both, a cluster that refused to give width up would
   push the pager out of the box. Wrapping is the last resort and not the first. */
.bw-pager-lead {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    column-gap: 16px;
    row-gap: 6px;
    margin-right: auto;
    flex: 0 1 auto;
    min-width: 0;
}

/* The row filter. The width is bounded because a filter as wide as the table reads as a heading over it
   rather than as a control acting on it; it is generous enough for a company name, which is what gets typed in
   here. Outlined and dense like every other field of this interface — an underlined field would look like a
   different kind of control from the search field over the analyses tree. */
.bw-table-filter {
    flex: 0 1 auto;
    min-width: 160px;
    max-width: 320px;
}

    /* The dense outlined field brings a top margin for its floating label that the line has no room for; the
       label sits in the notch of the outline, so the margin would only push the field off the pager's line. */
    .bw-table-filter .mud-input-control {
        margin-top: 0;
    }

/* The row count is drawn by BwTablePager, not by MudBlazor.
   The application runs under InvariantGlobalization — a decision about the delivered size, and the reason
   AnalysesFormat exists — so every number this product shows is formatted by hand. MudBlazor's own count was
   the single one that was not, and it read „1–50 von 24,761" with an English separator in a German interface.
   Hiding it rather than reformatting it: its text is built inside the component and cannot be reached.
   Our count stands in the LEADING cluster, at the left end of the line, where the flow-app's footer states
   its row range; the pager box at the right end keeps the picker and the arrows. */
.bw-pager-line {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex: 0 0 auto;
    min-width: 0;
}

    /* Read off the rendered DOM, not guessed: MudBlazor 9.8 wraps the count in
       ".mud-table-page-number-information", inside a ".mud-table-pagination-caption". Hiding the caption would
       take the „Zeilen pro Seite" label with it — the two are siblings of the same class. */
    .bw-pager-line .mud-table-page-number-information {
        display: none !important;
    }

.bw-pager-info {
    white-space: nowrap;
    font-size: 0.875rem;
    color: #676F7E;
}

/* The pager line keeps its height instead of giving it to the rows.
   In a flex column every item may shrink by default, and the row area is the only one that wants to: it asks
   for the height of five thousand rows. The pager asks for one line — but it shrinks too, and MudBlazor gives
   it "overflow: auto", so a pager squeezed to a dozen pixels does not disappear, it becomes a second scrollport
   under the table with the page count and the arrows moving about inside a strip too low to read them in. That
   is what stood under the analyses table: a scrollable area whose content could not be made out. The row area
   is the only thing here that gives height up. */
.bw-table-fill .mud-table-pagination,
.bw-table-large .mud-table-pagination,
.bw-table-fill .mud-table-toolbar,
.bw-table-large .mud-table-toolbar {
    flex: 0 0 auto;
}

/* Buttons: sentence case at medium weight, like the flow-app buttons (components/ui/button.tsx, "font-medium").
   MudBlazor's own capitals and tracking read as a different product. */
.mud-button-root {
    text-transform: none !important;
    font-weight: 500 !important;
    letter-spacing: 0 !important;
}

/* Tab strip: the same hairline the cards use, so a tabbed page and a plain one sit on the same grid, and 16px
   between the strip and what the open tab shows — the flow-app's TabsContent carries "mt-4"
   (components/ui/tabs.tsx). MudBlazor 9 names the strip ".mud-tabs-tabbar"; written for the older
   ".mud-tabs-toolbar" this rule matched nothing, and the dashboard's tiles stood pressed against the strip. */
.bw-tabs .mud-tabs-tabbar {
    border-bottom: 1px solid #E8E2D9;
    margin-bottom: 16px;
}

/* A divider inside a tab panel is a flex item of the panel area's column, and MudBlazor gives it "flex: 1 0
   auto" for the toolbars it was written for: in the „Tabelle" dialog the rule between the fields and the
   „Selten nötig" row grew to three hundred pixels and pushed that row to the dialog's floor. A divider is a
   line and keeps the height of one. */
.bw-page-fill .mud-tabs .mud-tabs-panels .mud-divider,
.bw-dialog-content-held > .bw-tabs .mud-tabs-panels .mud-divider {
    flex: 0 0 auto;
}

/* Tab captions in sentence case at medium weight, as the flow-app's tabs are (components/ui/tabs.tsx);
   MudBlazor sets them in capitals, which made the strip the one place on the page shouting its words. */
.bw-tabs .mud-tab {
    text-transform: none;
    font-weight: 500;
    letter-spacing: normal;
}

/* ── Favorites on the dashboard ────────────────────────────────────────────────
   The cards stand in a wrapping flex row that scrolls INSIDE its own box: the row is the one item of the tab
   panel's flex column that grows (the same chain that hands the page's height to a table, ".bw-page-fill"
   above), so the page never scrolls however many favorites there are. Every card takes a third of the width and
   never grows or shrinks, so the cards of a half-empty last row are as wide as the others; below 360px the
   minimum width wins and the row wraps after two. Three per row at a 1514px window, with the menu open
   (content about 1195px) and collapsed (about 1379px).

   Every card has FIXED measures: the head is 64px, the body 240px for every presentation. The placeholder, the
   loaded content, an error and an empty answer all stand in the same body box (one element, one class in
   FavoriteCard.razor), so nothing moves when an answer arrives. 240px is what the tallest content needs: a
   table's header (40px) and five dense rows (about 34px each) are 210px and leave room for the row area's
   horizontal scrollbar (10px); a chart drawn into 240px minus its 12px of padding keeps a 30px legend and a
   plot that is still readable with 45-degree labels; a figure is centred in the same height so the cards of one
   row end on one line. */
.bw-favorite-grid {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    align-items: flex-start;
    gap: 16px;
    padding-bottom: 8px;
}

/* The card is a paper like the dashboard tiles (border and shadow come from ".bw-main .mud-paper" above); it adds
   the column and its share of the row: a third of the width less the two gaps, at least 360px. */
.bw-favorite-card {
    display: flex;
    flex: 0 0 calc((100% - 32px) / 3);
    flex-direction: column;
    min-width: 360px;
    overflow: hidden;
}

/* The element the browser watches for the card's entry into the visible part of the grid. */
.bw-favorite-inner {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-width: 0;
}

.bw-favorite-head {
    display: flex;
    flex: 0 0 auto;
    align-items: flex-start;
    gap: 4px;
    box-sizing: border-box;
    height: 64px;
    padding: 10px 8px 0 16px;
}

/* The title and the tenants line may be cut with an ellipsis and never wrap, which is what keeps the head at its
   fixed height whatever the title says; the full text is the tooltip. */
.bw-favorite-titles {
    flex: 1 1 auto;
    min-width: 0;
}

.bw-favorite-title {
    display: block;
    overflow: hidden;
    line-height: 1.5rem;
    text-decoration: none;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
}

    .bw-favorite-title:hover {
        text-decoration: underline;
    }

.bw-favorite-tenants {
    overflow: hidden;
    font-size: 0.75rem;
    line-height: 1.25rem;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A failed action says why in the line of the tenants. The palette variable follows the theme, so there is no
   dark counterpart to write. */
.bw-favorite-notice {
    color: var(--mud-palette-error);
}

/* The body box of every state of a card; the modifier below names the presentation and carries the measure. */
.bw-favorite-body {
    flex: 0 0 auto;
    box-sizing: border-box;
    overflow: hidden;
}

/* A table stands edge to edge: its own cell padding (16px) puts the text where the title starts. */
.bw-favorite-body-table {
    height: 240px;
}

.bw-favorite-body-chart {
    display: flex;
    flex-direction: column;
    height: 240px;
    padding: 0 16px 12px 16px;
}

.bw-favorite-body-metric {
    display: flex;
    flex-direction: column;
    justify-content: center;
    height: 240px;
    padding: 0 16px;
}

.bw-favorite-message {
    padding: 8px 16px;
}

.bw-favorite-skeleton {
    margin: 0 16px 6px 16px;
}

/* The shared table inside a card has no frame of its own: the card is the frame, and a second hairline and shadow
   inside the first would draw a box in a box. "!important" because the paper rules it overrides carry it. Its
   rows never wrap, which is what makes five rows a fixed height; what does not fit scrolls sideways, and the
   magnified view shows all of it. */
.bw-favorite-card .bw-table-box {
    border: 0;
    box-shadow: none !important;
    margin-bottom: 0 !important;
}

.bw-favorite-card .bw-table .mud-table-cell {
    white-space: nowrap;
}

/* A dialog heading at the size the flow-app gives its dialogs (dialogs/dialog.tsx, "text-base font-semibold"
   for the modal header): a shade smaller than MudBlazor's h6, so the heading does not outweigh the page title
   behind it. */
.mud-dialog-title .mud-typography-h6 {
    font-size: 1.125rem;
    font-weight: 600;
}

/* A table the AI attached to a chat answer. It is plain HTML markup from the rendered markdown, not a
   BwTable, so none of the ".bw-table" rules above reach it and it needs the table conventions stated once
   more — including the ink, because the header band is a light fill that would otherwise inherit the dark
   theme's near-white text. */
.bw-chat-table th,
.bw-chat-table td {
    border-color: #E8E2D9 !important;
    font-size: 0.875rem;
    color: #252B37;
}

.bw-chat-table th {
    background-color: #F8F5EF;
    font-weight: 600;
    color: #676F7E;
    white-space: nowrap;
}

/* Horizontal value bar inside top-list tables. */
.bw-bar {
    height: 10px;
    border-radius: 5px;
    background-color: #F7BF18;
    min-width: 2px;
}

/* Inline form button: matches the height of a dense outlined field so the row reads as one unit. */
.bw-form-button {
    height: 40px;
    margin-top: 6px !important;
    white-space: nowrap;
}

/* The page's own actions — „Neuen Benutzer anlegen", „Jetzt sichern", „Neue Verbindung" — stand in ONE row
   above the table, at its RIGHT end, on every page and in every tab alike. The flow-app puts a list's actions
   at the far end of its title row, and this interface follows it on every tab of the settings page: a
   reader who has found the button on the right once must not have to look left on the next tab. The gap is the second half of the rule: two filled buttons of the same colour set
   side by side with no space read as one wide button with a seam. */
.bw-page-actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 16px;
    margin-bottom: 16px;
}

/* A settings row: one field with its own button beside it.

   Centring the button against the field does NOT align it with the input, and that is what made every row of
   the settings page look shifted. A dense outlined field is not 40 pixels tall — it is a floating label, the
   40-pixel input box, and a helper sentence below, together about 200. Centred against all of that, the
   button lands level with the helper text, roughly 80 pixels below the box it belongs to.

   So the row aligns at the TOP and the button is pushed down by exactly the height of the floating label, and
   then its own 40 pixels sit on the input's 40. The offset is stated here rather than inherited from
   bw-form-button, whose 6px belong to rows whose field carries no label above it. */
.bw-field-row {
    align-items: flex-start;
}

.bw-field-row .bw-form-button {
    margin-top: 8px !important;
}

/* Inputs: white fields on the warm page background. */
.bw-main .mud-input-outlined-border,
.bw-login-card .mud-input-outlined-border {
    border-color: #E8E2D9;
}

.bw-main .mud-input.mud-input-outlined,
.bw-login-card .mud-input.mud-input-outlined {
    background-color: #FFFFFF;
}

/* AI chat: fixed-height column with a scrollable message area and the input pinned to the bottom.
   The height comes from the shared constants at the top of this file; the page carries bw-content-flush,
   so the term subtracted at the bottom is the 12px the input keeps and not the .bw-content padding. */
.bw-chat-page {
    display: flex;
    flex-direction: column;
    height: calc(100vh - var(--bw-appbar-height) - var(--bw-content-top) - var(--bw-content-flush-bottom));
    min-height: 320px;
}

.bw-chat-scroll {
    flex: 1 1 auto;
    overflow-y: auto;
    padding-right: 6px;
}

/* Input row: the field takes the width that is left, the waiting notice and the send button keep theirs at
   the right end. min-width on the field is what lets it shrink instead of pushing the button out of the row. */
.bw-chat-input {
    gap: 12px;
}

.bw-chat-draft {
    flex: 1 1 auto;
    min-width: 0;
}

/* One turn: the bubble and, below it, its timestamp. The 8px live on the turn and not on the bubble, so the
   caption belongs to the bubble above it and the gap stands between two turns. */
.bw-chat-turn {
    margin-bottom: 8px;
}

.bw-chat-turn-user {
    text-align: right;
}

.bw-chat-time {
    display: block;
    margin: 2px 0 0 0;
}

/* Bubbles: the user's question sits right (max 60% wide, at least 300px), the AI answer sits left;
   the text inside both stays left-aligned. The role is not written in the bubble — the side says it. */
.bw-chat-user {
    padding: 10px 12px;
    background: #FDF3D3;
    border-left: 3px solid #F7BF18;
    margin-left: auto;
    width: fit-content;
    max-width: 60%;
    min-width: min(300px, 100%);
    text-align: left;
}

.bw-chat-assistant {
    padding: 10px 12px;
    background: #FFFFFF;
    margin-right: auto;
    width: fit-content;
    max-width: 100%;
    min-width: min(300px, 100%);
    text-align: left;
}

/* Attachment card: what a table, a chart, an image or a document looks like inside the conversation. It
   states what it is and how big it is, hints at its content, and keeps its two actions on the right; the
   attachment itself lives in the enlargement dialog. Background and ink are set together — this surface
   sits on the message flow and must not inherit the theme's text color. */
.bw-chat-card {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 10px 12px;
    margin-bottom: 12px;
    max-width: 100%;
    border: 1px solid #E8E2D9;
    border-radius: 10px;
    background-color: #FFFFFF;
    color: #252B37;
}

.bw-chat-card-click {
    cursor: pointer;
}

    .bw-chat-card-click:hover {
        background-color: #F8F5EF;
    }

.bw-chat-card-icon {
    color: #676F7E;
    flex: 0 0 auto;
    margin-top: 2px;
}

/* min-width: 0 lets the flex child shrink so a wide preview is clipped instead of stretching the card. */
.bw-chat-card-text {
    flex: 1 1 auto;
    min-width: 0;
}

.bw-chat-card-title {
    font-weight: 600;
    color: #252B37;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bw-chat-card-size {
    font-size: 0.8125rem;
    color: #676F7E;
}

/* The hint of the content: three rows or the series names, clipped in both directions. It is a hint, so it
   never scrolls and never grows — whoever wants to read it opens the attachment. */
.bw-chat-card-preview {
    margin-top: 6px;
    max-height: 108px;
    overflow: hidden;
    pointer-events: none;
}

.bw-chat-card-series {
    font-size: 0.8125rem;
    color: #252B37;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bw-chat-card-actions {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
}

.bw-chat-image-preview {
    max-height: 96px;
    max-width: 100%;
    border-radius: 6px;
}

.bw-chat-image-large {
    max-width: 100%;
    max-height: 65vh;
    object-fit: contain;
    display: block;
    margin: 0 auto;
}

/* Off-screen home of the one chart element an SVG export is serialized from. Deliberately not display:none:
   the exporter picks the largest SVG by its measured area, and a hidden element measures zero everywhere. */
.bw-chat-chart-export {
    position: fixed;
    top: 0;
    left: -20000px;
    width: 900px;
    height: 400px;
}

/* ── JSON editor ───────────────────────────────────────────────────────────
   A definition document runs to several hundred lines, so the editor is a scroll area of its own: the color
   layer decides its size, the input field lies exactly on top of it, and both scroll together because they
   sit in the same scroller — no script keeps them aligned.

   That alignment rests on exactly one rule: everything that decides where a character lands is set for the
   number, color and text layers TOGETHER. Change a line height, a font size, a padding or the wrapping for
   only one of the layers and text and color drift apart as soon as anyone types. That is why they stand in
   ONE rule for all three layers and nowhere else.

   Two classes from the markup are deliberately NOT here: `bw-json-line` (a rendered line) and
   `bw-json-linenumber` (one line number). They are hooks, not styling — both inherit line height and wrapping
   from their container, and they must, because a rule of their own for one of the layers would be the very
   mistake the paragraph above forbids. So they are no oversight and must neither be defined nor removed from
   the markup. `bw-json-linenumber` is also not a typo for `bw-json-linenumbers`: the plural is the sticky
   column, the singular one number inside it. */
.bw-json-tools {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}

.bw-json-editor {
    display: flex;
    align-items: flex-start;
    height: 52vh;
    min-height: 240px;
    overflow: auto;
    border: 1px solid #E8E2D9;
    border-radius: 8px;
    background-color: #FFFFFF;
    color: #252B37;
}

.bw-json-linenumbers,
.bw-json-color,
.bw-json-text {
    font-family: Consolas, 'Cascadia Mono', 'DejaVu Sans Mono', 'Liberation Mono', monospace;
    font-size: 13px;
    line-height: 20px;
    letter-spacing: normal;
    word-spacing: normal;
    font-variant-ligatures: none;
    tab-size: 4;
    white-space: pre;
    margin: 0;
    border: 0;
    padding: 8px 10px;
}

/* The number column does not travel along when the editor is scrolled sideways: it sticks to the left edge
   and therefore needs a fill of its own — including its text color, or the code shows through beneath it. */
.bw-json-linenumbers {
    position: sticky;
    left: 0;
    z-index: 2;
    flex: 0 0 auto;
    text-align: right;
    user-select: none;
    background-color: #F8F5EF;
    color: #676F7E;
    border-right: 1px solid #E8E2D9;
}

/* The area grows to the width of the longest line (flex-basis auto) and otherwise fills the rest. */
.bw-json-area {
    position: relative;
    flex: 1 0 auto;
}

/* The input field occupies exactly the area the color layer spans. Because it is precisely as large as its
   content, it never scrolls inside itself — which is exactly what would otherwise shift the two layers against
   each other as soon as the caret runs to the edge. Its text is invisible; what is visible is the color layer
   underneath, and only the caret gets a color back. */
.bw-json-text {
    position: absolute;
    inset: 0;
    resize: none;
    outline: none;
    overflow: hidden;
    background-color: transparent;
    color: transparent;
    caret-color: #252B37;
}

/* The one fill in this file that deliberately names NO text color: the input field's text is transparent, and
   the selection is meant to let the colored layer underneath show through. A text color here would draw the
   selected characters a second time on top of the color layer. */
    .bw-json-text::selection {
        background-color: #E4D6B4;
    }

.bw-json-key {
    color: #8A5A00;
}

.bw-json-string {
    color: #24694C;
}

.bw-json-number {
    color: #1F4FA8;
}

.bw-json-keyword {
    color: #8A2B6B;
}

.bw-json-punctuation {
    color: #676F7E;
}

.bw-json-plain {
    color: #252B37;
}

/* The expression of the relation a source reads (AnalysisSourceDialog). While a view of the model is named the
   dialog holds its height (.bw-dialog-content-held, a flex column): the fields, the order and the generation
   keep their natural height in ".bw-source-top" and scroll only when the window is too low for them, and the
   relation takes the height that is left. Its read-only code box fills that height and scrolls its own lines, so
   neither the dialog nor the page does. The description has a height of its own for the same reason: the longest
   one in the model is several paragraphs, and it must not push the code out of the box. */
.bw-source-top {
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
}

.bw-relation {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    min-height: 200px;
}

    .bw-relation > .mud-divider {
        flex: 0 0 auto;
    }

    .bw-relation .bw-json-editor {
        flex: 1 1 auto;
        height: auto;
        min-height: 0;
        margin-top: 8px;
    }

.bw-relation-description {
    flex: 0 0 auto;
    max-height: 5.5em;
    overflow-y: auto;
}

/* ── Dark mode ─────────────────────────────────────────────────────────────
   The effective mode (system preference by default, switchable in the app bar)
   is mirrored onto the body as .bw-dark by index.html and MainLayout; the
   MudBlazor palette switches via IsDarkMode. These overrides adapt the
   hard-coded flow-app colors above to the flow-app's own dark tokens (index.css .dark): neutral
   grey surfaces and a softer gold, since the full gold glares on a dark ground.
   Tokens: background #1A1A1A, card #1F1F1F, soft #262626, accent #2E2E2E, border #404040,
   text #F2F2F2 / #A6A6A6, gold #E9BC63. BwTheme.PaletteDark carries the same values for the components
   MudBlazor paints itself. */
html:has(> body.bw-dark) {
    background-color: #1A1A1A;
}

body.bw-dark {
    background-color: #1A1A1A;
    scrollbar-color: #404040 transparent;
}

    body.bw-dark *::-webkit-scrollbar-thumb {
        background-color: #404040;
    }

    body.bw-dark *::-webkit-scrollbar-thumb:hover {
        background-color: rgba(166, 166, 166, 0.55);
    }

    body.bw-dark .loading-progress circle {
        stroke: #404040;
    }

    body.bw-dark .loading-progress circle:last-child {
        stroke: #E9BC63;
    }

    /* Every gold mark of the light theme, in the dark theme's softer gold: the edge bar of the active entry
       and of the selected analysis, the proportion bars of the top lists, the edge of the reader's own chat
       turn. */
    body.bw-dark .bw-nav .mud-nav-link.active::before,
    body.bw-dark .bw-bar {
        background-color: #E9BC63;
    }

    body.bw-dark .bw-chat-user {
        border-left-color: #E9BC63;
    }

    body.bw-dark .bw-nav .mud-nav-group > .mud-nav-link {
        color: #A6A6A6;
    }

        body.bw-dark .bw-nav .mud-nav-group > .mud-nav-link:hover {
            background-color: transparent;
            color: #F2F2F2;
        }

    body.bw-dark .loading-progress-text {
        color: #A6A6A6;
    }

    body.bw-dark .bw-apptitle,
    body.bw-dark .bw-account-name,
    body.bw-dark .bw-section-title,
    body.bw-dark .bw-kpi-value {
        color: #F2F2F2;
    }

    body.bw-dark .bw-account-role,
    body.bw-dark .bw-kpi-label,
    body.bw-dark .bw-empty,
    body.bw-dark .bw-muted {
        color: #A6A6A6;
    }

    body.bw-dark .bw-values th[scope="row"] {
        color: #A6A6A6;
    }

    body.bw-dark .bw-values td {
        color: #F2F2F2;
    }

    /* Dark: the tint darkens instead of lightening, so the row still reads as marked while the value
       keeps the contrast it has everywhere else. The flow-app's dark "warning-soft" (hsl 38 90% 15%). */
    body.bw-dark .bw-deviation {
        background-color: #492F04;
    }

        body.bw-dark .bw-deviation td,
        body.bw-dark .bw-deviation th {
            color: #F2F2F2;
        }

    body.bw-dark .bw-drawer {
        border-right-color: #404040;
    }

    body.bw-dark .bw-drawer-foot {
        border-top-color: #404040;
    }

    body.bw-dark .bw-nav .mud-nav-link {
        color: #A6A6A6;
    }

        body.bw-dark .bw-nav .mud-nav-link .mud-icon-root {
            color: #A6A6A6;
        }

        body.bw-dark .bw-nav .mud-nav-link:hover {
            background-color: #262626;
            color: #F2F2F2;
        }

        /* One surface, on the link — same reason as in the light theme, and stated at MudBlazor's reach for
           the same measured reason. */
        body.bw-dark .bw-nav.mud-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled) {
            background-color: #2E2E2E;
        }

        body.bw-dark .bw-nav .mud-nav-link.active,
        body.bw-dark .bw-nav .mud-nav-link.active .mud-nav-link-text {
            color: #F2F2F2 !important;
        }

            body.bw-dark .bw-nav .mud-nav-link.active .mud-icon-root {
                color: #F2F2F2 !important;
            }

    /* The analyses tree in the dark theme: same structure, the dark tokens of this section. */
    body.bw-dark .bw-tree .mud-treeview > .mud-treeview-item > .mud-treeview-item-content .mud-treeview-item-label,
    body.bw-dark .bw-tree .mud-treeview > .mud-treeview-item > .mud-treeview-item-content .mud-treeview-item-label *,
    body.bw-dark .bw-tree .mud-treeview-group .mud-treeview-item-label,
    body.bw-dark .bw-tree .mud-treeview-group .mud-treeview-item-label * {
        color: #F2F2F2;
    }

    body.bw-dark .bw-tree .mud-treeview-group {
        border-left-color: #404040;
    }

    body.bw-dark .bw-tree .mud-treeview-item-icon .mud-icon-root {
        color: #A6A6A6;
    }

    body.bw-dark .bw-tree .mud-treeview-hover .mud-treeview-item-content:hover {
        background-color: #262626;
    }

    body.bw-dark .bw-tree .mud-treeview .mud-treeview-item-content.mud-treeview-item-selected {
        background-color: #2E2E2E;
        box-shadow: inset 3px 0 0 #E9BC63;
    }

        body.bw-dark .bw-tree .mud-treeview .mud-treeview-item-content.mud-treeview-item-selected .mud-treeview-item-label,
        body.bw-dark .bw-tree .mud-treeview .mud-treeview-item-content.mud-treeview-item-selected .mud-treeview-item-label *,
        body.bw-dark .bw-tree .mud-treeview .mud-treeview-item-content.mud-treeview-item-selected .mud-icon-root {
            color: #F2F2F2;
        }

    /* The dark halves carry the magnified table as their second selector for the same reason the light ones
       do: it stands in a dialog, outside .bw-main. */
    body.bw-dark .bw-table-large,
    body.bw-dark .bw-main .mud-paper,
    body.bw-dark .bw-login-card {
        border-color: #404040;
        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) !important;
    }

    body.bw-dark .bw-table .mud-table-cell {
        border-bottom-color: #404040 !important;
        color: #F2F2F2;
    }

    body.bw-dark .bw-table th.mud-table-cell {
        background-color: #1F1F1F;
        color: #A6A6A6;
    }

    /* The dark half of the fixed-header band; ".bw-table" reaches the page table and the magnified one alike,
       same as the light rule. */
    body.bw-dark .bw-table.mud-table-sticky-header .mud-table-root .mud-table-head .mud-table-cell {
        background-color: #1F1F1F;
    }

    body.bw-dark .bw-table.mud-table-hover .mud-table-body .mud-table-row:hover {
        background-color: #262626;
    }

    body.bw-dark .bw-main .mud-input-outlined-border,
    body.bw-dark .bw-login-card .mud-input-outlined-border {
        border-color: #404040;
    }

    body.bw-dark .bw-main .mud-input.mud-input-outlined,
    body.bw-dark .bw-login-card .mud-input.mud-input-outlined {
        background-color: #1F1F1F;
    }

    body.bw-dark .bw-tabs .mud-tabs-tabbar {
        border-bottom-color: #404040;
    }

    /* The dark half of the card's table: the dark paper rule above is more specific than the light override and
       would draw the shadow back inside the card. */
    body.bw-dark .bw-favorite-card .bw-table-box {
        box-shadow: none !important;
    }

    body.bw-dark .bw-chat-table th,
    body.bw-dark .bw-chat-table td {
        border-color: #404040 !important;
        color: #F2F2F2;
    }

    body.bw-dark .bw-chat-table th {
        background-color: #1F1F1F;
        color: #A6A6A6;
    }

    body.bw-dark .bw-chat-user {
        background: #2E2E2E;
    }

    body.bw-dark .bw-chat-assistant {
        background: #1F1F1F;
    }

    /* Every surface below carries its own ink with it: a card that only swapped its background would leave
       the light theme's near-black text on a dark fill. */
    body.bw-dark .bw-chat-card {
        background-color: #1F1F1F;
        border-color: #404040;
        color: #F2F2F2;
    }

        body.bw-dark .bw-chat-card-click:hover {
            background-color: #262626;
        }

    body.bw-dark .bw-chat-card-icon {
        color: #A6A6A6;
    }

    body.bw-dark .bw-chat-card-title {
        color: #F2F2F2;
    }

    body.bw-dark .bw-chat-card-size {
        color: #A6A6A6;
    }

    body.bw-dark .bw-chat-card-series {
        color: #F2F2F2;
    }

    /* JSON editor in dark mode. Every fill names its text color with it — the editor and the number column are
       two separate surfaces, and one without a text color of its own would stand dark on dark here. The metrics
       stay untouched: only color is set here, nothing that would shift a line. */
    body.bw-dark .bw-json-editor {
        border-color: #404040;
        background-color: #1F1F1F;
        color: #F2F2F2;
    }

    body.bw-dark .bw-json-linenumbers {
        background-color: #1F1F1F;
        color: #A6A6A6;
        border-right-color: #404040;
    }

    body.bw-dark .bw-json-text {
        caret-color: #F2F2F2;
    }

        body.bw-dark .bw-json-text::selection {
            background-color: #404040;
        }

    body.bw-dark .bw-json-key {
        color: #E3B341;
    }

    body.bw-dark .bw-json-string {
        color: #86C99F;
    }

    body.bw-dark .bw-json-number {
        color: #8FB6EC;
    }

    body.bw-dark .bw-json-keyword {
        color: #E7A6D2;
    }

    body.bw-dark .bw-json-punctuation {
        color: #A6A6A6;
    }

    body.bw-dark .bw-json-plain {
        color: #F2F2F2;
    }
