@import url("https://fonts.googleapis.com/css2?family=Cairo:wght@400;500;600;700;800;900&display=swap");

:root {
    /* ── Base BRAND palette (the ONLY vars a tenant may override — see Tenant.Theme / TenantTheme.AllowedKeys).
       Per-tenant theming injects a <style>:root{…}</style> that overrides these 6; every derived var below +
       every var(--…) reference across the app recomputes automatically. Defaults = the SHIFT brand. */
    --sidebar: #071731;
    --sidebar-2: #0b1f3d;
    --blue: #247cff;
    --blue-2: #0f45bd;
    --cyan: #35a8ff;
    --green: #14b889;
    --icon: #35a8ff;   /* sidebar nav-icon color — its own themeable key (owner's call) */

    /* ── Derived brand surfaces (computed from the base vars → follow the tenant theme, no separate override).
       Tuned so the SHIFT defaults reproduce the previous hardcoded shades closely. */
    --hero-accent: color-mix(in srgb, var(--blue) 82%, var(--sidebar-2));  /* ≈ old #2461d8 hero-end / brand accent */
    /* 🎯 THE BRAND BAND — one treatment for every surface that IS the brand: the detail hero, the modal
       headers, the active nav pill. The owner picked it by pointing at two of them and asking for the third
       to match («يبقى زي أول صورة أو زي الزرار اللي في nav bar») — and they turned out to be the SAME value,
       hand-copied in three places.
       Both ends are the brand and they sit CLOSE together, so it reads as a solid fill with a little life in
       it at any width. That is the property the previous version lacked: a dark→bright 135° sweep needs a
       wide banner to be a colour at all, and it started at `--sidebar-2`, a different themeable key, so on a
       grey-sidebar tenant its midpoint landed on mud.
       It flips direction by mode and that is correct: `--brand-strong` is mixed DARKER than the brand in
       light and toward WHITE in dark, so the band leans away from the page either way. */
    --hero: linear-gradient(90deg, var(--brand-strong), var(--blue));
    --brand-strong: color-mix(in srgb, var(--blue-2) 68%, var(--blue));    /* ≈ old #1b4fc0 dark link/text blue */
    /* ⚠️ Mixed against var(--panel), NOT a literal #fff: with a literal, every selected row would keep
       glowing pale white in dark mode. Same for every other color-mix in this file. */
    --blue-soft: color-mix(in srgb, var(--blue) 12%, var(--panel));        /* ≈ old #e3edff active tint */
    --brand-wash: color-mix(in srgb, var(--blue) 7%, var(--panel));        /* row hover / soft chip fill */

    /* ── Neutrals + status (NOT themeable — fixed for readability/semantics) */
    --ink: #172033;
    --muted: #7a8496;
    --line: #e2e5ea;
    --soft: #f4f5f7;
    --panel: #ffffff;
    --amber: #ffb21e;
    --red: #d94f45;
    /* 🟢 SUCCESS / «نشط» — semantic, and deliberately SEPARATE from the brand `--green` above
       (owner's call 2026-08-15). `--green` is one of the 7 keys a tenant may re-brand, and this one had
       both roles: `.status-active` set a themeable `var(--green)` text on a HARDCODED `#e6faf4`
       background, so audio-tech's orange palette rendered «نشط» as ORANGE TEXT ON GREEN.
       Status must mean the same thing on every tenant, which is the rule --amber/--red already follow.
       It is also darker than the old mint `#14b889`, which was barely legible on the pale background. */
    --ok: #12855c;
    --ok-soft: #e6faf4;
    --ok-line: #bfe6d1;

    /* ── 🌗 THE DARK-ABLE LAYER (2026-09-18). Everything below is redefined once, under the dark block at the
       bottom of this file — which is the whole point of naming them: 807 colour sites across 38 stylesheets
       cannot each be given a dark twin, so they route through these instead.

       🔑 NAMED BY MEANING, NOT BY VALUE, and the inventory is why: `#fff` appears 198 times and says THREE
       different things — 125 card surfaces, 49 pieces of text on a dark/brand surface, 16 borders. A
       value-map would have painted those 49 dark-on-dark. Assign by the PROPERTY the colour sits on.

       ⚠️ Brand vars (above) are NOT here: they belong to the tenant, and a tenant must not be able to
       repaint «approved» or the page canvas. A per-tenant DARK palette is the natural next step and needs
       no rework — it injects `html[data-theme="dark"]{--blue:…}` beside today's `:root{…}`. */
    --bg: #f5f6f8;          /* the page canvas — neutral, never the brand (a canvas is not identity) */
    --panel: #ffffff;       /* card / panel surface */
    --panel-2: #fbfbfc;     /* a card sitting ON a card (nested panels, inputs) */
    --ink-2: #4a5568;       /* secondary text — labels, table meta */
    --on-brand: #ffffff;    /* text + borders ON a brand or dark surface. NEVER flips: the sidebar and the
                               hero stay dark in both modes, so their text stays light in both. */
    --line-2: #d3d7de;      /* the stronger divider — table header underline, panel separators */
    --input-gloss: rgba(255, 255, 255, .75);   /* the inset top highlight on inputs — light-surface only */

    --amber-soft: #fdf3e0;
    --amber-line: #f0d9a8;
    --amber-ink: #8a5200;   /* amber as TEXT needs to be far darker than amber as a fill */
    --red-soft: #fdecec;
    --red-line: #f3c7ca;
    --red-ink: #b91c1c;
    /* 🔴 A SOLID RED FILL IS A DIFFERENT JOB FROM RED INK, and one token was doing both.
       `--red` is tuned to be READ as a status ("غياب", an error line) — softened so it does not shout
       from inside a table — and used as a button background it came out pale beside the tenant's
       saturated brand button next to it. Worse, it FAILED: white 13px/700 on #d94f45 is 4.07:1, under
       the 4.5:1 a label owes. `--red-strong` is the fill: deeper, unmistakably red, 5.6:1.
       The 3px KPI accent bar and the map pin stay on `--red` — they carry no text, so they want the
       colour that READS, not the one that carries a label. Same split as `--ok` / `--ok-soft`. */
    --red-strong: #c62828;
    --ok-ink: #1c6b45;
    /* Informational blue, DELIBERATELY fixed and not the brand: «إجازة» must mean the same thing on a
       tenant whose brand is orange. Same rule that split --ok from --green. */
    --info: #1d4ed8;
    --info-soft: #eef4ff;
    --info-line: #c7d6ee;

    /* ── Sidebar width (owner's call 2026-08-15: «شايفها عريضة»). 300px → 248px.
       The budget for the longest label, «المصروفات والعهد»: 28 sidebar padding + 28 button padding
       + 28 icon and gap + ~125 text ≈ 209px, so 248 keeps a comfortable margin and nothing wraps.
       The width was pure dead space — the labels sit right-aligned against the icon, so everything
       trimmed comes off the empty LEFT half of each row. */
    --sidebar-w: 248px;
    --rail-w: 76px;     /* the collapsed icon-only rail */
    /* ── SHADOW SCALE — four steps (owner's call 2026-08-15: «قلل الـ shadow العالي في كل النظام»).
       Same disease as the corners: ~34 hand-picked shadows, blur from 2px to 80px, offsets to 32px,
       opacity .04→.45 — so "raised" meant something different on every component and the big ones
       looked like the element was floating off the screen. Everything neutral now maps onto these four.
       Tune the whole console from here. Deliberately EXCLUDED, because a neutral token would erase what
       they mean: coloured brand/destructive glows, `inset` shadows, focus rings, and the two
       DIRECTIONAL shadows (the sidebar edge and the error bar) whose offset carries the direction. */
    --sh-sm: 0 1px 2px rgba(20, 45, 90, .06);      /* barely there: inputs, chips, small lifts */
    --sh-md: 0 2px 6px rgba(20, 45, 90, .08);      /* cards, panels, table wrappers */
    --sh-lg: 0 6px 16px rgba(20, 45, 90, .10);     /* dropdowns, toasts, hover lifts, sticky bars */
    --sh-xl: 0 12px 32px rgba(15, 23, 42, .16);    /* modals — the one place real depth is justified */

    --shadow: var(--sh-md);   /* legacy alias — 5 call sites still use it, so it just points at a tier */

    /* ── UI scale — ONE number sizes the whole console (owner's call 2026-08-15).
       The console is drawn ~10% larger than intended; he had been setting his BROWSER to 90% on every
       machine, which fixed it for him and left every employee and the client on the large version.
       Set to 1 to switch this off instantly. See the `body { zoom }` note for why zoom and not rem. */
    --ui-scale: .9;

    /* 🔴 FULL VIEWPORT SIZE — use these, never a raw viewport unit, for anything that must span the screen.
       A `vh`/`vw` is measured against the REAL viewport and is THEN scaled by the zoom, so a raw 100vh
       renders at 90% and leaves a bare strip: it did that to the sidebar, and then again to the modal
       scrim, which stopped 10% short and left a live, unclickable gap down one side of the screen.
       Dividing by the scale first cancels it out. Every other vh/vw in the app is wrapped the same way. */
    --app-h: calc(100vh / var(--ui-scale));
    --app-w: calc(100vw / var(--ui-scale));   /* same story sideways — a full-screen scrim needs this */

    /* ── CORNER RADIUS SCALE — five steps, tune the whole console from here (owner's call 2026-08-15:
       «احنا عاملينها round corner عالي اوي»). The app had grown FOURTEEN different radii between 4px and
       22px, picked per component, so nothing agreed with anything and the big ones read as bubbles.
       Everything now maps onto these five (pills stay 999px, avatars/dots stay 50%).
       Previous values, for reference when tuning:  xs←4/5  ·  sm←6/7  ·  md←8/9/10  ·  lg←12/14  ·  xl←16/18/20/22
       — then .panel/.stat-card were pulled xl→lg and the inputs lg→md, because that map went by VALUE and
       those three are the surfaces the eye lands on first. USE THE TIER, NOT A px, on anything new.
       Tightened one more step on his second look (4/6/8/10/12 → 3/4/6/8/10) — the point of the tokens is
       that this is five numbers, so keep tuning HERE rather than reaching into components. */
    --r-xs: 3px;    /* tiny: swatches, tags, inline markers */
    --r-sm: 4px;    /* chips, badges, small toggles */
    --r-md: 6px;    /* buttons, inputs, selects, small boxes — the workhorse */
    --r-lg: 8px;    /* cards, panels, table wrappers */
    --r-xl: 10px;   /* modals and full-width hero blocks — the largest corner in the app */
}

* {
    box-sizing: border-box;
}

/* The direction comes from <html dir> (the user's language), never from here — a `direction: rtl` on body
   would pin every page right-to-left whatever the language. `start` follows it. */
html,
body {
    text-align: start;
}

/* +1 in English, −1 in Arabic: the sign of "toward the reading END" on the x axis, for the few properties
   with no logical form (a directional box-shadow offset, a translateX). */
:root { --dir-x: -1; }
:root[dir="ltr"] { --dir-x: 1; }

/* 🔑 Whatever a user types is his — Arabic, English or both in one field — and it must read correctly in
   EITHER interface. plaintext lets each field (and each line of a textarea) take its direction from its own
   first strong character, so an English name in the Arabic UI aligns left and an Arabic note in the English
   UI aligns right, with nothing stored differently. */
/* ⚠️ Numbers, phones, dates and times are left out on purpose: they carry no strong character, so plaintext
   would resolve them LTR and left-align them inside an Arabic form the moment someone typed. */
input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="number"]):not([type="tel"]):not([type="date"]):not([type="time"]):not([type="datetime-local"]):not([type="month"]),
textarea {
    unicode-bidi: plaintext;
}

/* WHY `zoom` and not `html { font-size: 90% }`: these stylesheets are ~2700 px values against 17 rem,
   so a root font-size moves almost nothing — matching this uniformly the "proper" way means converting
   every px to rem, a far larger and riskier pass for an identical result.
   And NOT `transform: scale()`: that establishes a containing block and would trap every
   position:fixed modal/toast inside the page — the same trap documented on `.topbar`'s backdrop-filter.
   `zoom` is layout-level, so percentages and hit-testing stay consistent; an old browser that ignores it
   simply renders at 100%, i.e. exactly today's look.
   ⚠️ vw/vh do NOT come along — they measure the real viewport and are then scaled. EVERY viewport unit in
   the Admin is therefore wrapped as `calc(Nvh / var(--ui-scale))`, with `var(--app-h)`/`var(--app-w)` for
   the full-screen cases. Write a raw `vh`/`vw` and it silently comes out 10% short.
   ⚠️ It MULTIPLIES with browser zoom — the browser has to go back to 100%. */
body {
    margin: 0;
    zoom: var(--ui-scale);
    color: var(--ink);
    /* NEUTRAL, not brand (owner's call 2026-08-16). It was a hardcoded blue gradient — a radial
       rgba(36,124,255,.10) glow over #f7fbff→#eef5ff — so every page sat on a BLUE cast no matter the
       tenant's palette, which on an orange-branded tenant read as a mistake. Same family as the
       --green/«نشط» and the amber report icon. A canvas is not identity: it must recede on every brand,
       so it is neutral rather than themed. */
    background: var(--bg);
    font-family: "Cairo", "Segoe UI", Tahoma, Arial, sans-serif;
}

a,
.btn-link {
    color: var(--blue);
}

/* `--line-2`, NOT `--info-line`: the latter is the semantic BLUE, so every input on a dark page drew a
   pale blue outline that read as white — the same wrong-token bug as the table header underline.
   The top gloss is a TOKEN because a bright inset white is a LIGHT-surface convention; on a dark field
   it painted a white hairline across the top edge of every control. */
.form-select,
.form-control {
    min-height: 46px;
    border-color: var(--line-2);
    border-radius: var(--r-md);
    background-color: var(--panel);
    color: var(--ink);
    font-size: 14px;
    font-weight: 500;
    box-shadow: inset 0 1px 0 var(--input-gloss);
}

/* Dropdowns share the input weight/size — no heavier "selected value" look. */
.form-select {
    font-weight: 500;
}

/* The focus ring follows the TENANT's brand — it used to be a hardcoded blue (#6fb2ff), which drew a
   blue halo around the tenant switcher on an orange-branded tenant. Same "theme-proof, not re-theme"
   rule as the sidebar's nav pill. */
.form-control:focus,
.form-select:focus {
    /* 🔴 `background-color` is RE-STATED here on purpose. Bootstrap ships
       `.form-control:focus { background-color: var(--bs-body-bg) }` — leave it out and clicking into a
       search box on a dark page turns the field solid WHITE. Ours sets it at rest but not on focus,
       which is exactly how that shipped unnoticed. */
    background-color: var(--panel);
    color: var(--ink);
    border-color: color-mix(in srgb, var(--blue) 55%, var(--panel));
    box-shadow: 0 0 0 .24rem color-mix(in srgb, var(--blue) 16%, transparent);
}

/* The small grey line above a page title (topbar breadcrumb + 5 page headers). GLOBAL, not scoped:
   it used to be defined only inside MainLayout.razor.css, so every OTHER page's eyebrow rendered
   completely unstyled. NO letter-spacing / text-transform — the text is Arabic, and letter-spacing
   pulls apart letters that are meant to join (uppercase is just a no-op). */
.eyebrow {
    display: block;
    margin-bottom: 2px;
    color: var(--muted);
    font-size: 12.5px;
}

/* A page-level action row. The main screens no longer carry a title block (the topbar names the page),
   so a screen with no `.panel` header to host its primary button puts it here instead. */
.page-actions {
    display: flex;
    justify-content: flex-end;   /* the LEFT edge in RTL, matching every other screen's action */
    gap: 10px;
    margin-bottom: 16px;
}

/* Only the three FORM pages still use this — there it carries the eyebrow + title + رجوع/حفظ buttons.
   It was removed from every main page: the topbar already names the page, so the block was a duplicate
   title plus a paragraph of explanation nobody reads twice. */
.section-title {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 16px;
    margin-bottom: 16px;
}

.section-title h2 {
    font-size: 20px;
    font-weight: 800;
    margin: 0;
}

.section-title p {
    color: var(--muted);
    margin: 4px 0 0;
}

/* ===== /setup overview: a compact brand callout + grouped card panels =====
   Replaced a 190px gradient hero + a flat 17-card grid. Everything here is themed through --blue
   (color-mix), never a literal — the owner's reference looked orange only because his tenant is. */
.setup-callout {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 16px;
    padding: 14px 18px;
    border: 1px solid color-mix(in srgb, var(--blue) 26%, transparent);
    border-radius: var(--r-lg);
    background: color-mix(in srgb, var(--blue) 6%, var(--panel));
}

