/* Octopus Manager — the design system.
 *
 * This is NOT Tabler. /admin keeps Tabler and wwwroot/css/theme.css; /manager
 * loads this file and nothing else. The two share wwwroot, so every bare element
 * selector here is scoped under `.mgr` (which sits on <body>) and can never leak
 * into an admin page. See OCTOPUS_MANAGER_PLAN.md §D8 and §5.
 *
 * The palette, the glass panels, the type scale and the two breakpoints were
 * carried over from the Flutter owner dashboard this portal replaced, so the
 * product did not change its face when it changed its technology. That app was
 * deleted on 2026-09-18; THIS FILE is now the source of truth for every value
 * in it. (The old code is reachable at tag `dashboards-retired-2026-09-18`.)
 */

/* ===========================================================================
   1. Fonts — served from wwwroot/fonts, never from a CDN.

   🚨 A production page must not depend on fetching a font at runtime. The
   Flutter dashboard learned this the hard way: Arabic drew as empty boxes
   wherever fonts.gstatic.com was slow or blocked. Nunito is the VARIABLE
   upstream file, so one download covers every weight 200–1000.
   =========================================================================== */

@font-face {
    font-family: 'Nunito';
    src: url('../fonts/Nunito.ttf') format('truetype-variations');
    font-weight: 200 1000;
    font-style: normal;
    font-display: swap;
}

/* Nunito has no Arabic. Naskh is listed as a FALLBACK family rather than
   swapped in by language, so a page mixing Arabic labels with Latin product
   codes gets the right glyphs for both without markup. */
