/* توکن‌های بصری — بخش ۱۸ مستر پلن. سه لایه: بخش ۸۸ (موردِ ۱۹ صفِ کارفرما). */
/* ============================================================================================
   موردِ ۱۹: «ظاهرِ سامانه خیلی ساده و بی‌روح است» — و اندازه‌گیری نشان داد حق با اوست.

   Measured on the dashboard before this change: of 234 elements on a page 2294 pixels tall,
   **eight** carried any background colour at all, all eight were the same `#FAF8FB`, and
   **95.8 per cent of the page was pure white**. Not «the palette is dull» — the palette has
   twelve colours and the landing screen of the system was using one of them, two per cent away
   from white. A list page such as `/suppliers` measured 22.2 per cent, because the colour in this
   system is attached to *state*, and the dashboard has almost no state on it.

   The cause was not the colours. It was that there was no **ground**: `--bg` was the page *and*
   the card, so eleven cards on the dashboard were white boxes on a white page, separated from it
   by a `--line` border whose contrast against white is 1.23 : 1. A card was not a surface; it was
   an outline. Add to that two `box-shadow`s and three `transition`s in 1334 lines, and one
   `:focus` rule in the whole application, covering `input` only.

   So this is answered with depth and not with paint — which is also the only answer the client's
   own condition allows («کاربر ممکن است روزی هشت ساعت با آن کار کند، پس نه پرزرق‌وبرق و
   خسته‌کننده»). Saturating a tool somebody sits in front of for eight hours is worse than leaving
   it white. Three planes, no new hue, the one new value a tint of the purple already here:

       --ground   منو و زمینِ دورِ کارت‌ها   recessive chrome — the eye should leave it alone
       --bg2      زمینِ صفحه، و تورفتگی داخلِ کارت
       --bg       سطح: کارت، جدول، جعبهٔ ورودی

   `--bg2` keeps every job it already had — a `th`, the groove of a `.bar`, the fill of a
   `.dropzone` — because all of those sit *inside* a white card, where the card's own white is what
   separates them from the identically-coloured field outside. Only `body` and `.side` change, and
   every page in the system changes with them. */
:root {
    --bg: #FFFFFF;
    --bg2: #FAF8FB;
    /* ۱۱٪ کم‌نورتر از سفید — دو برابرِ گامِ `--bg2`. Deep enough that the menu reads as chrome
       rather than as a column of the page, shallow enough that it is still paper and not a panel. */
    --ground: #F4F1F6;
    --line: #ECE6EE;
    /* لبهٔ کنترل‌ها — بخش ۸۸، و نه `--line`.
       One token was doing two jobs, and failing the harder one. Measured against a white card,
       `--line` is **1.23 : 1** — which is right for a rule between two rows of a table, where
       twenty hard lines down a dense sheet would be worse than none, and wrong for the edge of a
       box you are meant to type into. A text field on a white card had no visible boundary at all;
       so did a plain `.btn`, which left a secondary button reading as a word.

       `--edge` is **3.02 : 1** against white, which is the ratio WCAG 1.4.11 asks of the boundary
       of a user-interface component. It is the same mauve family as everything else here, picked
       as light as it could be and still clear that — the brief was «نه پرزرق‌وبرق و خسته‌کننده»,
       and nothing is more tiring over eight hours than hunting for the box.

       The division to keep: `--line` **separates**, `--edge` **bounds a control**. */
    --edge: #9E90A2;
    --tx: #2E2433;
    --tx2: #6E6475;
    --pr: #59045D;
    --pr-dark: #3C0D41;
    --pr-light: #F5EEF6;

    --draft-fg: #B45309;
    --draft-bg: #FEF3C7;
    --final-fg: #15803D;
    --final-bg: #DCFCE7;
    --void-fg: #B91C1C;
    --void-bg: #FEE2E2;
    --reserved-fg: #1D4ED8;
    --reserved-bg: #DBEAFE;
    --healthy-fg: #0E7490;
    --healthy-bg: #CFFAFE;
    --plain-fg: #57534E;
    --plain-bg: #F5F5F4;

    --nav-w: 232px;
    --radius: 10px;
}

@font-face {
    font-family: Vazirmatn;
    src: url('/fonts/Vazirmatn-Regular.woff2') format('woff2');
    font-weight: 400;
    font-display: swap;
}

@font-face {
    font-family: Vazirmatn;
    src: url('/fonts/Vazirmatn-Medium.woff2') format('woff2');
    font-weight: 500;
    font-display: swap;
}

@font-face {
    font-family: Vazirmatn;
    src: url('/fonts/Vazirmatn-Bold.woff2') format('woff2');
    font-weight: 700;
    font-display: swap;
}

/* B Nazanin — the letter's face and nothing else's (موردِ ۱ صفِ کارفرما). Declared here because
   this is the stylesheet the *screen* loads; the PDF never sees this file and gets the same two
   faces inlined as data URIs by `HtmlToPdf`. The two declarations never meet in one document, so
   they do not fight — and both have to exist, or the preview and the paper set in different fonts,
   which is the one way a print preview stops being a preview.

   Nothing here applies it. `letter-sheet.css` does that, on `.letter-sheet` alone: the client asked
   for the *letter* in B Nazanin, and a 217-glyph face from 2000 is not what to set an eight-hour
   working screen in. */
@font-face {
    font-family: 'B Nazanin';
    src: url('/fonts/BNazanin-Regular.woff2') format('woff2');
    font-weight: 400;
    font-display: swap;
}

@font-face {
    font-family: 'B Nazanin';
    src: url('/fonts/BNazanin-Bold.woff2') format('woff2');
    font-weight: 700;
    font-display: swap;
}

* { box-sizing: border-box; }

html, body { margin: 0; height: 100%; }

/* The field the cards sit on. One declaration, and every page in the system gains a ground —
   `main` has no background of its own, so this is what shows between and around the cards.
   On `body` rather than on `main` because the background of `body` propagates to the canvas, so
   the tint also covers the overscroll area and the strip below a short page. */
body {
    background: var(--bg2);
    color: var(--tx);
    font: 400 14px/1.75 Vazirmatn, Tahoma, "Segoe UI", sans-serif;
    -webkit-font-smoothing: antialiased;
}

/* On paper there is no ground to sit on, and a tinted field is either a grey rectangle or — more
   usually — silently dropped by the browser, which would leave the printed sheet looking like a
   different document from the one on screen. The `/print` pages are measured in millimetres
   against the client's own samples; none of this belongs on them. */