.setup-callout > i {
    flex: 0 0 auto;
    font-size: 28px;
    color: var(--blue);
}

.setup-callout p {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    color: var(--brand-strong);
    font-size: 14px;
    font-weight: 600;
    line-height: 1.6;
}

.setup-group {
    margin-bottom: 16px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--panel);
    box-shadow: var(--sh-sm);
    overflow: hidden;   /* clips the grid's 1px overhang (see .setup-group-grid) */
}

.setup-group-head {
    padding: 14px 18px;
}

.setup-group-head h2 {
    margin: 0;
    font-size: 16px;
    font-weight: 800;
    color: var(--ink);
}

/* Cells share hairlines instead of floating as separate cards. Every cell draws its own top +
   inline-start border: row 1's top border becomes the header separator (wanted), and column 1's
   inline-start border is pulled onto the panel border by the -1px margin, then clipped. Handles a
   ragged last row for free — an absent cell simply draws nothing. */
.setup-group-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    margin-inline-start: -1px;
}

.setup-module {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding: 16px 18px;
    border-top: 1px solid var(--line);
    border-inline-start: 1px solid var(--line);
    background: var(--panel);
    text-align: start;
    color: inherit;
    text-decoration: none;
    transition: background .16s ease;
}

/* A SHORT LAST ROW left the final card with no neighbour to draw the line beside it, so «السيارات»
   sat open on one side (owner-caught). Every other line here is drawn by the cell that comes AFTER it;
   the last card has nothing after it, so it draws its own closing edge — unless it already sits in the
   last column, where that edge is the panel border. The nth-child step IS the column count, so this is
   restated in each breakpoint below and switched off entirely at one column. */
.setup-group-grid > .setup-module:last-child:not(:nth-child(3n)) {
    border-inline-end: 1px solid var(--line);
}

.setup-module:hover {
    background: color-mix(in srgb, var(--blue) 5%, var(--panel));
    color: inherit;
    text-decoration: none;
}

.setup-module:focus,
.setup-module:active {
    color: inherit;
    text-decoration: none;
}

/* A bare outline glyph in the brand colour — no tile (same call as .stat-card).
   26px against a 14.5px title ≈ the reference's proportion; the negative margin pulls the taller
   glyph box back so it reads centred on the TITLE line rather than sinking into the description. */
.setup-module-ico {
    flex: 0 0 auto;
    margin-top: -2px;
    font-size: 26px;
    line-height: 1;
    color: var(--blue);
}

.setup-module-body {
    flex: 1 1 auto;
    min-width: 0;
}

.setup-module-body strong {
    display: block;
    font-size: 14.5px;
    color: var(--ink);
}

.setup-module-body span {
    display: block;
    margin-top: 3px;
    color: var(--muted);
    font-size: 12.5px;
    line-height: 1.65;
}

/* Points to the reading END in RTL = "go". */
.setup-module-go {
    flex: 0 0 auto;
    align-self: center;
    font-size: 14px;
    color: var(--muted);
    transition: color .16s ease;
}

.setup-module:hover .setup-module-go {
    color: var(--blue);
}

/* Not-yet-built modules: visible but inert (no destination yet). */
/* DORMANT since 2026-08-22 — the three «قيد الإعداد» cards are hidden (commented out in
   SetupMockService), so no card currently renders as `is-soon`. Kept because restoring one brings
   this straight back; same call as the other inert hooks. */
.setup-module.is-soon {
    cursor: not-allowed;
    background: var(--panel);
}

.setup-module.is-soon:hover {
    background: var(--panel);
}

.setup-module.is-soon .setup-module-ico {
    color: var(--muted);
}

.setup-module.is-soon .setup-module-body strong {
    color: var(--muted);
}

/* ===== KPI / summary cards (page overview strip — reusable across list pages) ===== */
.stat-cards {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
    margin: 0 0 16px;
}

/* row-reverse so the LAST child (the text body) lands on the reading start (right in RTL) beside the
   accent bar, and the icon is pushed to the far end — the mock's layout, mirrored for RTL instead of
   copied literally. DOM order stays <icon><body> so no page markup had to change. */
.stat-card {
    position: relative;
    display: flex;
    flex-direction: row-reverse;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 18px 22px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--panel);
    box-shadow: var(--sh-sm);
}

/* the card's only colour — themed, so it follows the tenant's brand.
   inset-inline-start = the right edge in RTL; swap to inset-inline-end to move it to the left. */
.stat-card::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    top: 18px;
    bottom: 18px;
    width: 3px;
    border-radius: 999px;
    background: var(--blue);
}

/* A bare outline glyph — no tile. The .green/.cyan/.amber classes are still on the markup but are
   deliberately INERT now: colour lives in the accent bar, and the icons read as one quiet family. */
.stat-card-icon {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    color: var(--muted);
    font-size: 22px;
}

.stat-card-body {
    display: grid;
    gap: 2px;
    min-width: 0;
}

.stat-card-value {
    font-size: 26px;
    font-weight: 800;
    color: var(--ink);
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.stat-card-label {
    font-size: 13px;
    font-weight: 700;
    color: var(--ink);
}

.stat-card-sub {
    font-size: 12px;
    color: var(--muted);
}

/* An HONEST placeholder — a KPI whose module is not built yet («—» + «قريبًا»). Muted rather than
   removed, because "this number is coming" is information; the grey accent stops it competing with the
   live cards beside it. */
.stat-card.soon {
    opacity: .74;
}

.stat-card.soon::before {
    /* Grey, and MUTED rather than a literal: a "this number is coming" card must recede in both modes —
       a fixed light grey would be a bright bar on a dark canvas. */
    background: var(--muted);
}

/* ── Compact variant: a row of many small counts (the mission attendance summary).
   Same species as .stat-card, NOT a third card style — it used to be its own `.att-kpi` with its own
   border colour, its own hardcoded ink, and its own accent scale.

   ⚠️ The colour variants below are named by MEANING, not by colour, and that is the point: the
   `.green/.cyan/.amber` variants deleted from .stat-card were decoration, so they went. Here the colour
   IS the information — «غياب» and «في الموعد» are not the same news — so it stays, named so it cannot
   be reused as decoration later. */
.stat-cards.compact {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.stat-cards.compact .stat-card {
    flex: 1;
    min-width: 110px;
    padding: 12px 14px;
}

.stat-cards.compact .stat-card-value { font-size: 22px; }

.stat-card.is-ok::before { background: var(--ok); }
.stat-card.is-warn::before { background: var(--amber); }
.stat-card.is-bad::before { background: var(--red); }
.stat-card.is-info::before { background: var(--blue); }

/* A ZERO is not news. No bar, muted number — so the eye lands on the counts that are not zero
   instead of on seven equally-loud boxes, four of which say nothing happened. */
.stat-card.is-zero::before { background: transparent; }
.stat-card.is-zero .stat-card-value { color: var(--muted); font-weight: 700; }
/* A KPI card that is also the filter for what it counts (e.g. «بدون موقع» on /projects). */
.stat-card-btn { font: inherit; color: inherit; text-align: start; cursor: pointer; width: 100%; }
.stat-card-btn:hover { border-color: color-mix(in srgb, var(--blue) 35%, var(--line)); }
/* A project with no located site — amber: nothing is wrong yet, but no mission can be made on it. */
.no-loc-tag { display: inline-flex; align-items: center; gap: 3px; margin-inline-start: 6px; padding: 1px 7px;
  border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap;
  color: var(--amber-ink); background: color-mix(in srgb, var(--amber) 14%, transparent); }

/* Not a status count — hours, sitting in a row of day-counts. Kept last and deliberately plain. */
.stat-card.is-total::before { background: var(--line); }

/* ===== Mission-day chips (the per-day strip on /missions and the mission view).
   GLOBAL because both render them and each kept a private copy that had already drifted (.clickable
   existed on only one) — eighth instance of that trap.

   COLOUR RULE: «مخطّطة» is the DEFAULT state of every day that has not happened yet, so it is quiet —
   a grey pill and no accent bar. Colour is spent only on the three states that differ from normal.
   Its old styling was also the «نشط» bug again: a themed `var(--blue-2)` text on a HARDCODED `#eef4ff`
   background, i.e. orange letters on a blue pill for an orange-branded tenant. ===== */
.mday-list { display: flex; flex-wrap: wrap; gap: 8px; }

.mday {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 9px 12px;
    min-width: 138px;
    /* A UNIFORM border on all four sides. Reserving the inline-start edge as `3px solid transparent`
       for the accent left a 3px hole where the card's own outline should be — the border read as
       missing on that side (owner-caught). The accent is a ::before instead, exactly like .stat-card,
       so a card with no meaningful status is simply a clean box. */
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--panel);
    font-size: 13px;
}

.mday.clickable { cursor: pointer; }
.mday.clickable:hover { border-color: var(--blue); box-shadow: var(--sh-md); }

.mday-num { color: var(--muted); font-size: 11px; font-weight: 700; }
.mday-id { display: flex; align-items: baseline; gap: 7px; }
.mday-id strong { font-weight: 800; }
.mday-date { color: var(--muted); font-variant-numeric: tabular-nums; font-size: 12px; }

.mday-meta { display: flex; align-items: center; gap: 8px; }
.mday-roster { display: inline-flex; align-items: center; gap: 3px; color: var(--muted); font-size: 12px; font-weight: 600; }
.mday-roster i { font-size: 11px; }
.mday-meta > .bi-sticky-fill { color: var(--amber-ink); font-size: 12px; }

/* Default = quiet. The three states below override it. */
.mday-tag {
    padding: 1px 8px;
    border-radius: 999px;
    background: var(--soft);
    color: var(--muted);
    font-size: 11.5px;
    font-weight: 700;
}

/* `.mday.st-planned` has NO rule on purpose — the default above IS the planned look. The class stays
   on the markup as a hook, like the inert .stat-card colour variants.
   The amber below is now the only amber on these chips, and it means exactly «عطلة». (A --warn /
   --warn-soft pair mirroring --ok / --ok-soft would tidy it further — not done, not asked.) */
.mday.st-working::before,
.mday.st-done::before,
.mday.st-off::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    top: 8px;
    bottom: 8px;
    width: 3px;
    border-radius: 999px;
}

.mday.st-working::before { background: var(--blue); }
.mday.st-working .mday-tag { background: var(--blue-soft); color: var(--brand-strong); }
.mday.st-done::before { background: var(--ok); }
.mday.st-done .mday-tag { background: var(--ok-soft); color: var(--ok); }
.mday.st-off::before { background: #c47d04; }
.mday.st-off .mday-tag { background: var(--amber-soft); color: var(--amber-ink); }

/* A weekend is a property of the DATE, not a status — so it is a neutral wash. Amber on these chips
   now means one thing only: the day is marked «عطلة». */
.mday.weekend { background: var(--soft); }

/* ===== Read-only detail fields inside a panel (project / mission views).
   GLOBAL because both views render them and each kept a private copy — seventh instance of that trap.

   Deliberately NOT card-like: these are five pieces of TEXT, and giving each a fill AND a border made
   them read as five objects sitting under four real KPI cards. A recessed grey field on a white panel,
   against white cards on a grey page, is what makes the two layers mean different things. The old
   #eef4ff / #d4e2ff were also hardcoded blue — a blue island on an orange-branded tenant. ===== */
.rule-grid {
    display: grid;
    /* auto-fit, never a fixed count: 5 boxes in a 4-column grid leave an orphan on its own row. */
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px;
}

.rule-grid.two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.rule-grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.rule-box {
    padding: 12px 14px;
    border-radius: var(--r-md);
    background: var(--soft);
}

.rule-box span { display: block; margin-bottom: 4px; color: var(--muted); font-size: 12px; }
.rule-box strong { font-size: 18px; font-weight: 700; }
.rule-box.wide,
.rule-box.premium-rules { grid-column: 1 / -1; }
.rule-box.premium-rules { margin-top: 12px; }
.rule-box.premium-rules strong { font-size: 15px; }
.rule-box.premium-rules small { display: block; margin-top: 5px; color: var(--muted); font-size: 12px; }

/* A label→value list — the vertical counterpart, for a panel with narrow width. */
.meta-list { display: grid; gap: 8px; }
.meta-list div {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--line);
}
.meta-list span { color: var(--muted); font-size: 13px; }
.meta-list strong { font-size: 14px; font-weight: 700; }

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

@media (max-width: 520px) {
    .stat-cards { grid-template-columns: 1fr; }
}

/* ===== Change-trail badges — used by /audit AND the platform trail on /platform/tenants/{code}.
   Global because two pages render them; a scoped copy left one of them unstyled.
   Note: the insert/update/delete colours are literal on purpose (they are a fixed 3-state vocabulary, not
   brand), but they predate --ok/--ok-soft and are a slightly different green — converge only if asked. ===== */
.ent-badge {
    display: inline-block;
    padding: 3px 10px;
    border-radius: 999px;
    background: var(--info-soft);
    color: var(--ink);
    font-size: 12px;
    font-weight: 700;
}

.act-badge {
    display: inline-block;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 800;
}

.act-badge.act-insert { background: var(--ok-soft); color: var(--ok-ink); }
.act-badge.act-update { background: var(--amber-soft); color: var(--amber-ink); }
.act-badge.act-delete { background: var(--red-soft); color: var(--red-ink); }

.soon-badge {
    display: inline-block;
    margin-inline-start: 6px;
    padding: 1px 8px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    color: var(--muted);
    background: var(--info-soft);
    vertical-align: middle;
}

/* ===== Standalone auth (login / change-password / error): split hero + form =====
   The page before sign-in is SHIFT's own. Nobody picks a company any more (the credential decides it), so
   the palette is PINNED here and no tenant theme can reach it: a customer's colours on this page would greet
   every other customer too. The --sb-* tokens are SHIFT's DEFAULT palette, the same values as :root's --sidebar /
   --sidebar-2 / --blue / --blue-2 / --cyan, so the page before sign-in looks like the console behind it (owner,
   2026-10-12). Literals on purpose: :root's copies can be repainted by a tenant theme. */
.auth-split {
    --sb-blue: #247cff;
    --sb-blue-2: #0f45bd;
    --sb-strong: color-mix(in srgb, var(--sb-blue-2) 68%, var(--sb-blue));   /* = --brand-strong in light */
    --sb-navy: #071731;
    --sb-navy-2: #0b1f3d;
    --sb-glow: #35a8ff;
    --sb-ink-on-form: var(--sb-blue);
    min-height: var(--app-h);
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
    background: var(--soft);
}

/* The wordmark is the mobile app's mask PNG, painted by `background`, so one file serves white on the hero
   and brand blue on the form without a second image or a dark-mode swap. */
.auth-wordmark {
    display: block;
    width: 150px;
    aspect-ratio: 1000 / 524;
    background: #fff;
    -webkit-mask: url("img/shift-logo-mask.png") center / contain no-repeat;
    mask: url("img/shift-logo-mask.png") center / contain no-repeat;
}

/* Brand hero panel */
.auth-hero {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 24px;
    padding: 52px 56px;
    color: #fff;
    background:
        radial-gradient(60% 55% at 85% 0%, color-mix(in srgb, var(--sb-glow) 38%, transparent), transparent 70%),
        radial-gradient(55% 50% at 0% 100%, color-mix(in srgb, var(--sb-blue) 45%, transparent), transparent 70%),
        linear-gradient(160deg, var(--sb-navy) 0%, var(--sb-navy-2) 55%, color-mix(in srgb, var(--sb-navy-2) 72%, var(--sb-blue)) 100%);
}

/* A faint dot grid, fading out toward the form side, so the navy reads as a surface rather than a flat fill. */
.auth-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background-image: radial-gradient(rgba(255, 255, 255, .09) 1px, transparent 1.4px);
    background-size: 22px 22px;
    -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 85%);
    mask-image: linear-gradient(to bottom, #000 0%, transparent 85%);
}

.auth-hero-top,
.auth-hero-mid,
.auth-hero-foot {
    position: relative;
    z-index: 2;
}

/* The live network (js/authNetwork.js) fills the whole panel behind the text. */
.auth-net {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    z-index: 0;
    pointer-events: none;
}

/* A scrim under the text so the network never competes with the words. */
.auth-hero::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    background: linear-gradient(to top, rgba(8, 18, 38, .92) 0%, rgba(8, 18, 38, .6) 30%, transparent 55%);
}

.auth-hero-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}