@font-face {
    font-family: 'NotoNaskhArabic';
    src: url('../fonts/NotoNaskhArabic-Regular.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'NotoNaskhArabic';
    src: url('../fonts/NotoNaskhArabic-Bold.ttf') format('truetype');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

/* ===========================================================================
   2. Tokens
   =========================================================================== */

:root {
    /* ---- Dark palette (the DEFAULT — this is a dark-mode-first design, and
       it does NOT follow the OS preference, exactly as the Flutter app does
       not). Values are AppPalette.dark, as rgb triplets so alpha can be
       composed with rgb(... / a) without relative-colour support. ---- */
    --c-accent: #389DCB; /* 6.85:1 on black — carries the money unaided */
    --c-accent-rgb: 56 157 203;
    --c-indigo: #7986CB;
    --c-indigo-rgb: 121 134 203;
    --c-positive: #4ADE80;
    --c-positive-rgb: 74 222 128;
    --c-negative: #FF6B6B;
    --c-negative-rgb: 255 107 107;
    --c-warning: #FFB020;
    --c-warning-rgb: 255 176 32;
    --c-neutral: #8E8E93;
    --c-neutral-rgb: 142 142 147;
    --c-text: #FFFFFF;
    --c-text-rgb: 255 255 255;
    --c-base: #000000;
    --c-on-accent: #041019; /* dark ink on the accent fill */
    /* Glass tint multiplier. Black at a given alpha reads far heavier than
       white does, so the light theme lands on the same perceived weight with a
       much smaller number — GlassCard applies exactly this 0.4 factor. */
    --glass-mult: 1;
    --a-border: 0.20;
    /* ---- Radii — AppTheme.cardRadius / sheetRadius / controlRadius ---- */
    --r-card: 24px;
    --r-sheet: 28px;
    --r-control: 12px;
    /* ---- Type scale — AppText's named roles ---- */
    --fs-hero: 38px;
    --fs-app-title: 32px;
    --fs-title: 22px;
    --fs-headline: 17px;
    --fs-body: 15px;
    --fs-label: 13px;
    --fs-caption: 12px;
    /* ---- Layout caps — Layout.* ---- */
    --w-content: 1100px;
    --w-form: 460px;
    --w-dialog: 520px;
    --w-sidebar: 248px;
    --w-drawer: 288px;
    --w-rail: 88px;
    --w-rail-open: 248px;
    /* ---- Glass transparency. Overwritten inline on <html> by the pre-paint
       boot script in manager.js from localStorage["glassTransparency"].
       AppSettings: 0.05–0.50, default 0.20. ---- */
    --glass-alpha: 0.20;
}

:root[data-theme="light"] {
    /* AppPalette.light. The accent is NOT the same blue: the brand seed
       measures 3.06:1 on white and fails AA, and this dashboard sets money in
       the accent colour, so light mode uses the darkened 4.67:1 variant. */
    --c-accent: #2A7CA1;
    --c-accent-rgb: 42 124 161;
    --c-indigo: #3F51B5;
    --c-indigo-rgb: 63 81 181;
    --c-positive: #1E8E3E;
    --c-positive-rgb: 30 142 62;
    --c-negative: #D93025;
    --c-negative-rgb: 217 48 37;
    --c-warning: #E07C00;
    --c-warning-rgb: 224 124 0;
    --c-neutral: #6E6E73;
    --c-neutral-rgb: 110 110 115;
    --c-text: #000000;
    --c-text-rgb: 0 0 0;
    --c-base: #FFFFFF;
    --c-on-accent: #FFFFFF;
    --glass-mult: 0.4;
    --a-border: 0.12;
}

/* ===========================================================================
   3. Reset — scoped, so none of it reaches an /admin page
   =========================================================================== */

.mgr *,
.mgr *::before,
.mgr *::after {
    box-sizing: border-box;
}

.mgr {
    margin: 0;
    min-height: 100vh;
    background: var(--c-base);
    color: var(--c-text);
    font-family: 'Nunito', 'NotoNaskhArabic', system-ui, -apple-system, 'Segoe UI', sans-serif;
    font-size: var(--fs-body);
    font-weight: 400;
    line-height: 1.45;
    /* Stop iOS re-flowing text at its own scale in landscape. */
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
    -webkit-font-smoothing: antialiased;
}

/* No sideways scroll, ever. `clip` rather than `hidden` because `hidden` turns
   the element into a scroll container, which breaks position:sticky inside. */
html:has(> .mgr) {
    overflow-x: clip;
    background: var(--c-base);
}

.mgr img,
.mgr svg {
    max-width: 100%;
}

.mgr h1, .mgr h2, .mgr h3, .mgr h4, .mgr p {
    margin: 0;
}

.mgr a {
    color: var(--c-accent);
    text-decoration: none;
}

.mgr button {
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.mgr input,
.mgr select,
.mgr textarea {
    font: inherit;
    color: inherit;
}

/* Long unbroken values — emails, tax numbers, barcodes — wrap instead of
   widening their container. */
.break-anywhere {
    overflow-wrap: anywhere;
}

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

/* ===========================================================================
   4. Type roles — AppText, one class each
   =========================================================================== */

.t-hero {
    font-size: var(--fs-hero);
    font-weight: 800;
    letter-spacing: -0.5px;
    line-height: 1.1;
}

.t-app-title {
    font-size: var(--fs-app-title);
    font-weight: 800;
    line-height: 1.15;
}

.t-title {
    font-size: var(--fs-title);
    font-weight: 700;
    line-height: 1.25;
}

.t-headline {
    font-size: var(--fs-headline);
    font-weight: 700;
}

.t-eyebrow {
    font-size: var(--fs-caption);
    font-weight: 700;
    letter-spacing: 1.2px;
    text-transform: uppercase;
}

.t-body {
    font-size: var(--fs-body);
    font-weight: 400;
}

.t-body-strong {
    font-size: var(--fs-body);
    font-weight: 600;
}

.t-label {
    font-size: var(--fs-label);
    font-weight: 600;
}

.t-caption {
    font-size: var(--fs-caption);
    font-weight: 400;
}

/* `dim` in the Flutter palette: the primary text colour at reduced opacity,
   never a separate grey — so it flips with the theme for free. */
.dim { color: rgb(var(--c-text-rgb) / 0.65); }
.dim-55 { color: rgb(var(--c-text-rgb) / 0.55); }
.dim-70 { color: rgb(var(--c-text-rgb) / 0.70); }
.accent { color: var(--c-accent); }
.indigo { color: var(--c-indigo); }
.negative { color: var(--c-negative); }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ===========================================================================
   5. Glass — GlassCard and GlassPill

   Blur is deliberately NOT applied to content cards. BackdropFilter blurs what
   is painted behind it, and the page background here is a flat black or white:
   blurring a flat colour costs a full compositing pass and changes not one
   pixel. Blur is kept for surfaces that float OVER scrolling content — the
   drawer and dialogs (.glass-overlay).
   =========================================================================== */

.glass {
    --a-fill: calc(var(--glass-alpha) * var(--glass-mult));
    background-color: rgb(var(--c-text-rgb) / var(--a-fill));
    border: 1px solid rgb(var(--c-text-rgb) / var(--a-border));
    border-radius: var(--r-card);
    padding: 20px;
}

/* Reduce-transparency mode: pre-blend the tint onto the opaque base so nothing
   shows through and no compositing layer is needed. A translucent gradient over
   an opaque background-color is exactly Color.alphaBlend. */
:root[data-glass="off"] .glass,
:root[data-glass="off"] .pill,
:root[data-glass="off"] .glass-overlay {
    background-color: var(--c-base);
    background-image: linear-gradient(rgb(var(--c-text-rgb) / var(--a-fill)), rgb(var(--c-text-rgb) / var(--a-fill)));
    backdrop-filter: none;
}

.glass-overlay {
    --a-fill: calc(var(--glass-alpha) * var(--glass-mult));
    background-color: rgb(var(--c-text-rgb) / var(--a-fill));
    border: 1px solid rgb(var(--c-text-rgb) / var(--a-border));
    border-radius: var(--r-sheet);
    backdrop-filter: blur(24px);
    -webkit-backdrop-filter: blur(24px);
}

/* Pill-shaped translucent chrome — the "Filter Date" and refresh controls.
   Never smaller than a fingertip: 46px inside its 1px border, so it is as easy
   to hit on a tablet as a full button. */
.pill {
    --a-fill: calc(var(--glass-alpha) * var(--glass-mult) * 0.7);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    min-height: 48px;
    min-width: 48px;
    padding: 9px 16px;
    border: 1px solid rgb(var(--c-text-rgb) / calc(var(--a-border) * 0.8));
    border-radius: 999px;
    background-color: rgb(var(--c-text-rgb) / var(--a-fill));
    color: var(--c-text);
    font-size: var(--fs-label);
    font-weight: 600;
    transition: background-color .12s ease;
}

.pill:hover {
    background-color: rgb(var(--c-text-rgb) / calc(var(--a-fill) + 0.06));
}

.pill-icon {
    padding: 13px; /* 20px icon + 13 each side + the border = a 48px target */
}

/* ===========================================================================
   6. Controls
   =========================================================================== */

.field-label {
    display: block;
    margin-bottom: 8px;
    font-size: var(--fs-caption);
    font-weight: 600;
    color: rgb(var(--c-text-rgb) / 0.8);
}

.input {
    display: block;
    width: 100%;
    min-height: 48px; /* finger-sized on a tablet */
    padding: 13px 14px;
    border: 1px solid rgb(var(--c-text-rgb) / var(--a-border));
    border-radius: var(--r-control);
    background-color: rgb(var(--c-text-rgb) / calc(0.06 * var(--glass-mult) + 0.02));
    color: var(--c-text);
    outline: none;
    transition: border-color .12s ease, box-shadow .12s ease;
}

.input::placeholder {
    color: rgb(var(--c-text-rgb) / 0.4);
}

.input:focus-visible {
    border-color: var(--c-accent);
    box-shadow: 0 0 0 3px rgb(var(--c-accent-rgb) / 0.25);
}

/* An email address and a URL read left-to-right in every language, including
   Arabic — without this the cursor and the @ land on the wrong side. */
.input-ltr {
    direction: ltr;
    text-align: start;
}

.input-wrap {
    position: relative;
}

/* Directional, so the reveal button sits inside the field's trailing edge in
   Arabic too. */
.input-wrap .input {
    padding-inline-end: 48px;
}

.input-affix {
    position: absolute;
    inset-block: 0;
    inset-inline-end: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    border: 0;
    background: none;
    color: rgb(var(--c-text-rgb) / 0.6);
    border-radius: var(--r-control);
}

.input-affix:hover {
    color: var(--c-text);
}

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: 48px;
    padding: 12px 20px;
    border: 1px solid transparent;
    border-radius: var(--r-control);
    font-size: var(--fs-body);
    font-weight: 700;
    transition: filter .12s ease, background-color .12s ease;
}

.btn:disabled {
    opacity: .55;
    cursor: default;
}

.btn-primary {
    background: var(--c-accent);
    color: var(--c-on-accent);
}

.btn-primary:hover:not(:disabled) {
    filter: brightness(1.08);
}

.btn-lg {
    min-height: 52px;
    width: 100%;
}

.btn-quiet {
    background: rgb(var(--c-text-rgb) / calc(0.08 * var(--glass-mult) + 0.03));
    color: var(--c-text);
    border-color: rgb(var(--c-text-rgb) / var(--a-border));
}

.btn-danger {
    background: rgb(var(--c-negative-rgb) / 0.15);
    color: var(--c-negative);
}

.btn-danger:hover {
    background: rgb(var(--c-negative-rgb) / 0.24);
}

/* Focus ring for everything that takes focus. Keyboard only — :focus-visible
   keeps it off mouse clicks. */
.mgr :focus-visible {
    outline: 2px solid var(--c-accent);
    outline-offset: 2px;
}

/* ---- Switch: a checkbox drawn as the Material switch ---- */
.switch {
    position: relative;
    flex: 0 0 auto;
    width: 52px;
    height: 32px;
    appearance: none;
    border-radius: 999px;
    background: rgb(var(--c-text-rgb) / 0.22);
    transition: background-color .15s ease;
}

.switch::after {
    content: '';
    position: absolute;
    top: 4px;
    inset-inline-start: 4px;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--c-base);
    transition: translate .15s ease;
}

.switch:checked {
    background: var(--c-accent);
}

.switch:checked::after {
    background: var(--c-on-accent);
    /* Logical, so the knob slides toward the trailing edge in Arabic too. */
    translate: 20px 0;
}

:root[dir="rtl"] .switch:checked::after {
    translate: -20px 0;
}

/* ---- Range ---- */
.range {
    width: 100%;
    height: 32px;
    appearance: none;
    background: none;
}

.range::-webkit-slider-runnable-track {
    height: 4px;
    border-radius: 2px;
    background: rgb(var(--c-text-rgb) / 0.2);
}

.range::-moz-range-track {
    height: 4px;
    border-radius: 2px;
    background: rgb(var(--c-text-rgb) / 0.2);
}

.range::-webkit-slider-thumb {
    appearance: none;
    margin-top: -8px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--c-accent);
}

