﻿/* Marjan Web — shared design system (Bootstrap 5.3 RTL + Cairo + FA 6.7).
   Brand tokens follow the VB6 color mapping (green #0b8a5c) so the migrated
   ERP keeps its identity while gaining a modern surface language. */

/* ---------------------------------------------------------------------------
   1. Tokens
   --------------------------------------------------------------------------- */
:root {
    --mj-green: #0b8a5c;
    --mj-green-dark: #076b47;
    --mj-green-soft: #e3f4ed;
    --mj-amber: #f0ad4e;
    --mj-red: #d8000c;
    --mj-teal: #26a69a;
    --mj-ink: #1e272e;
    --mj-muted: #6b7a86;
    --mj-surface: #f4f6f9;
    --mj-card: #ffffff;
    --mj-border: #e4e9ee;
    --mj-radius: 14px;
    --mj-radius-sm: 10px;
    --mj-shadow: 0 1px 2px rgba(16, 40, 30, .06), 0 2px 8px rgba(16, 40, 30, .05);
    --mj-shadow-hover: 0 10px 28px rgba(11, 138, 92, .16), 0 3px 10px rgba(16, 40, 30, .08);
}

/* ---------------------------------------------------------------------------
   2. Base
   --------------------------------------------------------------------------- */
body {
    font-family: 'Cairo', 'Segoe UI', sans-serif;
    background:
        radial-gradient(1100px 500px at 85% -10%, rgba(11, 138, 92, .08), transparent 60%),
        radial-gradient(900px 420px at 10% 110%, rgba(38, 166, 154, .07), transparent 55%),
        var(--mj-surface);
    color: var(--mj-ink);
    overflow-x: hidden;
}

::selection { background: rgba(11, 138, 92, .22); }