.auth-live {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 7px 13px;
    border: 1px solid rgba(157, 190, 245, .28);
    border-radius: 999px;
    background: rgba(10, 23, 48, .55);
    font-size: 12px;
    font-weight: 700;
    color: rgba(232, 240, 255, .9);
}

.auth-live i {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #3ddc97;
    box-shadow: 0 0 0 0 rgba(61, 220, 151, .6);
    animation: auth-live 2s ease-out infinite;
}

@keyframes auth-live {
    0% { box-shadow: 0 0 0 0 rgba(61, 220, 151, .55); }
    100% { box-shadow: 0 0 0 9px rgba(61, 220, 151, 0); }
}

.auth-hero-mid {
    margin-top: auto;
    max-width: 520px;
}

.auth-hero-title {
    max-width: 16ch;
    margin: 0;
    font-size: 38px;
    font-weight: 900;
    line-height: 1.25;
    color: #fff;
}

.auth-hero-lede {
    margin: 14px 0 0;
    max-width: 46ch;
    font-size: 15px;
    line-height: 1.8;
    color: rgba(226, 236, 255, .78);
}

.auth-points {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    margin: 22px 0 0;
    padding: 0;
    list-style: none;
}

.auth-points li {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 11px 13px;
    border: 1px solid rgba(157, 190, 245, .18);
    border-radius: var(--r-md);
    background: rgba(255, 255, 255, .05);
    backdrop-filter: blur(6px);
    font-size: 13px;
    font-weight: 700;
    color: #eef4ff;
}

.auth-points i {
    display: grid;
    place-items: center;
    flex: none;
    width: 30px;
    height: 30px;
    border-radius: var(--r-sm);
    background: rgba(111, 155, 230, .22);
    color: #cfe0ff;
    font-size: 15px;
}

.auth-hero-foot {
    font-size: 12px;
    font-weight: 600;
    color: rgba(255, 255, 255, .55);
}

/* Form side */
.auth-main {
    display: grid;
    place-items: center;
    padding: 40px 24px;
}

.auth-form {
    width: 100%;
    max-width: 420px;
    display: grid;
    gap: 16px;
    padding: 36px 34px 32px;
    border: 1px solid var(--line);
    border-radius: var(--r-xl);
    background: var(--panel);
    box-shadow: 0 1px 2px rgba(10, 23, 48, .04), 0 12px 32px rgba(10, 23, 48, .07);
}

.auth-form-head {
    margin-bottom: 4px;
}

/* A label belongs to the field under it: tight inside a group, looser between groups. */
.auth-form form {
    display: grid;
    gap: 18px;
}

.auth-form .field-group {
    display: grid;
    gap: 7px;
    margin: 0;
}

.auth-form .form-label {
    margin: 0;
    font-size: 13px;
    font-weight: 700;
    color: var(--ink-2);
}

.auth-form-head h2 {
    margin: 0;
    font-size: 28px;
    font-weight: 900;
    color: var(--ink);
}

.auth-form-head p {
    margin: 6px 0 0;
    color: var(--muted);
    font-size: 14px;
}

/* On a wide screen the hero carries the wordmark; the form shows its own only once the hero is gone. */
.auth-wordmark--form {
    display: none;
    width: 118px;
    margin-bottom: 18px;
    background: var(--sb-ink-on-form);
}

/* A field with its icon at the reading start. The input keeps its own direction (inputs are
   `unicode-bidi: plaintext`), so an e-mail typed into the Arabic page still reads left to right. */
.auth-input {
    position: relative;
}

.auth-input > i:first-child {
    position: absolute;
    inset-inline-start: 15px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 16px;
    color: var(--muted);
    pointer-events: none;
    transition: color .15s;
}

.auth-input .form-control {
    min-height: 50px;
    padding-inline-start: 44px;
}

.auth-input:focus-within > i:first-child { color: var(--sb-blue); }

.auth-split .form-control:focus {
    border-color: var(--sb-blue);
    box-shadow: 0 0 0 .24rem color-mix(in srgb, var(--sb-blue) 16%, transparent);
}

/* Autofill paints its own pale blue; keep the field on the card surface instead. */
.auth-split .form-control:-webkit-autofill,
.auth-split .form-control:-webkit-autofill:hover,
.auth-split .form-control:-webkit-autofill:focus {
    -webkit-box-shadow: 0 0 0 60px var(--panel) inset;
    -webkit-text-fill-color: var(--ink);
    caret-color: var(--ink);
}

/* password input + eye toggle, INSIDE the field (shared by both auth pages). It used to be a second
   bordered box beside the input, 4px taller, which read as a separate button. */
.pw-row {
    position: relative;
}

.pw-row .form-control {
    padding-inline-end: 48px;
}

.pw-row .btn-ghost {
    position: absolute;
    inset-inline-end: 6px;
    top: 50%;
    transform: translateY(-50%);
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    min-height: 0;
    padding: 0;
    border: 0;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--muted);
}

.pw-row .btn-ghost:hover { background: var(--soft); color: var(--ink); }

.login-submit {
    justify-content: center;
    width: 100%;
    min-height: 50px;
    margin-top: 8px;
    font-size: 15px;
}

.auth-split .btn-save {
    /* The same band as the active menu item (--hero), so «دخول» is the console's primary action already. */
    background: linear-gradient(90deg, var(--sb-strong), var(--sb-blue));
    color: #fff;
}

.auth-split .btn-save:hover:not(:disabled) { filter: brightness(1.08); }

.auth-split .btn-save:focus-visible {
    outline: none;
    box-shadow: 0 0 0 .24rem color-mix(in srgb, var(--sb-blue) 30%, transparent);
}

html[data-theme="dark"] .auth-split {
    --sb-strong: color-mix(in srgb, var(--sb-blue) 72%, #ffffff);   /* = --brand-strong in dark */
    --sb-ink-on-form: #8fbcff;
}

@media (prefers-reduced-motion: reduce) {
    .auth-live i { animation: none; }
}

.login-tenant {
    background: color-mix(in srgb, var(--blue) 10%, var(--panel));
    color: var(--info);
    border-radius: var(--r-lg);
    padding: 10px 14px;
    font-size: 13px;
    text-align: center;
}

.topbar-auth {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
}

@media (max-width: 920px) {
    .auth-split {
        grid-template-columns: 1fr;
    }

    /* The hero is decoration on a phone; the form carries the wordmark itself. */
    .auth-hero {
        display: none;
    }

    .auth-wordmark--form {
        display: block;
    }

    .auth-main {
        align-items: start;
        padding: 72px 16px 28px;
    }

    .auth-form {
        padding: 28px 22px 24px;
    }
}

.panel {
    min-width: 0;
    /* Named so the full-bleed table below cancels it by NAME — a literal -18px sitting hundreds of
       lines from the 18px it cancels is the drift this file keeps recording. */
    --panel-pad: 18px;
    /* Named for the same reason as the padding: a full-bleed table that is the panel's LAST child has to
       pick these corners back up, and two pages still carry an older `.panel` copy at --r-xl. */
    --panel-radius: var(--r-lg);
    padding: var(--panel-pad);
    margin-top: 16px;
    border: 1px solid var(--line);
    border-radius: var(--panel-radius);
    background: var(--panel);
    box-shadow: var(--shadow);
}

.panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 14px;
}

.panel-header h3 {
    margin: 0;
    font-size: 16px;
    font-weight: 800;
}

.panel-actions {
    display: inline-flex;
    align-items: center;
    justify-content: flex-end;
    /* Stays at the END of the header even when it is the ONLY child — several panels dropped their
       title block (the topbar and the KPI card above already said it) and `space-between` alone would
       have swung the button across to the other side. */
    margin-inline-start: auto;
    gap: 8px;
    flex-wrap: wrap;
}

.split-grid {
    display: grid;
    grid-template-columns: 1.1fr .9fr;
    gap: 16px;
}

.split-grid.no-margin {
    margin: 0;
}

.split-grid > * {
    min-width: 0;
}

.project-logo-field {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 14px;
    margin-bottom: 14px;
    border: 1px solid var(--info-line);
    border-radius: var(--r-md);
    background: var(--panel);
}

.project-logo-preview {
    display: grid;
    place-items: center;
    width: 54px;
    height: 54px;
    border-radius: var(--r-md);
    color: var(--on-brand);
    font-size: 18px;
    font-weight: 850;
    /* no letter-spacing: it holds an Arabic initial, and the trailing gap knocks it off centre */
    background: linear-gradient(135deg, var(--sidebar), var(--blue));
    box-shadow: var(--sh-lg);
}

.project-logo-preview.large {
    width: 86px;
    height: 86px;
    flex: 0 0 86px;
    font-size: 26px;
}

.project-logo-field small,
.field-hint {
    display: block;
    color: var(--muted);
    margin-top: 6px;
    font-size: 12px;
}

.tenant-logo-field .form-control[type="file"] {
    margin-bottom: 12px;
}

.form-label {
    color: var(--ink);
    font-size: 12px;
    font-weight: 800;
    /* No text-transform / letter-spacing — the text is ARABIC (see .data-table th). */
}

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

.form-grid.one {
    grid-template-columns: 1fr;
}

.form-grid.three {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.form-grid .span-2 {
    grid-column: span 2;
}

.form-grid .span-3 {
    grid-column: span 3;
}

/* One labelled field inside a .form-grid cell. Lives here rather than in a page's scoped file because seven
   screens use it — CSS isolation means a class two pages share cannot live in one page's stylesheet, and a
   page that lacks its own copy renders it unstyled with nothing to warn you. Same trap as .gap-note. */
.field-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.field-group > label {
    font-weight: 700;
    font-size: 13px;
    color: var(--ink);
}

.field-hint {
    color: var(--muted);
    font-size: 11px;
    line-height: 1.6;
}

/* A checkbox styled to match a form control, so it lines up with selects/inputs on the same grid row. */
.check-control {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 50px;
    padding: 0 14px;
    border: 1px solid var(--info-line);
    border-radius: var(--r-lg);
    background-color: var(--panel);
    color: var(--ink);
    font-weight: 600;
    cursor: pointer;
}

.check-control input {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
}

/* Employee modal: two side-by-side panels — master data (wide) | system account (narrow). */
.emp-cols {
    display: flex;
    gap: 24px;
    align-items: flex-start;
}

.emp-col-main {
    flex: 2 1 0;
    min-width: 0;
}

.emp-col-side {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 14px;
    border-inline-start: 1px solid var(--line);
    padding-inline-start: 24px;
}

.emp-col-title {
    font-size: 13px;
    font-weight: 800;
    color: var(--ink);
    margin-bottom: 12px;
}

.emp-col-subtitle {
    font-size: 13px;
    font-weight: 800;
    color: var(--ink);
    border-top: 1px dashed var(--line);
    padding-top: 12px;
    margin-top: 2px;
}

.emp-col-note {
    margin-top: 4px;
    padding: 10px 12px;
    border-radius: var(--r-md);
    background: var(--amber-soft);
    border: 1px solid var(--amber-line);
    color: var(--amber-ink);
    font-size: 12px;
    line-height: 1.55;
}

.filters {
    display: grid;
    grid-template-columns: repeat(5, minmax(140px, 1fr));
    gap: 10px;
    margin-bottom: 14px;
}

.filters-six {
    grid-template-columns: repeat(6, minmax(120px, 1fr));
}

.table {
    margin: 0;
    border-color: var(--info-line);
}

.table th {
    background: var(--info-soft);
    color: var(--info);
    font-size: 12px;
    /* No text-transform / letter-spacing — the text is ARABIC (see .data-table th). */
}

.table td,
.table th {
    vertical-align: middle;
    white-space: nowrap;
    border-color: var(--info-line);
}

.comment-note {
    padding: 12px;
    margin-top: 14px;
    border-inline-end: 4px solid var(--amber);
    border-radius: var(--r-sm);
    color: var(--amber-ink);
    background: var(--amber-soft);
}

/* Keep this a real table-cell (NOT display:flex/inline-flex — that wraps the <td> in an anonymous cell,
   so its border-bottom only spans the buttons, breaking the row's horizontal line). Buttons flow inline. */
.row-actions {
    white-space: nowrap;
}
.row-actions .icon-btn + .icon-btn { margin-inline-start: 6px; }

/* auto-fill, not a fixed 2 — the same grid is used in a narrow modal and a wide panel, and a hardcoded
   column count wastes the width in one and cramps the other. */
.capability-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 10px;
}

.capability-grid label {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 44px;
    padding: 11px 12px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--panel);
    color: var(--ink);
    font-size: 13px;
    font-weight: 750;
}

.capability-grid input {
    width: 16px;
    height: 16px;
    accent-color: var(--blue);
}

/* Grouped feature sections (tenant create + detail toggle screens) */
.feature-group { margin-bottom: 18px; }
.feature-group:last-child { margin-bottom: 0; }

.feature-group-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
    font-weight: 800;
    color: var(--muted);
    margin: 0 2px 10px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--on-brand);
}

.feature-group-title i { color: var(--blue); font-size: 13px; }

.feature-group-count {
    margin-inline-start: auto;
    font-size: 11px;
    font-weight: 700;
    color: var(--blue-2);
    background: var(--soft);
    border: 1px solid var(--info-line);
    border-radius: 999px;
    padding: 1px 9px;
}

.site-modal {
    border: 0;
    border-radius: var(--r-xl);
    overflow: hidden;
    box-shadow: var(--sh-xl);
}

.site-modal .modal-header {
    color: var(--on-brand);
    border: 0;
    padding: 18px 22px;
    background: var(--hero);
}

.site-modal .modal-title {
    font-weight: 700;
}

.site-modal .btn-close {
    filter: invert(1) grayscale(1);
    opacity: .9;
}

.site-modal .modal-body {
    padding: 22px;
    background: var(--soft);
}

.site-modal .modal-footer {
    border-top: 1px solid var(--line);
    padding: 14px 22px;
}

.site-modal .form-control,
.site-modal .form-select {
    font-size: 15px;
}

.modal-kicker {
    display: block;
    color: rgba(255, 255, 255, .75);
    font-size: 11px;
    font-weight: 800;
    /* No text-transform / letter-spacing — the text is ARABIC (see .data-table th). */
    margin-bottom: 3px;
}

.geofence-map {
    position: relative;
    min-height: 210px;
    display: grid;
    place-items: center;
    margin-top: 14px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--soft);
    overflow: hidden;
    text-align: center;
}

.range-circle {
    position: absolute;
    width: 150px;
    height: 150px;
    border: 3px solid color-mix(in srgb, var(--blue) 55%, transparent);
    border-radius: 50%;
    background: color-mix(in srgb, var(--blue) 10%, transparent);
}

.range-circle.small {
    width: 110px;
    height: 110px;
}

.map-pin {
    position: absolute;
    width: 18px;
    height: 18px;
    border-radius: 50% 50% 50% 0;
    background: var(--red);
    transform: rotate(-45deg);
}

.project-location-pin {
    left: 50%;
    top: 42%;
}

.geofence-map strong,
.geofence-map small {
    position: relative;
    z-index: 1;
    display: block;
}

.geofence-map small {
    max-width: 420px;
    margin-top: 72px;
    color: var(--muted);
}

/* `.page-heading` was deleted with its last two consumers (Attendance + Home) — it was the second way
   of drawing the same duplicate page title. If a page ever needs a title again, it gets ONE way. */

.note-text {
    margin: 0;
    color: var(--muted);
}

.blazor-error-boundary {
    padding: 1rem;
    color: white;
    background: #b32121;
}

.validation-message {
    color: var(--red-ink);
}

@media (max-width: 900px) {
    .setup-group-grid,
    .split-grid,
    .filters,
    .filters-six,
    .capability-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    /* Two columns → the step changes. Stated both ways so it overrides the 3-column rule either way. */
    .setup-group-grid > .setup-module:last-child:nth-child(2n) { border-inline-end: none; }
    .setup-group-grid > .setup-module:last-child:nth-child(2n + 1) { border-inline-end: 1px solid var(--line); }

    .emp-cols {
        flex-direction: column;
    }

    .emp-col-side {
        border-inline-start: none;
        padding-inline-start: 0;
        border-top: 1px solid var(--line);
        padding-top: 16px;
    }
}

@media (max-width: 640px) {
    .section-title {
        flex-direction: column;
        align-items: stretch;
    }

    /* flex-start, not stretch — otherwise the pill chip inflates to the full width. */
    .setup-callout {
        flex-direction: column;
        align-items: flex-start;
    }

    .setup-group-grid {
        grid-template-columns: 1fr;
    }

    /* One column: the cell already spans the full width, so a closing edge would double the panel border. */
    .setup-group-grid > .setup-module:last-child { border-inline-end: none; }

    .split-grid,
    .filters,
    .filters-six,
    .capability-grid,
    .form-grid,
    .form-grid.three {
        grid-template-columns: 1fr;
    }

    .form-grid .span-2,
    .form-grid .span-3 {
        grid-column: auto;
    }

    .project-logo-field,
    .panel-header,
    .panel-actions {
        align-items: stretch;
        flex-direction: column;
    }
}

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

