/* Last edited by Nathan | 28-Sep-2026 | 15:30 IST | Added .flow-geo-help for GeoFixAlert's numbered fix-it steps */

/* Self-hosted, latin subset only, ~86 KB for all four faces. No font CDN: the app has to
   come up on a shop's 3G connection and inside a single container with no outbound DNS. */
@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url(../fonts/ibm-plex-sans-latin-400-normal.woff2) format("woff2");
}
@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url(../fonts/ibm-plex-sans-latin-500-normal.woff2) format("woff2");
}
@font-face {
    font-family: "IBM Plex Sans";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url(../fonts/ibm-plex-sans-latin-600-normal.woff2) format("woff2");
}
@font-face {
    font-family: "IBM Plex Mono";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url(../fonts/ibm-plex-mono-latin-500-normal.woff2) format("woff2");
}

:root {
    --flow-ground: #14161C;
    /* Fixed-light-context accent (boot screen, which stays light regardless of theme — see
       below) and fixed-dark-context accent (the crash overlay, always on #14161C). Everywhere
       else inside the running app uses --mud-palette-primary, which already tracks the
       user's light/dark toggle and computes correct contrast text. */
    --flow-accent: #6B3F63;
    --flow-accent-on-dark: #9D7195;
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    /* The light ground must fill the viewport, not just wrap the content. On a short page
       the body box stops at its content; without this the root shows through below it. */
    min-height: 100dvh;
    background: #FAFAFB;
    font-family: "IBM Plex Sans", system-ui, sans-serif;
    -webkit-font-smoothing: antialiased;
}

h1:focus { outline: none; }

/* MudBlazor leans on shadows to separate surfaces. This product separates them with a
   hairline instead, so a dense list stays flat and readable on a small screen. */
.mud-elevation-1, .mud-elevation-2, .mud-elevation-3, .mud-elevation-4 { box-shadow: none !important; }

.mud-paper-outlined { border-color: var(--mud-palette-lines-default); }

/* Anywhere digits line up — PINs, counts, money later on. */
.flow-mono {
    font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace;
    font-variant-numeric: tabular-nums;
}

.flow-nums { font-variant-numeric: tabular-nums; }

/* The wordmark. Two weights of one face, no image — it has to stay crisp at 14px in an
   app bar and it has to render before the network does anything. */
.flow-wordmark {
    font-weight: 600;
    letter-spacing: .16em;
    font-size: 1rem;
}

.flow-wordmark span { color: var(--mud-palette-primary); margin-left: .3em; }

/* --- the two screens that exist outside the shell -------------------------
   Sign-in and the forced PIN change both render with no app bar and no drawer,
   so they share one centred card rather than each inventing a layout. */
.signin-page {
    min-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
    background: var(--mud-palette-background);
}

.signin-card {
    width: 100%;
    max-width: 22rem;
}

.signin-mark {
    font-weight: 600;
    font-size: 1.15rem;
    letter-spacing: .18em;
    color: var(--mud-palette-text-primary);
}

.signin-mark span { color: var(--mud-palette-primary); margin-left: .35em; }

/* A PIN is four to six digits. Spacing them makes a mistyped one visible without
   ever showing what it is. */
.flow-pin input {
    font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace;
    letter-spacing: .5em;
}

/* A badge wraps a nav link (pending-lead count); without this it shrinks to the badge's own
   content width instead of the full drawer row the unwrapped links get. */
.flow-nav-badge {
    display: block;
    width: 100%;
}

/* --- drawer footer -------------------------------------------------------- */
.flow-nav-foot {
    margin-top: auto;
    padding: 1rem 1.25rem;
    border-top: 1px solid var(--mud-palette-lines-default);
}

/* The build number, in the drawer foot and on the sign-in card. Tabular figures because it gets
   read aloud down a phone line, and tracked out slightly so "1.13" cannot be misheard as "1.3". */
.flow-version {
    font-variant-numeric: tabular-nums;
    letter-spacing: .04em;
}

/* --- Login as banner ------------------------------------------------------
   Accent, not red: this is a state to be aware of, not an error. It sits above
   the page content and scrolls with it, because a sticky bar costs a row of screen
   on the phones this runs on. */
.flow-imp {
    display: flex;
    align-items: center;
    gap: .75rem;
    padding: .5rem 1rem;
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
    font-size: .8125rem;
}

.flow-imp-text { flex: 1 1 auto; }

.flow-imp-left {
    font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace;
    opacity: .75;
}

@media (max-width: 599px) {
    .flow-imp-text strong { display: block; }
}

/* --- page furniture ------------------------------------------------------- */
.flow-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

.flow-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: .75rem;
}

