/*
 * The whole panel's styling.
 *
 * One hand-written file rather than a framework. This is an operator's tool with two screens
 * on it, used by one person on a desktop browser, and a CSS framework would be a build step,
 * a dependency and a download in exchange for a grid and some table borders.
 *
 * Dark by default and not switchable. There is nobody to disagree with the choice, and a
 * theme toggle is a preference to store, a flash to prevent and a second palette to keep
 * correct — all for a page nobody looks at for pleasure.
 */

/*
 * Vazirmatn, self-hosted as a single variable file (100–900 in one download, Persian +
 * Latin in the same face) rather than pulled from Google Fonts — this panel has no build
 * step and no guarantee of internet access at the moment it's opened. font-display: swap
 * shows Tahoma immediately and swaps in Vazirmatn once it lands, so a cold cache never
 * blanks the page.
 */
@font-face {
    font-family: "Vazirmatn";
    src: url("/admin/fonts/Vazirmatn-Variable.woff2") format("woff2-variations"),
         url("/admin/fonts/Vazirmatn-Variable.woff2") format("woff2");
    font-weight: 100 900;
    font-style: normal;
    font-display: swap;
}

:root {
    /*
     * Dark Mode OLED — deep slate, green ops accent, status colours that scan at a glance.
     * Tuned from the ui-ux-pro-max design system for an operator SaaS dashboard; Vazirmatn
     * stays the body face (self-hosted), so nothing here pulls Google Fonts.
     */
    --bg: #020617;
    --bg-glow: #0f1f17;
    --panel: #0f172a;
    --panel-2: #1e293b;
    --line: #334155;
    --line-strong: #475569;
    --text: #f8fafc;
    --muted: #94a3b8;
    /* CTA / active / KPI glow — green for ops-positive, not the old info blue. */
    --accent: #22c55e;
    --accent-soft: rgba(34, 197, 94, 0.16);
    --accent-glow: 0 0 20px rgba(34, 197, 94, 0.18);
    /* Links and informational chrome that should not read as "success". */
    --info: #38bdf8;
    --info-soft: rgba(56, 189, 248, 0.14);
    --up: #22c55e;
    --down: #ef4444;
    --warn: #f59e0b;
    --radius: 12px;
    --shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
    --shadow-lift: 0 10px 28px rgba(0, 0, 0, 0.45);

    /* One 4px-based scale, named instead of re-typed at each call site. */
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;

    /*
     * The reading measure, and it applies to prose only.
     *
     * This used to be a max-width on .main — one cap over the whole content column, prose
     * and tables alike. That is the wrong place for it. A paragraph past about eighty
     * characters is genuinely harder to read, which is what the cap was for; a table is not
     * prose and does not get easier to read by being made narrower, it gets a scrollbar. The
     * pricing grid and the for-sale table were both losing columns off the end of a screen
     * that had room for them, and `.main:has(.edit)` was a patch over exactly this.
     *
     * So the measure moved onto the thing it describes. Text blocks are capped; grids and
     * tables take the width they are given.
     */
    --measure: 78ch;

    /* The sidebar's width, named because the responsive rules have to undo it. */
    --side: 248px;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    /*
     * A quiet radial lift behind the content column — not decoration for its own sake. Flat
     * black made every card edge fight the page edge; a one-stop glow keeps depth without a
     * second palette or a theme toggle.
     */
    background:
        radial-gradient(900px 480px at 85% -5%, var(--bg-glow), transparent 55%),
        radial-gradient(700px 400px at 0% 100%, rgba(15, 23, 42, 0.9), transparent 50%),
        var(--bg);
    color: var(--text);
    /*
     * Vazirmatn first now that it's self-hosted; Tahoma stays as the fallback that covers
     * the render before the font file lands and any environment where it fails to load.
     * Line height is 1.7 rather than 1.5 because Arabic-script ascenders and descenders
     * collide at the tighter figure.
     */
    font: 14px/1.7 Vazirmatn, Tahoma, "Segoe UI", ui-sans-serif, system-ui, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    font-variant-numeric: tabular-nums;
}

::selection {
    background: var(--accent-soft);
    color: var(--text);
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        transition-duration: 0.01ms !important;
        animation-duration: 0.01ms !important;
    }

    .skeleton {
        animation: none;
        background: var(--panel-2);
    }

    .card:hover {
        transform: none;
    }
}

/* ---- Shell ------------------------------------------------------------------ */

.shell {
    display: grid;
    grid-template-columns: var(--side) 1fr;
    min-height: 100vh;
}