@media print {
    body { background: #FFFFFF; }
}

/* فیلدهای انگلیسی چپ‌به‌راست — بخش ۱۸ */
.ltr {
    direction: ltr;
    text-align: left;
    /* Without isolation an inline `::1` or `a.b` is reordered by the surrounding RTL paragraph. */
    unicode-bidi: isolate;
    font-family: "Segoe UI", Tahoma, sans-serif;
}

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

h1, h2, h3 { font-weight: 700; margin: 0 0 12px; line-height: 1.5; }
h1 { font-size: 20px; }
h2 { font-size: 17px; }
h3 { font-size: 15px; }

.muted { color: var(--tx2); }

/* چیدمان */
.shell {
    display: grid;
    grid-template-columns: var(--nav-w) 1fr;
    min-height: 100vh;
}

/* The menu scrolls on its own, and the page scrolls on its own.
   Found while measuring the chat panel (موردِ ۲۰): signed in as an administrator, every module is
   visible and the menu is 1752 pixels tall, so it — not the content — was setting the height of the
   grid row. On a 900-pixel screen the page therefore scrolled 850 pixels no matter what page you
   were on, and the content scrolled away with the menu. Reading the bottom of the menu cost you
   sight of the thing you were reading.

   `position: sticky` and not `fixed`: the grid column already reserves the width, and `fixed` would
   take the menu out of flow and leave that column empty. `block-size: 100vh` with its own
   `overflow-y` makes the menu a scrollport, so a long menu scrolls inside itself; `align-self:
   start` is what lets it be shorter than the grid row rather than being stretched to it.
   `scrollbar-gutter: stable` keeps the menu entries from shifting sideways by the width of a
   scrollbar when one appears. */
/* The deepest of the three planes — see the note on `--ground`. The menu is the one region of the
   screen the user is *not* meant to be looking at while working, and the way to say that is to let
   it recede rather than to shrink it. Thirty-two entries of bare text need the help: with nothing
   to its name but a border, the rail used to read as a first column of the page. */
.side {
    background: var(--ground);
    border-inline-start: 1px solid var(--line);
    display: flex;
    flex-direction: column;
    padding: 14px 10px;
    gap: 2px;
    position: sticky;
    inset-block-start: 0;
    align-self: start;
    block-size: 100vh;
    overflow-y: auto;
    scrollbar-gutter: stable;
}

.brand {
    padding: 6px 10px 16px;
    font-weight: 700;
    font-size: 15px;
    color: var(--pr);
}

.brand small {
    display: block;
    font-weight: 400;
    font-size: 11px;
    color: var(--tx2);
}

/* جست‌وجوی سراسری — بخش ۴۳
   Styled on its own rather than by adding `input[type=search]` to the shared rule at line 190:
   this is the only search input in the system that is not inside a `.field`, it has no label
   above it, and it has to be shorter than a form input or it reads as the first menu row. */
.nav-search { padding: 0 10px 10px; }

.nav-search input[type=search] {
    width: 100%;
    border: 1px solid var(--edge);
    border-radius: 8px;
    padding: 6px 9px;
    font: inherit;
    font-size: 12.5px;
    color: var(--tx);
    background: var(--bg);
    /* Chrome draws its own ✕ inside a `type=search`, at the inline end, which on an RTL page is
       the left edge — where it overlaps nothing, but it is a second control the user did not ask
       for. Escape already clears the box. */
    -webkit-appearance: none;
    appearance: none;
}

.nav-search input[type=search]::-webkit-search-cancel-button { display: none; }

.nav-item {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 10px;
    border-radius: var(--radius);
    color: var(--tx);
    font-size: 13.5px;
}

/* این دو، زیرِ `--ground` تازه، باید عوض می‌شدند — وگرنه ناپیدا بودند.
   Both of these used to paint `--pr-light`, and both were correct while the menu sat on `--bg2`.
   Moving the rail to `--ground` broke them, and broke them silently: `--pr-light` is `#F5EEF6` and
   `--ground` is `#F4F1F6`, a **1.6 per cent** difference in luminance — contrast 1.02 : 1. The
   hover had disappeared outright, and the active row was left being carried by its colour and its
   weight alone. Caught by measuring the new ground against everything painted on it rather than by
   looking, which is the only way a one-per-cent mistake is ever caught.

   The replacement is the plane ladder rather than a fourth tint: a row lifts toward the surface.
   Hover goes halfway (`--bg2`, 5.6 per cent lighter than the rail), the current page goes all the
   way (`--bg`, 11.2 per cent). That is also a gain on what was there before — hover and active
   used to paint the identical background, and the only thing telling them apart was that one of
   them was purple. */
.nav-item:hover { background: var(--bg2); }

.nav-item.active {
    background: var(--bg);
    color: var(--pr);
    font-weight: 500;
}

/* بخش ۱۰۶. The rows of a collapsible section, set in a touch from their `<summary>` so the
   grouping reads without a rule or an indent guide.

   Here and not in `MainLayout.razor.css` with the rest of the accordion, for the reason every
   `.nav-item` rule is here: scoped CSS stamps its attribute on the elements the component itself
   renders, and this row is rendered by `NavLink`. The scoped selector would have compiled, loaded,
   and matched nothing at all. */
.nav-sec > .nav-item {
    margin-block-start: 2px;
    margin-inline-start: 6px;
}

.nav-sec > summary + .nav-item { margin-block-start: 4px; }

/* Pushed to the far edge so the count reads as a property of the row, not part of its label. */
.nav-badge {
    margin-inline-start: auto;
    min-width: 20px;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--void-fg);
    color: #fff;
    font-size: 11px;
    text-align: center;
}

.nav-spacer { flex: 1; }

main { padding: 22px 26px 60px; }

.topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding-bottom: 14px;
    margin-bottom: 18px;
    border-bottom: 1px solid var(--line);
}

/* اجزا */
/* The surface. Now that there is a ground under it, the border is no longer the only thing saying
   where a card begins — which is the point, because that border is 1.23 : 1 against white and was
   being asked to do a job it cannot do.

   The shadow is deliberately almost nothing: four per cent of `--tx` at a two-pixel blur, under a
   one-pixel offset. Read on its own it is invisible; read against the eleven cards of the
   dashboard it is the difference between a card that was cut out of the page and one that is
   lying on it. Anything larger would be the «پرزرق‌وبرق» the client asked not to have, and on a
   screen somebody looks at for eight hours a drop shadow is the first thing to become tiring. */
.card {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 16px 18px;
    background: var(--bg);
    box-shadow: 0 1px 2px rgb(46 36 51 / .04);
}

.btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--edge);
    background: var(--bg);
    color: var(--tx);
    border-radius: 8px;
    padding: 7px 14px;
    font: inherit;
    font-size: 13.5px;
    cursor: pointer;
}

.btn:hover { background: var(--bg2); }

.btn.primary { background: var(--pr); border-color: var(--pr); color: #fff; }
.btn.primary:hover { background: var(--pr-dark); border-color: var(--pr-dark); }
.btn:disabled { opacity: .5; cursor: default; }

/* For buttons that live inside a table row. At the full size a «حذف» in the last column sets the
   row height for the whole table, which makes a list of twenty cost lines twice as long as the
   numbers in it need. */
.btn.sm { padding: 3px 10px; font-size: 12px; border-radius: 6px; }

/* `input:not([type])` is here because a bare `<input>` is a text box to the browser but matched
   none of the selectors below, so the hand-written grid rows came out unstyled beside the
   `InputText` fields above them. */
input:not([type]), input[type=text], input[type=email], input[type=password], input[type=number], select, textarea {
    border: 1px solid var(--edge);
    border-radius: 8px;
    padding: 8px 10px;
    font: inherit;
    font-size: 13.5px;
    color: var(--tx);
    background: var(--bg);
    width: 100%;
}

input:focus, select:focus, textarea:focus {
    outline: 2px solid var(--pr-light);
    border-color: var(--pr);
}

label { display: block; font-size: 12.5px; color: var(--tx2); margin-bottom: 4px; }
.field { margin-bottom: 14px; }

/* توضیحِ روانِ زیرِ خانه — بخش ۳۶
   `<p class="fh">` beneath the input, not beside the label and not a tooltip. The client asked
   for this in so many words («زیر هر فیلد یک توضیح روان»), and the reason to honour the
   position literally is that a hint above the box is read before the user knows what box it
   belongs to, while a hint in a `title=` attribute is invisible to the person who needs it —
   you have to already suspect a field is confusing to go hunting for its tooltip.

   Below the box and not above it also means the hint never pushes the label away from its
   input, so the form still scans as label-then-box all the way down.

   The numbers are not new: `12px`/`--tx2`/`margin: 4px 0 0` are exactly what the hints that
   already existed on these forms carried, each as its own inline `style=`. Keeping them
   identical is deliberate — it makes converting those hints to this class a change with **no
   visual effect at all**, so the diff can be read for what it moved rather than squinted at for
   what it also restyled. The new hints then arrive looking like the old ones.

   `12px` against the 13.5px value text is a clear step down, so a page with a hint under every
   second field still reads as a form and not as prose. Where a hint is a caution rather than an
   explanation, `.fh.warn` carries the same size in the alert colour. */
.fh {
    font-size: 12px;
    line-height: 1.7;
    color: var(--tx2);
    margin: 4px 0 0;
}
/* `--draft-fg`, the same amber `.alert.warn` uses, so a cautionary hint and a cautionary
   banner on the same page are the same colour. */
.fh.warn { color: var(--draft-fg); }
/* A hint belonging to a tick box, which has no `<label>` above it to line up with: the tick's
   own label is to its right, so the hint is indented to sit under the words and not the box. */
.fh.tick { margin-inline-start: 26px; }

/* `Hint.razor` — ردیف ز. The mark beside a label, and the bubble it opens.

   `.fh` above is not retired: the two now divide the work. A sentence that merely *explains* a
   field moves in here, behind the mark, because the client read a form full of them and called it
   «شلوغ» — and a page nobody reads is a page where the one hint that mattered is also unread. A
   line that *warns*, or that echoes back the value just typed, stays visible: hiding a caution
   behind a hover is how it goes unseen on exactly the form where it was needed.

   Sized to sit inside a 12.5px `<label>` without pushing the line taller: 15px across against a
   label line box that is already taller than that. `vertical-align: middle` rather than baseline,
   because a circle aligned on its baseline reads as if it has fallen off the text. */
.hint {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 15px;
    block-size: 15px;
    margin-inline-start: 5px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--bg2);
    color: var(--tx2);
    font-size: 10px;
    line-height: 1;
    vertical-align: middle;
    cursor: help;
    user-select: none;
    flex: none;
}