.range::-moz-range-thumb {
    width: 20px;
    height: 20px;
    border: 0;
    border-radius: 50%;
    background: var(--c-accent);
}

/* ---- Dropdown (the language picker) — a <details> so it opens with no JS
   and closes on Escape for free. ---- */
.dropdown {
    position: relative;
}

.dropdown > summary {
    list-style: none;
    cursor: pointer;
}

.dropdown > summary::-webkit-details-marker {
    display: none;
}

.dropdown-menu {
    position: absolute;
    z-index: 40;
    min-width: 180px;
    margin-top: 6px;
    padding: 6px;
    inset-inline-end: 0;
    border-radius: var(--r-control);
    background: var(--c-base);
    border: 1px solid rgb(var(--c-text-rgb) / var(--a-border));
    box-shadow: 0 12px 32px rgb(0 0 0 / 0.35);
}

.dropdown-menu.is-above {
    bottom: 100%;
    margin-top: 0;
    margin-bottom: 6px;
}

.dropdown-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 11px 12px;
    border: 0;
    border-radius: 8px;
    background: none;
    text-align: start;
    font-size: var(--fs-body);
}

.dropdown-item:hover {
    background: rgb(var(--c-text-rgb) / 0.08);
}

.dropdown-item[aria-current="true"] {
    color: var(--c-accent);
    font-weight: 700;
}

