/*
 * static/css/style.css
 * The ONE stylesheet of the project, served at /static/css/style.css.
 *
 * Every page loads this file and no other. If a screen needs something
 * special, add a class here rather than a second stylesheet - one file is
 * what keeps the pages looking like one product.
 *
 * Order: tokens, base, layout, sidebar, then components. Components are
 * generic on purpose (.table, .btn, .field) so a new screen is markup only.
 *
 * Theming: every colour is a token on :root, and [data-theme="dark"] simply
 * restates the same tokens. Nothing below the token block hard-codes a
 * colour, which is what makes the dark switch a five-line change.
 *
 * Anything public goes in `static/` (css, js, images).
 * Anything a user uploads goes in `uploads/` - never mix the two.
 */

/* ---------------------------------------------------------------- tokens */
:root {
    --sidebar-w: 258px;
    --sidebar-w-collapsed: 0px;
    --topbar-h: 64px;

    /* Neutral greys, R = G = B, so the purple accent is the only hue on the
       screen. Anything tinted here reads as a colour cast beside it. */
    --bg: #ffffff;
    --surface: #ffffff;
    --surface-2: #fafafa;
    --sidebar-bg: #fbfbfb;
    --border: #ededed;
    --border-strong: #dcdcdc;

    --text: #171717;
    --text-2: #5c5c5c;
    --muted: #8f8f8f;

    --accent: #7c3aed;
    --accent-hover: #6d28d9;
    --accent-soft: #f3eeff;
    --accent-text: #ffffff;

    --green: #15803d;
    --green-soft: #eaf7ef;
    /* Second series colour, for the outlet-type donut. */
    --blue: #2563eb;
    --amber: #b45309;
    --amber-soft: #fdf4e7;
    --red: #dc2626;
    --red-soft: #fdeeee;

    --radius: 14px;
    --radius-sm: 9px;
    --gap: 26px;

    --shadow-sm: 0 1px 2px rgba(20, 20, 30, .05);
    --shadow: 0 6px 22px rgba(20, 20, 30, .10);
    --shadow-lg: 0 22px 54px rgba(20, 20, 30, .20);

    --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* Neutral greys, R = G = B. An earlier version leaned blue (#131318 has more
   blue than red or green) which read as a colour cast next to the purple
   accent; true neutrals let the accent be the only hue on the screen. */
[data-theme="dark"] {
    --bg: #0d0d0d;
    --surface: #171717;
    --surface-2: #1f1f1f;
    --sidebar-bg: #141414;
    --border: #272727;
    --border-strong: #383838;

    --text: #f5f5f5;
    --text-2: #a8a8a8;
    --muted: #757575;

    --accent: #8b5cf6;
    --accent-hover: #7c3aed;
    --accent-soft: #241a3d;

    --green: #4ade80;
    --green-soft: #14301d;
    --blue: #60a5fa;
    --amber: #fbbf24;
    --amber-soft: #302510;
    --red: #f87171;
    --red-soft: #331a1a;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .5);
    --shadow: 0 6px 22px rgba(0, 0, 0, .6);
    --shadow-lg: 0 22px 54px rgba(0, 0, 0, .7);

    color-scheme: dark;   /* native scrollbars, form controls and caret follow */
}

/* ------------------------------------------------------------------ base */
*,
*::before,
*::after {
    box-sizing: border-box;
}

body {
    margin: 0;
    font-family: var(--font);
    font-size: 14px;
    line-height: 1.55;
    color: var(--text);
    background: var(--bg);
    -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: none; }

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.01em; }

code {
    font-family: var(--mono);
    font-size: .88em;
    background: var(--accent-soft);
    color: var(--accent);
    padding: 2px 7px;
    border-radius: 5px;
}

/* Inline icons: stroked, never filled, so one SVG works on both themes. */
.ico {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.9;
    stroke-linecap: round;
    stroke-linejoin: round;
    flex: none;
}

.ico-sm { width: 15px; height: 15px; }

/* --------------------------------------------------------------- sidebar */
.sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    width: var(--sidebar-w);
    display: flex;
    flex-direction: column;
    background: var(--sidebar-bg);
    border-right: 1px solid var(--border);
    z-index: 30;
    transition: transform .2s ease;
}

.sidebar-brand {
    display: flex;
    align-items: center;
    gap: 11px;
    height: var(--topbar-h);
    padding: 0 20px;
    flex: none;
    border-bottom: 1px solid var(--border);
}

.brand-mark {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border-radius: 11px;
    background: var(--accent);
    color: var(--accent-text);
    flex: none;
}

.brand-mark .ico { width: 20px; height: 20px; }

.brand-text { display: flex; flex-direction: column; line-height: 1.2; min-width: 0; }

.brand-name {
    font-weight: 700;
    font-size: 16px;
    letter-spacing: -.02em;
    white-space: nowrap;
}

.brand-tag { color: var(--muted); font-size: 11.5px; white-space: nowrap; }

.sidebar-nav {
    display: flex;
    flex-direction: column;
    padding: 18px 14px;
    gap: 2px;
    overflow-y: auto;
    flex: 1;
}

.menu-group + .menu-group { margin-top: 10px; }

.menu-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    padding: 9px 10px;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--text);
    font: inherit;
    font-size: 14.5px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}

.menu-head:hover { background: var(--surface-2); }

/* The icon sits in a bordered square, as on the reference: it gives every
   menu the same left edge, so the names line up whatever their length. */
.menu-head-icon {
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    flex: none;
    margin-right: 10px;
    border: 1px solid var(--border-strong);
    border-radius: 8px;
    color: var(--text-2);
}

.menu-head-icon .ico { width: 16px; height: 16px; }
.menu-head-name { flex: 1; min-width: 0; text-align: left; }

.menu-head:hover .menu-head-icon { border-color: var(--accent); color: var(--accent); }

/* The open menu, and the menu holding the current screen, carry the accent —
   otherwise nine identical rows give no clue which one you are inside. */
.menu-group.is-open .menu-head-icon,
.menu-group:has(.menu-item.is-active) .menu-head-icon {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent);
}

.menu-chevron {
    width: 16px;
    height: 16px;
    opacity: .5;
    transition: transform .18s ease;
    transform: rotate(-90deg);            /* closed -> pointing right */
}

.menu-group.is-open .menu-chevron { transform: rotate(0deg); }

.menu-items {
    display: none;
    flex-direction: column;
    gap: 1px;
    padding: 4px 0 2px;
}

.menu-group.is-open .menu-items { display: flex; }

.menu-item {
    padding: 9px 12px;
    border-radius: var(--radius-sm);
    color: var(--text-2);
    font-size: 14px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.menu-item:hover { background: var(--surface-2); color: var(--text); }

/* The screen you are on. This used to be `--surface-2` in light mode — #fafafa
   on a #fbfbfb sidebar, a one-value difference that was effectively invisible;
   only dark mode, which used the accent tint, actually showed it. Now both
   themes use the accent: a tinted fill, accent text, and a bar down the left
   edge that reads even at a glance. */
.menu-item.is-active {
    position: relative;
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 600;
}

.menu-item.is-active::before {
    content: "";
    position: absolute;
    left: 0;
    top: 6px;
    bottom: 6px;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--accent);
}

.menu-item.is-active:hover { background: var(--accent-soft); color: var(--accent); }

.sidebar-foot {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 14px 18px;
    border-top: 1px solid var(--border);
    flex: none;
}

.sidebar-foot .user-meta { flex: 1; min-width: 0; }

/* ----------------------------------------------------------------- shell */
.shell {
    margin-left: var(--sidebar-w);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    transition: margin-left .2s ease;
}

/* Collapsed: the menu slides away entirely and the content takes the width. */
body.nav-collapsed .sidebar { transform: translateX(-100%); }
body.nav-collapsed .shell { margin-left: 0; }

.topbar {
    position: sticky;
    top: 0;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 0 22px;
    height: var(--topbar-h);
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    z-index: 20;
}

.topbar-divider {
    width: 1px;
    height: 22px;
    background: var(--border);
}

.topbar-title {
    font-size: 15.5px;
    font-weight: 500;
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.icon-btn {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text-2);
    cursor: pointer;
    flex: none;
    transition: background .12s ease, color .12s ease;
}

.icon-btn:hover { background: var(--surface-2); color: var(--text); }

.topbar-user { display: flex; align-items: center; gap: 10px; flex: none; }

.user-meta { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }

.user-name {
    font-weight: 600;
    font-size: 13.5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.user-role { color: var(--muted); font-size: 11.5px; }

.avatar {
    display: grid;
    place-items: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--accent);
    color: var(--accent-text);
    font-weight: 700;
    font-size: 14px;
    flex: none;
}

.content { flex: 1; padding: var(--gap); }

/* ----------------------------------------------------------- page header */
.page-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    margin-bottom: 22px;
    flex-wrap: wrap;
}

/* The title block absorbs the spare width; the actions keep their natural
   size. Without this the text block sized to its content, squeezed .page-actions
   and the search box wrapped onto its own line above the button. */
.page-head > :first-child {
    flex: 1 1 300px;
    min-width: 0;
}

.page-eyebrow {
    display: none;
    color: var(--accent);
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    margin-bottom: 3px;
}

.page-title {
    font-size: 25px;
    font-weight: 700;
    letter-spacing: -.01em;
    text-transform: uppercase;
}

.page-sub {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 6px 0 0;
    color: var(--text-2);
    font-size: 13.5px;
}

.page-sub .ico { width: 16px; height: 16px; color: var(--muted); }

/* nowrap: the search field and the primary action stay on one line. They only
   drop below the title at the narrow breakpoint, where wrapping is intended. */
.page-actions {
    display: flex;
    gap: 10px;
    align-items: center;
    flex: 0 0 auto;
    flex-wrap: nowrap;
}