.hint:hover,
.hint:focus,
.hint:focus-visible {
    border-color: var(--pr);
    background: var(--pr-light);
    color: var(--pr);
    /* The ring is replaced, not removed: the border and fill above already mark focus, and the
       default outline on a 15px circle draws a square around it. */
    outline: none;
}

/* The glyph is nudged up by a hair because «؟» in Vazirmatn hangs low in its em box. */
.hint-i { display: block; margin-block-start: -1px; }

.hint-box {
    position: absolute;
    /* Above the mark, not below it. Below would cover the field this hint describes, which is the
       one thing on screen the reader needs to look at while reading the explanation. */
    inset-block-end: calc(100% + 8px);
    inset-inline-start: -9px;
    z-index: 40;
    inline-size: max-content;
    max-inline-size: 260px;
    padding: 8px 11px;
    border-radius: 8px;
    background: var(--tx);
    color: #FFFFFF;
    /* Set, not inherited: the mark sits inside a `<label>` at 12.5px and inside a `legend` or a
       table header elsewhere, and a bubble that changes size with its surroundings looks broken. */
    font-size: 12px;
    font-weight: 400;
    line-height: 1.8;
    text-align: start;
    box-shadow: 0 6px 20px rgba(46, 36, 51, .24);
    /* Hidden by `visibility` and not by `display`, so the fade has something to animate and the
       box keeps its measured width instead of being laid out from scratch on first hover. */
    visibility: hidden;
    opacity: 0;
    transition: opacity .12s ease, visibility .12s;
    /* The bubble never swallows a click: it overlaps the field above it, and a stray mouse-down
       on it must land on whatever is underneath. */
    pointer-events: none;
}

.hint-box a { color: #FFFFFF; text-decoration: underline; pointer-events: auto; }

/* `<Hint Flip="true" />`. The bubble hangs from the mark's other side and opens toward the inline
   start — rightwards here — for a mark near the left margin, where the default 260px would go off
   the glass. Only the two inset properties change; the arrow below follows with the same swap so
   it still points at the mark it belongs to. */
.hint.flip > .hint-box { inset-inline-start: auto; inset-inline-end: -9px; }
.hint.flip > .hint-box::after { inset-inline-start: auto; inset-inline-end: 12px; }

/* `<Hint Below="true" />`. The same move on the vertical axis, for a mark with nothing above it —
   a page title, where the bubble otherwise opens off the top of the window and no amount of
   scrolling brings it back, because the page is already at the top. The arrow swaps ends with it
   so it still points up at the mark. */
.hint.below > .hint-box { inset-block-end: auto; inset-block-start: calc(100% + 8px); }
.hint.below > .hint-box::after {
    inset-block-start: auto;
    inset-block-end: 100%;
    margin-block-start: 0;
    margin-block-end: -4px;
}

/* The arrow. A rotated square rather than a border triangle, so it inherits the bubble's own
   colour and needs no second value to keep in step. */
.hint-box::after {
    content: "";
    position: absolute;
    inset-block-start: 100%;
    inset-inline-start: 12px;
    inline-size: 8px;
    block-size: 8px;
    margin-block-start: -4px;
    background: var(--tx);
    transform: rotate(45deg);
}

/* Three triggers, not one. `:hover` for the mouse, `:focus` for the tap and the keyboard,
   `:focus-within` for the case where the link inside the bubble is what holds focus. */
.hint:hover > .hint-box,
.hint:focus > .hint-box,
.hint:focus-within > .hint-box {
    visibility: visible;
    opacity: 1;
}

/* A label that is a flex row — `label.check` — would otherwise stretch the mark to the row's
   height and turn the circle into a pill. */
label.check .hint { align-self: center; }

.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 18px; }
.form-grid .wide { grid-column: 1 / -1; }
@media (max-width: 720px) { .form-grid { grid-template-columns: 1fr; } }

/* یک فصل برداشت در فرمِ محصول — بخش ۱۹ ردیف ۵۴
   A repeated group of fields inside a card, which the rest of this form does not have: every
   other card holds one set of boxes and needs nothing to say where it begins. Two harvest
   windows one after another do — without a boundary, «روز پایان» of the first and «ماه شروع»
   of the second are two adjacent cells of one grid, and the reader has to count to know which
   season they are editing. `--line` and not `--edge`, because this separates rather than bounds
   a control, which is the division §۸۸ sets out. */
.season {
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 12px 14px 0;
    margin-bottom: 12px;
}
.season:last-child { margin-bottom: 0; }
/* The heading and its remove button on one line, with the button pushed to the far edge: a
   «حذف» sitting next to the season's name is a button you press by accident. */
.season-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
}

label.check { display: flex; align-items: center; gap: 8px; margin: 0; cursor: pointer; }
label.check input { width: auto; margin: 0; }
label.check span { color: var(--tx); font-size: 13.5px; }
/* Several ticks that belong to one question — the kinds of test one sheet carries. Wrapping
   rather than a column, because six of them down the page read as six separate questions. */
.check-row { display: flex; flex-wrap: wrap; gap: 8px 22px; }

/* جعبه بارگذاری فایل — بخش ۷-الف-۱ و ۱۲ */
/* The dashed block is itself the `<label>`, and the file input is stretched invisibly across the
   whole of it instead of sitting beside it. That is what makes a dropped file land on the input,
   and it is the only reason drag-and-drop works here with no JavaScript at all. */
.dropzone {
    position: relative;
    border: 2px dashed var(--edge);
    border-radius: var(--radius);
    background: var(--bg2);
    padding: 22px 16px;
    margin: 0;
    text-align: center;
    cursor: pointer;
}
.dropzone:hover { border-color: var(--pr); background: var(--pr-light); }
/* Faded to nothing, not `display:none` — a hidden input receives no drop. */
.dropzone input[type=file] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: inherit;
}
.dropzone span { display: block; font-size: 13.5px; color: var(--tx); }
.dropzone small { display: block; margin-top: 6px; font-size: 11.5px; }

/* How far a measurement came towards its limit. The track is full width so the bars of one
   table are all drawn against the same scale — a bar sized to its own row would make every
   result look the same. The fill reuses the verdict colours. */
.bar { background: var(--bg2); border-radius: 999px; height: 8px; overflow: hidden; }
.bar span { display: block; height: 100%; border-radius: 999px; }
.bar span.final { background: var(--final-fg); }
/* The four score bands of §۶-الف-۵ are drawn as bars as well as tags, so the bar needs the same
   four colours the tag has. Without this line the «خوب» band drew an empty groove. */
.bar span.healthy { background: var(--healthy-fg); }
.bar span.draft { background: var(--draft-fg); }
.bar span.void { background: var(--void-fg); }
.bar span.plain { background: var(--plain-fg); }
/* And the same gap again, for the same reason, with the lab outcome classes: «نسبت نتایج» in
   §۷-الف-۵ stacks `Lab.OutcomeTag`'s four names in one groove, and two of them — `organic` and
   `rejected` — existed only as tag colours. Without these two lines the organic slice, which on a
   good season is the largest one on the chart, drew as empty background. */
.bar span.organic { background: var(--final-fg); }
.bar span.rejected { background: var(--void-fg); }