/* ---- Notices ---- */
.notice {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 12px 14px;
    border-radius: var(--r-control);
    font-size: var(--fs-label);
}

.notice svg {
    flex: 0 0 auto;
    margin-top: 1px;
}

.notice-error {
    background: rgb(var(--c-negative-rgb) / 0.12);
    color: var(--c-negative);
}

.notice-warning {
    background: rgb(255 176 32 / 0.14);
    color: var(--c-warning);
}

.notice-success {
    background: rgb(74 222 128 / 0.14);
    color: var(--c-positive);
}

.notice-info {
    background: rgb(var(--c-accent-rgb) / 0.12);
    color: var(--c-accent);
}

/* ===========================================================================
   7. Layout — the three tiers from breakpoints.dart
      compact  < 600px   drawer
      medium   600–1024  icon rail
      expanded > 1024    full sidebar
   =========================================================================== */

.shell {
    display: flex;
    min-height: 100vh;
}

.shell-main {
    flex: 1 1 auto;
    min-width: 0; /* or a wide table stretches the flex item instead of scrolling */
}

.page {
    padding: 12px 16px 32px;
}

@media (min-width: 600px) {
    .page { padding: 16px 24px 32px; }
}

.page-body {
    margin-inline: auto;
    max-width: var(--w-content);
}

.page-body-narrow {
    margin-inline: auto;
    max-width: 720px;
}

.page-header {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 16px;
}

.page-header-titles {
    flex: 1 1 auto;
    min-width: 0;
}

.page-header-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