.side {
    background: linear-gradient(180deg, #0b1220 0%, var(--panel) 40%, #0a1220 100%);
    border-inline-end: 1px solid var(--line);
    padding: var(--space-5) var(--space-3) var(--space-4);
    display: flex;
    flex-direction: column;
    gap: 2px;

    /*
     * Sticks while the content scrolls. Every page here is longer than the viewport and the
     * navigation was disappearing off the top of all of them — on a long errors table,
     * getting back to another screen meant scrolling to the top first.
     */
    position: sticky;
    top: 0;
    align-self: start;
    max-height: 100vh;
    overflow-y: auto;
}

.brand {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 0 var(--space-2) var(--space-5);
    white-space: nowrap;
}

.brand-logo {
    width: 32px;
    height: 32px;
    border-radius: 9px;
    display: grid;
    place-items: center;
    background: var(--accent-soft);
    border: 1px solid rgba(34, 197, 94, 0.35);
    box-shadow: var(--accent-glow);
    color: var(--accent);
    flex: none;
}

.brand-logo svg {
    width: 18px;
    height: 18px;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.brand-text {
    display: flex;
    flex-direction: column;
    gap: 0;
    line-height: 1.25;
}

.brand-mark {
    font-weight: 700;
    font-size: 15px;
    letter-spacing: 0.02em;
}

.brand-sub {
    color: var(--muted);
    font-weight: 500;
    font-size: 11px;
}

.nav-group {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-bottom: var(--space-4);
}

.nav-label {
    padding: 4px var(--space-2) 6px;
    color: var(--muted);
    font-size: 10.5px;
    font-weight: 600;
    opacity: 0.7;
}

.nav {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-radius: 9px;
    color: var(--muted);
    text-decoration: none;
    font-size: 13.5px;
    cursor: pointer;
    transition: background-color 180ms ease, color 180ms ease, box-shadow 180ms ease;
}

.nav-ico {
    width: 16px;
    height: 16px;
    flex: none;
    opacity: 0.75;
}

.nav-ico svg {
    display: block;
    width: 16px;
    height: 16px;
    stroke: currentColor;
    fill: none;
    stroke-width: 1.75;
    stroke-linecap: round;
    stroke-linejoin: round;
}

.nav:hover {
    background: var(--panel-2);
    color: var(--text);
}

.nav:hover .nav-ico {
    opacity: 1;
}

/* Blazor's NavLink adds this class to whichever route is showing. */
.nav.active {
    background: var(--accent-soft);
    color: var(--text);
    font-weight: 600;
    box-shadow: inset 2px 0 0 var(--accent), var(--accent-glow);
}

.nav.active .nav-ico {
    opacity: 1;
    color: var(--accent);
}

.signout {
    margin-top: auto;
    padding-top: var(--space-4);
}

.signout button {
    width: 100%;
    background: none;
    border: 1px solid var(--line);
    color: var(--muted);
    border-radius: 9px;
}

.signout button:hover {
    color: var(--text);
    border-color: var(--line-strong);
    background: var(--panel-2);
}

.main {
    padding: var(--space-5) var(--space-6) 60px;

    /*
     * A grid item's implicit `min-width: auto` is its content's minimum, so the 1fr column
     * refuses to shrink below the widest table in it — and the whole page, sidebar included,
     * slides sideways instead of the table scrolling inside itself. This is what lets the
     * `overflow-x` on the table actually take effect.
     */
    min-width: 0;
}

/*
 * Prose is capped; everything else is not. See --measure.
 *
 * Scoped to direct children of .main so that a paragraph inside a card or a table cell is
 * bounded by its container instead of by the page measure, which would leave a card's note
 * wrapping at a width the card never had.
 */
.main > p,
.main > .meta,
.main > ul,
.main > ol {
    max-width: var(--measure);
}

/* ---- Type ------------------------------------------------------------------- */

h1 {
    font-size: 22px;
    font-weight: 700;
    margin: 0;
    letter-spacing: -0.01em;
    line-height: 1.35;
}

h2 {
    /*
     * No text-transform/letter-spacing here — both are Latin-dashboard conventions, and
     * every h2 in this panel is Persian. Uppercase is a no-op on Persian script, and
     * letter-spacing forces each letter into its isolated form, breaking the joins that
     * make the word readable at a glance.
     */
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
    margin: var(--space-6) 0 var(--space-2);
}

.head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    /* Wraps rather than squeezing: the range buttons on the usage page are a fixed row of
       four and would otherwise push the title into two lines at the first narrow window. */
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    margin-bottom: var(--space-5);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--line);
}