/* ── SKELETON — the "this value is on its way" language, for a slot rather than an area.
   The third member of the loading family and the one that was missing: `.loading-state` covers a whole
   page, `.reload-overlay` covers a table being refreshed, and this one holds the EXACT slot the value
   will land in — so nothing jumps when it arrives, and an empty row can no longer be mistaken for a
   real answer. A literal «…» was doing this job in the user panel and read as data, not as waiting.
   GLOBAL on purpose: the first copy of a shared class in a scoped stylesheet is how .gap-note, .brand*,
   .eyebrow, .mday and four others each ended up drifting. Size it per caller with --skel-w. */
/* ⚠️ THREE SHAPES, ONE SWEEP — and the shape is not decoration. `.skeleton-bar` is a PILL sized for a
   line of text; stretched to a chart's height it becomes a giant capsule that looks like a rendering fault
   rather than something loading. A block is for a rectangle (a column, a tile), a circle for the donut. */
.skeleton-bar,
.skeleton-block,
.skeleton-circle {
    display: inline-block;
    background: linear-gradient(90deg, var(--soft) 0%, var(--info-soft) 45%, var(--soft) 90%);
    background-size: 300% 100%;
    animation: skeleton-sweep 1.15s ease-in-out infinite;
}

.skeleton-bar {
    width: var(--skel-w, 56px);
    height: 11px;
    border-radius: 999px;
}

.skeleton-block {
    display: block;
    width: var(--skel-w, 100%);
    border-radius: var(--r-sm);
}

.skeleton-circle {
    width: var(--skel-d, 104px);
    height: var(--skel-d, 104px);
    border-radius: 50%;
}

@keyframes skeleton-sweep {
    from { background-position: 100% 0; }
    to { background-position: -100% 0; }
}

/* Still a visible placeholder when the sweep is off — it just stops moving. */
@media (prefers-reduced-motion: reduce) {
    .skeleton-bar, .skeleton-block, .skeleton-circle { animation: none; }
}

/* Google Places Autocomplete dropdown must sit above the branch modal (z-index 1050). */
.pac-container {
    z-index: 1200 !important;
}

/* ============================================================
   Shared list/table pattern — used by every setup screen.
   Toolbar (search + filters) + sortable headers + grid table.
   ============================================================ */

/* Toolbar: search + filters */
.table-toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}

.toolbar-search {
    position: relative;
    flex: 1 1 280px;
    min-width: 220px;
}

.toolbar-search i {
    position: absolute;
    inset-inline-start: 12px;
    top: 50%;
    transform: translateY(-50%);
    color: var(--muted);
    font-size: 14px;
    pointer-events: none;
}

.toolbar-search .form-control {
    padding-inline-start: 34px;
}

.table-toolbar .form-select {
    flex: 0 0 auto;
    width: auto;
    min-width: 150px;
}

.btn-link-clear {
    background: none;
    border: none;
    color: var(--blue-2);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    padding: 6px 8px;
    border-radius: var(--r-md);
}

.btn-link-clear:hover { background: color-mix(in srgb, var(--blue) 10%, var(--panel)); }

.toolbar-result {
    font-size: 12px;
    color: var(--muted);
    font-weight: 600;
}

/* Sortable column headers */
.th-sort {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    cursor: pointer;
    font: inherit;
    font-weight: 700;
    font-size: 12px;
    /* 🔴 THE HEADER LABEL LIVES HERE, NOT ON `th` — every sortable column renders its Arabic text inside
       this BUTTON, so stripping the uppercase/letter-spacing off `.data-table th` alone left the real
       text still stretched. Check the element that actually holds the text, not the one that looks like it. */
    color: var(--muted);
}

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

.sort-ind {
    font-size: 10px;
    color: var(--blue-2);
    min-width: 8px;
}

.no-match {
    text-align: center;
    color: var(--muted);
    padding: 24px 16px;
    font-size: 13px;
}

/* Pagination */
.table-pager {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    margin-top: 12px;
}

.pager-info {
    font-size: 12px;
    color: var(--muted);
    font-weight: 600;
}

.pager-controls {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-inline-start: auto;
}

.pager-btn {
    min-width: 32px;
    height: 32px;
    padding: 0 8px;
    border-radius: var(--r-md);
    border: 1px solid var(--line);
    background: var(--panel);
    color: var(--ink, var(--ink));
    font-size: 13px;
    font-weight: 700;
    cursor: pointer;
    transition: all .15s;
}

.pager-btn:hover:not(:disabled):not(.active) { background: var(--soft); color: var(--blue-2); }
.pager-btn.active { background: var(--blue); color: var(--on-brand); border-color: var(--blue); }
.pager-btn:disabled { opacity: .45; cursor: default; }

.pager-size {
    flex: 0 0 auto;
    width: auto;
    min-width: 110px;
}

/* Grid table */
.table-wrap {
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    /* clip rounded corners (Y) but let wide tables scroll horizontally instead of clipping */
    overflow-x: auto;
    overflow-y: hidden;
}

/* 🔲 A LIST TABLE RUNS TO THE CARD'S OWN EDGES.
   It used to sit inset inside the panel's padding inside its own bordered box — a frame within a
   frame, two hairlines a few pixels apart saying the same thing. Now the PANEL's border is the
   table's frame: the wrapper cancels the padding and drops its own side border + side radius.

   ⚠️ Scoped with `>` ON PURPOSE. A `.table-wrap` nested deeper is a SUB-table inside its own block
   (a report's per-employee card, a plan's line editor inside a modal) and must stay inset — bleeding
   it would push it past a container that is not the card. */
.panel > .table-wrap,
.panel > .table-area > .table-wrap {
    margin-inline: calc(-1 * var(--panel-pad));
    border-inline: none;
    border-radius: 0;
}

/* The reload scrim has to bleed with the table it covers, or an 18px strip down each side stays
   un-dimmed while the rows behind it are being replaced. */
.panel > .table-area > .reload-overlay {
    inset-inline: calc(-1 * var(--panel-pad));
    border-radius: 0;
}

/* Last thing in the card (no pager under it) ⇒ pick the panel's bottom corners back up, or a square
   corner pokes out past the rounded card. `.table-wrap`'s own `overflow-x: auto` clips the table to
   it. `border-bottom: none` so it doesn't double up against the panel's own border. */
.panel > .table-wrap:last-child,
.panel > .table-area:last-child > .table-wrap:last-child {
    border-bottom: none;
    border-end-start-radius: var(--panel-radius);
    border-end-end-radius: var(--panel-radius);
}

.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
    color: var(--ink);
    font-variant-numeric: tabular-nums;   /* dates / hours / distances / counts align in their columns */
}

/* Flat, not a gradient: a header band is a LABEL strip, and the gradient read as a third surface
   competing with the card and the canvas. `--line-2` (not `--info-line`, which is the semantic BLUE)
   because its own comment names this exact job — the table header underline. */
.data-table thead tr {
    background: var(--soft);
    border-bottom: 1.5px solid var(--line-2);
}

.data-table th {
    padding: 12px 16px;
    font-weight: 700;
    color: var(--ink);
    font-size: 11.5px;
    /* NO text-transform / letter-spacing here — these headers are ARABIC: `uppercase` does nothing
       to it, and `letter-spacing` pulls apart letters that are supposed to JOIN. Same rule already
       applied to `.eyebrow`; this was the other place this file flagged to watch. */
    white-space: nowrap;
}

.data-table td {
    padding: 13px 16px;
    border-bottom: 1px solid var(--line);
    vertical-align: middle;
    transition: background .12s ease;
}

/* Grid: vertical separators between cells match the horizontal rules for a complete grid
   (outer edge comes from .table-wrap) */
.data-table th,
.data-table td {
    border-inline-end: 1px solid var(--line);
}

.data-table th:last-child,
.data-table td:last-child {
    border-inline-end: none;
}

.data-table tbody tr:last-child td { border-bottom: none; }

/* Whisper-quiet zebra for scan-ability; hover wins over it (declared after). */
.data-table tbody tr:nth-child(even) td { background: var(--panel); }
.data-table tbody tr:hover td { background: color-mix(in srgb, var(--blue) 9%, var(--panel)); }

/* A soft brand accent on the row's start edge while hovered — RTL start = the first cell's outer edge. */
/* The accent sits on the row's reading-START edge: −3px in Arabic, +3px in English (--dir-x). */
.data-table tbody tr:hover td:first-child { box-shadow: inset calc(3px * var(--dir-x)) 0 0 0 var(--blue); }

/* Fee-entry table inside a modal: distance-type label + a value input per row */
.fee-grid td .form-control { margin: 0; }
.fee-grid th:last-child,
.fee-grid td:last-child { width: 220px; }

.code-chip {
    display: inline-block;
    padding: 2px 8px;
    border-radius: var(--r-sm);
    background: color-mix(in srgb, var(--blue) 10%, var(--panel));
    color: var(--blue-2);
    font-size: 12px;
    font-weight: 700;
    font-family: "SF Mono", "Fira Code", monospace;
}

/* A mission that arrived as a TICKET — the second axis beside «نوع المهمة». Global, not scoped: the list, the
   mission view and (later) the reports all render it, and a class more than one page uses cannot live in one
   page's stylesheet.

   ONE outlined look for all four types, brand-tinted so it follows the tenant palette — the LABEL is what tells
   them apart, the same call as the department chips. The single exception is «طارئة», which is a real severity
   and earns amber. Deliberately NOT green for «مدفوعة» or red for «طارئة»: green means success/«نشط» here and
   red means absent/failed, and borrowing either would say something the ticket type does not mean. */
.tk-badge {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    margin-inline-start: 6px;
    padding: 2px 9px;
    border: 1px solid color-mix(in srgb, var(--blue) 34%, var(--panel));
    border-radius: 999px;
    background: var(--panel);
    color: var(--brand-strong);
    font-size: 11.5px;
    font-weight: 700;
    white-space: nowrap;
    vertical-align: middle;
}

.tk-badge i { font-size: 11px; opacity: .85; }

.tk-badge.tk-Emergency {
    border-color: color-mix(in srgb, var(--amber) 45%, var(--panel));
    background: color-mix(in srgb, var(--amber) 12%, var(--panel));
    color: var(--amber-ink);
}

/* Clickable entity-name in a list row → opens its page (shared standard-list affordance). */
.link-name {
    border: 0;
    background: none;
    padding: 0;
    font: inherit;
    font-weight: 700;
    color: var(--blue-2);
    cursor: pointer;
    text-align: start;
}

.link-name:hover { text-decoration: underline; }

/* The boolean yes/no mark — see Components/Shared/BoolTick.razor. No background on purpose: a filled
   chip reads as a STATUS, and these columns are a plain yes/no. */
.bool-tick {
    color: var(--ok);
    font-size: 16px;
    line-height: 1;
    font-weight: 700;
}

.bool-tick.off {
    color: var(--muted);   /* recedes — a column that is mostly "no" should read as mostly empty */
    font-size: 14px;
}

.status-badge {
    display: inline-flex;
    align-items: center;
    padding: 3px 10px;
    border-radius: var(--r-md);
    font-size: 12px;
    font-weight: 700;
}

/* THE status palette, defined once. The two platform pages each carried a byte-identical copy in their
   scoped stylesheets — three definitions of «نشط» is three chances for it to drift. */
.status-active   { background: var(--ok-soft); color: var(--ok); }
.status-trial    { background: var(--amber-soft); color: var(--amber-ink); }
.status-suspended { background: var(--red-soft); color: var(--red); }
.status-expired  { background: var(--soft); color: var(--ink); }

/* Row action icon buttons */
.icon-btn {
    display: inline-grid;
    place-items: center;
    width: 30px;
    height: 30px;
    border-radius: var(--r-md);
    border: 1px solid var(--line);
    background: var(--panel);
    color: var(--muted);
    cursor: pointer;
    transition: all .15s;
}

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

/* ============================================================
   Shared screen chrome — tenant bar, modal, toast, confirm,
   buttons, alerts. Used by every setup screen.
   ============================================================ */

.tenant-bar {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 20px;
    padding: 14px 18px;
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    background: var(--panel);
}

.tenant-bar-label {
    font-size: 13px;
    font-weight: 700;
    color: var(--muted);
    white-space: nowrap;
}

/* The topbar's tenant picker. It KEEPS the form-control look on purpose — it is a real <select>, you
   pick a value in it, so the border is the affordance. That is exactly why the user chip beside it
   dropped its own border: one of the two is a control and the other is a menu.
   ⚠️ It used to carry `min-height: 42px`, in a rule whose only job was capping the width — quietly
   overriding `.form-select`'s 46px and making the tenant switcher the ONE control in the app at a
   different height, 4px out of line with the chip next to it. Removed; it inherits 46px now.
   Width moved here from MainLayout.razor.css, where it was dead (see the note in that file). */
.tenant-select {
    width: 240px;
    max-width: 360px;
}

@media (max-width: 980px) {
    .tenant-select { width: 100%; max-width: none; }
}

.empty-hint {
    padding: 48px 0;
    text-align: center;
    color: var(--muted);
    font-size: 15px;
}

.loading-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    color: var(--muted);
    padding: 56px 0;
    font-size: 14px;
    font-weight: 600;
}

/* Designed spinner ring (the inline bi-arrow-repeat icon is hidden in favour of this). */
.loading-state i { display: none; }

.loading-state::before {
    content: "";
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: 3px solid var(--info-line);
    border-top-color: var(--blue);
    animation: shift-spin .7s linear infinite;
}

.spinning { animation: shift-spin .8s linear infinite; }
.muted { color: var(--muted); font-size: 13px; }

/* Primary save button */
.btn-save {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 18px;
    border-radius: var(--r-md);
    border: none;
    background: var(--blue);
    color: var(--on-brand);
    font-weight: 700;
    font-size: 13px;
    cursor: pointer;
    white-space: nowrap;
    transition: background .15s;
}

.btn-save:hover:not(:disabled) { background: var(--blue-2); }
.btn-save:disabled { opacity: .6; cursor: default; }

/* The compact primary action inside a table row («مراجعة» on /leaves and /permissions). Same language as
   .btn-save — a SOLID brand fill — at a smaller size.
   ⚠️ It used to carry the HERO GRADIENT, and that is the lesson: --hero is drawn for a wide banner, so
   across a ~90px button the whole 135° sweep compresses and you see a dark corner rather than a colour.
   A gradient needs room; a button wants a fill.
   GLOBAL, not scoped: it was byte-copied into Permissions.razor.css AND Leaves.razor.css. */
.btn-review {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 6px 12px;
    border: none;
    border-radius: var(--r-md);
    background: var(--blue);
    color: var(--on-brand);
    font-size: 12.5px;
    font-weight: 700;
    cursor: pointer;
    transition: background .15s;
}

.btn-review:hover:not(:disabled) { background: var(--blue-2); }

/* ── MONEY STATUS PILLS — GLOBAL, because two pages draw them now: the /expenses hub and the mission
   view's «الأعمال المرتبطة» tab. They were defined only inside Expenses.razor.css, so the second page
   would have rendered them bare — the same scoped-copy trap this file keeps recording.
   ⚠️ THE TWO FAMILIES ARE NOT INTERCHANGEABLE, and `Approved` is exactly why: on a CLAIM it means the
   company agreed to pay (green, finished), on a CUSTODY REQUEST it means agreed but the cash has NOT
   been handed over (brand — the thing still to be done). Same word, two states; sharing one class
   would tell an employee his money had arrived. */

/* Where each claim stands. Colour carries the stage so a full queue is readable without reading. */
.exp-status {
    display: inline-block;
    font-size: 11.5px;
    font-weight: 700;
    padding: 3px 10px;
    border-radius: 999px;
    white-space: nowrap;
}

.st-PendingTeamLeader,
.st-PendingManager,
.st-PendingDirector {
    background: color-mix(in srgb, var(--amber) 16%, var(--panel));
    color: var(--amber-ink);
    border: 1px solid color-mix(in srgb, var(--amber) 38%, var(--panel));
}

.st-PendingFinance {
    background: color-mix(in srgb, var(--cyan) 15%, var(--panel));
    color: var(--info);
    border: 1px solid color-mix(in srgb, var(--cyan) 34%, var(--panel));
}