/* ----------------------------------------------------------------- cards */
.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    margin-bottom: var(--gap);
    overflow: hidden;
}

.card-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 16px 22px;
    flex-wrap: wrap;
}

/* The small dot + tracked label from the reference screens. */
.card-title {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--text);
}

.card-title::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--accent);
    flex: none;
}

.card-body { padding: 0 22px 22px; }

.card-text { margin: 0 0 4px; color: var(--text-2); }

.muted { color: var(--muted); font-size: 12.5px; margin: 0; }

/* ----------------------------------------------------------------- stats */
.stat-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 16px;
    margin-bottom: var(--gap);
}

.stat {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 17px 19px;
    box-shadow: var(--shadow-sm);
}

.stat-label {
    color: var(--muted);
    font-size: 11.5px;
    text-transform: uppercase;
    letter-spacing: .06em;
    font-weight: 700;
}

.stat-value {
    font-size: 27px;
    font-weight: 700;
    letter-spacing: -.02em;
    margin-top: 3px;
}

/* --------------------------------------------------------------- buttons */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 9px 17px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: 13.5px;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
    transition: background .12s ease, border-color .12s ease;
}

.btn:hover:not(:disabled) { background: var(--surface-2); }
.btn:disabled { opacity: .5; cursor: not-allowed; }

.btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-text);
    box-shadow: 0 1px 2px rgba(124, 58, 237, .3);
}

.btn-primary:hover:not(:disabled) {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
}

.btn-danger { background: var(--red); border-color: var(--red); color: #fff; }
.btn-danger:hover:not(:disabled) { filter: brightness(.93); background: var(--red); }

.btn-sm { padding: 6px 11px; font-size: 12.5px; }

/* Square icon action, used in table rows. */
.btn-icon {
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text-2);
    cursor: pointer;
    transition: background .12s ease, color .12s ease, border-color .12s ease;
}

.btn-icon:hover { background: var(--surface-2); color: var(--text); }

.btn-icon.is-danger:hover {
    background: var(--red-soft);
    border-color: var(--red-soft);
    color: var(--red);
}

/* ---------------------------------------------------------------- tables */
.table-wrap { overflow-x: auto; }

.table { width: 100%; border-collapse: collapse; font-size: 14px; }

.table th {
    text-align: left;
    padding: 12px 22px;
    background: var(--surface-2);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
    color: var(--text-2);
    font-weight: 600;
    font-size: 13px;
    white-space: nowrap;
}

.table td {
    padding: 15px 22px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
    color: var(--text-2);
}

.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover td { background: var(--surface-2); }

.table .num { text-align: right; font-variant-numeric: tabular-nums; }

.table .actions { text-align: right; white-space: nowrap; }

.actions-cell { display: inline-flex; gap: 7px; }

.row-title { font-weight: 600; color: var(--text); }
.row-sub { color: var(--muted); font-size: 12.5px; }

/* ------------------------------------------------------------ pagination */
.pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 15px 22px;
    border-top: 1px solid var(--border);
    flex-wrap: wrap;
}

.pager-info { color: var(--text-2); font-size: 13.5px; }

.pager-controls { display: flex; align-items: center; gap: 9px; }

.pager-page { color: var(--text-2); font-size: 13.5px; }

/* --------------------------------------------------------------- badges */
.badge {
    display: inline-flex;
    align-items: center;
    padding: 3px 11px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 600;
    background: var(--surface-2);
    color: var(--text-2);
    white-space: nowrap;
}

.badge + .badge { margin-left: 5px; }

.badge-green { background: var(--green-soft); color: var(--green); }
.badge-amber { background: var(--amber-soft); color: var(--amber); }
.badge-red   { background: var(--red-soft);   color: var(--red); }
.badge-blue  { background: var(--accent-soft); color: var(--accent); }

/* ---------------------------------------------------------------- forms */
.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 16px;
}

.field { display: flex; flex-direction: column; gap: 6px; }
.field-wide { grid-column: 1 / -1; }

.label { font-size: 13px; font-weight: 600; color: var(--text); }

.input,
.select {
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: 14px;
}

.input:focus,
.select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.input::placeholder { color: var(--muted); }

.hint { color: var(--muted); font-size: 12px; }

.check { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }
.check input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }

.search { width: 250px; min-width: 170px; }

.toolbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ----------------------------------------------------------------- modal */
.modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(20, 20, 30, .5);
    display: none;
    align-items: flex-start;
    justify-content: center;
    padding: 44px 16px;
    overflow-y: auto;
    z-index: 60;
}

.modal-backdrop.is-open { display: flex; }

.modal {
    width: 100%;
    max-width: 620px;
    background: var(--surface);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

.modal-wide { max-width: 1000px; }

.modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 18px 22px;
    border-bottom: 1px solid var(--border);
}

.modal-title { font-size: 16px; font-weight: 700; }

.modal-close {
    border: 0;
    background: none;
    font-size: 23px;
    line-height: 1;
    color: var(--muted);
    cursor: pointer;
    padding: 0 4px;
}

.modal-close:hover { color: var(--text); }

.modal-body { padding: 22px; max-height: 64vh; overflow-y: auto; }

/* .card-foot is the same bar outside a modal — a card whose actions sit at the
   bottom rather than in the header. */
.modal-foot,
.card-foot {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 16px 22px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
}

/* Textareas share .input, but need a sane starting height and no sideways
   drag (which would break the card's layout). */
textarea.input {
    min-height: 96px;
    resize: vertical;
    line-height: 1.5;
}

/* --- msel: a multi-select dropdown (app.msel) ------------------------
   Closed it is a single line the same size as a .select; open, its panel is
   overlaid rather than inserted, so opening a filter never moves the buttons
   underneath it.                                                           */
.msel {
    position: relative;
    min-height: 38px;
}

.msel-toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: 14px;
    text-align: left;
    cursor: pointer;
}

.msel-toggle:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/* An untouched filter and a set one must not look alike. */
.msel-toggle.is-set { border-color: var(--accent); color: var(--accent); font-weight: 600; }

.msel-label {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.msel-caret { flex: none; color: var(--muted); }
.msel.is-open .msel-caret { transform: rotate(180deg); }

.msel-panel {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 40;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
}

.msel.is-open .msel-panel { display: flex; }

.msel-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px;
    border-bottom: 1px solid var(--border);
    flex: 0 0 auto;
}

.msel-head .msel-search { flex: 1 1 auto; min-width: 0; }

.msel-list {
    flex: 1 1 auto;
    max-height: 220px;
    overflow-y: auto;
    padding: 4px 0;
}

.msel-row {
    display: flex;
    width: 100%;
    padding: 5px 10px;
    font-size: 13px;
    gap: 8px;
}

.msel-row:hover { background: var(--surface-2); }
.msel-name { overflow: hidden; text-overflow: ellipsis; }
.msel-sub {
    color: var(--muted);
    font-size: 12px;
    margin-left: auto;
    padding-left: 8px;
    white-space: nowrap;
}
.msel-none { padding: 14px 10px; text-align: center; }

.msel-actions {
    display: flex;
    gap: 8px;
    padding: 8px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
    flex: 0 0 auto;
}

.btn-sm { padding: 4px 10px; font-size: 12px; }

/* A card holding dropdowns must not clip them. .card hides overflow so its
   rounded corners crop the table inside; a card of msels has no table and
   does need its panels to escape. */
.card-dropdowns { overflow: visible; }

/* The filter row: four msels side by side, wrapping to two and then one as
   the window narrows. Its own grid rather than .form-grid, whose 230px
   minimum would let five squeeze on to a very wide screen. */
.msel-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 16px;
}

@media (max-width: 1200px) {
    .msel-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
    .msel-grid { grid-template-columns: minmax(0, 1fr); }
}

/* A denser page header, for screens whose point is the table underneath it.
   The title, the eyebrow and the filter bar are all navigation — on a report
   they should cost as little of the first screen as possible, so the data
   starts near the top rather than a third of the way down. Opted into with
   `{% block content_class %}is-tight{% endblock %}`. */
.content.is-tight { padding: 14px 22px 22px; }
.content.is-tight .page-head { margin-bottom: 12px; }
.content.is-tight .page-title { font-size: 21px; }
.content.is-tight .page-eyebrow { font-size: 10.5px; margin-bottom: 1px; }
.content.is-tight .page-sub { margin-top: 2px; font-size: 12.5px; }
.content.is-tight .page-sub .ico { width: 14px; height: 14px; }

/* The filter card is a control strip, not a panel: no title bar, and only
   enough padding to keep the inputs off the edges. The labels above each
   control already say what it is, so a "Filters" heading is a whole row of
   the fold spent saying nothing.

   (Not `:first-of-type` — that counts tags, not classes, and the first div
   child here is the page header, so it matched nothing at all.) */
.content.is-tight .card-dropdowns { margin-bottom: 14px; }
.content.is-tight .card-dropdowns > .card-head { display: none; }
.content.is-tight .card-dropdowns > .card-body { padding: 14px 16px; }

/* One size down again, on top of `is-tight`. Product Analysis carries six
   filters, six KPI cards, a forty-column matrix and six panels; at the
   standard scale the chrome — page header, filter card, card heads — used the
   whole first screen and the matrix, the reason the page exists, opened below
   the fold. Everything here is padding and type size; no layout changes, so a
   screen can opt in with `is-tight is-dense` and nothing else. */
.content.is-dense { padding: 10px 18px 18px; }
.content.is-dense .page-head { margin-bottom: 9px; }
.content.is-dense .page-title { font-size: 18px; }
.content.is-dense .page-sub { font-size: 11.5px; }
.content.is-dense .card { margin-bottom: 12px; }
.content.is-dense .card-dropdowns { margin-bottom: 11px; }
.content.is-dense .card-dropdowns > .card-body { padding: 10px 12px; }
.content.is-dense .card-head { padding: 10px 14px; gap: 10px; }
.content.is-dense .card-title { font-size: 11px; }
.content.is-dense .card-body { padding: 0 14px 14px; }