/* Slim brand scrollbars everywhere */
* { scrollbar-width: thin; scrollbar-color: #b9c6c0 transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-thumb { background: #c3d0c9; border-radius: 8px; }
*::-webkit-scrollbar-thumb:hover { background: var(--mj-green); }
*::-webkit-scrollbar-track { background: transparent; }

:focus-visible {
    outline: 3px solid rgba(11, 138, 92, .35);
    outline-offset: 2px;
    border-radius: 6px;
}

/* ---------------------------------------------------------------------------
   3. Cards & surfaces
   --------------------------------------------------------------------------- */
.card, .modal-content {
    border: 1px solid var(--mj-border);
    border-radius: var(--mj-radius);
    box-shadow: var(--mj-shadow);
}

.card-soft {
    background: var(--mj-card);
    border: 1px solid var(--mj-border);
    border-radius: var(--mj-radius);
    box-shadow: var(--mj-shadow);
}

.section-title {
    font-weight: 800;
    color: var(--mj-ink);
    display: flex;
    align-items: center;
    gap: .5rem;
}
.section-title i { color: var(--mj-green); }

/* ---------------------------------------------------------------------------
   4. Buttons
   --------------------------------------------------------------------------- */
.btn { border-radius: 10px; font-weight: 700; }

.btn-brand, .btn-success {
    --bs-btn-color: #fff;
    --bs-btn-hover-color: #fff;
    --bs-btn-active-color: #fff;
    --bs-btn-bg: var(--mj-green);
    --bs-btn-border-color: var(--mj-green);
    --bs-btn-hover-bg: var(--mj-green-dark);
    --bs-btn-hover-border-color: var(--mj-green-dark);
    --bs-btn-active-bg: var(--mj-green-dark);
    --bs-btn-active-border-color: var(--mj-green-dark);
    --bs-btn-disabled-bg: var(--mj-green);
    --bs-btn-disabled-border-color: var(--mj-green);
}

.btn-outline-brand, .btn-outline-success {
    --bs-btn-color: var(--mj-green);
    --bs-btn-border-color: rgba(11, 138, 92, .55);
    --bs-btn-hover-bg: var(--mj-green);
    --bs-btn-hover-border-color: var(--mj-green);
    --bs-btn-hover-color: #fff;
    --bs-btn-active-bg: var(--mj-green-dark);
    --bs-btn-active-color: #fff;
    --bs-btn-active-border-color: var(--mj-green-dark);
}

.btn:focus-visible {
    box-shadow: 0 0 0 3px rgba(11, 138, 92, .22);
    border-color: var(--mj-green);
}

/* ---------------------------------------------------------------------------
   5. Tables
   --------------------------------------------------------------------------- */
.table-theme { --bs-table-bg: transparent; }

.table-theme thead th {
    background: #f6f9f7;
    color: #4c5a54;
    font-weight: 800;
    font-size: .8rem;
    border-bottom: 2px solid #dde7e1;
    white-space: nowrap;
}

.table-theme tbody td { vertical-align: middle; }

.table-theme tbody tr {
    transition: background-color .12s ease;
}

.table-theme tbody tr:hover { background-color: #f2f8f5; }

/* ---------------------------------------------------------------------------
   6. Badges & pills
   --------------------------------------------------------------------------- */
/* Extra-small caption text (billing dock labels, grid footers) —
   Bootstrap ships fs-6 as the smallest font-size utility. */
.fs-xsmall { font-size: .6875rem; }

.badge-soft-green { background: var(--mj-green-soft); color: var(--mj-green-dark); font-weight: 700; }
.badge-soft-red { background: #fdeaea; color: var(--mj-red); font-weight: 700; }
.badge-soft-amber { background: #fdf3e0; color: #9c6b13; font-weight: 700; }

.col-monospace {
    font-family: "Consolas", "Courier New", monospace;
    font-size: .85rem;
    letter-spacing: .015em;
    background: #f1f5f9;
    border-radius: .25rem;
    padding: .2rem .45rem;
}
.badge-soft-teal { background: #e2f4f2; color: #176b62; font-weight: 700; }

/* ---------------------------------------------------------------------------
   7. Topbar / ticker / footer chrome
   --------------------------------------------------------------------------- */
.topbar {
    background: rgba(255, 255, 255, .88);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-bottom: 1px solid var(--mj-border);
    position: sticky;
    top: 0;
    z-index: 1030;
}

/* Gradient page header (feature pages: inventory, AI, batch update…)
   Owner directive 2026-09-28: Marjan blue #2167C5 to match the example bar. */
.header-banner {
    background: linear-gradient(90deg, #2167C5, #1a52a3);
    color: #fff;
    box-shadow: 0 2px 12px rgba(33, 103, 197, .25);
}

/* Active-page chip: translucent white pill next to the header title */
.mj-active-chip {
    display: inline-flex;
    align-items: center;
    background: rgba(255, 255, 255, .18);
    border: 1px solid rgba(255, 255, 255, .35);
    border-radius: 50rem;
    font-size: .72rem;
    font-weight: 700;
    padding: .1rem .6rem;
    margin-inline-start: .5rem;
    white-space: nowrap;
}

/* Connected-database badge (session-resolved, like the dashboard topbar) */
.mj-db-badge {
    display: inline-flex;
    align-items: center;
    background: rgba(255, 255, 255, .14);
    border: 1px solid rgba(255, 255, 255, .3);
    border-radius: 10px;
    font-size: .72rem;
    font-weight: 700;
    padding: .25rem .55rem;
    white-space: nowrap;
}

/* Breathing room between the trailing header slot's siblings (user menu ↔
   DB badge). The badge sits flush against the username text otherwise —
   مدير النظام reads as glued to Marjan_template (owner fix 2026-09-23). */
.header-banner .dropdown {
    margin-inline-start: .75rem;
}

/* The assistant door in the shared header (owner directive 2026-10-12).
   It wears the same 44px light-bordered square language as the header's
   other controls (the close X, the home button's gap-2 sibling) and carries
   the ONE brand SVG instead of a glyph. ONE source, because the control now
   lives in the SHARED partial — the inventory page that used to own its copy
   (.sub-toolbar-bar .inv-ai-btn) no longer declares any rule for it. */
.header-banner #btn-ai-assistant {
    width: 44px;
    height: 44px;
    flex: 0 0 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid #e2e8f0;
    border-radius: 10px;
    background: #fff;
    transition: border-color .15s ease, background-color .15s ease;
}
.header-banner #btn-ai-assistant img {
    width: 26px;
    height: 26px;
    display: block;
}
.header-banner #btn-ai-assistant:hover {
    border-color: #2167C5;
    background: #eff6ff;
}
.header-banner #btn-ai-assistant:focus-visible {
    outline: 3px solid #2167C5;
    outline-offset: 2px;
}

@media (max-width: 575.98px) {
    .mj-db-badge { display: none; }
}

.topbar .brand-title {
    background: linear-gradient(120deg, var(--mj-green), var(--mj-teal));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    font-weight: 900;
}

.topbar .icon-btn {
    width: 38px;
    height: 38px;
    border-radius: 12px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--mj-muted);
    transition: background-color .15s ease, color .15s ease;
}
.topbar .icon-btn:hover { background: var(--mj-green-soft); color: var(--mj-green-dark); }

.ticker-bar {
    background: linear-gradient(90deg, #e9a63c, var(--mj-amber), #e9a63c);
    background-size: 200% 100%;
    animation: mj-ticker-sheen 9s linear infinite;
    color: #fff;
    padding: 8px 15px;
    font-weight: 600;
    box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .06);
}
@keyframes mj-ticker-sheen {
    0% { background-position: 0% 0; }
    100% { background-position: 200% 0; }
}

.ticker-link { color: #fff; text-decoration: underline; font-size: .9rem; }

.footer-bar {
    background: linear-gradient(90deg, #c40008, var(--mj-red), #c40008);
    color: #fff;
    text-align: center;
    font-weight: 800;
    padding: 10px;
    letter-spacing: 5px;
    box-shadow: 0 -4px 16px rgba(216, 0, 12, .25);
}

/* ---------------------------------------------------------------------------
   8. Dashboard tiles (VB6 color mapping, modernized)
   --------------------------------------------------------------------------- */
.dashboard-tile {
    border: none;
    border-radius: var(--mj-radius);
    color: #fff;
    text-align: center;
    cursor: pointer;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 20px 10px;
    text-decoration: none;
    position: relative;
    overflow: hidden;
    box-shadow: var(--mj-shadow);
    transition: transform .18s ease, box-shadow .18s ease, filter .18s ease;
}
.dashboard-tile::after {
    /* soft radial sheen for depth */
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(360px 180px at 50% -30%, rgba(255, 255, 255, .28), transparent 65%);
    pointer-events: none;
}
.dashboard-tile:hover {
    transform: translateY(-4px);
    box-shadow: var(--mj-shadow-hover);
    color: #fff;
    filter: saturate(1.06);
}
.dashboard-tile i {
    font-size: 2.6rem;
    margin-bottom: 12px;
    text-shadow: 0 3px 10px rgba(0, 0, 0, .18);
}
.dashboard-tile h5 { font-weight: 800; margin: 0; font-size: 1.25rem; text-shadow: 0 1px 6px rgba(0, 0, 0, .16); }

.tile-products { background: linear-gradient(150deg, #0ea26c, var(--mj-green) 55%, #08684a); }
.tile-sales { background: linear-gradient(150deg, #12d96a, #00c851 55%, #067a37); }
.tile-purchases { background: linear-gradient(150deg, #ff6b6b, #ff5252 55%, #c73030); }
.tile-returns { background: linear-gradient(150deg, #a3b4ff, #8c9eff 55%, #5c6bd6); }
.tile-customers { background: linear-gradient(150deg, #37bfb2, #26a69a 55%, #17766d); }
.tile-suppliers { background: linear-gradient(150deg, #ff77a9, #f06292 55%, #c23f6e); }
.tile-employees { background: linear-gradient(150deg, #63dce9, #4dd0e1 55%, #2b97a7); }
.tile-accounts { background: linear-gradient(150deg, #5ede4a, #4cd137 55%, #2f9423); }
.tile-journals { background: linear-gradient(150deg, #93c9ff, #74b9ff 55%, #4a8fd0); }
.tile-service { background: linear-gradient(150deg, #b7c349, #a4b036 55%, #79831f); }
.tile-market { background: linear-gradient(150deg, #b1d8fb, #90caf9 55%, #6aa7dc); color: var(--mj-ink); }
.tile-ai { background: linear-gradient(150deg, #8f6bff, #6a48e0 55%, #46309e); }

.tile-products-badge, .tile-service-badge {
    border-radius: 12px;
    font-weight: 700;
    font-size: .95rem;
    box-shadow: var(--mj-shadow);
}
.tile-products-badge { background: #ffb6b9; color: var(--mj-red); }
.tile-service-badge { background: #b9f6ca; color: #006064; }

.tall-tile { min-height: 300px; }
.med-tile { min-height: 120px; }
.short-tile { min-height: 145px; }

/* Metro tile strip — uniform height in the 6-column grid */
.metro-tile {
    min-height: 100px;
    padding: 16px 10px;
}
.metro-tile i {
    font-size: 2rem;
    margin-bottom: 8px;
}
.metro-tile h5 {
    font-size: 1rem;
    margin: 0;
}

/* ---------------------------------------------------------------------------
   9. Error / session-expiry pages
   --------------------------------------------------------------------------- */
.error-shell {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}

.error-card {
    width: 100%;
    max-width: 400px;
    background: var(--mj-card);
    border: 1px solid var(--mj-border);
    border-radius: 18px;
    box-shadow: 0 18px 50px rgba(16, 40, 30, .12);
    padding: 28px 28px 26px;
    text-align: center;
    animation: mj-card-in .35s ease-out;
}

.error-card .error-art {
    width: 116px;
    height: 116px;
    margin: 0 auto 10px;
}

.error-card .error-title {
    font-size: 1.05rem;
    font-weight: 800;
    color: #8f1d22;
    margin: 0 0 6px;
}

.error-card .error-text {
    font-size: .85rem;
    line-height: 1.8;
    color: var(--mj-muted);
    margin: 0;
}

.error-card .btn-brand { min-width: 190px; }

/* Amber variant (session expiry / CSRF) */
.error-card.error-card-warn .error-title { color: #9c6b13; }

@keyframes mj-card-in {
    from { opacity: 0; transform: translateY(14px) scale(.985); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .error-card { animation: none; }
}

/* ---------------------------------------------------------------------------
   10. Auth pages (welcome code / login)
   --------------------------------------------------------------------------- */
.auth-shell {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
}
.auth-card {
    width: 100%;
    max-width: 460px;
    background: rgba(255, 255, 255, .92);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: 1px solid var(--mj-border);
    border-radius: 18px;
    box-shadow: 0 18px 50px rgba(16, 40, 30, .16);
    overflow: hidden;
}
.auth-card .auth-head {
    background: linear-gradient(135deg, var(--mj-green), var(--mj-teal));
    color: #fff;
    padding: 28px 24px 22px;
    text-align: center;
}
.auth-card .auth-head i { font-size: 2.2rem; opacity: .92; }
.auth-card .auth-head h1 { font-size: 1.45rem; font-weight: 800; margin: 10px 0 4px; }
.auth-card .auth-body { padding: 26px 26px 30px; }
.auth-card .form-control, .auth-card .form-select {
    border-radius: 12px;
    padding: .65rem .9rem;
    border-color: #d7dfe4;
}
.auth-card .form-control:focus, .auth-card .form-select:focus {
    border-color: var(--mj-green);
    box-shadow: 0 0 0 .22rem rgba(11, 138, 92, .18);
}
.auth-brand-note { color: var(--mj-muted); font-size: .85rem; }

/* ---------------------------------------------------------------------------
   11. AI chat
   --------------------------------------------------------------------------- */
.ai-messages { scroll-behavior: smooth; }

.ai-bubble {
    max-width: 78%;
    padding: .7rem 1rem;
    border-radius: 16px;
    font-size: .95rem;
    line-height: 1.7;
    box-shadow: var(--mj-shadow);
    white-space: pre-wrap;
    word-break: break-word;
}
/* Tail corners: in RTL, "start" is the RIGHT edge and "end" is the LEFT
   edge, so the user bubble (right side) gets a square top-right corner and
   the assistant bubble (left side) a square top-left one. */
.ai-bubble-user {
    background: linear-gradient(135deg, var(--mj-green), #0d9d69);
    color: #fff;
    border-start-start-radius: 4px;
}
.ai-bubble-bot {
    background: #fff;
    border: 1px solid var(--mj-border);
    border-start-end-radius: 4px;
}
.ai-bubble-error {
    background: #fdeaea;
    border: 1px solid #f3c1c1;
    color: #8f1d22;
}

/* ---------------------------------------------------------------------------
   12. Misc shared bits
   --------------------------------------------------------------------------- */
.skeleton {
    background: linear-gradient(90deg, #eef1f4 25%, #f7f9fa 37%, #eef1f4 63%);
    background-size: 400% 100%;
    animation: mj-shimmer 1.3s ease infinite;
    border-radius: 8px;
}
@keyframes mj-shimmer {
    0% { background-position: 100% 0; }
    100% { background-position: 0 0; }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
    }
}

/* ---------------------------------------------------------------------------
   13. Shared dialogs (mjShowNotice / mjConfirm) — templates/partials/mj_dialogs.html
   The ONLY in-page dialog mechanism: never alert()/confirm() (AGENTS.md rule).
   --------------------------------------------------------------------------- */
.mj-dialog-modal .modal-content {
    border-radius: 16px;
    border: 1px solid var(--mj-border);
    box-shadow: 0 18px 50px rgba(16, 40, 30, .18);
}

.mj-dialog-modal .mj-notice-icon {
    width: 64px;
    height: 64px;
    border-radius: 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.6rem;
    color: #fff;
    background: linear-gradient(135deg, var(--mj-green), var(--mj-teal));
    box-shadow: 0 8px 20px rgba(11, 138, 92, .30);
}

.mj-dialog-modal .mj-notice-icon.mj-notice-warn {
    background: linear-gradient(135deg, #e53935, #fc6161);
    box-shadow: 0 8px 20px rgba(229, 57, 53, .30);
}

/* Quick Education / Tips & Tricks (#quickEducationModal) — mjShowEducation()
   in static/js/mj-education.js populates the generic areas dynamically.
   ONE global education modal for the whole app (owner directive 2026-09-21). */
.mj-education-modal .modal-dialog {
    max-width: 26rem;
}

.mj-education-image {
    width: 100%;
    max-width: 320px;
    height: auto;
    border-radius: 12px;
    border: 1px solid var(--mj-border);
    margin: 0 auto 0.75rem;
    display: block;
    background: #f8fafc;
}

/* ONE placement for the education image input (id mj-image-input) so the
   admin "modify" form and any enumeration pass share a single selector. */
.mj-education-extra {
    font-size: 0.92rem;
    color: #475569;
    background: #f8fafc;
    border: 1px solid var(--mj-border);
    border-radius: 10px;
    padding: 0.6rem 0.9rem;
    margin-bottom: 0.75rem;
}

/* The education tip dialog reuses the shared close box (static/css/mj-modal-close.css)
   instead of re-declaring the whole recipe here. The button's class now owns the box;
   this file only pins it. Do NOT float this button: the shared recipe pins absolutely
   on purpose, so the X cannot bias the centred title.
   Owner directive 2026-09-28: the physical left is the reading start, so the X belongs
   on that side, not the reading end. */
.mj-education-modal .btn-close {
    position: absolute;
    top: .55rem;
    inset-inline-end: .75rem;
}

/* ---------------------------------------------------------------------------
   Customers grid — shared navy/blue shell tokens (also works when the module
   is swapped into the dashboard workspace by HTMX).
   --------------------------------------------------------------------------- */
#clients-grid .tabulator-header {
    background: #142e55 !important;
    color: #fff !important;
    border: 0 !important;
    border-radius: 8px 8px 0 0;
    font-weight: 800;
}
#clients-grid .tabulator-col {
    background: #142e55 !important;
    color: #fff !important;
    border-left: 1px solid rgba(255,255,255,.18) !important;
}
#clients-grid .tabulator-col-title {
    padding: .72rem .55rem;
    font-size: .78rem;
    text-align: center;
    /* white-space is owned by the shared "headers never truncate" rule
       (theme.css) — a per-grid nowrap here would re-hide a caption. */
}
#clients-grid .tabulator-col-sorter { color: #9fc7f2; }
#clients-grid .tabulator-row { background: #fff; border-bottom: 1px solid #e5edf5; }
#clients-grid .tabulator-row-even { background: #f8fbfe; }
/* NO HOVER PAINT (owner report 2026-10-06: «the mouse selection on grid moves
   with mouse movement»; AGENTS.md universal rule: row selection changes only
   on CLICK, never as the pointer passes). A rule
   `#clients-grid .tabulator-row:hover { background:#edf5ff !important }` lived
   here, and its ID + !important outranked every neutralizer the page carried —
   so an arbitrary row lit up under the pointer, and hovering the SELECTED row
   repainted it away from its own bar. The grid now has NO row hover rule at
   all: a row keeps its resting paint until a click makes it current. */
#clients-grid .tabulator-placeholder { background: #fff !important; }
#clients-grid .tabulator-placeholder span {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: .55rem;
    padding: 5rem 1.25rem;
    color: #527392 !important;
    font-family: 'Cairo', sans-serif;
    font-size: 1rem;
    font-weight: 700;
}
#clients-grid .tabulator-placeholder span::before {
    content: '\\f1ad';
    font-family: 'Font Awesome 6 Free';
    font-weight: 900;
    width: 48px;
    height: 48px;
    display: grid;
    place-items: center;
    border-radius: 14px;
    background: #e7f0fb;
    color: #2167c5;
    font-size: 1.35rem;
}
#clients-grid { border: 1px solid #dce8f3; border-radius: 9px; overflow: hidden; }

/* ===========================================================================
   Login Page — Clean CRM style (light theme, blue accents).
   Matches the reference design: light background, white card, simple inputs.
   =========================================================================== */
/* Page backdrop: light gray. */
body.login-page {
    min-height: 100vh;
    background: linear-gradient(160deg, #F3F7FB 0%, #EEF4FA 100%);
    font-family: 'Cairo', 'Segoe UI', Tahoma, sans-serif;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    margin: 0;
    position: relative;
    overflow: hidden;
}

body.login-page::before,
body.login-page::after {
    content: '';
    position: fixed;
    border-radius: 50%;
    opacity: .3;
    pointer-events: none;
    z-index: 0;
}
body.login-page::before {
    width: 360px; height: 360px;
    top: -100px; left: -80px;
    background: radial-gradient(circle at center, rgba(20, 46, 85, .14), transparent 70%);
}
body.login-page::after {
    width: 280px; height: 280px;
    bottom: -70px; right: -70px;
    background: radial-gradient(circle at center, rgba(52, 123, 201, .1), transparent 70%);
}

/* Container */
.login-container {
    width: 100%;
    max-width: 528px;
}

/* Card */
.login-card {
    background-color: #ffffff;
    border-radius: 12px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.04);
    padding: 32px 28px;
    border: 1px solid #e5e7eb;
}

/* Brand header */
.brand-header {
    text-align: center;
    margin-bottom: 24px;
}

.brand-logo {
    width: 88px;
    height: auto;
    margin-bottom: 10px;
}

.brand-logo-fallback {
    display: none;
    color: #2167C5;
    font-size: 3rem;
    margin-bottom: 10px;
}

.brand-title {
    font-size: 1.5rem;
    color: #142E55;
    font-weight: 700;
    margin: 0;
}

.brand-title span {
    color: #212529;
}

.brand-subtitle {
    font-size: 0.78rem;
    color: #6c757d;
    letter-spacing: 0.5px;
    margin-top: 4px;
    margin-bottom: 0;
}

/* Alerts */
.alert {
    padding: 10px 14px;
    border-radius: 8px;
    font-size: 0.85rem;
    margin-bottom: 12px;
    display: flex;
    align-items: center;
    gap: 8px;
}

.alert-success {
    background-color: #e6f4ea;
    border: 1px solid #b7e1cd;
    color: #137333;
}

.alert-danger {
    background-color: #fce8e6;
    border: 1px solid #f5c2c7;
    color: #c5221f;
}

/* Form groups */
.form-group {
    margin-bottom: 16px;
}

.form-label {
    display: block;
    font-size: 0.8rem;
    font-weight: 600;
    color: #6c757d;
    margin-bottom: 6px;
}

.input-wrapper {
    position: relative;
    display: flex;
    align-items: center;
}

/* Form controls — LOGICAL padding, not physical: the icon slot is on the
   INLINE-START side (right in RTL, left in LTR), so a physical
   `padding: 10px 38px` left the username text flush against the browser
   edge and unbalanced against the eye icon on the other side
   (2026-09-21). padding-inline-start reserves the icon slot;
   padding-inline-end keeps clear of the password eye. */
.login-form-control,
.login-form-select,
.form-control {
    width: 100%;
    padding-block: 10px;
    padding-inline-start: 38px;
    padding-inline-end: 38px;
    border: 1px solid #d1d5db;
    border-radius: 8px;
    font-size: 0.9rem;
    color: #212529;
    background-color: #ffffff;
    outline: none;
    transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}

.login-form-control:focus,
.login-form-select:focus,
.form-control:focus {
    border-color: #2167C5;
    background-color: #eff6ff;
    box-shadow: 0 0 0 3px rgba(33, 103, 197, 0.15);
}

.login-form-control:-webkit-autofill,
.login-form-control:-webkit-autofill:focus {
    -webkit-box-shadow: 0 0 0 30px #fff inset;
    -webkit-text-fill-color: #212529;
}

/* Input icons — anchored by LOGICAL inset properties, so the start icon
   tracks the reading edge (right in RTL) and the end icon (the password
   eye) tracks the opposite edge in both directions. */
.input-icon-start {
    position: absolute;
    inset-inline-start: 12px;
    color: #2167C5;
    font-size: 1rem;
    pointer-events: none;
}

.input-icon-end {
    position: absolute;
    inset-inline-end: 12px;
    color: #6c757d;
    cursor: pointer;
    background: transparent;
    border: none;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.input-icon-end:hover {
    color: #2167C5;
}

/* Submit button */
.btn-submit {
    width: 100%;
    padding: 11px;
    background-color: #2167C5;
    color: #ffffff;
    border: none;
    border-radius: 8px;
    font-size: 0.95rem;
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.2s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-top: 20px;
}

.btn-submit:hover {
    background-color: #347BC9;
    color: #ffffff;
}

.btn-submit:disabled,
.btn-submit[aria-disabled="true"] {
    opacity: 0.65;
    cursor: not-allowed;
}

/* Footer text */
.footer-text {
    text-align: center;
    font-size: 0.75rem;
    color: #142E55;
    margin-top: 20px;
}

/* Footer link */
.footer-link {
    text-align: center;
    font-size: 0.8rem;
    margin-top: 12px;
}

.footer-link a {
    color: #2167C5;
    text-decoration: none;
    font-weight: 600;
}

.footer-link a:hover {
    text-decoration: underline;
}

/* Lost key link (legacy class kept for compatibility) */
.login-lost-key {
    text-align: center;
    font-size: 0.8rem;
    margin-top: 12px;
    color: #6c757d;
}

.login-lost-key .login-footer-link {
    color: #2167C5;
    text-decoration: none;
    font-weight: 600;
}

.login-lost-key .login-footer-link:hover {
    text-decoration: underline;
}

/* Shield line */
.login-shield-line {
    color: #6c757d;
}

/* Select box padding */
.login-form-select,
#id_db {
    padding-inline-start: 2.3rem;
}

/* ==========================================================================
   Login page — Database picker, FLAT borderless list (2026-09-21)
   Owner directive: the login card is already the container, so a bordered grid
   inside it read as a legacy box-in-a-box. The list is now borderless with
   soft horizontal dividers only, a muted low-weight header, and the SAME
   44×44px ghost controls AGENTS.md requires ("Touch targets ≥ 44×44px") —
   shrinking them to btn-sm/btn-link would break that rule.
   ========================================================================== */
.db-classic-section {
    margin-bottom: 1rem;
    margin-top: 1.5rem;
}

.db-classic-header {
    margin-bottom: 6px;
}

.db-classic-title {
    font-weight: 700;
    font-size: .85rem;
    color: #212529;
}

/* FLAT: no outer box — the parent card supplies the boundary. */
.db-classic-table-wrap {
    border: none;
    border-radius: 0;
    background: transparent;
}

/* The table uses Bootstrap's .table-borderless + .table-hover + .align-middle:
   no vertical gridlines, no cell boxes — only the soft dividers below. */
.db-classic-table-wrap .table {
    margin-bottom: 0;
    font-size: .82rem;
}

/* Header: low visual weight (muted, small, semibold) so the DATA leads; a
   single hairline separates it from the list. */
.db-classic-table-wrap .table thead th {
    background: transparent;
    color: #6b7280;
    font-weight: 600;
    font-size: .72rem;
    border: none;
    border-bottom: 1px solid #e5e7eb;
    padding: .4rem .5rem;
    white-space: nowrap;
    text-align: center;
}

.db-classic-table-wrap .table tbody td {
    vertical-align: middle;
    padding: .35rem .5rem;
    border: none;
    /* The ONLY rule in the list: a soft horizontal divider per row. */
    border-bottom: 1px solid #eef0f2;
    text-align: center;
}

.db-classic-table-wrap .table tbody tr:last-child td {
    border-bottom: none;
}

.db-classic-table-wrap .table tbody tr {
    transition: background-color .08s ease;
}

.db-classic-table-wrap .table tbody tr:hover {
    background-color: #f8fafc;
}

/* Flat list: the selected row is a soft tint, not a boxed cell. The rounded
   ends are keyed on CLASSES (never :first-child/:last-child — positional
   selectors are what silently resized a touch target when the column order
   changed). */
.db-classic-table-wrap .table tbody tr .td-select {
    border-start-start-radius: 6px;
    border-end-start-radius: 6px;
}

.db-classic-table-wrap .table tbody tr .td-actions {
    border-start-end-radius: 6px;
    border-end-end-radius: 6px;
}

/* DISPLAY-ONLY row — a database this device has not authenticated. The class
   no longer SHIPS from the template (every listed row is selectable), but the
   legacy styling stays for tampered/stale markup, exactly as the guard in
   tests/Security.Tests.ps1 requires. */
.db-classic-table-wrap .table tbody tr.db-row-display-only .td-desc,
.db-classic-table-wrap .table tbody tr.db-row-display-only .td-db {
    color: #94a3b8;
}

.db-classic-table-wrap .table tbody tr.db-row-display-only .db-row-check {
    opacity: .5;
    cursor: not-allowed;
}

.db-classic-table-wrap .table tbody tr.db-row-display-only .td-select {
    cursor: not-allowed;
}

/* WHOLE ROW is the selection target (owner directive 2026-09-21: "when i
   click around the radio button it clicks"). The radio's own 16px box inside
   a 40px cell is a punishing target, so the row answers the click instead —
   the pointer promises it. */
.db-classic-table-wrap .table tbody tr.db-classic-row {
    cursor: pointer;
}

/* …except the editable description: that cell keeps the text caret, because a
   click there must place the cursor, not re-select the row. */
.db-classic-table-wrap .table tbody tr.db-classic-row .td-desc[contenteditable="true"] {
    cursor: text;
}

/* The radio's own 16px box is a punishing target, so the WHOLE first column is
   a native label (templates/login.html): clicking anywhere in it checks the
   radio with NO JavaScript involved, which is what still works when a cached
   or blocked script has not loaded (owner directive 2026-09-21: "when i click
   on the server table first column where the radio button is, it should be
   selected"). The script extends the target to the whole row; this one is the
   floor, not the ceiling. */
.db-classic-table-wrap .table .td-select-hit {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: .75rem 0;
    margin: 0;
    cursor: pointer;
}

/* KEYBOARD focus lives on the ROW, not on the radio alone (owner directive
   2026-09-21): the ring follows the focused radio, so walking the list with
   the arrow keys highlights whole rows instead of a 16px dot. Keyed on
   :focus-visible (via :has) — NOT :focus-within — so a MOUSE click on a row
   never paints a keyboard ring; same reasoning as the global :focus-visible
   ring above. It matches the row's own green so the two read as one system. */
.db-classic-table-wrap .table tbody tr.db-classic-row:has(.db-row-check:focus-visible) {
    outline: 3px solid rgba(11, 138, 92, .35);
    outline-offset: -3px;
}

/* Fallback where :has() is unavailable: a row ring on any focus, keyboard or
   otherwise, is still far better than none at all. */
@supports not selector(:has(*)) {
    .db-classic-table-wrap .table tbody tr.db-classic-row:focus-within {
        outline: 3px solid rgba(11, 138, 92, .35);
        outline-offset: -3px;
    }
}

/* Active row — Bootstrap's table-primary-subtle provides the highlight */
.db-classic-table-wrap .table tbody tr.table-primary-subtle {
    background-color: #d6e4f0 !important;
}

/* FIVE columns, RTL reading order (owner directive 2026-09-21):
     1. .td-select  — the radio ONLY (selection)
     2. .td-num     — م: the numeric row index (never the radio)
     3. .td-desc    — وصف السيرفر
     4. .td-db      — قاعدة البيانات
     5. .td-actions — إجراءات, far left (rare destroy action sits outside the
                      reading path; AGENTS.md "Common actions are closer, rare
                      actions are farther")
   The two fixed-width columns are addressed by CLASS, not by :first-child /
   :last-child position, so adding a column cannot silently resize a touch
   target again. */
.db-classic-table-wrap .table .td-select,
.db-classic-table-wrap .table .th-select {
    text-align: center;
    padding-left: .25rem;
    padding-right: .25rem;
    width: 40px;
}

.db-classic-table-wrap .table .td-num,
.db-classic-table-wrap .table .th-num {
    text-align: center;
    padding-left: .25rem;
    padding-right: .25rem;
    width: 40px;
    color: #6b7280;
    font-variant-numeric: tabular-nums;
}

/* إجراءات — sized for two 44×44px touch targets + gap. */
.db-classic-table-wrap .table .td-actions,
.db-classic-table-wrap .table .th-actions {
    text-align: center;
    padding-left: .25rem;
    padding-right: .25rem;
    width: 110px;
}

.db-classic-table-wrap .table .td-desc,
.db-classic-table-wrap .table .td-db {
    text-align: right;
    padding-right: .5rem;
}

.db-classic-table-wrap .table .td-db {
    font-weight: 600;
    color: #212529;
    direction: ltr;
    text-align: left;
}

/* STRAY-DASH ARTIFACT FIX (2026-09-21): a <td> is a table cell —
   `display: flex` took it out of the table layout, so its borders collapsed
   and the browser painted a stray dash-like fragment past the row edge. The
   cell stays a cell; only its INNER wrapper is a flex row (the template's
   .d-flex div handles the icon layout). */
.db-classic-table-wrap .table .td-actions {
    padding-left: .25rem;
    padding-right: .25rem;
}

/* Action buttons — ghost icons, 44×44px touch targets */
.db-classic-table-wrap .table .btn-action-ghost {
    width: 44px;
    height: 44px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 6px;
    font-size: 1rem;
    background: transparent;
    border: none;
    color: #64748b;
    transition: all 0.15s ease-in-out;
}

.db-classic-table-wrap .table .btn-action-ghost:hover {
    background-color: #e0e7ff;
    color: #2563eb !important;
}

.db-classic-table-wrap .table .btn-action-ghost.hover-danger:hover {
    background-color: #fee2e2;
    color: #dc2626 !important;
}

.db-classic-table-wrap .table .btn-action-ghost:focus {
    outline: none;
    box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.25);
}

.db-classic-table-wrap .table .btn-action-ghost.hover-danger:focus {
    box-shadow: 0 0 0 3px rgba(220, 38, 38, 0.25);
}

/* Inline add-by-name entry */
.db-add-row {
    display: none;
    gap: .3rem;
    padding: 8px 0 0;
    align-items: center;
}

.db-add-row.open { display: flex; }

/* The fixed 'marjan_' affix (owner directive 2026-09-21): greyed, visually
   fused to the input so it reads as one field — the user types only the
   distinctive part. Hidden from assistive tech (the real label names the
   field) and locked against user selection, so a triple-click copy grabs
   only what the user typed. */
.db-add-row .db-add-prefix {
    color: #98a2b3;
    font-size: .82rem;
    direction: ltr;
    user-select: none;
    padding: 0 .1rem 0 .35rem;
    white-space: nowrap;
}

.db-add-row .db-add-input {
    border-radius: 6px;
    border: 1px solid #d0d5dd;
    direction: ltr;
    text-align: left;
    font-size: .82rem;
    padding: .35rem .6rem;
    flex: 1;
    min-width: 0;
}

.db-add-row .db-add-input:focus {
    border-color: #2167C5;
    box-shadow: 0 0 0 2px rgba(13, 110, 253, 0.15);
    outline: none;
}

.db-add-row .db-add-confirm {
    width: 26px;
    height: 26px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
    font-size: .7rem;
    background: #2167C5;
    border-color: #2167C5;
    color: #fff;
}

.db-add-row .db-add-confirm:hover { background: #347BC9; border-color: #347BC9; }

.db-add-row .db-add-cancel {
    width: 26px;
    height: 26px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
    font-size: .7rem;
    border-color: #d0d5dd;
    color: #6b7280;
}

.db-add-row .db-add-cancel:hover { background: #f3f4f6; }

.db-add-error {
    display: none;
    padding: .3rem 0 0;
    font-size: .75rem;
    color: #c5221f;
}

/* ---------------------------------------------------------------------------
   Add-database SUCCESS state.

   Adding a database is the last thing a user does before signing in, and the
   row that appears is also the one they must not get wrong. So the success is
   choreographed in three beats instead of announced by a modal:
     1. the confirm button morphs to a spinner, then back to a tick;
     2. the new row slides in with a green wash and a drawn tick that fades;
     3. an inline status line reports the CANONICAL registry spelling.

   The wash is applied to the cells, not the row: `.active` owns the row
   background with !important, and a cell background paints above it, so the
   new — and immediately active — row still animates.
   --------------------------------------------------------------------------- */
.db-add-status {
    display: none;
    align-items: center;
    gap: .35rem;
    padding: .4rem 0 0;
    font-size: .75rem;
    font-weight: 600;
    color: var(--mj-green-dark);
}

.db-add-status.show {
    display: flex;
    animation: mj-db-status-in .28s ease-out both;
}

.db-add-status i {
    color: var(--mj-green);
    font-size: .8rem;
}

/* "Just unlocked" note under the picker (welcome-code arrival cue): always
   visible when the server rendered it — the row above it is highlighted. */
.db-new-note {
    display: flex;
    align-items: center;
    gap: .35rem;
    margin-top: .5rem;
    padding: .45rem .6rem;
    border-radius: 6px;
    background: #ecfdf5;
    color: var(--mj-green-dark);
    font-size: .75rem;
    font-weight: 600;
    animation: mj-db-status-in .28s ease-out both;
}

.db-new-note i {
    color: var(--mj-green);
    font-size: .8rem;
}

/* Busy: the controls stay legible rather than dimming to "broken". */
.db-add-row.is-busy .db-add-input { pointer-events: none; }

.db-add-row .db-add-confirm:disabled {
    opacity: 1;
    background: var(--mj-green);
    border-color: var(--mj-green);
}

@keyframes mj-db-status-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

@keyframes mj-db-cell-in {
    0%   { background-color: #d6f2e6; }
    55%  { background-color: #d6f2e6; }
    100% { background-color: transparent; }
}

@keyframes mj-db-tick-in {
    0%   { opacity: 0; transform: rotate(40deg) scale(.4); }
    25%  { opacity: 1; transform: rotate(40deg) scale(1); }
    70%  { opacity: 1; transform: rotate(40deg) scale(1); }
    100% { opacity: 0; transform: rotate(40deg) scale(1); }
}

/* DEAD-SELECTOR FIX (2026-09-21): these rules were keyed on `.db-classic-table`,
   a class NO element carries — the wrapper is `.db-classic-table-wrap`, so the
   whole arrival cue silently did nothing. Retargeted to the real wrapper; it now
   also serves the "newly unlocked database" cue after a welcome-code referral. */
.db-classic-table-wrap .table tbody tr.db-row-new > td {
    animation: mj-db-cell-in 1.5s ease-out both;
}

/* Drawn tick (no icon font dependency): a rotated L, fading out after the
   wash so the row settles into its normal resting look. */
.db-classic-table-wrap .table tbody tr.db-row-new .td-db::after {
    content: "";
    display: inline-block;
    width: 5px;
    height: 10px;
    margin-right: .5rem;
    border-right: 2px solid var(--mj-green);
    border-bottom: 2px solid var(--mj-green);
    transform: rotate(40deg);
    animation: mj-db-tick-in 1.5s ease-out both;
}

@media (prefers-reduced-motion: reduce) {
    .db-add-status.show,
    .db-classic-table-wrap .table tbody tr.db-row-new > td,
    .db-classic-table-wrap .table tbody tr.db-row-new .td-db::after { animation: none; }
    /* No motion, but the arrival must still be unmistakable. */
    .db-classic-table-wrap .table tbody tr.db-row-new > td { background-color: #d6f2e6; }
    .db-classic-table-wrap .table tbody tr.db-row-new .td-db::after { opacity: 1; }
}

.btn-outline-brand {
    border-color: #2167C5;
    color: #2167C5;
    background: transparent;
}

.btn-outline-brand:hover {
    background: #2167C5;
    color: #fff;
}

@media (max-width: 480px) {
    .login-card { padding: 22px 16px; }
    .brand-title { font-size: 1.2rem; }
    /* Same dead-selector fix as the arrival cue: `.db-classic-table` is not a
       class any element carries, so the compact phone sizing never applied. */
    .db-classic-table-wrap .table thead th,
    .db-classic-table-wrap .table tbody td {
        padding: .3rem .35rem;
        font-size: .75rem;
    }
    .th-desc, .td-desc { display: none; }
}

@media (max-height: 720px) {
    body.login-page { padding: 8px; }
    .login-card { padding: 20px 16px; }
    .brand-header { margin-bottom: 16px; }
    .form-group { margin-bottom: 10px; }
    .db-classic-section { margin-top: 1rem; }
    .btn-submit { padding: .6rem 1.2rem; }
}

/* ===========================================================================
   Tabulator — universal sticky header rule (AGENTS.md Grid Conventions)
   Every Tabulator grid in the project must keep its header visible while
   scrolling. The header uses position:sticky so column titles remain
   readable regardless of row count or container height.
   =========================================================================== */
.tabulator {
    display: flex !important;
    flex-direction: column !important;
    height: 100% !important;
}
.tabulator .tabulator-header {
    position: sticky !important;
    top: 0 !important;
    z-index: 10 !important;
    flex-shrink: 0 !important;
}
.tabulator .tabulator-tableholder {
    flex: 1 !important;
    overflow: auto !important;
}
/* One font for the WHOLE grid, named explicitly (owner report 2026-10-01:
   "the grid typography looks inconsistent with the rest of the interface — the
   Arabic glyphs look like a fallback face while Latin and digits look like they
   come from a different font").
   The grid previously INHERITED its typeface. Tabulator's own stylesheet sets no
   `font-family` (only `font-size: 16px` on `.tabulator`), so the family was
   whatever the ancestor chain happened to give it — which is why one cell could
   mix two faces: `font-family: 'Cairo', 'Segoe UI', sans-serif` resolves PER
   GLYPH, and Segoe UI carries no Arabic, so an Arabic run fell through to a
   system Arabic face while the digits in the same cell stayed in Segoe UI.
   Naming the stack on the grid root and every text layer it owns makes the
   rendering deterministic instead of inherited, and keeps the header and the
   body on ONE family (the weight hierarchy below still differentiates them).
   `Tahoma` sits in the chain deliberately: it is the Windows family that HAS
   Arabic glyphs, so a failed Cairo degrades to one consistent face rather than
   splitting the run in two. No `!important`, no inline styles, no layout change.
   The sorter/arrow glyphs are Tabulator's own background icons and are not
   affected — they are not text. */
.tabulator,
.tabulator .tabulator-header,
.tabulator .tabulator-col,
.tabulator .tabulator-col-title,
.tabulator .tabulator-row,
.tabulator .tabulator-cell,
.tabulator .tabulator-placeholder {
    font-family: 'Cairo', 'Segoe UI', Tahoma, sans-serif;
}

/* ==========================================================================
   Grid headers NEVER truncate (owner directive 2026-10-03):
   «the user does not ever want to see the grid's headers in ellipsis like
   this» — reported on اليوميات, where تاريخ العملية, وقت العملية and
   وارد / منصرف painted as تاريخ العمل..., وقت العمل... and وارد / منص...
   ---------------------------------------------------------------------------
   Tabulator's vendor rule clips every title with `white-space: nowrap;
   overflow: hidden; text-overflow: ellipsis`, and each screen repeated that
   trio in its own `#<grid> .tabulator-col-title` block (with !important), so
   the caption was lost on every grid the moment a column was narrower than
   its own header. The replacement is written ONCE here:

     * the title WRAPS at its spaces instead of being cut;
     * `overflow-wrap: anywhere` is the LAST resort — a single word that
       cannot fit its column at all breaks onto a second line, because a
       broken word is still a readable word while an ellipsised one is lost
       information;
     * the header GROWS to the lines it needs (Tabulator writes the header
       heights INLINE, hence !important on the box heights). A wrapped caption
       therefore costs one extra line, NOT the 66px padding stack this project
       already fixed — `tests/ui/grid_header_parity.Tests.ps1` asserts BOTH:
       no caption is truncated AND no header grows past a bounded height.

   A screen may still style its own header (padding, font, colour) but may not
   re-hide a caption: the per-screen `nowrap` + ellipsis declarations are gone
   from journals, clients, suppliers, employees and the inventory grid, and
   from the shared module-shell skin. */
.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title {
    /* !important is load-bearing twice over: the vendored
       tabulator_bootstrap5.min.css is linked AFTER this file with the SAME
       5-class selector (so an equal-specificity rule here loses on source
       order), and this rule is a FLOOR — a page block may style its caption
       but must never be able to re-hide it behind «...». */
    white-space: normal !important;
    text-overflow: clip !important;
    overflow-wrap: anywhere !important;
}

.tabulator .tabulator-header .tabulator-headers,
.tabulator .tabulator-header .tabulator-col,
.tabulator .tabulator-header .tabulator-col .tabulator-col-content {
    height: auto !important;
}

/* ==========================================================================
   Shared item navigation — item card and inventory record pointer
   ========================================================================== */
.item-navigation-footer {
    --mj-nav-accent: var(--ic-accent, #2167c5);
    --mj-nav-accent-soft: var(--ic-accent-soft, #eff6ff);
    --mj-nav-border: var(--ic-border, #dfe9f3);
    --mj-nav-card: var(--ic-card, #fff);
    --mj-nav-muted: var(--ic-muted, #62809e);
    --mj-nav-text: var(--ic-text, #17365d);
}

.item-navigation-footer .ic-nav-btn {
    width: 2.75rem;
    height: 2.75rem;
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border-radius: 0.65rem;
    border: 1px solid var(--mj-nav-border);
    background: var(--mj-nav-card);
    color: var(--mj-nav-muted);
    transition: border-color .15s ease, color .15s ease, background-color .15s ease;
}

.item-navigation-footer .ic-nav-btn:hover:not(:disabled),
.item-navigation-footer .ic-nav-btn:focus-visible:not(:disabled) {
    border-color: var(--mj-nav-accent);
    color: var(--mj-nav-accent);
    background: var(--mj-nav-accent-soft);
}

.item-navigation-footer .ic-nav-btn:disabled {
    opacity: .4;
    cursor: not-allowed;
}

.item-navigation-footer .ic-counter {
    min-height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #f1f5f9;
    border-radius: 999px;
    padding: .25rem .9rem;
    font-weight: 700;
    color: var(--mj-nav-text);
    font-size: calc(.8rem * var(--ic-fs, 1));
    font-variant-numeric: tabular-nums;
}

@media (max-width: 575.98px) {
    .item-navigation-footer {
        padding: .35rem .5rem !important;
        gap: .25rem;
    }

    .item-navigation-footer .ic-nav-btn {
        width: 44px;
        height: 44px;
    }
}

/* ==========================================================================
   Shared stock-bar component — per-warehouse quantity share
   ========================================================================== */   /*
   warehouse: the item card's الكميات pane and the inventory page's
   توزيع الكميات الداخلي sub-grid. The computation lives in
   static/js/mj-stock-bars.js (mjStockBarsDraw); these rules only paint what
   that renderer writes.

   Row contract (server partials own the markup):
     <tr data-ic-qty="...">
       .ic-stock-pct > [data-ic-pct]        — the percentage badge
       .ic-stock-bar-track > .ic-stock-bar  — grey track + blue fill

   The item card defines --ic-accent/--ic-muted/--ic-fs on .item-card-root;
   the fallbacks below are what a plain page (no card vars) resolves to, so
   both surfaces render IDENTICAL visuals from one declaration.
   ========================================================================== */

.ic-stock-bar-track {
    height: 20px; margin-top: 0.35rem; background-color: #e2e8f0;
    border-radius: 999px; overflow: hidden;
    box-shadow: inset 0 1px 2px rgba(15, 23, 42, 0.08);
    /* A track squeezed to zero width reads as MISSING data, so it keeps a
       usable minimum wherever it is laid out (measured 2026-09-24: in the
       one-line column the badge alone can eat a phone-width cell). */
    min-width: 64px;
}
.ic-stock-bar-track.is-zero { background-color: #f1f5f9; }
/* SIDE-BY-SIDE arrangement: when the badge and the track are direct siblings
   they sit in ONE flex row (the الكمية grids' «النسبة من الإجمالي» column), so
   the stacked layout's top offset would push the track off the badge's centre
   line. A structural selector, not a per-page override: the stacked consumers
   (the number row ABOVE the bar) keep their offset, the column consumers lose
   it — one styling source, no fork. */
.ic-stock-pct + .ic-stock-bar-track { margin-top: 0; }
/* …and on a phone the two texts cannot share a row with a readable bar: the
   bar takes its own line INSIDE the column instead of collapsing. The column
   layout (and the height it hands back to the transactions grid) is what
   desktop/tablet get; a phone keeps the stacked pair. */
@media (max-width: 575.98px) {
    .ic-pct-row { flex-wrap: wrap; }
    .ic-pct-row .ic-stock-bar-track { flex-basis: 100%; }
}
.ic-stock-bar {
    width: 0; height: 100%; border-radius: 999px;
    background-color: var(--ic-accent, #2563eb);
    transition: width 0.4s ease;
}
.ic-stock-bar.is-zero { background-color: #94a3b8; opacity: 0.25; }
/* Shared percentage badge: bold, pill-shaped, large enough to read at a
   glance, and it can never be clipped (unlike a number placed INSIDE the
   progress fill — that is what cropped the "850%" the owner reported). */
.ic-stock-pct {
    font-size: calc(0.95rem * var(--ic-fs, 1)); font-weight: 700; line-height: 1.2;
    color: var(--ic-accent, #2563eb); background-color: #eff6ff;
    border: 1px solid #bfdbfe; border-radius: 999px;
    padding: 0.1rem 0.5rem; min-width: 48px; text-align: center;
    font-variant-numeric: tabular-nums; white-space: nowrap;
}
.ic-stock-pct.is-zero {
    font-weight: 600; color: var(--ic-muted, #64748b);
    background-color: #f1f5f9; border-color: #e2e8f0;
}
/* The caption inside the badge — "67% من إجمالي الكميات". Smaller/muted so
   the NUMBER stays the visual anchor; the pill wraps both as one unit. */
.ic-stock-pct .ic-pct-of {
    font-size: calc(0.68rem * var(--ic-fs, 1)); font-weight: 600;
    color: var(--ic-muted, #64748b); margin-inline-start: 0.3rem;
}
.ic-stock-pct.is-zero .ic-pct-of { color: var(--ic-muted, #64748b); opacity: 0.75; }

.mj-grid-row-actions {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .25rem;
    min-height: 44px;
}

.mj-grid-row-action {
    width: 44px;
    min-width: 44px;
    height: 44px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 8px;
    background: transparent;
    color: #2167c5;
    cursor: pointer;
    transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}

.mj-grid-row-action:hover,
.mj-grid-row-action:focus-visible {
    border-color: #9fc0e6;
    background: #eaf3ff;
}

.mj-grid-row-action:focus-visible {
    outline: 3px solid #2167c5;
    outline-offset: 1px;
}

.mj-grid-row-action-delete {
    color: #a5265a;
}

.mj-grid-row-action-delete:hover,
.mj-grid-row-action-delete:focus-visible {
    border-color: #e3a9bd;
    background: #fdf1f6;
    color: #8f1748;
}

/* Universal modal header: centered title, close button on the FAR LEFT in RTL.
   Owner directive 2026-09-28: the document is RTL, so the reading START is the
   right edge — the X belongs on the physical LEFT, out of the flow so it cannot
   bias the centered title. The pin is the LOGICAL property (inset-inline-end is
   the physical left in RTL), not the physical `right: 0` this used to carry:
   a physical pin reads the same in both directions, which is how the X ended
   up on the reading start of every .mj-modal-header in the app. */
.modal-header.mj-modal-header {
    justify-content: center;
    position: relative;
}
.modal-header.mj-modal-header .modal-title {
    text-align: center;
}
.modal-header.mj-modal-header > .btn-close {
    position: absolute;
    top: 0;
    inset-inline-end: 0;
    margin: 0.5rem;
}

/* Icon-to-caption gap — the ONE place, every modal (owner directive
   2026-10-04: «the حفظ icon needs to be away from the text on the button»).
   A glyph that LEADS a caption sits at the reading start, so the space
   between it and the text is its INLINE-END side — in RTL that is the LEFT.
   Templates used `ms-*` on such an icon, which in the RTL build is margin-
   RIGHT: it pushed the icon away from the caption and left ZERO space
   between them, which is how the 🏭 ended up welded to إدارة المخازن and
   the 💾 sat on the word حفظ. `gap` on a flex row is the mechanism that
   cannot be written on the wrong side, so it is the mechanism used here. */
.modal-header .modal-title {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
}
/* `!important` is not a smell here, it is the enforcement: Bootstrap ships its
   spacing utilities as `!important`, so a template that still carries the
   wrong-side `ms-2` would otherwise win and re-weld the icon to its caption.
   Zeroing the inline margins on the LEADING icon is safe — nothing can sit
   before the first child, so that side has no gap to give. */
.modal-header .modal-title > i:first-child {
    margin-inline: 0 !important;
}

/* Modal footer: a real gutter, the 44px Fitts floor on every action, and the
   primary one step LARGER than the secondaries (AGENTS.md: «Primary actions
   are LARGER and HIGH-CONTRAST… secondary actions must never be the same
   size/weight as the primary»). The floor is a min-height, never a height, so
   a modal that already ships a taller button keeps it. */
.modal-footer {
    gap: .5rem;
}
.modal-footer .btn {
    min-height: 44px;
}
.modal-footer .btn-primary,
.modal-footer .btn-brand,
.modal-footer .btn-success,
.modal-footer .btn-danger {
    min-height: 48px;
    font-weight: 700;
}
/* The icon-to-caption gap on a BUTTON. Same defect, same fix: a leading glyph
   on a `ms-*` margin puts its space on the far side and leaves the caption
   touching it. Making the button a flex row puts the gap in the ONE place
   that cannot be mirrored by a wrong logical side. Scoped to `.modal` so no
   toolbar on the page is restyled by this rule. */
.modal .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .4rem;
}
.modal .btn > i:first-child {
    margin-inline: 0 !important;
}

/* ---------------------------------------------------------------------------
   14. Toasts (non-blocking notices) — templates/partials/mj_toasts.html
   The shared renderer is static/js/mj-toasts.js (window.mjToast). A toast is
   for a message the user must NOT have to answer (تذكير بقسط مستحق); anything
   that needs a decision stays a dialog (section 13). One styling source: pages
   and feeds never re-declare these rules.
   The stack sits at the PHYSICAL bottom-CENTER of the viewport (owner directive
   2026-10-10): toasts were easy to miss hugging the bottom edge, so they now
   rise from the middle of the bottom and read 30% larger in width and height.
   ---------------------------------------------------------------------------
   */
.mj-toast-host {
    position: fixed;
    z-index: 1090;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    align-items: center;
    left: 0;
    right: 0;
    bottom: 12%;
    gap: .7rem;
    padding: 0 1rem;
    pointer-events: none;
}

.mj-toast {
    pointer-events: auto;
    display: flex;
    align-items: flex-start;
    gap: .78rem;
    width: min(calc(100vw - 2rem), 620px);
    background: #fff;
    border: 1px solid var(--mj-border, #dfe9f3);
    border-inline-start: 6.5px solid var(--mj-primary-blue, #2167c5);
    border-radius: 12px;
    box-shadow: 0 16px 39px rgba(20, 46, 85, .18);
    padding: .91rem .98rem;
    animation: mj-toast-in .22s ease-out;
}

.mj-toast.mj-toast-out {
    opacity: 0;
    transform: translateY(6px);
    transition: opacity .2s ease, transform .2s ease;
}

.mj-toast-warn { border-inline-start-color: #8f1748; }
.mj-toast-ok { border-inline-start-color: #16a34a; }

.mj-toast .mj-toast-icon {
    width: 44px;
    height: 44px;
    min-width: 44px;
    border-radius: 10px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #eaf2fc;
    color: #2167c5;
}

.mj-toast-warn .mj-toast-icon { background: #fdf1f6; color: #8f1748; }
.mj-toast-ok .mj-toast-icon { background: #ecfdf3; color: #16a34a; }

.mj-toast .mj-toast-body { flex: 1 1 auto; min-width: 0; width: 100%; }

.mj-toast .mj-toast-title {
    font-weight: 800;
    font-size: 1.12rem;
    color: #142e55;
    margin-bottom: .13rem;
}

.mj-toast .mj-toast-text {
    font-size: 1.09rem;
    color: #33415c;
    line-height: 1.6;
}

.mj-toast .mj-toast-controls {
    display: flex;
    align-items: center;
    gap: .3rem;
    flex: 0 0 auto;
}

/* 44px hit areas even for the small glyphs (AGENTS.md touch-target rule),
   enlarged 30% here to match the owner's bigger toast. */
.mj-toast .mj-toast-action {
    min-height: 57px;
    padding: .46rem 1.04rem;
    border: none;
    border-radius: .45rem;
    background: var(--mj-primary-blue, #2167c5);
    color: #fff;
    font-weight: 700;
    font-size: 1.07rem;
    white-space: nowrap;
}

.mj-toast .mj-toast-action:hover,
.mj-toast .mj-toast-action:focus-visible {
    background: #1a52a0;
    color: #fff;
}

.mj-toast .mj-toast-close {
    width: 57px;
    height: 57px;
    border: none;
    border-radius: .45rem;
    background: transparent;
    color: #64748b;
}

.mj-toast .mj-toast-close:hover,
.mj-toast .mj-toast-close:focus-visible {
    background: #eef2f7;
    color: #142e55;
}

@keyframes mj-toast-in {
    from { opacity: 0; transform: translateY(8px); }
    to { opacity: 1; transform: translateY(0); }
}

@media (max-width: 575.98px) {
    .mj-toast-host { left: 0; right: 0; bottom: .9rem; padding: 0 .6rem; }
}

/* ===== Modal X close button sits at the LEFT (owner directive 2026-09-26) =====
 * The document is RTL: inside any flex header the FIRST flex child renders on
 * the RIGHT — which is exactly where the X used to land whenever its source
 * put it before the title (كارت الصنف, التوالف, الجرد, تنبيهات المخازن,
 * الباحث البطيء …) while modals whose close follows the title already ended
 * up left. One rule unifies every modal: the close control is pushed to the
 * flex flow END (= LEFT in RTL) with `order`, whatever its source position.
 * Scoped to X controls ONLY — footer text dismissers (إلغاء / رجوع / حسناً /
 * فهمت / إغلاق) are never matched, source order and centered titles are
 * untouched, and the rule is idempotent (a close already left stays left).
 * Absolutely pinned X buttons ignore `order`; they are inline-end (left in
 * RTL) at their own site rules (mj_dialogs start-0 → end-0, ch-close-wrap
 * inset-inline-end, bc-modal-close margin-inline-start:auto, and كارت الصنف
 * .ic-header, absolutely pinned at inset-inline-end — its former `order: 0`
 * override, which put the X back at the RTL reading start, is RETIRED per the
 * owner directive 2026-09-28: do not reinstate it). */
.modal .btn-close,
.modal .close,
.modal .modal-header > [data-bs-dismiss="modal"],
.modal .ic-header > [data-bs-dismiss="modal"],
.modal .pd-header > [data-bs-dismiss="modal"],
.modal .sm-header > [data-bs-dismiss="modal"],
.modal .inv-alerts-close,
.modal .invcount-close-btn,
.modal .ic-icon-btn[data-bs-dismiss="modal"],
.modal .bc-modal-close {
    order: 9;
}

/* ---------------------------------------------------------------------------
   15. Single-flight search buttons (owner directive 2026-09-30)
   The behaviour lives in static/js/mj-busy-action.js (ONE module, loaded once
   from partials/page_header.html); this is its ONE styling source. A search
   control marked `data-mj-busy` gets this state class while its search is in
   flight: the label/icon is replaced by a spinner, the control is inert, and
   a second press does nothing until the request settles. Never re-declare
   `.mj-busy-btn` in a page stylesheet.

   The content is made transparent rather than hidden so the button never
   reflows (its width is part of every toolbar's layout); the spinner is
   painted centred over it. `--mj-busy-spin` is the ring colour, for a control
   whose surface needs a darker ring than the default white.
   --------------------------------------------------------------------------- */
.mj-busy-btn {
    position: relative;
    color: transparent !important;
    pointer-events: none;
    cursor: progress;
}

.mj-busy-btn::after {
    content: '';
    position: absolute;
    inset: 0;
    margin: auto;
    width: 1.05rem;
    height: 1.05rem;
    border: 2px solid rgba(255, 255, 255, .35);
    border-top-color: var(--mj-busy-spin, #ffffff);
    border-radius: 50%;
    animation: mj-busy-spin .7s linear infinite;
}

@keyframes mj-busy-spin {
    to { transform: rotate(360deg); }
}

/* A page that disables motion still gets a spinner, just a calmer one. */
@media (prefers-reduced-motion: reduce) {
    .mj-busy-btn::after { animation-duration: 2s; }
}

/* ---------------------------------------------------------------------------
   16. The number stepper (- / value / +)
   ONE component, ONE styling source. Markup + behaviour are the contract:

     <div class="mj-stepper">
       <button type="button" class="mj-step" data-step="1"  aria-label="+">+</button>
       <input ... class="mj-stepper-input">
       <button type="button" class="mj-step" data-step="-1" aria-label="-">&minus;</button>
     </div>

   RTL: the FIRST control in the DOM paints at the reading START, which IS the
   physical right, so + comes first and - last - the owner mockup's pixel order
   (owner mockup 2026-09-30: the الكمية column in billing; 2026-10-01: عدد النسخ
   on the barcode screen).

   The behaviour is static/js/mj-stepper.js: ONE delegated `click` listener on
   document, so a stepper that arrives by htmx swap needs no re-binding. Native
   <button>s mean Enter/Space work with no JS keyboard code, and the value change
   re-dispatches a bubbling `input`, so any existing listener (a total, a summary,
   a print) fires exactly as it does for typing.

   `data-min` / `data-max` on the WRAPPER clamp the value, and the buttons
   disable themselves at the bounds, so the control cannot present a state the
   field would refuse (عدد النسخ can never reach 0; a quantity never goes
   negative). Never re-declare `.mj-stepper` in a page stylesheet.
   --------------------------------------------------------------------------- */
.mj-stepper {
    display: flex;
    align-items: stretch;
    background: var(--mj-stepper-bg, #ffffff);
    border: 1px solid var(--mj-stepper-border, #dfe9f3);
    border-radius: var(--mj-stepper-radius, .6rem);
    overflow: hidden;
    height: var(--mj-stepper-h, 44px);   /* 44px: the Fitts floor */
    width: var(--mj-stepper-w, 100%);
}

.mj-stepper-input {
    flex: 1 1 auto;
    min-width: 0;
    background: transparent !important;
    border-color: transparent !important;
    box-shadow: none !important;
    color: var(--mj-stepper-value, #17365d) !important;
    font-weight: 700;
    font-size: var(--mj-stepper-fs, .95rem);
    text-align: center;
    border-radius: 0;
    height: auto;
    padding: 0 .25rem;
}

/* ONE suppression rule for EVERY numeric field in the ERP, and the only place
   it is allowed to live (owner directive 2026-10-03, measured sweep).

   WHY, in the owner's words on the journal screen: "no need for the up and down
   arrows in field on this page". The sweep that answered it counted 29 numeric
   inputs across templates and 4 more built by JS (mj-stepper, excel_import,
   transfer_direct, the journal grid editor), spread over accounts, accounts
   reconciliation, commercial history, employees, inventory (product form,
   barcode, damage, demand forecast, excel import), admin and settings - every
   one of them on a page that already loads this file.

   WHY IT MUST BE ONE RULE AND NOT PER-PAGE: the journal screen had its own copy
   (accounting_entries.html), which is how a house rule becomes a per-screen
   folklore nobody else inherits. And the per-page copy was also NOT load-bearing
   where a grep would say it was - with it deleted, the browser still reported
   `appearance: none`, because Bootstrap's own `.form-control { appearance: none }`
   already covers Chromium. What `appearance: none` on the input does NOT cover is
   the spin-button PSEUDO-ELEMENT, which is why the arrows survived on the engines
   the owner was looking at. So the rule names the pseudo-elements explicitly and
   the standard property for Firefox, and it is scoped to `input[type="number"]`
   so nothing else in the app can lose a control it needs.

   THE STEPPER: .mj-stepper-input used to carry this pair of rules. Its own +/-
   pair IS the affordance, and it is an input[type="number"], so this rule now
   covers it exactly as before - one mechanism, one place, nothing to forget. */
input[type="number"] {
    -moz-appearance: textfield;
    appearance: textfield;
}

input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.mj-step {
    flex: 0 0 44px;
    width: 44px;
    border: 0;
    background: transparent;
    color: var(--mj-stepper-btn, #62809e);
    font-size: 1.05rem;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    transition: background .15s, color .15s;
}

.mj-step:hover:not(:disabled) {
    background: var(--mj-stepper-hover-bg, #eff6ff);
    color: var(--mj-stepper-accent, #2167c5);
}

.mj-step:focus-visible {
    outline: 2px solid var(--mj-stepper-accent, #2167c5);
    outline-offset: -2px;
}

/* At a bound the control is honest about it rather than silently doing nothing:
   dimmed and inert, so "why did nothing happen" has a visible answer. */
.mj-step:disabled {
    color: var(--mj-stepper-btn, #62809e);
    opacity: .35;
    cursor: not-allowed;
}

/* ---------- KPI card - a FIGURE that reads as a card, not a textbox ----------
   Owner directive (2026-10-04): the journals totals strip («الإجماليات») had
   five white outlined boxes with a grey word and a navy number, which read as
   FORM FIELDS the operator might type into - and they are not: they are read-
   only figures. The reference card is the one the owner pointed at: a tinted
   panel, a coloured accent down its LEADING edge (the reading start, so the
   right edge in this RTL app), an icon tile at the far end, a muted caption
   and a large figure in the card's own accent colour.

   ONE component, here, because a card that five screens each draw differently
   is how a house look stops being inherited: a screen uses `mj-kpi-grid` +
   `mj-kpi` + a tone modifier and owns NO css of its own.

   THE TONES are the six the ledger itself speaks (إيرادات / مصروفات / صافي /
   أصول / التزامات / رأس مال): money IN is green, money OUT is the app's own
   delete-rose (#A5265A, the one the row delete already uses), and the rest are
   distinct hues rather than five greys. Every accent is dark enough on its own
   tint for the 1.5rem 900-weight figure (>= 4.5:1), and the tint is light
   enough that the caption still reads - a KPI card that fails contrast is a
   worse figure than the textbox it replaced.

   The icon is decorative (`aria-hidden` in the markup), so the figure is read
   once, as its caption plus its number, by a screen reader too. */
.mj-kpi-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: .6rem;
}
.mj-kpi {
    --mj-kpi-accent: #1B5FA8;
    --mj-kpi-tint: #EFF6FF;
    --mj-kpi-line: #CFE0F7;
    display: flex;
    align-items: center;
    gap: .7rem;
    min-height: 66px;
    padding: .6rem .75rem;
    background: var(--mj-kpi-tint);
    border: 1px solid var(--mj-kpi-line);
    /* The leading edge - logical, so it is the RIGHT edge in this RTL app and
       the left one on an LTR screen, without a second rule. */
    border-inline-start: 4px solid var(--mj-kpi-accent);
    border-radius: 12px;
}
.mj-kpi-body { min-width: 0; }
.mj-kpi-label {
    color: #5C6B78;
    font-size: .8rem;
    font-weight: 700;
    line-height: 1.2;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.mj-kpi-value {
    color: var(--mj-kpi-accent);
    font-size: 1.5rem;
    font-weight: 900;
    line-height: 1.2;
    font-variant-numeric: tabular-nums;
}
.mj-kpi-icon {
    flex: 0 0 auto;
    margin-inline-start: auto;      /* the FAR end: the tile never hugs the text */
    width: 34px;
    height: 34px;
    border-radius: 10px;
    display: grid;
    place-items: center;
    background: #fff;
    border: 1px solid var(--mj-kpi-line);
    color: var(--mj-kpi-accent);
    font-size: 1.05rem;
}
.mj-kpi--in     { --mj-kpi-accent: #1D7A3E; --mj-kpi-tint: #E9F7EF; --mj-kpi-line: #BFE3CD; }
.mj-kpi--out    { --mj-kpi-accent: #A5265A; --mj-kpi-tint: #FDF1F6; --mj-kpi-line: #F0CBD8; }
.mj-kpi--net    { --mj-kpi-accent: #1B5FA8; --mj-kpi-tint: #EFF6FF; --mj-kpi-line: #CFE0F7; }
.mj-kpi--asset  { --mj-kpi-accent: #96650A; --mj-kpi-tint: #FFF6E8; --mj-kpi-line: #EFDCB4; }
.mj-kpi--liab   { --mj-kpi-accent: #53439F; --mj-kpi-tint: #F2F0FD; --mj-kpi-line: #D3CCF0; }
.mj-kpi--equity { --mj-kpi-accent: #142E55; --mj-kpi-tint: #EEF4FA; --mj-kpi-line: #C9DAEC; }

@media (max-width: 575.98px) {
    .mj-kpi { min-height: 58px; padding: .5rem .6rem; gap: .5rem; }
    .mj-kpi-value { font-size: 1.22rem; }
    .mj-kpi-icon { width: 30px; height: 30px; font-size: .95rem; }
}

/* A NEUTRAL figure (a count, a status) that is still a card: the default
   accent, because inventing a hue per screen is how a palette drifts. */
.mj-kpi--neutral { --mj-kpi-accent: #41607F; --mj-kpi-tint: #F1F5F9; --mj-kpi-line: #D5E0EA; }


/* ---------------------------------------------------------------------------
   17. مرسال — the WhatsApp send modal
       (templates/partials/whatsapp_send_modal.html)

   THE OWNER'S MOCKUP (2026-10-04) is what this block is: a green header
   carrying the WhatsApp mark and the close control at the END of the row, a
   send-device notice, the party as a FOUR-ROW fact table, the message in a
   tinted preview card with the WhatsApp avatar, one row per phone (check badge,
   number, copy), ONE hint, and the footer with the send pill at the end.

   ONE THING THE MOCKUP COULD NOT HAVE MEANT. Its top box is red and its text
   is filler — it says "على جهازك" twice and is not a sentence. The box is
   #wa-ready and it is red ONLY for `is-missing`, which the script sets from the
   live probe answering `ok === false`: a machine that really cannot hand off.
   Amber is "could not confirm", green is "ready", and while the probe runs the
   box is hidden. The verdict stays ADVISORY and never touches the send button;
   the one loud treatment for a device that really failed is #wa-handoff-error,
   which fires after an actual handoff failure. A permanent red alarm above a
   workstation that already has WhatsApp installed is the defect this screen was
   reviewed for earlier the same day — the mockup's SHAPE is adopted, its static
   alarm is not. */

/* ---- the shell ---------------------------------------------------------- */

/* OWNER DIRECTIVE 2026-10-04, twice: «make the page 100% wider» and then
   «decrease width by 15%». The dialog was a 600px column, went edge to edge,
   and now sits at 85% of the viewport — full width was more than the screen
   needs, and the empty band down each side read as a mistake rather than as
   air. 85% is the ONLY desktop width; the phone branch below still goes
   full-bleed, because on a 375px screen any side band is a lost column, not
   air. */
#wa-modal .modal-dialog { max-width: 85%; margin: .5rem auto; }
#wa-modal .wa-modal-content { overflow: hidden; }
#wa-modal .modal-body { padding: 1rem 1.05rem; }

/* ---- the header --------------------------------------------------------- */

#wa-modal .wa-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    padding: 1rem 1.15rem;
    background: linear-gradient(135deg, #17995A 0%, #0C6E3B 100%);
    border-bottom: 0;
    color: #fff;
}
/* The mark is part of the CAPTION (it leads the title textbox), so theme.css's
   `.modal-header .modal-title` flex rule and its zeroed-icon enforcement apply
   to it unchanged — the gap comes from there, never from a spacing utility. */
#wa-modal .wa-title {
    display: inline-flex;
    align-items: center;
    gap: .7rem;
    margin: 0;
    color: #fff;
}
#wa-modal .wa-mark {
    font-size: 2.4rem;
    line-height: 1;
    color: #fff;
    opacity: .95;
    filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .18));
}
#wa-modal .wa-title-text { display: flex; flex-direction: column; gap: .1rem; }
#wa-modal .wa-title-name {
    font-size: 1.4rem;
    font-weight: 700;
    line-height: 1.2;
    color: #fff;
}
#wa-modal .wa-sub {
    font-size: .82rem;
    font-weight: 500;
    line-height: 1.3;
    color: rgba(255, 255, 255, .9);
}
/* Bootstrap's close is a dark glyph; on the green band it has to be light. */
#wa-modal .wa-close {
    flex: 0 0 auto;
    opacity: 1;
    filter: invert(1) grayscale(1) brightness(2);
    opacity: .9;
    margin: 0;
    padding: .55rem;
}
#wa-modal .wa-close:hover { opacity: 1; filter: invert(1) grayscale(1) brightness(1.6); }

/* ---- 1. the send-device NOTICE (a status, never a standing alarm) ------- */

#wa-modal .wa-ready {
    display: flex;
    align-items: center;
    gap: .55rem;
    padding: .6rem .75rem;
    margin-bottom: .9rem;
    font-size: .82rem;
    line-height: 1.45;
    border: 1px solid #D5E0EA;
    border-radius: 12px;
    background: #F8FAFC;
    color: #41607F;
}
#wa-modal .wa-ready-icon { flex: 0 0 auto; font-size: .95rem; color: inherit; }
#wa-modal .wa-ready-text { flex: 1 1 auto; min-width: 0; }
#wa-modal .wa-ready-links {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: .6rem;
}
#wa-modal .wa-ready-link {
    font-weight: 700;
    font-size: .78rem;
    text-decoration: none;
    white-space: nowrap;
    padding: .3rem .6rem;
    border-radius: 8px;
    background: rgba(255, 255, 255, .7);
}
/* The download link is the reason the mockup's box exists: an operator whose
   device cannot hand off needs somewhere to GET WhatsApp. */
#wa-modal .wa-ready-site {
    font-weight: 700;
    font-size: .78rem;
    text-decoration: none;
    white-space: nowrap;
}
#wa-modal .wa-ready.is-ok     { background: #E9F7EF; border-color: #BFE3CD; color: #1D7A3E; }
#wa-modal .wa-ready.is-unknown { background: #FFF6E8; border-color: #EFDCB4; color: #96650A; }
/* The mockup's red — reachable ONLY when the probe proved the machine cannot
   hand off. Amber for the unconfirmed case stays amber (tests/pester/
   WhatsAppReadiness.Tests.ps1): an unconfirmed device is not a broken one. */
#wa-modal .wa-ready.is-missing {
    background: #FDECEC;
    border-color: #F2B8B5;
    color: #B42318;
}
#wa-modal .wa-ready.is-missing .wa-ready-site,
#wa-modal .wa-ready.is-missing .wa-ready-link { color: #1B5FA8; }

/* ---- 2. the party as a FOUR-ROW fact table ------------------------------ */

#wa-modal .wa-facts {
    border: 1px solid #E2E8F0;
    border-radius: 12px;
    overflow: hidden;
    margin-bottom: .9rem;
}
#wa-modal .wa-fact {
    display: grid;
    grid-template-columns: minmax(0, 9rem) minmax(0, 1fr);
}
#wa-modal .wa-fact + .wa-fact { border-top: 1px solid #EDF1F6; }
/* The label cell carries the screen's own field tone and is separated from the
   value by a hairline — the mockup's vertical rule, kept on every row. */
#wa-modal .wa-fact-label {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    padding: .6rem .8rem;
    background: #F7F9FC;
    border-inline-end: 1px solid #EDF1F6;
    font-size: .85rem;
    font-weight: 700;
    color: #5C6B78;
}
#wa-modal .wa-fact-label i { flex: 0 0 auto; color: #94A3B8; font-size: .95rem; }
#wa-modal .wa-fact-value {
    display: flex;
    align-items: center;
    padding: .6rem .8rem;
    font-size: .92rem;
    font-weight: 600;
    color: #142E55;
    overflow-wrap: anywhere;
}
/* The name is the subject of the message and the only row that reads at body
   size; the other three are facts that support it. */
#wa-modal .wa-fact-value.wa-who-name { font-size: 1rem; font-weight: 700; }
/* The code cell keeps `direction: ltr` so a Latin-numeral code reads «39-» and
   not «-39» — that is the whole point of the class — but the CELL must still
   sit flush to the RIGHT, aligned with the three rows above and below it.
   It used not to: `direction: ltr` on a `display: flex` container moves the
   anonymous value item from flex-start to the LEFT edge, so «39-» floated to
   the far side while the name and the balance hugged the label column, and
   the one fact table read as two. The culprit was the DIRECTION, never
   `text-align`, which cannot move a flex item at all. Text order and box
   alignment are separate questions, and only the second one was wrong. */
#wa-modal .wa-fact-code {
    direction: ltr;
    /* `text-align` cannot move this: the value is an anonymous FLEX item,
       and `direction: ltr` also flips flex-start to the LEFT edge. The
       pair below is therefore load-bearing and must stay together - drop
       `direction` and flex-end becomes the left edge again. */
    justify-content: flex-end;
}
/* المستحق is the only FIGURE on the screen, so it is the only cell that carries
   a colour — the mockup's amber pill. Dark ink on amber, never white. */
#wa-modal .wa-due {
    display: inline-block;
    padding: .18rem .7rem;
    border-radius: 8px;
    background: #F6B93B;
    color: #3B2700;
    font-size: .92rem;
    font-weight: 700;
}

/* ---- 3. the message, as a preview card ---------------------------------- */

#wa-modal .wa-preview {
    display: flex;
    align-items: stretch;
    gap: .85rem;
    padding: .8rem .9rem;
    margin-bottom: 1rem;
    background: #EAF2FC;
    border: 1px solid #C9DCF5;
    border-radius: 14px;
}
#wa-modal .wa-preview-text { flex: 1 1 auto; min-width: 0; }
#wa-modal .wa-preview-rule { flex: 0 0 auto; width: 1px; background: #C9DCF5; }
/* VB6 TxtMessage parity: the message is still the ONE editable field. It is
   dressed as the preview the mockup draws, not frozen into one — an operator
   who cannot edit it cannot send a correct statement. */
#wa-modal .wa-preview-label {
    display: block;
    font-size: .72rem;
    font-weight: 700;
    color: #41607F;
    margin-bottom: .2rem;
}
#wa-modal .wa-message {
    display: block;
    width: 100%;
    min-height: 86px;
    padding: .5rem .6rem;
    font-size: .88rem;
    line-height: 1.6;
    color: #142E55;
    background: #fff;
    border: 1px solid #C9DCF5;
    border-radius: 10px;
    resize: vertical;
}
#wa-modal .wa-message:focus {
    outline: 0;
    border-color: #2167C5;
    box-shadow: 0 0 0 .18rem rgba(33, 103, 197, .15);
}
#wa-modal .wa-avatar {
    flex: 0 0 auto;
    align-self: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    border-radius: 50%;
    background: #fff;
    border: 1px solid #C9DCF5;
    color: #128C4A;
    font-size: 1.5rem;
}

/* ---- 3b. the MESSAGE TOOLS (owner directive 2026-10-05) ---------------- */

/* One band directly above the message card, holding the two things the owner
   asked for: قوالب الرسائل (the saved-template picker + إدارة/حفظ) and the
   «إضافة اسم الشركة» checkbox. Both are about the message text and nothing
   else, so they share the band instead of adding two chrome rows. The
   checkbox gets its own line inside the band rather than sitting beside the
   picker: a 44px-tall checkbox squeezed between a label and two link buttons
   reads as a fourth control in a row of actions, not as a setting. */
#wa-modal .wa-msg-tools {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .5rem;
    padding: .55rem .7rem .4rem;
    margin-bottom: .75rem;
    background: #F8FAFC;
    border: 1px solid #E2E8F0;
    border-radius: 12px;
}
#wa-modal .wa-template-select {
    flex: 1 1 12rem;
    min-width: 8rem;
    max-width: 24rem;
    min-height: 40px;
    padding: .3rem .55rem;
    font-size: .88rem;
    color: #142E55;
    background: #fff;
    border: 1px solid #CBD5E1;
    border-radius: 10px;
}
#wa-modal .wa-template-select:focus {
    outline: 0;
    border-color: #2167C5;
    box-shadow: 0 0 0 .18rem rgba(33, 103, 197, .15);
}
/* The two band buttons are LINK actions, so they keep the link tone - but they
   are a row control now, not a caption under a list, so they get the band's
   hit area rather than `.wa-all`'s bare text box. */
#wa-modal .wa-msg-tools .wa-all {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    min-height: 40px;
    padding: 0 .35rem;
    font-weight: 700;
    color: #1B5FA8;
    text-decoration: none;
}
#wa-modal .wa-msg-tools .wa-all:hover { color: #14497F; }
#wa-modal .wa-signature { flex: 1 1 100%; }
/* Bootstrap's `.form-check` carries `padding-right: 1.5em`, built for a
   FLOATED input so its label clears it. This row is a flex pair with its own
   gap, so that padding clears nothing and only indents the whole row from the
   band's reading-start edge, measurably ~18px left of the label above it.
   Reset it so the checkbox leads the row flush with the band's controls. */
#wa-modal .wa-signature-check {
    display: flex;
    align-items: center;
    gap: .45rem;
    min-height: 44px;
    padding: 0;
    margin: 0;
}
#wa-modal .wa-signature-check .form-check-input {
    width: 1.15rem;
    height: 1.15rem;
    margin: 0;
}
#wa-modal .wa-signature-check .form-check-label {
    font-size: .86rem;
    font-weight: 600;
    color: #334155;
}

/* ---- 4. the phone rows -------------------------------------------------- */

#wa-modal .wa-phones-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-bottom: .35rem;
}
#wa-modal .wa-phones-title { font-size: .92rem; font-weight: 700; color: #334155; margin: 0; }
#wa-modal .wa-count { font-size: .78rem; color: #64748B; }
#wa-modal .wa-all { padding: 0; font-size: .78rem; }
#wa-modal .wa-phones-wrap {
    border: 1px solid #E2E8F0;
    border-radius: 12px;
    overflow: hidden;
    margin-bottom: .75rem;
}
/* ONE row per number. The row is a div (not a <label>) because it now carries a
   COPY BUTTON, and a button inside a label would toggle the checkbox with it.
   Selection is still one click on the badge or the number — both are wired to
   the same native checkbox — and Space still toggles it. */
#wa-modal .wa-phone-row {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .5rem .8rem;
    background: #fff;
}
#wa-modal .wa-phone-row + .wa-phone-row { border-top: 1px solid #EDF1F6; }
#wa-modal .wa-phone-row.is-picked { background: #F4FBF7; }
#wa-modal .wa-phone-row.is-unusable { background: #FAFBFC; }
/* The check BADGE: the mockup's green square. `appearance: none` because a
   native checkbox cannot carry this shape, and the tick is a CSS glyph so the
   row needs no image. */
#wa-modal .wa-phone-check {
    flex: 0 0 auto;
    appearance: none;
    -webkit-appearance: none;
    width: 1.3rem;
    height: 1.3rem;
    margin: 0;
    border: 1.5px solid #94A3B8;
    border-radius: 6px;
    background: #fff;
    cursor: pointer;
    position: relative;
}
#wa-modal .wa-phone-check:checked {
    background: #128C4A;
    border-color: #128C4A;
}
#wa-modal .wa-phone-check:checked::after {
    content: "";
    position: absolute;
    inset-inline-start: .34rem;
    top: .1rem;
    width: .38rem;
    height: .66rem;
    border: solid #fff;
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}
#wa-modal .wa-phone-check:focus-visible {
    outline: 2px solid #2167C5;
    outline-offset: 2px;
}
#wa-modal .wa-phone-check:disabled { background: #EEF2F6; border-color: #CBD5E1; cursor: default; }
/* The number is the row's own label, so clicking the digits selects the number
   without a wrapper that would swallow the copy button. */
#wa-modal .wa-phone-text {
    flex: 1 1 auto;
    min-width: 0;
    font-size: .92rem;
    font-weight: 600;
    color: #142E55;
    direction: ltr;
    text-align: right;
    cursor: pointer;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
#wa-modal .wa-phone-row.is-unusable .wa-phone-text { color: #94A3B8; font-weight: 500; cursor: default; }
#wa-modal .wa-phone-copy {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: 10px;
    background: transparent;
    color: #64748B;
    cursor: pointer;
}
#wa-modal .wa-phone-copy:hover { background: #F1F5F9; color: #142E55; }
#wa-modal .wa-phone-copy:focus-visible { outline: 2px solid #2167C5; outline-offset: -2px; }

/* ---- the hint ----------------------------------------------------------- */
/* The hint sits as a full-width row under the thing it describes; an inline
   icon utility would weld the glyph to the caption (the icon-spacing rule). */
#wa-modal .wa-hint {
    display: flex;
    align-items: flex-start;
    gap: .45rem;
    font-size: .78rem;
    line-height: 1.6;
    color: #64748B;
    margin-top: .5rem;
}
#wa-modal .wa-hint i { margin-top: .2rem; }
#wa-modal .wa-fail { margin-top: .9rem; }
#wa-modal .wa-fail .wa-hint { margin-top: .5rem; }

/* ---- the footer --------------------------------------------------------- */

#wa-modal .wa-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1rem;
    padding: .85rem 1.05rem;
    background: #fff;
    border-top: 1px solid #EDF1F6;
}
#wa-modal .wa-foot-links { display: flex; align-items: center; gap: .25rem; }
#wa-modal .wa-foot-link {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .5rem .6rem;
    font-size: .88rem;
    font-weight: 700;
    color: #1B5FA8;
    text-decoration: none;
    min-height: 44px;
}
#wa-modal .wa-foot-link:hover { color: #14497F; }
/* theme.css zeroes the margin only on a LEADING icon (`.btn > i:first-child`),
   and this icon TRAILS its caption, so the gap is the only thing spacing them. */
#wa-modal .wa-foot-link > i { margin: 0; }
#wa-modal .wa-foot-rule { width: 1px; height: 26px; background: #E2E8F0; }
/* The primary action: the mockup's big green pill. It is a step TALLER than the
   two link actions beside it (AGENTS.md), and its label counts the selection. */
#wa-modal .wa-send {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    min-height: 52px;
    padding: .5rem 1.4rem;
    border: 0;
    border-radius: 999px;
    background: #128C4A;
    color: #fff;
    font-size: .95rem;
    font-weight: 700;
    box-shadow: 0 6px 16px rgba(18, 140, 74, .28);
}
#wa-modal .wa-send:hover:not(:disabled) { background: #0F7A40; color: #fff; }
#wa-modal .wa-send:disabled { background: #A9C9B6; color: #F2F7F4; box-shadow: none; }
#wa-modal .wa-send i { margin: 0; }

/* Declared here because `mj-ltr-cell` is a semantic hook used by several
   screens, and because inside #wa-modal it sits on ONE element: the code cell
   above. `text-align` is right, not left, for the reason given there — a value
   that starts at the opposite edge from its own row neighbours reads as a
   second column. The rule is scoped to #wa-modal on purpose: the same class on
   a grid cell or an invoice number elsewhere keeps its own alignment. */
#wa-modal .mj-ltr-cell {
    direction: ltr;
    justify-content: flex-end;
}

/* ---- قوالب رسائل الواتساب — the message-template manager sheet ----------
   Owner directive 2026-10-05. Scoped to ITS OWN host (#wa-tpl-modal-host),
   never to #wa-modal: it stacks OVER the send dialog, and borrowing that
   dialog's selectors would make the two sheets edit each other's padding the
   moment one of them changed. The navy header + centred title + 44px close are
   the log modal's shape, because it is the same kind of sheet: a bounded
   dialog read while the work behind it stays put. */
#wa-tpl-modal-host .wa-tpl-modal { overflow: hidden; }
#wa-tpl-modal-host .wa-tpl-head {
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    padding: .7rem .9rem;
    color: #fff;
    background: linear-gradient(90deg, #142E55, #183B68);
}
#wa-tpl-modal-host .wa-tpl-title {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: 0;
    font-size: 1.05rem;
    font-weight: 800;
    text-align: center;
    color: #fff;
}
#wa-tpl-modal-host .wa-tpl-close {
    position: absolute;
    inset-inline-end: .45rem;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    flex: 0 0 44px;
    border: 0;
    border-radius: .5rem;
    background: rgba(255, 255, 255, .14);
    color: #fff;
    font-size: 1.1rem;
}
#wa-tpl-modal-host .wa-tpl-close:hover { background: rgba(255, 255, 255, .26); }
#wa-tpl-modal-host .wa-tpl-body {
    padding: .9rem 1rem 1.1rem;
    background: #F3F7FB;
    color: #1F2D3D;
}
#wa-tpl-modal-host .wa-tpl-form {
    padding: .75rem .85rem .8rem;
    background: #fff;
    border: 1px solid #E2E8F0;
    border-radius: 12px;
}
#wa-tpl-modal-host .wa-tpl-label {
    display: block;
    margin-bottom: .25rem;
    font-size: .78rem;
    font-weight: 700;
    color: #41607F;
}
#wa-tpl-modal-host .wa-tpl-title-input { font-size: .9rem; }
#wa-tpl-modal-host .wa-tpl-text {
    display: block;
    width: 100%;
    min-height: 86px;
    padding: .5rem .6rem;
    font-size: .88rem;
    line-height: 1.6;
    color: #142E55;
    background: #fff;
    border: 1px solid #C9DCF5;
    border-radius: 10px;
    resize: vertical;
}
#wa-tpl-modal-host .wa-tpl-text:focus {
    outline: 0;
    border-color: #2167C5;
    box-shadow: 0 0 0 .18rem rgba(33, 103, 197, .15);
}
#wa-tpl-modal-host .wa-tpl-body-tools {
    display: flex;
    justify-content: flex-end;
    margin-top: .35rem;
}
#wa-tpl-modal-host .wa-tpl-body-tools .wa-tpl-btn {
    font-size: .76rem;
}
#wa-tpl-modal-host .wa-tpl-form-foot {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: .6rem;
    margin-top: .7rem;
}
#wa-tpl-modal-host .wa-tpl-check {
    display: flex;
    align-items: center;
    gap: .45rem;
    margin: 0;
}
#wa-tpl-modal-host .wa-tpl-check .form-check-label {
    font-size: .85rem;
    font-weight: 600;
    color: #334155;
}
/* The two form actions sit at the reading END of the row. */
#wa-tpl-modal-host .wa-tpl-actions {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-inline-start: auto;
}
#wa-tpl-modal-host .wa-tpl-actions .wa-tpl-btn {
    min-height: 44px;
    padding: .35rem 1.05rem;
    border-radius: 999px;
    font-size: .88rem;
    font-weight: 700;
}
#wa-tpl-modal-host .wa-tpl-actions .wa-tpl-btn i { margin: 0; }
#wa-tpl-modal-host .wa-tpl-list-head {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: .95rem 0 .4rem;
}
#wa-tpl-modal-host .wa-tpl-list-title { font-size: .92rem; font-weight: 700; color: #334155; }
#wa-tpl-modal-host .wa-tpl-empty {
    display: flex;
    align-items: center;
    gap: .4rem;
    padding: .75rem .8rem;
    font-size: .84rem;
    color: #64748B;
    background: #fff;
    border: 1px dashed #CBD5E1;
    border-radius: 12px;
}
#wa-tpl-modal-host .wa-tpl-list {
    display: flex;
    flex-direction: column;
    gap: .4rem;
}
#wa-tpl-modal-host .wa-tpl-row {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .5rem .7rem;
    background: #fff;
    border: 1px solid #E2E8F0;
    border-radius: 10px;
}
#wa-tpl-modal-host .wa-tpl-row-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: .1rem;
}
#wa-tpl-modal-host .wa-tpl-row-title {
    font-size: .9rem;
    font-weight: 700;
    color: #142E55;
    overflow-wrap: anywhere;
}
#wa-tpl-modal-host .wa-tpl-row-body {
    font-size: .78rem;
    color: #64748B;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* «بدون اسم الشركة» — the one thing about a stored template an operator must
   see before using it, so it is a chip on the row and not a hidden field. */
#wa-tpl-modal-host .wa-tpl-tag {
    flex: 0 0 auto;
    padding: .1rem .5rem;
    font-size: .68rem;
    font-weight: 700;
    color: #96650A;
    background: #FFF6E8;
    border: 1px solid #EFDCB4;
    border-radius: 999px;
}
#wa-tpl-modal-host .wa-tpl-row .wa-tpl-btn {
    min-height: 40px;
    padding: .25rem .65rem;
    font-size: .8rem;
    font-weight: 700;
}
/* The three row actions wear BOOTSTRAP's own variants (outline-success /
   outline-secondary / outline-danger) — this block sets their SIZE only. A
   hand-rolled red here would spend the very tokens the send-device failure
   state owns, and the "the loud red appears exactly once" pin in
   tests/pester/WhatsAppReadiness.Tests.ps1 could then no longer tell a second
   status state from an unrelated button. */
@media (max-width: 575.98px) {
    /* A phone keeps the sheet: at this width 85% is a lost column, not air. */
    #wa-modal .modal-dialog { max-width: 100%; }
    #wa-modal .wa-mark { font-size: 2rem; }
    #wa-modal .wa-title-name { font-size: 1.2rem; }
    #wa-modal .wa-fact { grid-template-columns: minmax(0, 7rem) minmax(0, 1fr); }
    #wa-modal .wa-ready { flex-wrap: wrap; }
    #wa-modal .wa-foot { flex-wrap: wrap; }
    #wa-modal .wa-send { width: 100%; justify-content: center; }
    /* The message-tools band stacks: the picker takes the full width and the
       two link actions share the next line, each still a 40px target. */
    #wa-modal .wa-template-select { max-width: none; flex: 1 1 100%; }
    #wa-modal .wa-msg-tools .wa-all { flex: 1 1 0; justify-content: center; }
    /* The manager's row actions move under the text rather than squeezing it
       into an ellipsis on every line. */
    #wa-tpl-modal-host .wa-tpl-row { flex-wrap: wrap; }
    #wa-tpl-modal-host .wa-tpl-row .wa-tpl-btn { flex: 1 1 0; }
    #wa-tpl-modal-host .wa-tpl-actions { margin-inline-start: 0; width: 100%; }
    #wa-tpl-modal-host .wa-tpl-actions .wa-tpl-btn { flex: 1 1 0; }
}


/* ---------------------------------------------------------------------------
   mj UTILITIES + the two shared bits lifted out of page templates
   (2026-10-05, mj ratchet debt: these were DECLARED only inside pages, which
   is what tests/pester/MjNamespace.Tests.ps1 flags. The ratchet exists because
   a rule copied into three templates is three rules that drift apart; these two
   were byte-identical in three pages each.)

   .mj-min-h-0  - min-height:0 on a flex item, the fix for the clipped-child
                  bug (an item's automatic minimum size pins it at content
                  height). Was duplicated VERBATIM in clients_main.html and
                  employees_main.html.
   .mj-stats-sep - the «|» between figures in a stats strip. Identical in
                  clients_main.html, employees_main.html and
                  suppliers_main.html; only the CONTAINER differs, and the
                  container is not what makes it this component.
   --------------------------------------------------------------------------- */
.mj-min-h-0 { min-height: 0; }
.mj-stats-sep { color: #e2e8f0; margin: 0 .5rem; }

/* .mj-cell-copy - a grid cell whose text the operator can select and copy.
   The POINTER half is the same on every grid, so it is shared; the selection
   COLOUR stays with the page (clients blue, suppliers their own), which is what
   a page-scoped adaptation is for. */
.mj-cell-copy,
.mj-cell-copy * { cursor: text; }

/* .mj-col-check - the checkbox in EVERY grid's column-visibility menu. The grid
   scripts build the markup (MarjanGridLayout and each page's column menu), so the
   class is shared and its box belongs here; it was declared in inventory's
   <style> only, which made the other seven grids' checkboxes unstyled whenever
   that page was not on screen. The WIDTH is left to each page's modal scope. */
.mj-col-check { cursor: pointer; }

/* .mj-page-close - the page-close X in partials/page_header.html, a SHARED
   partial included by 24 screens, with the click handled by the shared
   marjan-ui.js (data-mj-close-page). So the class is shared by reachability,
   not by one page's taste - and it was declared NOWHERE: its 44x44 geometry
   lived in an inline style attribute on the button, which is why the 2026-10-05
   mj sweep could not see the component at all (it only ever read selectors).
   44px is the Fitts floor the home button on the same row already uses. */
.mj-page-close {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* --------------------------------------------------------------------------
   26. mjSpellcheck — the squiggly line under a wrong word (2026-10-05)

   The marks live in a MIRROR behind the textarea (mj-spellcheck.js builds it
   from the textarea's own computed metrics), so the squiggle is a normal
   text-decoration we own: a browser spellcheck squiggle cannot be styled,
   cannot carry a correction and cannot be tested. `text-decoration: underline
   wavy` is the squiggle; nothing animates and nothing reflows.
   ------------------------------------------------------------------------ */
.mj-spellcheck-mirror {
    color: transparent;
    user-select: none;
    overflow: hidden;
}
.mj-spellcheck-mirror .mj-spellcheck-bad {
    /* the word itself stays invisible (the textarea's text shows through);
       only its UNDERLINE is painted */
    text-decoration: underline wavy var(--mj-spellcheck-ink, #d92d20);
    text-decoration-thickness: 1px;
    text-underline-offset: 3px;
}
/* Away from the field the marks fade back, so a screen full of advisory
   underlines is not shouting at someone who is only reading. */
.mj-spellcheck-mirror.is-idle .mj-spellcheck-bad {
    text-decoration-color: color-mix(in srgb, var(--mj-spellcheck-ink, #d92d20) 45%, transparent);
}

/* ---------------------------------------------------------------------------
   THE ZOOM DOCK — ONE rail, one shape, for every تكبير/تصغير خط الجدول
   ---------------------------------------------------------------------------

   Owner directive 2026-10-05: «cant we just preserve a standard? the zoom in
   zoom out need to be in the same placement as in inventory page».

   THE STANDARD, in full, because a standard nobody can recite is a standard
   that gets re-invented:
     * a 46px VERTICAL RAIL riding the reading start (the RIGHT edge in this
       RTL document) of the content it resizes - the INVENTORY grid's
       `.grid-side-toolbar`, which is the reference every other surface is
       measured against;
     * ONE column, the pair at the BOTTOM (`justify-content: flex-end`) -
       below the rows they resize, beside the eye already reading them. A rail
       that also carries navigation controls stays TOP-anchored, because that
       is a different rail and mixing the two conventions is the defect
       `tests/pester/GridDockPlacement.Tests.ps1` exists to prevent;
     * the same two buttons, the same captions (تكبير خط الجدول /
       تصغير خط الجدول), the same Bootstrap-Icon glyphs (bi-zoom-in /
       bi-zoom-out), and a 44px square target (the Fitts floor).

   WHY IT IS A SHARED BLOCK AND NOT A FOURTH COPY. Three screens each carried
   their own version of this rail: inventory and the item card styled
   `.grid-side-toolbar` in their own <style>, clients REBUILT it as
   `.clients-zoom-dock` because (its own comment says) "inventory's dock CSS
   lives in inventory_main.html and does NOT travel with this screen", and
   suppliers left the pair in its horizontal toolbar entirely. Four copies of
   one shape is how they drift apart, and they had.

   WHAT IS *NOT* UNIFIED HERE, on purpose: the BUTTON SIZE and the ZOOM
   LADDER. Inventory paints its dock buttons 2.1rem and its ladder in px;
   clients and the item card want the 44px Fitts floor; suppliers steps 11-22px
   while the item card and billing step a 0.75-1.5 FACTOR. Those are
   per-surface values with their own pinned tests and visual baselines, and
   forcing one number on all of them would change five screens to satisfy a
   request about PLACEMENT. `.mj-zoom-btn` therefore sets the 44px floor, and a
   page with a tighter rail outranks it with its own scoped rule - the same
   arrangement the item card already uses deliberately.
   --------------------------------------------------------------------------- */
.mj-zoom-dock {
    width: 46px;
    flex: 0 0 46px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    gap: .25rem;
    padding: .25rem;
    background: #f8fafc;
    border-inline-start: 1px solid #dfe9f3;
    user-select: none;
    overflow: hidden;
}

.mj-zoom-dock .mj-zoom-btn {
    width: 44px;
    height: 44px;
    min-height: 44px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: #2167c5;
    border-radius: 6px;
    border: 0;
    line-height: 1;
}

/* The icon's own size lives here, not on a `fs-6` utility in the markup: the
   utility is what the three historical copies disagreed about (two carried it,
   one did not), and a size that must agree across screens belongs in one
   place a stylesheet can hold. */
.mj-zoom-dock .mj-zoom-btn i { font-size: 1.25rem; }

.mj-zoom-dock .mj-zoom-btn:hover {
    background: #eaf3ff;
    color: #174e97;
}

.mj-zoom-dock .mj-zoom-btn:focus-visible {
    outline: 3px solid #2167c5;
    outline-offset: 1px;
}

/* The مرسال card, greyed for a row that carries no phone number (owner
   directive 2026-10-05). GREY, never hidden: a control that disappears from a
   toolbar the operator is looking at reads as a broken screen. It also leaves
   the tab order (aria-disabled + tabindex -1, set by the script), so a
   keyboard user is not offered something that refuses. The reason rides on the
   title, set by the same script, from the one sentence it owns. */
.side-card.is-disabled {
    opacity: .45;
    filter: grayscale(1);
    cursor: not-allowed;
    pointer-events: none;
}
.side-card.is-disabled:hover {
    border-color: inherit;
    box-shadow: inherit;
}