.stack {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.stack-sm {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.row {
    display: flex;
    align-items: center;
    gap: 12px;
}

.row-between {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.grow { flex: 1 1 auto; min-width: 0; }

/* ---- Sidebar / rail ---- */

.sidebar {
    flex: 0 0 auto;
    width: var(--w-sidebar);
    /* Directional: the sidebar's edge faces the content in Arabic too. */
    border-inline-end: 1px solid rgb(var(--c-text-rgb) / 0.1);
    padding: 20px 12px 16px;
    display: flex;
    flex-direction: column;
    /* Sticky rather than fixed: it scrolls its own overflow when the window is
       short (eight 48px rows plus the brand need ~460px, and a phone in
       landscape has less) without taking the page out of normal flow. */
    position: sticky;
    top: 0;
    align-self: flex-start;
    max-height: 100vh;
    overflow-y: auto;
}

.sidebar-brand {
    padding-inline-start: 10px;
    margin-bottom: 22px;
    font-size: 24px;
    font-weight: 800;
}

.sidebar-company {
    display: block;
    margin-top: 2px;
    font-size: var(--fs-caption);
    font-weight: 500;
    color: rgb(var(--c-text-rgb) / 0.55);
}

.nav-item {
    display: flex;
    align-items: center;
    gap: 12px;
    /* 14 + 20px icon + 14 = a 48px row, the size of a fingertip. */
    padding: 14px 12px;
    margin-bottom: 4px;
    border-radius: var(--r-control);
    color: rgb(var(--c-text-rgb) / 0.7);
    font-size: 14px;
    font-weight: 500;
    transition: background-color .12s ease, color .12s ease;
}

.nav-item svg {
    flex: 0 0 auto;
}

.nav-item:hover {
    background: rgb(var(--c-text-rgb) / 0.05);
    color: var(--c-text);
}

.nav-item[aria-current="page"] {
    background: rgb(var(--c-accent-rgb) / 0.15);
    color: var(--c-accent);
    font-weight: 700;
}

.nav-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.sidebar-foot {
    margin-top: auto;
    padding-top: 12px;
}

/* Medium tier: the rail. Icons only, labels underneath, until the operator
   expands it — the same toggle the Flutter NavigationRail carries. */
@media (max-width: 1024px) {
    .sidebar {
        width: var(--w-rail);
        padding-inline: 8px;
    }

    .sidebar.is-open {
        width: var(--w-rail-open);
        padding-inline: 12px;
    }

    .sidebar:not(.is-open) .sidebar-brand,
    .sidebar:not(.is-open) .nav-label,
    .sidebar:not(.is-open) .sidebar-foot-label {
        display: none;
    }

    .sidebar:not(.is-open) .nav-item {
        justify-content: center;
        padding-inline: 8px;
    }
}

/* Compact tier: no rail at all — the sidebar becomes a drawer, opened by the
   ☰ in the page header. There is deliberately no app bar: a bar holding only ☰
   cost a full strip of height above every screen's own header. */
@media (max-width: 599.98px) {
    .sidebar {
        position: fixed;
        z-index: 60;
        inset-block: 0;
        inset-inline-start: 0;
        width: var(--w-drawer);
        max-height: none;
        /* Logical translate so it slides off the correct edge in Arabic. */
        transform: translateX(-100%);
        transition: transform .2s ease;
        backdrop-filter: blur(24px);
        -webkit-backdrop-filter: blur(24px);
        background-color: rgb(var(--c-text-rgb) / calc(var(--glass-alpha) * var(--glass-mult)));
    }

    :root[dir="rtl"] .sidebar {
        transform: translateX(100%);
    }

    .sidebar.is-open,
    :root[dir="rtl"] .sidebar.is-open {
        transform: translateX(0);
    }

    :root[data-glass="off"] .sidebar {
        background-color: var(--c-base);
    }

    .drawer-scrim {
        position: fixed;
        inset: 0;
        z-index: 50;
        background: rgb(0 0 0 / 0.5);
    }
}

/* ☰ only exists on the compact tier; above it the sidebar is always visible. */
.menu-button {
    display: none;
}

@media (max-width: 599.98px) {
    .menu-button {
        display: inline-flex;
    }
}

/* ===========================================================================
   8. Dashboard pieces
   =========================================================================== */

.hero-card {
    padding: 24px;
}

/* Two columns once each can hold a chart at a readable 420px. Measured on the
   CONTENT's width, not the window's, so a sidebar beside it is accounted for. */
.card-columns {
    display: grid;
    grid-template-columns: 1fr;
    gap: 16px;
    align-items: start;
}

@container manager-content (min-width: 856px) {
    .card-columns {
        grid-template-columns: 1fr 1fr;
    }
}

.page-body {
    container-type: inline-size;
    container-name: manager-content;
}

/* ---- Charts ------------------------------------------------------------

   The bars are CSS, not SVG. An SVG chart has to scale to fit its card, and
   scaling scales the TEXT with it — the same axis label would render at 8px
   inside a phone-width card and at 28px across a 1100px one. Percentage
   heights are something the browser does natively, so the labels stay one size
   at every width and the whole thing reflows for free.

   Both charts run LEFT TO RIGHT even in Arabic. A time axis reads
   earliest-to-latest in every locale; mirroring it would read as time running
   backwards. */

.bar-chart,
.line-chart {
    direction: ltr;
    margin-top: 16px;
}

.bar-chart-plot,
.line-chart-plot {
    position: relative;
    display: flex;
    align-items: flex-end;
    gap: 4px;
    height: 190px;
}

.line-chart-plot {
    height: 165px;
    display: block;
}

.bar-grid {
    position: absolute;
    inset-inline: 0;
    height: 1px;
    background: rgb(var(--c-text-rgb) / 0.08);
    pointer-events: none;
}

.bar-col {
    flex: 1 1 0;
    min-width: 0;
    text-align: center;
}

.bar-chart-plot .bar-col {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    height: 100%;
}

.bar {
    width: 100%;
    max-width: 18px;
    min-height: 2px;
    border-radius: 6px 6px 0 0;
    /* Bottom-to-top gradient, as the Flutter BarChartRodData paints it. */
    background: linear-gradient(to top, rgb(var(--c-accent-rgb) / 0.55), var(--c-accent));
}

.bar-chart-axis {
    display: flex;
    gap: 4px;
    margin-top: 8px;
}

.chart {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
}

.chart-line {
    fill: none;
    stroke: var(--c-indigo);
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.chart-area {
    fill: rgb(var(--c-indigo-rgb) / 0.14);
    stroke: none;
}

/* One invisible column per point, carrying the value as a native tooltip.
   HTML rather than SVG circles, which the non-uniform stretch would squash
   into ellipses. */
.line-hit-row {
    position: absolute;
    inset: 0;
    display: flex;
}

.line-hit {
    flex: 1 1 0;
}

.line-hit:hover {
    background: rgb(var(--c-text-rgb) / 0.05);
}

.ranked-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 0;
}

.ranked-row + .ranked-row {
    border-top: 1px solid rgb(var(--c-text-rgb) / 0.1);
}

.divider {
    height: 1px;
    border: 0;
    margin: 0;
    background: rgb(var(--c-text-rgb) / 0.08);
}

.section-label {
    padding-inline-start: 4px;
    margin-bottom: 8px;
    color: rgb(var(--c-text-rgb) / 0.55);
}

/* ---- Empty / error states ---- */
.state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 48px 24px;
    text-align: center;
}

.state svg {
    color: rgb(var(--c-text-rgb) / 0.3);
}

/* ---- Preset grid in the date filter ---- */
.preset-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
}

@media (min-width: 420px) {
    .preset-grid { grid-template-columns: repeat(4, 1fr); }
}

.preset {
    min-height: 44px;
    padding: 10px 8px;
    border-radius: var(--r-control);
    border: 1px solid rgb(var(--c-text-rgb) / var(--a-border));
    background: rgb(var(--c-text-rgb) / 0.04);
    font-size: var(--fs-label);
    font-weight: 600;
}

.preset:hover {
    background: rgb(var(--c-text-rgb) / 0.1);
}

.preset[aria-pressed="true"] {
    background: rgb(var(--c-accent-rgb) / 0.16);
    border-color: rgb(var(--c-accent-rgb) / 0.5);
    color: var(--c-accent);
}

/* ---- Dialog ---- */
.mgr dialog {
    width: min(var(--w-dialog), calc(100vw - 32px));
    max-height: calc(100vh - 48px);
    padding: 24px;
    border: 0;
    color: var(--c-text);
    /* The dialog element is centred by the UA; only the inset needs resetting
       so it does not sit at the very top on a phone. */
    margin: auto;
}

.mgr dialog::backdrop {
    background: rgb(0 0 0 / 0.55);
}

/* ===========================================================================
   8b. Tones and the POS Sessions screens

   A tone is a palette colour a component borrows as `--tone` / `--tone-rgb`,
   so one set of rules draws a green LIVE pill, an indigo COUNTING glyph and a
   red difference chip. Mapped through the palette, never literal colours, so
   every tone flips with the theme — the Flutter sessionTint(), ported.
   =========================================================================== */

.tone-accent   { --tone: var(--c-accent);   --tone-rgb: var(--c-accent-rgb); }
.tone-indigo   { --tone: var(--c-indigo);   --tone-rgb: var(--c-indigo-rgb); }
.tone-positive { --tone: var(--c-positive); --tone-rgb: var(--c-positive-rgb); }
.tone-negative { --tone: var(--c-negative); --tone-rgb: var(--c-negative-rgb); }
.tone-warning  { --tone: var(--c-warning);  --tone-rgb: var(--c-warning-rgb); }
.tone-neutral  { --tone: var(--c-neutral);  --tone-rgb: var(--c-neutral-rgb); }

.tone-text { color: var(--tone); }
.positive { color: var(--c-positive); }
.warning-text { color: var(--c-warning); }

/* An arrow that points "forward" must point the other way in Arabic. */
:root[dir="rtl"] .flip-rtl { transform: scaleX(-1); }

/* ---- Status pill ---- */
.session-pill {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 10px;
    border-radius: 999px;
    background: rgb(var(--tone-rgb) / 0.16);
    border: 1px solid rgb(var(--tone-rgb) / 0.4);
    color: var(--tone);
    font-size: 11px;
    font-weight: 800;
    letter-spacing: 0.6px;
    white-space: nowrap;
}

.session-pill svg { width: 13px; height: 13px; }

/* The "still trading" beacon: a dot inside a soft halo. STATIC on purpose —
   a forever-repeating pulse repaints every frame of a page left open all day. */
.beacon {
    display: inline-block;
    flex: 0 0 auto;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--tone, var(--c-positive));
    box-shadow: 0 0 0 3px rgb(var(--tone-rgb, var(--c-positive-rgb)) / 0.3),
                0 0 6px 2px rgb(var(--tone-rgb, var(--c-positive-rgb)) / 0.45);
}