/* Owner's accent swatch picker, Company → Preferences. A finite palette, not a color
   input — FlowAccentPalette.Swatches is the only source of truth for what's clickable. */
.flow-accent-picker {
    display: flex;
    flex-wrap: wrap;
    gap: .6rem;
}

.flow-accent-swatch {
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    cursor: pointer;
    border: 2px solid transparent;
    transition: transform .1s ease, border-color .1s ease;
}

.flow-accent-swatch:hover { transform: scale(1.08); }

.flow-accent-swatch.is-selected {
    border-color: var(--mud-palette-text-primary);
}

/* --- the people list ------------------------------------------------------
   Cards, not a data grid: the sentence saying what somebody can see is the most
   important thing on the row and it does not fit in a column. */
.flow-people {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.flow-person-card { padding: .875rem 1rem; }

.flow-person-off { opacity: .55; }

.flow-person {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
}

.flow-person-main { flex: 1 1 auto; min-width: 0; }

.flow-person-name {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
}

.flow-person-access {
    margin-top: .35rem;
    color: var(--mud-palette-text-primary);
}

.flow-person-acts {
    display: flex;
    align-items: center;
    gap: .125rem;
    flex: 0 0 auto;
}

/* --- the parties list -----------------------------------------------------
   Same card-not-grid reasoning as the people list: the completeness ring and
   the where-it-is line matter more than any single column would allow. */
.flow-parties {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

/* The one .flow-parties that holds a <Virtualize> rather than a plain loop.
   Virtualize stands in for the rows it hasn't rendered with two spacer divs of
   a computed pixel height — it sets flex-shrink: 0 on them, so a flex column is
   supported by design, and this is not that bug. `gap`, though, applies at the
   spacers' own edges too, so a spacer asked to be 500px measures 508px and the
   scroll extent is a little longer than the component believes. Measured, the
   error is small and bounded (the gap lands on the ~20 items in the DOM, not on
   the thousands it is standing in for, and Virtualize re-measures item size from
   the rendered rows anyway) — so this is precision, not a bug fix. Spacing the
   cards from the cards costs nothing and leaves the declared heights exact:
   card-to-card pitch is identical either way. */
.flow-parties-virtual {
    gap: 0;
}

.flow-parties-virtual > .flow-party-card {
    margin-bottom: .5rem;
}

/* One not-yet-arrived row. Its height is set inline from the height Virtualize
   budgeted for that row, because the component works out the real row height by
   measuring the block between its spacers and subtracting the budget it had
   allowed for each placeholder in it — so a skeleton of any other height leaves a
   residue, multiplied by (placeholders / real rows), and a continuous scroll (where
   that ratio is far above one) drove the estimate away instead of towards the truth.
   The wrapper takes the whole budget, gap included, and no margin of its own, so
   what the browser lays out and what the component subtracted are the same number;
   the card inside keeps the visual spacing the loaded rows have. flex-shrink is the
   same guard Virtualize puts on its own spacers — this is a flex column, and a fixed
   height means nothing to a child the browser is still free to squeeze. */
.flow-party-ph {
    flex-shrink: 0;
    overflow: hidden;
}

.flow-party-ph > .flow-party-card {
    height: calc(100% - .5rem);
    overflow: hidden;
}

.flow-party-card {
    padding: .75rem 1rem;
    display: flex;
    align-items: center;
    gap: 1rem;
    text-align: left;
    width: 100%;
    background: transparent;
    border-radius: 0;
    text-transform: none;
}

/* MudBlazor sets a min-height and centre-justify on button-styled surfaces; a
   row of parties reads as a list, not a stack of buttons. */
.flow-party-card.mud-button-root { justify-content: flex-start; font-weight: 400; }

.flow-party-main { flex: 1 1 auto; min-width: 0; }

.flow-party-name {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
}

.flow-party-name .flow-party-code {
    font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace;
    font-size: .75rem;
    color: var(--mud-palette-text-secondary);
}

.flow-party-where {
    margin-top: .15rem;
    color: var(--mud-palette-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Every chip on a row, gathered into one block. They used to be scattered — two
   inside the name line, three after it — which reads the same either way on a
   desktop, where a row is one line regardless. On a phone it was what made the
   rows different heights from one another: the name line wrapped by however many
   chips that particular party happened to carry, so a list of four thousand shops
   had rows anywhere from 93px to 159px, measured.

   Virtualize keeps a single item height and re-derives it from whatever is on
   screen at the time. A list with no one height has nothing for it to settle on:
   it chases the running average, the spacers resize under the thumb, and every
   resize is another window and another cancelled fetch. Gathering the chips is
   what makes a fixed row height possible at all — everything that varies in number
   now sits in one box that can be given a budget of its own.

   Their order is operational rather than decorative, and it is the order they
   would clip in if a row ever ran out of room: when the follow-up is due, then
   where it has got to, then what is waiting to be claimed, then the labels. */
.flow-party-tail {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex: 0 0 auto;
}

/* The two layouts below are scoped to .flow-parties-virtual on purpose. A fixed
   row height is a requirement of the virtualised list and of nothing else, and
   .flow-party-card is also worn by the branch list on a party's own page and by
   the merge dialog's picker, where rows should still size to their content. */

/* The phone and tablet row, and the one that matters: this list is read on a
   phone far more than anywhere else. Two rows beside the ring — the shop above,
   its chips below — at a height that is the same for every row whatever it holds.

   The height is the whole point of the layout. It is declared here rather than
   left to the content so Virtualize measures one number and is never asked to
   revise it, which is what stops the list drifting under a scrolling thumb. It is
   budgeted for two lines of chips, so a party carrying all six still shows all
   six, and it works out at about what the average row already cost — the rows
   become uniform without the list getting any longer.

   Nothing is dropped to buy it. The two lines that can no longer grow truncate
   with an ellipsis instead of wrapping, which is the treatment the where-line
   already had. */
@media (max-width: 899px) {
    .flow-parties-virtual .flow-party-card {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        column-gap: .75rem;
        row-gap: .25rem;
        /* start, not centre: if a row ever does overflow — a tenant whose party-type
           label is long enough to push the chips onto a third line — the slack has to
           sit at the bottom, so what the card clips is the last chip and never the
           shop name. Centring splits the overflow across both ends and takes the name
           with it. */
        align-content: start;
        align-items: start;
        padding: .625rem .75rem;
        height: 8rem;
        overflow: hidden;
    }

    .flow-parties-virtual .flow-party-card > .flow-ring {
        grid-row: 1 / span 2;
        align-self: center;
    }

    .flow-parties-virtual .flow-party-main { grid-column: 2; }

    .flow-parties-virtual .flow-party-tail {
        grid-column: 2;
        align-items: flex-start;
        align-content: flex-start;
        flex-wrap: wrap;
        gap: .25rem;
        overflow: hidden;
    }

    /* A skeleton is still the height Virtualize budgeted for it, not the height a
       real row happens to be — those agree once the estimate has settled, and it
       is the first render, before they agree, that this is here for. */
    .flow-parties-virtual .flow-party-ph > .flow-party-card {
        height: calc(100% - .5rem);
    }
}

/* The narrowest phones still in the field. A party carrying all six chips needs a
   third chip line at this width, and 8rem clips it by a few pixels — measured. The
   rows stay uniform either way; this only decides which uniform height they take. */
@media (max-width: 359px) {
    .flow-parties-virtual .flow-party-card { height: 9rem; }
}

/* Wide enough for the whole row on one line. Fixed here too, for the same reason
   and against the same failure — there must be no width at which two rows of this
   list disagree about how tall they are. */
@media (min-width: 900px) {
    .flow-parties-virtual .flow-party-card {
        height: 4.25rem;
        overflow: hidden;
    }

    /* One line means the chips and the name compete for the same width, and the name
       is the one that must not lose: with only min-width 0 to hold it, a party wearing
       every chip squeezes it to nothing and the row shows classifications for a shop it
       no longer names. A floor makes the tail overflow instead, which the card clips
       from the right — the last chip first, which is the order the tail is written in. */
    .flow-parties-virtual .flow-party-main { min-width: 8rem; }

    .flow-parties-virtual .flow-party-ph > .flow-party-card {
        height: calc(100% - .5rem);
    }
}

/* The name line can no longer grow, so it has to be able to shrink: the name
   itself absorbs all of it and ellipsises, while the short code beside it stays
   whole rather than being sliced down the middle. Scoped to the virtualised list
   for the same reason the heights are — the other cards wearing these classes are
   free to wrap. */
.flow-parties-virtual .flow-party-name {
    flex-wrap: nowrap;
    overflow: hidden;
}

.flow-parties-virtual .flow-party-name > .mud-typography {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.flow-parties-virtual .flow-party-name > .flow-party-code { flex: 0 0 auto; }


/* The completeness ring, centred number inside a MudProgressCircular. */
.flow-ring {
    flex: 0 0 auto;
    position: relative;
    display: grid;
    place-items: center;
}

.flow-ring-num {
    position: absolute;
    font-size: .6875rem;
    font-variant-numeric: tabular-nums;
    color: var(--mud-palette-text-secondary);
}

/* --- the party form ------------------------------------------------------- */
.flow-party-form {
    display: flex;
    flex-direction: column;
    gap: .75rem;
}

.flow-party-row {
    display: flex;
    gap: .75rem;
    flex-wrap: wrap;
}

.flow-party-row > * { flex: 1 1 10rem; min-width: 8rem; }

/* Tap-friendly facet chips — follow-up bands and data-quality gaps — wrap on narrow screens. */
.flow-facet-chips {
    display: flex;
    gap: .4rem;
    flex-wrap: wrap;
    align-items: center;
}

.flow-facet-chips .mud-chip { cursor: pointer; }

/* The advanced-filter drawer sits under the search row and reads as one grouped surface. */
.flow-filter-panel { border-radius: var(--mud-default-borderradius); }

/* The live GSTIN read-out — state + PAN once it parses. */
.flow-gstin-parsed {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
    font-size: .8125rem;
}

/* --- party detail --------------------------------------------------------- */
.flow-detail-head {
    display: flex;
    align-items: flex-start;
    gap: 1rem;
    flex-wrap: wrap;
}

.flow-detail-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: .75rem 1.5rem;
}

.flow-fact-label {
    font-size: .6875rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--mud-palette-text-secondary);
}

/* One row per active brand: claimed (owned + due) or unclaimed (log-first-visit). */
.flow-brand-rows {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.flow-brand-row {
    padding: .625rem 1rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
}

.flow-brand-main { flex: 1 1 auto; min-width: 0; }

.flow-timeline {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.flow-timeline-row {
    display: flex;
    gap: .75rem;
    align-items: baseline;
}

.flow-timeline-when {
    flex: 0 0 auto;
    font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace;
    font-size: .75rem;
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
}

/* --- merge chooser -------------------------------------------------------- */
.flow-merge {
    display: flex;
    gap: .75rem;
    align-items: stretch;
}

.flow-merge-side { flex: 1 1 0; min-width: 0; }

.flow-merge-arrow {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    color: var(--mud-palette-text-secondary);
}

/* --- territory editor ----------------------------------------------------- */
.flow-terr-add {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    margin: .5rem 0 .75rem;
    flex-wrap: wrap;
}

.flow-terr-state { flex: 1 1 9rem; min-width: 9rem; }
.flow-terr-city  { flex: 2 1 12rem; min-width: 12rem; }

.flow-terr-chips {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
}

/* --- vocabulary editors (company master lists) ---------------------------- */
.flow-vocab-add {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    flex-wrap: wrap;
}

.flow-vocab-add-field { flex: 1 1 18rem; min-width: 14rem; }

.flow-vocab-group { margin-top: 1rem; }

.flow-vocab-rows {
    display: flex;
    flex-direction: column;
    gap: .4rem;
}

.flow-vocab-row {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .35rem .5rem .35rem .25rem;
}

/* A hidden row is still shown — just visibly set aside, not gone. */
.flow-vocab-off { opacity: .6; }

.flow-vocab-order {
    display: flex;
    flex-direction: column;
    flex: 0 0 auto;
}

.flow-vocab-label {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
}

.flow-vocab-acts {
    display: flex;
    align-items: center;
    gap: .125rem;
    flex: 0 0 auto;
}

/* The one-time PIN. Big, spaced and monospaced, because it is read aloud down a
   phone line more often than it is copied. */
.flow-pin-reveal {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    padding: .75rem 1rem;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 6px;
    background: var(--mud-palette-background-gray);
}

.flow-pin-reveal .flow-mono {
    font-size: 1.5rem;
    letter-spacing: .35em;
}

/* --- the today feed -------------------------------------------------------
   The day's work, grouped into three bands. Same card-not-grid reasoning as the
   parties list: the shop's name and its due line matter more than any column. */
.flow-today-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
    margin-bottom: 1rem;
}

.flow-bucket { margin-top: 1.25rem; }

.flow-bucket-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-bottom: .5rem;
}

.flow-bucket-count { font-variant-numeric: tabular-nums; }

.flow-feed {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.flow-feed-card {
    padding: .75rem 1rem;
    display: flex;
    align-items: center;
    gap: 1rem;
}

/* Overdue that has crossed the red threshold: a hairline left edge, not a filled
   card — loud enough to find, quiet enough to sit in a list of them. */
.flow-feed-card.is-red { border-left: 3px solid var(--mud-palette-error); }

.flow-feed-main { flex: 1 1 auto; min-width: 0; cursor: pointer; }

.flow-feed-name {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
}

.flow-feed-name .flow-feed-code {
    font-family: "IBM Plex Mono", ui-monospace, Consolas, monospace;
    font-size: .75rem;
    color: var(--mud-palette-text-secondary);
}

.flow-feed-where {
    margin-top: .15rem;
    color: var(--mud-palette-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* An admin's pushed note — reads as a pinned instruction, not plain metadata like flow-feed-where. */
.flow-feed-instruction {
    margin-top: .25rem;
    padding-left: .5rem;
    border-left: 2px solid var(--mud-palette-primary);
    color: var(--mud-palette-text-secondary);
    font-style: italic;
}

.flow-feed-meta {
    display: flex;
    align-items: center;
    gap: .35rem;
    flex-wrap: wrap;
    margin-top: .25rem;
}

/* The "last bought" hint — a quiet chip, so it reads as context on the meta line, not an action. */
.flow-feed-meta .flow-feed-brand.mud-chip {
    height: 20px;
    font-size: .7rem;
}

.flow-feed-meta .flow-feed-brand .mud-chip-icon {
    font-size: .85rem;
}

.flow-feed-acts {
    display: flex;
    align-items: center;
    gap: .25rem;
    flex: 0 0 auto;
}

/* --- the three-tap log ----------------------------------------------------
   Chips and tiles, sized for a thumb. Big enough to hit one-handed in a shop
   doorway, laid out to wrap rather than scroll. */
.flow-log-chips {
    display: flex;
    flex-wrap: wrap;
    gap: .4rem;
    margin-top: .35rem;
}

.flow-outcome-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
    gap: .5rem;
    margin: .5rem 0;
}

.flow-outcome-tile {
    min-height: 3rem;
    text-transform: none;
    justify-content: center;
}

.flow-log-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    margin-top: 1.25rem;
}

/* The confirm step's read-only recap — a quiet key/value grid, aligned so the eye runs down it fast. */
.flow-log-summary {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: .4rem .9rem;
    margin-top: .6rem;
}

.flow-log-summary-row {
    display: contents;
}

.flow-log-summary-key {
    align-self: center;
    text-transform: uppercase;
    letter-spacing: .03em;
}

.flow-log-summary-val {
    align-self: center;
    word-break: break-word;
}

/* The GPS alert's "how do I fix this?" steps — numbered so a rep can follow them one at a time
   without a phone call walking them through it. */
.flow-geo-help {
    padding-left: 1.2rem;
    font-size: .85rem;
}
.flow-geo-help li {
    margin-top: .2rem;
}

/* --- pre-Blazor boot screen ------------------------------------------------
   Plain CSS because none of the app exists yet. Deliberately quiet: it is on
   screen for under a second on a warm cache and should not announce itself. */
.boot {
    position: fixed; inset: 0;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center; gap: 1.1rem;
    background: #FAFAFB;
}
.boot-mark {
    font: 600 1.05rem/1 "IBM Plex Sans", system-ui, sans-serif;
    letter-spacing: .18em;
    color: #14161C;
}
.boot-mark span { color: var(--flow-accent); margin-left: .35em; }
.boot-bar {
    width: 9rem; height: 2px;
    background: #E4E6EB; border-radius: 2px; overflow: hidden;
}
.boot-bar i {
    display: block; height: 100%;
    background: var(--flow-accent);
    width: calc(var(--blazor-load-percentage, 15%));
    transition: width .15s linear;
}

/* No dark-mode override here. The app renders a single light MudBlazor theme; darkening
   the root for prefers-color-scheme:dark left the light content sitting on a dark root, so
   every dark-mode user saw a black band below short pages. The boot screen stays light to
   match the app it hands off to. */

@media (prefers-reduced-motion: reduce) {
    .boot-bar i { transition: none; }
}

#blazor-error-ui {
    display: none;
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 2000;
    background: #14161C; color: #F2F3F5;
    padding: .7rem 1.25rem;
    font-size: .875rem;
}
#blazor-error-ui .reload { color: var(--flow-accent-on-dark); }
#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: .75rem; top: .55rem; }

/* The update bar. flow-update.js reveals this only when a new build takes over while the reader
   is visibly mid-something; every other case reloads silently, so seeing this bar at all should be
   rare. Same ground and geometry as the error bar above, so the two read as one system - with the
   safe-area inset added, because unlike that bar this one is meant to be tapped, and on a notched
   phone bottom:0 puts a tap target under the home indicator. */
#flow-update-ui {
    display: none;
    position: fixed; bottom: 0; left: 0; right: 0; z-index: 2000;
    background: #14161C; color: #F2F3F5;
    padding: .7rem 1.25rem;
    padding-bottom: calc(.7rem + env(safe-area-inset-bottom));
    font-size: .875rem;
    cursor: pointer;
}
#flow-update-ui .reload { color: var(--flow-accent-on-dark); text-decoration: underline; }

/* --- block 5a: targets, pace line, attendance -----------------------------
   The pace strip and the attendance strip sit at the top of the Today feed; the
   targets grid is its own manager/owner screen. All three lean on the same MudBlazor
   palette variables the rest of the app uses, so they follow the theme without a
   second set of colours. */

/* The month stepper — one tap either way around a centred label, no calendar. */
.flow-month {
    display: flex;
    align-items: center;
    gap: .25rem;
}
.flow-month-label {
    min-width: 10rem;
    text-align: center;
    font-variant-numeric: tabular-nums;
}

/* The grid scrolls sideways on a narrow screen rather than shoving the page body. */
.flow-grid-scroll { overflow-x: auto; }

.flow-target-grid {
    width: 100%;
    border-collapse: collapse;
}
.flow-target-grid th,
.flow-target-grid td {
    padding: .4rem .5rem;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    vertical-align: middle;
}
.flow-target-grid thead th {
    text-align: left;
    font: 600 .75rem/1.2 inherit;
    color: var(--mud-palette-text-secondary);
    text-transform: uppercase;
    letter-spacing: .04em;
}
.flow-target-name { min-width: 11rem; }
.flow-target-col {
    width: 7.5rem;
    text-align: center;
}
.flow-target-input { max-width: 6rem; margin: 0 auto; }

.flow-target-actions {
    display: flex;
    align-items: center;
    gap: .75rem;
}

/* Bulk apply — three narrow fields and a button, wrapping on a phone. */
.flow-bulk {
    display: flex;
    align-items: center;
    gap: .5rem;
    flex-wrap: wrap;
}
.flow-bulk-state { min-width: 11rem; }
.flow-bulk-metric { min-width: 9rem; }
.flow-bulk-value { max-width: 7rem; }

.flow-mix-unset {
    color: var(--mud-palette-text-secondary);
    font-style: italic;
}

.flow-detail-shop { font-weight: 500; }
.flow-detail-code {
    font-size: .72rem;
    color: var(--mud-palette-text-secondary);
    font-variant-numeric: tabular-nums;
}
/* A win reads at a glance without a whole extra column. */
.flow-detail-won {
    color: var(--mud-palette-success-text, var(--mud-palette-success));
    font-weight: 600;
}

/* ---- The report host and its shared table furniture ---- */

/* The report picker — a single dropdown button instead of a six-way segmented control, so the
   header stops crowding at any width instead of needing a scroll-to-see-more escape hatch. */
.flow-report-picker-btn { text-transform: none; }

/* "Group by" label sitting beside its chip row, both on one line. */
.flow-groupby {
    display: flex;
    align-items: center;
    gap: .25rem;
    flex-wrap: wrap;
}
.flow-groupby-label {
    text-transform: uppercase;
    letter-spacing: .04em;
}

/* Numbers that carry a warning wear the colour rather than an extra icon. */
.flow-num-warn { color: var(--mud-palette-warning); font-weight: 600; }

/* The drill panel — the rows behind one tapped number. */
.flow-drill { padding: 0; overflow: hidden; }
.flow-drill-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .75rem;
    padding: .6rem .9rem;
    background: var(--mud-palette-background-grey);
    border-bottom: 1px solid var(--mud-palette-lines-default);
    flex-wrap: wrap;
}

.flow-detail-tags {
    font-size: .72rem;
    color: var(--mud-palette-text-secondary);
}
.flow-detail-notes {
    font-size: .72rem;
    color: var(--mud-palette-text-secondary);
    font-style: italic;
    margin-top: .1rem;
}

/* Coverage detail — the due note under the date, and the tap-to-call cell. */
.flow-detail-late { color: var(--mud-palette-error); font-size: .72rem; font-weight: 600; }
.flow-detail-soon { color: var(--mud-palette-info); font-size: .72rem; }
.flow-detail-call {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
}
.flow-detail-who { font-size: .78rem; }

/* Shared report stat-card tile — every tab's header numbers now render through <ReportStatCard>
   onto the same .flow-kpis grid Dashboard already uses, instead of each view inventing its own
   hero/chip/mix-kpi treatment. A card doubles as a filter button when it's given a click handler. */
.flow-stat-click { cursor: pointer; transition: border-color .12s ease, background-color .12s ease; -webkit-tap-highlight-color: transparent; }
.flow-stat-click:hover { background: var(--mud-palette-action-default-hover); }
.flow-stat.is-on { border-color: var(--mud-palette-primary); background: var(--mud-palette-primary-hover); }
.flow-stat-value.is-alert { color: var(--mud-palette-error); }
.flow-stat-value.is-warn { color: var(--mud-palette-warning); }
.flow-stat-value.is-accent { color: var(--mud-palette-primary); }
.flow-stat-value.is-clear { color: var(--mud-palette-success); }

.flow-rpt-groups {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.flow-rpt-group {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    overflow: hidden;
    background: var(--mud-palette-surface);
}

.flow-rpt-group-row {
    position: relative;
    padding: .65rem .8rem;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
}
.flow-rpt-group-row:hover { background: var(--mud-palette-action-default-hover); }
.flow-rpt-group-row.flow-rpt-group-open,
.flow-rpt-group-row.flow-rpt-group-open:hover { background: var(--mud-palette-primary-hover); }

/* The real share bar — a visible accent wash, not a faint tint. */
.flow-rpt-group-bar {
    position: absolute;
    inset: 0;
    width: var(--w, 0%);
    background: color-mix(in srgb, var(--mud-palette-primary) 16%, transparent);
    border-right: 2px solid var(--mud-palette-primary);
    pointer-events: none;
}

.flow-rpt-group-top {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
}

.flow-rpt-group-name { font-weight: 500; }

.flow-rpt-group-counts {
    display: flex;
    align-items: baseline;
    gap: .5rem;
    flex: 0 0 auto;
}

.flow-rpt-group-num { font-size: 1rem; font-weight: 600; }

.flow-rpt-badge {
    font-size: .7rem;
    font-weight: 600;
    border-radius: 10px;
    padding: .1rem .5rem;
    white-space: nowrap;
}
.flow-rpt-badge.is-alert { color: var(--mud-palette-error); background: color-mix(in srgb, var(--mud-palette-error) 14%, transparent); }
.flow-rpt-badge.is-warn { color: var(--mud-palette-warning); background: color-mix(in srgb, var(--mud-palette-warning) 16%, transparent); }
.flow-rpt-badge.is-accent { color: var(--mud-palette-primary); background: color-mix(in srgb, var(--mud-palette-primary) 16%, transparent); }

/* The detail — cards, not a table nested in a table, same feed-card language as Today/Parties. */
.flow-rpt-detail {
    border-top: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-background-grey);
}
.flow-rpt-detail-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .75rem;
    padding: .55rem .8rem;
    border-bottom: 1px solid var(--mud-palette-lines-default);
    flex-wrap: wrap;
}
.flow-rpt-detail-cards {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    padding: .6rem;
}
.flow-rpt-detail-card {
    background: var(--mud-palette-surface);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    padding: .6rem .75rem;
    display: flex;
    gap: .6rem;
    align-items: flex-start;
}
.flow-rpt-detail-card.is-late { border-left: 3px solid var(--mud-palette-error); }
.flow-rpt-detail-card.is-won { border-left: 3px solid var(--mud-palette-success); }