.head-copy {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.head-lead {
    margin: 0;
    max-width: var(--measure);
    font-size: 13px;
    color: var(--muted);
    line-height: 1.6;
}

.muted {
    color: var(--muted);
}

.mono {
    /*
     * Tahoma last, as a shaping fallback. Monospace faces carry no Arabic-script shaping, so
     * a Persian word that ends up in one renders as disconnected letters — the identifiers
     * this class is for are Latin, but the panel is Persian and the mistake is easy to make.
     */
    font-family: ui-monospace, "Cascadia Mono", Menlo, Consolas, monospace, Tahoma;
}

/* A figure the panel computes rather than accepts — sized like the inputs it sits beside
   in a card row, so a derived value and an editable one line up. */
.derived {
    font-size: 18px;
    font-variant-numeric: tabular-nums;
    padding: 6px 0;
}

.up {
    color: var(--up);
}

.down {
    color: var(--down);
}

.warn {
    color: var(--warn);
}

.meta {
    margin-top: 10px;
    font-size: 13px;
}

.back {
    color: var(--info);
    text-decoration: none;
}

.back:hover {
    text-decoration: underline;
}

/* Quiet loading placeholder — pulse only, no spinner dependency. */
.skeleton {
    display: block;
    height: 14px;
    border-radius: 6px;
    background: linear-gradient(90deg, var(--panel-2) 0%, #243247 50%, var(--panel-2) 100%);
    background-size: 200% 100%;
    animation: skeleton-pulse 1.4s ease-in-out infinite;
    max-width: 220px;
}

.skeleton.block {
    height: 88px;
    max-width: none;
    border-radius: var(--radius);
}

.skeleton-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: var(--space-3);
    margin-top: var(--space-2);
}

@keyframes skeleton-pulse {
    0% { background-position: 100% 0; }
    100% { background-position: -100% 0; }
}

/* ---- Controls --------------------------------------------------------------- */

button {
    font: inherit;
    background: var(--panel-2);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 9px;
    padding: 7px 12px;
    cursor: pointer;
    transition: border-color 180ms ease, background-color 180ms ease, box-shadow 180ms ease, filter 180ms ease;
}

button:hover:not(:disabled) {
    border-color: var(--line-strong);
    background: #243247;
}

button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
.nav:focus-visible,
.store-tabs a:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

button:disabled {
    opacity: 0.4;
    cursor: default;
}

.ranges {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.range.on {
    border-color: var(--accent);
    color: var(--accent);
    background: var(--accent-soft);
    box-shadow: var(--accent-glow);
}

.toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: 14px;
}

input {
    font: inherit;
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 8px 11px;
    width: 280px;
    /* The fixed width is a preferred size, not a floor. Without this an input is wider than
       a phone screen and takes the page sideways with it. */
    max-width: 100%;
    transition: border-color 150ms ease;
}

input:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.pager {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 14px;
}

/* ---- Cards ------------------------------------------------------------------ */

.cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: var(--space-3);
}

.cards.thin {
    margin-top: var(--space-3);
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

.card {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--space-4);
    /* One elevation step, everywhere a card appears — not a value picked per component. */
    box-shadow: var(--shadow);
    transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease;
}

.card:hover {
    border-color: var(--line-strong);
    box-shadow: var(--shadow-lift);
    transform: translateY(-1px);
}

/* The one card the page is really about gets the outline. Only ever one per screen. */
.card.accent {
    border-color: rgba(34, 197, 94, 0.55);
    background: linear-gradient(160deg, rgba(34, 197, 94, 0.08), var(--panel) 55%);
    box-shadow: var(--shadow), var(--accent-glow);
}

/*
 * A card whose figure is the bad news — a failed lesson count above zero, revenue below
 * provider cost.
 *
 * The pages already put `warn` on the card rather than on the figure inside it, and until
 * now that only worked by accident: `.warn` sets a text colour, `.card .label` and
 * `.card .note` set their own, so the one element left inheriting it happened to be the
 * figure. Stated properly here, with the border carrying the signal — a coloured number is
 * easy to miss in a grid of six, an outlined card is not.
 */
.card.warn {
    border-color: rgba(245, 158, 11, 0.65);
    color: inherit;
    background: linear-gradient(160deg, rgba(245, 158, 11, 0.1), var(--panel) 55%);
}

.card.warn .figure {
    color: var(--warn);
}

.card .label {
    /* Same reasoning as h2/th: no letter-spacing on Persian text. */
    color: var(--muted);
    font-size: 12px;
    font-weight: 500;
}

.card .figure {
    font-size: 30px;
    margin-top: 8px;
    font-weight: 700;
    letter-spacing: -0.03em;
    font-variant-numeric: tabular-nums;
    line-height: 1.15;
}

.card .figure.small {
    font-size: 19px;
}

.card .note {
    color: var(--muted);
    font-size: 12px;
    margin-top: 6px;
}

.card .muted {
    margin-top: 6px;
    font-size: 12px;
}

/* ---- Chart ------------------------------------------------------------------ */

/*
 * Bars made of divs, on purpose. The chart answers one question — which day was unusual —
 * and a charting library would be a dependency and a download to answer it.
 */
.chart {
    display: flex;
    align-items: flex-end;
    /* Bars grow from the left rather than sharing the width out between them. */
    justify-content: flex-start;
    gap: 3px;
    height: 130px;
    padding: 10px 12px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow-x: auto;
}

/*
 * Capped, and that is the point. Left to divide the width between them, three days of data
 * produce three bars a third of the panel wide — which reads as a design, not as a chart
 * with three points in it. A bar keeps its width and the row simply fills up over time.
 */
.chart .bar {
    flex: 1 1 8px;
    max-width: 26px;
    height: 100%;
    display: flex;
    align-items: flex-end;
    min-width: 6px;
}