.content.is-dense .filter-bar { gap: 10px; padding-top: 8px; }
.content.is-dense .field { gap: 4px; }
.content.is-dense .label { font-size: 11.5px; }
.content.is-dense .input,
.content.is-dense .msel-toggle { padding: 6px 9px; font-size: 12.5px; }
.content.is-dense .msel { min-height: 32px; }

.content.is-dense .kpi-row {
    grid-template-columns: repeat(auto-fit, minmax(146px, 1fr));
    gap: 10px;
    margin-bottom: 12px;
}
.content.is-dense .kpi { padding: 9px 11px; }
.content.is-dense .kpi-label { font-size: 9.5px; margin-bottom: 3px; }
.content.is-dense .kpi-value { font-size: 17px; }
.content.is-dense .kpi-foot { margin-top: 3px; gap: 5px; }
.content.is-dense .kpi-sub,
.content.is-dense .delta { font-size: 10.5px; }

/* Plain tables too, not only the ones inside a `dash-split`. The base cell is
   15px/22px, which is right for a six-row master list and far too airy for a
   report: it put half a screen of white space between eight rows of data.
   The `dash-split` and `cat-table` rules below are tighter still and, being
   later and no less specific, keep their own values. */
.content.is-dense .table th { padding: 8px 14px; font-size: 11.5px; }
.content.is-dense .table td { padding: 7px 14px; }
.content.is-dense .table { font-size: 13px; }
.content.is-dense .row-sub { line-height: 1.35; }

.content.is-dense .dash-split { margin-bottom: 12px; gap: 12px; }
.content.is-dense .dash-split .card-head { padding: 9px 12px; }
.content.is-dense .dash-split .table { font-size: 12px; }
.content.is-dense .dash-split .table th { padding: 6px 11px; font-size: 10.5px; }
.content.is-dense .dash-split .table td { padding: 5px 11px; }
.content.is-dense .dash-split .table-wrap { max-height: 340px; }
.content.is-dense .row-sub { font-size: 10px; }
.content.is-dense .dash-foot { margin: 0 2px 16px; font-size: 11px; }

/* =========================================================== dashboards ==
   The Sales Performance screen. Layout only — every colour comes from the
   same tokens the rest of the app uses, so both themes follow automatically.
   ======================================================================== */
.filter-bar {
    display: grid;
    grid-template-columns: 210px repeat(3, minmax(0, 1fr));
    gap: 16px;
    align-items: end;
    padding-top: 18px;
}

@media (max-width: 1000px) {
    .filter-bar { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 620px) {
    .filter-bar { grid-template-columns: minmax(0, 1fr); }
}

/* ---- KPI cards ---- */
/* This page carries far more numbers than any other screen, so it runs a size
   down throughout — cards, rows and gaps. At the app's default scale six KPI
   cards and four tables filled two and a half screens. */
.dash-gap { --gap: 16px; }

.kpi-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(165px, 1fr));
    gap: 14px;
    margin-bottom: 16px;
}

.kpi {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-sm);
    padding: 12px 14px;
}

.kpi-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: 5px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.kpi-value { font-size: 21px; font-weight: 700; line-height: 1.15; }
.kpi-value.is-up { color: var(--green); }
.kpi-value.is-down { color: var(--red); }

.kpi-foot {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-top: 5px;
    flex-wrap: wrap;
}

.kpi-sub { font-size: 11.5px; color: var(--muted); }

/* A growth figure: up is green, down is red, and "no basis to compare" is a
   grey dash rather than a green zero. */
.delta { font-size: 12px; font-weight: 600; white-space: nowrap; }
.delta.is-up { color: var(--green); }
.delta.is-down { color: var(--red); }
.delta.is-flat { color: var(--muted); }

/* ---- main grid: table beside the side panels ---- */
/* The main table and the side panels.

   Every panel here scrolls inside a bounded height rather than growing the
   page. Five regions and 322 outlets are the same component, and without a
   cap the Locations tab ran several screens long while the Regions tab left
   a hole beside the side column. A fixed frame fixes both at once, and the
   header stays put while the rows move under it. */
.dash-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 16px;
    align-items: start;
    margin-bottom: 16px;
}

.dash-split { margin-bottom: 16px; }

/* Sized so the default view fits without scrolling — five regions here, six
   rows in the compact cards — and anything longer (13 cities, 322 outlets)
   scrolls inside the same frame instead of stretching the page. */
.dash-grid > .card > .table-wrap { max-height: 490px; overflow-y: auto; }
/* The side column is sized to land near the main table's height, so the two
   end together instead of leaving a slab of empty page beside the shorter
   one. Both scroll inside, which is why they can be capped at all. */
.dash-side .table-wrap { max-height: 245px; overflow-y: auto; }
.dash-split .table-wrap { max-height: 460px; overflow-y: auto; }
.dash-side #insights { max-height: 175px; overflow-y: auto; }

/* Sticky headers, so a scrolled table still says which column is which. */
.dash-grid .table th,
.dash-side .table th,
.dash-split .table th {
    position: sticky;
    top: 0;
    z-index: 1;
}

/* The TOTAL line stays visible at the foot of a scrolling table — it is the
   number most people came for. */
.dash-grid .table .row-total td,
.dash-split .table .row-total td {
    position: sticky;
    bottom: 0;
    z-index: 1;
    border-top: 1px solid var(--border-strong);
}

.dash-side { display: flex; flex-direction: column; gap: 16px; }

/* Trend beside the donut: the chart needs the room, the donut does not. */
.dash-split {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}

/* Two tables of equal weight — five money columns each, so an uneven split
   clips the last one. */
.dash-split.is-even { grid-template-columns: repeat(2, minmax(0, 1fr)); }

@media (max-width: 1150px) {
    .dash-grid,
    .dash-split { grid-template-columns: minmax(0, 1fr); }
}

.dash-foot { margin: 4px 2px 24px; font-size: 12px; }

.dash-grid .table,
.dash-side .table,
.dash-split .table { font-size: 13px; }

.dash-grid .table th,
.dash-split .table th { padding: 9px 14px; font-size: 11.5px; }

.dash-grid .table td,
.dash-split .table td { padding: 8px 14px; }

.dash-side .table th { padding: 8px 10px; font-size: 11px; }
.dash-side .table td { padding: 8px 10px; }

/* Headers and the first column carry the labels — region, city, outlet,
   product. Bold makes them the frame the numbers hang off, rather than
   competing with them for attention. */
.dash-grid .table th,
.dash-split .table th,
.dash-side .table th { font-weight: 700; color: var(--text); }

.dash-grid .table td:first-child,
.dash-split .table td:first-child,
.dash-side .table td:first-child,
.dash-side .table .row-title { font-weight: 700; color: var(--text); }

.dash-grid .card-head,
.dash-split .card-head,
.dash-side .card-head { padding: 12px 16px; }

.dash-split .card-body,
.dash-side .card-body { padding: 0 16px 16px; }

.filter-bar { padding: 14px 16px 16px; gap: 14px; }

/* ---- Category Sales ----
   This screen is one dense grid, so it runs tighter than the rest: the filter
   bar is a single row and the table's own padding is cut back, because a
   month block of four categories should be readable without scrolling. */