.flow-rpt-detail-main { flex: 1 1 auto; min-width: 0; }
.flow-rpt-detail-due {
    flex: 0 0 auto;
    text-align: right;
    font-variant-numeric: tabular-nums;
}
.flow-detail-time { font-size: .75rem; }

/* The pace strip — one thin bar per set metric, stacked. */
.flow-pace {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    padding: .75rem 1rem;
}
.flow-pace-row {
    display: flex;
    flex-direction: column;
    gap: .2rem;
}
.flow-pace-top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
}
.flow-pace-label { font-weight: 600; }
.flow-pace-bar { height: 6px; border-radius: 3px; }

/* The day's scoreboard — "done / planned · pending", one headline bar, and a per-rep breakdown
   underneath when more than one salesperson is in view. */
.flow-score {
    display: flex;
    flex-direction: column;
    gap: .4rem;
    padding: .75rem 1rem;
}
.flow-score-head {
    display: flex;
    align-items: baseline;
    gap: .5rem;
}
.flow-score-title { font-weight: 600; }
.flow-score-tally { margin-left: auto; }
.flow-score-of { color: var(--mud-palette-text-secondary); }
.flow-score-pending { color: var(--mud-palette-warning); margin-left: .35rem; }
.flow-score-clear { color: var(--mud-palette-success); margin-left: .35rem; }
.flow-score-bar { height: 6px; border-radius: 3px; }
.flow-score-reps {
    display: flex;
    flex-direction: column;
    gap: .45rem;
    margin-top: .5rem;
    padding-top: .6rem;
    border-top: 1px solid var(--mud-palette-divider);
}
.flow-score-rep {
    display: flex;
    flex-direction: column;
    gap: .15rem;
}
.flow-score-rep-top {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .5rem;
}
.flow-score-rep-name { font-weight: 500; }
.flow-score-rep-bar { height: 4px; border-radius: 2px; }