.chart .fill {
    width: 100%;
    background: var(--accent);
    border-radius: 3px 3px 0 0;
    opacity: 0.8;
    box-shadow: 0 0 8px rgba(34, 197, 94, 0.35);
}

.chart .bar:hover .fill {
    opacity: 1;
}

/* ---- Tables ----------------------------------------------------------------- */

/*
 * Scrolls inside itself rather than taking the page with it.
 *
 * Cells are `nowrap` — a phone number or a token broken across two lines is unreadable —
 * so a narrow window would otherwise leave the whole layout sliding sideways, sidebar
 * included. `display: block` is what gives the table an overflow box of its own.
 */
table {
    display: block;
    width: 100%;
    border-collapse: collapse;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--radius);

    /*
     * One declaration, not two.
     *
     * This rule used to set `overflow-x: auto` at the top and `overflow: hidden` at the
     * bottom — and the shorthand, being later, won both axes. So every table on the panel
     * was clipped rather than scrollable: the columns past the edge were not reachable by
     * any means, which is the "it doesn't fit" this file was written to prevent. The
     * `hidden` was there to make the border-radius clip the corner cells, and `auto` does
     * that too on a box that is not actually overflowing.
     */
    overflow: auto;

    /* Momentum scrolling, so a trackpad flick reaches the far columns in one gesture. */
    overscroll-behavior-x: contain;
}

/*
 * Scrollbars the operator can actually see.
 *
 * The platform default on this panel's dark surfaces is an overlay bar that fades out when
 * idle, so a table with columns past the edge looks identical to one without — the last
 * visible cell reads as the last cell. Given that horizontal scrolling inside a table is the
 * mechanism the whole layout depends on, the affordance for it has to be permanent.
 */
table::-webkit-scrollbar,
.trace::-webkit-scrollbar,
.chart::-webkit-scrollbar {
    height: 10px;
    width: 10px;
}

table::-webkit-scrollbar-track,
.trace::-webkit-scrollbar-track,
.chart::-webkit-scrollbar-track {
    background: var(--panel-2);
}

table::-webkit-scrollbar-thumb,
.trace::-webkit-scrollbar-thumb,
.chart::-webkit-scrollbar-thumb {
    background: var(--line);
    border-radius: 999px;
}

table::-webkit-scrollbar-thumb:hover,
.trace::-webkit-scrollbar-thumb:hover,
.chart::-webkit-scrollbar-thumb:hover {
    background: var(--muted);
}

/* Firefox, which has its own two properties and none of the pseudo-elements. */
table,
.trace,
.chart {
    scrollbar-width: thin;
    scrollbar-color: var(--line) var(--panel-2);
}

th,
td {
    text-align: start;
    padding: 9px 12px;
    border-bottom: 1px solid var(--line);
    white-space: nowrap;
}

th {
    /* Same reasoning as h2: no letter-spacing on Persian headers, it disconnects the letters. */
    color: var(--muted);
    font-weight: 600;
    font-size: 12px;
    /*
     * Sticks under the scroll of a long table so the column meanings stay visible. Background
     * is opaque — without it the rows slide through the header and the labels go illegible.
     */
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--panel-2);
    box-shadow: 0 1px 0 var(--line);
}

tbody tr:nth-child(even) td {
    background: rgba(15, 23, 42, 0.35);
}

tbody tr:last-child td {
    border-bottom: none;
}

tbody tr:hover td {
    background: rgba(30, 41, 59, 0.85);
}

/*
 * Numbers line up on the decimal point or they cannot be compared down a column.
 *
 * Latin digits and a left-to-right run, inside a right-to-left page. Persian digits would be
 * the consistent choice and the wrong one here: these columns sit beside SKUs, dollar amounts
 * and version codes that are Latin whatever the surrounding language, and a column mixing the
 * two scripts cannot be scanned. The isolate keeps a figure like "1,900,000" from being
 * re-ordered by the paragraph direction around it.
 */
.n {
    text-align: end;
    direction: ltr;
    unicode-bidi: isolate;
    font-variant-numeric: tabular-nums;
}

tr.click {
    cursor: pointer;
}

tr.click td {
    transition: background-color 150ms ease;
}

tr.click:hover td {
    background: var(--panel-2);
}

.tag {
    display: inline-block;
    margin-inline-start: 6px;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--up);
    font-size: 11px;
    font-family: inherit;
    font-weight: 500;
}

/* ---- Editing --------------------------------------------------------------- */

/*
 * The pricing table edits in place rather than behind a modal per row. There are four plans
 * and eight columns, and the thing an operator actually wants to see while changing one
 * price is the other three prices.
 */
.edit td {
    padding: 5px 8px;
}

/*
 * The `.main:has(.edit)` escape hatch that used to live here is gone. It existed because the
 * reading measure was applied to the whole content column and the pricing grid had to opt
 * out of it; the measure now applies to prose only, so there is nothing left to opt out of,
 * and every wide table gets the same treatment this one page had to ask for.
 */

.edit input[type="text"],
.edit input:not([type]) {
    padding: 5px 8px;
}

input.tiny {
    width: 110px;
}