.cat-filters {
    display: grid;
    /* From, To, Region, Category, Type, and the two buttons in one cell. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, .85fr)
                           minmax(0, .85fr) auto auto;
    gap: 12px;
    align-items: end;
    padding: 0;
}

@media (max-width: 1150px) {
    .cat-filters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 620px) {
    .cat-filters { grid-template-columns: minmax(0, 1fr); }
}

.cat-filters .card-title,
.cat-filters .label { margin-bottom: 2px; }

.month-pick { display: flex; align-items: center; gap: 5px; position: relative; }
.month-pick .input { flex: 1 1 auto; min-width: 0; padding: 7px 9px; }

/* The face and the real input.

   `<input type="month">` renders its own text and the browser chooses the
   wording — "August, 2026" here, and something else in another locale. It
   cannot be restyled, so the input stays for the picker it provides and this
   button in front of it carries the short label. The input is kept in the
   layout (not `display:none`) because a hidden element cannot open a picker. */
.month-face {
    text-align: center;
    font-weight: 600;
    cursor: pointer;
    white-space: nowrap;
}

.month-face:hover { border-color: var(--accent); color: var(--accent); }

.month-native {
    position: absolute;
    left: 50%;
    bottom: 0;
    width: 1px;
    height: 1px;
    padding: 0;
    border: 0;
    opacity: 0;
    pointer-events: none;
}

/* The graph/table switch beside the page title. Bigger than a row action —
   it is the way into the charts, not a tidy-up beside a table row — and its
   icon is drawn in the series colours rather than one flat stroke. */
.view-toggle {
    width: 42px;
    height: 42px;
    border-radius: var(--radius-sm);
}

.view-icon { width: 24px; height: 24px; display: block; }

/* Tinted, not filled: a solid accent background would swallow the coloured
   bars the icon exists for. */
.view-toggle.is-on {
    background: var(--accent-soft);
    border-color: var(--accent);
}

.view-toggle:hover { border-color: var(--accent); }

.vi-axis {
    fill: none;
    stroke: var(--text-2);
    stroke-width: 1.8;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* Set outright, not from the theme tokens: those are tuned for text and large
   areas, and at 24px the darker greens and ambers read as grey. An icon needs
   its colour to survive being small. */
.vi-bar-1 { fill: #7c3aed; }
.vi-bar-2 { fill: #10b981; }
.vi-bar-3 { fill: #f59e0b; }
[data-theme="dark"] .vi-bar-1 { fill: #a78bfa; }
[data-theme="dark"] .vi-bar-2 { fill: #34d399; }
[data-theme="dark"] .vi-bar-3 { fill: #fbbf24; }
.radio-row { display: flex; gap: 14px; align-items: center; min-height: 34px; }
.radio-row input { accent-color: var(--accent); }
/* The two buttons share one grid cell and sit on the same baseline as the
   inputs, instead of each claiming a row of its own. */
.cat-export {
    justify-self: end;
    flex-direction: row;
    align-items: end;
    gap: 8px;
}

/* The table is wide (a column per region) and long (a block per month), so it
   scrolls in both directions with the header and the Date column pinned. */
.cat-wrap { max-height: 620px; overflow: auto; }

/* Every cell is ruled. A grid this wide is read by tracking across a row and
   down a column, and hairlines between only some of them made that guesswork. */
.cat-table th,
.cat-table td {
    border: 1px solid var(--border-strong);
    padding: 7px 12px;
    font-size: 13px;
}

.cat-table th {
    position: sticky;
    top: 0;
    z-index: 2;
    font-weight: 700;
    color: var(--text);
    padding: 9px 12px;
}

.cat-table td.cat-date,
.cat-table th.cat-date {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--surface);
    vertical-align: middle;
    text-align: center;
    font-weight: 700;
    color: var(--text);
    white-space: nowrap;
}
.cat-table th.cat-date { z-index: 3; background: var(--surface-2); }
.cat-total-col { background: var(--surface-2); font-weight: 700; color: var(--text); }

/* Total rows carry a tint, not just bold text.

   They used to sit on `--surface-2` — #fafafa against a white table, a
   one-value difference that vanished. A month's total is the line most people
   scan for, so it gets a real background, and the grand total a stronger one
   so the two are told apart at a glance rather than by reading them. */
.cat-table .row-subtotal td,
.cat-table .row-total td {
    font-weight: 700;
    color: var(--text);
    border-top: 2px solid var(--border-strong);
}

/* One month's total. `--accent-soft` (#f3eeff) is too close to white to
   register as a fill in light mode, though it reads fine in dark mode, so
   light mode gets its own stronger tint rather than darkening the shared
   token — `--accent-soft` is used elsewhere as a pale hover/badge fill that
   must stay pale there. */
.cat-table .row-subtotal td { background: #e4d9fb; }
[data-theme="dark"] .cat-table .row-subtotal td { background: var(--accent-soft); }

/* Everything in view. Deeper, and ruled off above so it reads as the end of
   the table rather than another month. */
.cat-table .row-total td {
    background: var(--accent);
    color: var(--accent-text);
    border-top-width: 3px;
}

/* The Total column sets its own `--surface-2` background so it reads as a
   summary column; on a total row that would punch a plain hole through the
   coloured band, so the row wins there. (Neither total row carries a
   `.cat-date` cell, so the sticky Date column needs no such override.) */
.cat-table .row-subtotal td.cat-total-col { background: #e4d9fb; }
[data-theme="dark"] .cat-table .row-subtotal td.cat-total-col { background: var(--accent-soft); }

.cat-table .row-total td.cat-total-col {
    background: var(--accent);
    color: var(--accent-text);
}

/* Hover must not repaint a total row: it is a summary, not a row you act on. */
.cat-table tbody tr.row-subtotal:hover td { background: #e4d9fb; }
[data-theme="dark"] .cat-table tbody tr.row-subtotal:hover td { background: var(--accent-soft); }
.cat-table tbody tr.row-total:hover td { background: var(--accent); }

/* A chip per category, coloured from a fixed palette by position, so the same
   category is the same colour in every month block. */
.cat-chip {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 700;
    white-space: nowrap;
}

.chip-0 { background: var(--accent-soft); color: var(--accent); }
.chip-1 { background: var(--green-soft);  color: var(--green); }
.chip-2 { background: var(--amber-soft);  color: var(--amber); }
.chip-3 { background: var(--red-soft);    color: var(--red); }
.chip-4 { background: var(--accent-soft); color: var(--blue); }
.chip-5 { background: var(--green-soft);  color: var(--text-2); }
.chip-6 { background: var(--amber-soft);  color: var(--text-2); }
.chip-7 { background: var(--red-soft);    color: var(--text-2); }

/* ======================================================== skeleton loading ==
   A grey stand-in the shape of the thing that is coming, rather than a
   spinner. The page keeps its layout while it loads, so nothing jumps when
   the data lands — and the wait reads as "this is filling in" rather than
   "this is stuck".

   One shimmer, on a token colour, so both themes follow automatically.
   ======================================================================== */
.skel {
    position: relative;
    overflow: hidden;
    background: var(--surface-2);
    border-radius: var(--radius-sm);
}

.skel::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg,
                transparent, rgba(127, 127, 127, .13), transparent);
    animation: skel-sweep 1.25s ease-in-out infinite;
}

@keyframes skel-sweep {
    100% { transform: translateX(100%); }
}

/* Respect a reader who has asked the OS for less motion: the block still
   shows, it just stops sweeping. */
@media (prefers-reduced-motion: reduce) {
    .skel::after { animation: none; }
}

.skel-line { height: 11px; border-radius: 5px; }
.skel-line.is-sm { height: 9px; }
.skel-line.is-lg { height: 17px; }

/* A KPI card's worth: label, value, footnote. */
.skel-kpi { display: flex; flex-direction: column; gap: 8px; }
.skel-kpi .skel-line:nth-child(1) { width: 55%; }
.skel-kpi .skel-line:nth-child(2) { width: 78%; }
.skel-kpi .skel-line:nth-child(3) { width: 40%; }

/* A panel body — a chart, a donut, a stock summary. */
.skel-block { width: 100%; height: 190px; }

/* Table rows. The first cell is a label and the rest are numbers, so the
   first stand-in is wide and left-aligned and the others are short and
   right-aligned — the shape of the row that replaces it. */
.skel-cell .skel-line { width: 62%; }
.skel-cell.num { display: flex; justify-content: flex-end; }
.skel-cell.num .skel-line { width: 48px; }

/* ---- Promoter / visit reports ---- */
/* Seven controls on one row: the date needs its arrows, the rest share what
   is left, and the button gets a fixed slot so it never wraps alone. */
.pv-filters { grid-template-columns: 190px repeat(5, minmax(0, 1fr)) 82px; }

@media (max-width: 1350px) {
    .pv-filters { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .pv-actions { grid-column: 1 / -1; justify-content: flex-start; }
}

@media (max-width: 700px) {
    .pv-filters { grid-template-columns: minmax(0, 1fr); }
}

.pv-actions { flex-direction: row; align-items: end; justify-content: flex-end; }

/* A sortable header says so, and shows which way it is sorted. */
.pv-table th.is-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.pv-table th.is-sortable::after { content: " ⇅"; color: var(--muted); font-size: 10px; }
.pv-table th.is-asc::after { content: " ↑"; color: var(--accent); }
.pv-table th.is-desc::after { content: " ↓"; color: var(--accent); }

/* ---- the week calendar ---- */
/* A dropdown can only offer consecutive weeks. Real rounds skip weeks, so the
   choice is a calendar and the weeks it holds need not touch each other. */
.wk-field { position: relative; }

.wk-open { text-align: left; cursor: pointer; }
.wk-open:hover { border-color: var(--accent); }

.wk-cal {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 40;
    width: 268px;
    padding: 10px;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
}

.wk-cal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.wk-cal-month { font-size: 13px; font-weight: 700; }

.wk-dow,
.wk-week {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}

.wk-dow {
    margin-bottom: 4px;
    font-size: 10.5px;
    font-weight: 700;
    color: var(--muted);
    text-align: center;
}

.wk-weeks { display: flex; flex-direction: column; gap: 3px; }

/* The whole week is the target, not the day: a schedule is weekly, so a day
   is not a thing you can pick here. */
.wk-week {
    width: 100%;
    padding: 4px 2px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: none;
    font: inherit;
    cursor: pointer;
}

.wk-week:hover { background: var(--surface-2); border-color: var(--border-strong); }

/* A finished week is shown but cannot be picked — hiding it would leave a
   gap in the month and make the calendar harder to read, not easier. */
.wk-week.is-past { opacity: .38; cursor: not-allowed; }
.wk-week.is-past:hover { background: none; border-color: transparent; }
.wk-week.is-chosen { background: var(--accent-soft); border-color: var(--accent); }

/* The week on screen is always saved, so it reads as chosen and refuses to
   be toggled off — a dashed edge says "fixed", not "available". */
.wk-week.is-open {
    background: var(--accent-soft);
    border-style: dashed;
    border-color: var(--accent);
    cursor: default;
}

.wk-day { font-size: 11.5px; text-align: center; color: var(--text); line-height: 1.6; }
.wk-day.is-outside { color: var(--muted); opacity: .55; }
.wk-day.is-today { font-weight: 700; color: var(--accent); }

.wk-cal-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid var(--border);
}

/* The chosen weeks, spelled out under the selection so the choice survives
   closing the calendar. */
.wk-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }

.wk-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 6px 3px 10px;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 12px;
    font-weight: 600;
}

/* The week on screen: quiet, and it keeps its place in the row rather than
   being pulled to the front. */
.wk-chip.is-open { padding-right: 10px; background: var(--surface-2); color: var(--text-2); }
.wk-chip-here { font-weight: 600; }

/* No separate "edited" mark. Every week picked here is unsaved until Save, so
   the mark fired on all of them and said nothing; the badge already reports
   how many weeks are pending. Selected is the only state worth showing. */

/* The label is the way into that week, so it looks like a link and not like
   part of the chip's padding. */
.wk-chip-open {
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    font-weight: 600;
    padding: 0;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: transparent;
    text-underline-offset: 3px;
}

.wk-chip-open:hover { text-decoration-color: currentColor; }

.wk-chip-x {
    border: 0;
    background: none;
    color: inherit;
    font-size: 14px;
    line-height: 1;
    padding: 0 2px;
    cursor: pointer;
}

.wk-chip-x:hover { color: var(--red); }

/* ---- "Same outlets every day" ---- */
/* Sits between the card title and the visit count, so it reads as an option
   on the board rather than another filter up in the selection panel. */
.mirror-check {
    margin-right: auto;
    padding-left: 14px;
    font-size: 12.5px;
    color: var(--text-2);
    cursor: pointer;
    white-space: nowrap;
}

.mirror-check input { accent-color: var(--accent); }

/* A followed day is shown, not hidden: you can see what it will be, but its
   controls are gone because Monday owns them. */
.day-col.is-locked { opacity: .82; }
.day-col.is-locked .day-head { color: var(--muted); }

.day-locked {
    display: block;
    text-align: center;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--accent);
    padding: 7px 0;
}

/* A promoter's day is full at one outlet. Grey, not accent: this is the
   normal finished state of the day, not something needing attention. */
.day-full {
    display: block;
    text-align: center;
    font-size: 11.5px;
    color: var(--muted);
    padding: 7px 0;
}

/* ---- CheckSum / Item Sync ---- */
.cs-filters { grid-template-columns: 190px 160px 160px minmax(0, 1fr) 82px; }

@media (max-width: 1000px) {
    .cs-filters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .pv-actions { grid-column: 1 / -1; justify-content: flex-start; }
}

@media (max-width: 620px) {
    .cs-filters { grid-template-columns: minmax(0, 1fr); }
}

.cs-wrap { max-height: 620px; overflow: auto; }
.cs-wrap .pv-table th { position: sticky; top: 0; z-index: 2; }

/* The sign is the diagnosis: positive means the outlet counted rows the web
   never received, which is the direction that loses sales. */
.cs-diff { font-weight: 700; font-variant-numeric: tabular-nums; }
.cs-diff.is-up { color: var(--red); }
.cs-diff.is-down { color: var(--amber); }

.cs-kind {
    display: inline-block;
    padding: 1px 9px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    white-space: nowrap;
}

.cs-kind.is-mismatch { background: var(--amber-soft); color: var(--amber); }
.cs-kind.is-missing-web { background: var(--red-soft); color: var(--red); }
.cs-kind.is-missing-local { background: var(--accent-soft); color: var(--accent); }

/* ---- Attendance ---- */
.at-filters { grid-template-columns: 210px repeat(5, minmax(0, 1fr)) 82px; }

@media (max-width: 1400px) {
    .at-filters { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 700px) {
    .at-filters { grid-template-columns: minmax(0, 1fr); }
}

/* The week is shown, not typed — it always spans Monday to Sunday, so a free
   text field would only invite entering something that is not a week. */
.at-week {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    min-width: 0;
    white-space: nowrap;
    font-weight: 600;
    cursor: default;
}

.at-chip {
    display: inline-block;
    padding: 1px 9px;
    border-radius: 999px;
    font-size: 11.5px;
    font-weight: 700;
    white-space: nowrap;
}

.at-chip.type-5 { background: var(--accent-soft); color: var(--accent); }
.at-chip.type-4 { background: var(--green-soft);  color: var(--green); }

/* Still checked in. Amber, not red: it is a gap in the record, not a failure. */
.at-open { color: var(--amber); font-weight: 600; }

/* A merchandiser's day: the name and the date span their visits, and a rule
   across the top of each block keeps one person's round visually together. */
.pv-table td.pv-span {
    vertical-align: middle;
    background: var(--surface-2);
    border-right: 1px solid var(--border-strong);
}

.pv-table tr.pv-group-start > td { border-top: 1px solid var(--border-strong); }
.pv-table tbody tr:first-child > td { border-top: 0; }

/* The row hover must not repaint a cell that belongs to several rows —
   otherwise hovering one visit lights up the whole group's name cell. */
.pv-table tbody tr:hover td.pv-span { background: var(--surface-2); }

.pv-table tfoot .row-total td {
    font-weight: 700;
    background: var(--surface-2);
    border-top: 1px solid var(--border-strong);
}

/* The "20 items" badge. A button, not a chip: it opens the breakdown, and
   anything clickable should look like it can be clicked. */
.pv-badge {
    border: 0;
    border-radius: 999px;
    padding: 3px 11px;
    background: var(--accent-soft);
    color: var(--accent);
    font: inherit;
    font-size: 12px;
    font-weight: 700;
    cursor: pointer;
}

.pv-badge:hover { background: var(--accent); color: var(--accent-text); }

/* Sized to the text beside it, so the button does not set the row height. */
.pv-photo-btn {
    display: inline-grid;
    place-items: center;
    width: 26px;
    height: 26px;
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    color: var(--accent);
    cursor: pointer;
}

.pv-photo-btn:hover { background: var(--accent); color: var(--accent-text); }

.pv-foot-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.pv-pager { display: flex; align-items: center; gap: 8px; }
.pv-pager .icon-btn:disabled { opacity: .4; cursor: default; }

/* The product breakdown scrolls inside the modal, so a visit with sixty
   products does not push the total off the bottom of the screen. */
.pv-modal-body { max-height: 60vh; overflow-y: auto; }
.pv-modal-body thead th { position: sticky; top: 0; z-index: 1; background: var(--surface); }
.pv-modal-body tfoot .row-total td {
    position: sticky;
    bottom: 0;
    font-weight: 700;
    background: var(--surface-2);
    border-top: 1px solid var(--border-strong);
}

/* Photos: one large, the rest as thumbnails under it. `contain`, not `cover`
   — these are evidence shots, so the whole frame matters more than filling
   a neat rectangle. */
.pv-photo-main {
    display: grid;
    place-items: center;
    height: 340px;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    overflow: hidden;
}

.pv-photo-main img { max-width: 100%; max-height: 100%; object-fit: contain; }

.pv-photo-fail {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    color: var(--muted);
    font-size: 12.5px;
}

.pv-thumbs { display: flex; gap: 9px; margin-top: 10px; flex-wrap: wrap; }

.pv-thumb {
    width: 58px;
    height: 58px;
    padding: 0;
    border: 2px solid transparent;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    overflow: hidden;
    cursor: pointer;
}

.pv-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pv-thumb.is-active { border-color: var(--accent); }

/* ---- Cluster Matrix ---- */
.cm-filters { grid-template-columns: 160px repeat(4, minmax(0, 1fr)); }

@media (max-width: 1100px) {
    .cm-filters { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 620px) {
    .cm-filters { grid-template-columns: minmax(0, 1fr); }
}

/* One colour per tier, used by the card, the badge and the bar alike, so the
   tier is the same green everywhere it appears. */
.tier-HTP  { --tier: var(--green);  --tier-soft: var(--green-soft); }
.tier-MTP  { --tier: var(--blue);   --tier-soft: var(--accent-soft); }
.tier-LTP  { --tier: var(--amber);  --tier-soft: var(--amber-soft); }
.tier-ULTP { --tier: var(--red);    --tier-soft: var(--red-soft); }

.cm-tier { background: var(--tier-soft); border-color: var(--tier); }
.cm-tier-head { display: flex; align-items: center; gap: 7px; }
.cm-tier-code { font-size: 14px; font-weight: 700; color: var(--text); }
.cm-tier-slab { font-size: 17px; font-weight: 700; color: var(--tier); margin: 2px 0; }
.cm-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--tier); flex: none; }

.cm-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 9px;
    border-radius: 999px;
    background: var(--tier-soft);
    color: var(--tier);
    font-size: 11.5px;
    font-weight: 700;
}

.cm-wrap { max-height: 620px; overflow: auto; }
.cm-table th { position: sticky; top: 0; z-index: 2; }
.cm-row { cursor: pointer; }
.cm-row.is-open > td { background: var(--surface-2); }
.cm-caret { width: 34px; color: var(--muted); }
.cm-products { color: var(--text-2); font-size: 12px; }

/* The expanded outlet list sits in a full-width cell, so its own columns are
   free to differ from the cluster row's above it. */
.cm-detail-cell { padding: 0 !important; background: var(--surface-2); }
.cm-detail { padding: 10px 14px 14px; }
.cm-detail-head { font-size: 11.5px; color: var(--muted); margin-bottom: 8px; }
.cm-inner-wrap { max-height: 330px; overflow-y: auto; background: var(--surface);
                 border: 1px solid var(--border); border-radius: var(--radius-sm); }
.cm-inner th { position: sticky; top: 0; z-index: 1; background: var(--surface-2); }
.cm-inner td, .cm-inner th { padding: 5px 12px; font-size: 12px; }

.cm-bar { height: 8px; border-radius: 999px; background: var(--border); overflow: hidden;
          min-width: 160px; }
.cm-bar > span { display: block; height: 100%; background: var(--tier); }

/* ---- Sales Report ---- */
/* Seven fields, seven columns, one row: Month and Type need their own width
   (a two-option radio row does not shrink), the four pickers share what is
   left, and the buttons get a fixed slot rather than wrapping onto their own
   line when the row runs out of room. */
.sr-filters { grid-template-columns: 190px 205px repeat(4, minmax(0, 1fr)) 152px; }

@media (max-width: 1350px) {
    .sr-filters { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .sr-actions { grid-column: 1 / -1; justify-content: flex-start; }
}

@media (max-width: 700px) {
    .sr-filters { grid-template-columns: minmax(0, 1fr); }
}

.sr-actions { flex-direction: row; align-items: end; gap: 8px; justify-content: flex-end; }
.radio-row { flex-wrap: nowrap; white-space: nowrap; }

/* A column per product, so the frame scrolls in both directions with the
   header and the S.No column pinned — the same pattern as Category Sales.
   The header carries the whole product name sideways; keeping the column
   narrow means it reads as a label, not a block wasting the row's height. */
.sr-wrap { max-height: 640px; }
.sr-table th.sr-col {
    min-width: 40px;
    max-width: 40px;
    writing-mode: vertical-rl;
    text-align: left;
    font-size: 10.5px;
    padding: 8px 4px;
}
.sr-table td.row-title { white-space: nowrap; }

/* ---- Product Analysis ---- */
/* The month column is fixed and wide enough for "August, 2026" between its two
   arrows — a month input that ellipsises its own value is worse than useless. */
.pa-filters { grid-template-columns: 232px repeat(5, minmax(0, 1fr)); }

@media (max-width: 1250px) {
    .pa-filters { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 700px) {
    .pa-filters { grid-template-columns: minmax(0, 1fr); }
}

.pa-matrix-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* One line under the card head saying how to read the table, because the same
   frame shows two different things depending on the switch beside it. */
.pa-note { margin: -2px 0 8px; padding: 0 14px; font-size: 11px; color: var(--muted); }

/* The city header spans its columns; the periods sit under it. */
.cat-table th.pa-group {
    text-align: center;
    background: var(--accent-soft);
    color: var(--accent);
}

.cat-table th.pa-sub { font-size: 10.5px; font-weight: 600; }

/* Ranked view: the row is a position, not a product, so the first column is a
   number and each city carries its own name column. */
.cat-table .rank-col { min-width: 44px; width: 44px; }
.cat-table.is-ranked td.rank-name { min-width: 138px; font-weight: 600; color: var(--text); }
.cat-table.is-ranked td.rank-name .row-sub { font-weight: 400; }

/* A two-state switch — Sales or Qty. Not two buttons: only one can be true. */
.seg {
    display: inline-flex;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    overflow: hidden;
}

.seg-btn {
    border: 0;
    background: var(--surface);
    color: var(--text-2);
    font: inherit;
    font-size: 12px;
    font-weight: 600;
    padding: 5px 14px;
    cursor: pointer;
}

.seg-btn + .seg-btn { border-left: 1px solid var(--border-strong); }
.seg-btn:hover { background: var(--surface-2); }
.seg-btn.is-active { background: var(--accent); color: var(--accent-text); }

/* Stock summary: one proportion and four counts, and they are not the same
   kind of thing. The proportion gets a bar, because a percentage is read
   against its own scale; the counts get a plain grid, so every label sits on
   one baseline and every figure in one column. The earlier version stacked a
   centred hero on four full-width rows, and nothing lined up with anything. */
.stock-avail {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    padding: 10px 12px;
    margin-bottom: 10px;
}

.stock-avail-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
}

.stock-avail-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
    color: var(--muted);
}

.stock-pct { font-size: 22px; font-weight: 700; color: var(--accent); line-height: 1.1; }
.stock-avail-foot { font-size: 10.5px; color: var(--muted); margin-top: 5px; }

.stock-bar {
    height: 6px;
    border-radius: 999px;
    background: var(--border);
    overflow: hidden;
    margin-top: 7px;
}

.stock-bar > span { display: block; height: 100%; background: var(--accent); }

.stock-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
}

.stock-cell {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 8px 10px;
}

.stock-cell-label {
    font-size: 10px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--muted);
}

.stock-cell-value { font-size: 15px; font-weight: 700; margin-top: 2px; }
.stock-cell-sub { font-size: 10.5px; color: var(--muted); margin-top: 1px; }

@media (max-width: 520px) {
    .stock-grid { grid-template-columns: minmax(0, 1fr); }
}

.cell-sub { font-size: 11px; color: var(--muted); }
.row-total td { font-weight: 700; background: var(--surface-2); }

/* The side panel is narrow, so give the product name the room and keep the
   three number columns to what their digits actually need — otherwise every
   name wraps to four lines and the panel runs twice the height of the table
   it sits beside. */
#low-rows td:first-child,
#low-rows + thead th:first-child { min-width: 150px; }
.dash-side .table th,
.dash-side .table td { padding-left: 10px; padding-right: 10px; }
.dash-side .table th.num,
.dash-side .table td.num { white-space: nowrap; width: 1%; }

/* ---- insights ---- */
.insight {
    display: flex;
    gap: 9px;
    align-items: flex-start;
    padding: 8px 0;
    font-size: 12.5px;
    line-height: 1.4;
    border-bottom: 1px solid var(--border);
}

.insight:last-child { border-bottom: 0; }
.insight-ico { color: var(--accent); flex: none; margin-top: 2px; }

/* ---- charts (inline SVG; no chart library, the CSP forbids one) ---- */
.chart { width: 100%; height: 210px; display: block; }
.chart-grid { stroke: var(--border); stroke-width: 1; }
.chart-axis { fill: var(--muted); font-size: 10px; }

/* ---- Category Sales, graph view ---- */
/* Taller than the app's other charts: nine categories over a year of months
   is a lot of line to separate, and a short chart turns it into a smudge. */
.cg-chart { width: 100%; height: auto; display: block; }
.cg-wrap { padding-top: 4px; }

.cg-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 10px;
}

.cg-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 16px;
    margin-top: 12px;
    font-size: 12px;
    color: var(--text-2);
}