/* The attendance strip — a status line and a single action, on one row. */
.flow-attend {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: .6rem 1rem;
}
.flow-attend-text {
    display: flex;
    align-items: center;
    gap: .5rem;
}

/* --- dashboard ------------------------------------------------------------
   The escalation row wears a party card but keeps its reassign button at the
   end, so the card body can be tapped through to the party while the action
   stays its own target. */
.flow-esc-card {
    padding: .75rem 1rem;
    display: flex;
    align-items: center;
    gap: 1rem;
}

/* Two columns on a wide screen — pace on the left, the day's roll and feed on
   the right — collapsing to a single stack on a phone. */
.flow-dash-cols {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.5rem;
}

@media (min-width: 900px) {
    .flow-dash-cols { grid-template-columns: 1fr 1fr; }
}

/* The dashboard's due-date calendar — a real month grid, not a fortnight strip. Header row (prev/
   next + label), a day-of-week row, then a 7-column grid of day cells shaded by how many pending
   follow-ups are due, plus leading blank cells so day 1 lands under its own weekday. Grid columns
   shrink to fit the container exactly — no min-width floor to force a scrollbar on ordinary widths. */
.flow-month-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: .25rem;
}
.flow-month-dow {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    text-align: center;
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--mud-palette-text-secondary);
    padding: 0 .1rem .3rem;
}
.flow-month-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(0, 1fr));
    gap: .3rem;
    padding: .1rem;
}
.flow-month-blank { visibility: hidden; }
.flow-load-cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .1rem;
    aspect-ratio: 1 / 1;
    border-radius: var(--mud-default-borderradius);
    border: 1px solid var(--mud-palette-divider);
    overflow: hidden;
    cursor: pointer;
}
.flow-load-cell.is-today { border-color: var(--mud-palette-primary); border-width: 2px; }
.flow-load-date { font-size: .78rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.flow-load-count { font-size: .78rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.flow-load-1 { background: color-mix(in srgb, var(--mud-palette-primary) 8%, transparent); }
.flow-load-2 { background: color-mix(in srgb, var(--mud-palette-primary) 16%, transparent); }
.flow-load-3 { background: color-mix(in srgb, var(--mud-palette-primary) 26%, transparent); }
/* Hot = busy (volume, relative to this caller's own month). Overdue = urgent (still-pending work past
   its own due date) — a different question, so it gets a different signal: a red corner marker layered
   on top rather than fighting Hot's background for the same space. A day can be both at once. */
.flow-load-cell.is-hot {
    border-color: var(--mud-palette-warning);
    background: color-mix(in srgb, var(--mud-palette-warning) 16%, transparent);
}
.flow-load-cell.is-hot .flow-load-count { color: var(--mud-palette-warning); }
.flow-load-cell.is-overdue { position: relative; }
.flow-load-cell.is-overdue::after {
    content: "";
    position: absolute;
    top: .3rem;
    right: .3rem;
    width: .4rem;
    height: .4rem;
    border-radius: 50%;
    background: var(--mud-palette-error);
}

@media (max-width: 480px) {
    .flow-month-grid { gap: .2rem; }
    .flow-load-date { font-size: .7rem; }
    .flow-load-count { font-size: .7rem; }
}

/* Cells are square and stretch to fill their column, so left unbounded on a wide desktop
   container (MaxWidth.Large) they balloon into oversized tiles. Cap the whole calendar's
   width past tablet so cells settle at a normal size instead of scaling with the page. */
@media (min-width: 900px) {
    .flow-month-wrap { max-width: 26rem; }
}

.flow-attend-roll {
    display: flex;
    flex-direction: column;
}

.flow-attend-mark {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .35rem .5rem;
}

.flow-attend-mark .flow-nums { margin-left: auto; }

.flow-help-idea {
    border-left: 3px solid var(--mud-palette-primary);
}

.flow-help-idea-label {
    color: var(--mud-palette-primary);
    letter-spacing: .05em;
}

.flow-help-steps {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.flow-help-steps li {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
}

.flow-help-num {
    flex: none;
    width: 1.5rem;
    height: 1.5rem;
    border-radius: 50%;
    background: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
    font-size: .8rem;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
}

.flow-help-sub {
    margin-top: .35rem;
    color: var(--mud-palette-text-secondary);
    font-size: .85rem;
}

.flow-help-glossary {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: .6rem;
}

.flow-help-glossary-row {
    display: grid;
    grid-template-columns: 6.5rem 1fr;
    gap: .75rem;
}

.flow-help-glossary-row dt {
    font-weight: 600;
}

.flow-help-glossary-row dd {
    margin: 0;
    color: var(--mud-palette-text-secondary);
}