input.wide {
    width: 190px;
}

input.num {
    width: 90px;
    text-align: end;
    direction: ltr;
    font-variant-numeric: tabular-nums;
}

.card input.num {
    width: 100%;
    margin-top: 6px;
    text-align: start;
    font-size: 17px;
}

/* A retired plan is still listed — it can be brought back — but reads as inactive. */
tr.retired td {
    opacity: 0.45;
}

button.primary {
    background: var(--accent);
    border-color: var(--accent);
    color: #052e16;
    font-weight: 600;
}

button.primary:hover:not(:disabled) {
    filter: brightness(1.08);
    box-shadow: 0 0 0 3px var(--accent-soft), var(--accent-glow);
}

button.danger {
    border-color: var(--down);
    color: var(--down);
}

button.danger:hover:not(:disabled) {
    background: rgba(239, 68, 68, 0.12);
}

.saved {
    color: var(--up);
}

.tag.ocr {
    background: var(--info-soft);
    color: var(--info);
}

/* ---- Login ------------------------------------------------------------------ */

.signed-out {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 24px;
    background:
        radial-gradient(720px 420px at 50% 15%, var(--bg-glow), transparent 58%),
        radial-gradient(500px 320px at 80% 90%, rgba(56, 189, 248, 0.06), transparent 50%),
        var(--bg);
}

.login {
    width: 380px;
    max-width: 100%;
    background: linear-gradient(165deg, #132033 0%, var(--panel) 45%);
    border: 1px solid var(--line);
    border-radius: 16px;
    padding: var(--space-5) var(--space-5) var(--space-6);
    box-shadow: var(--shadow-lift), var(--accent-glow);
}

.login-brand {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-weight: 700;
    font-size: 14px;
    margin-bottom: var(--space-3);
    color: var(--text);
}

.login-brand::before {
    content: "";
    width: 10px;
    height: 10px;
    border-radius: 999px;
    background: var(--accent);
    box-shadow: 0 0 12px rgba(34, 197, 94, 0.7);
}

.login h1 {
    font-size: 24px;
}

.login-lead {
    margin: 6px 0 0;
    font-size: 13px;
}

.login form {
    margin-top: 22px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.login label {
    color: var(--muted);
    font-size: 12px;
    font-weight: 500;
}

.login input {
    width: 100%;
}

.login button {
    margin-top: 10px;
    background: var(--accent);
    border-color: var(--accent);
    color: #052e16;
    font-weight: 600;
    padding: 11px 12px;
    border-radius: 10px;
}

.login button:hover:not(:disabled) {
    filter: brightness(1.08);
    box-shadow: var(--accent-glow);
}

.error {
    color: var(--down);
    margin: 12px 0 0;
    font-size: 13px;
}

/* ---- Blazor's own ----------------------------------------------------------- */

#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    inset-inline: 0;
    padding: 10px 14px;
    background: var(--warn);
    color: #1b1400;
    z-index: 1000;
}

/*
 * Latin text quoted inside Persian prose — SKUs, model ids, phone numbers, URLs.
 *
 * Without the isolate, a right-to-left paragraph reorders a run like "elpdf_credits_500"
 * against its neighbours, and trailing punctuation jumps to the wrong end of it. These are
 * values an operator copies into the Cafe Bazaar panel character for character, so being
 * shown them backwards is not cosmetic.
 */
.mono,
.ltr {
    direction: ltr;
    unicode-bidi: isolate;
    text-align: start;
}

/* ---- Releases ---------------------------------------------------------------- */

/*
 * The drop target on the updates page.
 *
 * Large and obvious while it is empty, and it shrinks once files are listed under it — the
 * zone matters most before the first upload and least afterwards, when what an operator is
 * looking at is the table of what landed.
 */
.drop {
    border: 2px dashed var(--line);
    border-radius: var(--radius);
    background: var(--panel);
    padding: var(--space-6) var(--space-5);
    text-align: center;
    cursor: pointer;
    transition: border-color 150ms ease, background-color 150ms ease, padding 150ms ease;
}

.drop:hover {
    border-color: var(--muted);
}

/* While a file is over it. Added from apk-upload.js, not by :hover — a drag does not hover. */
.drop.over {
    border-color: var(--accent);
    background: var(--panel-2);
}

.drop.compact {
    padding: var(--space-4);
}

.drop-title {
    font-size: 15px;
    font-weight: 600;
    margin-bottom: var(--space-2);
}

/*
 * An earlier upload UI lived here — `.uploads`, `.upload`, `.upload-head`, `.upload-note`
 * and a `.bar` / `.bar .fill` progress pair. No markup has referenced any of it since the
 * upload moved to the `.drop` / `.drop-bar` pair below, and the bare `.bar` was not merely
 * dead but harmful: the daily chart's bars are also `.bar` with a `.fill` inside them, so
 * `.bar .fill` was matching them at equal specificity to `.chart .fill` and winning on
 * source order. It only ever looked right because the inline height on each chart bar
 * outranks a stylesheet.
 */

.signer-override {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin-top: var(--space-3);
    max-width: 720px;
    color: var(--muted);
    font-size: 13px;
}

.signer-override input,
.card .force input {
    width: auto;
    margin: 0;
}

.card .force {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: var(--space-2);
}

/* The hashes under a release row: present when looked for, quiet when not. */
tr.fingerprint td {
    border-top: none;
    padding-top: 0;
    font-size: 12px;
    word-break: break-all;
}

/* A button that reads as a link — for the per-row actions in the release table, where a
   framed button in every cell would out-shout the rows themselves. */
button.link {
    background: none;
    border: none;
    padding: 0;
    color: var(--info);
}

button.link.down {
    color: var(--down);
}

button.link:hover:not(:disabled) {
    text-decoration: underline;
}

.tag.warn {
    background: rgba(232, 168, 56, 0.15);
    color: var(--warn);
}

h2.section {
    margin-top: var(--space-6);
}

/* Where a label, its input and a sentence about it stack — used by the settings below the
   release table, whose values are long enough that a card grid would cut them off. */
.field {
    margin-top: var(--space-4);
    max-width: 720px;
}

.field .label {
    color: var(--muted);
    font-size: 13px;
    margin-bottom: 6px;
}

.field .muted {
    display: block;
    margin-top: 6px;
    font-size: 12px;
}

.field input.wide,
.field textarea.wide {
    width: 100%;
}

/* The account page's one write: a form, not a card, because a card implies a figure to
   read rather than an action to take. */
.grant {
    margin-top: var(--space-4);
    max-width: 560px;
}

.grant .label {
    color: var(--muted);
    font-size: 13px;
    margin-bottom: 6px;
}

.grant-row {
    display: flex;
    gap: 8px;
    align-items: center;
}

.grant-note {
    flex: 1;
}

.grant .note {
    color: var(--muted);
    font-size: 12px;
    margin-top: 6px;
}

textarea {
    font: inherit;
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 8px 11px;
    resize: vertical;
    transition: border-color 150ms ease;
}

textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/* ---- Release upload --------------------------------------------------------- */

/*
 * `.drop`, `.drop:hover`, `.drop.over` and `.drop-title` were declared a second time here,
 * having already been declared under "Releases" above. The second copy won on source order
 * and it was the worse of the two: its `.drop.over` set the background to `--panel`, which
 * is what the zone already is at rest, so dragging a file over the target changed nothing
 * but the border. The single declaration above uses `--panel-2` and is visible.
 *
 * A dashed outline rather than a filled panel, on the reasoning kept with that block: the
 * zone has to read as a hole in the page that a file goes into, and every filled surface in
 * here already means "a thing that is here", not "a place to put something".
 */

.drop input[type="file"] {
    display: none;
}

/*
 * The progress bar, which is the whole reason the upload does not go through Blazor.
 * Zero-width and therefore invisible until the first progress event.
 */
.drop-bar {
    height: 4px;
    margin: var(--space-4) auto 0;
    max-width: 420px;
    background: var(--panel-2);
    border-radius: 999px;
    overflow: hidden;
}

.drop-bar span {
    display: block;
    width: 0;
    height: 100%;
    background: var(--accent);
    transition: width 120ms linear;
}

.drop-status {
    margin-top: var(--space-2);
    min-height: 20px;
    color: var(--muted);
    font-size: 13px;
}

/* A warning, not an error: the file uploaded, something about it is worth a second look. */
.drop-status.bad {
    color: var(--warn);
}

.drop-option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin: var(--space-3) 0 var(--space-5);
    color: var(--muted);
    font-size: 13px;
}