/* یک عددِ از حد گذشته */
/* A figure that has crossed a threshold, in a place where a whole `.tag` would be too loud: a cell
   in a comparison table, or one of the `.minis` headline numbers. `.tag.void` exists and is the
   right thing when the state *is* the content; this is for when the number is the content and the
   fact that it is over the line is an adjective on it.

   Not reusing `.bad`, which looks like the obvious candidate: that rule is scoped to `.tabs .tab .n`
   and paints a white-on-red pill. Widening it to a bare `.bad` would repaint every tab counter the
   day somebody used the short name here. */
.over { color: var(--void-fg); }

/* یک عددِ تسویه‌شده — بخش ۸-۹ */
/* The counterpart of `.over`, and added for the same reason: on §۸-۹'s payment-version table the
   number *is* the content, and «this one has been received in full» is an adjective on it rather
   than a state of its own worth a whole `.tag`. Green on the figure says it without adding a
   second column that would be empty on every row but one.

   Deliberately not `.ok`, which is already taken by `.alert.ok` — a block-level banner with a
   background. A bare `.ok` would be read as «this element is a success message» by the next person
   who needs to colour a single number, and they would get a tinted box inside a table cell. */
.settled { color: var(--final-fg); }

/* نوارِ ترکیب بهای تمام‌شده — بخش ۷-ب-۸ */
/* The composition bar stacks the purchase against however many cost types the office has defined,
   and the number of those is not known when this file is written: §۷-ب-۴ made them a table a
   manager edits. A palette of distinct hues would therefore run out — and the band that got a
   repeat would get one that another band on the same bar is already using, in a chart whose entire
   job is telling bands apart.

   So one ramp off the brand purple, darkest first, and the projection hands out shades in
   descending order of amount. When it wraps, the repeat lands on the two smallest slivers on the
   bar, next to a legend that names and measures each one. That is a repeat rather than a
   contradiction, and it degrades in the right direction: the big bands are always distinct.

   The purchase is not on the ramp at all. Everything shaded is money we chose to spend moving and
   storing the crop; `goods` is the crop. Giving it the darkest shade of the same ramp would read as
   «the largest cost type», which on a normal bar — where the purchase is most of the width — is
   exactly the wrong thing to imply. */