.cg-key { display: inline-flex; align-items: center; gap: 6px; }
.cg-dot { width: 10px; height: 10px; border-radius: 3px; flex: none; }

.cg-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.cg-mark { stroke: var(--surface); stroke-width: 1.2; }
.cg-bar { stroke: none; }

/* Small multiples: one chart per category, months across, a bar per region.
   A grid rather than one big chart, because nine categories on one axis is a
   tangle and the comparison being asked for lives INSIDE a category. */
.cg-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
    gap: 12px;
}

.cg-cell {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    padding: 10px 12px 4px;
}

.cg-cell-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 4px;
}

.cg-cell-name {
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--text);
}

.cg-cell-total { font-size: 11.5px; font-weight: 700; color: var(--accent); }
.cg-small { width: 100%; height: auto; display: block; }

/* The all-in-one chart carries 20 stacked bars and two rows of labels, so its
   viewBox is taller; the width comes from the container either way. */
.cg-tall { width: 100%; height: auto; display: block; }

/* A faint band per alternate month: five bars read as one month without the
   eye having to count them. */
.cg-band { fill: var(--border); fill-opacity: .30; }
[data-theme="dark"] .cg-band { fill: var(--surface-2); fill-opacity: .55; }

.cg-cap { font-size: 9px; fill: var(--text-2); }