/* ---- The rounded state square in the list ---- */
.glyph {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 42px;
    height: 42px;
    border-radius: 14px;
    background: linear-gradient(135deg, rgb(var(--tone-rgb) / 0.38), rgb(var(--tone-rgb) / 0.12));
    color: var(--tone);
}

.glyph svg { width: 18px; height: 18px; }

/* ---- Headline stat cards: four across on a desktop, two on a phone, from
   the width the grid got — IlyassTileGrid(minTileWidth: 150, maxPerRow: 4). ---- */
.stat-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(max(150px, calc((100% - 36px) / 4)), 1fr));
    gap: 12px;
}

.stat-card { padding: 14px; min-width: 0; }

.stat-card-label {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.8px;
    text-transform: uppercase;
    color: rgb(var(--c-text-rgb) / 0.55);
    display: flex;
    align-items: center;
    gap: 7px;
}

.stat-card-label::before {
    content: '';
    width: 8px;
    height: 8px;
    border-radius: 2px;
    background: var(--tone);
}

.stat-card-value {
    margin: 8px 0 4px;
    font-size: 21px;
    font-weight: 800;
    overflow-wrap: anywhere;
}

/* ---- Filter chips ---- */
.chip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* On a phone the chips scroll rather than wrap, so the list keeps its first
   screenful — as the Flutter screen does. */
@media (max-width: 599.98px) {
    .chip-row {
        flex-wrap: nowrap;
        overflow-x: auto;
        scrollbar-width: none;
        padding-bottom: 2px;
    }
}

.chip {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 6px;
    min-height: 48px; /* tapped far more often than read */
    padding: 0 14px;
    border-radius: 999px;
    border: 1px solid rgb(var(--c-text-rgb) / 0.08);
    background: rgb(var(--c-text-rgb) / 0.05);
    color: rgb(var(--c-text-rgb) / 0.7);
    font-size: var(--fs-label);
    font-weight: 600;
}

.mgr a.chip { color: rgb(var(--c-text-rgb) / 0.7); }

.chip[aria-current="true"],
.mgr a.chip[aria-current="true"] {
    background: rgb(var(--tone-rgb) / 0.16);
    border-color: rgb(var(--tone-rgb) / 0.45);
    color: var(--tone);
    font-weight: 700;
}

.chip-count {
    padding: 1px 6px;
    border-radius: 999px;
    background: rgb(var(--c-text-rgb) / 0.08);
    font-size: var(--fs-caption);
    font-weight: 700;
}

/* Search, register and history side by side while each keeps a usable width. */
.filter-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 8px;
}

/* ---- Live strip ---- */
.live-strip {
    display: flex;
    gap: 12px;
    overflow-x: auto;
    padding-bottom: 6px;
    scroll-snap-type: x proximity;
}

.live-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    flex: 0 0 258px;
    min-height: 210px;
    padding: 16px;
    border-radius: var(--r-card);
    border: 1px solid rgb(var(--tone-rgb) / 0.32);
    background: linear-gradient(135deg, rgb(var(--tone-rgb) / 0.20), rgb(var(--tone-rgb) / 0.04));
    scroll-snap-align: start;
}