.bar span.goods { background: var(--pr-dark); }
.bar span.s1 { background: #6E2A72; }
.bar span.s2 { background: #86478A; }
.bar span.s3 { background: #9E65A2; }
.bar span.s4 { background: #B683B9; }
.bar span.s5 { background: #CEA1D1; }
.bar span.s6 { background: #E2C4E4; }

/* نوار درصد پُری انبار — بخش ۷-ج-۳ */
/* Two segments in one groove: what is sellable, and what is only taking up room. A single bar
   would have to pick one of the two, and whichever it picked would be the wrong answer to the
   other question the bar gets asked. The rounded ends belong to the groove, not to the
   segments, so the join between them stays square. */
.bar.split { display: flex; }
.bar.split span { border-radius: 0; }
/* Taller, because on the stores page it is the figure of the card rather than a cell in a
   table, and read from further away. */
.bar.tall { height: 12px; }

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

th {
    background: var(--bg2);
    text-align: start;
    font-weight: 500;
    color: var(--tx2);
    padding: 9px 10px;
    border-bottom: 1px solid var(--line);
}

td { padding: 9px 10px; border-bottom: 1px solid var(--line); }
tbody tr:hover { background: var(--pr-light); }

/* Item ۵ of the client's queue: «انتخابِ محصول و بعد تیکِ ارگانیک چیدمانِ جدول را می‌شکند و
   محصولِ انتخاب‌شده ناپدید می‌شود». Both halves of that sentence are one cause, and the cause is
   the default `table-layout: auto`: the column widths are decided by the widest thing inside each
   cell, so every cell competes with every other one and a widget that appears later re-deals the
   whole row. Ticking organic reveals a 140px lot picker, and measured on the running app the
   product `select` went 87px → **22px** — wide enough for the arrow and nothing else. The chosen
   product was never lost (its `value` was still there); it had no room to be drawn. And the table
   stopped fitting its card: 993px of content inside 981px.

   `fixed` is the fix because it inverts who decides. The `<colgroup>` on the table states the
   plan, the browser honours it, and nothing inside a cell can take another column's space — so a
   field that appears or disappears changes nothing but its own cell. Fields are set to `width:
   100%` here rather than carrying pixel widths of their own, for the same reason: a box measured
   in pixels inside a column measured in percent is a disagreement waiting for a narrow screen. */
table.dense { table-layout: fixed; }

/* Tighter than the reading tables because this one is typed into, not read: twelve columns of
   `9px 10px` spend 240px of a 980px card on padding alone. */
table.dense th, table.dense td { padding: 7px 5px; }
table.dense input, table.dense select { width: 100%; min-width: 0; }
table.dense input[type="checkbox"] { width: auto; }

/* The one thing in the row that is not a field, and the last thing to stop fitting. Found at a
   1024px viewport, where the card is 725px wide: «حذف» wanted 47px and its column had 41, so the
   button alone put the table back outside its card. A button sized by its own padding is the same
   mistake as a field sized in pixels — it fills the column it was given. */
table.dense .btn { width: 100%; padding-inline: 2px; justify-content: center; overflow: hidden; }

/* A derived figure under the box it is derived from, for the two columns that were only ever
   read — it keeps the number vertically aligned down the table, so it can still be scanned, while
   costing no column of its own. */
.sub { display: block; font-size: 11px; color: var(--tx2); margin-top: 3px; }

/* Item ۱۶ of the client's queue: «جدول هم چینش درستی نداره، عناوینش فاصله داره تا ستون زیرش».
   He was right, and the cause is here rather than in any one page.

   `.ltr` does two jobs at once — it sets `direction: ltr` so a number or a code is not reordered
   by the Persian text around it, and it sets `text-align: left`. The second one is correct for a
   standalone span in a paragraph and wrong for a cell in a table: the `th` above it has
   `text-align: start`, which in this right-to-left page is the **right** edge, so the heading
   hangs from one side of the column and the figures hang from the other. Measured on the supplier
   table before this rule: the «بهای تمام‌شدهٔ هر کیلو» heading ended at x=407 and the number under
   it ended at x=265 — a gap of 142px, which is exactly what he was looking at.

   A physical `right` and not a logical `start`/`end` on purpose. Logical values resolve against
   the element's *own* direction, and this element's direction is `ltr` by definition, so neither
   keyword can name the edge the Persian heading hangs from. The physical side is the only one
   that says what is meant: the same edge as the heading.

   It also makes the numbers readable, which the left-aligned version did not. In a left-to-right
   run the right edge is the end of the string, so right-aligning a column of figures lines up the
   units under the units and the thousands under the thousands — the one arrangement in which two
   numbers can be compared by looking rather than by reading. */
th.ltr, td.ltr { text-align: right; }

.tag {
    display: inline-block;
    border-radius: 999px;
    padding: 2px 9px;
    font-size: 11.5px;
    font-weight: 500;
}

.tag.draft { color: var(--draft-fg); background: var(--draft-bg); }
.tag.final { color: var(--final-fg); background: var(--final-bg); }
.tag.void { color: var(--void-fg); background: var(--void-bg); }
.tag.reserved { color: var(--reserved-fg); background: var(--reserved-bg); }
.tag.organic { color: var(--final-fg); background: var(--final-bg); }
.tag.healthy { color: var(--healthy-fg); background: var(--healthy-bg); }
.tag.plain { color: var(--plain-fg); background: var(--plain-bg); }
.tag.rejected { color: var(--void-fg); background: var(--void-bg); }

/* Badges arrive in runs, generated in a loop, and Razor eats the whitespace between them. Without
   this they render as one unreadable word: «ارگانیک پایدارافت کمآزمایش‌های پاک». */
.tag + .tag { margin-inline-start: 4px; }

.alert { border-radius: var(--radius); padding: 10px 14px; font-size: 13px; margin-bottom: 14px; }
.alert.error { color: var(--void-fg); background: var(--void-bg); }
.alert.ok { color: var(--final-fg); background: var(--final-bg); }
.alert.warn { color: var(--draft-fg); background: var(--draft-bg); }

.validation-message { color: var(--void-fg); font-size: 12.5px; }
.invalid { border-color: var(--void-fg) !important; }

.nav-group {
    padding: 14px 10px 4px;
    font-size: 11px;
    font-weight: 500;
    color: var(--tx2);
}

/* ویرایشگر دسترسی — بخش ۱۵-ب */
.seg { display: flex; flex-wrap: wrap; gap: 6px; }

/* `a` as well as `button`, for the packing print page: the two printings of a packing list are a
   segmented control whose arms are real addresses, so the broker's copy can be bookmarked and
   mailed. Everywhere else in the system a `.seg` arm is still a button, and adding the selector
   changes nothing for them — there is no anchor inside any other `.seg` today. */
.seg button, .seg a {
    border: 1px solid var(--edge);
    background: var(--bg);
    color: var(--tx);
    border-radius: 999px;
    padding: 5px 14px;
    font: inherit;
    font-size: 13px;
    cursor: pointer;
    /* The two an anchor needs and a button does not: without them the arms sit on the text
       baseline with an underline through them and stop looking like one control. */
    text-decoration: none;
    display: inline-block;
}

.seg button.on, .seg a.on { background: var(--pr); border-color: var(--pr); color: #fff; }
.seg.sm button, .seg.sm a { padding: 2px 10px; font-size: 12px; }

/* پرونده خواندنی — ردیف‌های «عنوان / مقدار» */
/* Three columns because these are short answers, and one answer per line turns a supplier file
   into four screens of scrolling for twenty words of content. */
.facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px 20px; }
.facts .wide { grid-column: 1 / -1; }
.facts .k { display: block; font-size: 11.5px; color: var(--tx2); }
.facts .v { font-size: 13.5px; white-space: pre-wrap; }
@media (max-width: 860px) { .facts { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .facts { grid-template-columns: 1fr; } }

/* زبانه‌های صفحه تأمین‌کننده — بخش ۶-الف-۸ */
/* Underline rather than folder tabs: the strip sits directly on the page body with no box around
   it, and a boxed tab with nothing boxed underneath looks like a button that failed to press.
   The line along the bottom is what makes the seven read as one row of choices. */
.tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 2px;
    border-bottom: 1px solid var(--line);
    margin-bottom: 18px;
}

.tabs .tab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    color: var(--tx2);
    font-size: 13.5px;
    border-bottom: 2px solid transparent;
    /* Sits on the container's own border so the active underline replaces the grey line rather
       than being drawn one pixel below it. */
    margin-bottom: -1px;
}

/* `--bg` and not `--bg2`: the tab strip is one of the few controls that sits directly on the field
   rather than inside a card, and the field is now `--bg2` itself — so the old value hovered a tab
   to exactly the colour it was already painted on. Lifting it to white is the right direction
   anyway: a hovered tab moves toward the card plane it is about to show. */
.tabs .tab:hover { color: var(--tx); background: var(--bg); }

.tabs .tab.on {
    color: var(--pr);
    font-weight: 500;
    border-bottom-color: var(--pr);
}

/* The count. Grey by default — it is a quantity, not a verdict.

   `--ground` since بخش ۸۸, and it replaces `--plain-bg`. Two reasons, and the second is the real
   one. The measurable one: `--plain-bg` is #F5F5F4, which used to sit on a white card at an 8.7٪
   luminance step, and this strip now sits directly on the `--bg2` field, where the same grey
   measured **3.36٪** — a third of the field's own step below white, faint enough to read as a
   printing flaw rather than a pill. `--ground` measures 5.9٪ there, which is exactly the ladder's
   own step.

   The reason that would hold even if the numbers were fine: `--plain-bg` is a *state* colour. It is
   the «غیرارگانیک» pair of `--plain-fg`, and it means something on `.tag.plain` and `.av.c5`. A
   count pill has no verdict in it, so borrowing the verdict palette for it was a category error
   that happened to be invisible while everything was white. `--ground` is a neutral depth from the
   three-plane ladder, which is what a neutral pill should be asking for. */
.tabs .tab .n {
    min-width: 19px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--ground);
    color: var(--tx2);
    font-size: 11px;
    text-align: center;
}

.tabs .tab.on .n { background: var(--pr-light); color: var(--pr); }
/* Overdue follow-ups, and nothing else on this strip. */
.tabs .tab .n.bad, .tabs .tab.on .n.bad { background: var(--void-fg); color: #fff; }

.minis { display: flex; flex-wrap: wrap; gap: 22px; }
.minis .l { display: block; font-size: 11.5px; color: var(--tx2); }
.minis .v { font-weight: 700; font-size: 16px; }

.pg { padding: 14px 0; border-bottom: 1px solid var(--line); }
.pg:last-child { border-bottom: 0; }

.pg h3 { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.pg h3 .n { font-weight: 400; font-size: 12px; color: var(--tx2); }

.pr {
    display: grid;
    grid-template-columns: 40px 1fr auto;
    align-items: center;
    gap: 10px;
    padding: 5px 0;
}

.pr.ov { grid-template-columns: 200px 1fr auto; }
.pr code { font-size: 11.5px; color: var(--tx2); }

/* کلید روشن/خاموش */
.sw {
    inline-size: 36px;
    block-size: 20px;
    border-radius: 999px;
    border: 1px solid var(--edge);
    /* `--ground` and not `--plain-bg`, for the same category reason as the tab count above: an
       off switch is not «غیرارگانیک», it is off. The step also improves, 8.7٪ → 11.2٪ on a card. */
    background: var(--ground);
    padding: 0;
    position: relative;
    cursor: pointer;
    transition: background .12s;
}

.sw i {
    position: absolute;
    inset-block-start: 2px;
    inset-inline-start: 2px;
    inline-size: 14px;
    block-size: 14px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgb(0 0 0 / .25);
    transition: inset-inline-start .12s;
}

.sw.on { background: var(--pr); border-color: var(--pr); }
.sw.on i { inset-inline-start: 18px; }
.sw:disabled { opacity: .45; cursor: default; }

.sticky-save {
    position: sticky;
    inset-block-end: 0;
    display: flex;
    align-items: center;
    gap: 12px;
    background: var(--bg);
    border-block-start: 1px solid var(--line);
    padding: 12px 0;
}

/* فرم با پیش‌نمایش زنده کنار آن — بخش ۳۵
   ============================================================================================
   The form in one column, the sheet it produces in the other, redrawn as the form is filled.

   §۱۱ promised this under «امکانات مشترک» and the real build never had it — the HTML demo did,
   and the client noticed. The classes keep the demo's names (`.ed`, `.ed-form`, `.ed-pv`,
   `.pv-sticky`, `.pv-frame`, `.pv-in`, `.pv-note`) so that the two can be read side by side.

   One number, `--pv-scale`, and everything else derives from it.

   That is the whole trick here and it is worth a sentence. The sheet is 210mm × 297mm and will
   not be anything else — the whole point of it is that it is the size of the paper — so showing
   it in a 420px column means scaling it, and `transform: scale()` takes a plain number with no
   unit. The demo solved that in JavaScript: measure the column, divide by 794, write the result
   back as a style. Here there is no JavaScript at all, because the division runs the other way:
   the scale is the constant and the column is `calc(210mm * var(--pv-scale))`. Nothing has to be
   measured, so nothing has to be re-measured when the window is resized, and the page needs no
   interop round-trip to draw itself correctly the first time. */
.ed {
    /* About 111mm, i.e. 420px — wide enough that a subject line and the first lines of the body
       are legible, narrow enough to leave the form itself a usable column on a 1440px screen. */
    --pv-scale: .53;

    display: grid;
    grid-template-columns: minmax(0, 1fr) calc(210mm * var(--pv-scale));
    gap: 28px;
    align-items: start;
}

/* The preview can be put away. It is the second thing on the page, not the first: somebody typing
   a long letter body wants the width. */
.ed.nopv { grid-template-columns: minmax(0, 1fr); }
.ed.nopv .ed-pv { display: none; }

/* `min-inline-size: 0` on the form column: a grid item's default `auto` minimum is its content's,
   and one wide table inside a card would push the column past its track and squeeze the preview
   out of the viewport rather than scrolling inside itself. */
.ed-form { min-inline-size: 0; }

/* The sheet follows the form down the page. Without this, filling in the fields at the bottom of a
   long form means the preview has scrolled off the top — which is every field that is not in the
   first screenful, i.e. most of them. */
.pv-sticky { position: sticky; inset-block-start: 14px; }

.pv-frame {
    position: relative;
    inline-size: calc(210mm * var(--pv-scale));
    block-size: calc(297mm * var(--pv-scale));
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--bg2);
    /* The sheet inside is at full size and merely drawn small: `transform` does not change layout,
       so without this the untransformed 210mm box would still claim its space and overlap the
       form. */
    overflow: hidden;
}

/* Physical `top`/`left` and not `inset-block-start`/`inset-inline-start`, on a page whose document
   direction is RTL. The sheet is scaled from its top-left corner and pinned to the frame's
   top-left corner, and those two have to be the same corner; logical properties would make the
   second one the top-*right* and leave the sheet hanging off the edge. The sheet's own contents are
   RTL or LTR according to the document's language, which is decided inside the sheet. */
.pv-in {
    position: absolute;
    top: 0;
    left: 0;
    transform: scale(var(--pv-scale));
    transform-origin: top left;
}

/* The drop shadow is what made it read as a sheet of paper on a page; inside a bordered frame it
   reads as a smudge. */
.pv-in .letter-sheet, .pv-in .sales-sheet { box-shadow: none; margin: 0; }

.pv-note { font-size: 12px; color: var(--tx2); margin-block-start: 8px; line-height: 1.6; }
.pv-note p { margin: 0 0 4px; }

.pv-tabs { display: flex; gap: 6px; margin-block-end: 10px; flex-wrap: wrap; align-items: center; }

/* Under about 1280px there is no room for two columns, so the preview goes under the form rather
   than squeezing it. It stops being sticky at the same time: a sticky block below the thing it is
   stuck to would sit over the form's own fields. */
@media (max-width: 1280px) {
    .ed { grid-template-columns: minmax(0, 1fr); }
    .pv-sticky { position: static; }
}

/* Ctrl+P on an editor is somebody printing the form — a checklist to fill in by hand, usually.
   The preview is a 53%-scale picture of a sheet and has no business on it; the sheet itself is
   printed from `/print`, at full size, with `A4PageRule` in force. */
@media print {
    .ed { display: block; }
    .ed-pv { display: none; }
}

/* The button row above a print preview. Global, and that is the whole point of it being here:
   three pages draw this row — `/letters/{id}/print`, `/sales/{id}/print`, `/packing/{id}/print` —
   and it used to live in `LetterPrint.razor.css`, where Blazor's CSS isolation rewrites every
   selector to `.toolbar[b-…]` and stamps that attribute onto one component's markup only. So the
   letter's toolbar was styled and the other two were not, which the compiler cannot see and a
   screen barely shows: the row still reads as buttons. What was lost is the last rule below —
   the sales and packing toolbars printed onto the sheet, above a document measured in
   millimetres against the client's own sample. */
.toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    padding: 12px 16px;
    margin-bottom: 10px;
    background: #fff;
    border-bottom: 1px solid #e6e6e6;
}

.toolbar .paper { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.toolbar .warn { font-size: 13px; color: #9a5b00; }

@media print {
    .toolbar { display: none; }
}

/* The signed-out card, here for the same reason as `.toolbar` above: two pages draw it — the
   password form and the second-factor form of §۱۵-الف — and these rules sat in `Login.razor.css`,
   so the second one got none of them. Both files write `class="login-card card"`, which is the
   author's statement that they are the same card; the width cap is what makes them the same width
   when a user sees one straight after the other. */
.login-card {
    width: min(380px, 92vw);
    padding: 28px 30px 30px;
}

.login-card h1 { margin-bottom: 2px; }
.login-card p { margin: 0 0 20px; font-size: 12.5px; }
.login-card .btn { width: 100%; justify-content: center; margin-top: 4px; }

/* چت §۱۴ — فهرست افراد در یک ستون و گفتگو در ستون دیگر.
   بازطراحی: موردِ ۲۰ صفِ کارفرما.

   The rewrite is about one decision: the panel has a height, and the thread scrolls inside it.

   Before, the three parts of the conversation — who you are talking to, what was said, and the box
   you say the next thing in — were three `.card`s stacked down the page, each as tall as its
   contents. The page therefore grew with the conversation, and the composer, the one part somebody
   opens this screen in order to *use*, moved further down every time it was used. Twelve messages
   put it under the fold.

   Here the panel is `block-size`d, `.chat-head` and `.chat-compose` are `flex: 0 0 auto`, and
   `.chat-thread` is the only part that gives: `flex: 1` plus `min-block-size: 0`. That last
   declaration is not decoration — a flex item's automatic minimum size is its content, so without
   it the thread refuses to shrink below the height of two hundred messages and pushes the composer
   out of the panel exactly as the old stacked layout did.

   `clamp` rather than a bare `calc`: the lower bound keeps the panel usable on a laptop with a
   browser toolbar and a Windows taskbar — below it the page scrolls, which is the right failure —
   and the upper bound stops a thread on a 27-inch monitor from becoming a column of text a metre
   tall, which is harder to read than a shorter one, not easier. */
.chat {
    display: grid;
    grid-template-columns: 272px 1fr;
    gap: 14px;
    block-size: clamp(380px, calc(100vh - 196px), 760px);
}

/* The list of people. Scrolls on its own so that a long staff list cannot push the message box off
   the bottom of the page — the box is the thing the user came here to use.
   `padding: 0` because the card is now a scrollport and its own padding would be a strip of blank
   above the sticky search box that rows scroll through; the padding moved onto the children. */
.chat-people { padding: 0; overflow-y: auto; }
/* Both of the card's prose states — «کاربر دیگری در سیستم نیست» and «همکاری با این نام پیدا نشد» —
   which carry `margin:0` inline and so need their breathing room as padding. */
.chat-people > p { padding: 12px 14px; }

/* Stays put while the names scroll under it. A filter you have to scroll up to reach is a filter
   that gets used once and then forgotten about. */
.chat-find {
    position: sticky;
    inset-block-start: 0;
    z-index: 1;
    padding: 10px;
    background: var(--bg);
    border-bottom: 1px solid var(--line);
}

/* `input[type=search]` is deliberately absent from the shared field rule at line 237 — see the note
   on `.nav-search` for why — so this box is styled here, and styled to match that one: the two are
   the only search inputs in the system and they should not look like two different controls. */
.chat-find input[type=search] {
    width: 100%;
    border: 1px solid var(--edge);
    border-radius: 8px;
    padding: 6px 9px;
    font: inherit;
    font-size: 12.5px;
    color: var(--tx);
    background: var(--bg);
    -webkit-appearance: none;
    appearance: none;
}

.chat-find input[type=search]::-webkit-search-cancel-button { display: none; }

.chat-people a {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 9px;
    margin: 3px 7px;
    border-radius: var(--radius);
    color: var(--tx);
    text-decoration: none;
    border: 1px solid transparent;
}
.chat-people a:first-of-type { margin-block-start: 7px; }
.chat-people a:last-of-type { margin-block-end: 7px; }
.chat-people a:hover { background: var(--bg2); }
.chat-people a.on { background: var(--pr-light); border-color: var(--pr); }

/* `min-inline-size: 0` is what lets the two lines inside actually truncate: a flex item's default
   minimum is its content width, so without it a long name widens the row instead of being cut. */
.chat-people .txt { flex: 1; min-inline-size: 0; }
.chat-people .who { display: flex; align-items: baseline; gap: 6px; }
.chat-people .who b {
    font-weight: 600;
    font-size: 13px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.chat-people .when { margin-inline-start: auto; flex: 0 0 auto; font-size: 10.5px; color: var(--tx2); }
.chat-people .last { display: flex; align-items: center; gap: 6px; margin-block-start: 1px; }
.chat-people .last .tx {
    font-size: 11.5px;
    color: var(--tx2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The unread count. A pill rather than a `.tag`, because it sits at the end of a line of prose
   instead of in a column of its own, and because a one- or two-digit number is easier to find when
   it is round. `margin-inline-start: auto` pushes it to the far end of `.last`, so the pills of a
   whole list line up with each other instead of each sitting wherever its own preview ended. */
.chat-unread {
    margin-inline-start: auto;
    flex: 0 0 auto;
    background: var(--pr);
    color: #fff;
    border-radius: 999px;
    font-size: 10.5px;
    line-height: 1.6;
    padding: 0 7px;
}

/* The two letters that stand for a person, in the list and in the thread header, in the same colour
   in both places — that sameness is the whole point: it is what stops a reply being typed to the
   wrong colleague. `Initials` in `Chat.razor` explains which letters; `Tint` explains the colour. */
.av {
    flex: 0 0 auto;
    inline-size: 34px;
    block-size: 34px;
    border-radius: 999px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11.5px;
    font-weight: 700;
    line-height: 1;
}

/* Six tints, each a pale fill with its own darker text. Taken from the state palette in `:root`
   rather than invented, so the page introduces no new colours — and red is left out of the set on
   purpose, because `--void-fg` means «باطل شده» everywhere else in this system and a surname is not
   a cancelled document. The sixth is the neutral stone of `--plain-*`. */
.av.c0 { background: var(--pr-light); color: var(--pr); }
.av.c1 { background: var(--healthy-bg); color: var(--healthy-fg); }
.av.c2 { background: var(--draft-bg); color: var(--draft-fg); }
.av.c3 { background: var(--reserved-bg); color: var(--reserved-fg); }
.av.c4 { background: var(--final-bg); color: var(--final-fg); }
.av.c5 { background: var(--plain-bg); color: var(--plain-fg); }

/* The conversation: a header that does not move, a thread that does, a composer that does not. */
.chat-panel { padding: 0; display: flex; flex-direction: column; overflow: hidden; }

.chat-head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--line);
}
.chat-head b { font-size: 14px; display: block; line-height: 1.5; }
.chat-head .muted { font-size: 11.5px; display: block; line-height: 1.4; }

/* The thread. `column-reverse` with the newest message first in the markup, so the browser opens it
   already scrolled to the newest line without any JavaScript telling it to — a chat that opens at
   the oldest message is a chat the reader has to scroll before he can read it.

   One consequence worth stating, because it reads as a bug in the markup: later in the source is
   *higher* on the screen, so the day heading for a run of messages is emitted after them. See
   `NewDayAt`. */
.chat-thread {
    flex: 1;
    min-block-size: 0;
    display: flex;
    flex-direction: column-reverse;
    gap: 8px;
    overflow-y: auto;
    padding: 14px;
}

/* Nothing said yet. Centred, because an empty thread with its one sentence pinned to the top of a
   panel four hundred pixels tall reads as a page that failed to load the rest. */
.chat-thread.empty {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: center;
    text-align: center;
}

/* No conversation chosen. Same treatment, same reason. */
.chat-blank { align-items: center; justify-content: center; text-align: center; }

/* The date, once per day, instead of under every message. `::before`/`::after` draw the rules, so
   the heading is one element and the lines close in on however wide the label happens to be —
   «امروز» is four characters and «۲۷ مهر ۱۴۰۵» is eleven. */
.chat-day {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-block: 6px 2px;
    font-size: 11px;
    color: var(--tx2);
}
.chat-day::before, .chat-day::after {
    content: "";
    flex: 1;
    block-size: 1px;
    background: var(--line);
}

.chat-msg {
    max-inline-size: 72%;
    padding: 8px 11px;
    background: var(--bg2);
    border: 1px solid var(--line);
    /* Square on the top corner nearest its own side and round everywhere else — written logically,
       so the flattened corner follows the page's direction and does not have to be undone for RTL.
       It is the one piece of bubble shaping here, and it earns its place: it is what makes a long
       run of messages from one person read as a run. */
    border-radius: 14px;
    border-start-start-radius: 4px;
}
.chat-msg.mine {
    margin-inline-start: auto;
    background: var(--pr-light);
    border-color: transparent;
    border-start-start-radius: 14px;
    border-start-end-radius: 4px;
}
/* `pre-wrap` so a message typed over three lines stays three lines, and `anywhere` so a pasted URL
   with no spaces in it does not stretch the bubble past the edge of the page. */
.chat-msg p { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.chat-msg .meta { display: block; font-size: 10.5px; color: var(--tx2); margin-block-start: 4px; }
/* «خوانده شد» in the primary colour and «ارسال شد» in the grey of the line it sits on. The one that
   is worth a glance is the one that changed. */
.chat-msg .meta .seen { color: var(--pr); }

/* An attached file, in a message and in the composer before it is sent. No pictogram: see the note
   in `Chat.razor`. The chip is the same shape in both places so that what you picked looks like
   what you sent. */
.chat-files { display: flex; flex-wrap: wrap; gap: 6px; }
.chat-msg p + .chat-files { margin-block-start: 6px; }

.chat-file {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    max-inline-size: 100%;
    padding: 3px 8px;
    /* `--edge`, بخش ۸۸ — this is the eighth control in the list at the top of the file and it was
       missed on the first pass. A chip you can click to download a file is a control, so its border
       is a boundary at 3.02 : 1 and not a separator at 1.23 : 1. */
    border: 1px solid var(--edge);
    border-radius: 8px;
    background: var(--bg);
    color: var(--tx);
    text-decoration: none;
    font-size: 11.5px;
}

/* The hover is the border alone, and the fill is deliberately left where it is.

   It used to also paint `--pr-light`, which worked on an incoming bubble and failed completely on
   one of your own: `.chat-msg.mine` is *itself* `--pr-light`, so hovering a chip there filled it
   with exactly the colour behind it and the chip dissolved into the bubble. The ladder says a
   hovered thing lifts toward the surface — but this chip is already white, which is the surface,
   so there is nowhere left to lift it to and the border has to carry the state. `--edge` → `--pr`
   is a grey line becoming a dark purple one, which it does on both kinds of bubble. */
a.chat-file:hover { border-color: var(--pr); }
.chat-file .nm { max-inline-size: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-file .sz { flex: 0 0 auto; font-size: 10.5px; color: var(--tx2); }

/* The composer. Pinned to the bottom of the panel and on `--bg2`, which is the only thing that
   separates it from the thread — a second border would be a line directly under the border it
   already has. */
.chat-compose {
    flex: 0 0 auto;
    padding: 10px 14px 11px;
    border-top: 1px solid var(--line);
    background: var(--bg2);
}

.chat-picked { display: flex; flex-wrap: wrap; gap: 6px; margin-block-end: 8px; }

/* Textarea, attach, send — on one line, with the textarea taking whatever is left. `align-items:
   end` so that the two controls sit on the bottom edge of a box that grows when somebody writes
   three lines, rather than floating at the middle of it. */
.chat-row { display: flex; align-items: end; gap: 8px; }
.chat-row textarea { flex: 1; min-inline-size: 0; resize: vertical; margin: 0; }
.chat-row .btn { flex: 0 0 auto; }

/* Still a `.dropzone`, so the stretched-and-faded input underneath still catches a dropped file —
   that mechanism is explained at line 423 and is unchanged. What changed is that it is no longer a
   22-pixel-padded panel taking more of the composer than the message does. The dashes stay: they
   are the only thing on the control that says it will accept something dropped on it. */
.chat-clip {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    margin: 0;
    /* The same padding, border width, radius and font size as `.btn` above, which is how the two
       controls come out the same height without either of them being given one: a fixed
       `block-size` here would have to be re-guessed the day the button's padding changes. The
       border stays dashed, because that is the only thing on the control that says it will accept
       something dropped on it — and `--bg` rather than the dropzone's `--bg2`, which is the colour
       of the composer it sits in and would make it invisible. */
    padding: 7px 14px;
    border-width: 1px;
    border-radius: 8px;
    background: var(--bg);
}
.chat-clip span { font-size: 13.5px; color: var(--tx2); }
.chat-clip:hover span { color: var(--pr); }

.chat-hint { display: block; margin-block-start: 7px; font-size: 11px; line-height: 1.7; }

/* One column, and the panel stops being a fixed height: on a phone the whole page scrolls, and a
   thread locked to 60% of a 640-pixel screen would show four messages. The people list keeps a cap
   so that it does not take the whole screen before the conversation starts. */
@media (max-width: 860px) {
    .chat { grid-template-columns: 1fr; block-size: auto; }
    .chat-people { max-block-size: 34vh; }
    .chat-thread { max-block-size: 54vh; }
    .chat-msg { max-inline-size: 88%; }
}

/* داشبورد §۱۴ — دیوار کارت‌ها.

   `align-items: start` is the whole reason this is not a plain two-column grid: a card holding one
   number must not be stretched to the height of the stock card beside it, because an empty half of
   a box reads as data that failed to load. `.wide` is for the cards that are lists rather than
   figures — the public-notice banner and the activity log — which get the full width because a
   table squeezed into half a screen wraps every row onto three lines. */
.dash { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; align-items: start; }
.dash .wide { grid-column: 1 / -1; }
.dash h3 { margin-bottom: 10px; }
/* The one-line caption under a card's heading: which days the figure covers, whose rows it counts.
   Small and grey on purpose — it is read once, when the reader first doubts the number. */
.dash .cap { display: block; font-size: 11.5px; color: var(--tx2); margin: -6px 0 10px; }

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

/* موبایل: منو به بالای صفحه می‌رود — بخش ۱۸ */
@media (max-width: 860px) {
    .shell { grid-template-columns: 1fr; }

    .side {
        flex-direction: row;
        flex-wrap: wrap;
        border-inline-start: 0;
        border-bottom: 1px solid var(--line);
        /* Undoes the desktop scrollport above. Here the menu is a band across the top of the page
           and the page is the thing that scrolls; a `100vh` sticky band would cover the screen. */
        position: static;
        align-self: stretch;
        block-size: auto;
        overflow: visible;
    }

    .brand { width: 100%; padding-bottom: 8px; }
    /* Its own row, like the brand above it. Left to wrap with the menu entries it reads as the
       first one, and a text box is not a menu entry. */
    .nav-search { width: 100%; padding: 0 10px 8px; }

    /* بخش ۱۰۶. Here the rail is a band across the top, so a shut section is a chip that sits in
       the row with the others and eleven of them wrap into three lines instead of filling the
       screen — which is most of what the accordion buys on a phone.

       Opening one gives it the whole width. Left as an `auto`-width flex item it would try to hold
       its rows in a column as narrow as its own title, and «مقایسهٔ قیمت تأمین‌کنندگان» would wrap
       to four lines inside a chip. */
    .nav-sec { inline-size: auto; }
    .nav-sec[open] { inline-size: 100%; }
    .nav-sec > .nav-item { margin-inline-start: 0; }

    main { padding: 16px 14px 50px; }
}

/* نتیجه‌های جست‌وجوی سراسری — بخش ۴۳ */
/* The heading of one module's results: its name, how many matched, and the button that raises the
   cap. `margin-inline-end: auto` on the count rather than on the button, so the count stays beside
   the name it belongs to and the button goes to the far edge on its own. */
.search-head {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 8px;
}

.search-head .muted { margin-inline-end: auto; }

/* A list and not a table: the three parts of a hit are a link, a state and a line of context, and
   in fourteen modules at once they do not line up into columns that mean the same thing. */
.search-hits { list-style: none; margin: 0; padding: 0; }

.search-hits li {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: 8px;
    padding: 7px 0;
    border-top: 1px solid var(--line);
    font-size: 13.5px;
}

.search-hits li:first-child { border-top: 0; }

/* The context line can be long — a letter's subject, an address — and it is the part that may be
   given up. `min-width: 0` lets it shrink instead of pushing the tag off the row. */
.search-hits .muted { min-width: 0; font-size: 12px; }

/* خطاهای Blazor */
#blazor-error-ui {
    background: var(--draft-bg);
    color: var(--draft-fg);
    bottom: 0;
    display: none;
    inset-inline: 0;
    padding: 10px 16px;
    position: fixed;
    z-index: 1000;
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    top: 8px;
    inset-inline-start: 14px;
}

.blazor-error-boundary { background: var(--void-bg); color: var(--void-fg); padding: 12px 16px; border-radius: var(--radius); }
.blazor-error-boundary::after { content: "خطایی رخ داد."; }

/* ================================================================================================
   بخش ۸۸ — پاسخ، و حرکتی که هست ولی دیده نمی‌شود (موردِ ۱۹)
   ================================================================================================

   The other half of «بی‌روح», and the half that is not about colour at all: before this block the
   whole application contained **three** `transition` declarations — a tooltip's fade and the two
   halves of one toggle switch. Every other state change in the system was a jump cut. A row that
   lights up the instant the pointer crosses it, and goes out the instant it leaves, does not read
   as responsive; it reads as a redraw. That is a large part of what «بی‌روح» names, and it is the
   cheapest part to answer.

   120 milliseconds, and only on colour. That number is chosen to be *below* the point where a
   change reads as an animation: it takes the hard edge off the transition without the eye ever
   catching something in motion. Nothing here moves, grows, or fades in; no element changes
   position or size, so nothing can shift under a pointer that is already travelling toward it.
   On a screen somebody works at for eight hours, that distinction is the whole brief — motion you
   notice once is charming, and motion you notice four hundred times a day is the thing that made
   him write «خسته‌کننده». */
.btn,
.nav-item,
.tabs .tab,
.seg button, .seg a,
.chat-people a,
.search-hits li a,
tbody tr,
input, select, textarea {
    transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}

/* Honoured rather than assumed. Somebody who has turned motion down at the operating system has
   usually done it because motion costs them something, and a 120ms colour fade is not worth
   arguing with them about. `.01ms` and not `0` because a zero-length transition never fires its
   `transitionend`, and a stylesheet should not be the reason a script somewhere stops being
   called. */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        transition-duration: .01ms !important;
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        scroll-behavior: auto !important;
    }
}

/* نشانهٔ تمرکزِ صفحه‌کلید
   ------------------------------------------------------------------------------------------------
   The one `:focus` rule this application had covered `input`, `select` and `textarea` — and
   nothing else. Every button, every menu entry, every tab and every segmented arm fell through to
   whatever ring the browser draws by default, in whatever colour that browser likes, which in
   Chrome is a blue that belongs to no palette in this system.

   This is listed under «حرفه‌ای‌تر» and not under accessibility on purpose. The people who will
   use this for eight hours a day are entering documents, and the fast way to enter a document is
   with the keyboard; an application that cannot show you where you are is one you have to drive
   with the mouse. The ring is the brand purple at full strength, offset two pixels so it reads as
   a ring around the control rather than as a change to the control's own border.

   `:focus-visible` and not `:focus`: the ring appears for the keyboard and stays out of the way of
   the mouse, which is what stops it from looking like a control that got stuck on after a click. */
.btn:focus-visible,
.nav-item:focus-visible,
.nav-group:focus-visible,
.tabs .tab:focus-visible,
.seg button:focus-visible, .seg a:focus-visible,
.sw:focus-visible,
.chat-people a:focus-visible,
.dropzone:focus-within,
a:focus-visible {
    outline: 2px solid var(--pr);
    outline-offset: 2px;
}

/* The fields already marked focus with a purple border, and that stays — it is the one signal here
   that survives being seen out of the corner of the eye, because it changes the shape the eye was
   already tracking. What this adds is a ring worth the name: the existing `input:focus` rule draws
   its halo in `--pr-light`, which is `#F5EEF6`, a two-per-cent difference against a white field.
   That rule is left where it is as the fallback for the pointer; this one is later in the file and
   so wins wherever `:focus-visible` matches, which for a text box is every time. */
input:focus-visible, select:focus-visible, textarea:focus-visible {
    outline: 2px solid var(--pr);
    outline-offset: 1px;
    border-color: var(--pr);
}