.st-Approved {
    background: color-mix(in srgb, var(--ok) 15%, var(--panel));
    color: var(--ok);
    border: 1px solid color-mix(in srgb, var(--ok) 32%, var(--panel));
}

.st-Rejected {
    background: var(--panel);
    color: var(--red-ink);
    border: 1px solid var(--red-line);
}

.st-Cancelled {
    background: var(--soft);
    color: var(--muted);
    border: 1px solid var(--line);
}

/* ── طلبات العهدة ─────────────────────────────────────────────────────────────────────
   Status pills. The colour choice follows the rules this codebase has already settled the
   hard way, rather than picking what looks nice:

   · «بانتظار الموافقة» → AMBER. Waiting on a person to decide.
   · «بانتظار الصرف»   → THE BRAND colour, because it is the thing still to be DONE — the
     same call the mobile made when it painted «لم تسجّل» in the brand instead of green.
     It also follows the tenant, so it is orange here and blue elsewhere.
   · «تم الصرف»        → --ok / --ok-soft, the SEMANTIC success pair. Deliberately NOT
     --green, which is one of the seven keys a tenant may re-brand: a status must mean the
     same thing on every tenant, and «نشط» rendering orange-on-green is exactly the bug that
     pair was introduced to fix.
   · «مرفوض»           → red, the one verdict.
   · «مسحوب»           → neutral. Nothing went wrong; the employee changed his mind. */
.req-pill {
    display: inline-block;
    padding: 3px 10px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 700;
    white-space: nowrap;
}

.req-PendingTeamLeader,
.req-PendingApproval,
.req-PendingDirector {
    background: color-mix(in srgb, var(--amber) 14%, var(--panel));
    color: color-mix(in srgb, var(--amber) 78%, var(--ink));
}

.req-Approved {
    background: color-mix(in srgb, var(--blue) 12%, var(--panel));
    color: color-mix(in srgb, var(--blue) 82%, var(--ink));
}

.req-Disbursed {
    background: var(--ok-soft);
    color: var(--ok);
}

.req-Rejected {
    background: color-mix(in srgb, var(--red) 12%, var(--panel));
    color: color-mix(in srgb, var(--red) 80%, var(--ink));
}

.req-Cancelled {
    background: var(--soft);
    color: var(--muted);
}

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

/* Attendance status pills — GLOBAL, and they had to become global the moment a THIRD page wanted them.
   They were defined in `Attendance.razor.css` AND byte-copied into `MissionView.razor.css` (the copies had
   already drifted apart on padding and font-size), so the project view's attendance tab would have rendered
   them bare — a shared class cannot live in one page's scoped stylesheet. Same call as `.exp-status`
   /`.req-pill` above.
   The key is the neutral `AttendanceStatusResolver` code, so a new state gets a pill by being named here
   once. `Absent` and `Incomplete` share red on purpose: one is the work not happening, the other the record
   being broken — told apart by the LABEL, because a fourth colour on a status strip stops meaning anything. */
.att-badge {
    display: inline-block;
    padding: 3px 11px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 700;
    white-space: nowrap;
}
.att-OnTime { background: var(--ok-soft); color: var(--ok-ink); }
.att-Late { background: var(--amber-soft); color: var(--amber-ink); }
.att-Incomplete { background: var(--red-soft); color: var(--red-ink); }
.att-Absent { background: var(--red-soft); color: var(--red-ink); }
.att-Pending { background: var(--info-soft); color: var(--ink); }
.att-Off { background: var(--info-soft); color: var(--info); }
.att-OnLeave { background: var(--info-soft); color: var(--info); }

/* The two row FLAGS that ride beside a status pill. Both amber, and deliberately the same amber: neither is
   a failure — «اشتغل وهو في إجازة» is a leave to review, «يدوي» is a day somebody typed. Red is reserved for
   a punch that was REFUSED. */
.att-onleave-warn,
.att-manual-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-inline-start: 6px;
    padding: 2px 8px;
    border-radius: var(--r-xl);
    font-size: 11px;
    font-weight: 700;
    white-space: nowrap;
    background: var(--amber-soft);
    color: var(--amber-ink);
    border: 1px solid var(--amber-line);
}

/* Ghost / danger buttons */
.btn-ghost {
    padding: 8px 18px;
    border-radius: var(--r-md);
    border: 1px solid var(--line);
    background: var(--panel);
    color: var(--muted);
    font-weight: 700;
    font-size: 13px;
    cursor: pointer;
}

.btn-ghost:hover:not(:disabled) { background: var(--soft); }

.btn-danger {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 20px;
    border-radius: var(--r-md);
    border: none;
    background: var(--red-strong);
    color: var(--on-brand);
    font-weight: 700;
    font-size: 13px;
    cursor: pointer;
    transition: background .15s;   /* .btn-save beside it has one; without it the two felt different */
}

.btn-danger:hover:not(:disabled) { filter: brightness(.92); }
.btn-danger:disabled { opacity: .6; cursor: default; }

/* Alerts */
.alert-success {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    margin-bottom: 14px;
    border-radius: var(--r-md);
    background: var(--ok-soft);
    color: var(--ok);
    font-size: 13px;
    font-weight: 600;
}

.alert-error-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    margin-bottom: 14px;
    border-radius: var(--r-md);
    background: var(--red-soft);
    color: var(--red);
    font-size: 13px;
    font-weight: 600;
}

.alert-error-bar button {
    margin-inline-start: auto;
    padding: 4px 12px;
    border-radius: var(--r-sm);
    border: 1px solid var(--red);
    background: transparent;
    color: var(--red);
    cursor: pointer;
    font-weight: 700;
    font-size: 12px;
}

/* Required marker + locked select */
.req { color: var(--red); font-weight: 700; }

.form-select:disabled {
    background-color: var(--soft);
    color: var(--ink, var(--ink));
    opacity: 1;
    cursor: not-allowed;
}

.check-row {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 42px;
    cursor: pointer;
    font-size: 14px;
}

.check-row input {
    width: 18px;
    height: 18px;
    cursor: pointer;
}

/* Modal (gradient-header style) */
/* The explicit width/height are NOT redundant with inset:0 — under `body { zoom }` a fixed box sized by
   inset alone renders at 90% and leaves a live, unclickable strip down one side of the screen. */
.modal-backdrop-shift {
    position: fixed;
    inset: 0;
    width: var(--app-w);
    height: var(--app-h);
    background: rgba(15, 23, 42, .45);
    z-index: 1040;
}

.modal-shift {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(960px, calc(96vw / var(--ui-scale)));
    max-height: calc(90vh / var(--ui-scale));
    display: flex;
    flex-direction: column;
    background: var(--panel);
    border-radius: var(--r-xl);
    box-shadow: var(--sh-xl);
    z-index: 1050;
    overflow: hidden;
}

/* Wider variant for dense modals (e.g. the 3-column employee form). */
.modal-shift.wide {
    width: min(1160px, calc(96vw / var(--ui-scale)));
}

.modal-shift-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px;
    border-bottom: 1px solid var(--line);
}

.modal-shift-header h3 { margin: 0; font-size: 16px; font-weight: 800; }

.modal-shift-header.gradient {
    background: var(--hero);
    border-bottom: none;
    padding: 18px 22px;
}

.modal-shift-header.gradient h3 { color: var(--on-brand); }

.icon-btn-light {
    display: inline-grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border-radius: var(--r-md);
    border: 1px solid rgba(255, 255, 255, .35);
    background: rgba(255, 255, 255, .15);
    color: var(--on-brand);
    cursor: pointer;
    transition: background .15s;
}

.icon-btn-light:hover { background: rgba(255, 255, 255, .28); }

.modal-shift-body {
    padding: 18px 20px;
    overflow-y: auto;
}

.modal-shift-footer {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 14px 20px;
    border-top: 1px solid var(--line);
}

/* Confirm dialog */
.confirm-dialog {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: min(420px, calc(92vw / var(--ui-scale)));
    padding: 24px;
    text-align: center;
    background: var(--panel);
    border-radius: var(--r-xl);
    box-shadow: var(--sh-xl);
    z-index: 1110;
}

.confirm-icon {
    display: grid;
    place-items: center;
    width: 52px;
    height: 52px;
    margin: 0 auto 12px;
    border-radius: 50%;
    background: var(--red-soft);
    color: var(--red);
    font-size: 24px;
}

.confirm-dialog h4 { margin: 0 0 6px; font-size: 17px; font-weight: 800; }
.confirm-dialog p { margin: 0 0 18px; font-size: 14px; color: var(--muted); }

.confirm-actions {
    display: flex;
    justify-content: center;
    gap: 10px;
}

/* Toast */
.shift-toast {
    position: fixed;
    top: 20px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 10px;
    max-width: calc(90vw / var(--ui-scale));
    padding: 12px 18px;
    border-radius: var(--r-lg);
    background: var(--red-soft);
    color: var(--red);
    border: 1px solid var(--red);
    box-shadow: var(--sh-lg);
    font-size: 14px;
    font-weight: 700;
    z-index: 1100;
    animation: toast-in .2s ease-out;
}

.shift-toast i { font-size: 18px; }

.toast-close {
    margin-inline-start: 6px;
    border: none;
    background: transparent;
    color: var(--red);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
}

@keyframes toast-in {
    from { opacity: 0; transform: translate(-50%, -10px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
}

/* ===== Switch — an on/off SETTING, not a checkbox =====================================
   GLOBAL on purpose: the Admin has 43 `type="checkbox"` inputs and a class more than one page
   uses cannot live in one page's scoped stylesheet — this codebase has re-learned that eight times.

   A checkbox says «tick this as part of filling in a form»; a switch says «this is on or off right
   now». The policy settings are the second kind, which is why the owner asked for it.

   ⚠️ THE KNOB MOVES BY `inset-inline-start`, NOT `transform: translateX`. A translate has to know
   which way the page reads — it would need +18px in LTR and −18px in RTL — and the Admin runs RTL
   while the same class will be wanted in an English build. The logical property is direction-agnostic
   and animates identically. */
.switch {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    cursor: pointer;
    user-select: none;
}

/* Kept in the DOM and reachable by keyboard; only its default box is hidden. */
.switch > input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    margin: 0;
}

.switch-track {
    position: relative;
    flex: 0 0 auto;
    width: 42px;
    height: 24px;
    border-radius: 999px;
    background: var(--line);
    transition: background .18s ease;
}

.switch-track::after {
    content: "";
    position: absolute;
    top: 3px;
    inset-inline-start: 3px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--panel);
    box-shadow: var(--sh-sm);
    transition: inset-inline-start .18s ease;
}

/* Themed, so it follows the tenant's palette like every other interactive surface. */
.switch > input:checked + .switch-track { background: var(--blue); }
.switch > input:checked + .switch-track::after { inset-inline-start: 21px; }

.switch > input:focus-visible + .switch-track {
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 24%, transparent);
}

.switch > input:disabled + .switch-track { opacity: .5; cursor: not-allowed; }

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

/* ===== Topbar sidebar toggle + collapsible rail (desktop) ===== */
.topbar-lead {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

/* 46px, matching .form-select and the user chip — the three things in the topbar were 42 / 42 / 46. */
.sidebar-toggle {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 46px;
    height: 46px;
    border-radius: var(--r-lg);
    border: 1px solid var(--line);
    background: var(--panel);
    color: var(--ink);
    font-size: 18px;
    cursor: pointer;
    transition: background .15s, border-color .15s, color .15s;
}

.sidebar-toggle:hover {
    background: var(--soft);
    border-color: var(--blue);
    color: var(--blue);
}

/* Collapsed rail: only on desktop — mobile keeps the existing hamburger menu. */
@media (min-width: 981px) {
    html.sidebar-collapsed .app-shell {
        grid-template-columns: var(--rail-w) minmax(0, 1fr);
    }

    html.sidebar-collapsed .sidebar {
        padding: 18px 10px;
    }

    /* hide text, keep icons */
    html.sidebar-collapsed .nav-text,
    html.sidebar-collapsed .nav-section-title,
    html.sidebar-collapsed .brand-name,
    html.sidebar-collapsed .brand-subtitle,
    html.sidebar-collapsed .sidebar-foot .foot-text {
        display: none;
    }

    html.sidebar-collapsed .brand,
    html.sidebar-collapsed .brand-link {
        justify-content: center;
        gap: 0;
    }

    /* center the nav icons in the rail (.nav-item bumps specificity over the scoped base) */
    html.sidebar-collapsed .nav-item .nav-link-btn {
        justify-content: center;
        gap: 0;
        padding-inline: 0;
    }

    /* Same size as the expanded nav — this used to be 17 against 13, so the icons visibly resized on
       every collapse. The rail having needed a bump was the hint that 13 was too small everywhere. */
    html.sidebar-collapsed .nav-item .nav-link-btn i {
        font-size: 18px;
    }
}

@media (max-width: 980px) {
    .sidebar-toggle {
        display: none;
    }
}

/* ===== Projects: long single page with a sticky anchor nav ===== */
.proj-form-top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}

.proj-top-actions,
.proj-bottom-actions {
    display: flex;
    align-items: center;
    gap: 10px;
}

.proj-bottom-actions {
    justify-content: flex-end;
    padding-top: 4px;
}

.proj-layout {
    display: grid;
    grid-template-columns: 220px minmax(0, 1fr);
    gap: 18px;
    align-items: start;
    margin-top: 12px;
}

.proj-anchor-nav {
    position: sticky;
    top: 88px;
    display: grid;
    gap: 4px;
    padding: 12px;
    border: 1px solid var(--line);
    border-radius: var(--r-xl);
    background: var(--panel);
    box-shadow: var(--sh-md);
}

.proj-anchor-nav button {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    padding: 9px 12px;
    border: none;
    background: none;
    border-radius: var(--r-md);
    color: var(--ink);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    text-align: start;
    cursor: pointer;
    transition: background .14s, color .14s;
}

.proj-anchor-nav button:hover {
    background: var(--soft);
    color: var(--blue);
}

.proj-anchor-nav button i {
    color: var(--blue);
    font-size: 15px;
}

.proj-sections {
    display: grid;
    gap: 16px;
    min-width: 0;
}

/* each section is a .panel; clear the sticky topbar when jumped to via anchor */
.proj-section {
    scroll-margin-top: 88px;
    margin-top: 0;
}

.proj-logo-row {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 14px;
}

.proj-logo-stub {
    flex: 0 0 auto;
    width: 56px;
    height: 56px;
    display: grid;
    place-items: center;
    border-radius: var(--r-xl);
    color: var(--on-brand);
    font-weight: 900;
    font-size: 20px;
    background: linear-gradient(135deg, var(--blue), var(--blue-2));
}

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

    .proj-anchor-nav {
        position: static;
        grid-auto-flow: column;
        grid-auto-columns: max-content;
        overflow-x: auto;
    }
}

/* Role multi-select chips (employee modal) + inline empty hint */
.role-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.role-chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 7px 13px; border-radius: 999px; cursor: pointer;
    background: var(--info-soft); color: var(--ink); border: 1px solid transparent;
    font-size: 13px; font-weight: 700; transition: background .12s, color .12s, border-color .12s;
}
.role-chip:hover { background: var(--info-soft); }
.role-chip.on { background: var(--blue-soft); color: var(--brand-strong); border-color: color-mix(in srgb, var(--blue) 30%, transparent); }
.role-chip .chip-inactive { font-size: 11px; font-weight: 700; color: var(--ink-2); }
.empty-inline { color: var(--muted); font-size: 13px; padding: 6px 2px; }

/* ── Print: drop the app chrome so a printed page (or "Save as PDF") carries the CONTENT only.
   Global on purpose — CSS isolation can't reach the layout from a page's scoped stylesheet.
   Pages add `.no-print` to their own toolbars/pagers. ───────────────────────────────────── */
@media print {
    /* Paper stays 1:1 — the report print rules were tuned at full size, and printing already gets the
       full width back once the sidebar is hidden, so there is nothing for the screen scale to solve.
       Resetting the TOKEN (not `body{zoom:1}`) reverts the zoom AND --app-h together — otherwise the
       compensated full-height would stay divided by .9 on unzoomed paper and push out a blank page. */
    :root { --ui-scale: 1; }

    .sidebar,
    .topbar,
    .app-footer,
    .sidebar-toggle,
    #blazor-error-ui { display: none !important; }

    .app-shell { display: block !important; }
    .main { margin: 0 !important; width: 100% !important; }
    .content { padding: 0 !important; overflow: visible !important; }
    .page-wrap { padding: 0 !important; max-width: none !important; }

    .panel {
        box-shadow: none !important;
        border: 1px solid var(--line) !important;
        break-inside: auto;
    }

    .table-wrap { overflow: visible !important; }
    .data-table thead { display: table-header-group; }   /* repeat headers across printed pages */
    .data-table tr { break-inside: avoid; }

    body { background: var(--panel) !important; }
}