.mgr a.live-card { color: var(--c-text); }

.live-card-title {
    margin-top: 6px;
    font-size: 19px;
    font-weight: 800;
}

.live-card-foot { margin-top: auto; }

.mini-stat {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.mini-stat-end { align-items: flex-end; text-align: end; }

.mini-stat-label {
    font-size: var(--fs-caption);
    font-weight: 700;
    text-transform: uppercase;
    color: rgb(var(--c-text-rgb) / 0.45);
}

.mini-stat-value {
    font-size: 17px;
    font-weight: 800;
    white-space: nowrap;
}

/* ---- Session rows ---- */
.session-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    transition: background-color .12s ease;
}

.mgr a.session-row { color: var(--c-text); }

.session-row:hover {
    background-color: rgb(var(--c-text-rgb) / calc(var(--a-fill) + 0.05));
}

/* Capped so the register name always keeps a readable share of a 360px row. */
.session-row-figures {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    align-items: flex-end;
    gap: 3px;
    max-width: 128px;
    text-align: end;
}

.session-row-figures-inner {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 3px;
}

.diff-chip {
    padding: 2px 7px;
    border-radius: 999px;
    background: rgb(var(--tone-rgb) / 0.16);
    color: var(--tone);
    font-size: var(--fs-caption);
    font-weight: 700;
    white-space: nowrap;
}

/* A figure still being fetched. */
.skeleton {
    display: inline-block;
    height: 15px;
    border-radius: 6px;
    background: rgb(var(--c-text-rgb) / 0.12);
}

/* ---- Session detail ---- */
.hero-tint {
    padding: 18px;
    border-radius: var(--r-card);
    border: 1px solid rgb(var(--tone-rgb) / 0.3);
    background: linear-gradient(135deg, rgb(var(--tone-rgb) / 0.18), rgb(var(--tone-rgb) / 0.03));
}

.session-hero-title {
    margin: 14px 0 4px;
    font-size: 26px;
    font-weight: 800;
    overflow-wrap: anywhere;
}

/* Label and value both flexible, pushed apart — never Expanded + Flexible,
   which strands the value at the midpoint ("Ilyass style"). */
.fact-row {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 12px;
}

.fact-label {
    flex: 0 1 auto;
    font-size: var(--fs-label);
}

.fact-value {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    flex: 0 1 auto;
    min-width: 0;
    text-align: end;
    font-size: var(--fs-label);
    font-weight: 700;
}

.flag-banner {
    padding: 14px;
    border-radius: calc(var(--r-control) + 4px);
    border: 1px solid rgb(var(--tone-rgb) / 0.32);
    background: rgb(var(--tone-rgb) / 0.1);
    border-inline-start-width: 4px;
    border-inline-start-color: var(--tone);
}

.takings {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.takings-col {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 0 6px;
    text-align: center;
    min-width: 0;
}

.takings-col + .takings-col {
    border-inline-start: 1px solid rgb(var(--c-text-rgb) / 0.1);
}

.takings-value {
    font-size: 17px;
    font-weight: 800;
    overflow-wrap: anywhere;
}

.money-row {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 5px 0;
}

.money-symbol {
    flex: 0 0 14px;
    font-size: var(--fs-label);
    font-weight: 700;
    color: rgb(var(--c-text-rgb) / 0.35);
}

.money-label {
    display: block;
    color: rgb(var(--c-text-rgb) / 0.75);
}

.money-row .grow > .t-caption { display: block; }

.money-value { white-space: nowrap; }

.money-row-strong .money-label { color: var(--c-text); font-weight: 600; }
.money-row-strong .money-value { font-size: 17px; font-weight: 800; }

/* The payment-mix ring. A square viewBox scales uniformly, so it stays round. */
.ring {
    position: relative;
    width: 176px;
    height: 176px;
    margin: 0 auto 14px;
}

.ring svg { width: 100%; height: 100%; display: block; }

.ring-track {
    fill: none;
    stroke: rgb(var(--c-text-rgb) / 0.06);
    stroke-width: 16;
}

.ring-slice {
    fill: none;
    stroke: var(--tone);
    stroke-width: 16;
}

.ring-centre {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

.method-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 0;
}

.method-row + .method-row { border-top: 1px solid rgb(var(--c-text-rgb) / 0.08); }

.dot {
    flex: 0 0 auto;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--tone);
}

.method-figures {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    text-align: end;
}

.kv-list { padding: 14px 20px; }

.kv {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 12px;
    padding: 4px 0;
    font-size: var(--fs-caption);
}

.kv > span:first-child { flex: 0 1 40%; color: rgb(var(--c-text-rgb) / 0.5); }
.kv > span:last-child { flex: 0 1 60%; text-align: end; color: rgb(var(--c-text-rgb) / 0.8); user-select: all; }

/* ===========================================================================
   9. Print — the manager prints the dashboard for a meeting more often than
      anyone expects. Chrome, not content.
   =========================================================================== */

/* ===========================================================================
   CATALOGUE — products & prices, product groups (phase 5)

   The colour of a product or a group is its OWN, picked by the manager and
   stored as #RRGGBB, so these rules take `--tone` from an inline style rather
   than from a .tone-* class. That is also why they blend with color-mix()
   instead of rgb(var(--tone-rgb) / ...): there is no rgb triple for a colour
   the stylesheet has never seen.
   =========================================================================== */