/* ---- Errors ---------------------------------------------------------------- */

/*
 * A failure's own account of itself, in the errors table.
 *
 * Clamped rather than truncated at write time: the first two lines say which failure this is,
 * which is all the table needs, and the whole thing is still there to be selected and copied
 * into a bug report. Wrapped on purpose — these carry model output and provider messages with
 * no spaces to break on, and a row that scrolls sideways hides the half that matters.
 */
.detail {
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
    max-width: 52ch;
    font-size: 12px;
    line-height: 1.5;
    opacity: 0.85;
}

/*
 * The exception's type and message, pulled out of the trace and shown on its own line.
 *
 * This is the part that answers "what happened" — the trace below it only answers "where".
 * Buried at the top of a hundred-line box in the same size and weight as everything under it,
 * it was easy to miss entirely; broken out here with the copy button, it is the first thing
 * the eye lands on.
 */
.exception-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    margin-top: 8px;
}

.exception-head .headline {
    color: var(--down);
    font-size: 12.5px;
    font-weight: 500;
    overflow-x: auto;
    white-space: nowrap;
}

.exception-head .copy {
    flex: none;
    font-size: 12px;
}

/*
 * A stack trace, shown verbatim.
 *
 * Scrolls in both directions inside its own box instead of stretching the table: a deep trace
 * through EF Core is a hundred lines long and every one of them is wider than the page.
 */
