html, body {
    /* Helvetica Neue and Arial carry no Arabic, so half of every client list
       was being drawn in whatever the browser fell back to -- a different face
       on every machine. Segoe UI leads because it has both scripts and is on
       every Windows box in the offices, with the platform faces behind it and
       a named Arabic fallback for Linux. No web font: these offices lose their
       connection often enough that a masthead that arrives late is worse than
       one that never left.

       Nothing loads over the network, so the app looks the same offline. */
    font-family: "Segoe UI", system-ui, -apple-system, "Noto Sans Arabic",
                 "Helvetica Neue", Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/* Long prose gets a readable measure and no single word left on its own line. */
.hint, .rate-note, .page__scope-note { max-width: 68ch; text-wrap: pretty; }

h1, h2, h3, .tile__value { text-wrap: balance; }

/* Headings are the one place tighter tracking pays: at these sizes the default
   spacing reads loose. */
.page__header h2 { letter-spacing: -.01em; }
.tile__value { letter-spacing: -.02em; }

a, .btn-link {
    color: #006bb7;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}
/* ---- Tokens --------------------------------------------------------------
   One place for the palette, the spacing and the edges. The colours are the
   ones the app already used; naming them is what changed, so a screen no
   longer carries its own copy of #64748b.

   The greys are one family throughout (cool slate). The single accent is the
   blue the collector app has always used for a value.
-------------------------------------------------------------------------- */

:root {
    /* Ink and paper */
    --ink:        #0f172a;
    --ink-soft:   #334155;
    --muted:      #64748b;
    --muted-soft: #94a3b8;
    --line:       #e2e8f0;
    --line-firm:  #cbd5e1;
    --paper:      #ffffff;
    --paper-sunk: #f8fafc;
    --paper-hover:#f1f5f9;

    /* The one accent, and the states money is reported in */
    --accent:      #31569F;
    --accent-deep: #274580;
    --accent-wash: #eff6ff;
    --good: #047857;
    --warn: #b45309;
    --bad:  #b91c1c;

    /* The menu */
    --shell:       #1e293b;
    --shell-deep:  #0f172a;
    --shell-line:  #334155;
    --shell-text:  #cbd5e1;

    --radius:    6px;
    --radius-sm: 4px;

    /* Tinted rather than black: a grey shadow over a slate interface reads as
       dirt, and a shadow carrying the surface's own hue reads as depth. */
    --shadow:    0 1px 2px rgba(15, 23, 42, .06), 0 1px 3px rgba(15, 23, 42, .08);
    --shadow-up: 0 8px 24px rgba(15, 23, 42, .14);

    /* Where the sidebar stops being a sidebar. */
    --shell-width: 232px;

    --ease: 180ms cubic-bezier(.2, 0, .2, 1);
}

/* Money and counters line up column-wise when the digits are all one width.
   In a screen that is mostly numbers this is the difference between a table
   that can be scanned and one that has to be read. */
.tile__value,
.power-grid .rz-cell-data,
.collect__value,
.collect__bill-money,
.pay__amount,
.num {
    font-variant-numeric: tabular-nums;
}

/* ---- PowerGrid -------------------------------------------------------- */
/* Compact, information-dense tables closer to the DevExpress grids users
   already work in than to Radzen's default Material spacing. */

/* A screen fills the space under the menu. By default it scrolls itself --
   Settings and Reports are ordinary documents and should. */
.page {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    padding: 1rem 1.25rem 1.25rem;
}

/* A screen built around a grid or a round hands the scrolling to that instead,
   so the toolbar above it stays put while the data moves underneath.

   Selected by what the screen contains rather than by a class on each one:
   there is nothing to remember to add, and no screen markup changes.

   The list has to be the page's own child, though. A .collect__list nested
   inside a card is one section of a longer page rather than the thing the page
   is -- the tenant detail screen reuses these rows for its handful of users --
   and matching it stopped that page scrolling while handing the scroll to a
   list that was never sized to take it. The rows all rendered; a thousand
   pixels of them were simply below the fold with no way to reach them. */
.page:has(.power-grid),
.page:has(> .collect__list) {
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

.page:has(.power-grid) > .power-grid,
.page:has(> .collect__list) > .collect__list {
    flex: 1;
    min-height: 0;
}

/* Unless the grid is not what the screen is. The dashboard holds three of them
   among its tiles and headings, and no one of them is the thing to scroll --
   the page is, because the figures below the fold are the point of it. Marked
   on the screen rather than guessed at, since containing a grid cannot tell
   the two cases apart. */
.page--scrolls,
.page--scrolls:has(.power-grid) {
    overflow-y: auto;
    display: block;
}

.page__header {
    display: flex;
    align-items: baseline;
    gap: 1rem;
    margin-bottom: .75rem;
}

.page__header h2 { margin: 0; font-size: 1.25rem; font-weight: 600; }

.page__subtitle { color: #6b7280; font-size: .875rem; }

.page__scope-note {
    margin-top: .5rem;
    padding: .5rem .75rem;
    background: #eff6ff;
    border-inline-start: 3px solid #3b82f6;
    border-radius: 4px;
    font-size: .875rem;
    color: #1e3a8a;
}

/* Search and its clear button used to cost 76px of height for one input and
   one button -- a quarter of what was left for the rows on a laptop. One row,
   the height of the control itself, and the search takes the space the other
   controls do not want. */
.power-grid__toolbar {
    display: flex;
    align-items: center;
    gap: .4rem;
    margin-bottom: .5rem;
    flex-wrap: wrap;
}

.power-grid__search { flex: 1 1 16rem; max-width: 28rem; min-width: 12rem; }
.power-grid__search input { width: 100%; }

.power-grid__summary { font-weight: 600; }

/* These selectors are Radzen's real class names. The previous set was written
   against a different version and matched nothing at all, which is why "dense"
   grids were rendering at the default 16px. */
.power-grid--dense .rz-data-grid-data td,
.power-grid--dense .rz-data-grid-data th { font-size: .8125rem; }

.power-grid .rz-data-grid-data td { padding: .5rem .6rem; }

/* Radzen gives cell text a 1rem line box and hides the overflow, so at 14px
   the glyphs need 19px and get 16 -- three pixels shaved off the bottom of
   every row. Latin loses the tails of g and y; Arabic loses more, because the
   script sits taller and hangs lower at the same size.

   It has to be written against Radzen's own selector shape. Their rule is
   `.rz-grid-table td .rz-cell-data`, two classes and an element; a plain
   `.power-grid .rz-cell-data` is two classes and loses the cascade, which is
   why an earlier attempt at this changed nothing at all.

   One height for both languages: Arabic content appears in the grid whatever
   the interface language is, so keying this off [dir="rtl"] would leave every
   Arabic name clipped for an owner working in English. */
.power-grid .rz-datatable-data td .rz-cell-data,
.power-grid .rz-grid-table td .rz-cell-data {
    line-height: 1.7;
}


.power-grid .rz-data-grid-data tbody tr:hover { background: #f1f5f9; }

.power-grid .rz-datatable-footer,
.power-grid tfoot td { background: #f8fafc; font-weight: 600; }

/* ---- The grid scrolls, not the page --------------------------------------
   The rows live in their own scroll area, so moving through them does not drag
   the whole screen up and away from the toolbar. Column headers and the totals
   row stay put while the rows move underneath.
-------------------------------------------------------------------------- */

/* The grid is a column: toolbar, then the table, then the pager. The table is
   the part that gives, so the pager stays visible at the bottom of the screen
   instead of being pushed below the fold. */
.power-grid { display: flex; flex-direction: column; min-height: 0; }

.power-grid .rz-datatable,
.power-grid .rz-data-grid {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

.power-grid .rz-data-grid-data {
    flex: 1;

    /* Never nothing. On a short window with a tall toolbar above it the rows
       are the first thing flexbox would give up, and a grid showing no rows at
       all is worse than one the screen has to be scrolled a little to finish. */
    min-height: 9rem;
    overflow: auto;
}

.power-grid .rz-data-grid-data thead {
    position: sticky;
    top: 0;
    z-index: 3;
}

/* The summary row: the sums and the row count are the reason to look at the
   bottom of a grid, and scrolling should not take them away. */
.power-grid .rz-data-grid-data tfoot {
    position: sticky;
    bottom: 0;
    z-index: 3;
}

.power-grid .rz-data-grid-data thead th { background: #fff; }
.power-grid .rz-data-grid-data tfoot td { background: #f8fafc; }

/* A visible edge where the sticky rows meet the scrolling ones. */
.power-grid .rz-data-grid-data thead tr:last-child th { box-shadow: inset 0 -1px 0 #cbd5e1; }
.power-grid .rz-data-grid-data tfoot td { box-shadow: inset 0 1px 0 #cbd5e1; }

/* Give the scrollbars enough of a track to be usable with a mouse. */
.power-grid .rz-data-grid-data::-webkit-scrollbar { width: 12px; height: 12px; }
.power-grid .rz-data-grid-data::-webkit-scrollbar-thumb {
    background: #cbd5e1; border-radius: 6px; border: 3px solid #fff;
}
.power-grid .rz-data-grid-data::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
.power-grid .rz-data-grid-data::-webkit-scrollbar-track { background: #f8fafc; }

/* ---- Layout ----------------------------------------------------------- */

/* Grid rather than flex, so the same two children can be a sidebar and a pane
   on a desk and a drawer over a page on a phone without either being moved in
   the markup.

   100dvh, not 100vh: on a phone browser the toolbars come and go, and a fixed
   vh makes the whole shell jump every time they do. */
.layout {
    display: grid;
    grid-template-columns: var(--shell-width) minmax(0, 1fr);
    grid-template-areas: "side main";

    /* The window, exactly. What scrolls is whatever inside the screen has more
       to show -- the rows of a grid, the clients on a round -- rather than the
       whole page carrying its own toolbar off the top of the screen with it. */
    height: 100dvh;
    overflow: hidden;
}

.layout__sidebar {
    grid-area: side;
    background: var(--shell);
    color: var(--shell-text);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

.layout__main {
    grid-area: main;
    min-width: 0;
    min-height: 0;
    background: var(--paper);
    display: flex;
    flex-direction: column;
}

/* The drawer's plumbing. Above the breakpoint the bar and the scrim are not
   rendered at all, so the desk layout is exactly what it was. */
.topbar, .nav-scrim { display: none; }

@media (max-width: 900px) {
    .layout {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas: "bar" "main";

        /* Without this, both rows are the implicit auto and an auto track
           stretches to soak up any height its content does not claim -- so a
           screen whose content is shorter than the viewport (no zone chosen
           yet, or a short list) grew the *topbar*, not the content below it,
           because the topbar's own auto track was just as eligible to stretch
           as main's. Pinning bar to its own content and giving every leftover
           pixel to main is what every screen actually wants. */
        grid-template-rows: auto 1fr;
    }

    /* The closed drawer is parked outside the shell, and a parked drawer must
       not be something the page can be scrolled sideways to reach. `clip`
       rather than `hidden`, because `hidden` would make this a scroll
       container and the grid's sticky header would stop sticking. */
    .layout { overflow-x: clip; }

    .topbar {
        grid-area: bar;
        position: sticky;
        top: 0;
        z-index: 40;
        display: flex;
        align-items: center;
        gap: .75rem;
        padding: .55rem .75rem;
        background: var(--shell);
        color: #fff;
        box-shadow: var(--shadow);
    }

    .topbar__name { font-weight: 700; font-size: 1rem; }
    .topbar__tag {
        font-size: .625rem; text-transform: uppercase; letter-spacing: .08em;
        color: #fde047; font-weight: 700;
    }
    .topbar__tenant {
        font-size: .75rem; color: var(--muted-soft);
        margin-inline-start: auto; text-align: end;
        overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    }

    /* Three bars, drawn rather than an icon font, so it needs nothing loaded. */
    .topbar__burger {
        display: flex; flex-direction: column; justify-content: center; gap: 4px;
        width: 40px; height: 40px; padding: 9px 8px;
        border-radius: var(--radius-sm); cursor: pointer;
        transition: background var(--ease);
    }
    .topbar__burger span { display: block; height: 2px; background: #fff; border-radius: 2px; }
    .topbar__burger:hover { background: var(--shell-line); }
    .topbar__burger:active { transform: scale(.94); }

    /* The drawer itself: off the side until asked for. Transform rather than
       inset, so opening it is one composited step and not a relayout. */
    .layout__sidebar {
        position: fixed;
        inset-block: 0;
        inset-inline-start: 0;
        width: min(82vw, 300px);
        z-index: 60;
        transform: translateX(-102%);
        transition: transform var(--ease);
        box-shadow: var(--shadow-up);
    }

    [dir="rtl"] .layout__sidebar { transform: translateX(102%); }

    .nav-drawer:checked ~ .layout__sidebar,
    [dir="rtl"] .nav-drawer:checked ~ .layout__sidebar { transform: translateX(0); }

    .nav-drawer:checked ~ .nav-scrim {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 50;
        background: rgba(15, 23, 42, .45);
    }

    /* A drawer that slides while the reader has asked for no motion is just a
       drawer that appears. */
    @media (prefers-reduced-motion: reduce) {
        .layout__sidebar { transition: none; }
    }
}

/* The menu is a column that fills the sidebar, so the license line and the
   signed-in user can be pushed to the foot of it rather than sitting under
   the last nav item on a tall screen. */
.nav-shell { display: flex; flex-direction: column; min-height: 100%; }

.nav-shell__brand { padding: 1rem 1rem .75rem; border-bottom: 1px solid #334155; }
.nav-shell__logo { display: block; font-size: 1.1rem; font-weight: 700; color: #fff; }
.nav-shell__tenant { display: block; font-size: .75rem; color: #94a3b8; margin-top: .15rem; }

.nav-shell__nav { padding: .5rem 0 2rem; }

.nav-group { margin-top: .75rem; }

.nav-group__title {
    padding: .35rem 1rem;
    font-size: .6875rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: #64748b;
    font-weight: 600;
}

.nav-item {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .4rem 1rem;
    color: #cbd5e1;
    text-decoration: none;
    font-size: .875rem;
    border-inline-start: 3px solid transparent;
}

.nav-item:hover { background: #334155; color: #fff; }

.nav-item.active {
    background: #0f172a;
    color: #fff;
    border-inline-start-color: #3b82f6;
    font-weight: 600;
}

.nav-item__icon { width: 1.2rem; text-align: center; font-size: .95rem; }

/* ---- Stat tiles -------------------------------------------------------- */

.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: .75rem; margin-bottom: 1rem; }

.tile {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: .75rem .9rem;
    background: var(--paper);
    box-shadow: var(--shadow);
}
.tile__label { font-size: .75rem; color: #64748b; text-transform: uppercase; letter-spacing: .04em; }
.tile__value { font-size: 1.5rem; font-weight: 700; color: #0f172a; margin-top: .2rem; }
.tile__note  { font-size: .75rem; color: #64748b; margin-top: .15rem; }
.tile--good .tile__value { color: #047857; }
.tile--warn .tile__value { color: #b45309; }
.tile--bad  .tile__value { color: #b91c1c; }

.toolbar-row { display: flex; gap: .75rem; align-items: end; flex-wrap: wrap; margin-bottom: .75rem; }
.field { display: flex; flex-direction: column; gap: .2rem; }
.field__label { font-size: .75rem; color: #475569; font-weight: 600; }

.section-title {
    font-size: .9375rem;
    font-weight: 700;
    color: #0f172a;
    margin: 1.25rem 0 .5rem;
    padding-bottom: .3rem;
    border-bottom: 1px solid #e2e8f0;
}

.section-title small { font-weight: 400; color: #64748b; font-size: .75rem; margin-inline-start: .5rem; }

.split { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }
@media (max-width: 1100px) { .split { grid-template-columns: 1fr; } }

/* ---- Settings ---------------------------------------------------------- */

/* Two cards to a row. Each is a short, self-contained group of switches, so
   stacking them in one column left two thirds of the screen empty and put the
   save button three scrolls down.

   A grid rather than CSS columns: it keeps the reading order the screen was
   written in, pairing VAT with Rounding and the receipt header with the A5
   bill, instead of flowing the end of one column into the top of the next. */
.settings {
    max-width: 1180px;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 1rem;

    /* Each card keeps its own height; a short one does not stretch to match
       the tall one beside it. */
    align-items: start;
}

/* The standing note and the save button belong to the whole screen. */
.settings > .hint,
.settings > .settings__actions { grid-column: 1 / -1; }

/* The gap does the spacing between cards now. */
.settings > .card { margin-bottom: 0; }

/* One column again once a card would be too narrow for a label and its control
   to share a line. */
@media (max-width: 1000px) {
    .settings { grid-template-columns: minmax(0, 1fr); max-width: 780px; }
}

.card {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: .9rem 1.1rem 1.1rem;
    margin-bottom: 1rem;
    background: #fff;
}

.card__title {
    font-size: .875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: #475569;
    margin: 0 0 .75rem;
}

.setting {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.5rem;
    padding: .45rem 0;
    border-bottom: 1px solid #f1f5f9;
}

.setting:last-child { border-bottom: none; }
.setting--stacked { flex-direction: column; align-items: stretch; }

.setting__label { font-size: .875rem; color: #0f172a; display: flex; flex-direction: column; }
.setting__label small { color: #64748b; font-size: .75rem; font-weight: 400; margin-top: .1rem; }

.settings__actions { margin: 0 0 1rem; }

.hint {
    margin: .5rem 0 0;
    padding: .5rem .7rem;
    background: #f8fafc;
    border-inline-start: 3px solid #94a3b8;
    font-size: .8125rem;
    color: #475569;
}

.expiry { font-size: .875rem; font-weight: 600; color: #0f172a; }
.expiry--grace { color: #b45309; }
.expiry--blocked { color: #b91c1c; }

/* ---- Billing toolbar on the Clients screen ----------------------------- */

/* The rates a run is priced at. Eight controls that have to be checked before
   billing a month, so they stay on one bar rather than behind a panel -- but
   they were costing 281px of a 734px window, which is the rows nobody could
   see. Tighter gaps and a label that sits on the control rather than above it
   at full size. */
.billing-bar {
    display: flex;
    align-items: end;
    gap: .55rem .9rem;
    flex-wrap: wrap;
    padding: .5rem .7rem;
    margin-bottom: .5rem;
    background: var(--paper-sunk);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

.billing-bar .field__label {
    font-size: .6875rem;
    letter-spacing: .01em;
    margin-bottom: .1rem;
}

.billing-bar .field { gap: 0; }

.billing-bar__actions { margin-inline-start: auto; display: flex; gap: .5rem; }

/* An explanation, not a heading. It earns one line, not three. */
/* An explanation, not a heading. It earns one line, not three. */
.rate-note {
    margin: -.15rem 0 .5rem;
    font-size: .75rem;
    line-height: 1.45;
    color: var(--ink-soft);
    padding: .35rem .55rem;
    background: var(--paper-sunk);
    border-inline-start: 3px solid var(--muted-soft);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}

.field__pair { display: flex; gap: .35rem; }

.run-result {
    margin: .75rem 0;
    padding: .7rem .9rem;
    border: 1px solid #bbf7d0;
    background: #f0fdf4;
    border-radius: 6px;
}
.run-result h3 { margin: 0 0 .4rem; font-size: .9375rem; color: #065f46; }
.run-result h3 small { font-weight: 400; color: #047857; margin-inline-start: .4rem; }
.run-result ul { margin: 0; padding-inline-start: 1.1rem; font-size: .8125rem; color: #14532d; }
.run-result__warn { color: #b45309; }

/* ---- Sign in ----------------------------------------------------------- */

.bare { min-height: 100vh; background: #0f172a; }

.login {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}

.login__card {
    width: 100%;
    max-width: 380px;
    background: #fff;
    border-radius: 10px;
    padding: 1.75rem 1.75rem 2rem;
    box-shadow: 0 12px 40px rgba(0, 0, 0, .35);
}

.login__brand {
    font-size: 1.5rem;
    font-weight: 700;
    color: #0f172a;
    text-align: center;
    margin-bottom: 1.25rem;
}

.login__label {
    display: block;
    font-size: .8125rem;
    font-weight: 600;
    color: #334155;
    margin: .75rem 0 .25rem;
}

.login__input {
    width: 100%;
    padding: .55rem .7rem;
    border: 1px solid #cbd5e1;
    border-radius: 6px;
    font-size: .9375rem;
}

.login__input:focus { outline: 2px solid #3b82f6; outline-offset: -1px; border-color: #3b82f6; }

.login__remember {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin: .9rem 0 .25rem;
    font-size: .8125rem;
    color: #475569;
}

.login__button {
    width: 100%;
    margin-top: 1rem;
    padding: .6rem;
    background: #1d4ed8;
    color: #fff;
    border: none;
    border-radius: 6px;
    font-size: .9375rem;
    font-weight: 600;
    cursor: pointer;
}

.login__button:hover { background: #1e40af; }

.login__error {
    margin-top: .9rem;
    padding: .5rem .7rem;
    background: #fef2f2;
    border-inline-start: 3px solid #dc2626;
    border-radius: 4px;
    color: #991b1b;
    font-size: .8125rem;
}

.validation-message { color: #dc2626; font-size: .75rem; display: block; margin-top: .2rem; }

/* ---- Signed-in user in the sidebar ------------------------------------- */

.nav-shell__user {
    margin-top: auto;
    padding: .75rem 1rem;
    border-top: 1px solid #334155;
    font-size: .8125rem;
}
.nav-shell__user-name { color: #e2e8f0; font-weight: 600; display: block; }
.nav-shell__user-role { color: #64748b; font-size: .75rem; }
.nav-shell__signout { color: #94a3b8; font-size: .75rem; text-decoration: underline; }

/* The license line, above the signed-in user at the foot of the menu. It takes
   the auto margin so it is the block that gets pushed to the bottom, and the
   user block then sits directly beneath it. */
.nav-license {
    margin-top: auto;
    padding: .6rem 1rem;
    border-top: 1px solid #334155;
    font-size: .75rem;
    line-height: 1.5;
    color: #94a3b8;
}
.nav-license + .nav-shell__user { margin-top: 0; }
.nav-license__label { text-transform: uppercase; letter-spacing: .04em; font-size: .6875rem; }
.nav-license__date { display: block; color: #e2e8f0; font-weight: 600; font-size: .8125rem; }
.nav-license__note { display: block; }

/* Inside the last five days, and past the date, it stops being a footnote. */
.nav-license--soon {
    background: #422006;
    border-inline-start: 3px solid #f59e0b;
    color: #fcd34d;
}
.nav-license--soon .nav-license__date { color: #fde68a; }

.nav-license--expired {
    background: #450a0a;
    border-inline-start: 3px solid #ef4444;
    color: #fca5a5;
}
.nav-license--expired .nav-license__date { color: #fecaca; }

.notice {
    max-width: 520px;
    margin: 3rem auto;
    padding: 1.25rem 1.5rem;
    border: 1px solid #fed7aa;
    background: #fffbeb;
    border-radius: 8px;
}
.notice h2 { margin: 0 0 .5rem; font-size: 1.0625rem; color: #92400e; }
.notice p { margin: 0 0 .75rem; font-size: .875rem; color: #78350f; }


/* ---- Users ------------------------------------------------------------- */

.users { display: grid; grid-template-columns: 260px 1fr; gap: 1.25rem; align-items: start; }
@media (max-width: 900px) { .users { grid-template-columns: 1fr; } }

.users__list { border: 1px solid #e2e8f0; border-radius: 6px; overflow: hidden; }

.users__list-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: .5rem .6rem; background: #f8fafc; border-bottom: 1px solid #e2e8f0;
    font-size: .8125rem; color: #475569;
}

.user-row {
    display: block; width: 100%; text-align: start;
    padding: .5rem .7rem; border: none; border-bottom: 1px solid #f1f5f9;
    background: #fff; cursor: pointer;
}
.user-row:hover { background: #f8fafc; }
.user-row--active { background: #eff6ff; border-inline-start: 3px solid #3b82f6; }
.user-row__name { display: block; font-size: .875rem; font-weight: 600; color: #0f172a; }
.user-row__meta { display: block; font-size: .75rem; color: #64748b; margin-top: .1rem; }

.users__detail { border: 1px solid #e2e8f0; border-radius: 6px; padding: 1rem 1.1rem 1.25rem; }
.users__detail-head { display: flex; justify-content: space-between; align-items: start; gap: 1rem; flex-wrap: wrap; }
.users__actions { display: flex; gap: .4rem; }
.users__sub { font-size: .8125rem; color: #64748b; }
.users__empty { color: #64748b; font-size: .875rem; padding: .75rem; }
.users__section { font-size: .8125rem; text-transform: uppercase; letter-spacing: .04em; color: #475569; margin: 1.1rem 0 .4rem; }

.perm-group { margin-bottom: .9rem; }
.perm-group__title { font-size: .8125rem; font-weight: 600; color: #334155; margin-bottom: .3rem; }

.ticks { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: .25rem .75rem; }
.tick { display: flex; align-items: center; gap: .4rem; font-size: .8125rem; color: #334155; cursor: pointer; }

.badge { display: inline-block; padding: .05rem .35rem; border-radius: 3px; font-size: .6875rem; margin-inline-start: .3rem; }
.badge--paused { background: #fee2e2; color: #991b1b; }
.badge--warn { background: #fef3c7; color: #92400e; }

.prompt { padding: .25rem; }
.prompt__text { font-size: .875rem; color: #334155; margin: 0 0 .6rem; }
.prompt__actions { display: flex; gap: .4rem; margin-top: .9rem; }


/* ---- Payment dialog ---------------------------------------------------- */

.pay__bill { border: 1px solid #e2e8f0; border-radius: 6px; padding: .6rem .8rem; margin-bottom: .9rem; background: #f8fafc; }
.pay__row { display: flex; justify-content: space-between; gap: 1rem; font-size: .8125rem; padding: .15rem 0; color: #475569; }
.pay__row strong { color: #0f172a; }
.pay__row--total { border-top: 1px solid #e2e8f0; margin-top: .3rem; padding-top: .4rem; font-size: .9375rem; }

.pay__amounts { display: flex; gap: 1.25rem; flex-wrap: wrap; }
.pay__amount { display: flex; flex-direction: column; gap: .25rem; }
.pay__input { display: flex; align-items: center; gap: .35rem; }

.pay__preview {
    margin: .9rem 0;
    padding: .5rem .7rem;
    border-radius: 4px;
    background: #f1f5f9;
    border-inline-start: 3px solid #94a3b8;
    font-size: .8125rem;
    color: #334155;
}
.pay__preview--settled { background: #f0fdf4; border-inline-start-color: #22c55e; color: #14532d; }

/* Wraps: the advance dialog carries three controls now -- take, reprint and
   cancel -- and on a phone they do not sit on one line. Without this the last
   one is clipped by the edge of the dialog rather than moving under the
   others. */
.pay__actions { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .25rem; }

/* Two buttons that belong together -- take the money, print what was taken --
   held on one line so a narrow dialog wraps whatever follows them instead of
   splitting the pair. */
.pay__actions-pair { display: flex; flex-wrap: nowrap; gap: .5rem; flex: 1 1 auto; }


/* ---- Collections ------------------------------------------------------- */

.collect { width: 100%; border-collapse: collapse; font-size: .875rem; }
.collect th, .collect td { padding: .45rem .6rem; border-bottom: 1px solid #e2e8f0; text-align: start; }
.collect th { background: #f8fafc; font-size: .75rem; text-transform: uppercase; letter-spacing: .04em; color: #475569; }
.collect td.num, .collect th.num { text-align: end; font-variant-numeric: tabular-nums; }
.collect td.actions { text-align: end; white-space: nowrap; }
.collect td.actions button { margin-inline-start: .3rem; }
.collect__zone { background: #f8fafc; color: #475569; }

.stock--none { color: #b91c1c; font-weight: 600; }
.owed { color: #b45309; }

.sale-summary {
    margin: .75rem 0;
    padding: .6rem .8rem;
    background: #f8fafc;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
}

/* ---- The collector's round -------------------------------------------------

   Laid out for a phone held in one hand: full-width tappable rows with a large
   touch target, the client's name first, and the money last on the line. The
   colour language is the Unity app's -- red for a client already dealt with,
   green for money still owed -- so a collector moving over from the phone app
   reads the list the same way.
---------------------------------------------------------------------------- */

.collect { padding: .75rem; max-width: 720px; margin: 0 auto; }

/* layout__main is a flex column, and .page fills it by declaring flex:1 --
   this is the collector shell's equivalent, so a short screen (no zone chosen
   yet, or a short list) reaches the bottom of the viewport instead of leaving
   plain white space under it, and a tall one scrolls itself instead of
   pushing the whole shell taller than the screen.

   Scoped to a direct child of layout__main rather than every .collect, because
   the desktop tables on Collections and PnL share the class name by accident
   -- a table nested inside .page, never a direct child of layout__main. */
.layout__main > .collect { flex: 1; min-height: 0; overflow-y: auto; }

.collector-bar {
    display: flex; flex-wrap: wrap; gap: .75rem; align-items: end;
    padding-bottom: .6rem; margin-bottom: .6rem; border-bottom: 1px solid #e2e8f0;
}

.collector-bar__zone { display: flex; flex-direction: column; gap: .2rem; }
.collector-bar__single { font-size: 1rem; font-weight: 700; color: #31569F; }
.collector-bar__none { font-size: .8125rem; color: #AD000A; }

.collector-bar__tabs { display: flex; gap: .35rem; margin-inline-start: auto; flex-wrap: wrap; }

.collector-tab {
    display: flex; align-items: center; gap: .35rem;
    padding: .5rem .8rem; border-radius: 6px; text-decoration: none;
    background: #f1f5f9; color: #334155; font-size: .875rem; font-weight: 600;
}

.collector-tab:hover { background: #e2e8f0; color: #0f172a; }
.collector-tab.active { background: #31569F; color: #fff; }
.collector-tab__icon { font-size: 1rem; }

.collect__summary {
    display: flex; justify-content: space-between; align-items: baseline;
    font-size: .875rem; color: #475569; margin-bottom: .5rem;
}

.collect__remaining { font-weight: 700; color: #31569F; }

/* Show/hide the reprint-only clients on the Invoices round. A checkbox rather
   than one of the pill buttons the screen already has: this is not another
   view of the round, just fewer or more rows in the one there is. */
.collect__toggle {
    display: flex; align-items: center; gap: .4rem;
    font-size: .8125rem; color: #475569;
    margin-bottom: .5rem;
    cursor: pointer;
}

.collect__search { width: 100%; margin-bottom: .6rem; }

.collect__views { display: flex; gap: .4rem; align-items: center; flex-wrap: wrap; margin-bottom: .75rem; }

.collect__header { display: flex; gap: .75rem; align-items: center; margin-bottom: .75rem; }
.collect__header h3 { margin: 0; font-size: 1.05rem; }

/* The round scrolls inside itself rather than making the page taller than the
   phone. A zone can hold two hundred clients, and dragging the whole screen
   past the zone picker to reach the next name is how a collector loses their
   place. */
.collect__list {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    max-height: min(64vh, 44rem);
    overflow-y: auto;
    overscroll-behavior: contain;

    /* Room for the scrollbar, so it does not sit on top of the amounts. */
    padding-inline-end: .3rem;
}

/* Unless the list is one section of a longer page. The cap above is for the
   round, where the list is the screen and holding it to a screenful is the
   point; inside a card the page already scrolls, and a second scrollbar there
   just hides rows behind a gesture nobody expects to need. */
.card .collect__list {
    max-height: none;
    overflow-y: visible;
}

.collect__list::-webkit-scrollbar { width: 10px; }
.collect__list::-webkit-scrollbar-thumb {
    background: var(--line-firm); border-radius: 5px; border: 2px solid var(--paper);
}
.collect__list::-webkit-scrollbar-thumb:hover { background: var(--muted-soft); }

.collect__row {
    display: grid;
    grid-template-columns: 1fr auto;

    /* Four rows declared, not named. Auto rows with nothing in them collapse to
       no height, so a two-line row looks exactly as it did -- but the last line
       exists, which is what lets the amount span the full height whatever the
       screen puts in column one. Row gap is zero for the same reason: a gap
       between two empty tracks is still a gap. */
    grid-template-rows: repeat(4, auto);
    row-gap: 0;
    column-gap: .75rem;
    width: 100%; text-align: start;
    padding: .65rem .8rem;
    border: 1px solid #e2e8f0; border-radius: 6px; background: #fff;
    font: inherit; cursor: pointer;
}

.collect__row:hover:not(:disabled) { background: #f1f5f9; }
.collect__row--static { cursor: default; }
.collect__row--open { border-color: #31569F; box-shadow: 0 0 0 1px #31569F inset; }

/* Already read this month, or already partly collected: the phone app's red. */
.collect__row--done { background: #fafafa; cursor: not-allowed; }
.collect__row--done .collect__name,
.collect__row--part .collect__name { color: #AD000A; }

/* Named grid areas gave the text side exactly three rows, so a screen that
   grew a fourth line -- the invoices list, once it started showing the box and
   the day -- stacked two of them in the same cell. Auto-placement instead: the
   name and however many detail lines follow it take the rows they need, and
   the amount spans all of them whatever that number turns out to be. */
.collect__name  { grid-column: 1; font-weight: 700; color: #0f172a; }
.collect__meta  { grid-column: 1; font-size: .8125rem; color: #64748b; margin-top: .1rem; }

.collect__value {
    grid-column: 2;

    /* The whole height of the row, so the amount sits beside the middle of the
       detail rather than level with the name. */
    grid-row: 1 / -1;
    align-self: center;
    font-weight: 700; color: #39529A; white-space: nowrap;
}
.collect__value--owed { color: #047857; }

.collect__empty { padding: 1.25rem; text-align: center; color: #64748b; font-size: .875rem; }

.collect__entry { margin-bottom: .75rem; }

.collect__bill {
    display: grid; grid-template-columns: 1fr auto auto; gap: .75rem; align-items: center;
    padding: .65rem .8rem; border: 1px solid #e2e8f0; border-radius: 6px; background: #fff;
}

.collect__bill--next { border-color: #31569F; }
.collect__bill--paid { background: #fafafa; }
.collect__bill-main  { display: flex; flex-direction: column; }
.collect__bill-money { text-align: end; }
.collect__bill-action { display: flex; flex-direction: column; align-items: end; gap: .15rem; }

/* The phone app stamps a paid bill in red; kept, because it is what a collector
   looks for before moving on. */
.collect__paid {
    color: #AD000A; font-weight: 800; letter-spacing: .08em; font-size: .8125rem;
    border: 2px solid #AD000A; border-radius: 4px; padding: 0 .3rem;
}

.hint--bad { color: #b91c1c; }

@media (max-width: 640px) {
    .collector-bar__tabs { margin-inline-start: 0; width: 100%; }
    .collector-tab { flex: 1; justify-content: center; }
    .collect__bill { grid-template-columns: 1fr auto; }
    .collect__bill-action { grid-column: 1 / -1; align-items: stretch; }
}

/* A report link looks like a button but is a real link, so it opens in a new
   tab without a pop-up blocker getting in the way. */
.report-link {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .5rem .9rem; border-radius: 4px; text-decoration: none;
    background: #31569F; color: #fff; font-size: .875rem; font-weight: 600;
}

.report-link:hover { background: #274580; color: #fff; }
.report-link--secondary { background: #475569; }
.report-link--secondary:hover { background: #334155; }
.report-link--off { background: #e2e8f0; color: #94a3b8; cursor: not-allowed; }

/* ---- Printing and the receipt designer ---------------------------------- */

.print { display: inline-flex; gap: .35rem; align-items: center; flex-wrap: wrap; }
.print__ready { font-size: .75rem; color: #047857; font-weight: 600; }
.print__unsupported { font-size: .75rem; color: #b45309; }

.receipt-designer { display: grid; grid-template-columns: minmax(0, 1fr) 420px; gap: 1.25rem; align-items: start; }
.receipt-designer__preview { position: sticky; top: 1rem; }

/* The preview is monospace and fixed to the paper width, because that is the
   only honest way to show what a 32-character line looks like. */
.receipt-paper {
    background: #fff; border: 1px solid #cbd5e1; border-radius: 4px;
    padding: .9rem .8rem; box-shadow: 0 1px 3px rgba(15, 23, 42, .08);
    overflow-x: auto;
}

/* Wrapped, not clipped, and at exactly the paper's width: a line too long for
   32 characters is one the printer wraps, and the owner needs to see that
   happen rather than see it cut off. */
.receipt-paper pre {
    margin: 0; font-family: "Consolas", "Courier New", monospace;
    font-size: .75rem; line-height: 1.35; color: #0f172a;
    white-space: pre;
}

/* One line, always. A line is never allowed to wrap here, because the paper
   will not wrap it either -- one that runs wide is set a size smaller instead,
   which is what the printer does with it. */
.receipt-paper__line { display: block; white-space: pre; }

.receipt-paper--57 pre { width: 32ch; }
.receipt-paper--80 pre { width: 48ch; }

.receipt-field {
    display: grid;
    grid-template-columns: 32px minmax(140px, 1.2fr) minmax(120px, 1fr) minmax(120px, 1fr) 80px;
    gap: .5rem; align-items: center;
    padding: .35rem 0; border-bottom: 1px solid #f1f5f9;
}

.receipt-field:last-child { border-bottom: none; }
.receipt-field--off { opacity: .45; }
.receipt-field__key { display: flex; flex-direction: column; font-size: .8125rem; }
.receipt-field__key small { color: #64748b; font-size: .7rem; }
.receipt-field__label--none { font-size: .75rem; color: #94a3b8; font-style: italic; }
.receipt-field__move { display: flex; gap: .2rem; justify-content: end; }

@media (max-width: 1100px) {
    .receipt-designer { grid-template-columns: 1fr; }
    .receipt-designer__preview { position: static; }
    .receipt-field { grid-template-columns: 32px 1fr 1fr; }
    .receipt-field__move { grid-column: 1 / -1; justify-content: start; }
}

/* ---- The platform portal ------------------------------------------------
   Deliberately a different colour from the tenant application. You are holding
   somebody's live database open in here, and it should never be mistaken for
   the app a business signs into.
-------------------------------------------------------------------------- */

.platform__sidebar { background: #422006; }
.platform__sidebar .nav-shell__brand { border-bottom-color: #713f12; }
.platform__sidebar .nav-item:hover { background: #713f12; }
.platform__sidebar .nav-item.active { background: #a16207; border-inline-start-color: #fde047; }
.platform__sidebar .nav-shell__user { border-top-color: #713f12; }

.platform-form { max-width: 980px; }

.login__card--platform { border-top: 4px solid #a16207; }
.login__brand-tag { font-size: .75rem; color: #a16207; font-weight: 600; text-transform: uppercase; }
.login__aside { display: block; margin-top: 1rem; font-size: .8125rem; color: #64748b; text-align: center; }

/* The platform's tenant rows reuse the collector row's classes and carried the
   same three-row template, which had the same fault: this row has three detail
   lines and the template only named two places to put them. Auto-placement
   handles any number, so there is nothing left to declare here. */
.tenant-problem { color: #b91c1c; font-weight: 600; }

.tenant-badge {
    display: inline-block; margin-inline-start: .4rem; padding: 0 .35rem;
    border-radius: 3px; font-size: .6875rem; font-weight: 700; text-transform: uppercase;
}

.tenant-badge--bad { background: #fee2e2; color: #b91c1c; }

/* A database behind the deployed application: not broken yet, but it will be
   the moment somebody opens a screen reading a column it does not have. Amber
   rather than red -- one button fixes it. */
.tenant-badge--warn { background: #fef3c7; color: #92400e; }
.expiry--grace { color: #b45309; }

.run-result--failed { background: #fef2f2; border-color: #fecaca; }
.run-result--failed h3 { color: #b91c1c; }
.run-result--failed ul { color: #7f1d1d; }

/* ---- Messages ----------------------------------------------------------- */

.message__meta { margin-top: .4rem; }

.message__preview {
    background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px;
    padding: .8rem; white-space: pre-wrap; font-size: .875rem; color: #0f172a;
    max-width: 46rem;
}

.placeholders { margin-top: .75rem; }
.placeholders summary { cursor: pointer; font-size: .8125rem; color: #475569; font-weight: 600; }
.placeholders__list { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .5rem; }

.placeholder {
    border: 1px solid #cbd5e1; border-radius: 4px; background: #fff;
    padding: .15rem .4rem; font-family: "Consolas", monospace; font-size: .75rem;
    color: #31569F; cursor: pointer;
}

.placeholder:hover { background: #eef2ff; border-color: #31569F; }

/* ---- Language and direction ---------------------------------------------
   The layout is built from logical properties throughout, so putting dir="rtl"
   on the document mirrors it: the sidebar moves to the right, the grids read
   right to left, and the nav indicator flips with them. The Unity app only ever
   right-aligned its text, which left every screen still laid out left to right.
-------------------------------------------------------------------------- */

/* English first, العربية second, in both languages.

   Without the fixed direction the row mirrors with the page, so the two names
   trade places the moment one is clicked -- and the button you just pressed
   ends up where the other one was. Whichever language you are in, you reach
   for the same side of the pair to get back. */
.lang { display: flex; gap: .25rem; margin-top: .5rem; direction: ltr; }

/* The pair still sits against the edge its panel is aligned to; it is the
   order inside it that holds still. The login card centres it instead, which
   is set further down and wins on source order. */
[dir="rtl"] .lang { justify-content: flex-end; }

.lang__option {
    padding: .1rem .45rem; border-radius: 3px; text-decoration: none;
    font-size: .75rem; color: #94a3b8; border: 1px solid transparent;
}

.lang__option:hover { color: #fff; border-color: #475569; }
.lang__option--on { color: #fff; background: #334155; border-color: #475569; }

/* Arabic needs a face that has the glyphs; the Latin fallbacks stay for the
   numbers, which are deliberately Latin in both languages.

   Not an icon, though. Radzen draws some of its icons as a button whose
   ::before is a ligature name -- the dropdown's clear button is
   `<button class="rzi rzi-times">` with the content "close" -- and the icon
   font turns that word into the glyph. `[dir="rtl"] button` outranks Radzen's
   own `.rzi` rule, so without this the text face won at exactly those spots,
   the ligature never formed, and an Arabic user read the word "close" where
   the little ✕ belongs. It showed up on the month pickers because they are the
   ones offering to be cleared. */
[dir="rtl"] body,
[dir="rtl"] input,
[dir="rtl"] button:not(.rzi),
[dir="rtl"] textarea {
    font-family: "Segoe UI", Tahoma, "Noto Naskh Arabic", "Arial", sans-serif;
}

/* Money and counters stay left-to-right inside a right-to-left line, so a
   figure like "45.00 $" does not come out as "$ 45.00" reversed. */
[dir="rtl"] .collect__value,
[dir="rtl"] .tile__value,
[dir="rtl"] .power-grid td[data-align="end"] {
    direction: ltr;
    unicode-bidi: isolate;
}


/* The switch also sits on the sign-in card, so the language can be chosen
   before reading anything. */
.login__card .lang { justify-content: center; margin-top: .9rem; }
.login__card .lang__option { color: #64748b; }
.login__card .lang__option:hover { color: #0f172a; border-color: #cbd5e1; }
.login__card .lang__option--on { background: #e2e8f0; color: #0f172a; border-color: #cbd5e1; }

/* ---- Assigning zones to a collector --------------------------------------
   Two lists, as the desktop had them: available on one side, assigned on the
   other. A single click moves a zone across. The desktop needed a double click,
   which is not something anybody discovers without being told.
-------------------------------------------------------------------------- */

.zones__search { width: 100%; max-width: 22rem; margin-bottom: .6rem; }

.zones {
    display: grid; grid-template-columns: 1fr 1fr; gap: .75rem;
    margin-bottom: .5rem;
}

.zones__panel {
    border: 1px solid #e2e8f0; border-radius: 6px; background: #fff;
    display: flex; flex-direction: column; min-height: 8rem;
}

.zones__panel--assigned { border-color: #bfdbfe; background: #f8fbff; }

.zones__head {
    display: flex; align-items: center; justify-content: space-between; gap: .5rem;
    padding: .4rem .6rem; border-bottom: 1px solid #e2e8f0;
    font-size: .75rem; font-weight: 700; color: #475569;
    text-transform: uppercase; letter-spacing: .04em;
}

.zones__list { padding: .5rem; display: flex; flex-direction: column; gap: .3rem; max-height: 18rem; overflow-y: auto; }
.zones__empty { margin: .4rem; font-size: .8125rem; color: #94a3b8; }

.zone-chip {
    display: flex; align-items: center; gap: .5rem; width: 100%;
    padding: .4rem .6rem; border: 1px solid #e2e8f0; border-radius: 4px;
    background: #fff; font: inherit; font-size: .875rem; color: #0f172a;
    cursor: pointer; text-align: start;
}

.zone-chip:hover { background: #eef2ff; border-color: #31569F; }
.zone-chip--on { background: #fff; border-color: #bfdbfe; }
.zone-chip--on:hover { background: #fee2e2; border-color: #b91c1c; }

.zone-chip__name { flex: 1; font-weight: 600; }

/* The number of clients in the zone. A list of names says nothing about how
   big a round is; two zones can be forty clients or seven hundred. */
.zone-chip__count {
    font-size: .75rem; color: #64748b; font-variant-numeric: tabular-nums;
    direction: ltr; unicode-bidi: isolate;
}

.zone-chip__move { font-weight: 700; color: #94a3b8; width: 1rem; text-align: center; }
.zone-chip:hover .zone-chip__move { color: #31569F; }
.zone-chip--on:hover .zone-chip__move { color: #b91c1c; }

.user-row__zones {
    display: block; font-size: .6875rem; color: #64748b; margin-top: .1rem;
}

@media (max-width: 720px) {
    .zones { grid-template-columns: 1fr; }
}

/* A value the screen shows but does not let you change. Sized like the input it
   replaces so the row does not jump about, and plainly not a box you can type
   in. */
.field__readonly {
    display: inline-flex; align-items: center; min-height: 2.25rem;
    padding: 0 .1rem; font-size: .875rem; font-weight: 600; color: #334155;
    font-variant-numeric: tabular-nums;
    direction: ltr; unicode-bidi: isolate;
}

[dir="rtl"] .field__readonly { justify-content: flex-end; }

/* The logo picker on Settings: the stored image, the file input and the way to
   clear it, on one line. */
.logo-setting { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; }
.logo-setting__preview {
    max-height: 44px;
    max-width: 180px;
    border: 1px solid var(--line, #cbd5e1);
    background: #fff;
    padding: .25rem;
}
.logo-setting__none { color: #64748b; font-size: .8125rem; font-style: italic; }

/* ---- Interaction --------------------------------------------------------
   Every control says when it has the keyboard and when it has been pressed.
   :focus-visible rather than :focus, so a mouse click does not leave a ring
   behind it while a Tab does.
-------------------------------------------------------------------------- */

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}

.layout__sidebar :focus-visible { outline-color: #93c5fd; }

.nav-item, .report-link, .collector-tab {
    transition: background var(--ease), color var(--ease), transform var(--ease);
}

.report-link:active, .collector-tab:active { transform: translateY(1px); }

/* ---- Small screens ------------------------------------------------------
   The screens themselves are unchanged; they are given room to stand in one
   column instead of being squeezed into the desk's.
-------------------------------------------------------------------------- */

@media (max-width: 900px) {
    /* On a small screen the toolbar above a grid can be taller than the screen
       itself, and a page that refuses to scroll would put its own controls out
       of reach. Here the page scrolls again and the grid goes back to being as
       tall as it wants to be -- scrolling is the right answer when there is
       genuinely not enough room. */
    .page:has(.power-grid),
    .page:has(> .collect__list) { overflow-y: auto; display: block; }

    .page:has(.power-grid) > .power-grid { flex: none; }

    .power-grid .rz-data-grid-data { max-height: 60vh; }

    .page { padding: .75rem .8rem 2rem; }

    .page__header { flex-wrap: wrap; gap: .35rem; }
    .page__header h2 { font-size: 1.125rem; }

    /* A toolbar of fixed-width Radzen controls is the one thing that will not
       fit: each field takes the row it needs rather than overflowing. */
    .toolbar-row { gap: .5rem; }
    .toolbar-row > .field { flex: 1 1 min(100%, 14rem); }
    .field > .rz-dropdown,
    .field > .rz-textbox,
    .field > .rz-numeric,
    .field > .rz-datepicker { width: 100% !important; }

    .power-grid__search { min-width: 0; width: 100%; }

    /* The rows keep their own scrollbar, but on a short screen the ceiling
       should be the screen and not a fixed 42rem. */
    .power-grid .rz-data-grid-data { max-height: min(58vh, 30rem); }

    .tiles { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: .5rem; }
    .tile__value { font-size: 1.25rem; }

    .card { padding: .75rem .8rem .9rem; }

    .settings, .platform-form { max-width: none; }

    .setting { flex-wrap: wrap; }

    /* A dialog pinned to a desktop width is a dialog with its buttons off the
       side of the phone. */
    .rz-dialog { max-width: 96vw !important; }
}

@media (max-width: 560px) {
    .pay__actions, .print { width: 100%; }

    /* Except a print control standing among other buttons. Full width there
       makes it take a row to itself and pushes everything after it onto rows
       of their own -- three stacked buttons in the advance dialog where two
       of them fit side by side. */
    .pay__actions .print { width: auto; flex: 0 0 auto; }

    /* On a phone the pair takes the whole row and shares it evenly, which is
       what puts Cancel underneath rather than beside them. */
    .pay__actions-pair { flex: 1 1 100%; }
    .pay__actions-pair > * { flex: 1 1 0; min-width: 0; }
    .pay__actions-pair .rz-button { width: 100%; }

    /* The print control is told above to keep its natural width among loose
       buttons. Inside the pair it is half of a pair, so it shares instead. */
    .pay__actions .pay__actions-pair .print { flex: 1 1 0; }

    .pay__actions .report-link { flex: 1 1 100%; justify-content: center; }
    .collect__summary { flex-wrap: wrap; gap: .35rem; }
}

/* ---- The renewal notice -------------------------------------------------
   Shown once, on arrival, when the license is nearly out. Its own element
   rather than a Radzen dialog: it has to be able to appear over a statically
   rendered shell, before anything else on the screen has a circuit.
-------------------------------------------------------------------------- */

.license-modal {
    position: fixed;
    inset: 0;
    z-index: 1200;
    display: grid;
    place-items: center;
    padding: 1rem;
    background: rgba(15, 23, 42, .55);
}

.license-modal__card {
    width: min(30rem, 100%);
    background: var(--paper);
    border-radius: 10px;
    box-shadow: var(--shadow-up);
    padding: 1.5rem 1.5rem 1.25rem;
    text-align: center;
}

.license-modal__mark {
    font-size: 1.75rem;
    line-height: 1;
    width: 3.25rem;
    height: 3.25rem;
    margin: 0 auto .9rem;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: #fef3c7;
}

.license-modal__mark--over { background: #fee2e2; color: var(--bad); font-weight: 800; }

.license-modal__title {
    margin: 0 0 .5rem;
    font-size: 1.125rem;
    font-weight: 700;
    color: var(--ink);
    text-wrap: balance;
}

.license-modal__body { margin: 0 0 .4rem; color: var(--ink-soft); font-size: .9375rem; }
.license-modal__body--muted { color: var(--muted); font-size: .875rem; }

.license-modal__actions { margin-top: 1.1rem; }

/* The bill lines inside the payment dialog: what the bill is made of, set
   apart from who it is for and what is due. */
.pay__bill--lines { background: var(--paper-sunk); }

.pay__settled { display: flex; align-items: center; gap: .6rem; margin: .25rem 0 0; }

/* A bill row in the collector's list is now a control in its own right. */
.collect__bill { cursor: pointer; transition: background var(--ease), border-color var(--ease); }
.collect__bill:hover { background: var(--paper-hover); }
.collect__bill:active { transform: translateY(1px); }

/* A payment worth more than the bill: refused, and said so in the preview. */
.pay__preview-bad { color: var(--bad); }

/* What a client is holding in advance, in the header of their bill list. */
/* Label over amount, against the far edge -- the same edge the amounts in the
   list underneath sit on, so the eye reads one column of money rather than
   two. */
.collect__advance {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    line-height: 1.2;
    margin-inline-start: auto;
    text-align: end;
}
.collect__advance-label { font-size: .75rem; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.collect__advance-value { color: var(--good); font-variant-numeric: tabular-nums; }

/* The send counts, on one line instead of four tiles. Four numbers do not need
   four boxes the height of a paragraph each. */
.send-strip {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .4rem .9rem;
    margin-top: .5rem;
    padding: .45rem .6rem;
    background: var(--paper-sunk);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    font-size: .8125rem;
    color: var(--muted);
}

.send-strip__item { display: inline-flex; align-items: baseline; gap: .3rem; }
.send-strip__item strong { color: var(--ink); font-size: .9375rem; font-variant-numeric: tabular-nums; }
.send-strip__item small { color: var(--muted-soft); font-size: .6875rem; }

.send-strip__item--warn strong { color: var(--warn); }
.send-strip__item--bad  strong { color: var(--bad); }
.send-strip__item--good strong { color: var(--good); }

/* The Messages screen is a send list with a note attached, not a form: its
   cards give up their padding so the list of people has room. */
.page:has(.send-strip) .card { padding: .6rem .8rem .7rem; margin-bottom: .5rem; }
.page:has(.send-strip) .card__title { margin-bottom: .4rem; }

.message__meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: .4rem .9rem;
    margin-top: .35rem;
}

.message__templates { display: inline-flex; align-items: center; gap: .5rem; }
.message__template { display: inline-flex; align-items: center; gap: .2rem; }

/* Who it goes to, and what they get, side by side. Stacks when there is not
   room for two columns of controls. */
.messages__top { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: .75rem; }
.messages__top > .card { margin-bottom: .5rem; }

@media (max-width: 1100px) {
    .messages__top { grid-template-columns: minmax(0, 1fr); gap: 0; }
}

/* The filter box a dynamic column brings with it, matched to Radzen's own. */
.power-grid__column-filter {
    width: 100%;
    padding: .3rem .4rem;
    border: 1px solid var(--line-firm);
    border-radius: var(--radius-sm);
    font: inherit;
    font-size: .8125rem;
    background: var(--paper);
    color: var(--ink);
}

.power-grid__column-filter:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/*  A column the grid sorts itself owns its header, so the header is a button.
    It has to read as a heading, not as a control: same weight and colour as
    every other, with the arrow only once it is the one being sorted by.  */
.power-grid__sort {
    display: flex;
    align-items: center;
    gap: .35rem;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: start;
    cursor: pointer;
}

.power-grid__sort:hover { color: var(--accent); }
.power-grid__sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.power-grid__sort-mark {
    font-size: .625rem;
    color: var(--accent);
}

/* ---- New client / edit client ------------------------------------------ */

/* A field that takes what is left of its row. Notes uses it, so a hundred
   characters of it sit beside Creds 2 instead of on a line of their own. */
.field--grow { flex: 1; min-width: 220px; }

/* Saving stands in the empty cell of the service grid, at its top, so the
   button lines up with the cards beside it rather than floating mid-cell. */
.client-form__finish {
    display: flex;
    flex-direction: column;
    gap: .6rem;
    align-self: start;
}

.client-form__finish .hint { margin: 0; }

/* A note that rides along in a toolbar row, taking whatever the controls left.
   Used where a screen has one standing explanation and a grid that wants the
   vertical space more than the note does. */
.toolbar-row__aside {
    flex: 1 1 260px;
    max-width: 68ch;
    margin: 0;
    margin-inline-start: auto;
    align-self: center;
}

@media (max-width: 900px) {
    /* Under the controls again once it would squeeze them. */
    .toolbar-row__aside { flex-basis: 100%; margin-inline-start: 0; }
}

/* A filter cell that holds a box and a tick list, for the columns the grid
   builds itself. Radzen cannot resolve those, so its own filter menu never
   appears on them and this stands in for it. */
.power-grid__filter { position: relative; display: flex; gap: .2rem; align-items: center; }

.power-grid__filter-toggle {
    flex: none;
    width: 1.4rem; height: 1.7rem;
    padding: 0;
    border: 1px solid var(--line-firm);
    border-radius: var(--radius-sm);
    background: var(--paper);
    color: var(--muted);
    font-size: .7rem;
    line-height: 1;
    cursor: pointer;
}

.power-grid__filter-toggle:hover,
.power-grid__filter-toggle.is-open { background: var(--accent-wash); color: var(--accent); }

.power-grid__checklist {
    position: absolute;
    inset-block-start: calc(100% + .25rem);
    inset-inline-start: 0;
    z-index: 20;
    min-width: 12rem;
    max-height: 16rem;
    overflow-y: auto;
    padding: .35rem;
    background: var(--paper);
    border: 1px solid var(--line-firm);
    border-radius: var(--radius);
    box-shadow: var(--shadow-up);
}

.power-grid__checklist-item {
    display: flex; align-items: center; gap: .4rem;
    padding: .2rem .3rem;
    font-size: .8125rem;
    font-weight: 400;
    white-space: nowrap;
    cursor: pointer;
}

.power-grid__checklist-item:hover { background: var(--paper-hover); }

.power-grid__checklist-all {
    display: block; width: 100%;
    margin-bottom: .25rem; padding: .25rem;
    border: 0; border-bottom: 1px solid var(--line);
    background: none;
    color: var(--accent);
    font: inherit; font-size: .75rem;
    text-align: start;
    cursor: pointer;
}

/* The tick list hangs out of a filter cell that is forty pixels tall and clipped
   on both axes, so every ancestor between it and the grid has to stop clipping
   while it is open -- and only while it is open, or the header would spill
   sideways as the grid scrolls. */
.power-grid:has(.power-grid__checklist) :is(
    thead, thead tr, thead th,
    .rz-unselectable-text,
    .rz-datatable-scrollable-header,
    .rz-datatable-scrollable-header-box,
    .rz-cell-filter,
    .rz-cell-filter-content
) { overflow: visible; }

/* ---- Bill Viewer -------------------------------------------------------
   The bills as a record to read: the two month pickers and the grid, and
   nothing that acts on a row. Everything else it needs -- the grid owning the
   scroll, the drawer instead of the sidebar -- it already gets from .page and
   the shell, so this is only about the pickers on a narrow screen.
------------------------------------------------------------------------- */
.viewer-month { width: 180px; }

/* On a phone two 180px boxes leave a ragged half-column of nothing beside
   them. One picker to a line, each as wide as the screen, is both easier to
   hit and quieter to look at. */
@media (max-width: 560px) {
    .page--viewer .toolbar-row .field { flex: 1 1 100%; }
    .viewer-month { width: 100%; }

    /* "Drag a column header here and drop it to group by that column" is 57px
       of advice nobody can take with a thumb. On a screen whose whole job is
       reading, that is 57px better spent on rows. */
    .page--viewer .rz-group-header { display: none; }
}

/* ---- The brand mark ----------------------------------------------------
   The same image the browser tab uses, set before the product name wherever
   the name is written: the login card, the sidebar, and the phone's top bar.

   The mark stays to the LEFT of the word in both languages. "PowerLink" is a
   Latin name and the two together read as one mark, so the pair does not
   mirror the way the Arabic line around it does -- only its alignment moves,
   to the edge the rest of that panel is aligned to.

   Sized in em rather than pixels, so it follows whatever text it sits beside
   -- 1.5rem on the login card, 1.1rem in the sidebar, 1rem on the top bar --
   instead of needing a size of its own in each place.
------------------------------------------------------------------------- */
.brand-lockup {
    display: flex;
    align-items: center;

    /* What keeps the mark on the left when the page is Arabic. */
    direction: ltr;
}

/* The panel still aligns the way its language does, even though the pair
   inside it does not turn around. */
[dir="rtl"] .brand-lockup { justify-content: flex-end; }

/* The login card centres its brand line in either language. Written after the
   rule above so it wins the tie between two selectors of equal weight. */
.login__brand.brand-lockup { justify-content: center; }

.brand-mark {
    height: 1.5em;
    width: auto;
    margin-inline-end: .45rem;
}

/* ---- The advance just taken --------------------------------------------
   Its own line under the client, not in the header. The header already holds
   a back button, a name, an address and a balance; adding a print control to
   it pushed the row off the side of the phone and clipped the button next to
   it. Wraps, because on 375px a label, an amount and two buttons do not sit
   on one line and should not be asked to.
------------------------------------------------------------------------- */
.advance-taken {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .4rem .7rem;
    margin-bottom: .5rem;
    padding: .5rem .7rem;
    background: #ecfdf5;
    border-inline-start: 3px solid #047857;
    border-radius: var(--radius);
}

.advance-taken__label {
    font-size: .75rem; color: var(--muted);
    text-transform: uppercase; letter-spacing: .04em;
}

.advance-taken__value { color: #047857; font-variant-numeric: tabular-nums; }

/* The header wraps too. It was a single no-wrap row, so a long Arabic name
   and a balance beside it squeezed the buttons until their labels clipped. */
.collect__header { flex-wrap: wrap; }
/* Centred between the back button and the balance, deliberately: it is the
   heading of the screen rather than a label hanging off the button beside it,
   and centring is what stops a long Arabic name reading as though it belongs
   to whichever edge it happened to touch. */
.collect__who { flex: 1 1 auto; min-width: 0; text-align: center; }
.collect__advance { flex-wrap: wrap; }

/* What can be done to the client, under who they are: the printer for the
   round at one end and taking money at the other. Both were in the heading and
   between them they pushed it to three lines on a phone. */
.collect__client-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    margin-bottom: .6rem;
}