/* ── Reports (SHARED by every report page under .report-page) ─────────────────────────────
   Lifted out of the per-page scoped stylesheets on purpose: four report screens are planned and
   CSS isolation would force a copy of all of this into each one. Page-specific bits (status
   colours a page invents, row tints, grouping marks) stay in that page's .razor.css. ────── */
.report-page .report-toolbar { flex-wrap: wrap; align-items: flex-end; gap: 10px 12px; }
.report-page .rt-field { display: flex; flex-direction: column; gap: 4px; min-width: 150px; }
.report-page .rt-field > label { font-size: 11.5px; font-weight: 600; color: var(--muted); }

.report-page .rt-check {
    display: flex; align-items: center; gap: 6px;
    font-size: 13px; color: var(--ink);
    padding-bottom: 11px; white-space: nowrap; cursor: pointer;
}

.report-page .rt-views {
    display: flex; gap: 4px; margin-inline-start: auto;
    background: var(--soft); border: 1px solid var(--line); border-radius: var(--r-md); padding: 3px;
}
.report-page .rt-view {
    border: 0; background: none; border-radius: var(--r-md); padding: 7px 13px;
    font-size: 13px; font-weight: 600; color: var(--muted); cursor: pointer;
    display: inline-flex; align-items: center; gap: 6px;
}
.report-page .rt-view.active {
    background: var(--panel); color: var(--brand-strong); box-shadow: var(--sh-sm);
}

.report-page .report-meta {
    display: flex; flex-wrap: wrap; gap: 8px 20px; align-items: center;
    padding: 10px 18px; border-block: 1px solid var(--line);
    background: color-mix(in srgb, var(--blue) 4%, var(--panel));
    font-size: 12.5px; color: var(--ink);
}
.report-page .report-meta i { color: var(--brand-strong); }
.report-page .report-meta .rm-note { margin-inline-start: auto; color: var(--muted); font-style: italic; }

/* A missing SETUP is a finding, not a legitimate zero — an employee with no leave plan, a department with no
   manager. Amber by default; `.info` for the calmer "here is why this looks empty" variant. */
.report-page .gap-note {
    display: flex; gap: 10px; align-items: flex-start;
    margin: 0 18px 4px; padding: 11px 14px;
    border-radius: var(--r-md); border: 1px solid var(--amber-line); background: var(--amber-soft);
    font-size: 12.5px; line-height: 1.8; color: var(--amber-ink);
}
.report-page .gap-note i { font-size: 15px; margin-top: 2px; }
.report-page .gap-note a { color: var(--amber-ink); font-weight: 700; }

.report-page .gap-note.info {
    border-color: color-mix(in srgb, var(--blue) 28%, var(--panel));
    background: color-mix(in srgb, var(--blue) 6%, var(--panel));
    color: var(--brand-strong);
}
.report-page .gap-note.info a { color: var(--brand-strong); }

/* A grouped block: one employee (attendance report) or one project (project-hours report). */
.report-page .emp-block { border-bottom: 8px solid var(--soft); }
.report-page .emp-block:last-child { border-bottom: 0; }
.report-page .emp-head {
    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px;
    padding: 12px 18px 10px;
    background: color-mix(in srgb, var(--blue) 5%, var(--panel));
    border-inline-start: 3px solid var(--blue);
}
.report-page .emp-id { display: flex; align-items: baseline; gap: 10px; }
.report-page .emp-name { font-weight: 700; font-size: 15px; color: var(--ink); }
.report-page .emp-code { font-size: 12px; color: var(--muted); }
.report-page .emp-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.report-page .etag {
    font-size: 11.5px; font-weight: 600; color: var(--brand-strong);
    background: var(--panel); border: 1px solid var(--line); border-radius: 999px; padding: 2px 10px;
}
.report-page .etag.bad { color: var(--red-ink); border-color: var(--red-line); background: var(--panel); }
.report-page .etag.hours { color: var(--ok-ink); border-color: var(--ok-line); background: var(--ok-soft); }

.report-page .report-table { font-size: 12.5px; }
.report-page .report-table td, .report-page .report-table th { white-space: nowrap; }
.report-page .report-table .mono { font-variant-numeric: tabular-nums; }
.report-page .report-table .cell-bad { color: var(--red-ink); font-weight: 700; }
.report-page .report-table .cell-main { font-weight: 600; }
.report-page .report-table .cell-sub { font-size: 11.5px; color: var(--muted); }
.report-page .report-table tfoot .emp-total td,
.report-page .report-table tfoot .grand-total td {
    background: var(--soft); font-weight: 700; border-top: 2px solid var(--line);
}
.report-page .report-table tfoot .grand-total td { background: color-mix(in srgb, var(--blue) 9%, var(--panel)); }

/* Status pill — the neutral AttendanceStatusResolver codes, so every report reads the same. */
.report-page .st {
    display: inline-block; font-size: 11px; font-weight: 700;
    border-radius: 999px; padding: 2px 9px; border: 1px solid transparent;
}
.report-page .st-OnTime { color: var(--ok-ink); background: var(--ok-soft); border-color: var(--ok-line); }
.report-page .st-Late { color: var(--amber-ink); background: var(--amber-soft); border-color: var(--amber-line); }
.report-page .st-Incomplete { color: var(--amber-ink); background: var(--amber-soft); border-color: var(--amber-line); }
.report-page .st-Absent { color: var(--red-ink); background: var(--red-soft); border-color: var(--red-line); }
.report-page .st-Pending { color: var(--muted); background: var(--soft); border-color: var(--line); }
.report-page .st-Off { color: var(--muted); background: var(--soft); border-color: var(--line); }
.report-page .st-OnLeave { color: var(--info); background: var(--info-soft); border-color: var(--info-line); }

.report-page .pager-size { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--muted); }
.report-page .pager-size .form-select { min-height: 34px; padding-block: 4px; width: auto; }

@media print {
    .report-page { padding: 0; }
    .report-page .report-table { font-size: 10.5px; }
    .report-page .emp-block { break-inside: avoid; border-bottom: 0; }
    .report-page .emp-head { background: var(--panel) !important; border-inline-start-width: 2px; }
    .report-page .report-meta { background: var(--panel) !important; }
    .report-page .gap-note { background: var(--panel) !important; }
    .report-page .st { border-color: var(--line-2) !important; background: var(--panel) !important; }
}

/* ── A printed report is a REPORT, not a screenshot of the page (owner, 2026-10-11) ──────────────────────
   The letterhead (<ReportPrintHeader/>: company, title, period, filters, who printed it and when) and the table,
   nothing else — no KPI cards, no toolbar, no pager. On its own landscape page so every column fits, with the
   page number in the footer. `page: report` names it, so other screens keep printing portrait.
   The numbers are «3 / 12» on purpose: @page content cannot read the UI language. Chrome 131+ draws margin
   boxes; a browser that does not simply prints without numbers. */
.print-header { display: none; }

@page report {
    size: A4 landscape;
    margin: 10mm 10mm 14mm;
    @bottom-center {
        content: counter(page) " / " counter(pages);
        direction: ltr;   /* «1 / 7» either way round; an RTL page would draw it «7 / 1» */
        font-family: Cairo, sans-serif;
        font-size: 9pt;
        color: #7a8496;
    }
}