/* A small multiple is a thumbnail; clicking it opens the real thing. Saying
   so with the cursor and a lift on hover, because an element that does
   something has to look like it does something. */
.cg-cell.is-clickable { cursor: zoom-in; transition: border-color .12s ease, transform .12s ease; }
.cg-cell.is-clickable:hover { border-color: var(--accent); transform: translateY(-1px); }

.cg-zoom {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: grid;
    place-items: center;
    padding: 24px;
    background: rgba(10, 10, 12, .62);
    backdrop-filter: blur(2px);
}

.cg-zoom-panel {
    width: min(1240px, 100%);
    max-height: 92vh;
    overflow: auto;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    padding: 16px 20px 18px;
}

.cg-zoom-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 10px;
}

.cg-zoom-name {
    font-size: 15px;
    font-weight: 700;
    letter-spacing: .05em;
    text-transform: uppercase;
    color: var(--text);
}

.cg-zoom-sub { font-size: 12px; color: var(--muted); margin-top: 2px; }
.cg-zoom-right { display: flex; align-items: center; gap: 12px; }
.cg-zoom-total { font-size: 17px; font-weight: 700; color: var(--accent); }
.cg-zoom-body { background: var(--surface-2); border-radius: var(--radius-sm); padding: 10px; }
.cg-zoom-chart { width: 100%; height: 58vh; min-height: 340px; display: block; }
.cg-zoom-panel .cg-bar { stroke: var(--surface-2); }
.cg-region-tick { font-size: 9px; }
.cg-month-tick { font-size: 11.5px; font-weight: 700; fill: var(--text); }

/* Eight series colours, reused by the dot, the line and the bar so one series
   is one colour wherever it appears. Deliberately not the chip palette: chips
   tint a background, these have to stand alone as a 2px line.

   Light mode gets the deeper end of each hue. The first pass used the same
   values in both themes, and colours picked to glow on near-black came out
   washed and candy-ish on white — a chart has to look deliberate on paper
   too. */