.trace {
    margin: 8px 0 0;
    padding: 10px 12px;
    max-height: 340px;
    overflow: auto;
    border-radius: 8px;
    background: var(--panel-2);
    font-family: var(--mono, ui-monospace, monospace);
    font-size: 11.5px;
    line-height: 1.55;
    white-space: pre;
    tab-size: 2;
}

/*
    The per-document cost breakdown on the documents table.

    A row inside the table rather than a page of its own: the question is asked about one
    document among fifty and the answer has to stay next to it, or the operator is reading a
    number with the filename it belongs to no longer on screen.
*/
tr.cost > td {
    /*
     * `var(--surface-2, #f6f7f9)`, as this was written, resolved to the fallback — nothing
     * in this file has ever defined `--surface-2`. That fallback is a near-white from some
     * light theme, and the panel's body text is `#eef1f6`, so the whole cost breakdown was
     * rendering as off-white on off-white. `--panel-2` is the token this file actually has
     * for "one step up from the surface around it".
     */
    background: var(--panel-2);
    padding: 12px 14px;
}

.cost-head {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 18px;
    align-items: baseline;
    margin-bottom: 10px;
}

/*
 * A nested table, and it has to opt out of the block-level scroll box the outer rule gives
 * every table — it is already inside one, and a second scroll container within the first is
 * two scrollbars for one gesture.
 */
table.inner {
    display: table;
    width: 100%;
    border-collapse: collapse;
    background: none;
    border: none;
    border-radius: 0;
    overflow: visible;
    font-size: 0.92em;
}

table.inner th,
table.inner td {
    padding: 4px 8px;
    border-bottom: 1px solid var(--line);
}

table.inner th {
    font-weight: 600;
    /* `start`, not `right`. They coincide on this right-to-left page, but the hardcoded side
       is the kind of thing that silently inverts if a screen is ever rendered left-to-right. */
    text-align: start;
    color: var(--muted);
}

table.inner tr:last-child td {
    border-bottom: none;
}

/* ---- Narrow screens --------------------------------------------------------- */

/*
 * The panel stopped being a desktop-only tool the first time it was opened on a phone to
 * check whether a release had landed, and the layout had no answer for it: a 210px sidebar
 * against a fixed 1180px content column on a 390px screen is a page that scrolls sideways
 * as a whole, which is the one thing the table rules were written to prevent.
 *
 * Two breakpoints, not a scale. There are only two shapes this panel has: a sidebar beside
 * the content, and a navigation bar above it.
 */

/*
 * The first is where the sidebar stops paying for itself — 210px of a 900px window is a
 * quarter of the screen spent on eleven links.
 */
@media (max-width: 900px) {
    .shell {
        grid-template-columns: 1fr;
    }

    /*
     * The sidebar becomes a bar across the top, scrolling horizontally. Still sticky: the
     * pages are longer here than anywhere, and this is the only way back off one.
     */
    .side {
        flex-direction: row;
        align-items: center;
        gap: var(--space-1);
        padding: var(--space-2) var(--space-3);
        border-inline-end: none;
        border-block-end: 1px solid var(--line);
        overflow-x: auto;
        overflow-y: hidden;
        max-height: none;
        z-index: 20;
    }

    .brand {
        padding: 0 var(--space-2) 0 0;
        flex: none;
    }

    /*
     * Group labels earn their keep in a tall column; in a horizontal strip they are noise
     * between links. The groups themselves stay as flex rows so the links keep flowing.
     */
    .nav-group {
        flex-direction: row;
        align-items: center;
        gap: var(--space-1);
        margin-bottom: 0;
        flex: none;
    }

    .nav-label {
        display: none;
    }

    .nav {
        white-space: nowrap;
        flex: none;
        padding: 6px var(--space-3);
        gap: 6px;
    }

    .nav-ico {
        width: 14px;
        height: 14px;
    }

    .nav-ico svg {
        width: 14px;
        height: 14px;
    }

    .brand-logo {
        width: 28px;
        height: 28px;
        border-radius: 8px;
    }

    .brand-logo svg {
        width: 15px;
        height: 15px;
    }

    .brand-sub {
        display: none;
    }

    /*
     * The active marker moves from the inline edge to the bottom one. Inset 2px 0 0 draws a
     * bar down the start side, which in a horizontal strip sits between two links and reads
     * as a separator rather than as a selection.
     */
    .nav.active {
        box-shadow: inset 0 -2px 0 var(--accent);
    }

    /* `margin-top: auto` pushed it to the bottom of a column; the row needs the inline end. */
    .signout {
        margin-top: 0;
        padding-top: 0;
        margin-inline-start: auto;
        flex: none;
    }

    .signout button {
        width: auto;
        white-space: nowrap;
    }

    .main {
        padding: var(--space-4) var(--space-4) 48px;
    }

    .head {
        margin-bottom: var(--space-4);
        padding-bottom: var(--space-3);
    }
}

/* The second is a phone held upright, where the remaining desktop-sized gaps are the thing
   costing the content its room. */