@media print {
    .no-print { display: none !important; }

    .report-page { page: report; }

    .report-page .stat-cards,
    .report-page .panel-header,
    .report-page .report-toolbar,
    .report-page .report-meta,
    .report-page .table-pager,
    .report-page .reload-overlay,
    .report-page .gap-note,
    .report-page .pending-note,
    .report-page .export-error { display: none !important; }

    .report-page .panel { --panel-pad: 0; border: 0 !important; padding: 0 !important; margin: 0 !important; }
    .report-page .table-wrap { border: 0 !important; margin: 0 !important; overflow: visible !important; }

    .report-page .report-table { width: 100%; table-layout: auto; font-size: 8.5pt !important; }
    .report-page .report-table th,
    .report-page .report-table td { padding: 2px 4px !important; line-height: 1.25; white-space: normal !important; }
    /* The grand total belongs at the END, once — Chrome repeats a <tfoot> on every printed page by default. */
    .report-page .report-table tfoot { display: table-row-group; }
    .report-page .report-table td.mono,
    .report-page .report-table td.ext-code { white-space: nowrap !important; }
    .report-page .report-table thead tr { background: #eef0f3 !important; -webkit-print-color-adjust: exact; print-color-adjust: exact; }

    .print-header {
        display: block;
        margin: 0 0 8px;
        padding-bottom: 8px;
        border-bottom: 1.5px solid #172033;
        color: #172033;
    }
    .print-header .ph-top { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
    .print-header .ph-company { display: flex; align-items: center; gap: 10px; font-size: 13pt; }
    .print-header .ph-company img { max-height: 36px; max-width: 140px; object-fit: contain; }
    .print-header .ph-stamp { display: flex; flex-direction: column; align-items: flex-end; font-size: 8.5pt; color: #4a5568; }
    .print-header .ph-title { font-size: 16pt; font-weight: 800; margin: 6px 0 4px; }
    .print-header .ph-lines { display: flex; flex-wrap: wrap; gap: 2px 18px; font-size: 9pt; }
    .print-header .ph-lines b { font-weight: 700; color: #4a5568; }
    .print-header .ph-note { margin-top: 4px; font-size: 8.5pt; color: #8a5200; }
}

/* An export that fails must say so — it used to be swallowed, leaving a button that just did nothing. */
.report-page .export-error {
    margin: 0 18px 6px;
    padding: 9px 13px;
    border-radius: var(--r-md);
    border: 1px solid var(--red-line);
    background: var(--red-soft);
    color: var(--red-ink);
    font-size: 12.5px;
}

/* Why a setup record can't be deleted — shown inside its own confirm dialog, above the actions. */
.confirm-dialog .delete-blocked {
    margin: 4px 0 14px;
    padding: 10px 13px;
    border-radius: var(--r-md);
    border: 1px solid var(--amber-line);
    background: var(--amber-soft);
    color: var(--amber-ink);
    font-size: 12.5px;
    line-height: 1.85;
    text-align: start;
}

/* No default work policy = attendance is being judged by a fallback nobody picked. Say it loudly. */
.no-default-warn {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    margin: 0 0 14px;
    padding: 12px 15px;
    border-radius: var(--r-lg);
    border: 1px solid var(--amber-line);
    background: var(--amber-soft);
    color: var(--amber-ink);
    font-size: 13px;
    line-height: 1.9;
}
.no-default-warn i { font-size: 16px; margin-top: 3px; }

/* Fields that are stored but not yet consumed — flagged so nobody ticks one expecting an effect. */
.section-label .not-wired {
    font-size: 11px;
    font-weight: 600;
    color: var(--muted);
    background: var(--soft);
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 1px 8px;
    margin-inline-start: 8px;
}
.capability-grid.muted-block { opacity: .72; }

/* ── Shared list chrome ──────────────────────────────────────────────────────────────────
   .table-area / .reload-overlay / .empty-state are used by every list screen, but were only ever defined
   inside a handful of PAGE-SCOPED stylesheets — so a page without its own copy (e.g. /expenses) rendered
   them with no styling at all: no loading overlay, a bare empty state. Blazor scopes a .razor.css to its
   own component, so a class several pages share has to live here. A page that already carries its own copy
   keeps winning on specificity, so nothing that looked right changes. */

/* The positioning context the overlay needs — without it, `inset: 0` resolves against the page. */
.table-area {
    position: relative;
    min-height: 120px;
}

.reload-overlay {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    /* Was a hardcoded rgba(255,255,255,.72) — a WHITE flash over a dark table on every reload. */
    background: color-mix(in srgb, var(--panel) 72%, transparent);
    color: var(--brand-strong);
    font-weight: 700;
    backdrop-filter: blur(1px);
    border-radius: var(--r-lg);
}

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 48px 20px;
    color: var(--muted);
    background: var(--panel);
    border: 1px dashed var(--line);
    border-radius: var(--r-lg);
}

.empty-state i { font-size: 30px; opacity: .55; }
.empty-state p { margin: 0; font-size: 14px; }

/* ─────────────────────────────────────────────────────────────────────────────
   Selection CHIPS — the shared employee/team/vehicle picker language.
   GLOBAL because FOUR pages draw these (MissionForm, TaskForm, ProjectForm, the
   /missions day modal) plus the shared <AssigneePicker>, and a class more than
   one page uses cannot live in one page's scoped stylesheet — the four scoped
   copies had already drifted (a stray margin here, a missing rule there), and a
   shared COMPONENT gets no scope attribute from its parent at all, so it would
   have rendered bare.
   ───────────────────────────────────────────────────────────────────────────── */
.scope-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
}

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

.chip-select {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 6px;
}

.chip-check {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 11px;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--panel);
    cursor: pointer;
    font-size: 13px;
    font-weight: 600;
}

/* THEMED, not a literal. The fill was a hardcoded #eef4ff under a var(--blue)
   border — so on an orange-branded tenant a picked chip drew an orange border
   around a blue tint. Same family as the «نشط» collision. */
.chip-check.on {
    border-color: var(--blue);
    background: var(--blue-soft);
    color: var(--brand-strong);
}

.chip-check input { accent-color: var(--blue); }
.chip-check .muted { color: var(--muted); font-weight: 500; }

/* Department badge on each employee chip — so you can tell who is from where. */
.chip-dept {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-inline-start: 4px;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--soft);
    color: var(--muted);
    font-size: 11.5px;
    font-weight: 700;
}

.chip-dept > i { font-size: 10.5px; }

/* The selected responsible team's members read as "primary". */
.chip-dept.is-primary { background: var(--blue-soft); color: var(--brand-strong); }
.chip-check.primary-team { border-color: color-mix(in srgb, var(--blue) 30%, transparent); }
.chip-check.primary-team.on { border-color: var(--blue); }

/* The "already chosen" group sits in its own tray so it reads apart from the
   candidates. Green here is the semantic --ok pair, not a brand green a tenant
   could repaint: it means "these are settled", the same thing everywhere. */
.chip-select.assigned-group {
    padding: 10px;
    margin-bottom: 10px;
    border: 1px solid color-mix(in srgb, var(--ok) 25%, transparent);
    border-radius: var(--r-lg);
    background: var(--ok-soft);
}

.scope-head .form-label > i { margin-inline-end: 5px; }
.scope-head .form-label .bi-check2-circle { color: var(--ok); }
.scope-head .form-label .bi-plus-circle { color: var(--blue-2); }

/* ═══════════════════════════════════════════════════════════════════════════════════════════════════════
   🌗 DARK MODE (2026-09-18) — per USER, not per tenant.
   The owner's call: localStorage like the sidebar collapse, so there is no Tenant.Theme key, no migration
   and no API, and a manager turns it on for himself without waiting for anybody.

   🔑 IT IS ONE BLOCK BECAUSE OF THE TOKEN PASS THAT PRECEDED IT. 682 literal colours across 35 stylesheets
   were routed through the semantic tokens first; without that this would have to be a second copy of every
   stylesheet, which is a copy that drifts the first time somebody is in a hurry.

   ⚠️ THE BRAND VARS ARE NOT REDEFINED HERE. They belong to the tenant, and a per-tenant DARK palette is the
   natural next step — it injects `html[data-theme="dark"]{--blue:…}`, which lands AFTER this block and wins,
   so that feature is additive and needs no rework here. What IS adjusted is `--brand-strong`: see below.

   Selector: [data-theme="dark"] only. Deliberately NOT `@media (prefers-color-scheme: dark)` — the OS
   preference would flip the console under managers who never asked for it, and the toggle is one click.
   ══════════════════════════════════════════════════════════════════════════════════════════════════════ */
html[data-theme="dark"] {
    /* 🔑 THE CANVAS IS DERIVED FROM THE SIDEBAR, and that is the fix for the first thing the owner saw on a
       real page: «ألوان الـ nav menu هي تقريبا نفس ألوان الخلفية». The sidebar is painted from the TENANT's
       --sidebar/--sidebar-2, while --bg was a fixed literal — so how well the two separated was an accident
       of whichever palette the tenant happened to pick, and on audio-tech's the accident was a bad one.
       Mixing the canvas FROM the sidebar makes the gap structural: the page is always the same distance
       darker, whatever he sets, and it picks up his colour family instead of fighting it.
       ⚠️ In LIGHT mode --bg stays a neutral literal on purpose — a canvas is not identity, and a pale tint
       of a brand is exactly the blue cast that was removed on 2026-08-16. Dark is the opposite case: on a
       dark canvas the tint is what tells two dark surfaces apart. */
    /* EVERY dark surface is mixed from the sidebar against a fixed dark anchor. The anchors set the
       LIGHTNESS STEPS — canvas darkest, card lifted off it, inputs a step further — while the sidebar sets
       the HUE, so the whole console lands in the tenant's colour family without him configuring anything.
       Owner-caught, 2026-09-18: on a neutral-grey sidebar the old literals («#1a202b», blue 17 points above
       red) rendered visibly NAVY cards he never chose, and surfaces are not among the 7 themeable keys, so
       he had no way to correct it. Deriving beats adding seven more pickers: it is right by default.
       On the SHIFT default navy the result is the same navy family as before. */
    --bg: color-mix(in srgb, var(--sidebar) 32%, #06080c);
    --panel: color-mix(in srgb, var(--sidebar) 22%, #14171c);   /* a card lifts off the canvas */
    --panel-2: color-mix(in srgb, var(--sidebar) 22%, #1b1f26);
    --soft: color-mix(in srgb, var(--sidebar) 22%, #1e222a);
    --ink: #e7eaf0;
    --ink-2: #b3bcca;
    --muted: #8b95a5;
    --line: color-mix(in srgb, var(--sidebar) 28%, #262a31);
    --line-2: color-mix(in srgb, var(--sidebar) 28%, #363b44);

    /* 🔴 --brand-strong FLIPS DIRECTION, and this is a real bug rather than polish. In light it is mixed
       DARKER than the brand (68% of --blue-2) so links read on white. On a #11151c canvas that same mix is
       nearly invisible — and on audio-tech, whose brand is #ff5905, brand-coloured links would go dim brown.
       In dark it mixes toward WHITE instead, which is the same rule the RN app already applies to a tenant
       accent (a contrast floor, not a fixed colour). */
    --brand-strong: color-mix(in srgb, var(--blue) 72%, #ffffff);

    /* Status: the INK tones lighten (they are read as text on a dark surface) and the SOFT fills become
       dark washes of the same hue, so a warning still reads as a warning rather than as a white slab. */
    --ok: #46c08d;
    --ok-ink: #5fd3a2;
    --ok-soft: #14291f;
    --ok-line: #28513c;
    --amber: #ffb21e;
    --amber-ink: #f0b458;
    --amber-soft: #2c2415;
    --amber-line: #55452a;
    --red: #e8695f;
    --red-ink: #f08a80;
    /* Brighter and more saturated than the light fill — a dark card absorbs, so the same #c62828 reads
       muddy on it. Still 5.14:1 against the white label. */
    --red-strong: #cf2f26;
    --red-soft: #2e1a1a;
    --red-line: #55302e;
    --info: #7aa7ff;
    --info-soft: #18202f;
    --info-line: #2c3a55;

    /* Shadows carry no weight on a dark canvas — depth comes from the surface steps above. Kept, softened. */
    --sh-sm: 0 1px 2px rgba(0, 0, 0, .35);
    --sh-md: 0 2px 6px rgba(0, 0, 0, .40);
    --sh-lg: 0 6px 16px rgba(0, 0, 0, .45);
    --sh-xl: 0 12px 32px rgba(0, 0, 0, .55);
    --shadow: var(--sh-md);

    /* A bright inset white across the top of every input is a LIGHT-surface convention — on a dark field
       it drew a white hairline. Keep the "lit from above" idea, at a weight the surface can carry. */
    --input-gloss: rgba(255, 255, 255, .045);

    /* 🔴 BOOTSTRAP'S OWN SURFACE VARIABLES, POINTED AT OUR TOKENS — this is the ROOT-CAUSE fix, not a patch.
       Bootstrap paints from --bs-body-bg (#fff) in rules we never override: `.form-control:focus` re-states
       the background, `.dropdown-menu` fills from it, `:disabled` fills from --bs-secondary-bg. Every one of
       those was a WHITE slab on a dark page, and chasing them rule by rule means finding the next one in
       production. Mapping the vars fixes the whole class at the source, and only inside dark mode. */
    --bs-body-bg: var(--panel);
    --bs-body-color: var(--ink);
    --bs-border-color: var(--line-2);
    --bs-secondary-bg: var(--soft);
    --bs-tertiary-bg: var(--panel-2);
    --bs-secondary-color: var(--muted);
    --bs-emphasis-color: var(--ink);
    /* 🔴 --bs-heading-color STAYS `inherit` (Bootstrap's own default) — it is NOT a body-text variable.
       Bootstrap applies it to every h1–h6, so pinning it to --ink made every heading sitting on a BRAND
       surface ignore its container: the project hero's title and the modal header's title rendered pale
       grey-blue on orange instead of white. Headings must follow whatever surface they are on. */
    --bs-heading-color: inherit;

    /* Tells the browser to paint form controls, scrollbars and the canvas dark — without it the native
       widgets stay light and every <select> is a white slab in a dark form. */
    color-scheme: dark;
}

/* ⚠️ PRINT IS ALWAYS LIGHT. A dark page prints as a black rectangle and empties the cartridge; the report
   print rules were tuned on white. Resetting the TOKENS (not a `background: white` override) reverts the
   whole cascade in one place, exactly as the --ui-scale print reset does. */
@media print {
    html[data-theme="dark"] {
        --bg: #fff; --panel: #fff; --panel-2: #fff; --soft: #f4f5f7;
        --ink: #172033; --ink-2: #4a5568; --muted: #7a8496;
        --line: #e2e5ea; --line-2: #d3d7de;
        --ok: #12855c; --ok-ink: #1c6b45; --ok-soft: #e6faf4; --ok-line: #bfe6d1;
        --amber-ink: #8a5200; --amber-soft: #fdf3e0; --amber-line: #f0d9a8;
        --red: #d94f45; --red-ink: #b91c1c; --red-soft: #fdecec; --red-line: #f3c7ca;
        --red-strong: #c62828;
        --info: #1d4ed8; --info-soft: #eef4ff; --info-line: #c7d6ee;
        --brand-strong: color-mix(in srgb, var(--blue-2) 68%, var(--blue));
        --input-gloss: rgba(255, 255, 255, .75);
        --bs-body-bg: #fff; --bs-body-color: #172033; --bs-border-color: #d3d7de;
        --bs-secondary-bg: #f4f5f7; --bs-tertiary-bg: #fff; --bs-secondary-color: #7a8496;
        --bs-emphasis-color: #172033; --bs-heading-color: inherit;
        color-scheme: light;
    }
}

/* The dark-mode toggle — the same 46px box as the sidebar toggle beside it, so the three topbar controls
   line up (they were 42/42/46 once, and the misalignment is what made the topbar look untidy). */
.theme-toggle {
    width: 46px;
    height: 46px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 0;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--ink-2);
    font-size: 18px;
    cursor: pointer;
    transition: background .15s, color .15s;
}
.theme-toggle:hover { background: var(--brand-wash); color: var(--brand-strong); }
/* The icon states what a click DOES, not what is on: a moon while light, a sun while dark. */
html[data-theme="dark"] .theme-toggle .bi-moon-stars::before { content: "\f5a2"; }

/* The placeholder had NO rule of its own, so it inherited Bootstrap's grey — which reads fine on white and
   nearly vanishes on a dark field. `opacity: 1` because Firefox dims placeholders by default on top of the
   colour, which would undo this. */
.form-control::placeholder,
.form-select::placeholder,
input::placeholder,
textarea::placeholder {
    /* ⚠️ Written at .form-control specificity ON PURPOSE: Bootstrap ships `.form-control::placeholder`, which
       is (0,1,1) and beats a bare `::placeholder`. A bare rule looks right in the file and does nothing.
       `opacity: 1` because Firefox dims placeholders on top of the colour, which would undo this. */
    color: var(--muted);
    opacity: 1;
}

/* Bootstrap draws the <select> chevron as a background-image SVG with the stroke colour BAKED IN (#343a40).
   `color-scheme: dark` cannot touch it — it is an image, not a widget part — so a dark arrow sits on a dark
   field and the control looks like it has no dropdown at all. Same SVG, light stroke. */
html[data-theme="dark"] .form-select {
    background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23b3bcca' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

/* ── Blazor's unhandled-error bar ────────────────────────────────────────────────────────────────────
   🔴 GLOBAL, and it has to be. The div is emitted by BOTH MainLayout and AuthLayout, and these rules
   used to live in `MainLayout.razor.css` — a SCOPED stylesheet, so the compiled selector is
   `#blazor-error-ui[b-…]`. AuthLayout has no scoped stylesheet at all, so the copy IT emits carries no
   scope attribute and the `display: none` could never match it. Found on production 2026-09-19: the bar
   was rendering PERMANENTLY at the bottom of the LOGIN page, in English, whether or not anything had
   failed — i.e. on the first screen every employee of the client sees. It is hidden until Blazor's own
   JS sets `display: block` on a real circuit error.
   This is the same trap this file keeps recording: a rule more than one component needs cannot live in
   one component's scoped stylesheet. */
#blazor-error-ui {
    color-scheme: light only;
    display: none;
    position: fixed;
    bottom: 0;
    inset-inline-start: 0;
    z-index: 1000;
    width: 100%;
    padding: .75rem 1.25rem;
    background: var(--amber-soft);
    box-shadow: 0 -1px 2px rgba(0, 0, 0, .2);
}

#blazor-error-ui .dismiss {
    cursor: pointer;
    position: absolute;
    /* ⚠️ inline-END, not start. The rule this replaced said `left: .75rem`, and the Admin is dir="rtl",
       so physical left IS the inline end — a close button belongs at the end of the line. Writing it
       logically as `inset-inline-start` would have silently moved it to the other side. */
    inset-inline-end: .75rem;
    top: .5rem;
}

/* ═══════════════════════════════════════════════════════════════════════════════════════════════════
   لوحة التحكم — the dashboard, its charts and its motion.

   🔑 THESE LIVE GLOBALLY ON PURPOSE. The chart components sit in Components/Shared/ and a shared
   component gets NO scope attribute from the page that uses it — a rule written in Home.razor.css
   would render every chart BARE, with a clean build and a 200. That is the trap this file has now
   recorded seventeen times.

   🎨 THE CHART PALETTE IS VALIDATED, NOT CHOSEN BY EYE. Both sets pass all six checks of the dataviz
   validator (lightness band · chroma floor · CVD separation · normal-vision floor · contrast vs the
   surface), in light AND dark, against the real panel colour:
       light  #12855c → #c98200 → #1d4ed8 → #c62828   (worst adjacent ΔE 9.1 protan / 21.9 normal)
       dark   #1f9e74 → #c08505 → #4b8ef0 → #e2574f   (worst adjacent ΔE 9.0 protan / 18.4 normal)
   ⚠️ The ORDER is the reason they pass: present → late → leave → absent is a severity ordering, and
   it is also what keeps green and red off each other's shoulder in a stacked bar. Re-ordering the
   stack re-opens the CVD check — re-run the validator if you do.

   ⚠️ These are STATUS colours, deliberately NOT the tenant palette: a tenant may repaint --green,
   and «غائب» must mean the same thing on every customer's console. Magnitude charts (top projects,
   mission counts) use the BRAND instead, because there the colour carries no meaning at all.
   ═══════════════════════════════════════════════════════════════════════════════════════════════ */
:root {
    --c-present: #12855c;
    --c-late: #c98200;
    --c-leave: #1d4ed8;
    --c-absent: #c62828;
    --c-none: var(--muted);          /* the de-emphasis grey: a rest day, a gap, "no data" */
    --c-grid: var(--line);
    --chart-gap: 2px;                /* the surface gap between stacked segments — secondary encoding */
}

:root[data-theme="dark"] {
    --c-present: #1f9e74;
    --c-late: #c08505;
    --c-leave: #4b8ef0;
    --c-absent: #e2574f;
}

/* ── Motion ──────────────────────────────────────────────────────────────────────────────────────
   No animation library. A Blazor Server circuit should not pay a round trip to move a pixel, and
   CSS keyframes cost nothing and cannot desync from the render.
   ⚠️ Everything below is switched OFF under prefers-reduced-motion, like .skeleton-bar already is.  */
@keyframes dash-rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
@keyframes dash-grow { from { transform: scaleY(0); } to { transform: scaleY(1); } }
@keyframes dash-wipe { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes dash-draw { to { stroke-dashoffset: var(--dash-to, 0); } }
@keyframes dash-pulse {
    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--c-present) 55%, transparent); }
    70%  { box-shadow: 0 0 0 9px color-mix(in srgb, var(--c-present) 0%, transparent); }
    100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--c-present) 0%, transparent); }
}

/* The staggered entrance. --i is the panel's index, set inline by the page. */
.dash-in {
    opacity: 0;
    animation: dash-rise .5s cubic-bezier(.22, .8, .3, 1) forwards;
    animation-delay: calc(var(--i, 0) * 70ms);
}

/* ── The hero strip ──────────────────────────────────────────────────────────────────────────── */
.dash-hero {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: 14px;
    margin-bottom: 16px;
}

.dash-tile {
    position: relative;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 18px;
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-md);
    overflow: hidden;
}

/* The accent bar follows the TENANT brand — it is decoration, not a status. */
.dash-tile::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    inset-block: 0;
    width: 3px;
    background: var(--blue);
}

.dash-tile.is-live::before { background: var(--c-present); }
.dash-tile.is-warn::before { background: var(--amber); }

.dash-tile-ico {
    font-size: 22px;
    color: var(--muted);
    line-height: 1;
}

.dash-tile.is-live .dash-tile-ico { color: var(--c-present); }
.dash-tile.is-warn .dash-tile-ico { color: var(--amber-ink); }

.dash-tile-body { min-width: 0; }

.dash-tile-value {
    font-size: 28px;
    font-weight: 800;
    color: var(--ink);
    line-height: 1.1;
    font-variant-numeric: tabular-nums;
}

.dash-tile-label {
    font-size: 12px;
    color: var(--muted);
    margin-top: 2px;
}

.dash-tile-sub {
    font-size: 11px;
    color: var(--ink-2);
    margin-top: 4px;
}

/* «على الموقع الآن» — the one number on this page that is about this minute. */
.live-dot {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--c-present);
    margin-inline-end: 6px;
    animation: dash-pulse 2.4s ease-out infinite;
}

/* ── The grid ────────────────────────────────────────────────────────────────────────────────── */
.dash-grid {
    display: grid;
    grid-template-columns: repeat(12, 1fr);
    gap: 16px;
    /* STRETCH, not start. Cards sharing a row have very different natural heights (a seven-person on-site
       list beside a six-bar week strip), and `start` left a dead gap of page canvas under the shorter one —
       which reads as a missing panel rather than as a short one. Stretching fills it with the card's own
       surface, and every card's content is top-aligned inside it anyway. */
    align-items: stretch;
}

.dash-col-8 { grid-column: span 8; }
.dash-col-6 { grid-column: span 6; }
.dash-col-4 { grid-column: span 4; }

@media (max-width: 1100px) {
    .dash-col-8, .dash-col-6, .dash-col-4 { grid-column: span 12; }
}

.dash-card {
    background: var(--panel);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-md);
    padding: 16px 18px;
    display: flex;
    flex-direction: column;
    /* The card fills its stretched grid row; its CONTENT stays at the top rather than spreading out. */
}

.dash-card > :last-child { margin-bottom: 0; }

.dash-card-head {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin-bottom: 14px;
}

.dash-card-head h3 {
    font-size: 15px;
    font-weight: 700;
    color: var(--ink);
    margin: 0;
}

.dash-card-note {
    font-size: 11px;
    color: var(--muted);
}

.dash-card-link {
    margin-inline-start: auto;
    font-size: 12px;
    color: var(--brand-strong);
    text-decoration: none;
    white-space: nowrap;
}

.dash-card-link:hover { text-decoration: underline; }

/* ── Legend — always present for ≥2 series, so identity is never colour alone ─────────────────── */
.chart-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 16px;
    margin-top: 12px;
    font-size: 11px;
    color: var(--ink-2);
}

.chart-legend span { display: inline-flex; align-items: center; gap: 6px; }

.chart-swatch {
    width: 9px;
    height: 9px;
    border-radius: 2px;
    flex: none;
}