/* The colour-tinted square carrying an initial or an icon — InitialTile. */
.tile {
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 12px;
    font-size: 17px;
    font-weight: 800;
    line-height: 1;
    color: var(--c-text);
    /* Fallback first, for a browser without color-mix. */
    background: rgb(var(--c-text-rgb) / 0.08);
    background: color-mix(in srgb, var(--tone, var(--c-accent)) 16%, transparent);
    border: 1.2px solid rgb(var(--c-text-rgb) / 0.16);
    border-color: color-mix(in srgb, var(--tone, var(--c-accent)) 50%, transparent);
}

.tile svg { width: 18px; height: 18px; color: var(--tone, var(--c-accent)); }

/* A product / group row. The row is a link; the price button beside it is not,
   so the two cannot be nested — they sit side by side in one card. */
.catalog-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 10px 8px 14px;
    transition: background-color .12s ease;
}

.catalog-row:hover { background-color: rgb(var(--c-text-rgb) / calc(var(--a-fill) + 0.05)); }

.catalog-row-main {
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    gap: 12px;
    min-width: 0;
    padding: 4px 0;
}

/* The groups list has no second control, so its whole row is the link. */
.mgr a.catalog-row,
.mgr a.catalog-row-main { color: var(--c-text); }

.catalog-row-link { padding: 12px 14px; }

/* Off sale: legible, and visibly not selling. */
.catalog-row.is-off .tile,
.catalog-row.is-off .catalog-row-money { opacity: .55; }

.catalog-row-money {
    display: flex;
    flex: 0 0 auto;
    flex-direction: column;
    align-items: flex-end;
    gap: 2px;
    max-width: 140px;
    text-align: end;
}

.catalog-row-price { flex: 0 0 auto; }

/* A small word beside a name: "Off sale", "Service". */
.tag {
    flex: 0 0 auto;
    padding: 2px 7px;
    border-radius: 999px;
    background: rgb(var(--tone-rgb) / 0.16);
    border: 1px solid rgb(var(--tone-rgb) / 0.3);
    color: var(--tone);
    font-size: 10px;
    font-weight: 800;
    letter-spacing: .4px;
    text-transform: uppercase;
    white-space: nowrap;
}

/* A wrapped line of tags under a name — a product's stock rules. It wraps
   rather than scrolls: a rule that is off screen is a rule nobody reads. */
.tag-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding-top: 6px;
}

.tag-row:empty { display: none; }

/* ---- Pager ---- */
.pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
    padding: 4px 0 24px;
}

.pager [aria-disabled="true"] { opacity: .4; pointer-events: none; }

/* ---- Forms ---- */

/* Two fields side by side while each keeps a readable width, stacked below —
   measured on the width the FORM got, never on the device (Ilyass Style). */
.field-pair {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 12px;
}

.switch-row { gap: 12px; align-items: center; }

/* ---- Colour swatches ----
   A radio group: no script, so it posts, restores and validates like any other
   field. The input stays in the layout (focusable, announced) but is drawn as
   nothing; the label next to it is the swatch. */
.swatches {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    border: 0;
    padding: 0;
    margin: 0;
}

.swatches legend { padding: 0; margin-bottom: 10px; }

.swatch-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

.swatch {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--tone, transparent);
    border: 1px solid rgb(var(--c-text-rgb) / 0.16);
    cursor: pointer;
    transition: transform .12s ease, box-shadow .12s ease, border-color .12s ease;
}

.swatch svg { opacity: 0; color: #fafafa; }

/* A light swatch needs a dark tick. Decided per swatch by the browser, from the
   colour itself, so the palette can grow without a table of luminances here. */
@supports (color: color-contrast(red vs white, black)) {
    .swatch svg { color: color-contrast(var(--tone, #000) vs #1a1a1a, #fafafa); }
}

.swatch-none {
    background: rgb(var(--c-text-rgb) / 0.06);
    color: rgb(var(--c-text-rgb) / 0.55);
}

.swatch-none svg { opacity: 1; color: rgb(var(--c-text-rgb) / 0.45); }

.swatch-input:checked + .swatch {
    border: 3px solid var(--c-accent);
    box-shadow: 0 0 0 3px rgb(var(--c-accent-rgb) / 0.25);
}

.swatch-input:checked + .swatch svg { opacity: 1; }

.swatch-input:checked + .swatch-none svg { color: var(--c-accent); }

.swatch-input:focus-visible + .swatch {
    outline: 2px solid var(--c-accent);
    outline-offset: 3px;
}

@media (hover: hover) {
    .swatch:hover { transform: scale(1.06); }
}

/* ---- Compact tier ---- */
@media (max-width: 599.98px) {
    .catalog-row-money { max-width: 110px; }
    .tile { width: 36px; height: 36px; border-radius: 11px; font-size: 15px; }
}

@media print {
    .sidebar,
    .page-header-actions,
    .menu-button,
    .drawer-scrim {
        display: none !important;
    }

    .mgr {
        background: #fff;
        color: #000;
    }

    .glass {
        border-color: #ccc;
        background: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .mgr *,
    .mgr *::before,
    .mgr *::after {
        transition-duration: .01ms !important;
        animation-duration: .01ms !important;
    }
}