.series-0 { --series: #6d28d9; }
.series-1 { --series: #0369a1; }
.series-2 { --series: #15803d; }
.series-3 { --series: #b45309; }
.series-4 { --series: #b91c1c; }
.series-5 { --series: #be185d; }
.series-6 { --series: #0f766e; }
.series-7 { --series: #4338ca; }

[data-theme="dark"] .series-0 { --series: #a78bfa; }
[data-theme="dark"] .series-1 { --series: #38bdf8; }
[data-theme="dark"] .series-2 { --series: #4ade80; }
[data-theme="dark"] .series-3 { --series: #fbbf24; }
[data-theme="dark"] .series-4 { --series: #f87171; }
[data-theme="dark"] .series-5 { --series: #f472b6; }
[data-theme="dark"] .series-6 { --series: #2dd4bf; }
[data-theme="dark"] .series-7 { --series: #c4b5fd; }

.cg-line { stroke: var(--series); }
.cg-mark { fill: var(--series); }
.cg-bar { fill: var(--series); }
.cg-dot { background: var(--series); }

/* Light mode: the chart sits on its own tinted panel rather than bare white.
   On a white page the gridlines either vanish or turn into hard rules, and
   the plot area had no edge at all — the numbers floated. A faint ground
   gives the axes something to sit on and the bars something to sit against. */
.cg-wrap {
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 12px 14px 14px;
}

[data-theme="dark"] .cg-wrap { background: transparent; border-color: transparent; padding: 4px 0 0; }

.cg-wrap .cg-cell { box-shadow: var(--shadow-sm); }
[data-theme="dark"] .cg-wrap .cg-cell { box-shadow: none; background: var(--surface-2); }

/* Gridlines want to be visible but never louder than the data. */
.cg-chart .chart-grid,
.cg-small .chart-grid { stroke: var(--border-strong); stroke-opacity: .55; }
[data-theme="dark"] .cg-chart .chart-grid,
[data-theme="dark"] .cg-small .chart-grid { stroke: var(--border); stroke-opacity: 1; }

.cg-chart .chart-axis,
.cg-small .chart-axis { fill: var(--text-2); }

/* A hairline of the page colour between stacked segments and grouped bars, so
   two similar hues never read as one block. */
.cg-bar { stroke: var(--surface); stroke-width: .75; }
[data-theme="dark"] .cg-bar { stroke: var(--surface-2); }
.cg-wrap .cg-bar { stroke: var(--surface-2); }
[data-theme="dark"] .cg-wrap .cg-bar { stroke: var(--surface); }
.chart-bar { fill: var(--accent); opacity: .85; }
.chart-bar:hover { opacity: 1; }
.chart-line { fill: none; stroke: var(--amber); stroke-width: 2; }

.chart-legend {
    display: flex;
    align-items: center;
    gap: 8px;
    justify-content: center;
    margin-top: 10px;
    font-size: 12px;
    color: var(--muted);
}

.legend-dot {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    display: inline-block;
    background: var(--accent);
}

.legend-dot.is-bar { background: var(--accent); }
.legend-dot.is-line { background: var(--amber); }
.legend-dot.slice-0 { background: var(--accent); }
.legend-dot.slice-1 { background: var(--blue); }
.legend-dot.slice-2 { background: var(--amber); }
.legend-dot.slice-3 { background: var(--green); }

.donut { width: 160px; height: 160px; display: block; margin: 0 auto; }
.donut-track { fill: none; stroke: var(--surface-2); stroke-width: 26; }
.donut-arc { fill: none; stroke-width: 26; }
.donut-arc.slice-0 { stroke: var(--accent); }
.donut-arc.slice-1 { stroke: var(--blue); }
.donut-arc.slice-2 { stroke: var(--amber); }
.donut-arc.slice-3 { stroke: var(--green); }

.donut-legend { margin-top: 12px; display: flex; flex-direction: column; gap: 7px; }
.donut-row { display: flex; align-items: center; gap: 9px; font-size: 13px; }
.donut-name { flex: 1 1 auto; }
.donut-pct { font-weight: 700; }

/* ---------------------------------------------------------------- toasts */
.toasts {
    position: fixed;
    right: 20px;
    bottom: 20px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    z-index: 80;
}

.toast {
    min-width: 250px;
    max-width: 390px;
    padding: 12px 16px;
    border-radius: var(--radius-sm);
    background: var(--text);
    color: var(--bg);
    font-size: 13.5px;
    font-weight: 500;
    box-shadow: var(--shadow);
    animation: toast-in .18s ease;
}

.toast.is-ok { background: var(--green); color: #fff; }
.toast.is-error { background: var(--red); color: #fff; }
/* Done, but not entirely as asked — something was skipped and the message
   says which. Amber, because it needs reading, not alarming. */
.toast.is-warn { background: var(--amber); color: #fff; }

@keyframes toast-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
}

/* ------------------------------------------------------------ empty/load */
.empty { padding: 48px 20px; text-align: center; color: var(--muted); }
.empty-title { color: var(--text); font-weight: 600; margin-bottom: 4px; }

.spinner {
    display: inline-block;
    width: 18px;
    height: 18px;
    border: 2px solid var(--border-strong);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: spin .7s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* --------------------------------------------------- permission matrix */
.matrix th.perm,
.matrix td.perm { text-align: center; width: 84px; }

.matrix tbody tr.group-row td {
    background: var(--surface-2);
    font-weight: 700;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: .07em;
    color: var(--text-2);
    padding-top: 10px;
    padding-bottom: 10px;
}

.matrix tbody tr.group-row:hover td { background: var(--surface-2); }

/* -------------------------------------------------- schedule assignment */
/* The filter row above the week. Fields size themselves; the week stepper
   sits at the end and never wraps mid-control. */
.selection-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    align-items: flex-end;
}

.selection-grid .field { min-width: 170px; flex: 0 1 auto; }

.week-nav {
    display: flex;
    align-items: center;
    gap: 8px;
}

.week-label {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 14px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    font-size: 13.5px;
    font-weight: 600;
    white-space: nowrap;
}

.week-label .ico { color: var(--muted); }

/* Seven equal columns that collapse to a horizontal scroll rather than
   squeezing a day into unreadability. */
.week-grid {
    display: grid;
    grid-template-columns: repeat(7, minmax(150px, 1fr));
    gap: 14px;
}

.day-col {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    overflow: hidden;
}

.day-col.is-today { border-color: var(--accent); }

.day-head {
    padding: 11px 13px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--border);
}

.day-name {
    display: block;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: .07em;
    text-transform: uppercase;
}

.day-date {
    display: block;
    color: var(--accent);
    font-size: 12px;
    font-weight: 600;
}

.day-body {
    flex: 1;
    min-height: 120px;
    padding: 11px;
    display: flex;
    flex-direction: column;
    gap: 7px;
}

.day-empty {
    margin: auto;
    color: var(--muted);
    font-size: 12.5px;
    font-style: italic;
}

/* One assigned outlet inside a day. */
.visit {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 9px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
}

.visit-order {
    display: grid;
    place-items: center;
    width: 19px;
    height: 19px;
    border-radius: 50%;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 10.5px;
    font-weight: 700;
    flex: none;
}

.visit-text { flex: 1; min-width: 0; }

.visit-name {
    display: block;
    font-size: 12.5px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.visit-city {
    display: block;
    color: var(--muted);
    font-size: 11px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.visit-remove {
    border: 0;
    background: none;
    color: var(--muted);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
    padding: 0 2px;
    flex: none;
}

.visit-remove:hover { color: var(--red); }

.day-foot { padding: 0 11px 11px; }

.day-add {
    width: 100%;
    justify-content: center;
    border-style: dashed;
}

/* The outlet picker, anchored under the day it was opened from. */
.picker {
    position: absolute;
    z-index: 50;
    width: 300px;
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

.picker-head { padding: 11px; border-bottom: 1px solid var(--border); }

.picker-list { max-height: 260px; overflow-y: auto; padding: 6px; }

.picker-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 9px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 13.5px;
}

.picker-item:hover { background: var(--surface-2); }

.picker-item input { width: 16px; height: 16px; accent-color: var(--accent); flex: none; }

.picker-item-text { min-width: 0; }

.picker-item-name {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.picker-item-city { display: block; color: var(--muted); font-size: 11.5px; }

.picker-foot {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 9px;
    padding: 10px 11px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
}

/* --------------------------------------------------- weekly schedule view */
/* A day cell holds either one outlet, several, or nothing. "Several" shows
   the first plus a count, and expands in place rather than in a popover —
   the table is already wide, and a floating panel would cover the next row. */
.no-visit {
    color: var(--muted);
    font-style: italic;
    font-size: 13px;
}

.cell-more {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 9px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: 13px;
    cursor: pointer;
    max-width: 100%;
}

.cell-more:hover { background: var(--surface-2); }

.cell-more-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cell-more-count { color: var(--accent); font-weight: 600; flex: none; }

.cell-more .ico {
    width: 14px;
    height: 14px;
    opacity: .6;
    flex: none;
    transition: transform .15s ease;
}

.cell-more.is-open .ico { transform: rotate(180deg); }

.cell-list {
    display: none;
    flex-direction: column;
    gap: 3px;
    margin-top: 6px;
}

.cell-list.is-open { display: flex; }

.cell-list-item {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 12.5px;
    color: var(--text-2);
}

.cell-list-item .visit-order { width: 17px; height: 17px; font-size: 10px; }

/* Sortable column headings. */
.th-sort {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    font-weight: 600;
    font-size: 13px;
    color: var(--text-2);
    cursor: pointer;
}

.th-sort:hover { color: var(--text); }

.th-sort .ico { width: 13px; height: 13px; opacity: .45; }
.th-sort.is-sorted { color: var(--accent); }
.th-sort.is-sorted .ico { opacity: 1; }

/* ------------------------------------------------------------ sign in */
/* frontend/login.html only. It does not extend base.html, so it brings its
   own centred layout rather than reusing .shell. */
.auth-body {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 24px;
    background:
        radial-gradient(1100px 460px at 50% -12%, var(--accent-soft) 0%, transparent 62%),
        var(--bg);
}

.auth-panel { width: 100%; max-width: 400px; }

.auth-brand { text-align: center; margin-bottom: 24px; }

.auth-mark {
    display: grid;
    place-items: center;
    width: 52px;
    height: 52px;
    border-radius: 15px;
    background: var(--accent);
    color: var(--accent-text);
    margin: 0 auto 15px;
}

.auth-mark .ico { width: 27px; height: 27px; }

.auth-name { font-size: 22px; font-weight: 700; letter-spacing: -.02em; }
.auth-tag { margin: 5px 0 0; color: var(--text-2); }

.auth-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 28px;
}

.auth-error {
    margin-bottom: 17px;
    padding: 11px 14px;
    border-radius: var(--radius-sm);
    background: var(--red-soft);
    color: var(--red);
    font-size: 13px;
    font-weight: 500;
}

.auth-submit { width: 100%; margin-top: 22px; padding: 11px; font-size: 14.5px; }

.auth-foot { margin: 20px 0 0; text-align: center; color: var(--muted); font-size: 12px; }

.auth-powered {
    margin: 6px 0 0;
    text-align: center;
    color: var(--muted);
    font-size: 12px;
}

.auth-powered strong { color: var(--text-2); font-weight: 700; }

/* The eye sits inside the field, so the input keeps its full width and the
   button never reflows the form when its icon swaps. */
.pw-wrap { position: relative; }
.pw-wrap .input { padding-right: 42px; width: 100%; }

.pw-eye {
    position: absolute;
    top: 50%;
    right: 6px;
    transform: translateY(-50%);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    border-radius: var(--radius-sm);
    background: none;
    color: var(--muted);
    cursor: pointer;
}

.pw-eye:hover { color: var(--text); background: var(--surface-2); }
.pw-eye .ico { width: 17px; height: 17px; }

/* ------------------------------------------------------------ responsive */
@media (max-width: 900px) {
    /* The menu is off-canvas by default here; the same button reveals it. */
    .sidebar { transform: translateX(-100%); box-shadow: var(--shadow-lg); }
    .shell { margin-left: 0; }

    body.nav-open .sidebar { transform: translateX(0); }
    body.nav-collapsed .sidebar { transform: translateX(-100%); }

    .topbar-user .user-meta { display: none; }
    .content { padding: 18px; }
    .page-title { font-size: 21px; }

    /* Now the actions may wrap - there is genuinely no room beside the title. */
    .page-head { align-items: flex-start; }
    .page-actions { flex-wrap: wrap; }
    .search { width: 100%; }
}

/* --------------------------------------------------------------------------
   Reports -> Product Report
   -------------------------------------------------------------------------- */
.pa-filters { grid-template-columns: 150px 150px 190px repeat(2, minmax(0, 1fr)) 200px 152px; }

@media (max-width: 1450px) {
    .pa-filters { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .pa-actions { grid-column: 1 / -1; justify-content: flex-start; }
}

@media (max-width: 700px) {
    .pa-filters { grid-template-columns: minmax(0, 1fr); }
}

.pa-actions { flex-direction: row; align-items: end; gap: 8px; justify-content: flex-end; }

.pa-head-right { display: flex; align-items: center; gap: 14px; margin-left: auto; }
.pa-only { white-space: nowrap; font-size: 12px; }

.pa-wrap { max-height: 660px; }
.pa-table td, .pa-table th { vertical-align: middle; }
.pa-code { font-family: var(--mono, ui-monospace, monospace); font-size: 11.5px; color: var(--text-2); }

/* A region header carries its name over the two counts that make a column of
   crosses readable — no sales across 13 outlets is a distribution gap, across
   one outlet it is noise. */
.pa-table th.pa-col { text-align: center; min-width: 122px; }
.pa-col-name { display: block; font-weight: 700; }
.pa-col-sub {
    display: block;
    font-weight: 400;
    font-size: 9.5px;
    color: var(--text-3, var(--text-2));
    margin-top: 2px;
    text-transform: none;
    letter-spacing: 0;
}

/* The answer cell. Colour does the scanning and the glyph does the saying —
   neither alone, because colour is unreadable to some people and a bare tick
   in a 63-row grid is hard to count down a column. */
.pa-cell { text-align: center; padding: 0; }
.pa-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
}
/* Deeper than the shared `--green-soft` — that tint is pale by design for
   badges and hovers, and washed out under a whole column of ticks. */
.pa-cell.is-yes { background: #bdedd0; }
[data-theme="dark"] .pa-cell.is-yes { background: #1c4a2c; }
.pa-cell.is-yes .pa-mark { color: var(--green); }
.pa-cell.is-no .pa-mark { color: var(--red); opacity: .55; }

/* A product available nowhere is the row worth acting on, so it is marked
   once on the left rather than by its five identical crosses. */
.pa-table tr.pa-nowhere td.row-title { color: var(--red); }
.pa-table tr.pa-nowhere td.cat-date {
    box-shadow: inset 3px 0 0 var(--red);
}

.pa-tally { color: var(--text-2); white-space: nowrap; font-size: 11.5px; }

/* The generic `.table tbody tr:hover` fill (rule near the top of this file)
   repaints every row as the mouse crosses it. On a grid read column by column
   rather than row by row, that band is a distraction rather than a cue, so
   this table opts out. The tick cells need their own restatement: the
   generic rule's selector (.table tbody tr:hover td) out-specifies
   `.pa-cell.is-yes` by element count even though this one has more classes,
   so without it hovering wipes the green fill back to plain grey. */
.pa-table tbody tr:hover td { background: transparent; }
.pa-table tbody tr:hover td.pa-cell.is-yes { background: #bdedd0; }
[data-theme="dark"] .pa-table tbody tr:hover td.pa-cell.is-yes { background: #1c4a2c; }

/* --------------------------------------------------------------------------
   Reports -> Unassigned Outlets
   -------------------------------------------------------------------------- */
.uo-filters { grid-template-columns: 150px repeat(3, minmax(0, 1fr)) 140px 180px 130px; }

@media (max-width: 1550px) {
    .uo-filters { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .uo-actions { grid-column: 1 / -1; justify-content: flex-start; }
}

@media (max-width: 700px) {
    .uo-filters { grid-template-columns: minmax(0, 1fr); }
}

.uo-actions { flex-direction: row; align-items: end; gap: 8px; justify-content: flex-end; }

/* A day-by-day grid: Outlet + City fixed, then a Promoter/Merchandiser pair
   of columns per day of the month (up to 31 days = 62 data columns), so the
   frame scrolls horizontally with Outlet pinned — the one column worth
   always having in view while scrolling through the month. City is worth
   seeing but not worth a second stacked sticky column's complexity, so it
   scrolls with the grid rather than staying pinned beside Outlet. */
.uo-wrap { max-height: 640px; }
.uo-grid th, .uo-grid td { white-space: nowrap; }
.uo-grid th { font-weight: 700; }

.uo-col-outlet {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--surface);
    min-width: 160px;
    max-width: 160px;
    font-weight: 700;
}
.uo-grid thead .uo-col-outlet { z-index: 4; background: var(--surface-2); }
.uo-col-city { min-width: 110px; max-width: 110px; }

/* Each day's two sub-columns are as narrow as a tick/cross needs — dense on
   purpose, since up to 31 of these sit side by side. A right border on the
   second (M) column marks where one day ends and the next begins, since
   nothing else in a grid this dense would show it. */
.uo-day-head { text-align: center; height: 36px; }
.uo-role-head {
    min-width: 30px;
    max-width: 30px;
    text-align: center;
    font-size: 10.5px;
    padding: 4px 2px;
    height: 28px;
}
.uo-role-head.is-last, .uo-cell.is-last { border-right: 2px solid var(--border-strong); }

/* Both header rows freeze while scrolling down through up to 322 outlets.
   .uo-wrap is its own scroll container (it sets max-height, and the
   .table-wrap base class's overflow-x: auto silently computes overflow-y:
   auto too, per the CSS overflow spec's "one axis visible + one not ->
   both auto" rule) -- so these stick to the TOP OF .uo-wrap's scrollport,
   the same convention already used for .dash-grid .table th, not to the
   page or the app's sticky top bar. Two rows means two `top` offsets: the
   day-number row sits at 0, the P/M row sits right under it at 36px --
   .uo-day-head's own height, set explicitly above so this arithmetic has
   something fixed to add to. The Outlet header is frozen in both
   directions at once (left AND top), so it needs the highest z-index of
   anything on the screen or a plain sticky-left body cell scrolling past
   underneath would cover half of it. */
.uo-grid thead th {
    position: sticky;
    z-index: 2;
}
.uo-grid thead tr:first-child th { top: 0; }
.uo-grid thead tr:last-child th { top: 36px; }

.uo-cell {
    text-align: center;
    padding: 4px 2px;
    font-size: 12.5px;
    font-weight: 700;
    cursor: default;
}

/* A background tint per cell, not just a coloured glyph — at this density
   (up to 62 of these across one row) a bare tick/cross reads as one grey
   texture until you look closely. Deeper than the shared --green-soft/
   --red-soft tokens: those are tuned pale for a badge sitting on its own,
   and wash out tiled edge-to-edge across a whole row the same way
   --surface-2 did on the Category Sales totals row before. */
.uo-cell.is-yes { background: #c8f0d7; color: var(--green); }
.uo-cell.is-no  { background: #fbd7d7; color: var(--red); }
[data-theme="dark"] .uo-cell.is-yes { background: #1c4a2c; }
[data-theme="dark"] .uo-cell.is-no  { background: #4a1c1c; }

/* The generic `.table tbody tr:hover` rule (near the top of this file)
   repaints every cell in the row as the mouse crosses it — on a grid this
   dense that reads as the whole row flashing grey, and it washes out the
   tick/cross tint along with it. #uo-table's id gives these enough
   specificity to win outright, no source-order games needed. The sticky
   Outlet column is the one cell that must keep an opaque background
   regardless of hover — it hides whatever day columns have scrolled
   underneath it, and turning transparent there would let them show through. */
#uo-table tbody tr:hover td { background: transparent; }
#uo-table tbody tr:hover td.uo-col-outlet { background: var(--surface); }
#uo-table tbody tr:hover td.uo-cell.is-yes { background: #c8f0d7; }
#uo-table tbody tr:hover td.uo-cell.is-no { background: #fbd7d7; }
[data-theme="dark"] #uo-table tbody tr:hover td.uo-cell.is-yes { background: #1c4a2c; }
[data-theme="dark"] #uo-table tbody tr:hover td.uo-cell.is-no { background: #4a1c1c; }