/* ── The trend chart (stacked columns, one per day) ───────────────────────────────────────────── */
.trend {
    display: flex;
    align-items: flex-end;
    gap: 3px;
    height: 150px;
    padding-top: 6px;
    /* With few enough columns to hit the cap the row would otherwise hug one edge; centring keeps a short
       window looking deliberate. A full month fills the line, so this changes nothing there. */
    justify-content: center;
    /* Deliberately INHERITS the page's RTL. An Arabic reader reads a time axis right-to-left natively, and
       the week strip below it is a calendar that must flow the same way — forcing this one plot to LTR would
       make the two charts on the same screen disagree about which end is "earlier". */
}

.trend-col {
    flex: 1 1 0;
    min-width: 0;
    /* 🔴 A DAY IS A BAR, NOT A SLAB. Once the window can be one day («اليوم») or seven, a column
       free to take the whole card renders as a coloured block that reads like a broken chart rather than a
       measurement — caught by RENDERING the 1-day case, not by reasoning about it. The cap is above a
       31-day column's natural width (≈ 45px at this card), so a full month is untouched. */
    max-width: 52px;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    gap: var(--chart-gap);
    position: relative;
    cursor: default;
}

.trend-seg {
    border-radius: 2px;
    transform-origin: bottom;
    animation: dash-grow .55s cubic-bezier(.22, .8, .3, 1) backwards;
    animation-delay: calc(var(--i, 0) * 14ms);
}

/* The 4px rounded data-end, anchored to the baseline — only the TOP segment gets it. */
.trend-seg:first-child { border-start-start-radius: 4px; border-start-end-radius: 4px; }

.trend-seg.s-present { background: var(--c-present); }
.trend-seg.s-late    { background: var(--c-late); }
.trend-seg.s-leave   { background: var(--c-leave); }
.trend-seg.s-absent  { background: var(--c-absent); }

/* 🔴 A rest day is not an empty day — it is a day nobody was expected, and the difference matters: an empty
   slot in a run of columns reads as MISSING DATA. It gets a full-height wash (the same convention the
   mission-day chips and the timesheet's .off-row already use) plus a floor line, so a weekend reads as
   "nobody was asked to be here" at a glance. A 2px hairline alone was invisible and was exactly the wrong
   answer — caught by rendering it, not by reading it. */
.trend-col.is-off {
    background: var(--soft);
    border-radius: var(--r-sm);
}

.trend-col.is-off::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 3px;
    border-radius: 3px;
    background: var(--c-none);
    opacity: .3;
}

.trend-col:hover { background: var(--brand-wash); border-radius: var(--r-sm); }

.trend-axis {
    display: flex;
    justify-content: space-between;
    margin-top: 8px;
    font-size: 10px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;   /* oldest sits at the START of the line, i.e. the RIGHT in RTL */
}

/* ── The donut meter ─────────────────────────────────────────────────────────────────────────── */
.donut-wrap { display: flex; align-items: center; gap: 18px; }

.donut { flex: none; position: relative; }

.donut svg { display: block; transform: rotate(-90deg); }

.donut-track { fill: none; stroke: var(--soft); }

.donut-value {
    fill: none;
    stroke-linecap: round;
    animation: dash-draw 1s cubic-bezier(.22, .8, .3, 1) forwards;
}

.donut-centre {
    position: absolute;
    inset: 0;
    display: grid;
    place-content: center;
    text-align: center;
}

.donut-centre b {
    font-size: 24px;
    font-weight: 800;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

.donut-centre small { font-size: 10px; color: var(--muted); display: block; }

/* ── The bar list (magnitude — ONE hue, the brand, because the colour carries nothing here) ───── */
.barlist { display: flex; flex-direction: column; gap: 10px; }

.barlist-row { display: grid; grid-template-columns: 1fr auto; gap: 4px 10px; align-items: baseline; }

.barlist-label {
    font-size: 12px;
    color: var(--ink-2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.barlist-value {
    font-size: 12px;
    font-weight: 700;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

.barlist-track {
    grid-column: 1 / -1;
    height: 8px;
    border-radius: 999px;
    background: var(--soft);
    overflow: hidden;
}

.barlist-fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--blue);
    transform-origin: inline-start;
    animation: dash-wipe .7s cubic-bezier(.22, .8, .3, 1) backwards;
    animation-delay: calc(var(--i, 0) * 60ms);
}

.barlist-fill.is-present { background: var(--c-present); }
.barlist-fill.is-late    { background: var(--c-late); }
.barlist-fill.is-leave   { background: var(--c-leave); }
.barlist-fill.is-absent  { background: var(--c-absent); }
.barlist-fill.is-muted   { background: var(--c-none); }

/* ── The week strip (a mission's run, clamped to this week) ───────────────────────────────────── */
.weekstrip { display: flex; flex-direction: column; gap: 10px; }

.weekstrip-head {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 4px;
    font-size: 10px;
    color: var(--muted);
    text-align: center;
}

.weekstrip-head span.is-today { color: var(--brand-strong); font-weight: 700; }

.weekstrip-row { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }

.weekstrip-bar {
    grid-row: 1;
    height: 30px;
    border-radius: var(--r-sm);
    display: flex;
    align-items: center;
    padding: 0 8px;
    font-size: 11px;
    font-weight: 600;
    color: var(--on-brand);
    background: var(--blue);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    transform-origin: inline-start;
    animation: dash-wipe .6s cubic-bezier(.22, .8, .3, 1) backwards;
    animation-delay: calc(var(--i, 0) * 60ms);
}

/* The mission's EFFECTIVE status, not its stored one. Brand = running (the thing being done today);
   --ok = finished; --muted = not started yet; --red-strong = called off. */
.weekstrip-bar.st-Completed { background: var(--ok); }
.weekstrip-bar.st-Scheduled { background: var(--c-none); }
.weekstrip-bar.st-Pending   { background: var(--amber-ink); }
.weekstrip-bar.st-Cancelled { background: var(--red-strong); }

.weekstrip-today {
    grid-row: 1 / -1;
    border-inline-start: 2px dashed color-mix(in srgb, var(--blue) 45%, transparent);
    pointer-events: none;
}

/* ── Queues, on-site list, findings ──────────────────────────────────────────────────────────── */
.dash-list { display: flex; flex-direction: column; gap: 2px; }

.dash-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 10px;
    border-radius: var(--r-md);
    font-size: 12.5px;
    color: var(--ink);
}

.dash-row:hover { background: var(--brand-wash); }

.dash-row-main { min-width: 0; flex: 1; }

.dash-row-main b { font-weight: 600; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.dash-row-main small { font-size: 11px; color: var(--muted); }

.dash-row-value { font-weight: 800; font-variant-numeric: tabular-nums; color: var(--ink); }

.dash-row-age { font-size: 10.5px; color: var(--amber-ink); white-space: nowrap; }

.dash-row-age.is-stale { color: var(--red-ink); font-weight: 700; }

.dash-row-ico { font-size: 15px; color: var(--muted); flex: none; }

.dash-row-ico.is-bad { color: var(--c-absent); }

.dash-row-ico.is-warn { color: var(--amber-ink); }

.dash-row-ico.is-ok { color: var(--c-present); }

/* ── Empty / denied — «there is none» and «you may not see it» are DIFFERENT facts ────────────── */
.dash-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 24px 12px;
    text-align: center;
    color: var(--muted);
    font-size: 12.5px;
}

.dash-empty i { font-size: 24px; opacity: .55; }

.dash-empty b { color: var(--ink-2); font-weight: 600; }

.dash-scope-note {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 14px;
    margin-bottom: 14px;
    border-radius: var(--r-md);
    background: var(--info-soft);
    border: 1px solid var(--info-line);
    color: var(--info);
    font-size: 12.5px;
}

.dash-window {
    font-size: 11.5px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* ── The period filter ───────────────────────────────────────────────────────────────
   🔴 GLOBAL, like every other rule on this page: the chart components are SHARED, so they carry no scope
   attribute from the page using them and a scoped stylesheet would style nothing. Same file, same reason. */
/* The tenant switcher's «could not reach the server» affordance — it replaces the loading spinner, so it
   sits in the same slot and is the same size. */
.tenant-retry {
    border: 0;
    background: transparent;
    color: var(--amber-ink);
    cursor: pointer;
    padding: 0 4px;
    font-size: 15px;
    line-height: 1;
}

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

.dash-presets {
    display: inline-flex;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    overflow: hidden;
    background: var(--panel);
}

.dash-preset {
    border: 0;
    background: transparent;
    padding: 7px 14px;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ink-2);
    cursor: pointer;
    transition: background .15s, color .15s;
}

.dash-preset + .dash-preset { border-inline-start: 1px solid var(--line); }

.dash-preset:hover { background: var(--soft); }

/* The tenant's brand, never a literal — a selected filter is an interaction, and on an orange-branded
   tenant a blue pill is the «نشط» collision this file keeps recording. */
.dash-preset.on {
    background: color-mix(in srgb, var(--blue) 12%, transparent);
    color: var(--brand-strong);
}

.dash-stepper {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    background: var(--panel);
    padding: 2px;
}

.dash-step {
    border: 0;
    background: transparent;
    color: var(--ink-2);
    width: 28px;
    height: 28px;
    border-radius: var(--r-sm);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
}

.dash-step:hover:not(:disabled) { background: var(--soft); }

.dash-step:disabled { opacity: .35; cursor: default; }

.dash-period-label {
    min-width: 108px;
    text-align: center;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}

.dash-today {
    border: 1px solid var(--line);
    background: var(--panel);
    border-radius: var(--r-md);
    padding: 6px 12px;
    font-size: 12px;
    color: var(--ink-2);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.dash-today:hover { background: var(--soft); }

.dash-custom {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.dash-custom label { font-size: 12px; color: var(--muted); margin: 0; }

.dash-custom .form-control {
    width: auto;
    min-height: 34px;
    padding: 4px 10px;
    font-size: 12.5px;
}

.dash-custom .btn-save { padding: 6px 16px; font-size: 12.5px; }

.dash-window { margin-inline-start: auto; }

.dash-range-error {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 14px;
    margin-bottom: 14px;
    border-radius: var(--r-md);
    background: var(--amber-soft);
    border: 1px solid color-mix(in srgb, var(--amber) 38%, transparent);
    color: var(--amber-ink);
    font-size: 12.5px;
}

/* ── The loading skeleton ────────────────────────────────────────────────────────────
   It mirrors the SHAPE of what is coming — a column chart, a donut, two lists — so the wait reads as the
   page arriving rather than as two grey slabs. Heights are a FIXED pattern, never random: a re-render
   would otherwise make the placeholder twitch. */
.skel-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 16px;
}

.skel-trend {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: 3px;
    height: 150px;
}

.skel-trend .skeleton-block {
    flex: 1 1 0;
    min-width: 0;
    max-width: 52px;   /* the same cap the real columns take */
}

.skel-donut {
    display: flex;
    align-items: center;
    gap: 20px;
    height: 150px;
}

.skel-lines {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.skel-rows {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.skel-row {
    display: flex;
    align-items: center;
    gap: 10px;
}

@media (prefers-reduced-motion: reduce) {
    .dash-in, .trend-seg, .barlist-fill, .weekstrip-bar, .donut-value {
        animation: none !important;
        opacity: 1;
        transform: none;
        stroke-dashoffset: var(--dash-to, 0) !important;
    }
    .live-dot { animation: none; }
}

@media print {
    .dash-in, .trend-seg, .barlist-fill, .weekstrip-bar { animation: none !important; opacity: 1; transform: none; }
    /* The filter is a control, not information — but the window it chose IS, so `.dash-window` stays. */
    .dash-presets, .dash-stepper, .dash-today, .dash-custom { display: none; }
}

/* ===== English (LTR) — directional ICONS =====
   The markup was drawn for Arabic, so a «next» chevron points LEFT and a «back» arrow points RIGHT. Mirroring
   every horizontal chevron/arrow in LTR keeps each one's MEANING rather than its pixels — the standard RTL↔LTR
   rule, and the reason no page has to carry two icon names. Vertical ones (down/up) are untouched. */
[dir="ltr"] .bi-chevron-left::before,
[dir="ltr"] .bi-chevron-right::before,
[dir="ltr"] .bi-arrow-left::before,
[dir="ltr"] .bi-arrow-right::before,
[dir="ltr"] .bi-arrow-left-short::before,
[dir="ltr"] .bi-arrow-right-short::before,
[dir="ltr"] .bi-arrow-left-circle::before,
[dir="ltr"] .bi-arrow-right-circle::before,
[dir="ltr"] .bi-caret-left-fill::before,
[dir="ltr"] .bi-caret-right-fill::before,
[dir="ltr"] .bi-box-arrow-in-left::before,
[dir="ltr"] .bi-box-arrow-in-right::before,
[dir="ltr"] .bi-box-arrow-right::before,
[dir="ltr"] .bi-box-arrow-up-left::before {
    display: inline-block;
    transform: scaleX(-1);
}

/* ===== The language switch (topbar + login) =====
   The same borderless 46px box as the dark-mode toggle beside it. It shows the language a click SWITCHES
   TO, written in that language («English» / «عربي») — the one label a reader of the other language can find. */
.lang-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    height: 46px;
    padding: 0 12px;
    border: 0;
    border-radius: var(--r-md);
    background: transparent;
    color: var(--ink-2);
    font-size: 13px;
    font-weight: 700;
    text-decoration: none;
    transition: background .15s, color .15s;
}
.lang-toggle i { font-size: 17px; }
.lang-toggle:hover { background: var(--brand-wash); color: var(--brand-strong); }

/* On the login screen the switch sits in the form side's top corner (the reading END, so it never competes
   with the heading for the first glance). .auth-main is the positioning context. */
.auth-main { position: relative; }
.lang-toggle.auth-lang { position: absolute; top: 18px; inset-inline-end: 18px; }

/* First login: the sign-out beside the password change — the only other way out while the change is owed. */
.auth-alt-action { margin-top: 12px; display: flex; justify-content: center; }
.auth-alt-action .btn-ghost { display: inline-flex; align-items: center; gap: 6px; }

/* A project with no located site cannot take a mission (2026-09-30). Global: the project view and the mission
   form both draw it. */
.no-location-note {
    display: flex; gap: 10px; align-items: flex-start;
    margin: 12px 0 4px; padding: 11px 14px;
    border-radius: var(--r-md); border: 1px solid var(--amber-line); background: var(--amber-soft);
    font-size: 13px; line-height: 1.8; color: var(--amber-ink);
}
.no-location-note i { font-size: 15px; margin-top: 2px; }
.no-location-note a { color: var(--amber-ink); font-weight: 700; margin-inline-start: 6px; }

/* ── Column picker («الأعمدة») — show/hide a list's columns, per user. Global so any list can use it. ── */
.col-picker { position: relative; flex: 0 0 auto; }
.col-picker-btn {
    display: inline-flex; align-items: center; gap: 6px;
    min-height: 46px; padding: 0 14px;
    background: var(--panel); color: var(--ink-2);
    border: 1px solid var(--line-2); border-radius: var(--r-md);
    font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.col-picker-btn:hover, .col-picker-btn[aria-expanded="true"] {
    border-color: color-mix(in srgb, var(--blue) 45%, var(--line-2));
    color: var(--brand-strong);
}
.col-picker-count {
    font-size: 11px; font-weight: 700; padding: 2px 7px; border-radius: 999px;
    background: var(--blue-soft); color: var(--brand-strong);
}
.col-picker-scrim { position: fixed; inset: 0; width: var(--app-w); height: var(--app-h); z-index: 40; }
.col-picker-panel {
    position: absolute; top: calc(100% + 6px); inset-inline-end: 0; z-index: 41;
    min-width: 230px; padding: 8px;
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-lg);
    box-shadow: var(--sh-lg);
}
.col-picker-head { font-size: 12px; font-weight: 700; color: var(--muted); padding: 4px 8px 8px; }
.col-picker-row {
    display: flex; align-items: center; gap: 10px;
    padding: 7px 8px; border-radius: var(--r-sm);
    font-size: 13px; color: var(--ink); cursor: pointer;
}
.col-picker-row:hover { background: var(--soft); }
.col-picker-row input { width: 16px; height: 16px; accent-color: var(--blue); }
.col-picker-reset {
    width: 100%; margin-top: 6px; padding: 8px;
    background: none; border: none; border-top: 1px solid var(--line);
    color: var(--brand-strong); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.col-picker-reset:disabled { color: var(--muted); cursor: default; }
.ext-code { font-variant-numeric: tabular-nums; color: var(--ink-2); white-space: nowrap; }
@media print { .col-picker { display: none; } }

.report-page .gap-note ul.unknown-gov { margin: 4px 0 0; padding-inline-start: 18px; }