@media (max-width: 560px) {
    body {
        font-size: 13.5px;
    }

    .main {
        padding: var(--space-3) var(--space-3) 40px;
    }

    h1 {
        font-size: 17px;
    }

    /* Two across rather than one: these are short figures, and a single column turns six
       cards into most of a screen's worth of scrolling. */
    .cards {
        grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
        gap: var(--space-2);
    }

    .cards.thin {
        grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
    }

    .card {
        padding: var(--space-2) var(--space-3);
    }

    .card .figure {
        font-size: 22px;
    }

    .card .figure.small {
        font-size: 16px;
    }

    .nav-ico {
        display: none;
    }

    .head-lead {
        font-size: 12px;
    }

    th,
    td {
        padding: 7px 9px;
    }

    .chart {
        height: 96px;
    }

    /* The stack of label, input and explanation under the release table: these were capped
       at 720px, which is wider than the screen. */
    .field,
    .grant,
    .signer-override {
        max-width: 100%;
    }

    .grant-row {
        flex-wrap: wrap;
    }

    /* The error detail's 52ch clamp is wider than a phone, and the column then decides the
       table needs to scroll for a cell that would have wrapped perfectly well. */
    .detail {
        max-width: 100%;
    }
}

/*
 * Printing, because the one thing an operator does with the money screen that is not
 * reading it is showing it to somebody.
 */
@media print {
    .side,
    .ranges,
    .toolbar,
    .pager,
    .store-tabs,
    .nav-ico,
    .brand-logo {
        display: none;
    }

    .shell {
        grid-template-columns: 1fr;
    }

    .main {
        padding: 0;
    }

    /* The palette is a screen decision. On paper the dark surfaces cost a cartridge and
       read worse than the plain thing. */
    body {
        background: #fff;
        color: #000;
    }

    .card,
    table {
        border-color: #999;
        background: #fff;
    }

    /* Every table on paper is as wide as it needs to be — there is no scrolling a sheet. */
    table {
        display: table;
        overflow: visible;
    }
}

/* ---- Store ------------------------------------------------------------------ */

/* The store section's own tabs, under each store page's title. */
.store-tabs {
    display: flex;
    flex-wrap: nowrap;
    gap: 4px;
    margin: -6px 0 var(--space-5);
    padding: 5px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    background: rgba(15, 23, 42, 0.85);
    border: 1px solid var(--line);
    border-radius: 14px;
    box-shadow: inset 0 1px 0 rgba(248, 250, 252, 0.03);
    scrollbar-width: thin;
    scrollbar-color: var(--line) var(--panel-2);
}

.store-tabs a {
    color: var(--muted);
    text-decoration: none;
    border: 1px solid transparent;
    border-radius: 10px;
    padding: 8px 13px;
    font-size: 13px;
    white-space: nowrap;
    flex: none;
    cursor: pointer;
    transition: background-color 180ms ease, color 180ms ease, border-color 180ms ease, box-shadow 180ms ease;
}

.store-tabs a:hover {
    color: var(--text);
    background: var(--panel-2);
}

.store-tabs a.active {
    color: var(--text);
    background: var(--accent-soft);
    border-color: rgba(34, 197, 94, 0.45);
    font-weight: 600;
    box-shadow: var(--accent-glow);
}

/* Label-over-field pairs, as many per row as fit. */
.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: var(--space-3) var(--space-4);
    margin-bottom: var(--space-4);
}

.form-grid label {
    display: flex;
    flex-direction: column;
    gap: 6px;
    font-size: 13px;
    color: var(--muted);
}

.form-grid label.check {
    flex-direction: row;
    align-items: center;
    color: var(--text);
}

.form-grid label.check input {
    width: auto;
}

.form-grid input,
.form-grid select,
.form-grid textarea {
    width: 100%;
    box-sizing: border-box;
}

.form-grid .full {
    grid-column: 1 / -1;
}

select,
textarea {
    font: inherit;
    background: var(--panel);
    color: var(--text);
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 8px 11px;
}

textarea {
    min-height: 90px;
    resize: vertical;
    line-height: 1.8;
}

textarea.code {
    font-family: ui-monospace, Consolas, monospace;
    direction: ltr;
    min-height: 420px;
    width: 100%;
    box-sizing: border-box;
    font-size: 12px;
}

.notice {
    border: 1px solid rgba(245, 158, 11, 0.55);
    color: var(--warn);
    background: rgba(245, 158, 11, 0.1);
    border-radius: var(--radius);
    padding: 10px 14px;
    margin-bottom: var(--space-3);
    line-height: 1.8;
}

.notice.info {
    border-color: var(--line);
    color: var(--muted);
    background: var(--panel);
}

.actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: var(--space-3) 0;
}

/* ---- Pager and error list tidying ------------------------------------------ */

.pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    margin: 12px 0 24px;
}

/* A message in the errors table: two lines say which failure this is; the rest opens on click. */
td.msg {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    max-width: 70ch;
    overflow-wrap: anywhere;
}
